 Okay, we're live Hi everybody, welcome to the October edition of the Wikimedia TechHack today. We have Roan Kekitao principal software engineer on the product team talking about resource loader tips and tricks You can follow us on the YouTube stream live That's listed here as well. And you can ask questions there as well as on the IRC office channel I'll be collecting answers throughout the talk and we will answer them at the end Talk should go about 45 minutes So we should have plenty of time at the end for questions and answers If there's anything that we don't get to or something that comes up later Feel free to let me know and we can talk with Roan to get that answer for you and Without further ado, here's Roan All right, everybody hear me and stuff. Yes, that's fine. And then Can I get my slides as well? Yeah, feel free to present them now. Oh, it's it thinks it's still There we go, I saw it and then I just I just stopped. Yeah. Yeah Here we go, so I will be talking about Tips and tricks for using resource loader and so I'll start with just like a basic introduction as hopefully most of you know resource loader is our asset management system in media wiki, so it's what lets you load JavaScript and CSS and other front-end assets on on a page and Just as an introduction, I'll first walk through some of the basic things But then we'll get into some of the fun more advanced things that you can do with resource loader and also some of the newer features that we introduced this year and how they work and why we want you to use them So starting with the basics resource loader revolves around modules. So a module is a piece of front-end stuff So here's an example definition of a module This is in the resources of PHP file in core and As you can see this module contains some JavaScript files. It contains the CSS file it contains some internationalization messages And it has a dependency it will Go into those into each of those components But what this is is this is called jQuery.confirmable and it's a library for giving the user a confirmation prompt and other So modules can either be something library-like that other code can use or they can just be functionality that you want to load somewhere so here's a Different Example of a module definition. This is how what it looks like if you have to do an extension an extension of JSON Where of course you're writing this in JSON and on a PHP This in this example is in multimedia viewer where they have a module called tipsy dialogue, which is a pretty clear Dialogue thing that they have. I don't know exactly what it is And you can see this one also has a JavaScript file a less file because that's also supported and a number of other modules that it depends on So then when you've defined a module you've defined a bundle of stuff about most scripts and styles Etc that you want to load in some situations you have to tell resource loader to actually load load it so On the PHP side when you're rendering a page you have an output page objects So you're in like a special page or in a before pages play hook or a place like that you can Call add modules on the output page objects and tell it which module or modules to load and that will get loaded In this example that is the HTML form code for media wiki You can also do lazy loading of modules from within JavaScript. So if you have If you discovered later that you need something or if you have a module that is really big and you only want to load it when the user interacts with your feature for example, if they Open visual editor they click edit and then you realize you need to load all this code because they want to use visual editor now You can use mw loader using for that So you can tell it which module or modules you want to load it gives you a promise and then That promise gets resolved when the module you asked for has loaded that code is available So in this example this lazy loads table sort of code and then when it arrives it Applies a table sorter to a thing another thing that is Maybe not as well known, but that is also pretty fundamental and that I Want more people understand is the notion of render blocking CSS so You saw that a lot of these modules that I showed before had both JavaScript and CSS in them And that's suitable for when the CSS is only needed to style the things that the JavaScript Does or creates But there's also some CSS that you need just to render the initial page like the skin CSS like the CSS that makes the sidebar and that makes the tabs at the top of the page and it makes the The links at the top right and all those kinds of things That is CSS that you need even when there's no JavaScript just to render the HTML it comes out of the out of your php code And that CSS we make render blocking which means it loads very early And it gets loaded before the browser paints anything and that's because it's so important that if you Try to render the HTML without it. It would just be a mess So obviously this should be used sparingly because of the performance implications of loading it so early that everything else is blocked on it, but you should also use it when when it's needed so for Things that do require this CSS very early you should put it you should make it render blocking Otherwise you will get a flash of on cell content the page will first load without a CSS It will look ugly and then it will jump as the CS arrives It will fix itself and that's not an experience that you want So this is definitely something to be judicious about but also Don't be afraid to use it when you do need it just check these Criteria to see whether you do or not dealing with render CSS block we're in relocking CSS is a little bit different in resource loader The way you do it is you put the CSS that you need to be render blocking in a separate module That only contains CSS that has no dependencies. It's it doesn't have scripts. It doesn't have anything else just CSS And you use add module styles instead of add modules to add it So one thing you'll frequently see is that The same component registers two modules one for the essential render blocking styles and one for All the other stuff that can come later. So the the JavaScript and the CSS that goes with that JavaScript So that's what you see in this code snippet Mediaweek action edit is the module that has the JavaScript and the CSS that goes with the JavaScript and the messages and all the stuff that you need for Dynamic things and mediaweek action edit dot styles is the essential styles that need to be render blocking and that is They're they have to be separate modules because they have to be loaded in different ways as you Can see over here and if you if you try to use that module styles with the module that contains anything It's not css. You will get an error message Another pretty fundamental thing that we have in resource loader modules internationalization messages And this is probably pretty familiar concept if you have ever written any php code in media wiki because we make everybody use this but I'll just go over it real quick You can define translatable messages in E n dot json both in both in core and extensions And those modules those messages can be translated into other languages and they can have parameters like this example Uploaded colon and then a parameter Um that message has a symbolic name in this case. That's multimedia viewer dash daytime dash uploaded um, you can then In when you create a module So this is an example module definition and extension of json um The module definition has a list of messages that isn't that are needed by the module. So you put it in that list Uh, that means that module gets shipped over to the client side and your JavaScript can then use it um And you end up writing code like this Where you call it w message give it the name of the message that you want you give it the parameter And then you tell it whether you want text or html from it um, and Now you will have a ground rise translated message in your Uh javascript and this is something that we also have a php library for and the way that you use it in javascript Is mostly the same except that you have to add the message to the module definition otherwise it won't work And so this means that international this and messages are also a core component of a module along with scripts and styles um another Thing that a lot most people know about is that your css can get flipped. So on the left is um A screenshot from english Wikipedia from a couple days ago And on the right is a screenshot of arabic Wikipedia and you see that you can see that it's basically a mirror image And almost everything is mirrored The sidebar is on the right instead of on the left the tabs flow from right to left um the icon Oh, this actually doesn't have a flipped search icon the magnifying glass icon is not flipped, but I think in the past it has been um Some icons are flipped uh the direction of absolutely everything is flipped. Um, and this has implications for how you write your css So we used to write a separate override style sheet for uh, right to left languages that was always out of date and always a pretty crappy experience But with resource loader you can just write css from a left to right perspective And when your page is viewed in the right to left language We will automatically take care of flipping things for you. So when you write float right that becomes float left When you write padding left that becomes padding right when you write these like Four value like margin padding or border rules with different values. We know that the second one is that Right one and the fourth one is a left one. And so we switch those around And if you put things like dash left or dash ltr in your Image names, uh, we will flip that to dash right or dash rtl and pick the other one. So if you have images like that magnifying glass or Or arrows or some other things That need to be reversed in rtl We won't actually go and reverse the image for yourselves But you can make a rtl version of the image Encoded in a file in this way and then we will load the other one when appropriate So that's a that's a pretty cool feature that I hope most people know about But also pretty fundamental for To understand how resource loader works on what kinds of implications it has All right, so that was the basic stuff Let's get into some of the more advanced stuff and stuff that not everybody knows you can do This is not always going to be useful to everybody all the time, but it's good to know that it's there So for example You can have style files. So css or less files in your module You can also have skin specific style files in your module. So in this example This is an example from echo. We have a style file Over here that is always used We also have style files specific to each skin one from minerva one from monobuck and one from vexer Down there. So this definition is pretty intuitive. It's pretty intuitive what this does And you don't always need it, but when you do you'll be glad it's there More obscure is that you can also do this for scripts. You can have JavaScript files that are only loaded in certain skins So this is a bit of a weird example, but here there's one file that gets loaded in The minerva skin and then a different file that gets loaded on every other skin. That's not minerva Is what this definition means You can even do this With languages you can have different scripts that load in different languages This is not used very often Video jest apparently use it because that's where I took this example from And things like moment jest which is a timestamp rendering library Use this it's uh, it's not super common But you might see it around sometimes and You know one in a hundred times you will actually need to use this And then you'll be glad to say um another thing is a little bit more commonly useful is template support so In addition to scripts styles and messages as resources in a module You can also add a template as a resource So in this example, um, I added a html file thumbnail html as a template To a module And when you do that it gets that html file gets loaded along And it means you can write JavaScript like this Where you call mw template that get with the module name and the template file name And you call dot render and now you have a jQuery object that Has the parse html from the template And you can go and fill in a bunch of your placeholders with With stuff and then put it in dot which is what this code does um This is not really normal templating as you might be used to so we also have support for moustache templates, which are more like actual templating Um, so you can write a file called food at moustache that looks like this with which actual template syntax with parameter and stuff And you can I think you can write like if statements and simple loops and stuff um, and you can also put that in Your module didn't show the example here, but the syntax is the same as the previous slide. You just have a moustache extension instead of a dot html extension um And the clients that use is also the same except you have to give the render function um You have to tell what do you want the parameters to be replaced with and so the result of this will be a JavaScript Object that is a link with that URL title unlabel filled in for the parameter values in a template And obviously it's a simple example, but um There are more advanced uses of this out there where people have large html structures with many grammars that they fill in this way um As an aside, it kind of crazy use of this is that um, you can also have templates whose file names end in dot reject And then there are rejects So this is used. I'm only aware of one place where this is used in the media. We could get your eye Module where there's some sort of massive rejects that needs to be used and you can Include that as a template file This pretty obscure you're not going to need very often But it's a nice illustration of the kind of cool stuff that uh people have figured out how to do this is I think this is something that barters came up with like four years ago or something um Another fun thing that you can do is you can have internationalized messages in your less style sheet Which seems kind of crazy that you would want translated messages in your styles But for a column before and column after content rules it can be useful Uh, so this is an example from core. You have to have this Class resource loader less of our file module magic thing Um, and then there is a less messages property And what this means is that you can write um Code like this in your less file where the Uh, the variable over here this msg that show to you see thing ends up being replaced with text like Show table of contents and but translated in whatever language you're viewing this in um, so use usually we try to put um Translated translated and translatable content in the html and not in the cs as much But in some cases This can be useful and so there's a couple of modules here and there to use this Uh, it's pretty cool trick And I think John robson was the one that introduced this like a year or two ago Maybe and then a volunteer named foma fix generalize it to let you use any message In any module this way um, all right, so those are Some pretty cool advanced things that you can do they've also been around for a few years I think the most The newest thing that I showed you was the less messages thing and that's from 2017 Uh, so now I want to talk about some of the stuff that we introduced earlier this year Um to do with package modules, which you may have heard about what I want to go into some more detail on Um, so something that's existed for longer is that you can use required for Uh, like the the node js like common js style require function for modules So for example in media we get a string the js which is a library and core We have this statement statement at the end where module of exports gets like a bunch of stuff put into it that the module wants to export And then in somewhere else in some file that uses module you can write code like this So this is a lot more like the node js style of using things um most of media wiki is most of media wiki's javascript is written around the idea that your Libraries just like put stuff in a global object somewhere and then you use a global object in your in your file where you use the library That's not how modern javascript is written anymore um, so a while ago we started supporting this uh syntax for modules Which is cool, but we can we figured we could do better Uh, what's new is that you can do require for files now, which is also how uh a lot of Modern javascript code is written So this is an example from I don't even remember where I pulled this from where one file has module of exports equals some function in the file and then another file in the same module um Just requires a file and this is a This is a typo. I think I got the file names the wrong way around here um, so excuse me for that, but the idea is that um, you can Export something using module of exports in one file and then Uh, require the file from another file and get the thing that was exported Um, and this is nice because now you don't have to use the global namespace to communicate between separate files in the same module Uh, you do have to do some magic and we're going to dive into this magic uh in a second Uh in your module definition you have to use this new thing called package files instead of scripts and so, uh, we'll um Go into that in a minute But just as an overview a package module is the term for a module that uses this functionality So instead of the scripts Thing in the module definition you use package files to indicate that you're using the new feature in a new style The first file that you list in this list of package files is the entry point So that is the first that is the file is going to be executed when Uh, the module loads the other files after that their order doesn't matter and they're not going to be executed unless Uh, something calls require on them. So that's a big difference because with the scripts The old style of defining modules with scripts Every file is going to be executed in the order that you listed them. So you have to think about Depend like which files depend on which other files and you have to think about the right order to execute them all in And typically the file that does all the work and ties everything together and it's sort of like the Main orchestration file is typically going to be the last one in your list there So with package files that is going to be the first file Um, and it is going to use require To call other files and that causes them to be executed. They are not executed in any given order Except through this on-demand mechanism the other thing to note is that there is a Syntax difference to indicate whether you're requiring a module or a file For a module you just require the module name For a file you have to start with dot slash or dot slash to indicate that you're To disambiguate basically and to indicate that you're trying to require a file This is a little different. This is most this is compatible with how node works It does mean that there's some shorthands that you can use and know that you can't use here So you can't require directories. You can't Amidst file name extensions that kind of stuff. So Everything we let you do on resource loader is compatible with how node works It's not the case of everything. I know let's you do we also like because we are a little bit stricter And there's a link at the bottom of the slide To documentation page that explains more but i'm also going to Walk through some of this stuff so here's a basic example of A module uses package files that was I think this is from code that I reviewed a few weeks ago um, so this very first File notifications.js. That's the entry point. So that file is going to be able to get it first um And that's going to like require a bunch of other files and pull everything together and then do what needs to be done um All these other files. So overlay address for example, but also all the other ones are only going to be executed if and when Uh, someone require calls require dot slash overlay address Hopefully every file is going to be executed because hopefully they were all needed if If not, then you are wasting your time putting them in and you're wasting bandwidth, etc um But if through some circumstance, you don't ever call require in a file it won't ever get run If you require it twice, it will only get run the first time and it will just give you a cash result the second time And the order of the files here is completely meaningless except for the first one Files will just get executed in whatever order they're required uh, another thing, uh, that's important to point out here is that It makes sense when you think about it, but it's going to be a bit of a pitfall sometimes that pads are relative So here's an example of a module that has um files in a bunch of different directories So the init file is in skins of nervous scripts But then that has a sub directory called page issues that some but not all the files are in and there's also this file That is in a completely different components directory um, and that means that in your init file Uh, this is the kind of thing you see before the for the files in a sub directory You have to have the sub directory in the path and for the file that was in the completely different directory You have to do dot slash a bunch of times to get there um, this makes a lot of sense, but it's um It's easy. It can be easy to get wrong sometimes Um, and another thing that makes a lot of sense and they can be easy to get wrong sometimes is that the paths Are relative to the file that you're in So once you've gone into the page issues directory and the init.js file requires the index of just file in that directory um Once you're there the other file in the directory parts of the jess Is you're already in a directory so you're not going to have to do dot slash page issues again The path is relative not to The first file but to whatever file you're in at that moment um, this is also how things work in uh, node gs So it should not be a huge surprise and it does make a lot of sense, but It can be easy to forget sometimes Especially if you're just looking at the module definition a lot and going on with that Or if you're moving files around Um, so because of that and because you end up with these kinds of repetitive definitions here There's a feature to make your life a little bit easier called base base paths. So Very frequently. So in this example two You'll see that almost every file is or actually every file Is in the same directory and they all have this common prefix And you can write The definition differently so that uh, you put the common prefix in the base path You have to do this weird incantation with a remote x path to make research loader happy And that differs a little bit between extensions and core but you can find examples of plenty in In the core research file and in extension files But this allows you to factor out the common prefix and now your practice files definition looks a lot simpler This is hopefully Obvious and it's clear whether it's clear why you would want this for convenience But it's about to become a lot more convenient with the next feature That i'll get to you in a second So first why would you use this why would you migrate existing Modules over to the system or and or why would you write new code using the system? The the main reasons for this are that Your code structure will be nicer if you don't have to Use global objects to sort of transmit classes or functions or whatever it is that you're Exporting and transmitting between different files. You won't have to believe the global namespace a bunch It's easier to actually keep your function the class is complete hidden from other people and not Make them accessible at all even in global namespace and it's easier to see Which file a thing came from or which files your file depends on Because you can just search for use require calls and see which files get required from which file which is Hopefully not too hard if you use like a consistent naming scheme with the old method, but This makes it a lot easier You also don't have to think endlessly about whether your file is listed in the right order And you don't get bugs from listing your files in the wrong order Because you know you might try to use something that's not been defined yet Or worse. You might have a sort of circular dependency and have things breaking and worse with With require you don't have those problems and You also will not have to adjust your module definition because you change which file depends on which file And a lot of people that have used this have said that it made their code a lot Better and more readable I didn't even realize this would happen until after I implemented the feature and I saw one of the patches from one of my team members who converted some of this code to the system and I was like wow this actually Does make things a lot better. All right, so That's That's cool, but now let's just let's take this a step further We decided that we were also going to let you require JSON files So for example, there's this module in media we could core That has special characters that we want you to be able to use in Toolbars and stuff for inserting different types of characters. So there's this big json file that Has like all the different characters in all different categories of characters So there's like here's all the latin extended characters and here's all the greek ones and here's all the Bengali ones, etc, etc Previously we had to write special code just to get that json file embedded in a resource loader module and it was pretty awkward Now that's just a core feature. So you can just list a json file as one of your package files Can't be the first file because you can't actually give a json file, but you can Have it as one of the non first files And then in a JavaScript file you can require an json file and that will do exactly what you expected to do it will Just json parts that file and give you the objects So now instead of writing a bunch of weird code that Like has php that generates a JavaScript dynamically and it was all awkward We can just add this special character to json file to our module as a file and then Just write some JavaScript that says require a special character to json and now you have the object with the dictionary of all the special characters Um, so this is cool, but we don't have this tremendously often the reason why this is Um, uh, so life changing is that we also support virtual json files So this is where things get a little spacey. So this is where I'll Start taking more time per slide to explain what I'm Uh, showing you Uh, so this module definition and I'll pick this apart in a second because it has a lot of elements But the important thing that I'm showcasing here is that it has a virtual json file called Uh names.json So the syntax is a bit weird here But uh, basically what this is is we say there's this file called names of json. It's not a real file Um, but here's some code that lets you generate the contents of this file. So Basically we're instructing researchers to pretend that there is a json file called names of json And to run this code to determine what is in that file. So in our case Uh, excuse me In our case that is the result of that fetch language names function over there. So this is a Uh virtual file that contains some names of all languages in our current language um So in order to make this work, uh, we use a base path Because uh things would get pretty awkward otherwise if we didn't use a base path then You would be seeing resources slash source slash media dot language slash You get a language of names that's yes on the first line and then for the second file We would either have to pretend that our fake file is in that same directory Which would get confusing because then if someone goes looking for a file it's not actually there Or we would not have a prefix and just call names of json, but then The javascript file would have to require dot dot slash dot slash dot slash names of json Which is also not very fun. Uh, so this is what this is the reason why Uh, I highlighted the base path feature so early We use it almost always in modules that have virtual files So that things don't get that awkward Uh, so this uh over here is a function that returns the contents of this file So in this case, it's the language names. It's the language names in the language Um, and so there is a context object that gets passed this parameter so that you can Find out some things that you need like which language are being executed in right now Uh, so with that all uh, this is basically a replacement for The way that you used to have to do this is either write Some code that like four people on earth understood how to write so that you could put dynamic things in this part of your module um, or you had to make this Uh a configuration variable basically and export it through the configuration system so that you could get it with mw.config.get And then language names or something But this is a way that you can put data in a module um, and That is pretty powerful. We're going to see some more applications of that Um, but this syntax can be confusing. So if uh, people have questions about how it all works then that would be very understandable Um, another hiccup there is that when you're in an extension of json final Uh, you can't of course write inline functions in json the way that you can in php the previous example Um, I had an inline function in the module declaration. You're not going to be able to do that in json So the syntax for that instead is that you put a function somewhere typically in your hooks class as a static function and then you just put the name of that function in the extension of json file and Then you write that function that takes a context object and a config object and Returns whatever data. So in this case there is a config.json file in This echo module that i'm using as an example that has uh that contains Json object with two keys and values one is the max notification count that it gets from a static variable somewhere And one is a config setting that it gets from the config So this is also this was previously using nw config yet. So instead of using nw config, this is now bundling this information with that module um That is a nice segue into the shorthand that we put in for config variables A lot of some of the time you want To have data that is generated by like real code Uh to be in your module, but most of the time Really what you want is just actual config variables. Um And that's what's happening here in this, uh meeting with your special block module So this just needs access to a couple of config variables from the php side And so instead of callback and you could write a callback that takes The config object in the second parameter and then returns Uh an object that has those keys and gets the values and config objects But that's work and there's no reason you would do that work if everyone's doing the same thing So we put in the shorthand syntax where you can just Add put in a list of config variables that you want And we will make that happen for you. So now in the block address file Uh, there is code that just requires the config of json file And then gets the config variables out of it Previously this was using nw config that get Um So let's talk about why you want want to use this instead of nw config to get Because the old way that a lot of this stuff was supported, especially with config variables, but also with Things that were data, but not too big and sort of like config variables is that You registered a resource loader get config bars hook Um where you added these config variables and then in your javascript code You could call nw config to get and get your variable Um The way that this works is that it puts your config variable in this in something called the startup module It doesn't really matter what it is What matters is that it gets loaded on every single page view and it gets put in a global nw config namespace Um, so this new way using package files Has advantages because now your config bars are namespace to your module So you can give them names that are not That don't have to be globally unique You don't have to have a long prefix in front of them to make sure they're globally unique Um, and you're not polluting that global uh config object And they also get loaded with your only with your modules So they don't get loaded on every page view. They only get loaded when they're needed and For config variables in core, this can be a bit different but for almost all config variables and extensions They're really only needed For modules from that extension and most the vast majority of them are only needed by one module Uh, so this makes that very suitable for that case in particular It also means that the data in that config variable gets cast for a longer period of time And doesn't cast get cast for only five minutes And it doesn't get reevaluated in a hot code path because we have to update it every five minutes um There are also cases when you can't use this um, you can't use this when you have config variables that depend on Data specific to the request So if there's something that depends on query string parameters or on The page title or just more broadly on Which page you're on? or On which I should have really put that in the slides which user Is logged in right now or whether the user is an administrator or whether the user has a certain right or all those kinds of things If you're the only thing that you can use when you're in One of those callbacks for a virtual json file is the skin in a language and that's it. That's the only thing you get so if you need to look at The page title or the user is that envy or the user's rights in order to decide whether the user gets Whether they get a certain feature or not and you're putting that in a config variable You can't use this feature. You're still going to have to use Uh output paid the ags config bars and output page the good news is that You already would not have been able to use restart or to get config bars because that has the same that hook has the same constraints as this So anything that used that before can Be migrated Can simply be migrated to your system So you can help us by converting your extensions or the code that you maintain to Use these new features, especially if we're exporting config variables or other like simple bits of data that you need Like I said, most config bars are only used in one module. It's often For the simple case. It's a pretty simple grabbing exercise to confirm that it's safe to convert something and then just convert it And another good thing to start with is a look at your extensions reach slower to get config bars hook See what it exports and see if you can Migrate all of those things and eliminate the hook. I've been able to do that in a few extensions Secondly you can use the package files feature and the profile require in a new code because We think it results in better code and less global namespace pollution and all those nice things Uh, and thirdly, you can tell us what your experience is using the system. It works well and what doesn't work I've already heard from some people that debugging is a bit harder when you use the package file feature and so and we're already thinking about some ways to fix that and Make the debugging experience better But there might be other things or there might be other reasons why This doesn't work work as well for you as it should or Maybe it's awesome and we and you love it and we like to hear that too So, uh, so yeah, tell us uh, and then let's see I have some bonus material But sarah, how am I on time and on questions? You have uh, we have 15 minutes left and it looks like we have one question So far so you could probably take a little bit more time for a bonus material And then we could ask the question or we could ask the question And then you could do bonus material Say say that again, let's do the question first Okay, so this is a question on irc It might be as though if you look at it, it would be in the office channel Um, just because it involves a file name, but I'll go ahead and read it out to you Um, it says I note that a lot of virtual file generators are stored in the extension name In paren hooks that php file Is that a good practice given that these aren't really hooks? That's a good question. Um I don't think that we really decided yet yet. Um, it's uh The reason that I am doing that and the reason that I'm sort of propagating that is because it's historically where we've put Other callbacks from extension of json. It aren't really hooks. So extension of json also has a Um like extension in it callback feature Where you can register a callback that gets run when your extension is Loaded and that lets you set up global variables and do some other stuff that wasn't like completely migratable from The old extension php system to the new extension of json system Um, those are also put in hook files. So it has sort of become the dumping ground for Random static functions that need to go somewhere Um, admittedly there's only one there's only zero or one in it extension callbacks for every extension and there's potentially more um Of these config or of these virtual file callbacks Uh, so if people want to start a convention where those go in a different class I would be very uh very open to that Um, it's just what we're doing now because that's sort of following this circle pattern, but we didn't really Give them much thought to be honest. I just went with that Okay, that looks like for now. That's the only question. I don't see anything on the um The youtube stream or anything else on the irc channel. Uh, so if you want to share Um, the rest that you have You have about 14 minutes left. Um, and we can always check for questions. Uh after that as well All right, so I'll go through the bonus material real quick. Um There is I only ended up having time to write two bonus slides. Uh, so there's really only one topic here and I'll We'll make sure to upload the slides somewhere. I'm sure and so this is Going to be useful to refer to for uh for people because the second slide is just going to be huge and have a lot of examples But another cool thing that you can do that none of people know about is that you can have what we call icon modules or image modules um, which uh are basically just Modules that add o UI icons or insert your favorite icon management system here and that embed a bunch of images So uh, this is a simple example where Uh, you use this class resource or image module thing magic thing to say that it's an image module you Indicate the selector pattern for how it should make the class selector for the For the icon with the name placeholder and then you just list a bunch of svg images and That produces that ends up producing a module whose css looks something like the below. It's Simplified a bit because there's actually more going on but that gives you the basic idea So this is compatible with o ui's icon system And so if you have custom icons that you want to be able to use o ui icons This is a very easy and concise way of defining them And that's what it gets used for a lot The more complex example is this one Um, and there's a there's a lot going on here, but it's I found this example in the wild in manorva and it's a Pretty great like omnibus example of using every Every feature that I know exists in this system and some features that I didn't know just to honestly And I didn't even have to contrive this example. I found it So there's a bunch of different things going on here for one thing You can have different octl or left to right and different and right to left versions of the same icon That will get selected automatically um We saw that you could already do this with css flipping if you just wrote icon stuff in css, but you can define it this way as well um You can make give your icons colors And I didn't actually know this feature existed until friday when I made these slides You can set up the fall color for your um For images and this means that all these images will uh, there's only works for svgs, but icons should always be svgs anyways um This means that all of your all things that are normally black will be made This I think grayish color that is defined here um You can also define color as variance So in this example, that's in verse which is set to white. So then you get a white on black version of the icon, but um, you can also use this to make things Red or green or blue, which is something that oi does for progressive and destructive icons um You can set the variance be global which means that every icon gets it or in this case, we didn't do that and we only do it for some Uh images. So now this icon can be used in two different ways You can use the regular clock icon and you just get a clock icon Or you can use clock dash inverts and you get a white on black uh clock icon and Uh in this case, they're using the mw ui icon uh system and naming scheme for dc assess selectors, but If you put in the right naming scheme for dc assess selectors up here Then you can this also ends up being compatible with oi system and it will um automatically invert icons for you if they appear in a place where they need to be Uh where there's a colored background the icon needs to be white um So this is a pretty crazy example, but it gives you an idea of how powerful this stuff all is and And what you can do with it and I don't know how much documentation there is but The if you look up the definition of this module skins from nerve icons images. It does basically everything I can do So it's pretty good. Let's look. Oh, yes, and I'm looking at irc now on my phone I see the andro auto says that uh he and team have recently added a call that pram Uh, which is another cool feature that's That I don't have an example of right now, but it's worth looking at um A Mirror asks is there any way to change the value of the virtual json file on tests? I want to write some key in a test for different values of a config variable that can find an easy way That's a really good question. I don't have an offhand answer to but that's uh, another thing that we should look into improving if it isn't already doable because um You're right that it would be good for especially these more complicated callback functions the ones that aren't just like oh here's a config variable put it in but The ones that actually do work It would be good to be able to test them And I don't think that we have a great way to unit tests. Um those callbacks right now and it's something I'll probably consult with timo on because He knows both more about resource loader and more about unit tests than I do And that's the combination of things that we'll need here awesome ron. I just um I didn't see any other questions and If you don't have any more Bonus stuff. I just wanted to say thank you so much for speaking today. Um, we really appreciate you Sharing all this information. That's really really interesting And um, I also wanted to thank uh, brendan cambell craven and the AV team. I don't think I think them enough on these calls or on these tech talks as well They do so much background work To help make this possible. So Um, is there anything else that you Would like to share ron or any um additional questions that folks have I have nothing else. So unless there are last minute questions, um, I will keep it at this Okay, great. So what I will do is um, again, if anybody does have questions on the youtube stream Maybe you're watching this later. Um, feel free to ask those I can forward those to ron Rone is okay for people to reach out directly and ask you questions too. Okay Great. Um, and then uh, we as always just kind of a little plug for tech talks. We have this talk every month This is open to members of our community As well as staff anyone who wants to share technical topics Definitely feel free to reach out and we'll schedule you in and uh, And have you shared the community? All right Yes see see um I had this slide up earlier and you can look at that. Um, and that has information about how you can How you can sign up for those as well. So again, thank you everyone for attending today. Um, thanks for everyone helping and thanks for everyone who has questions Okay