好久没写文章,这次预计会带来3篇的 Webpack 系列文章,将会在这几天内更新完(已更新完)。
关于基础知识部分,我之前写过一篇《webpack2基础教程》,足够入门了,不再多说。
Webpack3 自今年6月20日正式发布而来,给我们带来Scope Hoisting
和Magic 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-core
、babel-loader
、babel-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_ENV
为production
即可,但对于 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 的import
、export
,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-env
或 babel-preset-es2015
,就需要检查一下这些 preset 的配置了。默认情况下,它们都会ES 模块机制的语法特性重新编译为 CommonJS,如将 ES 的import
和export
编译为 CommonJS 的require
和module.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帮助找到更多无用代码
内容较多,大概就这样~
本文由 Chakhsu Lau 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。