webpack打包优化分为两部分,一部分是大小优化,另一部分是速度优化。
前端构建项目中,为了提高打包效率,往往将第三库与业务逻辑代码分开打包,因为第三方库往往不需要经常打包更新。webpack建议使用CommonsChunk 来单独打包第三方库:
module.exports = {
entry: {
vendor: ['react','react-dom'],
app: "./main",
},
output: {
path: './build',
filename: '[name].js',
library: '[name]_library'
},
plugins: [
new CommonsChunkPlugin({
name: "vendor",
}),
]
};
CommonsChunk虽然可以减少包的大小,但存在问题是:即使代码不更新,每次重新打包,vendor都会重新生成,不符合我们分离第三方包的初衷。
相比于前者,webpack 提供Externals的方法,可以通过外部引用的方法,引入第三方库: index.html
webpack.config.js
module.exports = {
externals: {
jquery: 'jQuery'
}
};
业务逻辑,如index.js
import $ from 'jquery';
$('.my-element').animate(...);
webpack打包时,发现jquery定义在externals,则不会打包jquery代码。由于不需要打包jquery,所以也减少打包时间。 不过externals虽然解决了外部引用问题,但是无法解决以下问题:
import xxx from 'react/src/xx';
webpack遇到此问题时,会重新打包react代码。 参考:https://gold.xitu.io/entry/57996222128fe1005411c649
相比于前者,通过前置这些依赖包的构建,来提高真正的build和rebuild构建效率。也就是说只要第三方库没有变化,之后的每次build都只需要去打包自己的业务代码,解决Externals多次引用问题。 webpack通过webpack.DllPlugin与webpack.DllReferencePlugin两个内嵌插件实现此功能。
1、新建webpack.dll.config.js
const webpack = require('webpack');
module.exports = {
entry: {
bundle: [
'react',
'react-dom',
//其他库
],
},
output: {
path: './build',
filename: '[name].js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: './build/bundle.manifest.json',
name: '[name]_library',
})
]
};
webpack.DllPlugin选项:
运行:
npm run webpack-dll
生成两个文件,一个是打包好的bundlejs,另外一个是bundle.mainifest.json,大致内容如下:
{
"name": "bundle_library",
"content": {
"./node_modules/react/react.js": 1,
"./node_modules/react/lib/React.js": 2,
"./node_modules/process/browser.js": 3,
"./node_modules/object-assign/index.js": 4,
"./node_modules/react/lib/ReactChildren.js": 5,
"./node_modules/react/lib/PooledClass.js": 6,
"./node_modules/react/lib/reactProdInvariant.js": 7,
//其他引用
}
2、配置webpack.config.js
const webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
main: './main.js',
},
output: {
path: path.join(__dirname, "build"),
publicPath: './',
filename: '[name].js'
},
module: {
loaders:[
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
{
test: /\.jsx?$/,
loaders: ['babel-loader?presets[]=es2015&presets[]=react'],
include: path.join(__dirname, '.')
}
]
},
plugins: [
new webpack.DllReferencePlugin({
context: '.',
manifest: require("./build/bundle.manifest.json"),
}),
]
};
webpack.DllReferencePlugin的选项中:
通过排除node_modules下的文件 从而缩小了loader加载搜索范围 高概率命中文件
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/, // 排除不处理的目录
include: path.resolve(dirname, 'src') // 精确指定要处理的目录
}
]
}
cacheDirectory是loader的一个特定的选项,默认值是false。指定的目录(use: ‘babel-loader?cacheDirectory=cacheLoader’)将用来缓存loader的执行结果,减少webpack构建时Babel重新编译过程。如果设置一个空值(use: ‘babel-loader?cacheDirectory’) 或true(use: ‘babel-loader?cacheDirectory=true’) 将使用默认的缓存目录(node_modules/.cache/babel-loader),如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader?cacheDirectory', // 缓存loader执行结果 发现打包速度已经明显提升了
exclude: /node_modules/,
include: path.resolve(dirname, 'src')
}
]
}
Webpack的resolve.modules配置模块库(即 node_modules)所在的位置,在 js 里出现 import ‘vue’ 这样不是相对、也不是绝对路径的写法时,会去 node_modules 目录下找。但是默认的配置,会采用向上递归搜索的方式去寻找,但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;同样,对于别名(alias)的配置,亦当如此:
const path = require('path');
function resolve(dir) { // 转换为绝对路径
return path.join(dirname, dir);
}
resolve: {
modules: [ // 优化模块查找路径
path.resolve('src'),
path.resolve('node_modules') // 指定node_modules所在位置 当你import 第三方模块时 直接从这个路径下搜索寻找
]
}
配置好src目录所在位置后,由于util目录是在src里面 所以可以用下面方式引入util中的工具函数
// main.js
import dep1 from 'util/dep1';
import add from 'util/add';
创建 import 或 require 的路径别名,来确保模块引入变得更简单。配置项通过别名来把原导入路径映射成一个新的导入路径 此优化方法会影响使用Tree-Shaking去除无效代码。
例如,一些位于 src/ 文件夹下的常用模块:
alias: {
Utilities: path.resolve(dirname, 'src/utilities/'),
Templates: path.resolve(dirname, 'src/templates/')
}
现在,替换「在导入时使用相对路径」这种方式,就像这样:
import Utility from '../../utilities/utility';
你可以这样使用别名:
import Utility from 'Utilities/utility';
resolve: {
alias: { // 别名配置 通过别名配置 可以让我们引用变的简单
'vue$': 'vue/dist/vue.common.js', // $表示精确匹配
src: resolve('src') // 当你在任何需要导入src下面的文件时可以 import moduleA from 'src/moduleA' src会被替换为resolve('src') 返回的绝对路径 而不需要相对路径形式导入
}
}
也可以在给定对象的键后的末尾添加 $,以表示精准匹配:
alias: {
util$: resolve('src/util/add.js')
}
这将产生以下结果:
import Test1 from 'util'; // 精确匹配,所以 src/util/add.js 被解析和导入
import Test2 from 'util/dep1.js'; // 精确匹配,触发普通解析 util/dep1.js
当引入模块时不带文件后缀 webpack会根据此配置自动解析确定的文件后缀
后缀列表尽可能小
频率最高的往前放
导出语句尽可能带上后缀
resolve: {
extensions: ['.js', '.vue']
}
用了noParse的模块将不会被loaders解析,所以当我们使用的库如果太大,并且其中不包含import require、define的调用,我们就可以使用这项配置来提升性能, 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理。
// 忽略对jquery lodash的进行递归解析
module: {
// noParse: /jquery|lodash/
// 从 webpack 3.0.0 开始
noParse: function(content) {
return /jquery|lodash/.test(content)
}
}
HappyPack是让webpack对loader的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。从而加速代码构建 与 DLL动态链接库结合来使用更佳。
npm i happypack@next -D
webpack.config.js
const HappyPack = require('happypack');
const os = require('os'); // node 提供的系统操作模块
// 根据我的系统的内核数量 指定线程池个数 也可以其他数量
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().lenght})
module: {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=babel',
exclude: /node_modules/,
include: path.resolve(dirname, 'src')
}
]
},
plugins: [
new HappyPack({ // 基础参数设置
id: 'babel', // 上面loader?后面指定的id
loaders: ['babel-loader?cacheDirectory'], // 实际匹配处理的loader
threadPool: happyThreadPool,
// cache: true // 已被弃用
verbose: true
});
]
happypack提供的loader,是对文件实际匹配的处理loader。这里happypack提供的loader与plugin的衔接匹配,则是通过id=happypack来完成。
这个插件可以帮助有很多入口点的项目加快构建速度。把对JS文件的串行压缩变为开启多个子进程并行进行uglify。
cnpm i webpack-parallel-uglify-plugin -D
// webpck.config.js
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
plugins: [
new ParallelUglifyPlugin({
workerCount: 4,
uglifyJS: {
output: {
beautify: false, // 不需要格式化
comments: false // 保留注释
},
compress: { // 压缩
warnings: false, // 删除无用代码时不输出警告
drop_console: true, // 删除console语句
collapse_vars: true, // 内嵌定义了但是只有用到一次的变量
reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
});
]
执行压缩
webpack --mode production
剔除JavaScript中用不上的代码。它依赖静态的ES6模块化语法,例如通过impot和export导入导出
参考链接:
webpack dllPlugin 使用
webpack的4.0打包优化如何实现