上一篇介绍了 Webpack 优化项目的四种技巧,分别是经过 UglifyJS 插件实现对 JavaScript 文件的压缩,css-loader 提供的压缩功能,配置NODE_ENV能够进一步去掉无用代码,tree-shaking帮助找到更多无用代码javascript
这一篇主要讲 Webpack 的改进缓存(hash)、切割代码css
开发过程常常须要一边预览代码运行结果一边修改代码,这个时候文件版本控制就显得尤其重要。默认作法是告诉浏览器这个文件的缓存时间,而后当文件内容被修改,则须要重命名该文件告诉浏览器须要从新下载和缓存,例如:html
<!-- Before the change --> <script src="./index.js?version=15"> <!-- After the change --> <script src="./index.js?version=16">
Webpack 也能作相似的工做。可是不是采用上面的版本方式去控制文件,它会计算文件的hash值去标识打包后文件。每次你修改了代码,文件名也跟着变化,这样浏览器就会加载新的新文件,再也不使用缓存过的文件,例如:java
// webpack.config.js module.exports = { entry: './index.js', output: { filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js } };
更多关于 [hash], [chunkhash], [name], [id] 和 [query] 能够查看:here,这里列一个表方便查看:node
Template | Description |
---|---|
[hash] | module 的 hash 标识 |
[chunkhash] | chunk 的 hash 标识 |
[name] | module 的名称 |
[id] | module 的惟一标识 |
[query] | module 的查询(文件名带有?) |
如今还剩下的问题是如何将会变化的文件传输到客户端,这里列出两种解决方法,分别是:HtmlWebpackPlugin
和WebpackManifestPlugin
react
HtmlWebpackPlugin
是一个更自动化的解决方法,在编译打包的期间,它会生成包含打包资源的 HTML 文件。若是你的业务逻辑相对简单的状况下,这个插件是绝对够用的:webpack
<!-- index.html --> <!DOCTYPE html> <!-- ... --> <script src="bundle.8e0d62a03.js"></script>
WebpackManifestPlugin
相比HtmlWebpackPlugin
是一个更灵活的解决方案,尤为是面对复杂的服务端的部分。它能生成JSON文件,包含文件名以及与其对应(映射)的 hash 过的文件名git
{ "bundle.js": "bundle.8e0d62a03.js" }
提示:Webpack的 hash 函数是不稳定的,这意味着在不一样开发环境等条件下下,即使是同一个文件,webpack 依然会计算出不一样的 hash 去标识这个相同的文件。正由于此,若是你有跨平台开发的需求,可使用webpack-chunk-hash
去代替webpack的原生的hash函数算法。更多关于 webpack hash 的问题能够查看:heregithub
想象一下你要开发一个大型网站,有首页和文章页,文章页里有文章内容和评论系统,可是你要将网站正常工做的代码都打包到一个文件里,这显然是不科学的。每次你修改其中一个模块,整改打包文件都要从新编译从新打包和生成,这意味着你仅仅只是修改一下评论模块,但当用户只访问首页,他们依然会下载这些暂时无用的代码,从而影响首页访问的加载速度web
这个时候就须要切割打包文件,把它切割为首页和文章页所需的两个打包文件,当用用户访问首页时,只加载首页的打包文件,当访问文章页的时候,只加载文章页的打包文件,配置以下:
module.exports = { // 设置多个入口点,webpack给每一个入口文件生成对应的打包文件 // 从而实现不一样页面加载不一样的打包文件 entry: { homepage: './index.js', article: './article.js' }, output: { // [name]对应的是入口点的 name,如 homepage、article filename: '[name].[chunkhash].js' }, plugins: [ // 生成打包资源列表 json 文件 new WebpackManifestPlugin(), // 取代 webpack 原生的 hash 函数 new WebpackChunkHash(), // 生成依赖包的块文件,转移全部的`node_modules`依赖到一个特别的该文件中 // 这容许你更新你的代码时,无需更新依赖 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: m => m.context && m.context.includes('node_modules'), }), // 生成 `webpack’s runtime` 自身的代码文件 // 这容许你更新你的代码时,无需更新其余无关代码 new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', chunks: ['vendor'], minChunks: Infinity, }), // 标识每一个模块 hash 值,当你添加新的模块时,若是该模块的依赖影响到别的模块 // 就能够更新这些受影响的模块从而区分旧的模块 new webpack.HashedModuleIdsPlugin(), // 生成资源映射文件,包含文件名以及与其对应的hash过的文件名,用于其余插件或者服务 new ChunkManifestPlugin({ filename: 'chunk-manifest.json', manifestVariable: 'webpackManifest' }) ] };
上面这个配置将会生成6个文件:
// 两个打包入口文文件,当你修改了业务代码,它们也会跟着变化 homepage.a68cd93e1a43281ecaf0.js article.d07a1a5e55dbd86d572b.js // 通用依赖文件与 webpack’s runtime 的文件,前者当你依赖包变化也跟着变化,后者极少变化,除非使用的 webpack 版本这类状况变化了才会跟着变化 vendor.1ebfd76d9dbc95deaed0.js runtime.d41d8cd98f00b204e980.js // 两个 manifest 文件,用于其余插件或服务,如 DllReferencePlugin manifest.json chunk-manifest.json
除了按照不一样页面的切割为不一样的入口文件外的切割代码外,还能够按照构成页面的组件的顺序作到按需加载的去切割代码
想象一下,有一个页面布局方式以下所所示:
+-------------------------------+ | logo menu | +-------+----------------+------+ | | | | | left | | right| | bar | | bar | | | article | | | | content | | | | | | | | | | | +----------------+ | | | | | | | comments | | | | | | +-------+----------------+------+ | copyright | +-------------------------------+
当访问这个页面时,用户但愿首先能阅读到文章的内容,诸如其余评论、侧边栏等其余页面组成部分是能够被延后查看的。惋惜的是,若是你将这些页面组成部分都打包到一个文件里,用户就须要等到整改打包文件加载后才能访问到他想要访问的内容
如何解决页面中各部分的按需加载?webpack 容许你作这方面的优化去实现代码的按需加载。首先你须要本身识别哪些代码是要首先加载的,而后 webpack 会移动须要延迟加载的代码到单独的块中,只有在当须要这些被延迟加载的代码时,才会下载
假设有一个article-page.js
业务代码文件,当你打包加载其会所有加载,包括文章内容、评论和侧边栏,以下:
// article-page.js import { renderArticle } from './components/article'; import { renderComments } from './components/comments'; import { renderSidebar } from './components/sidebar'; renderArticle(); renderComments(); renderSidebar();
作到按需加载则须要你将静态的import
修改成动态的import()
,webpack 会自带转移这些代码到单独的块中,只有当被须要时才会加载,以下:
// article-page.js import { renderArticle } from './components/article'; renderArticle(); import('./comments.js') .then((module) => { module.renderComments(); }); import('./sidebar.js') .then((module) => { module.renderSidebar(); });
将静态的import
修改成动态的import()
的操做会带来提高首次访问时加载性能,同事也会优化缓存,当你更改这些业务代码时,只会修改对应的块文件,从而不影响其余块文件
固然,还须要从新设置 output ,以下:
// webpack.config.js module.exports = { output: { filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js', } };
output.chunkFilename
能够标识按需加载的块文件
提示:当你使用默认的 presets 的 babel 去编译这些代码,你会获得一个语法错误提示:Babel don’t understand import() out of the box.
要避免这个错误,你须要给babel安装syntax-dynamic-import
插件
在一个大型项目中,若是有两段业务代码有共同的依赖,经过 webpack 的externals
,你在两段代码间能够共享这些依赖,以下:
// webpack.config.js module.exports = { externals: { 'react': 'React', 'react-dom': 'ReactDOM', } };
上面的作法是将这些框架或库的对象挂靠在全局对象中,而后经过另一个对象存储对象名以及映射到对应模块名的变量,webpack 就会替换全部全部模块下的相关的引用,而后你须要手动引入这些被externals
的框架或库到网站入口文件中,如HtmlWebpackPlugin
定义的template
文件
此次介绍如何经过 webpack 克服浏览器缓存打包文件,不去更新新的打包文件的问题,以及讲解了从不一样的页面的角度去切割代码和从不一样的页面组成部分去切割代码的过程,还有经过externals
去分离去共有的框架和库,从而实现对这些框架或库的CDN资源加载
内容较多,大概就这样~
文章首发于:https://www.linpx.com/p/webpa...
欢迎访问个人博客:https://www.linpx.com