从Laravel Mix (4.0.16)起,开始默认支持动态引入了(dynamic imports)。动态引入是一种资源文件分割(code-splitting)技术,可以让我们将js组件、第三方库及其他模块分割到单独的文件里。比如一个项目里,你用到了好些个js组件,有很多的vuejs或react组件,那么你最终打包的app.js或bundle.js文件就会很大,超过1M+是常有的事儿。这个时候,势必就会降低你的网站加载速度,尤其当用户处于一个慢的连接条件下时。
资源文件分割(code-splitting),可以将原本一个大文件,切分成很多小的文件,这样每个就可能只有几十几百K,而不是多少M了,这样就能极大改善加载速度。由于都切分开了,你就可以一开始只加载最想要的那块js文件,只加载页面上用到的,那些暂时用不到的,或者在其他页面才用到的,可以在背后自动去下载,就不影响一开始的页面渲染时间了。
配置动态引入
首先你得升级Laravel Mix到4.0.16
以上的版本,才能使用动态引入。
通过.babelrc
文件来配置
然后呢,在你的项目根目录创建一个.babelrc
文件,当然如果你已经有这个文件了,就在以前基础上更改即可。在这个文件里,将@babel/plugin-syntax-dynamic-import
加到“plugins”
这个array里面,这样呢就会启用laravel mix里已经带的自动引入插件。
{
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}
通过webpack.mix.js
文件来配置
当然了,如果你不想创建上面的.babelrc
文件,你对babel及其配置并不熟悉,你也可以在mix配置文件,也即webpack.mix.js
里做配置,可以加上下面的代码:
mix.babelConfig({
plugins: ['@babel/plugin-syntax-dynamic-import'],
});
这样你就不用创建或更改.babelrc
文件了
实际使用动态引入
原来我们引入一个js组件,经常是这样的:
// 标准的,或静态的引入
import Component from './components/ExampleComponent.vue';
现在,如果我们想着动态地引入一个组件,也即只有当这个组件在页面用到的时候,才去引入和加载,那么可以这样来写:
// 动态引入
const Component =
() => import('./components/ExampleComponent.vue');
默认的,Webpack会将这些动态引入的组件切块,切成单独的文件,以0.js, 1.js
这样的形式来命名。
Laravel Mix的在命名上,可以使用每个切块的名字,跟上这块内容的hash值,然后跟上.js
扩展。如果你想着具体设定每个分块文件的名字,那么在动态引入的时候,可以在组件路径前面,加上下面这样的一段注释
const Component =
() => import(/* webpackChunkName: "dynamically-imported-component" */ './components/ExampleComponent.vue');
可以看到这里,在vue组件具体路径前面,有一段/* webpackChunkName: "dynamically-imported-component" */
的注释,这就告诉webpack,我想要这块文件用这么个名字。那么这样,最终产生的文件名就是dynamically-imported-component.js
注意这个时候,这些分块的chunk文件,是直接放到你的public
目录下的,这不是我们想要的,我们想让它们起码是在public/js
目录下,那么这个可以在你设置分块名字时声明一下路径,比如/* webpackChunkName: "js/dynamic-component" */
,注意这里我们在具体文件名之前,加了个js/
路径,这样它就可以放到public/js
目录下了。
那么更进一步地,我们之前说了,可以在每个生成的文件名上加上这块内容的hash值,也即简单说文件名里包含一大堆随机字符,那么这个呢,可以在webpack.mix.js
里加上chunk分块文件的输出配置,像下面这样:
mix.webpackConfig({
output: {
chunkFilename: 'js/[name].[contenthash].js',
}
});
可以看到,最关键的是这里文件名里加了[contenthash]
,这样文件名上就包含有哈希值了,同时留意这里呢也声明了一个js/
的路径,这样的话,你在具体动态引入某个文件的时候,就可以不用声明路径了,就可以统一都放到public/js
目录下了。
在Vue-Router里使用动态引入
如果你项目里用到了Vue-Router,那么也就可以用动态引入,来将每个页面切分成单独的文件,可以像下面这样做:
const routes = [
{
name: 'dashboard',
path: '/dashboard',
component:
() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard.vue'),
},
];
[Vue warn]: Failed to resolve async component 问题的解决
可能你会非常兴奋地用上动态引入,可能你开发环境下使用npm run dev
或npm run watch-poll
一切都正常,可是呢,当你到了npm run prod
时,或者你npm run dev
也会有,你会发现你动态引入的组件,有的正常工作,有的呢却无法加载了,页面上没有任何效果了。尤其是当你dev编译是好的,但prod编译却是不好的,这种时候就最难debug,很难找到问题发生在哪里。
这个时候,如果你用dev编译有问题的组件,往往会看到类似这么个报错:
[Vue warn]: Failed to resolve async component: function () {
return __webpack_require__.e(/*! import() */ 6).then(__webpack_require__.bind(null, /*! ./components/bad.vue */ "./resources/js/components/bad.vue"));
}
Reason: TypeError: modules[moduleId] is undefined vue.common.dev.js:630
说无法解析某个异步组件,这是为啥呢?经试验,发现这是因为你这个组件里有<style>
标签,对,就是vue组件里都有的那个<style>
标签,原因是呢,动态引入编译的时候,相应的style-loader
没有被加载上,这导致了有<style>
标签的vue组件编译失败,很多人发现将<style>
标签删掉以后,就正常编译了。
但是,我们用vue组件,怎么可能不用<style>
标签呢?尤其是你先前已经有了很多样式时,你不可能轻松都移走吧。那么这个呢,似乎是一个官方的bug,更确切的说是webpack的一个缺陷,Mix的作者曾说在webpack 5发布之前,mix无法完全解决这个问题。
我们可不想听到这种解释,群众的力量向来是无穷的,于是有人发现,通过在app.js
引入一个空白的scss
文件,可以让mix在编译动态引入的组件前,就强行加载上style-loader,这样就能编译正常了。
具体做法是你随意创建一个空白的scss
文件,比如就叫empty.scss
,然后呢在app.js
里引入它,类似这样
import './empty.scss'
就这样,这样你的动态引入,编译就应该没问题了。
高级用法:脚本预加载
这样已经能够提升你不少页面的加载速度了,但是我们还不满足。现在假设有两个页面,分别是A页面和B页面,A页面是一个很轻量的、没啥组件的页面,而B页面假设用到了我们分离出来的一个my-component.js
。
那么这个时候,如果一个用户访问了页面A,然后他又点击了页面B,这个时候他就得等待my-component.js
这个文件去下载和处理——也即我们加速了页面A的加载,但是却降低了B页面的时间。因为之前不分离js文件的时候,当访问了A时,app.js文件就已经完全下载了,再访问B页面时就不需要请求了,就一般从浏览器缓存直接加载了。
如果怎么样,我们能让浏览器更智能一些就好了,比如说访问了A页面,我们就能预计他会访问B页面,这时浏览器可以在加载完A页面的脚本和资源以后,在合适的时候后台自己预先抓取B页面要用到的脚本,这样当访问B页面时,相关文件已经准备好了。
当然了,这肯定不只是个想法了,其实现在的浏览器都已经支持这样的做法了,使用传统的<link>
标签,我们其实就可以声明对某个文件的预加载,相当于在浏览器空闲的时候去背后获取。
<link
rel="prefetch"
href="/js/my-component.js"
as="script"
>
这样了以后,当访问了A页面,速度依然很快,当脚本都加载完了,浏览器就自动去获取my-component.js
文件了,当我们再访问到B页面时,也就不需要临时再加载什么文件了,已经都提前获取并放到缓存里了。
结论
这么好的一项功能,赶紧升级一下Mix,然后用到你的项目里吧,相信能提升不少你的网站加载速度。
本文是我们系列课程《Laravel&Vue深度整合实战第二版》的扩展文章,还记得课程里我们用到了Element-Ui,也即饿了么开发的vue ui组件,期间我们只是用了Element-Ui的几个模块,就导致我们的app.js文件瞬间膨胀到1M以上,在实际中,如果你严重依赖很多UI组件,那么你的打包文件好几M也都是很正常的。原来呢,我们得自行在webpack里进行各种设置,要么只是输出我们实际用到的UI模块,要么就自行实现今天说的文件分割效果,这对不怎么熟悉webpack的同学来说,挺恐怖的。那么现在,Mix默认简单地支持了资源文件分割效果,就大大解决了我们这方面的顾虑,让你在很多页面的效果体验上就可以更大胆、更有空间地做些事情了。