持续更新: 2022.06.07
webpack 是基于 nodejs 的, 所有的 nodejs 核心模块的 api 都可以直接引入使用。
集合
三石的webpack.config.js(基础篇)
三石的webpack.config.js(output篇)
三石的webpack.config.js(optimization篇)
三石的webpack.config.js(resolve篇)
三石的webpack.config.js(module篇)
三石的webpack(模块联邦篇)
三石的webpack(Plugins篇)
三石的webpack(Loader篇)
三石的webpack(tree-shaking篇)
三石的webpack.config.js(进阶篇)
三石的webpack(HMR篇)
三石的webpack(babel篇)
其他
import、require、export、module.exports 混合使用详解
webpack是什么?
可以看做一个模块化打包机,分析项目结构,处理模块化依赖,转换成为浏览器可运行的代码。
- 代码转换: TypeScript 编译成 JavaScript、SCSS,LESS 编译成 CSS.
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片。
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
webpack 工作流程
- 参数解析:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
- 找到入口文件:从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module
- 调用 Loader 编译文件:每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则
- 遍历抽象语法树(AST),收集依赖:对 Module 进行转换后,再解析出当前 Module 依赖的 Module
- 生成 Chunk:这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk
- 输出文件:最后 Webpack 会把所有 Chunk 转换成文件输出
想看具体的 webpack 编译原理源码的同学 传送门
脚本中的mode运用
许多 library 通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。如当process.env.NODE_ENV 没有被设置为 'production' 时,library 为了使调试变得容易,添加额外的 log(日志记录) 功能
// index.js
...
+ if (process.env.NODE_ENV !== 'production') {
+ console.log('Looks like we are in development mode!');
+ }
...
chunk 和 bundle是什么?
什么是chunk?
webpack专用术语,用于管理webpack打包进程。chunk和输出的bundle一一对应,但是,有些是一对多的关系。
如果entry配置的是对象object,可能就会出现多个chunk,这时候chunk名称是对象健值对中健的名称
产生Chunk的三种途径:
- entry入口
- 异步加载模块
- 代码分割(code spliting)
什么是bundle?
bundle是已经加载完毕,和被编译后的源代码最终版本。由多个模块产生,一个应用可以拆分为多个bundle。Bundle Splitting是webpack优化代码的一种方法。
总之,chunk和bundle都可以拆分,按需加载,减少代码量
按需加载
很多时候我们不需要一次性加载所有的JS文件,而应该在不同阶段去加载所需要的代码。webpack内置了强大的分割代码的功能可以实现按需加载。
比如,我们在点击了某个按钮之后,才需要使用使用对应的JS文件中的代码,需要使用 import() 语法:
document.getElementById('btn').onclick = function() {
import('./handle').then(fn => fn.default());
}
import() 语法,需要 @babel/plugin-syntax-dynamic-import 的插件支持,但是因为当前 @babel/preset-env 预设中已经包含了 @babel/plugin-syntax-dynamic-import,因此我们不需要再单独安装和配置。
webpack 遇到 import('src') 这样的语法的时候,会这样处理:
- 以 src 为入口新生成一个
Chunk - 当代码执行到
import所在的语句时,才会加载该Chunk所对应的文件(如这里的1.bundle.8bf4dc.js)
大家可以在浏览器中的控制台中查看文件加载的情况,只有点击之后,才会加载对应的 JS
预加载模块
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
preload chunk 会在父chunk加载时,在父chunk中立即请求,以并行方式加载
prefetch chunk 会在父chunk加载结束后,在浏览器闲置时下载
不正确地使用
webpackPreload会有损性能,请谨慎使用
// prefetch 加载
import(/* webpackPrefetch: true */ './path/LoginModal.js');
source map是什么?
source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。
map文件只要不打开开发者工具,浏览器是不会加载的。
线上环境一般有三种处理方案:
hidden-source-map:借助第三方错误监控平台 Sentry 使用nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)
注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。
hash
| hash类型 | 区别 |
|---|---|
| hash | hash是根据整个项目构建,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值 |
| chunkhash | chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的代码块(chunk),生成对应的哈希值,某文件变化时只有该文件对应代码块(chunk)的hash会变化 |
| contentHash | 每一个代码块(chunk)中的js和css输出文件都会独立生成一个hash,当某一个代码块(chunk)中的js源文件被修改时,只有该代码块(chunk)输出的js文件的hash会发生变化 |
- hash 主要用于开发环境中,在构建的过程中,当你的项目有一个文件发现了改变,整个项目的hash值就会做修改(整个项目的hash值是一样的),这样子,每次更新,文件都不会让浏览器缓存文件,保证了文件的更新率,提高开发效率
- chunkhash
在生产环境中,我们会把第三方或者公用类库进行单独打包,所以不改动公共库的代码,该
chunk的hash就不会变,可以合理的使用浏览器缓存了。
但是这个其实是存在问题的,生产环境中我们会用webpack的插件,将css代码打单独提取出来打包。这时候chunkhash的方式就不够灵活,因为只要同一个chunk里面的js修改后,css的chunk的hash也会跟随着改动。因此我们需要contenthash
- contenthash
模块之间存在相互之间的引用关系,有一个manifest文件,这也就导致了内容不做修改的话,contenthash值还是会有所改变。所以我们会用到
optimization.runtimeChunk配置
魔术注释是什么?
魔术注释是由 Webpack 提供的,可以为代码分割服务的一种技术。通过在 import 关键字后的括号中使用指定注释,我们可以对代码分割后的 chunk 有更多的控制权
- webpackChunkName
import(/* webpackChunkName:"myModule" */'./myModule');
添加注释后,配合 webpack.config.js 里的output配置,生成的chunk文件名会为 myModule.xxxxx(chunkhash).js
- webpackMode
import(/* webpackMode:"lazy" */'./myModule');
webpackMode 的默认值为 lazy 它会使所有异步模块都会被单独抽离成单一的 chunk,若设置该值为 lazy-once,Webpack 就会将所有带有标记的异步加载模块放在同一个 chunk 中。
- webpackPrefetch
import(/* webpackPrefetch:true */'./myModule');
通过添加 webpackPrefetch 魔术注释,Webpack 令我们可以使用与 相同的特性。让浏览器会在 Idle 状态时预先帮我们加载所需的资源
importLoaders 是什么?
概念
用于css相关loader中,它的作用是指该loader之后的几个loader可以来处理匹配的资源
默认数量是0
场景
我们现在有个配置:
{
test: /\.scss$/,
use: ['style-loader','css-loader','sass-loader','postcss-loader']
}
如果我们在.scss文件中再引入.scss呢?
// index.scss
@import './creare.scss';
body {
.imgtitle {
width: 100px;
height: 100px;
transform: translate(100px, 100px);
}
}
这个时候,打包到 './creare.scss' 这个时候,打包的话,它就不会帮你重新安装 postcss-loader 开始打包,因为这时候从下往上已经走到了 css-loader 里。
那这时候,我们就可以使用 importLoaders:2 来从 css-loader下面2个loader开始:
{
test: /\.scss$/,
use: ['style-loader',
{
loader: 'css-loader',
options:{
importLoaders:2,
modules : true
}
},
'sass-loader',
'postcss-loader'
]
}
此时,无论你是在js中引入scss文件,还是在scss中引入scss文件,都会重新依次从下往上执行所以loader。
shimming 是什么?
简单翻译就是垫片,它解决的场景有哪些呢? 当你在使用第三方库,每个js文件都需要依赖它,这时候一一引入的话让人很繁琐。此时,shimming就派上用场了。
我们需要使用 ProvidePlugin 插件,这个webpack是内置的。使用 ProvidePlugin后,能够在通过 webpack 编译的每个模块中,通过访问一个变量来获取到 package 包。
👇
new webpack.ProvidePlugin({
// 这里设置的就是你相应的规则了
// 等价于在你使用lodash模块中语句: import _ from 'lodash'
_: 'lodash'
})
//add.js
export const Arr_add = arr=>{
let str = _.join(arr,'++');
return str;
}
如果我们只想暴露某个库中的某个方法呢?
👇
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
//add.js
export const Arr_add = arr=>{
let str = join(arr,'++'); // 不是 _.join
return str;
}
更多的用法可以查看shimming垫片