Component-Based Refactor Overview0:00
Now, I'm back in the Laravel Mixed Code base one more time. In the previous episode, I discussed wanting to refactor to this component-like setup. And since then, you can see all of the various components that I've created. For example, one is just for autoloading. Here's how we register it with Mix. And then here are the plugins that will need to be passed to Webpack. Here's another one, React support. So for React, we do need the Babel preset React package. So we'll pull that in. And then once again, here's how we register it.
Planning Plugin Tests0:53
Now, I've already worked on that a little bit. But together in this episode, I want to test to make sure everything is working. This is a situation, I didn't choose TDD, I was still experimenting. So now we're going to fill in those tests. You can see I've created a new feature here called plugins.js. Now one thing I like to do is write out all of the functionality that the feature requires ahead of time. You don't have to do this, but it can sometimes be useful. And those series of tests that you haven't done yet, create a sort of to-do list for you.
And those series of tests that you haven't done yet, create a sort of to-do list for you. So for example, what would be the first thing? What is the feature that I'm offering here? Well, Mix can be extended with new functionality. All right. Now real quickly, the API I decided on is within your webpack.mix.js file, you would say mix.extend, and I want this new feature. Now maybe we offer stylus support out of the box, but what if we didn't? You could say, all right, I want stylus, and then here is where I will register and handle
Now maybe we offer stylus support out of the box, but what if we didn't? You could say, all right, I want stylus, and then here is where I will register and handle it. So once you extend it, you could then say mix.stylus, and then anything you pass here would be passed to this callback. Or real quick, what you can also do if you need more flexibility, is you could say extend it with a class. So what you would pass here is exactly like what I have created for all of these cases. So for example, here's how you would register it, and that's basically the method that gets called when the user does mix.stylus.
So for example, here's how you would register it, and that's basically the method that gets called when the user does mix.stylus. Here you could hook into any rules that you want to be applied. You could hook into any plugins that you want to be applied, or insert it into the webpack.config.js. Any of these should work. So we need to write tests to verify that. So how about mix can be extended with new functionality as a callback. Let's do another one. It can also be extended with new functionality as a class.
Testing Callback Extensions3:17
So notice how we do have a sort of to-do list at this point. So let's get started. Mix can be extended with new functionality as a callback. So we use our API, mix.extend foobar, and we know we want to give it some kind of callback there, right? And then ultimately, we want to make sure that we can now use it. So I could say mix.foobar. But now I do want an easy way to say, all right, well, once everything's been constructed, when I call mix.foobar, I expect this method to be triggered. So in these cases, signIn ends up being a really good tool that you can refer to.
when I call mix.foobar, I expect this method to be triggered. So in these cases, signIn ends up being a really good tool that you can refer to. It's as simple as this. The registration or the register method, we'll call it register or registration, and we're going to spy on it. A spy is useful for a stand-in that you later want to perform assertions against. So if you want to say, all right, well, here's a dummy function, but later I do want to write assertions to confirm how many times that function was called. There, a spy can be useful. So here, we'll pass that in.
There, a spy can be useful. So here, we'll pass that in. Now when we call this foobar method that should be generated dynamically, well, my assertion will be that the registration spy that we had here was called. And with signIn, there's a helpful property called called that we can use there. Okay. So let's give this a test. We run it. And sure enough, four failed. That's because these four have not yet been implemented.
And sure enough, four failed. That's because these four have not yet been implemented. We could even label those as pending, but it's okay. But we do see green. Excellent. So that means this does in fact work. And if you want to take a look at this, here is that extend method. So when we say mix.extend, I do have a little factory class here that we use to install a new component into the framework, so to speak. And then you can see this is running right here.
a new component into the framework, so to speak. And then you can see this is running right here. So our component in this case would be the anonymous function. So we say, well, if that's a function, well, then we're going to kind of wrap it up as an object that we will ultimately pass through here. And you'll notice that register, yep, that's the exact same thing as if you created a class, just like we had done here. All right. So we're just kind of molding it. You can give us a callback function if you want, or you can give us an object or a class,
Testing Class Extensions5:37
So we're just kind of molding it. You can give us a callback function if you want, or you can give us an object or a class, basically the same thing there. Okay. So looking good. I have a little more assurance now that this feature actually works. But let's do this one now. So we'll copy it. And this is fairly similar. So we're going to extend Foobar, but this time I'm going to give it a new class.
And this is fairly similar. So we're going to extend foobar, but this time I'm going to give it a new class. We'll call it Register. Now this time, though, I'm not going to use a spy. Why don't we actually do this traditionally? So I do want to make sure that whatever you pass here does ultimately get passed to your register method. So why don't we do an assertion there? $t is baz, and compare that against the value. Okay.
Verifying Plugin Dependencies6:53
So if we're going to offer a plugin functionality to users, they need to be able to say, all right, well, for my plugin to work, you need to pull in these two dependencies on demand. Let's give this a shot, and I think we'll run into a new gotcha. All right. So I'm going to extend, mix with this new functionality called foobar. We can leave register blank in this case, but now we want to specify the dependencies. And we'll return some kind of step here because we're not actually going to fetch it. So we'll say, how about this, npm package. Next, we will trigger this, so mix.foobar. But now this is where it gets a little bit tricky.
Next, we will trigger this, so mix.foobar. But now this is where it gets a little bit tricky. So we kind of want to say, well, if they have this dependencies method, we want to make sure that we call the necessary functionality to download that. And that's done through this class we have here called Verify. So if we take a look at that, we try to pull in the dependency. And if it doesn't exist, then we install the dependency. Now we can see where that's being used right up here. So you'll see, here's where we install a component. So right here, we listen for mix to fire the init event.
So you'll see, here's where we install a component. So right here, we listen for mix to fire the init event. And that's basically mix's way of dispatching that, hey, I'm all loaded up. I've read your webpack.mix.js file. I've initialized everything. We're ready to get started. So if you want to hook into that point, you can. And we do that in this case to install the dependencies. We can see the shape that takes, at least at the moment. Fetch the array of dependencies, filter out any null values that might be there, and then
We can see the shape that takes, at least at the moment. Fetch the array of dependencies, filter out any null values that might be there, and then for each one, we will call verify.dependency. So it sounds like this is a static method. Why don't we just override it? That seems like an easy way to go. All right, let's give that a shot. So we'll say let verify, and I'm going to go into that source directory and grab that file. Actually, on that note, I don't love the name verify.
file. Actually, on that note, I don't love the name verify. I think I will probably rename that at some point soon. That's always bugged me. Anyways, because it's a static method, it's very easy to just rewrite it on the fly. Let's just say, all right, well, that now equals sign and spy. Now we could say, all right, well, we had this new functionality, right? And that class has requested these dependencies. So let's just make sure that this method receives a call with npm package. How about this?
So let's just make sure that this method receives a call with npm package. How about this? verify.dependency. That is our new sign and spy at this point because we overwrote it. So let's just say it should be called and specifically with a request for npm package. So this is a good example of when you don't actually want to trigger certain functionality. I don't actually want to send off a request to install something, but I do want to ensure that we make a request to do that. So that's why we're using a spy here. So let's say our assertion, this spy was in fact called with that value.
Adding Webpack Rules and Plugins10:38
If I comment that out, it's going to fail as we can see. Excellent. All right. Next, webhook rules may be added. Yeah. So like we've discussed right up here, you need to be able to say, all right, we'll insert this rule into the webhook configuration file that you generate. So let's grab maybe this. This time we won't have dependencies, but we will have webhook rules, and I think what I'll do is just define kind of a stub here, and this is what it will usually take the
Okay. So that's our rule that we're going to say, all right, for this particular extension that we're creating, this rule needs to be added to the webhack configuration file. Let's trigger our new method here and then construct the webhack configuration. So we have that through a class called WebhackConfig, so I can just say WebhackConfig using everything I've given you here, construct your config file. So take a look at this. If we console.log config, and we're going to give that a run, here you can see is the full webhack config for the project. Now in our case, we want to inspect the module rules section.
So notice we're just working through our to-do list. The final one, webhack plugins may be added. Okay. It's going to be somewhat similar. Paste it in. Mix.extend. This time, we're going to say we want webhack plugins to be applied. And in this case, we're not actually triggering it with webhack. So we can just return, once again, a stub. Let plugin = sign.
So we can just return, once again, a stub. Let plugin equals signIn. You could do a spy, but in this case, we're not really interested in spying on it, I don't think. So a simple stub would be fine. And we'll return that. So now, once again, we trigger. So remember, when you call the method, that's what actually registers itself with mix. And that's what will then send off the process of applying all of the webhack rules, all of the webhack plugins, and stuff like that.
And that's what will then send off the process of applying all of the webhack rules, all of the webhack plugins, and stuff like that. So this is how we signal that we want that specific functionality. All right. So once again, let config equals new webhack config. Construct that for me. And once again, let's console.log that. And I'll show you what we get. So this time, we want to make sure that the plugin section reflects what we expect. Let's see.
Nobody should beat you up either way. Sometimes that TDD approach feels exactly right, and other times you might want to tinker around a little bit. It just depends. Or sometimes you want to tinker around, throw it all out now that you understand what you want, and then you start from scratch with TDD. Any of these options are fine. In this particular case, I was pretty happy with the production code, so we're only filling in the tests. The point being, you should never feel guilty or shamed if you're not following it the specific.
in the tests. The point being, you should never feel guilty or shamed if you're not following it the specific way somebody told you to. All of these are meant to be guidelines to help you. Sometimes I take a TDD approach as I've showed you, and sometimes I'll choose to do it afterwards. And that's what it means to be a programmer, making constant little decisions like this for yourself. But yeah, notice now, anyone new to our team can figure out exactly what it means to offer this plugin feature. They can read all the tests.
this plugin feature. They can read all the tests. Okay, so it looks like we have Mix, and it can be extended with new functionality by passing an anonymous function or even a class, and you can even request dependencies that should be downloaded, and you can add things to the Webpack rule set. You can add things to the Webpack plugins. It's a really nice layer of documentation.
