如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。
对于单独的模块不需要解析(不进行AST、不记录依赖、不进行依赖替换等操作,直接生成最终代码),可以做一下处理:
module.exports = {
mode:'development',
mdule:{
noParse:/ElementUI/,
}
}
对于存在依赖的模块,如果使用了noParse,存在的依赖并不会被记录和替换。
针对某些第三方库,不必要使用某些loader处理。例如babel-loader,处理一些已经打包好的第三方库,反而增加了项目构建时间。
module.exports = {
mode:'development',
mdule:{
rules:[
{
test:/\.js$/,
include:/\.\src/,//应用于src目录下
exclude:/node_modules/, //排除某些文件或目录
use:["xx-loader"]
}
],
}
}
当文件内容不变时,经过相同的loader解析后,解析结果并没有改变,所以这个时候将loader的解析结果保存下来,让后续的解析直接使用保存的结果,具体如下:
module.exports = {
module:{
rules:[
{
test:/\.js$/,
use:[{
loader:'cache-loader'.
cacheDirectory:'./cache'
},...loaders]
}
]
}
}
cache-loader的原理是,在执行loader之前,如果发现已缓存文件,直接在loader.pitch函数里return源代码。
通过thread-loader开启一个线程池,后续会把loader放进线程池的线程中运行以提高运行效率。
module.exports = {
module:{
rules:[
{
test:/\.js$/,
use:[{
loader:'cache-loader'.
cacheDirectory:'./cache'
},
'thread-loader']
}
]
}
}
thread-loader可以通过测试决定放置的位置。
热替换(Hot Module Replacement)可以降低代码改动到效果呈现的时间,最新webpack默认开启热替换。
module.exports = {
mode:'development',
devServer:{
open:true,
hot:true
},
plugins:[
new HtmlWebpackPlugin({
template:'./public/index.html'
})
]
}
默认情况下,webpack-dev-server不管是否开启热更新,当重新打包后,都会调用location.reload刷新页面,但如果运行module.hot.accept(),将改变这一行为。module.hot.accept的作用就是让webpack-dev-server通过socket管道,把服务器更新的内容发送到浏览器。
对于样式的热替换,可以添加style-loader
module.exports = {
mode:'development',
devServer:{
open:true,
hot:true
},
module:{
rules:[{
test:/\.css$/,
use:['style-loader','css-loader']
}]
},
plugins:[
new HtmlWebpackPlugin({
template:'./public/index.html'
})
]
}
loader名称 | loader描述 |
---|---|
babel-loader | 转换ES6+新特性语法 |
style-loader | 将css文件引用并插到html |
css-loader | 支持.css文件的加载和解析 |
less-loader | 支持less文件转换 |
sass-loader | 支持sass文件转换 |
ts-loader | 将ts转换成js |
file-loader | 进行图片、字体等文件的打包 |
raw-loader | 将文件以字符串形式导入 |
thread-loader | 多进程打包 |
plugin名称 | plugin描述 |
---|---|
CommonsChunkPlugin | 将chunks相同的模块代码提取成公共js |
CleanWebpackPlugin | 清理构建目录 |
ExtractTextWebpackPlugin | 从捆绑包或捆绑包中提取文本到单独的文件中 |
CopyWebpackPlugin | 将文件或者文件夹拷贝到构建的输出目录 |
HtmlWebpackPlugin | 创建html文件去承载输出的bundle |
UglifyjsWebpackPlugin | 压缩代码 |
TerserPlugin | 压缩代码 |
ZipWebpackPlugin | 将打包的资源生产zip包 |
OptimizeCSSAssetsPlugin | css压缩插件 |
WebpackBundleAnalyzer | 模块依赖的可视化 |
webpack默认不进行分包,会把所有依赖添加到同一个bundle,特别是在多页面打包的情况下,会存在多个chunk引入公共模块导致冗余代码的情况,占用打包体积。
分包的目的是在不影响源代码的情况下降低代码体积,并非所有的情况都适合分包,需要视具体情况而定。
思路:
注意事项:
公共模块打包过程如下:
//webpack.dll.config.js
const webpack = require('webpack')
module.exports = {
mode:"production",
entry:{
element:['element'],
jquery:['jquert']
},
output:{
filename:'dll/[name].js',
library:'[name]'//暴露全局变量名
},
plugin:[
new webpack.DllPlugin({
path:path.resolve(__dirname,'dll','[name].manifest.json'),//资源清单保存位置
name:'[name]'//资源清单中保存的变量名
}),
]
}
//webpack.config.js
const webpack = require('webpack')
module.exports = {
plugin:[
new webpack.DllReferencePlugin({
manifest:require('./dll/element.manifest.json')
}) //指定资源清单,在打包时对照资源清单,当发现该模块是资源清单里的资源时不进行打包处理。
]
}
引用: https://webpack.docschina.org/plugins/dll-plugin#dllplugin
webpack提供了optimization配置项,其中splitChunks是分包策略的配置,通过splitChunksPlugin配置分包策略进行分包,分包时,webpack开启了一个新的chunk,对分离的模块进行打包。
一般开发阶段没必要分包,因为有热替换,并且开发一般是在本机,大文件对开发阶段影响不大。
分包的基础单位是模块。所以在设置minSize时有时会出现打包的文件超过该值。
全局策略:
module.exports = {
entry:{},
output:{},
optimization:{
splitChunks:{
chunks:'all', //默认为async,可选all或者initial
maxSize:30 *1024,//控制包的最大字节数
automaticNameDelimiter:'.',//新chunk名称的分隔符,默认为~
minChunks:1,//一个模块被多少个chunk使用时才会进行分包,默认为1
minSize:30 * 1024,//单位为字节,当分包达到多少字节后才允许被真正地拆包,默认为30000
},
},
plugin:[
]
}
缓存组策略:
当不配置cacheGroups时,内部会存在vendors和default默认配置,我们也可以通过手动更改默认值。
module.exports = {
entry:{},
output:{},
optimization:{
splitChunks:{
cacheGroups:{
//默认存在以下配置,可继承全局配置
vendors:{
test:/[\\/]node_modules[\\/]/,//当匹配到相应模块时,将这些模块进行单独打包
priority:-10//缓存组优先级,优先级越高,该策略越先进行处理,默认值为0
},
default:{
minChunks:2,//最小chunk引用数为2
priority:-20,//优先级
reuseExistingChunk:true,//重用已经分离的chunk
},
elementUI:{
minSize:0,
test: /[\\/]node_modules[\\/]_?element-ui(.*)/,
minChunks:2
}
}
},
}
}
webpack内置terser,terser是一个代码压缩工具,压缩代码除了压缩代码体积,还可以提升破解成本。
Terser官网:https://terser.org/
默认情况下webpack的production模式是开启代码压缩,当我们要更改压缩配置时,可做以下处理:
module.exports = {
optimization:{
minimize:true, //是否启动压缩,默认是只在生产环境自动开启。
minimizer:[
//压缩时使用的插件,当你自动改压缩配置时必须配置相关压缩插件
new TerserPlugin(),//js压缩插件
new OptimizeCSSAssetsPlugin()//css压缩插件
]
}
}
terser、webpack、rollup.js都能够识别/*#__PURE__/注释标记,/*#__PURE__/的作用就是告诉打包工具该函数的调用不会产生副作用。
tree shaking,树摇,可以理解为摇动一棵树,即将腐烂的果实也随之摇动而掉落。
能够tree shaking的代码需要满足一定的代码规范,例如以下情况:
//有利于tree shaking
export xxx 导出,
import {xxx} from 'xxx'导入
//不利于tree shaking
export default {xxx}导出,
import xxx from ‘xxx'导入
上面的情况当webpack依赖分析完毕后,webpack会根据每个模块每个导出是否被使用,标记其为dead code,然后交给代码压缩工具处理。
commonjs很难做到tree shaking,所以主流的库为了做tree shaking,都会发布其es6版本。
以vue为例,vue的代码中大量使用/*#__PURE__*/注释,通常产生副作用的代码都是模块内函数的顶级调用。该注释可以作用于任何语句上,让打包工具识别。
tree shaking对于无法识别副作用的函数会认为是副作用函数,不会产生副作用的代码标记/*#__PURE__*/注释。
相比于js,css并无法做到tree shaking,不过可以通过正则匹配页面样式有没有引用进行移除样式代码。
可以通过purgecss-webpack-plugin进行处理,css module无法处理。
https://www.npmjs.com/package/purgecss-webpack-plugin
通过动态导入模块,例如在判断里使用导入语句。
导入语句不能使用commonjs,虽然require支持动态导入,但是它在打包环节也会进入依赖分析。
动态加载可以使用import(),import作为es6的草案,webpack打包发现使用import()的调用,会对其单独打包,打包结果该代码时,浏览器会使用jsonp远程请求该模块,import()返回的是一个promise。
async function run(){
if(判断条件){
const { chunk } = await import(/* webpackChunkName:'自定义chunkName' */'xxx.js')
}
}
run()
请求的异步的模块会加入webpackJsonp数组里。
值得注意的是,这样的异步导入是不可以做到tree shaking的,不过可以使用取巧的方法,通过一个媒介引入,打包分析过程既能tree shaking又能异步加载。
//媒介文件
export { xxx } from '目标文件'
//主文件
async function run(){
if(判断条件){
const { chunk } = await import( '媒介文件')
}
}
run()
http传输中,开启gzip需要进行如下配置:
对哪些文件压缩,采用哪种压缩算法,这个需要测试权衡,毕竟压缩文件和解压文件都是需要时间的,对于相对大点的文件一般会有收益。
webpack压缩参与的步骤在于将文件预压缩,当请求到来时直接响应已经压缩的文件,而不需要先压缩再响应。
gzip预压缩可以通过compression-webpack-plugin插件进行。
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.export = {
plugin:[
new CompressionWebpackPlugin({
test:/\.js$/ //针对需要预压缩的文件
minRatio:0.5 //压缩比率
})
]
}
以gzip为例,打包之后的文件包含了.js和.js.gz文件
运行性能这方面,主要指js在浏览器端运行速度,取决于我们如何书写高性能的代码。
关于高性能的代码,可以参考常见的设计模式、代码规范、最佳实践等。