前言:在现实项目中,咱们可能不多须要从头开始去配置一个webpack 项目,特别是webpack4.0发布之后,零配置启动一个项目成为一种标配。正由于零配置的webpack对项目自己提供的“打包”和“压缩”功能已经作了优化,因此实际应用中,咱们能够把精力更多专一在业务层面上,而无需分心于项目构建上的优化。然而从学习者的角度,咱们须要了解webpack在项目的构建和打包压缩过程当中作了哪些的优化,以及在原有默认配置上,还能够作哪些性能方面上的改进。
最近在完成vue的单页面应用后萌生了一个想法,抛弃掉vue-cli的构建配置,从零开始进行webpack优化,并将过程当中的思路和体会分享在这篇文章中。webpack的初始配置在我以前写的另外一篇手把手教你从零认识webpack4.0文章中,如下内容也不对基本的webpack配置作过多阐述。css
对开发者而言,咱们但愿webpack这个工具能够给咱们带来流畅的开发体验。好比,当不断修改代码时,咱们但愿代码的变动能及时的通知浏览器刷新页面,而不是手动去刷新页面。更进一步的咱们但愿,代码的修改只会局部更换某个模块,而不是整个页面的刷新。这样可使咱们不须要在等待刷新中浪费不少时间,大大提升了页面的开发效率。html
项目部署上线时,性能优化是咱们考虑的重点,有两个方向能够做为核心考虑的点,一个是减小HTTP请求,咱们知道在网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要快,所以,咱们要求webpack将多个文件打包成一个或者少许个文件;另外一个优化的重点是减小单次请求的时间,也就是尽量减小请求文件的体积大小。
webpack中在性能优化所作的努力,也大抵围绕着这两个大方向展开。另外在构建项目中,咱们也但愿能持续的提升构建效率。前端
开发环境下,咱们依然对代码的体积有必定的要求,更小的体积可让加载速度更快,开发效率更高,固然配置也相对简单。vue
// webpack.dev.js 开发环境webpack配置 module.exports = { devServer: { contentBase: path.join(__dirname, 'dist'), port: 9000, compress: true, // 代码压缩 }, }
开发过程当中,咱们但愿修改代码的过程当中,页面能实时且不须要手动的刷新。所以使用HRM, HMR 既避免了频繁手动刷新页面,也减小了页面刷新时的等待,大幅度提升了开发效率。node
// webpack.dev.js module.exports = { devServer: { compress: true, hot: true // 开启配置 }, plugins: [ new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin(), ], }
webpack 在构建中提供了很多于7种的sourcemap模式,其中eval模式虽然能够提升构建效率,可是构建后的脚本较大,所以生产上并不适用。而source-map 模式能够经过生成的 .map 文件来追踪脚本文件的 具体位置,进而缩小脚本文件的体积,这是生产模式的首选,而且在生产中,咱们须要隐藏具体的脚本信息,所以可使用 cheap 和module 模式来达到目的。
综上,在生产的webpack devtool选项中,咱们使用 cheap-module-source-map
的配置react
// webpack.pro.js 生产webpack配置脚本 module.exports = { mode: 'production', devtool: 'cheap-module-source-map', }
以单入口文件而论,一般咱们会将页面的全部静态资源都打包成一个JS 文件,这已经实现了1.2 中的优化部分,将代码合并成一个静态资源,减小了HTTP 请求。jquery
可是接下来,咱们须要将css代码独立开来,为何呢?最主要的一点是咱们但愿更好的利用浏览器的缓存,当单独修改了样式时,独立的css文件能够不须要应用去加载整个的脚本文件,提升效率。而且,当遇到多页面的应用时,能够单独将一些公共部分的样式抽离开来,加载一个页面后,接下来的页面一样能够利用缓存来减小请求。webpack
webpack4.0 中提供了抽离css文件的插件,mini-css-extract-plugin
,只须要简单的配置即可以将css文件分离开来git
const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { ··· plugins: [ new MiniCssExtractPlugin({ filename: "[name].[contenthash].css", chunkFilename: "[name].[contenthash].css" }) ], module: { rules: { test: /\.(css|scss)$/, use: [process.env.NODE_ENV == 'production' ? MiniCssExtractPlugin.loader : 'style-loader', { loader: 'css-loader', options: { sourceMap: true }, }, "sass-loader"] } } ··· }
要想优化构建后的体积,不断减小静态资源文件的大小,咱们但愿webpack帮助咱们尽量压缩文件的体积。对于js 脚本文件而言,webpack4.0 在mode 为‘production’时,默认会启动代码的压缩。除此以外,咱们须要手动对html和css 进行压缩。
针对html 的压缩,只须要对html-webpack-plugin进行相关配置。github
// webpack.base.js module.exports = { plugins: [ new HtmlWebpackPlugin({ title: 'minHTML', filename: 'index.html', template: path.resolve(__dirname, '../index.html'), minify: { // 压缩 HTML 的配置 collapseWhitespace: true, removeComments: true, useShortDoctype: true } }), ] }
针对css 的压缩, webpack4.0 使用optimize-css-assets-webpack-plugin
来压缩单独的css 文件。
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = { plugins: [ new OptimizeCSSAssetsPlugin() ], }
对比之下,咱们能够看到明显的效果,关于压缩css 更多的配置能够参考optimize-css-assets-webpack-plugin
处理完前端的三大块js,html,css后, 接下来优化能想到的是处理图片。前面提到,提高性能的一个重要的条件是下降http请求数,而应用中常常会有大大小小的图片须要处理,对应用中的小图标来讲,css sprite 是首选,将各类图标集合成一张大的图片能够很好的减小网络请求数。而对于须要独立开的图片,且大小在合理范围内时,咱们能够将图片转换成 base64位编码,内嵌到css 中,一样能够减小请求。
处理图片资源时,webpack 提供了 file-loader 和url-loader 两个loaders供选择,file-loader 和url-loader 的做用,能够用来解析项目中图片文件的url引入问题。二者的区别在于,url-loader 能够将小于指定字节的文件转为DataURL, 大于指定字节 的依旧会使用file-loader 进行解析
// webpack.base.js module.exports = { module: { rules: [{ test: /\.(png|jpe?g|gif|svg|ttf|woff2|woff)(\?.*)?$/, use: [{ loader: 'url-loader', options: { limit: 10000, // 限制大小 } }, ] }, }
处理完雪碧图和小图片的base64转换后,对于大图片来讲,webpack还能够作到对图片进行压缩,推荐使用image-webpack-loader
,插件提供了多种形式的压缩,详细能够参考官网文档
// webpack.base.js module.exports = { module: { rules: [ { loader: 'image-webpack-loader', options: { optipng: { // 使用 imagemin-optipng 压缩 png,enable: false 为关闭 enabled: true, }, pngquant: { // 使用 imagemin-pngquant 压缩 png quality: '65-90', speed: 4 }, } } ] } }
效果对好比下:
一个中大型应用中,第三方的依赖,庞大得可怕,占据了打包后文件的一半以上。然而,这些依赖模块又是不多变动的资源,和css 代码分离的逻辑类似,分离第三方依赖库,能够更好的利用浏览器缓存,提高应用性能。所以,将依赖模块从业务代码中分离是性能优化重要的一环。
webpack4.0 中,依赖库的分离只须要经过 optimization.splitChunks 进行配置便可。
// webpack.pro.js module.exports = { optimization: { splitChunks: { cacheGroups: { vendor: { chunks: "initial", test: path.resolve(__dirname, "../node_modules"), name: "vendor", // 使用 vendor 入口做为公共部分 enforce: true, }, }, }, }, }
公共库分离后的结果
正如前面所讲,在优化分析中,实际影响体积最大的是 node_modules 的第三方库,这一部分的优化能够大大的减小打包后的体积。这里咱们使用最新的webpack-bundle-analyzer
插件来分析打包好后的模块,它能够将打包后的内容束展现位方便交互的直观树状图,经过它,能够知道项目大体有哪些模块组成,哪一个模块占据的体积较大,是不是可替代的。
咱们大体能够从几个方向考虑
前面提到依赖分析的方向中,若是大型库不可或缺,并且使用率也不算低的时候,咱们能够经过按需加载的形式。这种方式其实是先把你的代码在一些逻辑断点处分离开,而后在一些代码块中完成某些操做后,当即引用或即将引用另一些新的代码块。这样加快了应用的初始加载速度,减轻了它的整体体积,由于某些代码块可能永远不会被加载。
webpack中利用require.ensure()实现按需加载,在不使用按需加载的状况下,首屏加载时会把全部的脚本同时加载出来,这每每会拖累首屏显示时间,带来很差的用户体验。例子来讲。当项目须要使用大型的图表类库,而首页并不须要时,按需加载每每比同时加载在用户体验上好好得多。
当不须要按需加载的时候,咱们的代码多是这样的:
import test from './components/test.vue' import test2 from './components/test2.vue'
开启按需加载时,咱们的代码修改成:
const test = r => require.ensure([], () => r(require('./components/test.vue')), 'chunk1') const test2 = r => require.ensure([], () => r(require('./components/test2.vue')), 'chunk2')
webpack 配置修改成
output: { ··· chunkFilename: '[name].[hash].js' }
这时编译出来的文件会从原来的一个,变成了多个小文件。每一个路由加载时会去加载不一样的资源。特别在首屏的资源加载上进一步优化了应用的体验。
尽管如此,实际中咱们须要根据项目的需求来衡量按需加载的可用性,尽管在首屏优化上取得较大的提高,但按需加载毕竟会将大的文件拆分红多个小文件,增长了http 的请求数。这又违背了性能优化的基础。因此实际中须要取舍,更须要权衡。
代码体积优化到这一步,基本能够优化的地方已经优化完毕了。接下来能够抓住一些细节作更细的优化。好比能够删除项目中上下文都未被引用的代码。这就是所谓的 Tree shaking 优化。webpack 4.0中,mode 为production 默认启动这一优化。可是,若是在项目中使用到babel的 话,须要把babel解析语法的功能关掉。只须要
// .babelrc { "presets": [["env", { "modules": false }]] }
说完如何减小项目构建后的大小后,接下来简单的谈一下如何提升构建的速度。实际上webpack的 构建速度,只须要简单的修改配置便能大幅提升速度。常见的设置以下。
因为babel-loader须要将语法进行转换,所耗费的时间较长,因此第一步须要限定babel-loader 做用的范围,让babel-loader 的搜索和转换准确的定位到指定模块。大幅提升构建速度。
例如:
// webpack.base.js module.exports = { module:{ rules: [ { test: /\.js$/, include: [resolve('src')],// 限定范围 use: { loader: 'babel-loader', }, },] } }
正由于babel-loader在解析转换上耗时太长,因此咱们但愿能缓存每次执行的结果。webpack的loader中恰好有 cacheDirectory 的选项,默认为false 开启后将使用缓存的执行结果,打包速度明显提高。
// webpack.base.js module.exports = { module: { rules: [ { test: /\.js$/, include: [resolve('src')], use: { loader: 'babel-loader?cacheDirectory', }, },] } }
webpack 的resolve 作相关的配置后,也可让项目的构建速度加快。具体看下文的配置:
// webpack.base.js module.exports = { resolve: { modules: [ path.resolve(__dirname, 'node_modules'), ], extensions: [".js"], // 避免新增默认文件,编码时使用详细的文件路径,代码会更容易解读,也有益于提升构建速度 mainFiles: ['index'], }, module: { noParse: function(content){ return /jquery/.test(content) } } }
webpack性能优化的瓶颈仍是集中在构建时间和构建体积上,做为构建工具业界霸主,webpack一直不停的优化构建打包流程。经过对旧有项目的优化也能够对webpack这个工具以及性能优化的知识有更深的了解。
转载请注明出处