An Unexpected Journey!
In this article, I’ll try to share with you an overview of the Laravel Mix 6 and Babel 7 migration journey. The aim is for you to have different perspectives about the problems that you may encounter during these packages migration and solutions.
PS: This article is not a guide about how migration should be done. This is about how we did it.
At the beginning of the journey, the expectation was easy about migration like the previous ones we did. I mean remove the old versions and install the new ones and tada! The migration is done and all is well.
I wish I could say that it is easy like I said but not this time :) Anyway, let’s get into our journey and how we did it.
Old but not Gold Versions
Here in the front-end team at Insider, we have decided to get rid of old dependencies. Because the old versions of dependencies were causing some troubles like having vulnerabilities in sub-dependencies and outdated features in packages. During our research, we saw that there are two important packages that should be upgraded in projects. These are Laravel Mix and Babel.
Laravel Mix
What is it?
It is not the goal to introduce the package of this article but I would like to give you a short description of it to cover all things clearly. During the article, I will be saying Mix instead of saying, Laravel Mix.
As all of you know, Webpack is an incredibly powerful module bundler that prepares our JavaScript codes and assets for the browser. The only understandable downside is that it requires a bit of a learning curve. In an effort to flatten that curve, Mix is a thin layer on top of Webpack. It exposes an API for dynamically constructing our Webpack configuration. This was a brief introduction to Mix. For further reading, you can take a look here.
Upgrade to Mix 6
Mix 6 ships with support for the latest versions of numerous dependencies, including Webpack 5, PostCSS 8, Vue Loader 16, and more. The previous sentence was explaining what we need to be free of many old dependencies. Since these are significant releases with their own sets of breaking changes, changing the version of the mix in package.json was the easiest part.
In light of all these explanations, it is time to explain the technical details. So, here we go;
I installed the package to the latest version with the installation command right after I read the migration guide as shown in the example below:
npm install laravel-mix@latest
After installing the latest version, I have tried bundling the app as the migration guide suggests. While bundling the app, I have seen errors in the terminal screen about some deprecated usage of package. I have encountered with those errors in different build tries of app but you can see the overview of the errors as written in below:
- Deprecated build scripts
- Old configuration
- Deprecated usage of CommonsChunkPlugin
- Deprecated css selectors /deep/
The first issue that I have focused on is the build scripts. A number of options that our NPM scripts were likely referencing were removed with Webpack 5. So I changed the scripts accordingly. You can see the example below.
Before;
"scripts": {
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}
After;
"scripts": {
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"production": "mix --production"
}
And I switched over to the new Mix CLI as written in the guide.
After these changes, I tried to rebuild the application and I saw some different errors, these were related to Vue.js that build script could not find Vue.Js configuration to build Vue.Js files. Then, I took a look at the migration guide to see what was going wrong with it. After reading it, I have realised that; In the old version, Vue.js support was provided by default. This means when you use Vue.js in your Laravel project there is no need for any other extra configuration to bundle your Vue.js single-file-components. In the new one, the configuration has changed and Vue.js support has been extracted to its own “featured flag”: mix.vue() and I changed the configuration of the mix file.
Before;
mix.js('resources/assets/js/app.js', 'public/js');
After;
mix.js('resources/assets/js/app.js', 'public/js').vue({ version: 2, extractVueStyles: false });
Then I saw that there was an error about CommonsChunkPlugin that we were using in our configuration to split chunks, and the plugin is deprecated with Webpack 4. As a result, I removed the configuration.
PS: There is another plugin to configure the chunks but since this is another topic I won’t be mentioning that. If you would like to read about it. You can take a look this article.
Another issue that I have focused on is the /deep/ selectors. It has been deprecated, and the documentation suggests a new way ::v-deep. we have changed all usages.
Finally, there was not any error in the terminal about bundling. So, I thought that we were done and all was clear. This means we were good to go. But the expectation was wrong and there was another error in the console that I faced. That was the babel package error known as the second hero of our journey.
I will be talking about this update later in the article. You can find all about it in Babel section. Let’s go!
Babel
What is it?
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards-compatible version of JavaScript in current and older browsers or environments. At least the documentation gives that description. So to get further information you can take a look here.
The example given below showcases what babel is doing in actuality.
// Babel Input: ES2015 arrow function
[1, 2, 3].map(n => n + 1);
// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
return n + 1;
});
The example was taken from the official documentation.
So, since we are done with the definition we can continue our journey by telling you what we lived through during the upgrade.
Upgrade to Babel 7
The installation part remains the same as it was for Babel 6. The only difference in Babel 7 is that all the packages need to be installed with @babel/, for example, @babel/core, etc.
Within this information, I have installed all Babel dependencies with the command as in the following example.
npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env
npm install --save-dev @babel/runtime-corejs3
npm install --save-dev @babel/plugin-transform-spread
npm install --save-dev @babel/plugin-proposal-object-rest-spread
The new update of Babel is a big deal, there are quite a few substantial changes and improvements that this upgrade delivers. All those improvements are useful and they all help us to speed up the process of finding just the right setup for your project. Since the improvements and what Babel 7 brings new to the ecosystem are not this article’s subject, I won’t go deep diving into them. But, If you would like to see more about them, I will be leaving some sources above for you to read more about Babel 7 features.
Babel 7 official release documentation is here. If you would like to get brief information about what is new in Babel, you can take a look at this blog post.
Now that we have given the resources on the subject, we can continue to describe our experience.
After the installation, there was one thing to do to complete our upgrade process. This was to update our configuration in line with new improvements. There are different ways to configure babel based on your project needs. You could use either .babelrc, .baberc.js, or babel.config.js or simply configure it straight in the babel-loader options in your build script. You can take a look at the official documentation to find out the correct configuration method. babel.config.js was the right option for us because we use a monorepo. Accordingly, we created the babel.config.js file and organised our configuration as you can see below.
Before;
{
"presets": ["stage-2"],
"plugins": ["transform-runtime"]
}
After;
{
"assumptions": {
"iterableIsArray": true,
"setSpreadProperties": true
},
"presets": [
[
"@babel/preset-env",
{
"targets": "defaults",
"useBuiltIns": "usage",
"loose": true,
"corejs": "3",
"debug": false,
"shippedProposals": true
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": { "version": "3", "proposals": true }
}
],
["@babel/plugin-proposal-object-rest-spread", { "useBuiltIns": true }],
[
"@babel/plugin-transform-spread",
{
"loose": true
}
]
]
}
In this part of our journey, we’ll go back a bit and talk about mistakes as we mentioned earlier. We only had one problem though :)
After configuration and all the processes of upgrading is done, I tried to build the project and there was not any error on the terminal in the building process. But, guess what? When I went to the project page in the browser, it was empty. A huge and white empty page. I saw that there was an error in the browser’s console about the spread operator that we used somewhere in our project. The usage of that operator was wrong and that was the reason for the error in the browser. But, there was a question in my mind, why? Why are we faced with the error now? Because the wrong usage of the operator was there for a long time and it was working fine. When I read the official documentation, I saw that the spread operator usage control has changed with the new version. With this info, I corrected the misuse. In the end, I rebuilt the project and I could see everything working fine.
Conclusion
It was quite an exhausting journey. I think this was the result of upgrading between two major versions.
I prefer to see the positive side of the journey. Even though we have faced some unexpected issues, during the journey we learned a lot of things and it was quite nice. Now, we have latest versions of these packages in our project. On the other hand we eliminated possible vulnerabilities which can be defined in these packages and their dependent packages. Last but not least, we can use any other 3rd party package such as Jest with its latest version in our project.
Thanks for reading! If you enjoyed this article, you can check our Insider Engineering Blog to see different articles that are written by our tech team.