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

Introducing Laravel Mix versioning0:00

So here's what I'm working on today. I'm still working on the successor to Laravel Elixir, which we're now calling Laravel Mix, but we're actually focusing on php for this workalong. So let me give you a quick rundown. Here's our webpack.mix.js file, and you can see that we're compiling app.js down to the js directory, and app.scss down to the css directory. So if we take a look at that, we haven't compiled yet. Let's do that now. npm run webpack. Give that a second, and we should now see those. Okay, so this is great. And in fact, in your master page, yeah, you could import them like you always would, and there's no problem at all. But here's the thing we need to solve. We also have the ability to version files. So for example, if I said version, this means when you compile these down, we also want a unique hash, and this is great for long-term caching. When you change a file,

So for example, if I said .version, this means when you compile these down, we also want a unique hash, and this is great for long-term caching. When you change a file, it generates a new hash, which busts the cache. So yeah, if we compile that down again, you'll see that these will be replaced with the hashed versions. But yeah, the problem is, now in your view, we're referencing the old version. So yeah, we could manually copy that hash and update it, but you'd have to do this every single time you change your code, right? And who wants to do that? So ideally, we could set up a function called mix, and then just do something like this. If you give us a path, that's what we're going to do. And then the same thing here. Now this specifically is what we are working on today. So let's get started. We need to dynamically figure out what these file names should be, whether they are versioned, not versioned,

Goal: mix() helper1:25

here. Now this specifically is what we are working on today. So let's get started. We need to dynamically figure out what these file names should be, whether they are versioned, not versioned, and also even if they are being hot reloaded. I have to apply a special base URL, and we'll talk about that later. So if I go into my app directory, I'm importing this file. It's just a temporary thing. In real life, it would be stored in Laravel's helper files if this is part of Laravel 5.4. Okay, so it sounds like we have a mix function where you give it a path. So you call mix.js app.js, and it will read from Webpack's statistics file that it'll generate automatically for each bundle and figure out which file we should use. Now on that note, let me go ahead and show you this. When we do run Webpack, what will happen is within the storage/framework/cache/mix.json file, yeah, this is going to automatically be created by Webpack. So if we beautify that real

and show you this. When we do run Webpack, what will happen is within the storage/framework/cache/mix.json file, yeah, this is going to automatically be created by Webpack. So if we beautify that real quick, here's what I want you to take a look at. So Webpack has this assets by chunk name object, and notice that it contains each of the items here. So it sounds like when the user gives us mix.js / app.js, I just need to read from this and figure out if it matches up. Okay, and you'll see that this unique hash, yeah, well that is stored up here. So what I'm thinking is what we could do is just filter through this, get rid of the hash, and then see if that matches up with what the user provided within the view. Okay, so it sounds like we're gonna have to read this file. But you know what? I also want to write some tests, and I'd prefer not to open and read this file for every test. So maybe this could also accept some optional JSON that we won't require. Okay, let's

Writing unversioned test3:00

what? I also want to write some tests, and I'd prefer not to open and read this file for every test. So maybe this could also accept some optional JSON that we won't require. Okay, let's get started. Now, quick note, I am using the dev version of Laravel 5.4, so this test directory might look a little unique to you, but it should be fairly common sense. Okay, so we'll go to ExampleTest and get started. How about it determines the path to an unversioned file? So yeah, like if we were to say we're going to call mix, and we're going to give it js/app.js, and we're going to give it the JSON file, which is basically like our stubbed version of what would be created here. And really, we don't need all of this stuff. We really just need this, as well as the hash, so we can manually create that. Maybe something like this. First, let's grab the hash, and we'll hard code that to 1, 2, 3, 4, 5. And then also assets by chunk name. Actually,

