Webpack 最佳实践总结(三)
还未看的,可以点击查看上两篇文章哟:Webpack 最佳实践总结(一)、Webpack 最佳实践总结(二)
好了,这篇是第三篇,也是完结篇,我感觉这一篇是最乱的一篇,凑合着看吧,不会让你失望的
整合 CSS 加工流
有时候,前端项目中除了 JavaScript 外,还有一个更重要的 CSS 需要我们花点精力进去。这里主要陈述一下如何将 CSS 加工流整合到 webpack 中,因为 CSS Modules 的情况比较复杂,所有暂还未打算介绍更多关于 CSS Modules 的内容
CSS 工作流指什么?好的工作流可以提供开发效率,节约开发成本。这里要介绍的是 CSS 工作流中的一种很普遍的代码加工流程:正常的 CSS 业务逻辑开发流程需要经过 CSS 预处理器(如 Sass 或 Less),然后再经过后处理器(如 PostCSS)进行深加工。Sass 和 less 让我们吃上'语法糖'去快捷编写 CSS,PostCSS 可以让我们不再关心每条语句是否兼顾不同和不同版本的浏览器
在 webpack 上整合 CSS 加工流实现方式如下:
配置预处理器
这里以 Sass 作为预处理器,如下:
// webpack.config.js const webpack = require('webpack'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ // ... { test: /\.scss$/, exclude: /node_modules/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { minimize: true } }, 'postcss-loader', 'sass-loader' ] }) } ] } }
配置后处理器
这里以 PostCSS 作为后处理器,如下:
// webpack.config.js const webpack = require('webpack'); const autoprefixer = require('autoprefixer'); module.exports = { plugins: [ new webpack.LoaderOptionsPlugin({ options: { postcss: [ autoprefixer({ browsers: [ 'last 3 version', 'ie >= 10' ] }) ], context: staticSourcePath } }) ] }
设置外联
// webpack.config.js const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 存放静态资源,诸如图片或者是 normalize.css const staticSourcePath = path.join(__dirname, 'static'); module.exports = { // ... entry: { // 设置入口文件,顺序是静态资源 -> custom.scss -> 项目里其他 scss base: path.resolve(staticSourcePath, 'src/public/custom.scss') }, // ... plugins: [ // 创建 <link> 标签,并将 src 指向最终生成的 CSS 文件,需要 html-webpack-plugin new ExtractTextPlugin({ filename: '[name].[contenthash].css', allChunks: true }) ] }
压缩第三方库
以 Moment.js 和 Lodash 为例
Moment.js
Moment.js(v2.18.1) 是一个用于日期的 JavaScript 库,默认情况下,只有你安装它到你的项目中,即使压缩后,也会占据217kb大小。相对于在2017年8月1日的统计,对比与 JavaScript 的 446kb 的平均大小,这是实在是太大了。不过 webpack 可以去掉 Moment.js 其中无用的代码。
其中有 165kb 的大小是用于本地化的语言包,即便你不去用它们,它们在默认的情况下也会被包含进来。如下代码来自 moment 的 gitihub
// moment/src/lib/locale/locales.js function loadLocale(name) { var oldLocale = null; // TODO: Find a better way to register and load all the locales in Node if (!locales[name] && (typeof module !== 'undefined') && module && module.exports) { try { oldLocale = globalLocale._abbr; require('./locale/' + name); // because defineLocale currently also sets the global locale, we // want to undo that for lazy loaded locales getSetGlobalLocale(oldLocale); } catch (e) { } } return locales[name]; }
上面的代码会使 Moment.js 在运行期间动态地选择相应文件去加载。
要解决它需要用到 ContextReplacementPlugin,一款替换上下文的插件,例子如下:
// webpack.config.js const webpack = require('webpack'); module.exports = { plugins: [ new webpack.ContextReplacementPlugin( // 需要被处理的文件目录位置 /moment[\/\\]locale/, // 正则匹配需要被包括进来的文件 /(en|zh-cn)\.js/ ) ] };
Lodash
Lodash 是一款方便开发 JavaScript 的工具集合,测试版本为4.17.4。
当你项目包含有 Lodash 的时候,你打包出来的文件至少增加 75kb,多出来的大小包含了 316 个 Lodash 的函数。如果你只是使用了其中少数,例如 20 个,那么大概有 65 kb 是多余的。下面将列出两种去掉这些多余的代码的方法:
方法1:
还记得 webpack最佳实践(一) 提及的 Tree-shaking 吗?正因为有它,我们可以利用这个特性非常容易做到按需引用,如下:
import _ from 'lodash'; _.get();
修改为
import get from 'lodash/get'; get();
代码量从 72kb 压缩到 8.27kb
方法2:
方法1只适合刚开始玩一个项目的时候,并不怎么适合玩开了的项目,除非重写一次,这工作量太大了,另外一个原因是 lodash 的方法名会容易跟自定义的函数名冲突,造成隐藏性bug。方法2就是解决这两个问题,那就是使用babel-plugin-lodash
babel-plugin-lodash
是一款通过 babel 去实现将 lodash 的import
用法编译为最佳实践的插件,配置如下:
打开.babelrc
,添加下面配置
{ "plugins": ["lodash"] }
更多的配置方式可以查看文档,这里不再作太多介绍。更具体的优化效果看下面:
import _ from 'lodash'; _.get({ a: { b: 5 } }, 'a.b');
上面的代码是没有使用babel-plugin-lodash
,使用之后,会被重新编译为下面:
import _get from 'lodash/get'; _get({ a: { b: 5 } }, 'a.b');
跟方法1一样,代码量从 72kb 压缩到 8.27kb
当然如果你想更进一步压缩代码,可以尝试与lodash-webpack-plugin
搭配,它会更深一步地去删除一些lodash的方法里的代码。例如_.get
默认支持深路径查询,如果你不需要支持深路径查询,你可以开启这个插件,这个方法就会被去掉这个支持:
只使用babel-plugin-lodash
import _ from 'lodash'; _.get({ a: { b: 5 } }, 'a.b'); // → returns 5
使用babel-plugin-lodash
和lodash-webpack-plugin
之后
import _get from 'lodash/get'; _get({ a: { b: 5 } }, 'a.b'); // → returns undefined
代码量从72kb 压缩到 772b
启用 scope hoisting
scope hoisting
对于 webpack 来说,就是将以前的模块引用链拍扁为一个但又不会影响到已有的代码。更好理解scope hoisting
推荐阅读:here
目前只有 webpack v3 以上版本才支持scope hoisting
,开启它是需要手动配置,如下:
// webpack.config.js const webpack = require('webpack'); module.exports = { plugins: [ new webpack.optimize.ModuleConcatenationPlugin() ] };
其他好用的插件
preload-webpack-plugin
让静态资源支持 DNS 预解析和预加载,配置如下:
// webpack.config.js const PreloadWebpackPlugin = require('preload-webpack-plugin'); module.exports = { // ... plugins: [ new PreloadWebpackPlugin({ rel: 'preload', as: 'script', include: 'all', fileBlacklist: [/\.(css|map)$/, /base?.+/] }) ] }
script-ext-html-webpack-plugin
让 js 加载方式支持 Async 或 defer,配置如下:
// webpack.config.js const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin'); module.exports = { // ... plugins: [ new ScriptExtHtmlWebpackPlugin({ defaultAttribute: 'defer' }) ] }
总结
有点乱,不好总结,大概就是整合 CSS 代码加工流程到 webpack 中、压缩第三方库(Moment.js 和 Lodash )、启用scope hoisting
和其他好用的插件
大概就这样,内容较多~
文章首发于:https://www.linpx.com/p/webpa...
欢迎访问我的博客:https://www.linpx.com