Refactor motivation overview0:00
Let's reflect on a recent refactor I did to the Laravel Mix CodeBase, and this is something I've been working on for the last few weeks, and it's just about done. So you may remember, in late January on YouTube, I did a stream where I was starting to rethink the way that the Laravel Mix CodeBase was constructed, because I felt like everything was too fragmented. But you can see, you don't have to watch this, but you can even see from this snapshot here, I was tinkering with the idea of creating dedicated components as classes, and that way I could have a single point to declare, well, what are the dependencies for this feature, for this component? What are the Webpack plugins I need? What are the rules?
What are the Webpack plugins I need? What are the rules? What is the Babel config that is necessary? So in this stream, I wasn't even really sure what I wanted. I just had this basic idea, and I wanted to experiment. Since then, I've worked on it quite a bit more, and it's actually at the point where I think it actually looks fairly good. So let me show you what we had before. Let's do a git log here. Let's just find something.
Problems in old structure1:12
I just want to find an earlier state. So let's check out the state as it was in January. Okay, and we'll open this in Sublime. So yeah, if we go into the source directory, you can see that... Let's just consider anything, like, well, we were looking at browserSync before. So you can see right here, this is what you will call mix.browserSync, right, from your webpack.mix.js file. But it ends up getting a little fragmented, and I'll show you. So you can see here, well, when you register it, we have this little functionality here that will basically make sure that the necessary dependencies are available to you, and if
So you can see here, well, when you register it, we have this little functionality here that will basically make sure that the necessary dependencies are available to you, and if not, we will install them. We then update a global config, and that's it for this section. So now if you want to figure out, well, where else is browser-sync being referenced within the codebase, it's kind of confusing. You have to know the codebase. So in this case, like, let's just look for browser-sync, and let's see. So there's the file we were just looking at. But now you can see right here, I have a webpack.plugins file, and this is checking, well, if you're
So there's the file we were just looking at. But now you can see right here, I have a webpack.plugins file, and this is checking, well, if you're using browser-sync, then we need to pull in that dependency, and then we need to push to the webpack.plugins array, and here's all of the config there. And this is just a basic example. In other situations, let's see if we can find one. Like TypeScript. Okay, let's look at that one. Close all this out. So TypeScript is something we offer out of the box.
Close all this out. So TypeScript is something we offer out of the box. So you say mix.typescript. This is going to reference this here, where we update a global option so that you can toggle it easily. We then make sure you have the dependency, and we then register it as a JavaScript compilation request. But yeah, then once again, if we look for TypeScript, well, once again, yeah, here in webpack rules, well, we need to add a rule, a loader, so that we match any file that ends in .ts or .tsx, and so we pipe it through the ts-loader.
webpack rules, well, we need to add a rule, a loader, so that we match any file that ends in .ts or .tsx, and so we pipe it through the ts-loader. This is all webpack stuff. You don't even need to know much about it. The main idea, though, is for any piece of functionality, here's another one, the process of building it up, building up the world, building up everything it needs to function correctly with webpack, it's all in a bunch of different places. Here's the config. So we can see one, two, three, four places alone just for TypeScript, and it gets confusing. There's not a single point of entry to say, okay, well, for TypeScript, you need this
Designing component API4:42
It's very, very important. Because if you don't take that step so often, you get started on the code, you start making it work, and then even once you get it finished, you realize the process of interacting with it, it's not good. So this was one of the main things I was working on in this live stream on YouTube, just figuring out what should the interface be, what should the API feel like to interact with. Here's an example component that I came up with. So I can call Mix.extend. I give it the name of a component. That can be any feature, basically, that Mix offers, whether it's stylus support or Sass.
I give it the name of a component. That can be any feature, basically, that Mix offers, whether it's stylus support or Sass or notifications or PostCSS, any of those. And then we can new up a class here. Now the class, here's the interface you basically have. What is the name? So basically, how will you interact with it? If I return foobar, then I would call Mix.foobar up here. Next the method to say, well, what are my dependencies? I don't want to have to be responsible in this class for actually installing it.
Next the method to say, well, what are my dependencies? I don't want to have to be responsible in this class for actually installing it. I just want to return an array. And that way I can take the responsibility of acting upon that, and I can send it up a level or send it down a level. So here we can just say, well, we need these two npm packages. Next, a standard register method. This is pretty common. If you need to accept the $user's data, if you need to massage it in some way, you would do it here.
If you need to accept the user's data, if you need to massage it in some way, you would do it here. Boot, all of these are optional, by the way. Boot would be like if you want to do anything after the Mix file has been constructed. You want to make sure all of the user's webpack.mix.js file has loaded, and then you want to act upon that configuration. You could do it here. Here you could say, is there anything we need to add to the webpack entry? Is there anything we need to add to the webpack rules list? Is there anything we need to add to the plugins?
Is there anything we need to add to the webpack rules list? Is there anything we need to add to the plugins? So once again, notice I'm not accepting a config and appending to it. We're just returning an object or returning an array of objects. And that's our way of saying, okay, this is the array of webpack plugins I want you to add. I'm not going to be responsible for doing it. I'm just going to let you know this is what I need ultimately to be inserted into the webpack configuration file. Then we have a master override if you do need to hook into it, because sometimes you do.
webpack configuration file. Then we have a master override if you do need to hook into it, because sometimes you do. Sometimes you need to grab kind of these isolated things here. In this case, for TypeScript, you would need to push to the extensions array that webpack provides. And then finally, is there anything special for Babel that you need to insert? So it was important for me to figure out this API, to figure out what the interface would be. Because then at that point, it's easy. Once you figure out what you want it to be, how you want to interact with it, well, then
Because then at that point, it's easy. Once you figure out what you want it to be, how you want to interact with it, well, then you just have to accept that data, send it up a level, and then figure out, okay, well, if you write here, return an object to me or an array of objects, I just need to make sure I call that method. I get the array and I insert it into the webpack.config.js. It's a much cleaner approach versus before where this stuff sometimes was split up between five different files. So I'll give you an example. Let's look at TypeScript.
New structure examples8:20
And then one more thing. All right. So back to our webpack.config.js. So once that has been constructed, we're going to do a quick little tweak there to push these two extensions and add the necessary alias. And in this case, it's just overriding what we do by default with view there. I guess TypeScript needs that. Very, very clean. So now if we take a look, let's look for TypeScript. Well, this is the loader.
And if you don't know the code base, it's too confusing. Let's do another example here. How about BrowserSync? All right. Well, here's how we interact with it, mix.browserSync. Here are the dependencies we need. When we register it, we can accept a string or an object there. So we're just checking for that, nothing interesting. Here is the plugin that BrowserSync needs to work. So we will return that.
Here is the plugin that BrowserSync needs to work. So we will return that. And then finally, here is the BrowserSync-specific configuration that we pass. Super, super clean. Once again, versus what we had before. We do a search for BrowserSync, and we can see, yeah, once again, exactly one file where it's being referenced. Let's do one more, PostCSS, all right? We're calling it mix.postCSS. We register it by doing a little validation.
We're calling it mix.postCSS. We register it by doing a little validation. Once again, we're massaging the data. So if you give us a source and output, well, sometimes you need to figure out, okay, well, if this is what you gave me for the source, then the output name should be that. Basic stuff. And then we store it as details on the object. Finally, for the Webpack config, I have to make sure that within the CSS Webpack rule that we add some exclusions there. Don't even worry about it.
that we add some exclusions there. Don't even worry about it. It's just low-level, boring stuff that you got to do in these cases. But once again, let's look for PostCSS. Once again, we have one and two, and this is just the abstract parent class. So you can see this extends Preprocessor, and there we do have one reference to PostCSS later. Once again, those two files are connected. So it's significantly easier than what we had before. So once again, I said in the last episode, it's important that when you perform a refactor,
Evaluating refactor success10:54
It made it more complicated. But now in this particular case, yes, we added more files, but that's okay. Nothing wrong with files. It's a matter of whether it made the code easier to reason about. And in this case, the answer is yes, I do think this is a good refactor, because no longer do I have to search through six different files to figure out how a basic mix component is constructed. Instead, I just go to the file and I can immediately see if I want less to work with mix and with Webpack, here are my dependencies. Here's how we register it.
Extending Mix with plugins11:20
Webpack, here are my dependencies. Here's how we register it. Here's the Webpack rules that are required. Done. And even better, this component system that Mix uses is entirely available to you. I haven't tagged to this yet, but once I do, and very likely by the time you watch this video, it will be tagged. So that means I have a little dummy file here. Yeah, you could create your own. You could do something like this, mix.extend foobar.
Yeah, you could create your own. You could do something like this, mix.extend foobar. You could pass a closure if you want, or often you will pass a class. You will register it. If there are special dependencies that you want Mix to install through NPM for you, if there are Webpack, you know, the basic interface that I showed you right here would be relevant. You extend Mix, and now you're good to go. And this can be stored in another file. So you could say require foobar, something like that. Or you could say require Laravel Mix foobar, something that you push and distribute on
So you could say require foobar, something like that. Or you could say require Laravel Mix foobar, something that you push and distribute on NPM. It doesn't matter. Now in your code base, you could say mix.js, give us your source and your output. Maybe you want Sass, your source and your output, and now you also want to call your new foo extension. Really really easy. So that means the exact plugin system or component system that Mix uses behind the scenes is the same one that you will use to construct your own components.
So that means the exact plugin system or component system that Mix uses behind the scenes is the same one that you will use to construct your own components. So now we've taken some time to reflect on it. I have with you, and we can see in the first episode, no, I don't think that was a good refactor. Time to revert. But in this episode and in this example, I do think this was a good refactor. It did make the code easier to reason about. So I'm keeping it, and we're going to tag it soon.
