前言
webpack 打包大多数前端工程师们都已经用过,然后今天我想和大家分享的是 webpack 如何打包才能输出最优生产环境文件,主要针对两种人群:未自己手把手配置过 webpack 的人,配置过 webpack 但是不熟悉或者不知所以然的的人.如果 fe 大神看到请勿略此文,谢谢!
准备工作
在做讲解之前,我希望大家先去我的 github 上 clone 下我的 demo 项目,然后按照我的讲解亲自 code 一边!
最基本的打包构建
这是项目目录结构:
ps:先来看下最简单的打包,这边为了模拟打包文件大点,index.js 引入了一些用不到的模块,然后 webpack 只做了最简单的 js 压缩处理.
在 webpack 刚出来的时候,大多数人使用 webpack 其实和用 grunt,gulp 一样,把项目中的引用到的模块,样式文件等都打包成一个 js 文件.这样做的缺点:项目越庞大,打包出来的 js 文件越大,打包时间越长,最关键的是在单页面应用当中,会很大程度加大首屏加载时间,用户体验不好
//index.js
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import Redux from 'redux';
import reactRedux from 'react-redux';
import App from './app/App';
import antd from 'antd';
import 'antd/dist/antd.min.CSS';
import './assets/common.scss';
import './index.scss';
render(<App/>, document.getElementById("app"));
//webpack
....
plugins: [
new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
compress: {
warnings: false
}
})]
上图可以看出打包时间 7s 左右,一个 app.js 文件达到 352kb.然后这边还不包括 antd.min.css(大概 400kb antd-ui 框架样式),你要想这仅仅是我这边只加了 react 开发需要用的一些基本模块,业务逻辑和业务 css 样式基本没有的情况下的数据.实际项目这个数据肯定还要来的大得多.
开始优化
首先我们考虑的是单个文件过大,拆分成多个打包.
css 与 js 分离
把一个超大文件,先按 js 和 css 拆分成两个文件,然后页面并行加载这两文件肯定比加载一个文件来的快的,然后文件体积大小肯定也是有所缩小的.
extract-text-webpack-plugin
//webpack
...
plugins: [
new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
compress: {
warnings: false
}
}),
new ExtractTextPlugin({
filename:'css/[name].css',
allChunks: true
})
]
打包时间有所缩短,app.js 的文件体积也有所缩小.由于我这边业务 css 和业务逻辑代码基本没有,所以这次优化效果不显著.但是及时这样,app.js 还是要比我们心里预计的来的大的多.
公共模块与业务模块分开打包
在实际项目当中,我们引入的模块其实可以分为公共模块与业务模块.
webpack 把入口分为两个,一个业务主入口,另一个公共模块打包入口
CommonsChunkPlugin
//webpack
entry:{
vendors:['react',
'redux',
'react-dom',
'react-redux',
'react-router',
'antd/dist/antd.min.css',//ui框架样式也打包进来
PATHS.ASSETS.join('common.scss')],
app:'./index.js'
},
...
plugins: [
new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
compress: {
warnings: false
}
}),
new ExtractTextPlugin({
filename:'css/[name].css',
allChunks: true
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors',
filename: 'js/[name].js',
warn:false
}),
]
时间为什么长了呢?其实这个不难理解,因为 ui 框架(antd)样式也打包进来了.打包成的四个文件:app.js/app.css,vendors.js/vendors.css.app 是你业务逻辑代码和业务样式,vendors 是你公共引用模块逻辑代码和公共样式.在实际大项目中,这四个文件 js 部分和 css 部分一般大小都差不多,所以分成四个文件后并行加载能大大缩减首页文件加载时间!ps:这边由于业务代码基本没有 所以 app 和 vendors 文件大小差异过大.
利用插件进一步优化打包文件
通过上面 js 和 css 分离,模块划分分离两大步骤,在单页面开发当中基本上你的文件划分定了.在这样的情况下,如果还想减少加载时间,提高体验.在自动化工程这块我们只能在文件体积上做文章了,继续减少文件体积.
optimize-css-assets-webpack-plugin 与 ModuleConcatenationPlugin
//css压缩
new OptimizeCssAssetsPlugin({
// assetNameRegExp: /.css$/g,
// cssProcessor: require('cssnano'),
cssProcessorOptions: {
discardComments: {
removeAll: true
}
},
canPrint: true
}),
//webpack3.0以上
new webpack.optimize.ModuleConcatenationPlugin(),
由于进一步压缩 css 和 js 导致时间打包会有所延长,但是效果还是有的上图可以看出 js 和 css 都一定程度缩小了.
衍生问题 - 打包时间过长怎么办
每次修改业务代码打包都会重新打包公共模块,但是实际情况公共模块打包基本是不会去修改的,那么我么你如果把公共模块打包单独提出来,每次只打包业务模块,这样打包时间是不是会大大缩减?事实上,webpack 确实提供了这样的功能 - DllPlugin 与 DllReferencePlugin
DllPlugin 与 DllReferencePlugin
Dll 这个概念应该是借鉴了 Windows 系统的 dll.一个 dll 包,就是一个纯纯的依赖库,它本身不能运行,是用来给你的 app 引用的.
打包 dll 的时候,Webpack 会将所有包含的库做一个索引,写在一个 manifest 文件中,而引用 dll 的代码(dll user)在打包的时候,只需要读取这个 manifest 文件,就可以了.
这么一来有几个好处:
Dll 打包以后是独立存在的,只要其包含的库没有增减,升级,hash 也不会变化,因此线上的 dll 代码不需要随着版本发布频繁更新.
App 部分代码修改后,只需要编译 app 部分的代码,dll 部分,只要包含的库没有增减,升级,就不需要重新打包.这样也大大提高了每次编译的速度.
假设你有多个项目,使用了相同的一些依赖库,它们就可以共用一个 dll.
如何使用呢?
首先要先建立一个 dll 的配置文件,entry 只包含第三方库:
webpack.DllPlugin 的选项中,path 是 manifest 文件的输出路径;name 是 dll 暴露的对象名,要跟 output.library 保持一致;context 是解析包路径的上下文,这个要跟接下来配置的 dll user 一致.
//webpack-dll
/*webpack-dll页面配置*/
const path = require('path');
const webpack = require('webpack');
//把css样式从打包文件里面分离出来
const ExtractTextPlugin = require('extract-text-webpack-plugin');
//css压缩
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const PATHS = require('./PATHS');
let dllConfig = {
entry: {
vendors: ['react', 'redux', 'react-dom', 'react-redux', 'react-router', 'antd/dist/antd.min.css', PATHS.ASSETS.join('common.scss')]
},
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: 'css-loader?sourceMap'
})
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?sourceMap', 'sass-loader?sourceMap']
})
}]
},
output: {
path: PATHS.DIST,
//打包编译完的文件根目录
filename: "js/[name].js",
//打包编译完文件路径和名称
library: '[name]',
},
plugins: [new ExtractTextPlugin({
filename: 'css/[name]-[contenthash:8]-dll.css',
allChunks: true
}), new OptimizeCssAssetsPlugin({
// assetNameRegExp: /.css$/g,
// cssProcessor: require('cssnano'),
cssProcessorOptions: {
discardComments: {
removeAll: true
}
},
canPrint: true
}), new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
compress: {
warnings: false
}
}),
//webpack3.0以上
new webpack.optimize.ModuleConcatenationPlugin(), new webpack.DllPlugin({
path: 'manifest.json',
name: '[name]',
context: __dirname,
}), ]
};
module.exports = dllConfig;
运行 Webpack,会输出两个文件一个是打包好的 vendor.js,一个就是 manifest.json,长这样:
Webpack 将每个库都进行了编号索引,之后的 dll user 可以读取这个文件,直接用 id 来引用.
{
"name": "vendors",
"content": {
"./node_modules/process/browser.js": {
"id": 0,
"meta": {}
},
"./node_modules/react/index.js": {
"id": 1,
"meta": {}
},
"./node_modules/warning/browser.js": {
"id": 2,
"meta": {}
},
"./node_modules/prop-types/index.js": {
"id": 3,
"meta": {}
},
"./node_modules/invariant/browser.js": {
"id": 4,
"meta": {}
},
"./node_modules/fbjs/lib/emptyFunction.js": {
"id": 5,
"meta": {}
},
"./node_modules/object-assign/index.js": {
"id": 6,
"meta": {}
},
.......
Dll user 的配置:
运行 Webpack 之后,结果如下:
const webpack = require('webpack');
module.exports = {
output: {
path: 'build',
filename: '[name].[chunkhash].js',
},
entry: {
app: './src/index.js',
},
plugins: [new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
}), ],
};
明显速度快了,文件也小了.
平时开发的时候,修改代码后重新编译的速度会大大减少,节省时间.
结尾
其实还有一些优化,高版本的 webpack 比低版本的 webpack 打包要快而且文件要小,这属于 webpack 本身的性能优化带给我们的,例如:
Scope Hoisting - 作用域提升 加快减少闭包函数数量从而加快 js 执行速度
本身打包速度提升 可以自己升级 webpack 去体验下.
2 小时前发布
来源: https://segmentfault.com/a/1190000012848772