تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Elixir Project Overview0:00

Today I'm working on Laravel Elixir. So as some of you may know, at the time of this recording, I have a baby on the way in like one or two weeks, so we're down to the wire. I'm kind of freaking out to be honest, but one of the things on my list is to get the next version of Laravel Elixir ready to go and ready to launch. Okay, so that means I've been cleaning up the API, doing a number of things. So I'm going to give you a quick overview, a very crash course overview, and then we'll work on a little refactor I need to do. Okay, so here's the source for Laravel Elixir. So basically, if we take a look at the index file, this is what loads whenever you require Laravel Elixir within your Gulp file. So you'll see this at the top of your file, something like that. This is the file you're pulling in. Okay, so you can see I make Elixir available as a global. I do that so that any task immediately has access to Elixir without having to

something like that. This is the file you're pulling in. Okay, so you can see I make Elixir available as a global. I do that so that any task immediately has access to Elixir without having to import it every single time. I could be wrong, but I'm of the mindset that one or two globals can actually be pretty useful in your projects. So Elixir is just a function. So this is what you would call, for example, when you say Elixir and then you accept the mix and you say something like mix.sass. Yeah, this function corresponds to this. So notice you pass in your recipe. Your recipe is this function right here that contains everything you want to do. Maybe something like that. That's your recipe, and that's how I refer to it. Anyways, so we initialize. That just does some last-minute stuff. Then we require all of the tasks. So the way Elixir is set up, every single task is stored within its own file. So here's the task task, and that's like if you want to do

some last-minute stuff. Then we require all of the tasks. So the way Elixir is set up, every single task is stored within its own file. So here's the task task, and that's like if you want to do something custom. Here is the gulp watch task. Here is the copy task. Here is the sass task, right? So we require all of those, and each of them registers themselves with Laravel Elixir. So like for copy, you can see we extend Elixir with a new task called copy, and the logic for that is stored right here, and that's usually where you'll find the typical gulp specific logic. And then down here is where I register any files to watch or any files to ignore. So for example, this means watch all of the source files that you pass in, but I want to ignore the output file itself. I don't want to watch that for changes or it would run twice, right? So we ignore that file. Anyways, so we require them in. They register themselves with Elixir, and then here I trigger

itself. I don't want to watch that for changes or it would run twice, right? So we ignore that file. Anyways, so we require them in. They register themselves with Elixir, and then here I trigger your function. So that's where you did mix.sass.scripts. I trigger that, and that's the point where this specifically runs. So we only trigger these functions if you register them within your gulpfile.js. And then finally, we filter through all of the tasks that were registered, and for each one, we create the gulp task and then run the default task. Fetch all the task names, handle any before hooks, and then as long as we have some tasks, meaning as long as you did mix.something, as long as we have them in sequence, we will apply each gulp task. And that's all that's happening here. Then down here, we have some bootstrappers type stuff. We allow you to extend Elixir. You can add some hooks. You can add some dependencies. So for example, if you want

Reducing Task Duplication3:23

that's happening here. Then down here, we have some bootstrappers type stuff. We allow you to extend Elixir. You can add some hooks. You can add some dependencies. So for example, if you want to take a look at set dependencies, this is just where I set some common helpers. It's where I add the dependencies directly to the Elixir object and stuff like that. All right, so now that you understand the basic setup for Laravel Elixir, I'm going to show you something I'm working on. Lately, I started to realize that so many of the gulp tasks share similar logic. So it's only a couple of things that will change, but most of them will probably have some kind of source map support. Most of them will be able to save to a path. Most of them will be able to set the source and stuff like that. So in the past, like you see with this copy file, I would just store the whole gulp logic directly here. And if there is a little duplication in another task, I would either

and stuff like that. So in the past, like you see with this copy file, I would just store the whole gulp logic directly here. And if there is a little duplication in another task, I would either extract it to a module and share it that way, or I would just let the duplication live there. But lately, I've decided to clean it up a little bit. So now you'll see that, for example, in the sass task, well, yeah, as you can imagine, sass or less or stylus or another one, they're all pretty similar in terms of the gulp logic. It's just probably you're using a different compiler plugin. You might use a gulp-sass plugin or a gulp-less plugin. But other than that, they're pretty similar stuff. So what I've done is I'm now referencing compiler specific objects. So you can see I have a CSSCompiler. And if I take a look at that, here is where you can see the gulp logic. And this allows me to make it that much cleaner. So again, like before, you have this stuff, and

