01
—
开发体验优化
01
优化Loader配置
由于Loader的文件转换操作非常耗时,因此需要让Loader处理尽可能少的文件。 可以使用test/include/exclude这三个配置项来命中Loader应用规则的文件。
module .exports = {
module : {
rules : [{
//如果项目源码中只有 文件,就不要写成/jsx?$/,以提升正则表达式的性能
test: /.js$/,
//babel -loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'] ,
//只对项目根目录下 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname,'src'),
}],
}
}
webpack官方文档中提到:#babel-loader-%E5%BE%88%E6%85%A2-
02
优化resolve.modules配置
resolve.modules的默认值是['node_modules'],意思是先去当前目录的node_modules目录中查找我们要找的模块。 如果没有找到,则去上层目录../node_modules查找。 如果没有找到,就去那里。 在../../node_modules中找到它并像这样推送它。 这与 Node.js 的模块搜索机制非常相似。 当安装的第三方模块放在项目根目录的node_modules目录下时,就不需要按照默认的方式逐层查找了。 您可以指定第三方模块存储的绝对路径,以方便查找。
module.exports = {
resolve: {
modules: [path.resolve( __dirname,'node modules')]
},
}
03
优化resolve.mainFields配置
安装的第三方模块中还会有一个package.json文件,用于描述这个模块的属性。 可以有多个描述条目文件的数组。 原因是各个模块可以同时在多个环境中使用,对于不同的运行环境需要使用不同的代码。
resolve.mainFields的默认值与当前目标配置有关,对应关系如下。
以target等于web为例,Webpack会首先使用第三方模块中的browser数组来查找模块的入口文件。 如果不存在,则使用模块数组,依此类推。
为了减少搜索步骤,在明确第三方模块的入口文件描述数组时,我们可以尽量少设置。 由于大多数第三方模块使用主数组来描述入口文件的位置,因此可以这样配置:
module.exports = {
resolve: {
//只采用 main 字段作为入口文件的描述字段,以减少搜索步骤
mainFields: ['main']
}
}
04
优化resolve.alias配置
resolve.alias配置项通过别名将原导出路径映射到新的导出路径。
在实际项目中,我们经常会依赖一些庞大的第三方模块。 以React库为例,发布的React库包含两套代码。
默认情况下,Webpack 会从入口文件 ./node_modules/react/react.js 开始,递归地解析和处理几十个依赖文件。 这将是一个非常耗时的操作。 通过配置resolve.alias,Webpack可以在使用React库时,直接使用单独完整的react.min.js文件,跳过耗时的递归解析操作。
module.exports = {
resolve: {
//使用 alias 将导入 react 的语句换成直接使用单独、完整的 react.min.js 文件,
//减少耗时的递归解析操作
alias: {
'react': path.resolve( __dirname ,'./node_modules/react/dist/react.min.js'),
}
}
}
但是,对个别库使用这种优化方法会影响使用Tree-Sharking消除无效代码的优化,因为打包的完整文件中的某些代码可能永远不会在我们的项目中使用。 该方法通常用于优化完整性强的库。 由于完整文件中的代码是一个整体,所以每一行都是缺一不可的。 某些工具库不建议使用此方法。
05
优化resolve.extensions配置
当导出没有文件后缀的句子时,Webpack 会手动添加后缀来尝试查找文件是否存在。 如果列表较长,或者正确的后缀更靠前,就会导致更多的尝试,所以resolve.extensions的配置也会影响创建的性能。 在配置resolve.extensions时,需要按照以下几点来做到尽可能的优化和提高性能。
module.exports = {
resolve : {
//尽可能减少后缀尝试的可能性
extensions : ['js'],
}
}
06
优化 module.noParse 配置
module.noParse 配置项可以让 Webpack 忽略一些未模块化的文件的递归解析。 这用于提高构建性能。 原因是一些库,例如jQuery。
module.exports = {
module: {
noParse: /jquery/,
}
};
07
使用Dll插件
DLLPlugin和DLLReferencePlugin以一定的方式实现了bundles的拆分,同时也大大提高了创建速度。
包含大量重用模块的动态链接库只需要编译一次。 在后续的创建过程中webpack加载优化,动态链接库包含的模块不会被重新编译,而是直接使用动态链接库中的代码,因为动态链接库包含大量的模块。 大多数都包含常用的第三方模块,如react、react-dom等,因此只要此类模块的版本不升级,动态链接库就不需要重新编译。
https://github.com/webpack/webpack/tree/master/examples/dll-user
module.exports = {
// mode: "development || "production",
plugins: [
new webpack.DllReferencePlugin({
context: path.join(__dirname, "..", "dll"),
manifest: require("../dll/dist/alpha-manifest.json") // eslint-disable-line
}),
new webpack.DllReferencePlugin({
scope: "beta",
manifest: require("../dll/dist/beta-manifest.json"), // eslint-disable-line
extensions: [".js", ".jsx"]
})
]
};
这个很容易理解,但是操作起来非常费力。好在Webpack5中已经不再使用了,而是使用了HardSourceWebpackPlugin,具有同样的优化效果,而且使用起来极其简单
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const plugins = [
new HardSourceWebpackPlugin()
]
更开心的是这个插件webapck4也可以使用,赶紧使用吧~
注意:该插件与 speed-measure-webpack-plugin 不兼容,后者检测每个进程的持续时间。
08
使用快乐包
Webpack 是单线程模型,这意味着 Webpack 需要一项一项地处理任务,无法同时处理多个任务。 HappyPack将任务分解为多个子流程并发执行。 子进程处理完成后,将结果发送给主进程,从而释放多核CPU笔记本的威力。
const HappyPack = require('happypack')
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
{
test: /.js$/,
// loader: 'babel-loader',
loader: 'happypack/loader?id=happy-babel-js', // 增加新的HappyPack构建loader
include: [resolve('src')],
exclude: /node_modules/,
}
plugins: [
new HappyPack({
id: 'happy-babel-js',
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happyThreadPool
})
]
在整个Webpack创建过程中,最耗时的过程可能就是Loader对文件的转换操作。 由于要转换的文件数据量巨大,所以这种转换操作只能一一处理。 HappyPack的核心原理就是将此文件中的一个任务分解为多个进程进行并行处理,从而减少总的构建时间。
09
使用 ParallelUglifyPlugin
Webpack默认提供了UglifyJS插件来压缩JS代码,而且它采用的是单线程压缩代码,这意味着需要压缩多个js文件,而且需要逐个压缩。 因此,在直接环境下打包压缩代码的速度非常慢(因为压缩JS代码需要先将代码解析成Object表示的AST语句树,然后利用各种规则对AST进行分析处理,导致很长的过程)。
当webpack有多个JS文件需要输出压缩时,会使用UglifyJS将它们一一压缩输出,ParallelUglifyPlugin插件会开启多个子进程,将压缩多个文件的工作分配给多个子进程- 过程。 ,并且每个子进程仍然使用UglifyJS来压缩代码。 无非就是并行处理需要压缩而已。 当多个子任务并行处理时,效率会日益提升。
10
优化文件窃听性能
当监听模式开启时webpack加载优化,配置的Entry文件以及Entry递归依赖的所有文件都会默认被监听。 其中许多文件将存在于node_modules 下。 由于现在的Web项目依赖了大量的第三方模块,所以在大多数情况下,我们不可能去编辑node_modules下的文件,而是去编辑我们自己完美的源代码文件。 一个很大的优化点是忽略node_modules下的文件而不是窃听它们。
module.export = {
watchOptions : {
//不监听的 node_modules 目录下的文件
ignored : /node_modules/,
}
}
通过这些技术优化后,Webpack 消耗的显存和 CPU 将会大大减少。
02
—
输出质量优化
01
Webpack实现CDN访问
事实上,建立需要做到以下几点:
更多参考:%E4%BC%98%E5%8C%96/4-9CDN%E5%8A%A0%E9%80%9F.html
02
使用 Tree Shaking
TreeShaking 正常工作的前提是提交到 Webpack 的 JavaScript 代码必须使用 ES6 模块化句型。 由于ES6模块句型是静态的,因此可以进行静态分析。
首先,为了将使用 ES6 模块化的代码提交到 Webpack,您需要配置 Babel 来保留 ES6 模块化句子。 更改 .babelrc 文件如下:
{
'presets':[
[
'env',{
'module':false
}
]
]
}
第二个要求是使用 UglifyJsPlugin 插件。 如果您处于mode:“生产”模式,则默认已添加此插件。 如果您处于其他模式,可以手动添加。
另一件要记住的事情是打开 optimization.usedExports。 在mode:“生产”模式下,也是默认开启的。 它告诉 webpack 每个模块显式地使用导出。 之后,webpack 会在包文件中添加 /*unusedharmonyexport*/ 等注释,然后 UglifyJsPlugin 插件就会理解这些注释。
module.exports = {
mode: 'none',
optimization: {
minimize: true,
minimizer: [
new UglifyJsPlugin()
],
usedExports: true,
sideEffects: true
}
}
03
提取公共代码
小型网站通常由多个页面组成。 每个页面都是一个独立的单页应用程序。 然而,由于所有页面都使用相同的技术栈和同一套样式代码,因此这些页面之间存在大量相同的代码。 您可以使用 splitChunks 来发送数据包:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
04
拆分代码以按需加载
Webpack 支持两种动态代码分割技术:
import() 用于动态加载模块。 它所引用的模块和子模块将被分割并打包成一个独立的chunk。 Webpack还允许以注释的形式传递参数,以更好地生成块。
// single target
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
'module'
);
// multiple possible targets
import(
/* webpackInclude: /.json$/ */
/* webpackExclude: /.noimport.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
`./locale/${language}`
);
回到实际的业务场景,页面基本上都是以路由的形式呈现的。 如果能够基于路由实现页面级的异步加载,岂不是方便多了。 例如,react中可以使用loadable:
import React from 'react'
import { Route } from 'react-router-dom'
import { loadable } from 'react-common-lib'
const Test = loadable({
loader: () => import('./test'),
})
const AppRouter = () => (
)
05
分析工具
官方可视化工具:
终于