以下测试中,均在webpack版本3.5以上,如有不对的地方请指出,谢谢。
新特性优化
Scope Hoisting-作用域提升,将模块都放到一个闭包函数中,通过减少闭包函数数量从而加快JS的执行速度。 基本配置如下:
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
配置后,自定义的两个模块函数被放在一个必包函数体中,打包结果如下:
配置前,自定义的两个模块函数分别在不同的必包函数体中,通过行数就能看出来,打包结果如下:
配置项优化
externals配置优化
设置externals配置项分离不需要打包的库文件,然后在模版文件中使用script引入即可,配置代码片段如下:
externals: {
'jquery': 'jquery'
},
alias配置优化
- 可以选择对应的需要打包文件的大小规格
- 通过定义全局路径,减少webpack编译过程中的搜索硬盘时间
基本配置代码片段如下:
resolve: {
extensions: ['.js', '.json'],
alias: {
'jquery': 'jquery/dist/jquery.slim.min.js',
'@': resolve('src'),
}
},
插件使用优化
内链css减少请求
webpack插件配置片段代码如下:
plugins: [
new StyleExtHtmlWebpackPlugin({
minify: true
})
]
最终打包可以将css文件直接以内链的形式插入网页中,进而可以减少网页请求。
preload插件使用
webpack插件配置片段代码如下:
plugins: [
new PreloadWebpackPlugin({
rel: 'preload',
as: 'script',
include: 'all'
})
]
最终打包生成的页面在head上会生成preload的link标签,代码片段如下:
<link rel="preload" as="script" href="app.8898b6c9e3b39f7a1c9d.js">
<link rel="preload" as="script" href="common.6ecc97ec2b5dceebbd5e800322c2a3c0.css">
<link rel="preload" as="script" href="vendor.dcd374ee43fd57d2365b.js">
<link rel="preload" as="script" href="manifest.f3e58576762e216d8867.js">
更多配置参考这里详细更多
缓存优化与分析
css打包优化
contenthash
使用webpack打包css用的是extract-text-webpack-plugin插件,由于hash是webpack的module identifier计算的,不变内容的情况下每次打包也会产生不同的hash,因此选用chunkhash,它是根据文件内容计算的,因此比较符合实际使用,基本配置片段代码如下:
module: {
rules: [{
test: /\.(less|scss|css)$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader:"css-loader",
options:{
minimize: true //css压缩
}
},
{
loader:"less-loader",
options:{
minimize: true //css压缩
}
},
{
loader:"sass-loader",
options:{
minimize: true //css压缩
}
}]
})
}]
},
plugins: [
new ExtractTextPlugin({
filename: 'common.[chunkhash].css',
allChunks: true
}),
]
单纯改变css文件
// a.less
.ac {
.bc {
font-size: 12px;
}
.cc {
font-weight: 700;
border: 1px solid #ccc;
}
}
module文件中的a.js引入了a.less文件
// module/a.js
require('../style/a.less');
多次打包发现hash值未改变,不但依赖的js文件hash未改变,就连css文件的hash也未改变,最终使用webpack打包如下:
这显然不是想要的结果,希望改变css文件内容,js文件的hash不会改变,只有相应的css文件的hash值改变,因此再ExtractTextPlugin插件中应该使用contenthash,这也是官方特别注明的,直到再次看到文档才发现。
设置contenthash之后,再次打包发现只有css文件hash变了,并且改变js代码依然不影响css文件的打包,最终打包结果如下:
js打包优化
HashedModuleIdsPlugin
webpack插件基本配置片段代码如下:
entry: {
app: './src/app.js',
vendor: ['lodash']
},
plugins: [
new CleanWebpackPlugin(['cdist']),
new HtmlWebpackPlugin({
template: './src/index.template.html', //html模板路径
filename: 'wq.html', //生成的html存放路径,相对于path
favicon: './src/favicon.ico', //favicon路径,通过webpack引入同时可以生成hash值
inject: 'body', //js插入的位置,true/'head'/'body'/false
// chunks: ['app', 'vendor'],
//hash: true ,//为静态资源生成hash值
minify: { //压缩HTML文件
removeComments: true, //移除HTML中的注释
collapseWhitespace: false //删除空白符与换行符
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new ExtractTextPlugin({
filename: 'common.[contenthash].css',
allChunks: true
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
comments: false,
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true,
}
})
]
项目入口文件为app.js
import _ from 'lodash';
import a from './module/a';
// import b from './module/b';
require("babel-polyfill")
require('./style/lib.css')
function fn () {
// let aa = _.clone({key:4})
// aa.key = 2;
return aa
}
fn()
第一次打包生成的文件如下:
修改入口文件app.js代码如下:
import _ from 'lodash';
import a from './module/a';
import b from './module/b';
require("babel-polyfill")
require('./style/lib.css')
function fn () {
let aa = _.clone({key:4})
aa.key = 2;
return aa
}
fn()
再次使用webpack打包,最后显示如下:
从打包结果来看,不但入口文件app.js哈希值变了,第三方库vendor.js的哈希值也变了,这当然不是我想要的,因为我更本没有改变第三方库文件。为了解决这个问题可以引入HashedModuleIdsPlugin这个配置项,这样就可以解决以上遇到的问题。
将插件配置做如下修改,代码片段如下:
plugins: [
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
]
在经过以上两个步骤,再次使用webpack打包,结果如下:
webpack3中tree-shaking分析
tree-shaking其实是webpack2中就有的功能特性,但是会因模块函数定义的形式,会有失效的概率,因此,我想用webpack3来测试下,看看是否官网有优化这部分特性。
tree-shaking必须有UglifyJsPlugin这个配置项才能生效,否则统一不生效。
常规定义方法
module/a.js代码如下:
require('../style/a.less');
function a() {
return 'aaaaaa'
}
export {a};
module/b.js代码如下:
require('../style/b.scss');
function b() {
return 'bbbbbbb'
}
export {b};
入口文件代码如下:
// app.js
import _ from 'lodash';
import { a } from './module/a';
import {b} from './module/b';
require("babel-polyfill")
require('./style/lib.css')
function wq() {
return a()
}
wq()
最后使用webpack打包,在压缩的文件中发现bbbbbbb被删除了,因此可以得出一个结论,webpack会动态判断引入的包是否被使用从而再次精简打包文件大小。
原型定义方法
这次我改写a.js和b.js的形式,再其原型链上定义方法,基本代码如下:
// module/a.js
require('../style/a.less');
function a() {
return 'aaaaaa'
}
a.prototype.fn = () => {
return 'aaaaaa'
}
export {a};
// module/b.js
require('../style/b.scss');
function b() {
return 'bbbbbbb'
}
b.prototype.fn = () => {
return 'bbbbbbb'
}
export {b};
入口文件代码如下:
// app.js
import _ from 'lodash';
import { a } from './module/a';
import {b} from './module/b';
require("babel-polyfill")
require('./style/lib.css')
function wq() {
return a.prototype.fn()
}
wq()
最后使用webpack打包,在压缩的文件中发现bbbbbbb没有被删除,只是调用函数的方法不一样,导致tree-shaking并没有生效。
class类编写模式
我再次使用es6中类的概念改写a、b的定义方法,基本代码如下:
// module/a.js
require('../style/a.less');
class a {
fn() {
return 'aaaaaaa'
}
}
export {a};
// module/b.js
require('../style/b.scss');
class b {
fn() {
return 'bbbbbb'
}
}
export {b};
最后使用webpack打包,在压缩的文件中发现bbbbbbb依然没有被删除,tree-shaking并没有生效。
let定义形式
最后我使用最简单的定义形式改写,基本代码如下:
// module/a.js
require('../style/a.less');
let a = 'aaaaaaa'
export {a};
// module/b.js
require('../style/b.scss');
let b = 'bbbbbbb'
export {b};
最后使用webpack打包,在压缩的文件中发现bbbbbbb被删除,tree-shaking生效。
分析
原型定义写法不会被删除很好理解,就是这些原型方法,因为原型方法可能会将要被调用,是一个未知情况,如果直接删除,那么一旦其他模块会动态调用原型方法,那就会造成代码报错,这样是不合理的。
class类的写法似乎不会产生副作用,类中的函数似乎也不是将来可能被调用,再来看最后webpack打包生成的代码,片段如下:
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck__ = __webpack_require__("Zrlr");
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck__);
var b = function () {
function b() {
__WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck___default()(this, b);
}
__WEBPACK_IMPORTED_MODULE_1_babel_runtime_helpers_createClass___default()(b, [{
key: 'fn',
value: function fn() {
return 'bbbbbb';
}
}]);
return b;
}();
首先b是一个自运行函数,并且最终还调用了__webpack_require__("Zrlr")这个模块,因此就类似prototype,会产生一点有副作用的函数,所以不能直接删除。
最后贴上完整的webpack配置项,这里并没有加上vue/react的相关配置,如需要可以自己安装相应插件,通过配合babel-loader进行编译打包,.babelrc配置入门详解。
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const StyleExtHtmlWebpackPlugin = require('style-ext-html-webpack-plugin')
const PreloadWebpackPlugin = require('preload-webpack-plugin')
const config = {
entry: {
app: './src/app.js',
vendor: ['lodash']
},
output: {
path: path.resolve(__dirname, 'cdist'),
filename: '[name].[chunkhash].js'
},
externals: {
'jquery': 'jquery'
},
// resolve: {
// extensions: ['.js', '.json'],
// alias: {
// 'jquery': 'jquery/dist/jquery.slim.min.js',
// '@': resolve('src'),
// }
// },
module: {
rules: [{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(less|scss|css)$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader:"css-loader",
options:{
minimize: true //css压缩
}
},
{
loader:"less-loader",
options:{
minimize: true //css压缩
}
},
{
loader:"sass-loader",
options:{
minimize: true //css压缩
}
}]
})
}
]
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
new CleanWebpackPlugin(['cdist']),
new HtmlWebpackPlugin({
template: './src/index.template.html', //html模板路径
filename: 'wq.html', //生成的html存放路径,相对于path
favicon: './src/favicon.ico', //favicon路径,通过webpack引入同时可以生成hash值
inject: 'body', //js插入的位置,true/'head'/'body'/false
// chunks: ['app', 'vendor'],
//hash: true ,//为静态资源生成hash值
minify: { //压缩HTML文件
removeComments: true, //移除HTML中的注释
collapseWhitespace: false //删除空白符与换行符
}
}),
new PreloadWebpackPlugin({
rel: 'preload',
as: 'script',
include: 'all'
}),
// 解决第三方打包文件hash值不变,最大化缓存
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
new ExtractTextPlugin({
filename: 'common.[contenthash].css',
allChunks: true
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
comments: false,
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true,
}
}),
new StyleExtHtmlWebpackPlugin({
minify: true
})
]
};
module.exports = config;