ES6 Compiler Architecture5:47

definitely makes it a little cleaner versus always having to do stuff like gulp if this condition, then minify it, otherwise don't. Yeah, it gets kind of gross. Now, the way I'm doing this is with ES6 classes. So some people don't like this in ES6. I do like it. It feels more familiar to me, obviously, coming from the server side predominantly. I feel like it fits my brain a bit better. So you can see I have a MasterCompiler class. This would sort of be like an abstract class in PHP, where I set the options and then I have some common methods, like to initialize source maps, and that just defers to a plugin, to write to the source maps, to concatenate files, to set the destination for a file, to handle errors and success and stuff like that. Then any specific logic can go in the compiler, like the gulp sequence itself, how it compiles, how it auto-prefixes, and stuff like that. Okay, so I would like to apply this to a PHPUnit and

Creating Testing Compiler6:36

Then any specific logic can go in the compiler, like the gulp sequence itself, how it compiles, how it auto-prefixes, and stuff like that. Okay, so I would like to apply this to a PHPUnit and PHPSpec. So you can see, yeah, I had the same situation here with shared logic, where whether it's PHPUnit or PHPSpec or if you have a bahad extension or a colon, they're all still going to take the same form. So the first step, what I had done a while back, was I just extracted a module there called tests. And if we take a look at that, yeah, notice that's just where I stored the similar logic. But now we're going to turn that into a custom testing compiler and see if we can clean this up. We'll take a look at the before and after and decide if I want to keep this. All right, so let's create a new class. And I will call this, how about TestingCompiler.js. And we can even steal some of this stuff to get a head start. So TestingCompiler,

All right, so let's create a new class. And I will call this, how about testingCompiler.js? And we can even steal some of this stuff to get a head start. So testingCompiler, I'm importing the parent class, and I will not be using cleanCSS. Okay, next, I can get rid of all of that stuff. And in fact, auto-prefix, most of that should be removed. Okay, and then finally, we export the module. Okay, next, so we'll go with phpunit to start. Now I would do something like this. I would say new elixir.task called phpunit. The compiler I'm going to use will be import compiler from compilers/testingCompiler. And then I would pass that in here. And then do I need to set up a watcher? I think I do. Yeah, so then this junk right here would be placed after here. Yeah, so new up the task. That's going to give me this class. And then on top of it, we're going to set files to watch and monitor for changes. And you'll see if we take a look at that,

placed after here. Yeah, so new up the Task. That's going to give me this class. And then on top of it, we're going to set files to watch and monitor for changes. And you'll see if we take a look at that, we're just adding a regular expression to this watchers array. And then when I do watch this, watchers array, and then when I do watch the files down here, you can see that I fetch that array of watchers off of the object. So that's it. When you say .watch, we add to the object. And then when you trigger gulp watch from the command line, we fetch the watchers off of the instance, and then we pass them to gulp.watch. Very, very simple. So in this case, we're watching the source directory. We're also watching the app path. So any PHP file within your app directory, and then also any PHP file within your views directory. So I think this is looking good. That means I can get rid of that chunk. Basically, what I'm trying to do is get rid of the shared

and then also any PHP file within your views directory. So I think this is looking good. That means I can get rid of that chunk. Basically, what I'm trying to do is get rid of the shared file and move it into a compiler file. So I'm just going to comment out as I go. Okay, so I'm going to grab this stuff right here. And I'm going to move it into my testingCompiler, and then add it right here. Okay, so next, I would clean some of this stuff up. So one, I don't have this $variable, I can just reference elixir.plugins. And that's basically going to give me my gulp shell, gulp plugin. And next, I'm going to need the command to run. So if we go back to our old implementation, you can see that I set the sourceDirectory equal to whatever the user passes in, or any test files within layer files testing folder. But then I also set the command equal to once again, whatever you want to do if there's a custom path to phpunit,

