通过 Laravel Mix + Vue Router 路由懒加载实现单页面应用 JS 文件按组件分割
前言
随着单页面应用体量越来越大,将所有 JavaScript 代码都打包到一个文件,这个 JavaScript 文件也会随之越来越大,这样会造成的一个后果是在浏览器打开应用首页,会出现 JavaScript 文件加载时间过长而导致页面出现一段时间的空白现象,用户体验很不好。
好在 Vue Router 也考虑到这个问题,为我们提供了路由懒加载功能,通过路由懒加载,我们可以将不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件及组件对应 JavaScript 代码,这样用户访问的时候页面加载速度就能大幅提升,这篇增补教程就来为大家演示如何在 Roast 应用中实现前端路由懒加载。
如果你还不了解 Vue Router 的路由懒加载功能,建议花点时间去看下,Vue Router 的路由懒加载基于 Vue 的异步组件和 Webpack 的代码分割功能实现,看完官方文档了解实现原理后,可以看下 Vue-Router + Webpack 路由懒加载实现 快速了解实现思路。
在我们的 Roast 应用中,我们是通过框架自带的 Laravel Mix 进行前端资源的编译打包的,Laravel Mix 其实就是对 Webpack 做了一层封装,所以我们可以结合 Vue 异步组件 + Laravel Mix 很轻松的实现 Vue Router 的路由懒加载。
配置 Laravel Mix
由于在实现 Vue 异步组件的时候我们会用到 import()
函数并返回 Promise,而 Laravel Mix 底层使用的是 Babel 进行 JS 编译,所以需要安装 Babel 的 syntax-dynamic-import 插件,Laravel Mix 底层使用的 babel-core
版本是 6.*
,对应版本的插件安装命令如下:
npm install --save-dev babel-plugin-syntax-dynamic-import
注:该插件官方文档提供的安装命令安装的插件版本是
7.*
,无法适配 Laravel Mix 使用的 Babel 版本,会导致npm run dev
运行失败。
然后我们需要在项目根目录下创建 .babelrc
用来指定编译时使用这个插件:
{
"plugins": ["syntax-dynamic-import"]
}
最后,编辑 webpack.mix.js
中的 webpackConfig
配置如下:
mix.js('resources/assets/js/app.js', 'public/js')
.webpackConfig({
output: {
chunkFilename: 'js/[name].js'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules(?!\/foundation-sites)|bower_components/,
use: [
{
loader: 'babel-loader',
options: Config.babel()
}
]
}
]
},
resolve: {
alias: {
'@': path.resolve('resources/assets/sass')
}
}
})
.sass('resources/assets/sass/app.scss', 'public/css');
我们在 webpackConfig
中新增了一个 output
配置项用于指定编译后 JS 文件存放位置,由于会有多个 JS 文件,所以我们通过 [name]
动态指定对应文件名。
编写 Vue Router 路由懒加载实现代码
做好上述准备工作后,接下来我们将 resources/assets/js/routes.js
中的路由之前引入的组件都改成异步组件:
// 基于 Vue 异步组件 + Webpack 实现路由懒加载
function loadView(dir, view) {
// 注释不要去掉,对应上面 webpack 编译后的文件名
return () => import(/* webpackChunkName: "[request]" */ './' + dir + '/' + view + '.vue');
}
// 前端路由定义
export default new VueRouter({
routes: [
{
path: '/',
redirect: {name: 'cafes'},
name: 'layout',
component: loadView('layouts', 'Layout'),
children: [
{
path: 'cafes',
name: 'cafes',
component: loadView('pages', 'Home'),
children: [
{
path: 'new',
name: 'newcafe',
component: loadView('pages', 'NewCafe'),
beforeEnter: requireAuth,
meta: {
permission: 'user'
}
},
{
path: ':id',
name: 'cafe',
component: loadView('pages', 'Cafe')
},
{
path: 'cities/:id',
name: 'city',
component: loadView('pages', 'City')
}
]
},
{
path: 'cafes/:id/edit',
name: 'editcafe',
component: loadView('pages', 'EditCafe'),
beforeEnter: requireAuth,
meta: {
permission: 'user'
}
},
{
path: 'profile',
name: 'profile',
component: loadView('pages', 'Profile'),
beforeEnter: requireAuth,
meta: {
permission: 'user'
}
},
{
path: '_=_',
redirect: '/'
}
]
},
{
path: '/admin',
name: 'admin',
component: loadView('layouts', 'Admin'),
beforeEnter: requireAuth,
meta: {
permission: 'owner'
},
children: [
{
path: 'actions',
name: 'admin-actions',
component: loadView('pages/admin', 'Actions'),
meta: {
permission: 'owner'
}
},
{
path: 'companies',
name: 'admin-companies',
component: loadView('pages/admin', 'Companies'),
meta: {
permission: 'owner'
}
},
{
path: 'companies/:id',
name: 'admin-company',
component: loadView('pages/admin', 'Company'),
meta: {
permission: 'owner'
}
},
{
path: 'companies/:id/cafe/:cafeID',
name: 'admin-cafe',
component: loadView('pages/admin', 'Cafe'),
meta: {
permission: 'owner'
}
},
{
path: 'users',
name: 'admin-users',
component: loadView('pages/admin', 'Users'),
meta: {
permission: 'admin'
}
},
{
path: 'users/:id',
name: 'admin-user',
component: loadView('pages/admin', 'User'),
meta: {
permission: 'admin'
}
},
{
path: 'brew-methods',
name: 'admin-brew-methods',
component: loadView('pages/admin', 'BrewMethods'),
meta: {
permission: 'super-admin'
}
},
{
path: 'brew-methods/:id',
name: 'admin-brew-method',
component: loadView('pages/admin', 'BrewMethod'),
meta: {
permission: 'super-admin'
}
},
{
path: 'cities',
name: 'admin-cities',
component: loadView('pages/admin', 'Cities'),
meta: {
permission: 'super-admin'
}
},
{
path: 'cities/:id',
name: 'admin-city',
component: loadView('pages/admin', 'City'),
meta: {
permission: 'super-admin'
}
},
{
path: '_=_',
redirect: '/'
}
]
}
]
});
至于异步组件的实现原理,Vue 官方文档上说的很详细,我就不再赘述了,Webpack 在编译路由文件的时候,会自动将异步组件中的 JavaScript 代码单独提取出来,并且在访问对应路由的时候自动加载对应 JavaScript 文件,非常强大,你完全不用关心如何在不同页面加载不同 JavaScript 文件。
测试路由懒加载
代码编写工作到这里就全部结束了,下面我们在项目根目录下运行 npm run dev
重新编译前端资源,可以看到所有组件中的 JavaScript 代码都已经被分割开了:
最终我们的公共 JS 文件 app.js
的体积相比没有分割之前已经减少了很多:
然后我们访问应用首页 http://roast.test
,页面加载成功:
F12 查看 JavaScript 文件的加载情况,可以看到只加载了该页面所需要的 JavaScript 文件,而且加载之后就会缓存起来,下次访问这个页面不会重新加载:
6 Comments
只是大致做了一遍就感觉学到了很多,感觉以后还要继续研究一下,谢谢学院君!
接下来再写个服务端渲染(SSR)的教程, Vue.js 的所有功能特性就全部囊括了
厉害了!冲鸭!
在 Docker 安装 PHP 的 V8js 扩展给我整吐血了 各种下载失败。。
看到前面我就意识到这个问题?刚好这篇文章来得及时
感谢学院君提供了这么好的练手项目, 我是前端vue, 后端go 跟着这个项目练了下来, 一直到第7部分, 途中虽然有磕磕绊绊, 不过最后还是达到了我练习go和vue的目的. 后面的我大致看了下, 感觉能学到的也都差不多了, 所以准备到这里就收工啦, 最后再次感谢学院君!