赞
踩
参考:
上一篇webpack相关的系列:webpack深入学习,搭建和优化react项目
爪哇教育@字节面试官解析webpack-路白
通常来讲,一个 module 模块就是指一个文件中导出的内容,webpack 会将这些 module 打包成一个或多个 chunk 。
在webpack中module就是一个个打包模块,它通过模块之间的依赖关系构建出打包依赖路径,最终构建出一个bundle。
webpack支持ESModule、CommonJS、AMD、各种资源模块(image\Json\font等等)的打包。
chunk是【webpack打包过程】中的Module集合,是打包过程中module集合的概念。
webpack打包是从一个入口模块开始,入口模块引用其他模块,然后其他的模块再引用更多模块,构成了一个引用关系集合,这些module就形成了一个chunk。
chunk会根据webpack的配置进行拆分,一般包含三种情况
Bundle是webpack最终输出的一个或者多个打包好的文件。
chunk是webpack的打包过程概念,bundle是打包完成后的结果,也就是说大部分的chunk在打包完成后,就成了bundle。
Chunk和Bundle的关系是什么?
大多数情况下,一个entry入口会生成一个chunk,一个bundle,但是也有例外,比如如果加入了devtool:sourcemap的配置,一个入口,一个chunk生成了两个Bundle。
module.exports = {
mode:'production', // webpack要求必填
entry: {
index: './src/index.js,
}, // 打包入口
output: {
path: path.join(__dirname, 'dist'), // 打包输出的路径
filename: '[name].js' , // 打包输出的bundle名称。
}
}
// 上面的配置方式会输出一个chunk(index.js) / 对应一个Bundle(index.js).
但是如果在配置中多加一行:devtool: "source-map" // 生成代码调试可用的源码-打包后代码映射关系
,打包出来的bundle就会多生成一个index.js.map。但是chunk还是只有一个,也就是说一个chunk对应了两个Bundle。
module.exports = {
// ...
entry: {
index: ['./src/index.js','./src/add.js'],
}, // 打包入口
}
// 上面的配置方式会输出一个chunk(index.js) / 对应一个Bundle(index.js).
module.exports = {
// ...
entry: {
index: './src/index.js',
common: './src/common.js'
},
}
module.exports = { mode:'production', // webpack要求必填 entry: { index: './src/index.js, other: './src/multiple.js' }, // 打包入口 output: { path: path.join(__dirname, 'dist'), // 打包输出的路径 filename: '[name].js' , // 打包输出的bundle名称。 }, optimization: { // 优化持久缓存的配置,比如动态引入导致的hashName变动 、 代码分包等。 runtimeChunk: 'single', // runtime splitChunks: { cacheGroups: { commons: { chunks: 'initial', minChunks: 2, minSize:0 }, vendor: { test: /node_modules/, chunks: 'initial', name: 'vendor', enforce: true } } } } }
出现了5个包
目的:为了更好地利用浏览器缓存,不至于每一次打包都导致主包的hash值变动。
官网说明
splitChunks探索
如何使用 splitChunks 精细控制代码分割
webpack splitChunks配置(一)chunks属性的使用
SplitChunks是 Webpack 中一个可以配置代码分割规则,提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,以及拆分过大的js文件,合并零散的js文件。
意思是只要符合下面的条件就会强制分块。
// webpack4的默认配置(开箱即用) splitChunks: { // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all chunks: "async", // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。 minSize: 30000, // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。 minChunks: 1, // 表示按需加载文件时,并行请求的最大数目。默认为5。 maxAsyncRequests: 5, // 表示加载入口文件时,并行请求的最大数目。默认为3。 maxInitialRequests: 3, // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js automaticNameDelimiter: '~', // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。 name: true, // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, // default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } // 分包条件 // 模块在代码中被复用或者来自 node_modules 文件夹 // 模块的体积大于等于30kb(压缩之前) // 当按需加载 chunks 时,并行请求的最大数量不能超过5 // 页面初始加载时,并行请求的最大数量不能超过3
Loader
由于webpack 本身只能打包commonjs规范的js文件,对css,图片等格式的文件没法打包,因此引入了Loader来帮助打包,Loader可以看作是一个各种类型的模块翻译器,将非js模块转化为webpack能识别的js模块,比如css-loader会把css文件最后打包成css.js。
loader运行在打包文件之前(loader为在模块加载时的预处理文件)
Plugin
可以完成各种loader不能完成的功能。plugin扩展了webpack的功能,webpack基于事件流框架 Tapable,它会在运行的各个阶段广播出事件,插件会监听对应的事件,在打包过程中对打包代码做相应处理。。从打包优化和压缩,到重新定义环境变量,还可以用来处理各种各样的任务。因此webpack可以做到针对各种情况灵活处理。
Plugin在整个编译周期中都可以使用。
Compiler
是一个包含了webpack环境的所有配置信息的对象,可以理解为webpack的实例,是全局唯一的,它内部包括options / loader / plugin的信息和 webpack 整个生命周期相关的钩子。
Compliation
包含了当前的模块资源和编译生成资源,webpack在开发模式下运行的时候,每当检测到一个文件变化,就会创建一次新的Compliation。
事件节点:
run:开始编译
make:从entry开始递归分析依赖并对依赖进行build
build-moodule:使用loader加载文件并build模块
normal-module-loader:对loader加载的文件用acorn编译,生成抽象语法树AST
program:开始对AST进行遍历,当遇到require时触发call require事件
seal:所有依赖build完成,开始对chunk进行优化(抽取公共模块、加hash等)
optimize-chunk-assets:压缩代码
emit:把各个chunk输出到结果文件
web前端高级webpack - webpack常见面试题及手写loader和plugin
在编写 loader 前,我们首先需要了解 loader 的本质
其本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader设为一个箭头函数
函数接受一个参数,为 webpack 传递给 loader 的文件源内容
函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息
函数中有异步操作或同步操作,异步操作通过 this.callback返回,返回值要求为 string 或者 Buffer
一般在编写loader的过程中,保持功能单一,避免做多种功能
代码如下所示:
// 导出一个函数,source为webpack传递给loader的文件源内容 module.exports = function(source) { const content = doSomeThing2JsString(source); // 如果 loader 配置了 options 对象,那么this.query将指向 options const options = this.query; // 可以用作解析其他模块路径的上下文 console.log('this.context'); /* * this.callback 参数: * error:Error | null,当 loader 出错时向外抛出一个 error * content:String | Buffer,经过 loader 编译后需要导出的内容 * sourceMap:为方便调试生成的编译后内容的 source map * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程 */ this.callback(null, content); // 异步 return content; // 同步 }
实现一个能够删除源码中的console语句的Loader
const parser = require('@babel/parser') ;// 可以把js源码转成 ast语法树 const traverse = require('@babel/traverse').default // 可以递归遍历 ast节点 const generator = require('@babel/generator').default; // 把修改好的ast语法树 再转成源码字符串 const types = require('@babel/types') // 操作节点的增删改 module.exports = function(source){ const ast = parser.parse(source,{sourceType:'module'}) // console.log(ast.program.body); traverse(ast,{ CallExpression(path){ // console.log(path) if(types.isMemberExpression(path.node.callee) && types.isIdentifier(path.node.callee.object,{name:'console'})){ path.remove() } } }) const output = generator(ast,{},source); return output.code }
如果自己要实现plugin,需要遵循一定的规范:
class MyPlugin {
// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);
// do something...
})
}
}
打包的时候生成一个显示当前项目文件列表的myfile.md说明文件。
class Myplugin{ // 可以再打包的时候产生一个新的文件 constructor(options){ // new 这个插件是传进来的参数 this.options = options } apply(compiler){ const hooks = compiler.hooks; // 监听事件 if(hooks){ hooks.emit.tap('myplugin',function(complication,callback){ var str = '文件列表是:\n'; for(let k in complication.assets){ str += `文件名:${k},,,大小是 ${ complication.assets[k].size()} \n\n` } complication.assets['myfile.md'] = { source(){ return str }, size(){ return str.length } } callback&&callback() }) }else{ compiler.plugin('emit',function(complication,callback){ var str = '文件列表是:\n'; for(let k in complication.assets){ str += `文件名:${k} , 大小是 ${ complication.assets[k].size()} \n\n` } complication.assets['myfile.md'] = { source(){ return str }, size(){ return str.length } } callback&&callback() }) } } } module.exports = Myplugin
流程可以大致划分为 以下7个阶段
Webpack HMR 原理解析
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。 HMR的核心就是客户端从webpack服务端拉取更新后的文件信息,准确的说是chunk 需要更新的部分的模块哈希值。
webpack-dev-server(WDS) 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。
客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash值的JSON串),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理。像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
在淘宝优化了一个大型项目,分享一些干货
「吐血整理」再来一打Webpack面试题
一般来说我们的优化有两个方向:
在webpack中可以使用按需加载、异步加载分包、提取公共代码等方式去优化。
为了保证首屏渲染时间,需要使用懒加载,也就是异步引入的方式优化,把首屏暂时用不到的包异步引入,这样分包的时候就会自动把这些文件提取到别的chunk里了。如果担心分的包太细碎会发起太多请求,可以使用MinSize和限制最高分包数等属性来限制。
开启 optimization.runtimeChunk 属性,将模块依赖关系单独拉出来一个分包,这样如果一个模块变动,与它相关的其他模块内容就不会跟着连锁变动了,名字hash值也不会变,就可以继续利用之前的缓存设置。
配置css和js文件的代码压缩:css-minimizer-webpack-plugin 和 webpack-parallel-uglify-plugin 插件。
可以通过多线程来提升Webpack打包速度的工具:HappyPack(不维护了)、thread-loader,一般用在babel-loader之类转译文件多、时间长的Loader上效果会比较明显。
webpack5还提出了一个Module Federation(模块联邦)的属性配置,有利于加快应用启动和编译速度。 比如UMI的项目就可以直接在config里面配置MFSU之后,编译速度大大加快。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。