Webpack 最佳实践总结(一)

in Tutorials with 1 comment

好久没写文章,这次预计会带来3篇的 Webpack 系列文章,将会在这几天内更新完(已更新完)。

关于基础知识部分,我之前写过一篇《webpack2基础教程》,足够入门了,不再多说。

Webpack3 自今年6月20日正式发布而来,给我们带来Scope HoistingMagic Comments两大功能,可惜不在这次系列文章内容范畴里(食言了),而本次文章的主要内容是从项目中总结得到,当然也看了很多别人写的文章,是可以被应用到生产中。这次主要介绍三个方面,分别是压缩 JavaScript 和 CSS、配置环境变量、ES 模块机制带来的Tree-shaking。

假设我们有一个前端开发需求,这个需求有点特别,不是业务上的需求,而是要求减少文件的大小。可知这个需求算是性能优化上范畴,减少文件大小,加速网络传输,缩短网页加载时间,增加用户体验,提高用户满意度。这是一个正向结果,得干~

压缩 JavaScript

这里不得不提一下 Google 的 Closure Compiler,一款可以让 JavaScript 下载与执行更快的工具,它的做法是执行一些"焦土"(scorched-earth)优化策略,它将函数展开,重写变量名,过滤多余代码,删除从不会调用的函数,从而生成可能是最优化的代码。如下

优化前

function map(array, iteratee) {
  let index = -1
  const length = array == null ? 0 : array.length
  const result = new Array(length)

  while (++index < length) {
    result[index] = iteratee(array[index], index, array)
  }
  return result
}

优化后

function map(n,e){let r=-1;for(const i=null==n?0:n.length,t=Array(i);++r<i;)t[r]=e(n[r],r,b);return t}

对比发现优化效果极好,使用 UglifyJS 一样可以达到相同的优化效果。由于 Webpack 内置这款插件,可以直接使用,配置如下

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin()
  ]
};

使用该插件优化前后的对比结果如下说明:

假设我们有一段代码,如下:

// comments.js
import './comments.css';
export function render(data, target) {
    console.log('Rendered!');
}

Webpack 编译之后的代码大概如下:

// bundle.js (part of)
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_css__ =
  __webpack_require__(4);
var __WEBPACK_IMPORTED_MODULE_0__comments_css___default =
  __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__);
__webpack_exports__["render"] = render;

function render(data, target) {
    console.log('Rendered!');
}

如果使用了 UglifyJS 插件,重新编译之后的代码大概如下:

// bundle.js (part of)
"use strict";function r(e,t){console.log("Rendered!")}
Object.defineProperty(t,"__esModule",{value:!0});
var o=n(4);n.n(o);t.render=r

特殊情况说明:目前,UglifyJS 2(Webpack自带的) 是无法编译 ES2015+ 的代码,意味着如果代码中是了类、箭头函数或者其他新的特性,而且你没有将代码编译为 ES5,那么 UglifyJS 插件是无法处理这些代码的,这里提供两种处理方法:

方法1:给 Webpack 添加支持 babel,基本过程如下

安装 babel-corebabel-loaderbabel-preset-es2015

npm i babel-core babel-loader babel-preset-es2015 --save-dev

给 Webpack 添加配置信息

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js[x]$/,
        use: [{'babel-loader', options: {
          presets: ['es2015']
        }]
      }
    ]
  }
};

方法2:使用 Babili 取代 UglifyJS 2,这是一款基于 Babel 的压缩工具,更多了解查看 babili-webpack-plugin,这里不作太多的描述

压缩 CSS

压缩 CSS 的方法比较简单,css-loader 就自带压缩功能,配置如下:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } }
        ]
      }
    ]
  }
};

另外我还写过 Webpack 跟 Less 和 Sass 的配置教程,具体看:here1, here2

配置环境变量

NODE_ENV设置为production也能帮助减少前端项目大小

NODE_ENV通常作为环境变量被各种js框架或库作为判断条件去作为哪种执行模式,如开发模式或是生产模式,这些框架或者库根据NODE_ENV的值去表现对应的行为。例如,当处于开发模式时,React 会做额外的检测和打印警告:

// …
if (process.env.NODE_ENV !== 'production') {
  validateTypeDef(Constructor, propTypes, 'prop');
}
// …

如果要打包构建项目到生产环境中,最好可以告诉项目中的框架或库当前的环境变量是生产环境。对于适用于 Node.js 的库来说,直接配置NODE_ENVproduction即可,但对于 Web 端来说,可以使用 Webpack 自带的插件实现对process.env.NODE_ENV的设置,如下

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: {
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  }
};

DefinePlugin 允许我们创建全局变量,同时这些变量会作用于webpack的打包编译期内。它会代替所有的process.env.NODE_ENV实例的值为"production",从而使 UglifyJS 知道那些判断表达式总是错误的,从而删除相关代码,进一步压缩打包文件

ECMAScript模块机制

项目中使用 ECMAScript 的importexport,Webpack 通过 tree-shaking 也能通过打包有用的代码,进一步减少大小。 Tree-shaking 可以检查整个打包依赖树,找到使用的部分。所以如果使用 ECMAScript 模块机制,Webpack 会去掉无用的代码,如下:

假设写了两个文件,但只使用了其中一个文件

// comments.js
export const commentRestEndpoint = '/rest/comments';
export const render = () => { return 'Rendered!'; };

// index.js
import { render } from './a.js';
render();

Webpack 知道componentRestEndpoint是没有被使用,打包文件也不会有该入口

// bundle.js (part of)
(function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  /* unused harmony export commentRestEndpoint */
  /* harmony export */__webpack_exports__["b"] = render;

  var commentRestEndpoint = '/rest/comments';
  var render = function () { return 'Rendered!'; }
})

UglifyJS 插件去掉无用部分

// bundle.js (part of)
(function(n,e){"use strict";e.b=r;var r=function(){return"Rendered!"}})

如果 JavaScript 框架或库使用了 ECMAScript 模块机制,那么 Tree-shaking 是对其也是有效滴

注意事项

1、 如果没有 UglifyJS ,tree-shaking 将不作用

实际上,去掉无用的代码不是 Webpack 本身,而是 UglifyJS。Webpack 只是去掉 export 表达式使那些 exports 不再使用,从而可以在压缩的时候被去掉。因此,如果你打包的时候没有使用压缩,打包文件就不会变得更小

2、不要将 ES 模块机制编译为 CommonJS

如果你使用了 babel-preset-envbabel-preset-es2015,就需要检查一下这些 preset 的配置了。默认情况下,它们都会ES 模块机制的语法特性重新编译为 CommonJS,如将 ES 的importexport编译为 CommonJS 的requiremodule.exports。我们可以使用 { modules: false } 禁止编译行为,如下:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js[x]$/,
        use: [{'babel-loader', options: {
          presets: [['es2015', { modules: false }]]
        }]
      }
    ]
  }
};

3、在特殊条件下 Webpack 不会进行优化

当你使用了export * from 'file.js' 或者是 TypeScript,Webpack 是不会进行优化。这些特殊条件在代码中很难被发觉,也很难知道这些特殊条件的代码是否被修复,更多了解可以查看 here

总结

Webpack 对前端项目优化有很多种,这里提供了四种技巧,分别是通过 UglifyJS 插件实现对 JavaScript 文件的压缩,css-loader 提供的压缩功能,配置NODE_ENV可以进一步去掉无用代码,tree-shaking帮助找到更多无用代码

内容较多,大概就这样~

Responses
  1. 大佬的研究太高深了

    Reply