首先,咱们要知道什么要用webpack来打包,这样打包有那些好处。咱们能够简单的列出如下几点:javascript
这篇文章的重点讲的就是webpack打包之优化浏览器缓存管理,vue-cli生成的脚手架的配置中,已经作了不少对于打包,利用缓存的优化处理,本文未来学校其中知识,而且作出改动。css
在此以前呢,咱们须要先了解浏览器缓存是怎么工做的,先抄了一张图。html
过了一个星期,再次访问这个页面vue
应产品经理需求更改了一个图标java
弄清了原理,咱们就知道怎么去破坏缓存机制,让浏览器请求到新的文件。node
手动强制刷新页面,可是用户不是程序员啊,他们怎么会知道须要强制刷新呢,因此这个方案给用户确定是不可行的。webpack
咱们了解完了如何清楚缓存,再来看看Vue-cli模版中是如何进行配置的ios
咱们直接生成一个Vue-cli的新项目,安装依赖后直接运行 npm run build命令,并打开/dist/js文件目录程序员
发现有3个js文件,这就是webpack将代码进行了分割。web
之因此把业务代码和第三方库代码分离出来,是由于产品经理的需求是源源不断的,所以业务代码更新频率大,相反第三方库代码更新迭代相对较慢且能够锁版本,因此能够充分利用浏览器的缓存来加载这些第三方库。
而按需加载的适用场景,好比说「访问某个路由的时候再去加载对应的组件」,用户不必定会访问全部的路由,因此不必把全部路由对应的组件都先在开始的加载完;更典型的例子是「某些用户他们的权限只能访问某些页面」,因此不必把他们没权限访问的页面的代码也加载
vue-cli中使用了 CommonsChunkPlugin这个webpack插件来提取框架代码。 打开 webpack.prod.conf.js 文件,找到下面这段代码
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
复制代码
这段代码在打包的时候把 node_modules 下面的 ,而且名字是 .js 结尾的,而且不是重复的模块提取到vender
里面。
因此打包后应该会生成app.js(业务代码)、vender.js(框架代码)这个两个文件,细心的同窗可能会发现还有个 manifest.js,在后面咱们讲进行解读。
若是咱们修改一下hello组件的加载方式改成路由懒加载(import()语法),在进行打包
// import Hello from '@/components/Hello'
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
// component: Hello,
component: () => import('@/components/Hello')
}
]
})
复制代码
很明显的看到,打包后有4个js文件,仔细的同窗还发现,app.js文件的大小加上新多出文件的大小,正好等于没有分割打包的app的大小。 这样等于异步加载的组件,是单独打包成了一个js,在页面首次加载的时候不须要加载他,等到请求相应的页面的时候在去服务器请求它,减少了页面首屏加载的时间。
vue-cli /webpack.prod.conf.js
中配置 output.chunkFilename 规定了打包异步文件的格式
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
复制代码
假设咱们如今有不少不少的静态文件,而后每次须要更新不少不少的文件,那是否是要手动地一个一个地修改文件的名字呢?咱们的理想固然是:哪一个文件更新了,就自动地生成一个新的文件名。
另外,若是咱们打包出来的静态文件只有一个单独的 JavaScript 文件 app.js ,那么每次改动一点代码,app.js 的文件名确定都会变。但实际上,我只改动了某个模块的代码(其余模块并无修改),就破坏了其余模块的缓存,这显然没有充分利用到缓存啊。咱们的目标是:
哪一个模块更新了破坏他的缓存,没更新的模块继续利用缓存。
上文中咱们提到清楚缓存的三种方式:修改文件名,修改路径,给url加参数,webpack的作法是修改文件名。 vue-cli /webpack.prod.conf.js
中 output.chunkFilename 规定了打包异步文件的格式
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// 规定文件名为 js文件夹下 Chunk.name . hash值 .js 的文件
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
// 规定文件名为 js文件夹下 module id. hash值 .js 的文件
},
复制代码
这样的话给每一个文件加上了hash值,那个文件发生了变化hash值就会改变
为何要提取manifast文件呢?
缘由是 vendor chunk 里面包含了 webpack 的 runtime 代码(用来解析和加载模块之类的运行时代码)
这样会致使:即便你没有更改引入模块(vendor的模块没有发生变更的状况下,你仅仅修改了其余代码) 也会致使 vendor
的chunkhash值发生变化,从而破坏了缓存,达不到预期效果
vue-cli /webpack.prod.conf.js
提取 manifast
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
复制代码
webpack 里每一个模块都有一个 module id ,module id 是该模块在模块依赖关系图里按顺序分配的序号,若是这个 module id 发生了变化,那么他的 chunkhash 也会发生变化。
这样会致使:若是你引入一个新的模块,会致使 module id 总体发生改变,可能会致使全部文件的chunkhash发生变化,这显然不是咱们想要的
这里须要用 HashedModuleIdsPlugin ,根据模块的相对路径生成一个四位数的hash做为模块id,这样就算引入了新的模块,也不会影响 module id 的值,只要模块的路径不改变的话。
vue-cli /webpack.prod.conf.js
new webpack.HashedModuleIdsPlugin()
复制代码
至此若是咱们改了某个模块的代码,是不会破坏其余模块的缓存,这就是咱们想要实现的持久性缓存。
咱们首先来看一个实际项目
运行 npm run build --report 能够查看打包分布图 咱们发现最大的文件仍是vendor,大部分框架代码都打包在这里面,而这些框架代码是不常变化的,也不须要每次进行打包。因此咱们能够想办法把他们提取出来,挂到cdn上面去。
以 vue, vue-router,element-ui为例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo-vue-project</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.0.8/theme-chalk/index.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.0.7/index.js"></script>
</body>
</html>
复制代码
module.exports = {
...
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT'
},
...
}
复制代码
修改 src/router/index.js
// import Vue from 'vue'
import VueRouter from 'vue-router'
// 注释掉
// Vue.use(VueRouter)
...
复制代码
修改 src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import ELEMENT from 'element-ui'
// import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ELEMENT)
Vue.prototype.$http = axios
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
template: '<App/>',
components: { App }
})
复制代码
打包后咱们发现vendor体积大大减少,由于库代码都用cdn加载了。可是这样会致使请求资源增多也有响应的代价,这仅可算是一个思路。 首屏问题的最终解决方案仍是SSR
至此 vue-cli中的打包配置,也有一些了解了。我的吐槽下webpack是真的复杂。观望和期待 parcel能来带不同的体验。