Blazor in more depth - Ryan Nowak & Steve Sanderson

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
okay so I know people are still coming in with us fine we don't mind we'll just get started now because we've got so much stuff we want to pack into sixty minutes you know is we can't win no time for pleasantries or whatever so I'm Steve and I'm Ryan I'm the other guy yes the other guy all right so we work together we're we're both at Microsoft on the SP net team and we are to the people who have mainly responsible for designing what you see in the blazer programming model today and Ryan's got extra special powers when it comes to compiler stuff so if the stuff you don't like about it and you know who to blame and we don't care but you know you can come and tell us anyway yeah if you found a bug you can ask us and I'll tell you whose fault it is if it's Steve's if it's mine yeah it's probably yes I'll give you a clue it's yeah all right so we're gonna get started and in this talk you're gonna get a bit of a two for the price of one thing because we're gonna go through two different subject matters that we think will be really interesting to you if you are a little bit more on the advanced end of the blazer developer spectrum so I'm just gonna get started pretty much straightaway with with that half of the top that I'm gonna focus on and now and I want to mention that I haven't seen yours and you haven't seen mine yes so if an argument breaks out it is genuine because this is all gonna be new to me yeah it may flatly contradict each other so my part of the talk so imagine that what okay firstly you know that blazer is a component or intered framework right you build components and you construct your application after out of that if you're a little bit more advanced you may also know that part of the design of this system is to let you put your components in packages and reuse them and share them across projects and to even publish them to NuGet and make yourself into an open-source superstar so that's what I want to focus on now I want to give you some tips on how you can build a really nice Razer class library that packages up Blaser components that you can share and it might be that you just want to share them with other teams within your company it might be that you want to share them with the public in general or even if that's not the case it might just be that you want to make things really good for your future self so that when you come to another blazer project in the future you can make use of stuff that you made in the past in the most sort of convenien we're and you can think how I was a genius for designing it in that way and it's so nice to reuse now so the scenario I want to cover of the example case that I'll be using for a Razer class library is to do with building charts you know graphs and pie charts and all that kind of stuff and I think it's a good fit for a Razer class library because you wouldn't probably expect the Blazer framework to have built-in graphing features or hope you don't but you probably also wouldn't expect to write that code yourself by hand you probably think that you're gonna get some third-party package that gives you graphing capabilities so that's what we're gonna do for the next 20 25 minutes or so and the particular way that I want to approach this is by showing you how we can wrap up some functionality that's provided by a third-party JavaScript library so there's this thing called Chartist j/s which is a really nice way of making graphs and charts but it's for JavaScript and what if we want to make a really nice set of Blazer components that follow all the kind of most convenient API patterns so that I users can have a really fun time making some charts and graphs now some of you have a question I heard that I heard that web everson web assembly is like antimatter for JavaScript that it like eats JavaScript is that true just what assembly eat JavaScript yeah yeah well you can use JavaScript from web assembly what else do you mean I guess I mean like there there are obviously lots of like JavaScript components that are out there that are still useful oh yeah yeah so like all that sounds like all that's needed is somebody has to make a blazer wrapper for that JavaScript component us and then people can use it without even knowing you know how its implemented yeah that's absolutely true yeah so if we give some nice dotnet API wrappers to whatever kind of underlying functionality we want to use then you can consume that very nicely as a.net developer and he didn't really need to know okay how the underlying implementation worked so how can we do it well I'm gonna give you eight steps to success and we will start with a very basic one which is just how to create and run arrays a class library in the first place so over in my IDE here you can see that I haven't really got started yet I've got this empty solution with just this folder you know I want to add a razor class library you may have done this yourself but if you haven't you can do it through the visual studio project creation dialog or you can do it on the command line that's a way of doing that there but I mean vs so I'm going to start searching for razor and we get razor class library and we'll go on from there I'm gonna put it in a particular place and I'll call it blazer chart East like that because that's the thing that I'm doing and then when I have them create there's one option that I want to point out to you yeah there's a bunch of options but the one that I think is the least obvious what it even means is this one support pages and views so what that means is what we're talking about when we say pages of use here our MVC views and razor pages so those are not laser technology these are a different type of razor class library and that's not the one that we want because we want to build something that works in all Blaser environments including on web assembly so we don't want to tie it to razor pages or MVC views so we're gonna leave that box unchecked that's just the one tip that you need to know okay so we create our razor class library and then we'll have a look and see what is in that and what we do find in there is a bit of example stuff so we've got a razor component and we've got some static files as well because razor class libraries can contain components as well as static assets that you will use from those components okay now before we do anything useful with this we need to have some way of running it and I can't run array as a class library on its own it's just like a dotnet class library we need to have some kind of application that it runs inside and so what I usually like to do when I create a razor purse library is to create a set of sample projects that show how it can be used now do we want to use a blazer webassembly sample project or a blazer server one well if we are being ambitious and we want to make the best possible cost library we want it to work in all environments so how about we do both so I'm going to add a couple of sample projects right now and in this talk I'm not really going to have time to type out everything by hand so I'm going to use git to jump backwards and forwards to different points in the history of my project so we can accelerate our progress a little bit so now you'll see that I have got this samples directory alongside my source files where I've got two different actual runnable projects webassembly and a server one now I'm just going to start of this web assembly one first before I tell you how this stuff is set up so I'll start that going in my browser right now and when that comes up you should see it's pretty familiar it's the typical sort of file new project blazer thing what have I changed about well I've removed a couple of the pages because I don't really need them I just want to render one component and I've also got this little bit of a text up here saying what environment I'm running in and the way that works if we go to our where as that's probably in is it nav menu it is okay so here's the implementation for that I'm using this runtime information API to check where we're running and displaying either web assembly or server okay now I've also got a server project and if I start that up you'll see what comes out of there should also be very familiar to you because when that appears you'll see it's exactly the same as what we had before except this time it says M server so to save myself a little bit of time I don't want to have to keep copy and pasting code between these two samples I've actually referenced the web assembly sample from the server sample maybe you didn't know you could do that but you can and then inside my host page on the server here the actual root component that my server page is rendering is the same app that's coming from the web assembly sample so I'm treating my web assembly sample as if itself was a class library and I could just reference one of my applications from another it just means these two applications stay in sync with each other and I don't have to duplicate any code okay but what I actually want to do is use some of the components from my Blaser Chartist project so I'm going to go to this index page and I'm going to get rid of that and I'll try putting in a blazer Chartist component what okay and that is what has just come out of my project template okay so let's start that going and we'll see what that renders well by default it renders this little bit of text which is not super interesting if you're wondering where that comes from well that comes out of our component one RAZR file so that's doing exactly what you might expect with the exception that you'll see we've also got this CSS class so what is that CSS class well that is wired up to this entry in our style CSS file so you see we've got a particular border and a background image and stuff so you might be wondering well why is that unstyled then why didn't the CSS stuff apply to it and the answer is because I didn't reference this style.css file yet so if I'm a consumer of this razor class library I need to choose which of the static assets in it I want to consume and where in my page they should appear so I'm adding that reference with this particular URL format now and if you're wondering why the framework doesn't that ad doesn't add that for you automatically that is exactly your question okay so the reason we did used to do that by the way we used to add the references automatically but we found it wasn't practical in the real world because you need often to let your user choose which of the static assets they want to use and choose which order those references should go in and the framework can't know that sort of thing so if you're the class library author you're going to have to choose for yourself and document what references people should add so now anyway you can see I've added that reference and my styling shows up cool all right so that's the very very basics now how are we going to bring in this third-party JavaScript stuff that we want to use well I've already done it so let's jump forward and I'll show you how that could be achieved and now in my project you'll see I've got these files Chartist min dot CSS charter Stockman das and I got those by literally just downloading them from the Chartists project website and putting them into my project and that it's not really the best way to do this because now we have to deal with figuring out the versioning manually and updating manually and stuff and I'll show you a better way we can approach this later but we're just getting started and I've also got displays of chartres J s where I'm going to write my own code I've also got chart raiser and you can see in there what I'm doing is just rendering it a bit of fixed markup which are deleting and I'm also using a blazer feature to capture a reference to that div so that I can start passing references to that into JavaScript okay so I want to use that now to actually render a chart at the moment it just displays this this little message there that says tell charters to draw a chart so how do we make it draw a chart well we probably will go to the website for charters J s and see what the API looks like and they're going to explain it to us in terms of JavaScript and they will tell us that the thing you need to do to render a chart is something like this so you'll new a an instance of Chartist line and pass an element to it and also pass a bunch of data that you want it to render ok so as you can see it's just hard coded stuff right now but let's see can we actually call that in the first place how do we call this create chart JavaScript function and pass an element to it from our raiser component well we can do so using a pattern that you see a lot in Blazer applications which is that we use ref to capture a reference to the element and then inside the on after render lifecycle method if this is the first time we've rendered then we can use JavaScript Interop to call this method and we can literally just pass the element directly from blazer into JavaScript so if you're wondering why that should be on after render and not say uninitialized async the reason mainly is because we're trying to use this element and that element doesn't even exist until we've rendered is something that gets produced by the rendering process so we can't use it until we've rendered therefore we're going to put this into on after render there's another reason as well that I'll get to later on okay so assuming that stuff works now can we do does our JavaScript code get run and do we actually render some charts inside the browser let's see the answer is yes we are now successfully rendering a little graph which is cool okay so we've successfully brought in some third-party JavaScript and we've invoked it but so far we don't really have any proper API there's no way for our package user to specify what data is they want to render how can they give us their own chart data well let me show you so I'm going to define a few extra c-sharp class files to do this and I'll show them to you right now so you'll see I've now about this data directory here with a bunch of c-sharp files inside it for example I've got chart type which is any num I've got these two and I could add support for other things if later if I wanted I've also got chart data and chart data is you define a set of labels which the horizontal x-axis labels and we can also define a set of series that we want to plot and each series is something that's got a name and some data okay now I've also added on to my chart component some parameters so you can now pass what type of chart you want it to render and you can also pass an instance of chart data and then when we first create the chart I'm passing the type into JavaScript and also every single time this thing renders which includes every time the data on it changes we're also going to call this JavaScript method update chart and pass that data and the implementation on the JavaScript side looks like this you can see the code later I published it to get over I'll tell you about that and if you look inside the update chart you'll see it's is taking that data and it's passing it through okay so how do we actually use this now as a consumer well I'm going to go back to my index page and you see I'm not passing any data just yet but I want to pass some data so the sample date arrived got looks like this and as you can see I'm investigating a certain conspiracy theory that I'm working on at the moment which is that maybe the global oil crisis caused Trump to tweet more or maybe the route the causality goes the other way I don't know I haven't decided what my conspiracy is yet but I'm gonna look at the graph and try and figure out what it is okay so I want to render some charts now using that data so I can simply pass the data using these parameters that I've defined so all being well we should now be able to plot these two series side-by-side and compare them so back in the browser reload and you'll see we've now plotted these two graphs and we can try and figure out you know what the theory about the conspiracy is and you could argue there's no real correlation but I don't know I'm I'm still going with my conspiracy theory on that one I'm gonna punch up some of my examples ok had some Boris jokes to them okay yeah that sounds good just try to mention brakes here as many times together okay so we've got we've got a couple of charts that's great good for us well done but so far we've got a very very basic API which is not very good it's not very idiomatic for Razer if someone is consuming this they will not feel like they're having a particularly luxurious experience because they have to define all this code down here wouldn't it be nicer if we could do it in a mol razor-like way for example we can pass a separate parameter for the labels like up there and maybe instead of defining the series as an object literal we could have a syntax that looks like this series and then we could start defining stuff like that that would be more razer e so how can we make a better api like that well first thing is I'm going to go over to my chart raiser and I'm going to start adding an extra parameter so I'm gonna say you can pass some labels but because this is a nice API I'm going to say it's optional so I'm going to say if labels is not equal to null then we're going to use them and if the labels is not equal to null then I'm going to write them onto this data property like that so now it's optional if you want to pass all the data as one blob like this you can and if you want to split it up into separate parameters you can now I'm just over writing some data that the user passed in which is arguably not the friendliest thing but it's it's a reasonable approximation to what we want to do right now so now back over here I can make sure that I've rebuilt everything and then hopefully the labels property will show up in inter sense and I can start passing my labels in and I'm going to do the same thing down here as well and now I don't even have to define it there at all so that's a slight improvement next improvement is like I said I don't want to have to define this object literal anymore I want to be able to use declarative razor syntax to specify the series so how do we do that well in order for something like this to work we're going to need to define an extra component that's actually called series so let me show you how we could achieve that and make it interact with the parent component so I've added a new class now well firstly you can see this is what the the resulting API is going to look like you can see I don't have that object literal at the bottom anymore so I think this is a bit nicer and I'll show you some advantages of that in a minute but the actual implementation of it looks like this so you'll see I've got this class now called series dot CS and that actually is a component now normally when you create a component you do so using a razor file and that's great that you could totally do that in this case as well but arrays a component is just a class after it's been compiled so if you want to you can define it with a regular CS class file just the same the only difference is if you use a regular CS file you don't really have any way of defining the rendered markup because you're just using C sharp but that's fine because this is going to be a data only component it's not going to render any visible output it only exists to supply data to its parent so I'm showing you that we can do that with a CSV file if we want and the syntax inside there is more or less the same as before so we've again able to accept parameters for the name and the values we can use the same life cycle methods so when the parameters get set I'm going to build up one of these series data objects I'm also going to receive a reference from the framework to the owner the parent component which has being cascaded down and you can check the implementation later if you want to see exactly how that works and then when this thing gets initialized and added to the UI we're adding the series to the parent and when it gets removed from the UI we're also removing the series from the parent so as long as I've done everything correctly I should be able to rebuild and reload in the browser and everything will behave like before which it does but we've now got a nicer API and then one of the ways in which this is a nicer API is that the person consuming it can do stuff in a more programmatic a more declarative way so previously if I wanted to make my series toggleable on and off the user would have to write lots of code to add and remove stuff from data structures but if we do it with razor syntax it can be done declaratively instead so let's say that I want to have an ability to toggle Trump's tweets on and off okay so I'm going to take that boolean and I'm gonna say just using normal razor syntax if we want to show Trump tweets then we'll have that series there and if not then we want so it's really easy to use and to make that toggleable I'm going to simply wire that up to a check box that's bound to that property okay let's check that this really works so back over here I'm going to reload and then now you'll see we've got this check box and as I toggle it then there's that second day two serials will go on and off so that's the advantage of using a nice idiomatic razor type syntax is that the person using it can do all kinds of things to change what's going on at runtime without needing to know about your special api's okay another thing that we can do to make our API is even better what if I wanted to add some kind of tooltip to these charts or some extra CSS styling it's not completely clear how I could do that as a consumer right now I could try to do it as follows for example I could try adding a title attribute and some CSS styling that's hard-coded there let's see what happens if I try and do that right now well if I do that and I hit reload then what happens now is we get an unhandled error has occurred and if we look at the reason for why there's an error it's because objects of type chart does not have a property matching the name title so by default the person consuming your component could only supply attributes that you've declared a way of handling but if you're a high quality component author that wants to provide a first-class experience to your users maybe you're going to make something like this work so we can't anticipate every possible attribute that someone's going to supply to us but we can handle them all in some default kind of way so what I'm going to do is add an extra parameter to my component which uses this special API capture and mash values and that's going to let the framework build up a dictionary of all the extra attributes that have been supplied that we haven't defied defined any handling for and then I can choose wearing my rendered markup I want them to go so I'm going to use this syntax attributes with the @ symbol too because it's a directive attribute to add all these extra attributes to my rendered output so now that I've done that instead of the thing that I did before being an exception we'll see what comes up this time in fact it works and you'll see we've got that faint yellow background and if I put the mouse there you'll see that our little tooltip actually shows up okay so we're treating our users with a lot of care and allowing them to do whatever kind of thing they want they can use that can pass different combinations of parameters they can use razor syntax to surpass data declaratively and they can use arbitrary attributes in order to you know control the rendered output quite nicely okay so we've got a nice API now if we're going to continue with this sort of goal of providing the most first-class service we can then how about we make the intellisense better as well I'm sure many of you know that with c-sharp you can add in line documentation that will show up in intellisense what what not everyone seems to know is you can do that with Razer components too so right now in the IDE if I'm consuming this stuff and I hover the mouse of a chart what does it tell me nothing really it just says it's a chart have fun with that and if I hover over labels it just says so it's an enumerable of string great there's just no real information being given to me but the editor supports XML comments so I could go to my series class right now and I could use this triple slash syntax like that and then I could fill in a bit of summary text and you can see we can have these strongly typed references in the documentation from one class to another but it's not just CS files I can also do this from a razor file as well for example let's say I want to add some information about this this labels parameter so I'm going to drop in some text for that you know it's the x-axis category labels okay so now I've done that if I go back over here and we try doing this stuff again I'll hover of a series and you'll say now it's got that information that I just added with the nice names first reference and if I hover over labels we'll get this message that we've added so it's a very small and simple thing that you can do but it hopefully gives a better experience to people consuming your library okay well okay let's think about compatibility a little bit so far I've been running exclusively on blazer webassembly but does this stuff actually work if we're running on the server even though we're doing JavaScript Interop and what do I even mean by it compatibility with pre-rendering well let me explain that to you well firstly let's check does this work if we run on the server so remember that we've got this server sample I'm just going to get that to run now and you remember I've made I've wired it up to the web assembly sample so that it always renders the exact same output so if I start that up now you'll see we're running on the server now but we have the same behavior as before the same toggling the same styling the same tooltips and everything and we can see an easily test that our component works equally well on the server and if you're wondering what it takes to make a component that works perfectly on the server - there's pretty much just one rule that you need to follow which is to do with JavaScript Interop when you're running on the server or the JavaScript Interop is taking place over a network connection and therefore it can only work if it's asynchronous so if you're using JavaScript Interop api's make sure you only use the async ones and that's quite easy to do because by default that's the only thing that we actually let you do for this exact reason there is a way of not doing it and I'm just telling you not to override us so you can see this j/s interface here it's got invocation can invoke void async if you use those then you are safe your code will work on the server as well there's a way of doing synchronous interrupt which I will not show you because I don't want you to do it and if you do that then it will not work on the server so just don't what about this pre-rendering thing what do I mean by that well when you're running the blazer application particularly if you're running on the server but you can do this with web assembly as well we also support server-side pre-rendering so the actual initial markup of all your components gets resolved on the server and sends down to the browser now my application doesn't actually do that just yet if I look inside my rendered HTML you'll see at the root here we're rendering this app element and there's not really any content inside there there's this massive comment block which is how the server and client stuff gets wired up together but there's no pre-rendered marker in there if we want to do server-side pre-rendering it's pretty easy you can go to the component tag in your host project and change this render mode in fact the default render mode is server pre-rendered but I turned it off just so that I could demo this like that if I change it into server pre-rendered mode then we're going to get slightly different output from the web server so if I come over here and hit reload then now instead of that comment block or as well as the comment block we also get all the initial markup that's rendered from our components including stuff like this which you'll recognize is coming from our chart component and everything works together at runtime so when I reload we do the browser initially sees the pre-rendered HTML and then the interactivity takes over so what does it take for you to ensure compatibility with the pre rendering mode well there's basically just one rule for that as well which is also to do with JavaScript Interop because when you are doing JavaScript Interop you need to understand that that does not make any sense during pre rendering because during pre rendering there is no browser therefore there is no JavaScript therefore you can't interrupt with any JavaScript so if you want to do jeaious Interop make sure you put it in the on after render async method and not inside on in it or on parameters sir because all the life cycle methods run during pre rendering as well except for this one the on after render ones do not run during pre rendering therefore that's a safe place to do JavaScript interrupt and thankfully that's also the only useful place to do it in many cases so as long as you follow that rule that your class library will be compatible with all three of these environments okay right then we're down to our last couple of tips that I want to give you now the next one is to do with how we're dealing with these static resources so at the moment you will remember that I put these Chartist files directly inside my project excuse me which is fine it's a very basic way of doing things but we're missing a few features here like how do we get updates to newer versions of it also how do we do things like bundling and minification of our resources and what if I wanted to write my Blaser Chartist JS file inside in typescript rather than JavaScript well to do that sort of thing we would want to have something like a web pack and typescript build process now I know many of you may be thinking hang on a minute web pack and typescript the whole reason I'm using blazer is to avoid those kind of things right so that's kind of the point right so if you're the component class library author then you're the one who's taking on the complexity of dealing with things like a web pack build system so that your users don't have to you're the hero in this story so let's be the ones who add the web pack build system and then we can get the benefits and our consumers don't have to even know what's going on with that so I'm going to jump ahead again to a web pack build system and if we look inside the project now and we dismiss these helpful notifications then you'll see I don't have a double double root directory anymore I've removed it what I've got inside now is this static assets folder doesn't have to be called that I've just made up this name and inside there I've got some stuff to do with typescript and web pack for example I've got blazer Chartist TS and that is a typescript based version of the same code that you saw me right before it's a bit longer because it does all this extra type checking stuff and it's classes and things now it's also quite cool because it's importing the charts Chartist library from NPM so don't need to have a separate reference to Chartist anymore I can just declare what version of Chartists I want to have inside my package JSON file and I don't need to have its actual files inside my project so that's nice then in order to actually build this stuff I've added a simple web pack config if you're familiar with web pack you'll know this is a fairly straight forwards one if you're not then you can just find this code and copy and paste it later it's pretty straightforward it's just using the typescript loader so that it knows how to read typescript and you will be able to compile that okay and then finally I've chosen just to be a bit ambitious to integrate this with the default msbuild build system you don't necessarily have to do that if you want you can create a web pack in typescript build system that you just manually invoke from the command line you can just go to command prompt and start running MPM install and web pack build and things like that on the command prompt but I wanted to see what it would look like to really integrate that with msbuild so I've made these really awful targets files that do things like if the package lock has changed we'll run NPM install automatically if your target script sources will rebuild and will also automatically copy the CSS out of the Chartists to package into our project so if I build that now that you'll see this dub-dub-dub root directory has now appeared and if we look in there this is the output we've copied the CSS out and we've copied this javascript file out and this is bundling everything together so I've got my code in here and I've got all the Chartist code and so if I just run my project again now then everything should work the same as before if I reload that everything is still working great that's wonderful and that's coming out of the build output but you may notice a couple of things like what about the fact that this is not minified right now well that's going to be not optimal in production well don't worry because we can set up all the necessary parameters for that so if I change myself into really release mode now and I do a build on this which I will in momentarily then this time because of all those targets it will say ah something has changed therefore I'm going to rerun webpack which I wouldn't have done otherwise and we're doing it in production burg now so you'll see we've now got a minified version of the output there so we've automated all these manual steps that we would normally have to do which is pretty nice I would say it's an absolute ordeal trying to figure out this stuff so I would try and find some example that that you can copy from if you want to do this kind of thing and of course I'll be publishing the code for this project if you want to check that out later ok so that's adding a web pack and typescript build system we've got a nice class library now it's got a nice API we've got a nice build system for it and finally we're going to want to actually publish it and share it with the world you can of course do that manually you can run dotnet pack on the command line you can get a new get package and you can manually go and start uploading that and you'll get a dog if you want to but it's a bit of a pain to keep doing that every time somebody sends you a PR and you're merging you then have to go through this process all over again wouldn't it be nice if you could automate this process all over if you could continually deploy every commit as a new package to new get dog that used to be really hard to do but it's actually quite straightforward to do now so if you've not seen how this sort of thing looks let me show you there's probably several ways you can do it you could do it with github actions or some other kind of CI system I'm going to do it with as you'd ever right now but just just because I'm familiar with that and because also it's free if you want to as long as you're not building your project too many times which we're not so my my definition for how that works is this yeah mold file and it's got a set of steps in it it says first we're going to choose which net SDK we want to use we're then going to run a build to make sure that everything is happy including all the samples and then if that passes will run dotnet pack to produce the actual package file and assuming that all of this stuff has succeeded so far and that we're on the master branch then we are going to push that up to new gap dog so when you create one of these config files like this probably buy a copy and pasting an example then you can go and configure that with Azure DevOps so you create a project on agile devops and you point it to your source files on github and it will find the yum or file in there and it'll say do you want to use this as the build definition and you say yes and once you've done that it will start watching for all the commits that go into your project and whenever a commit occurs it will run that pipeline and build your stuff so you can see it's been building every time I've done a commit on this and if we look inside there you'll see all the different steps within my job that I've configured such as the one that automatically publishes two new gap dog here you go this is where it's actually pushing this file to nuga okay so that being the case if I go and look at my project page on new get dog you'll see that we've got all these different versions of it that have been automatically deployed to new get every time I've done a commit and I'm actually quite scared the people have start to use this thing already it was not intentional this is just a demo and it's very limited functionality bit everybody go use it yeah send me books I'm going to disable the issues on get up so you can't okay so there you go those are my recommended eight steps to success as a robe razor plus library author I hope that's useful to you if you want to get the code for this example then you can go to this URL and obviously you'll find all the stuff that so with that I'm going to hand over to Mike link hmm he's gonna whack you with some other stuff I'm gonna trip you with this power hammer here we go okay great we're alive here so Steve mentioned Steve introduced me I'm Ryan I'm you know of the two of us maybe the compiler focused person or this sort of compiler whiz kid or maybe mad scientist is a more appropriate term and I wanted to talk to you all about some compiler stuff mostly because I think it's cool and I think it's interesting so hopefully you do too so about me developer and architect and asp net core I've worked on pretty much everything in asp net core for the last couple years really had the pleasure to be involved in the blazer effort and maybe spent about a year to a year and a half working on compiler and language features there you can find my presentation materials from this and other talks at that location after this is done so agenda for today I'm going to be talking about I'm going to spend 30 seconds you know saying what is a compiler just in case anybody doesn't learn that in computer science school pretty easy to grasp what's special and unique about the Razr compiler or what we're doing for blazer and then we're gonna look at some code and we're gonna try and put it under the x-ray and understand it a little bit and see examine maybe in detail some language features or what the compilers role law is or what the steps are you won't need this information you might you might be somebody who likes to learn this way you might be somebody who's like oh man this is blowing my mind if you're the kind of person that likes low-level concepts I think you'll appreciate it and enjoy it I hope it's fun and it's interesting for you even if it's not useful but I know I know some people out there really like to learn things from first principles so maybe maybe this is helpful for you I'm not going to promise that it's not but I do hope that it's fun and and so in order to talk about what to do today I'm gonna date myself pretty severely so you can choose you can choose your metaphor for what we're gonna look at this today I've got I've got two options for you we're gonna look at the code and try to see the high-level details that are buried in in all the code or this is either gonna be like an old time cartoon where somebody walks in front of an x-ray machine or gets electrocuted and you get to see all their bones and see all the things inside so simpson's and matrix may be dating myself a little bit there but that's kind of what I hope you're gonna get out of today so what is a compiler what does it compiler typically do well there's a set of steps that a compiler typically goes through you start with the source code or the things that you typed in or libraries that you pull in the compiler is going to read all that text and it's going to build an understanding of that we typically think about that as a syntax tree or like a like a sort of tree representation of the code that you've you've put in most compilers nowadays are then going to transform the syntax trees into an intermediate representation the intermediate representation is going to be a little bit closer to what the compiler outputs and usually is a useful data form a more useful data form than a syntax tree for what the compilers job is and then lastly the compiler produces some output after running a series of transformations over the intermediate representation so that output could be native code it could be IL it could be JavaScript in the case of the typescript compiler or in the case of the Blaser compiler it could be c-sharp so introducing the Blaser compiler or the razor compiler they're kind of one in the same the job of the razor compiler is to take your C sharp code your compiled assemblies that are your references and your CS HTML or your dot razor files and it actually produces c-sharp on the other end we don't produce dotnet IL directly we produce like valid c-sharp code that could actually be edited and compiled by the vanilla c-sharp compiler so walking through those steps from the point of view of the razor compiler we're gonna read your CS HTML or razor files we're gonna build those into syntax trees these syntax trees don't just represent one language which is kind of spooky because an HTML document or a CS HTML document or a razor document has got that mix of c-sharp and razor so we actually build syntax trees that contain multiple languages and sort of embed in transition between those in razor you can have c-sharp in HTML in c-sharp in HTML you can have nesting then we turn those syntax trees into an intermediate representation our intermediate representation in razor and in Blaser actually is pretty close to a generated c-sharp code it's pretty close to c-sharp class so RRR our represents structural concepts like classes and parameters and properties and methods as well as representing output concepts like markup or expressions or binding or things like that so your your razor and blazer level primitives are expressed in the IR or represented in the IR as well as a bunch of C sharp detail stuff like what is the name of the class that's going to be generated and then lastly we output generated c-sharp code so we literally write c-sharp files to disk and then run them through the see the normal c-sharp compiler in order to produce something that's runnable so some things that make the razor or blazer compiler kind of special and kind of unique and kind of tricky we have to parse and recognize the structure of all the HTML that you've pasted in some people may know that a difference between CS HTML and razor is that CS hTML is text so when you have an MVC view or an MVC razor page or something like that the runtime is actually treating the HTML as text it knows very little about the structure of that HTML and you can do all kinds of invalid stuff or I guess maybe not well-formed stuff is a better more precise term and the compiler and the runtime are happy with that because they treat the output as text not as elements or as a tree whereas in Blaser everything we create is ultimately a tree or like an instruction kind of thing so in Blaser your HTML actually has to be correct and well-formed and the compiler needs to be able to understand all of the structure of everything that you type in my friend AJ on the Razr team quick shout out to him did a massive amount of work in order to bring up the HTML parsing support up to scratch for Blaser when we started the blazer effort very early on when it was an experiment Steve's first prototype just grabbed a an external nougat library which one was it yeah we use angle shop thank you sir we grabbed angle shark which is a great library yeah Steve grabbed angle Sharpe off you get it's like I could do this and we had to retrofit that for the real compiler but for a long time we sort of continued in this mode of like while we we bolted this external HTML parser on to the compiler had lucky an adapter layer so that was a massive amount of work to ship blazer probably took one engineer about like a year or two year and a half to bring that up to scratch in terms of understanding C sharp and razor you've got your embedded C sharp statements in razor we understand like just enough C sharp to get by we're kind of close enough for jazz the reason why we sort of take a minimal approach to that is if you think about it like C sharp is changing every release C sharp is having stuff added every release or new options or new flavors of syntax we don't want to try and understand them all inside raisin if you don't have to we don't want to try and keep up with all of the C sharp changes inside of inside of the C sharp language if there isn't a need to do it in razor so you can sort of think about when the parser is looking at the document we find an @ symbol and we basically try to skip over the rest of that text without understanding it deeply because we don't really need to we don't really benefit from it it's not part of the programming model which helps us there are a whole bunch of c-sharp constructs that we need to understand in detail like for each and if and things like that so if you look at blazer you like writing blazer code and you're gonna have an unordered list you say ul for each or ul app for each we have to understand the syntax of for each enough to represent that as like a for each in the Razr code base in order to do the correct thing lastly what's special about the parser is that we break some rules I did mention that I'm a mad scientist there's a really interesting case that we'll look at today about how we break the rules we need to know basically all the components up front in order to do correct parsing the reason being that the syntax of attributes is different depending on whether or not something is a component or an HTML element so when we look at your text we need to know is this a component or is this an element in order to do the correct thing it's not really a good language design practice but hTML is not really a traditional language either and it's too useful for us not to do it about the code generation phase of razor or some some of the other sorts of things that we do we we get to know all of your components and what their types are and what the parameter types are as inputs the compiler we need to do that for a bunch of semantic features in in the compiler code generation like event handlers and event callback in those kinds of things we can't really know how to do the right thing from a code generation point of view unless we know the types of all the components generics because we support generics and generic type inference and razor and we will do an example of that make that very tricky because there are cases where we can't know or we have to rely on type inference or we have to rely on overloading this also kind of breaks the rules but it's it's a little bit of a useful thing for us to not do we'll see we'll also look at some examples of how our code generation strategies rely on overloading because I mentioned that we know the types of components and their parameters but we don't get to know the types of c-sharp expressions so if you open up a code block and you define a method and you start typing inside that method the razor compiler doesn't actually know anything about the types inside that method we just get to see the text which leads to some sort of leads to some sort of complicated machinations as a result which which will maybe bend people's minds a little bit so with that we're gonna do some examples that's all the all the slide where that I've got for you and to do that I have built the world's worst IDE so this is this is the world's worst IDE and it's for rate it's for razor and for blazer and it kind of only specifically does razor and blazer and the the reference the naming the naming here is a reference to God Bolt org which is a famous c-sharp and C++ compiler website that you should check out if you're interested in native code so if I flip through I can see that for this document I've got all of the things that I talked about I've got the syntax tree I've got the intermediate representation and I've got the generated c-sharp and I want to show a couple things about the syntax tree trust me that this this banner component has got has got a message and it's got a loudness so there's a banner component that we've defined here about a sad crying clown I mentioned there'd be jokes and so let's let's take a look at what's in the syntax tree for this and I realize that this is kind of hard to read but this is this is the code of the matrix this is the thing that the people were the compiler sort of interpret and like view in the debugger and the thing that I want to highlight here is like just how much structure and just how much detail there is for this so for this for this image alt a sad crying clown we don't even have a source element on this image you can see that we found a markup element we found a start tag and then we break up all of this text into individual little chunks so we've got the alt we've got the equals we've got the quotes we've got the values all of this stuff gets decomposed at the finest level of detail because we need that to represent in the structure of what we're going to generate as output if we flip over to what the output looks like there's a bunch of generated stuff to do with namespaces and file paths and things like that the output of that is this we've got this HTML string here and you can see that we've actually like taken all that stuff apart and then we've put it back together into an HTML string so you wrote you wrote a line of HTML in your razor file we rip that apart into like a hundred pieces and then we put it back together and we successfully put it back together into a single line of HTML Bravo that's brilliant so what's going on here why would we bother to do this the reason is we have two in the compiler take this apart in enough detail to a make sure it's correct be look at what are you doing in here is this all just static text or do you have some expressions or are any of these attributes conditional like if you put a if you put a boolean value in an attribute that makes that attribute sometimes appear and sometimes go away we've got to know all that stuff before we can turn this into a single markup string the reason why we would turn it into a single markup stream is well it's just more efficient that way so this add markup content becomes an instruction that goes to the runtime that says hey I've got a block a markup and it contains no dynamic content so when you're updating the Dom you can either add or remove this thing as one piece because it's always one piece and that's analysis that we do in the compiler likewise we can look at the invocation of a component and if I flip back over to the syntax tree here we can find this I'll just search for it so you can see that we've got a we call this a markup tag helper element so people might be familiar with tag helpers from server-side asp net core in the compiler we represent tag helpers and Razer and components and Blaser the same way but there's kind of a mishmash of features that they share and don't share so we represent the start tag of this we represent all the attributes to it so we've got an attribute here and we've got the text to satisfying clown that's being passed to it and those kinds of things so we basically take it apart and generate the same kind of structure here that we talked about on the lines of breaking the rules which I mentioned one of the questions that we get asked a lot about Blaser that I think is a really interesting question that has a really deep answer is for this loudness attribute if we compare those two you can find that here inside of here we can see that we've got an attribute value and it's text which is wrong so I think this hasn't updated just give me a sec here I think my IDE is bugging out but anyway what I wanted you to see is that we've got a c-sharp expression here inside of this we've got a c-sharp expression here inside of this attribute and this is actually an integer we know that this is c-sharp code if I wanted to come in here and instead of 11 I want it to say 10 plus 1 that would compile and that would be valid c-sharp code and you wouldn't see in the output 10 plus 1 you would see 11 because this is an integer and anything that you type in here is c-sharp code if you look at this message attribute instead I can type in here and it's just text it's not c-sharp right so I don't have to have a variable called a or a variable called sad or any of these things if I wanted to reference a variable here I would have to do I would have to do it like this I have to have the @ sign to get into c-sharp for this attribute and I don't have to have the @ sign to get into c-sharp for this attribute what's going on here the answer is well we a special thing for Strings so anytime you have a component parameter that's a string that effectively gets special handling in the compiler and in the in the tools in the editor in the IDE and the reason why we do that is because the alternative would just be terrible so imagine that you had to put the @ symbol in and then put in a c-sharp string every time you wanted to put a string inside of an attribute to pass to a component well that would look like this so you'd end up typing a whole bunch of like you'd end up typing a whole bunch of like quote @ paren quote and then your string and then the reverse on the way out and that would just be terrible so this is a case where we actually break the rules of language design like you get different syntax inside of this attribute depending on what the type is that it's being bound to it's not really a good language design thing but the results if we didn't fall if we followed the rules would actually be really bad so we do that for you and it's kind of complicated to make that work but we do it with love so it's because we value you likewise I want to look at this one this this is our this is another another example this just got some blocks of like HTML in it and I'm going to flip over to the intermediate representation which will probably be the most readable way to look at this and over here you can see that we've got a markup block and we've got a markup element and we've got HTML content and we've got all this kind of stuff here going down to this c-sharp expression for this second paragraph so our first paragraph which is an important announcement about peanut butter is all one is all one markup block as we can see or it's - its - markup blocks as we can see and then our second paragraph here our second P tag here is you can see that in the intermediate representation it's broken up into like this big series of things we have an element containing some texts containing an expression containing more text and the reason why this is basically is because of this date I'm now in here so you can see that putting some c-sharp into this big complex markup structure this is that optimization that I talked about before where when we can we represent chunks of more a static text because it's more optimal for the runtime it's more optimal for the generated code but when you put generated c-sharp in there we have to represent that as all of its full structure and generate that out into the C sharp with all of its full structure for the runtime to do the correct thing and these are some of the things that help Blaser work efficiently when you update something so like if you just kept refreshing this page or if you just kept reloading this page triggered rear Enders on this component the value of date time now would keep changing we wouldn't generate that whole string we wouldn't turn this entire paragraph into a string send that entire paragraph down to the client and then paint that entire paragraph into the Dom we would literally just update this inside of the CM tag and you're seeing that optimization and work and the difference between how this first section of text is represented in in the intermediate code versus this section of text that contains some dynamic code if we look at that in this c-sharp and the generated c-sharp you can see the same thing that we've got an add markup content here with this big block whereas for this other text we've got this add markup content here with like a lot more stuff going on too in order to capture that dynamic expression let's look at our next example and I'm actually going to jump out for these more more significant examples like this I'm actually going to jump out to vs code so we can have a better view of this but I've got a to do editor here and I've got this on submit handler so I've got it to a basic to do list sample with an on submit handler and I'm passing in this method to that on submit handler so when this - - editor submits itself I'm gonna add my item to my list pretty simple pretty straightforward stuff we know if you've done any basic blazer kind of stuff we're always telling people you know if you want a simple answer for how to pass commands and events and things around the type you're looking for is event callback event callback has got some special runtime smarts in it that will help the system know when to re-render the right stuff and it also handles a bunch of different things to do with acing so let me jump to UVs code where I have got this text sort of fanned out for you to look at so here's my index with my 2d editor and this is my generated code so I'm so here's our for each loop we understood the c-sharp and we were able to keep going okay here's our for each loop we're able to understand the c-sharp and sort of put that out into the into the output inside of this loop for each element we're adding an li tag which is represented by this this is the opening of the tag we're rendering the item and then we're closing the element and then we're going to call to the TD editor so when you call to another component in Blazer basically you can think of that like a function call to another class we're gonna we're gonna say okay I want a to do editor to appear here on the page and then the next set of things that are gonna happen with that to do editor is well we're gonna wire up all the parameters that need to get passed to it and that's what you're seeing in the generated code this very complicated very long set of string here is how we pass parameters to other components this is how I live my life scene so what is going on here can I help unpack all of this craziness the answer is that well this API add attribute is untyped so it's accepts an object which seems really flexible seems really good and it is really good unless you want to get good errors when you pass the wrong thing because whatever expression you type inside that on it submit attribute it's probably convertible to object right so because it's probably convertible to object but we know the type we know that the type is supposed to be an event callback we insert calls in the generated code to this type check method if you were to look at this type check method it's just the identity function so it's a method that take it's a static method that takes one parameter where we we tell it basically you only accept a parameter of this type and then it just immediately returns that value its entire purpose is there to make the compiler happy and make it so that you actually get an error about passing in the wrong type if you have done so which I would not advise the next thing that you'll see is there is this event callback Factory dot creates of system string so going back to our code example here our to new editor accepts an event callback of string but we are passing in a method we're passing in this on submit' method and it's a void method that accepts a string so the signature of that method is basically action of string right we need a way to convert an action of string into an event callback of string and that's what this event callback factory does the other trick that event callback factory does is it takes and takes an argument as the first argument that is the like receiver of the event this is basically the magic that makes blazars event routing and blazars efficiently rendering work we're passing some information into this event callback that says like who is the owner of this event or who's the person that's going to respond to this event so in our case the index form is the one that wants to respond to the event and so when we create an event callback we pass into this argument so that we're able to sort of bind that up and say when you dispatch this event dispatch it to this component lastly this event callback factory method is overloaded so in addition to accepting action of string it would accept action it would accept func of string to task and it would accept func of task and that's the magic for how all that sort of works in Blaser so if i came back over to this example and i said okay this is an async method now that would just work in Blaser and event callback factory is the magic of how that's done it's just overloading so there's four overloads of that method and then you're able to pass in those really flexible signatures and get what you get whatever you want going through there let's do one other example how are we doing on time are we almost out of time we yeah we've got one minute left we got one minute left so I'm gonna do one more example and I'm gonna go through it as quickly as I can so I've got a templated list here and my templated list is a generic component so my template of Lists has got a type parameter and it's a generic component and it is genericized on the type of item that I'm passing in so I can pass in a list of some item and then I can specify how I want to display each item that's that render fragment of item content and you can see that at work in here this kind of component is nice because you really get tired of writing this kind of boilerplate I want to show a list of something but I I have a special message if the list is empty do you want to write that all over the place or do you want to write a 30 line reusable thing so what does this look like when we use a template it lists without specifying the type and with specifying the type because blazer has type inference as a feature so let's take a look at that and this is in the generated code again so we can see here that we're calling template list and this is the example where we're not specifying the type we're passing in the items and we're passing in some child's content where we're basically just going to render we're basically going to just pass in that template that gets used in the body of it to render the item on the other hand this is not updated let me give this a kick real quick and see if it updates sorry about that I think about down there we go okay you can see that once type inference gets into play we generate this little static helper class here so there's a type inference class that shows up in your generated class and the reason is because in c-sharp there's no way - there's no way for us to call open component unless we know what type is going to go into here in the example where we're relying on type inference which is this one we don't know what to pass in there so we create a static method that then we call and then inside of that static method which is a generic method we can call templated list of T inside of here whereas in the case where you've explicitly specified the type of something we can just do it it's much more straightforward so Blaser has generic type inference it's really powerful it's also really hard to get right and kind of changes the semantics of a lot of things or changes the implementation of a lot of things in a really dramatic way cool yeah all right awesome I hope that you your minds are filled up with excitement about what you can do with internals of the compiler I think that's all we've got time for so I hope that's been fun and useful to you yeah if we if you got any questions come and chat to us but otherwise I hope you enjoy the rest of your day
Info
Channel: NDC Conferences
Views: 31,695
Rating: 4.9401994 out of 5
Keywords: Steve Sanderson, Ryan Nowak, Web, .NET, Blazor, SignalR-based, UI, APIs, API, Razor Class Library, NDC, London, 2020
Id: QnBYmTpugz0
Channel Id: undefined
Length: 62min 11sec (3731 seconds)
Published: Tue Feb 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.