so we can manually create that. Maybe something like this. First, let's grab the hash, and we'll hard code that to 1, 2, 3, 4, 5. And then also assets by chunk name. Actually, let me just grab all of this, like so. And then let's get rid of the hash for the time being. Okay, so it's an unversioned file. So what is generated here for our app entry point, and by the way, with webpack, you could have multiple so like you could have vendor. So I'm going to have to flatten that into an empty array. We'll think about that. But anyways, this should be all we need here. So now if I call mix js app.js, and we give it the JSON, it should read from here and just see, oh, it's the exact same name. So nothing should change, which means our assertion could be assertEquals js/app.js and compare that against the result. Okay, so let's run it. But of course, we're not doing anything here. So of course, it's going to fail. We're going to go

could be assertEquals js/app.js and compare that against the result. Okay, so let's run it. But of course, we're not doing anything here. So of course, it's going to fail. We're going to go into test/Unit. But it looks like we have an issue on line 16. Oh, yeah, we forgot to convert this to php. Sorry about that. So that should be an array. And then this should be an array. Anyways, if we run it again, it's going to fail. Because naturally, the mix function didn't return anything. So yeah, of course, we can always slime it if we want. And make a pass. But let's continue on var_dump the json. So you can see what you're getting here. And of course, we're getting this bit of data here. So to begin, why don't we grab this assets by chunkName, and that's going to be equal to an array of arrays. So I just want to flatten that down to a single array. So what we could do is collect that json assets by chunkName,

by chunk name, and that's going to be equal to an array of arrays. So I just want to flatten that down to a single array. So what we could do is collect that JSON assets by chunk name, and then flatten it into a single array. So we could say files. And then if we var_dump that, and run it again, yeah, now we have a collection that just contains those two items. And what's nice about this is, even if we do have multiple entry points, like I said earlier, like if we do have vendor, you know, if we did something like that, that's still going to be flattened to a single array. And that's exactly what we want. In this case, it'll make it a lot easier. So now we're going to read this file and just see if it matches up with what the user gave us. And if it did, we can return it. Okay, maybe something like this, collect all of our assets, and then flatten them. And then give me the first one. So this will be the compiled file. So we're basically saying

just so we don't have to worry about the entry point having a forward slash, but the compiled file not having a forward slash, you know, we don't want it to fail for that reason. So if there is a forward slash at the beginning or end of the file, then we'll get rid of it. So let's see if we trim that, and we compare that against the path that is given to us. If that matches, then that's the one the user wants. And if we run that, it's going to fail. So failed asserting that null matches JS/app. Yeah, and actually, you know what, we might have to trim that one as well. That gets kind of annoying. We'll see if we can get rid of that. But anyways, if we run that, yeah, it passes. So I hope that's not too confusing. And in this case, we could just return that directly, and we'll still get green. So collect all of the items from the JSON, flatten it into a single array, and then just give me the very first item from that array,

Handling versioned assets7:42

return that directly, and we'll still get green. So collect all of the items from the JSON, flatten it into a single array, and then just give me the very first item from that array, where that compiled file equals what was passed in right here. Okay, so in this case, it didn't really do much because it's the exact same path. But what if we do another one, and this time it is a versioned file? So let's try that. It determines the path to a versioned file. In that particular case, the output from Webpack will end up being something like this. So if we pass that in, then what should be returned is this. Make sense? We give it the normal URL. It reads from the JSON. It finds that this is actually the one you want. So it returns that from the function, and we see if they line up. All right, so let's move on to this phpunit filter. And it does fail. Of course it does. So take a look at this. If we come back,

and we see if they line up. All right, so let's move on to this phpunit filter. And it does fail. Of course it does. So take a look at this. If we come back, why don't we just var_dump the compiled file for each iteration through? And what we'll see here if we run this, yeah, there's the first one, and there's the second. So we just want to compare this. Here, if it's easier. I know it's kind of hard to explain in a screencast. We want to compare that against this, right, that we're giving in. So it sounds like we should just replace this right here with nothing, and then see if the two are the same. Let's try it out. Maybe we will rename this, and we'll say compiled file or reassign it. And we're going to say string_replace, and you're going to look for the hash. Well, if you remember, we can use json hash to grab that. Okay, so let's use the json, and then we're going to say