passes in, or any test files within layer files testing folder. But then I also set the command equal to once again, whatever you want to do if there's a custom path to phpunit, or otherwise, I will set a default here. So maybe I can do that up here, I knew up the testing compiler, I'm going to give it the source and the command. And then maybe I can set these defaults behind the scenes. We'll see. Okay, so up here, I'm going to set an explicit constructor. And that will accept the source as well as the command. Is that right? Yeah. And then I will assign these. Okay, create a new testing compiler instance. Okay, so that will accept a string, or no. And the command once again will be string or no. Okay, now I'm thinking I could either set the defaults up here, or I could have a dedicated method, something like getSource and then set the defaults there. I don't know. But for now, I'll put it in the constructor. And then later,

the defaults up here, or I could have a dedicated method, something like getSource and then set the defaults there. I don't know. But for now, I'll put it in the constructor. And then later, I can extract it if I don't want that. Yeah, I may extract that. Okay, so we accept that through the constructor, we pass them through. And then this, this toGold method here, that's actually triggered by the Task object. So when we run the task, you can see that well, if you gave us a Compiler class, then that means you didn't do something like this, where you gave me an anonymous function with your goal task. Instead, you gave us an object, in which case, I'm sorry, I got rid of it. In which case, we should just call the toGold method and then pass through the task. So that means this will be called whenever you run the task. Okay, so let's reference our commands. And then I could say, on error, I want to do this on error. And I can get rid of this stuff. Let's add

means this will be called whenever you run the task. Okay, so let's reference our commands. And then I could say, on error, I want to do this on error. And I can get rid of this stuff. Let's add that on error, paste that in. And change this to a new Elixir notification for failed tests. And if we take a look at that, here's my notification class. And there should be a for failed tests. Yeah, and that just, it's just kind of a helper to handle the OS notification where it pops up with the red X and says your tests have failed. Next, we're going to do the same thing here. So I will say on success. And it's basically going to do the same thing. New notification for past tests. Now, the name, where am I getting the name from? Let's go back here. The name is coming from Oh, it's the test name. Okay, so if we go back, I can say this.task.name. Yeah, I'm getting the name off of the task instance. Okay, is this boring

Let's go back here. The name is coming from Oh, it's the test name. Okay, so if we go back, I can say this.task.name. Yeah, I'm getting the name off of the task instance. Okay, is this boring to watch? Definitely stop watching it. This is boring. It's not as these videos aren't as seamless as some of the other lyricist videos because they're a bit more real life. So I'm kind of figuring it out on the fly, which is always harder. Oh, and by the way, I've been getting into this thing where I like two spaces between the methods. I don't do this with php. But for some reason with JavaScript, I want a little more air a little more breath between them. Anyways, let's go over this. So when we run it, we assign the task. And then we say gulp.source. That's because there are no source files for this particular task. I pipe it to a shell command. That's basically just going to run vendor/bin/phpunit. And then I handle any errors. And then

That's because there are no source files for this particular task. I pipe it to a shell command. That's basically just going to run vendor/bin/phpunit. And then I handle any errors. And then here, we can update that to pipe to this .env on success, and trigger that. What else? Maybe up here. Again, this is totally fine. But I'm just kind of getting into this habit of making it very, very clear what each pipe does. Because that can get tricky. So I will say this, how about runTests? Is that what we want? And then add that here. So yeah, it creates a little bit more code. But I think it's worth it because it's easier for me to come back to and understand each step versus, for example, the the version task we have, it's honestly just a big mess, we need to work on it. It's next on my list. But yeah, you end up with a bunch of this crap where when you've been away from it for months, it's hard to come back to. So I'm switching over to

about there. So I could get rid of this. Oh, and by the way, you're probably thinking about this right up here where we set the defaults. Obviously, I would make that dynamic in just a minute. Maybe that could just refer to the this->task->name. So if the task is phpunit, then we would just interpolate that there. I'm just hard coding it for now. And the same would be true up here. Okay, so I'm thinking we can at least try this out. So extend phpunit, we're going to accept the source and commands, or nothing at all. That way I can just do mix phpunit, and it'll just work. Then I new up the task, I'm referencing a new testing compiler, and then I register some watchers here. Now once again, I wonder if that's something I could do on the compiler itself. So for example, maybe I could do here and then just say this->task, that could be an option as well, and kind of get that out of the gold file. And that way,

