Webpack 最佳实践总结(三)

in Tutorials with 2 comments

还未看的,可以点击查看上两篇文章哟: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-lodashlodash-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和其他好用的插件,关于基础入门,我之前写过一篇:《webpack2基础教程》

大概就这样,内容较多~

Responses
  1. 非常不错!!!!

    Reply
  2. 学习了!写的很到位!

    Reply