Supporting hot reload URLs9:13

And we're going to say string replace, and you're going to look for the hash. Well, if you remember, we can use json hash to grab that. Okay, so let's use the json, and then we're going to say look for json hash, and then a period. So to show you, we're basically saying look for this, and then a period, and replace it with nothing. And then that should line up with what the user gave us. So let's do this. Look for that and a period, replace it with nothing, and we are working with the compiled file that you gave us. And then finally, we are going to trim it like we did before. Okay, so now we're going to return, does the compiled file equal what you gave us? We run it, and we get green. So now that's working. Great. Next though, I'm going to introduce something new. Let's grab this, create a new test, and it determines the path to a hot reloaded file. This is something new that I'll show in another video. But basically, in order to link to a hot

something new. Let's grab this, create a new test, and it determines the path to a hot reloaded file. This is something new that I'll show in another video. But basically, in order to link to a hot reloaded file, the base URL needs to be localhost:8080. So this is another thing we have to figure out. Is hot reloading enabled? If so, then this should be the base URL. All right, let's figure out how to do this. Now, if we go back to Mix, I happen to know that we're actually going to use file_get_contents to figure some of these things out. But we could also accept shouldHotReload here, and then set that to false. Now still, in the view, the User only wants to call Mix. They don't want to deal with this. But for the purpose of testing and configuration, we will allow these if you want to override them. Okay, so that means now I'm going to set hotReloaded to true. This is a little tip, by the way. If you ever need to pass a Boolean into a function, you know,

if you want to override them. Okay, so that means now I'm going to set hotReloaded to true. This is a little tip, by the way. If you ever need to pass a Boolean into a function, you know, six months from now, what does true mean there? Sometimes just setting a temporary variable there actually can be useful, even if you never use it. I think it's very much worth it for the readability alone. So let's do this. We will not have versioning here. And what I want returned is localhost:8080. So that's ultimately what I want if we have hot reloading enabled. All right, let's run it. And it fails. Of course it did. We expected this, but we haven't added that functionality. So maybe we can do this. Our path will be that. And then we'll just say return. And we'll say should we hot reload? If so, we'll do localhost:8080 and then tack on the path.

So maybe we can do this. Our path will be that. And then we'll just say return. And we'll say should we hot reload? If so, we'll do localhost:8080 and then tack on the path. Otherwise, we'll just return the path. Okay, let's rerun that. And it still fails. Division by zero. Oh, whoops. Not sure how that happened. Ah, we get green. So I think at this point, that means all of these should be returning green. And they are. It's a nice way to work. But one other thing. Right now, we are just returning a relative URL from the root of the project. But I think what I really might want to do is wrap that within a call to URL so that what gets returned here would be myapp.com/js/app. You know, we're just going to replace that with an absolute URL. So let's run this again. And it looks like we're gonna have to update a couple of things really quickly. Now, we're expecting that but we're getting a full URL in response.

absolute URL. So let's run this again. And it looks like we're gonna have to update a couple of things really quickly. Now, we're expecting that but we're getting a full URL in response. All right, so this is an easy fix. We're just going to visit our assertions and make sure that we wrap them within a call to url so that they do line up. So we have that one, that one, and in this case, that can be hardcoded. So if I run it again, now we get green. Great. So let's take a look at where we are in our mix function. You give us a path, we collect the JSON, we flatten it to a single array, and then we find the first one where the compiled file, once you remove the hash that may or may not be applied to it, equals what you sent through this function. And once we have that path, if we should hot reload, then we will prefix this. And actually, why don't we do this? Why don't we say, let's inline it there. Is that still working? Yeah. Anyways, if we should

Loading and caching manifest13:17