Compiling and Debugging15:59

on the compiler itself. So for example, maybe I could do here and then just say this->task, that could be an option as well, and kind of get that out of the gold file. And that way, this would apply. Yeah, because I think this would work because these watchers will apply to phpunit or phpspec as well, I think. Anyways, we're going to keep it like it is for now. And I think I'm ready to try this out. Now let me make sure that everything compiles down. See if I made any error. I did. Okay. Testing compiler constructor. What's the problem? Oh, okay. It's because we're extending a parent class, but I didn't call parent::__construct(). So right here, I would need to say parent::__construct(). Okay, that seemed to compile down. I already have a symlink set up to a test application, so let's try that one. Okay, so here's the folder. Let's open this in MacVim just to make it clear to

Okay, that seemed to compile down. I already have a symlink set up to a test application, so let's try that one. Okay, so here's the folder. Let's open this in MacVim just to make it clear to you which file is which. Okay, so I already have this set up, mix .phpunit. So if we run this, we'll see if I made a mistake. I probably did. Yes! Okay, phpunit line 21, testing compiler is not defined. phpunit. And we're fetching from compilers, testing compiler. Compiler, testing compiler. Oh, I thought I changed that. Compiler. Okay, so behind the scenes, I set a watcher up for that, which means it should be symlinked again. So if I run gulp, we'll probably get another. Damn it. Real life sucks. Okay, name, name is not defined for the onSuccess method. Yeah, right here, I'm using name, and I forgot to reference that properly. So what I can do is grab the name off of the task instance right here. So that would be phpunit.

on success method. Yeah, right here, I'm using name, and I forgot to reference that properly. So what I can do is grab the name off of the task instance right here. So that would be phpunit. But I think I have a problem. Yeah. So here's the issue. This, I can't do this. I can't grab that. And that's because within this context, this is going to refer to something else. So you can see when an error is thrown, we trigger this function, at which point this is going to refer to something I don't even know. Some kind of gulp transpiler, transformer, thingamajig, I don't even know. So how do I get the task name here? I can't pass it through. Yeah, see, I can't do anything like this, and then just say this.task.name, because I still need access to the gulp thingamajig. So what do we do? We call the function immediately. So on error, we'll have this equal to the testing compiler instance. That way, I can say let task equal this.task.name,

thingamajig. So what do we do? We call the function immediately. So on error, we'll have this equal to the testingCompiler instance. That way, I can say let task equal this.task.name, and then I return a new function. Yeah, I think that'll do the trick. So now, I take these two dudes, they go there, and then this becomes task. Yeah, I think that works. Does it? I hope it does. So we've changed it to say when an error is thrown, do whatever is returned from this function. And that happens to be this. So handle it in that way. But now, because of closures in JavaScript, I still have access to task even within this returned function. Okay, so now for this one right here, on success, yeah, we're calling that differently. So this is still going to refer to the testingCompiler instance. So I can just say this.task.name and return that. Yeah, all right, let's try it again. That has compiled down. So if I run gulp, yeah, you're seeing errors, but this is

Verifying Refactor Success19:35

the testing compiler instance. So I can just say this.task.name and return that. Yeah, all right, let's try it again. That has compiled down. So if I run gulp, yeah, you're seeing errors, but this is testing errors. Okay, so we ran phpunit. The test failed because we expected it to see layer of L5, but I guess we have some junk from a previous lesson or something like that. So let's make a pass just for fun. Let's go to welcome. And yeah, this is from some old lesson. What do we expect to see? layer of L5. All right, if I run it again, we get green, which means we're very much on the right track, which means I can get rid of this shared module. That makes me feel good. phpunit is now simpler than it was before. I just knew up a task, and I passed through a compiler, and then everything else should work, which also means now, yeah, whether I had phpspec or behatch or something else, I can get rid of all this shared junk and instead replace it like so.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