工程化
- 初始化参数,
webpack.config.js
的module.export
,结合默认参数,merge
出最终的参数。 - 开始编译,通过初始化参数来实例化
Compiler
对象,加载所有配置的插件,执行对象的run方法。 - 确认入口文件。
- 编译模块:从入口文件出发,调用所有配置的Loader对模块进行加载,再找出该模块依赖的模块。通过递归这个过程直到所有入口文件都经过处理,得到一条依赖线。
webpack.js
中核心的操作就是require
了node_modules/webpack-cli/bin/cli.js
cli.js
- 01 当前文件一般有二个操作,处理参数,将参数交给不同的逻辑(分发业务)
- 02
options
(初始化参数) - 03
complier
(实例化Compiler
对象) - 04
complier.run
( 那run
里面做了什么,后续再看 )
- 实例化complier对象,complier会贯穿整个webpack工作流的过程。
complier
继承Tabable
,所以complier
具有操作钩子的能力。例如监听、触发事件,而webpack
是个事件流。- 实例化
complier
对象时,会把很多属性挂载上去。其中NodeEnvironmentPlugin让complier
具备文件读写的能力。 - 将
plugins
中的插件都挂载到了complier
身上 - 将内部默认的
plugins
与complier
建立联系,其中有个EntryOptionsPlugin
处理了入口模块的id
- 在
webpack/lib/SingleEntryPlugin.js
里,compiler
监听了make
钩子- 在
singleEntryPlugin.js
模块的apply方法中有两个钩子监听 dep = SingleEntryPlugin.createDependency(entry,name)
- 其中
compilation
钩子就是让compilation
具备了利用normalModuleFactory
工厂创建一个普通模块的能力。因为compilation
就是利用自己创建出来的模块来加载需要被打包的模块。 - 其中
make
钩子在Compiler.run
的时候会被调用,到这里就意味着某个模块执行打包之前的所有准备工作就做完了。 - 然后
Compilation
调用addEntry
就标志着make
构建阶段开始了。
- 在
run
方法的执行- 刚说了Compiler.run方法执行会调用make钩子,那run方法里就是有一堆钩子按着顺序触发,例如
beforeRun
、run
、compile
- compile 方法的执行
- 先准备些参数,例如刚才提到的
normalModuleFactory
,用于后续创建模块。 - 触发
beforeCompile
- 将准备参数传入一个方法(
newCompilation
),用于创建一个compilation。在newCompilation
内部,先调用createCompilation
,然后触发this.compilation
钩子和compilation
钩子的监听
- 先准备些参数,例如刚才提到的
- 创建了
compilation
对象之后就触发了make
钩子。当触发make
钩子监听的时候,会将compilation
对象传入。compilation.addEntry
就意味着make构建阶段开始。 make
钩子被触发,接收到compilation
对象,那么从compilation
可以解构出三个值。entry
:当前被打包模块的相对路径,name
,context
:当前项目的跟路径processDependencies
处理模块间的依赖关系。函数内部通过async.forEach
来递归创建每个被加载进来的模块。compilation
调用addEntry
方法,内部调用_addModuleChain方法去处理依赖。compilation
当中可以通过normalModuleFactory
工厂来创建一个普通的模块对象。webpack
内部默认开启来一个100
并发量的打包操作. 源码里看到的是normalModuleFactory.create
这样一个方法。- 然后在
beforeResolve
方法里会触发一个factory
钩子监听。上述操作完成后,factory
获取到一个函数并对其进行调用。函数中又有一个resolver
钩子被触发,resolver
其实是处理loader
。当触发resolver
钩子,就意味着所有的Loader
处理完毕。 - 接下里就会触发
afterResolve
这个钩子,调用new NormalModule
- 最后就是调用
buildModule
方法开始编译 -> 调用build
-> 调用doBuild。bulid
过程中会将js
代码转化成ast
语法树,如果当前js
模块引用了其它模块,那就需要递归重复bulid
。当前所有入口模块都被存放在compilation
对象的entries
数组里。 - 那我还需要对当前模块的
ast
语法树进行一些修改,再转化回js
代码。例如将require
转化成__webpack_require__
- 最后
compile
方法最后调用compilation.seal
方法去处理chunk
。 生成代码内容,最终输出文件到指定打包路径下。
- 刚说了Compiler.run方法执行会调用make钩子,那run方法里就是有一堆钩子按着顺序触发,例如
https://mp.weixin.qq.com/s?__biz=Mzg3OTYwMjcxMA==&mid=2247483744&idx=1&sn=d7128a76eed20746cd8c5100f0899138&scene=21#wechat_redirect
babel 原理
- babel 主要是将 A 类型的文件转化为 B 类型
- webpack 只支持 JS 文件,所以需要将其他文件类型都转化为 JS 文件
- parse => 主要将代码转化为 AST
- traversal => 遍历 AST 进行修改
- generator => 将修改后的 AST 转化为 code
webpack 流程
webpack 常见 loader 和 plugin 有哪些?二者区别是什么?
loader
1、file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
2、url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
3、source-map-loader:加载额外的 Source Map 文件,以方便断点调试
4、image-loader:加载并且压缩图片文件
5、babel-loader:把 ES6 转换成 ES5
6、css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
7、style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
8、eslint-loader:通过 ESLint 检查 JavaScript 代码
9、vue-loader:解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理
10、svg-url-loader:是位置支持webpack打包时候可以引入svg文件
11、postcss-loader:用于补充css样式各种浏览器内核前缀,太方便了,不用我们手动写啦
12、ts-loader:用于配置项目typescript
plugin
- HtmlWebpackPlugin => 创建 HTML 文件并自动引入 JS 和 css
- CleanWebpackPlugin => 用于清理之前打包的残余文件
- SplitChunksPlugin => 用于代码分包
- DLLPlugin + DLLReferencePlugin => 用于避免大依赖被频繁重新打包,大幅降低打包时间
- EslintWebpackPlugin => 用于检查代码中的错误
- DefinePlugin => 用于在 webpack config 中添加全局变量
- CopyWebpackPlugin => 用于拷贝静态文件到 dist
- css-Modules=>实现css模块分离
- extract-text-webpack-plugin=>实现一个入口生成一个css文件
- mini-css-extract-plugin(4.0) =>实现了css按需加载
- commonsChunkPlugin => 提取公共文件
区别
- loader 是文件加载器,主要使用在 make 阶段,将 A 类型的文件转化为 B 类型。它能够对文件进行编译、优化、压缩等
- plugin 是 webpack 插件,plugin 可以挂载在整个 webpack 打包过程中的钩子中,可以实现更多功能,如定义全局变量、加速编译、Code Split
webpack 如何解决开发时的跨域问题
- 在配置中添加代理即可
devService: { proxy: { '/api': { target: 'http://host', changeOrigin: true } } }
如何实现 tree-shaking?
- tree-shaking 就是让没有用到的 JS 代码不打包,以减少包的体积
- 利用 tree-shaking 部分 JS 代码
- 使用 ES2015 模块语法,即 export 和 import。
- 不要使用 CommonJS,CommonJS 无法 tree-shaking => babel-loader 添加
modules: false
选项 - 引入的时候只引用需要的模块
import {cloneDeep} from 'lodash-es'
=> 仅仅打包 cloneDeepimport _ from 'lodash'
=> lodash 全部打包,无法 tree-shaking 没有用到的模块
- 不 tree-shaking JS 代码如何开启 tree-shaking => 在 webpack config 中将 mode 设置为 production =>
mode: production
给 webpack 加了非常多的优化- 在项目的 package.json 中添加 "sideEffects" 属性,防止某些文件被 tree-shaking
- case:
import x.js
x.js 添加了 window.x 属性,那么 x.js 就要放到 sideEffects 中 - 所有被 import 的 CSS 都要放在 sideEffects 中
- case:
- 在项目的 package.json 中添加 "sideEffects" 属性,防止某些文件被 tree-shaking
如何提高 webpack 构建速度
- 使用 DLLPlugin 将不常变化的代码提前打包并复用,如 Vue、React
- 使用 thread-loader 进行多线程打包
- 处于开发环境时,在 webpack config 中将 cache 设为 true
- 处于生产环境时,关闭不必要的环节,如 source map
Webpack 和 Vite 的区别
开发环境区别
- Vite 自己实现 server,不对代码打包,充分利用浏览器对
<script type=module>
的支持- 假设 main.js 引入了 vue
- 该 server 会把
import {createApp} from 'vue'
改为import {createApp} from '/node_modules/.vite/vue.js'
这样浏览器就知道去哪里找 vue.js 了
- webpack-dev-server 常使用 babel-loader 基于内存打包,比 Vite 慢很多很多
- 该 server 会把 vue.js 的代码(递归地)打包进 main.js
生产环境区别
- Vite 使用 rollup + esbuild 来打包 JS 代码
- Webpack 使用 babel 来打包 JS 代码,比 esbuild 慢很多很多
文件处理时机
- Vite 只会在你请求某个文件的时候处理该文件
- Webpack 会提前打包好 main.js,等你请求的时候直接输出打包好的 JS 给你
目前已知 Vite 缺点
- 热更新常常失败
- 有些功能 rollup 不支持,需要自己写 rollup 插件
- 不支持非现代浏览器
Webpack 怎么配置多页应用
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'admin.html',
chunks: ['admin']
})
]
}
- 但是这样配置后会有一个重复打包的问题:假设 app.js 和 admin.js 都引入了 vue.js,那么 vue.js 的代码会打包进 app.js,也会打包进 admin.js
- 我们需要使用
optimization.splitChunks
将共同依赖单独打包成 common.js,HtmlWebpackPlugin 会自动引入 common.js
swc、esbuild 是什么?
swc
- 实现语言 => Rust
- 功能 => 编译 JS/TS,打包 JS/TS => TS: 类型擦除
- 优势 => 比 babel 快很多很多(20倍以上)
- 能否集成进 webpack => 可以
- 缺点 => 1. 对 TS 代码进行类型检查(用 tsc 先检查再打包)+ 2. 无法打包 CSS/SVG 等非 JS 文件
esbuild
- 实现语言 => GO
- 功能 => 编译 JS/TS,打包 JS/TS => TS: 类型擦除
- 优势 => 比 babel 快很多很多(10 - 100倍)
- 能否集成进 webpack => 可以
- 缺点 => 1. 对 TS 代码进行类型检查(用 tsc 先检查在打包)+ 2. 无法打包 CSS/SVG 等非 JS 文件
Docker
- Docker 是一种技术
- 在系统上安装 Docker 软件即可使用
- 核心概念
- 容器(Container) => 一台虚拟的计算机,拥有独立的网络、文件系统、进程。默认和宿主机不发生任何交互
- 镜像(Image) => 一个预先定义好的模板文件,Docker 引擎按照这个模板文件启动无数个一模一样,互不干扰的容器。默认是分层的 => 复用、节省空间
- 命令 =>
- docker run --name -v -p -e
- docker images
- docker ps
- 解决了什么问题 =>实现隔离技术 => namespace + Control Groups
- 保证开发、测试、交付、部署的环境完全一致
- 保证资源的隔离
- 启动临时的、用完即弃的环境,如测试
- 秒级的超大规模的部署和扩容