have that path, if we should hot reload, then we will prefix this. And actually, why don't we do this? Why don't we say, let's inline it there. Is that still working? Yeah. Anyways, if we should hot reload, let's add a custom base URL. Otherwise, we will use our site_url. So that's going to make our tests pass. The only other thing I want to do is make it easy to reference this from a view where we dynamically fetch the actual file that is stored within a framework/cache/mix. So we could do something like this. If you did not give us a JSON file, then I'm going to say static JSON, and we will manually fetch that. And the same thing will be true for should hot reload. And I'll explain why we're making that static in just a minute. So now we can say, if we still don't have JSON, then the manifest_path is going to be storage_path. And we want to go into framework/cache/mix.json. Next, should hot reload? Well, the way we do that with Laravel

don't have JSON, then the manifest path is going to be storage_path. And we want to go into framework/cache/mix.json. Next, should hot reload? Well, the way we do that with Laravel Mix is it creates a temporary file in the same destination. So if we did something like this, file_exists(storage_path), and then it's just called hot. So if this file exists, that means Laravel Mix is telling us that hot reloading should be enabled during development. Now though, what if we run this, but this file doesn't exist, we do need to check for that. So we could say, if not file_exists(manifest path), then I can't proceed. So I'm just going to throw an Exception. And let me figure out a message behind the scenes. Okay, looks good enough. The Laravel Mix manifest file does not exist. Please first run webpack and try again. Otherwise, if the file does exist, then and only then we could say $json = json_decode(file_get_contents(...)

mix manifest file does not exist. Please first run webpack and try again. Otherwise, if the file does exist, then and only then we could say JSON equals json_decode, good old file_get_contents, provided the manifest path. And then I will pass true so that we can fetch it as an associative array. And I think that should do the trick. Okay, real quick, let's go over static because this could be foreign to you. So we're setting static within a function. This means imagine you call this function three times, or two, the first time you get your app.css file. So we hit this function, JSON does not exist. So we declare it as static. Then all the way down here, we decode and file_get_contents. So now what will happen is the next time you call this mix function, because this is static, it will already be equal to the decoded JSON here. So effectively, it's just a nice way to cache it so that we don't run this over and over for every single time you.

because this is static, it will already be equal to the decoded JSON here. So effectively, it's just a nice way to cache it so that we don't run this over and over for every single time you call mix. It should only happen one time per request. So that's why we're making it static. So that means if we run this, our tests are still going to pass. And actually, on that note, what you might want to do, we might want to write one test to make sure that this logic works. And I'll show you how you can do something like that. Once again, we're going to copy this. Actually, let's start from scratch. How about it pulls from the storage path to determine the file path. That's a little weird, but it pulls from the storage directory and reads that JSON. So from the storage, the mix.json file to determine the file path. I don't know. Good enough. So that means we'd want to do something like this. I'll paste it in. Given we have JSON

So from the storage, the mix json file to determine the file path. I don't know. Good enough. So that means we'd want to do something like this. I'll paste it in. Given we have JSON like this, let's file_put_contents or we can use File facade. And we're going to put to storage_path. So we're manually going to create that file. And we're going to give it json_encode that JSON. So yeah, whenever you're dealing with things like this, sometimes it's a good way to go just create the world that your test is going to live in. So rather than figuring out like we're going to inject all of these in a file dependency and mock it out, it gets so complicated for simple function. So instead, yeah, we're just going to say, okay, this is the world that the test expects. So we're going to create it, create the file, add this, and then we're going to call it mix/js/app.js. And then we perform our assertion. So assertEquals js/app.js against the result.

So we're going to create it, create the file, add this, and then we're going to call it mix/js/app.js. And then we perform our assertion. So assertEquals js/app.js against the result. And then finally, we might want to delete this. So we could say File::delete($file) because we're done with it. Okay, let's give this another test, we're going to trigger our entire suite, but it does fail. Oh, yeah, because it does prefix that. So let's wrap that within a call of url(), run it again. And now we've tested this as well as we could possibly want to. And honestly, it's even debatable if we would need to write tests for this. This is the sort of thing you'll write it once and almost never come back to it. So in those situations where there's not a lot of change, the tests, in this case, are mostly just for your peace of mind and to help your workflow. They're not overly necessary. But yeah, I mean, this is going to be the code I end up using,

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