黑知识
为什么说webpack只能处理js
webpack的Parser内核是acorn,是用于解析js的。
所以webpack只能编译处理js。
node能够处理的有两种,一个是字符串,一个是buffer流,
比如文件就是一个buffer流,
在webpack中,处理图片文件时,会通过loader,将此图片编译为一个base64 url(也是一个字符串) 或将这个图片拷贝至dist目录,
然后生成一个该图片的引用路径(也是一个字符串),
也就是说,文件流经过loader处理后,都变成了一个字符串,这个时候再传给webpack,webpack就可以识别了。
下面摘录至官网
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
1 | // node_modules\webpack\lib\Parser.js |
为什么说webpack要借助loader来处理非js
参考《为什么说webpack只能处理js》
插件比loader能做的事情更加广泛
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。
插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
参考官网
插件目的在于解决 loader 无法实现的其他事。参考官网
监听(Watching)
参考1
2
3
4
5
6
7
8
9
10
11
12
13
14const webpack = require('webpack');
const compiler = webpack({
// [配置对象](/configuration/)
});
const watching = compiler.watch({
// [watchOptions](/configuration/watch/#watchoptions) 示例
aggregateTimeout: 300,
poll: undefined
}, (err, stats) => { // [Stats Object](#stats-object)
// 这里打印 watch/build 结果...
console.log(stats);
});
由多入口文件共享引起的问题
主要是对象的浅拷贝问题,解决方法如下:
详细参考官网1
2
3optimization: {
runtimeChunk: 'single',
}
import() and CommonJs 问题
我们之所以需要 default,是因为 webpack 4 在导入 CommonJS 模块时,将不再解析为 module.exports 的值,
而是为 CommonJS 模块创建一个 artificial namespace 对象,更多有关背后原因的信息,请阅读 webpack 4: import() and CommonJs。
详细参考官网1
2
3
4return import('lodash')
.then(({ default: _ }) => {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
如何代码分离重型依赖库
不推荐,可拓展下视野
虽然下面是多entry,重复依赖提取,但也给了一种思路,如何将重型第三方包提取到一个单独的 chunk中的思路。
参考官网1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20module.exports = {
mode: 'development',
entry: {
index: {
import: './src/index.js',
dependOn: 'shared',
},
another: {
import: './src/another-module.js',
dependOn: 'shared',
},
shared: 'lodash',
// 也可以使用数组
// shared: ['lodash','react'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
推荐SplitChunksPlugin
当然最好的方法,推荐用官网的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
resolveLoader 与 resolve
webpack的世界有上面两套 resolve 规则.
resolver 是一个库(library),用于帮助找到模块的绝对路径。参考官网
说明webpack最后将所有的相对路径解析为 绝对路径。
它支持的绝对路径有这几种:1
2import "/home/me/file";
import "C:\\Users\\me\\file";
resolveLoader 与 resolve 的配置意义规则相同,
秉着 单一职责原则,设计两个分别控制 module的import(用于业务代码内的import)以及 loader的import。
从这个角度看出。
webpack的世界有上面两套 resolve 规则定义,
一般只需要定义 resolve 即可,除非我们要自定义loader 时,可能要用到 resolveLoader。
模块热替换如何运行的
在应用程序中
通过以下步骤,可以做到在应用程序中置换(swap in and out)模块:
- 应用程序要求 HMR runtime 检查更新。
- HMR runtime 异步地下载更新,然后通知应用程序。
- 应用程序要求 HMR runtime 应用更新。
- HMR runtime 同步地应用更新。
等等。。
详细参考官网
webpack 是什么
它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如:images, fonts 和 stylesheets。
详细参考官网
以上说明了webpack的两个特色和两个主要工作内容:
- 编译js(支持 ESM 和 CommonJS)
- 同时有能力编译 文件流
chunk 有两种形式
initial(初始化) 是入口起点的 main chunk。此 chunk 包含为入口起点指定的所有模块及其依赖项。
non-initial 是可以延迟加载的块。可能会出现在使用 动态导入(dynamic imports) 或者 SplitChunksPlugin 时。
这篇官网内容也介绍了1
2output.filename - 用于 initial chunk 文件
output.chunkFilename - 用于 non-initial chunk 文件
magic comment(魔术注释)
同样参考官网1
2
3
4
5
6
7import(
// 下面就是魔术注释,会被webpack解析,魔术注释在webpack中还有其他应用
/* webpackChunkName: "app" */
'./app.jsx'
).then((App) => {
ReactDOM.render(<App />, root);
});
好用的cli
注意版本号要在 webpack 5.x1
2
3
4
5
6
7npx webpack info --output markdown #打印webpack loader信息,以及电脑、浏览器信息,非常棒
npx webpack configtest ./webpack.config.js
npx webpack init ./my-app #这个用于webpack 测试非常方便,然后根据提示是否使用es、scss,不要使用--template=default
npx webpack --progress #显示构建进度
npx webpack --json stats.json #生成stats对象,可用于 webpack-bundle-analyzer 分析
npx webpack --config ./first.js --config ./second.js --merge #合并配置,底层基于webpack-merge
npx webpack --analyze #先确保 webpack-bundle-analyzer 安装
cli配置优先级高于webpack.config.js
注意的是 cli 的配置,优先级要比 webpack.config.js 的高。
比如:1
npx webpack --output-path customdist
上述优先级高于webpack.config.js:1
2
3
4
5const config = {
output: {
path: path.resolve(__dirname, 'dist'),
},
}
export与import的对象无法使用 现象汇集
懒加载的时候
1 | // 在懒加载的使用: |
注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。
ts
如果想在 TypeScript 中保留如import _ from ‘lodash’;的语法被让它作为一种默认的导入方式,需要在文件 tsconfig.json 中设置 “allowSyntheticDefaultImports” : true 和 “esModuleInterop” : true 。这个是与 TypeScript 相关的配置,在本文档中提及仅供参考。
1 | // 在ts中 |
webpack.config.ts
1 | import * as path from 'path'; |
Shimming 如何兼容(老)library打包
Shimming 有点类似打补丁的意思。
Shimming 有两部分意思:
第一 polyfill 的注入;
第二 非规范的使用一些库library,这里又有两层意思:
- 处理 比如 将lodash 放到 全局环境中;
- 将以前的一些老版本库或没有提供commonjs 或 esm 标准的,如何嵌入到当前主流的模块化项目中使用。
Shimming 的思想不符合 webpack的推荐的闭环模块化思想,但webpack推荐的闭环模块化思想不一定能满足一个项目所有的编译需求,
因此也是webpack 对其编译思想的一个延申。
webpack 背后的整个理念是使前端开发更加模块化。也就是说,需要编写具有良好的封闭性(well contained)、不依赖于隐含依赖(例如,全局变量)的彼此隔离的模块
webpack的target配置
直接定义的方式,参考官网,
官网还写了配合使用
其实是这样理解的,任何 browserslist 的定义方式,都会被webpack target 读取。
因此webpack target 定义 browserslist 时,并非给target赋值,
而是按照 browserslist 规范,定义成 .browserslistrc 或 package.json内,等等。
webpack-dev-server 与 IE 兼容问题
参考《babel笔记 – webpack-dev-server 与 IE 兼容问题》
webpack-dev-server 调试
参考 官网
如果你碰到了问题,请将路由导航至 /webpack-dev-server 将会为你展示服务文件的位置。例如: http://localhost:9000/webpack-dev-server。
正则与 glob 模式区别:
webpack中的正则与 glob 模式区别:
关于glob-to-regexp:
参考-glob-to-regexp
参考 - webpack官网
当使用 glob 模式时,我们使用 glob-to-regexp 将其转为正则表达式,因此,在使用 watchOptions.ignored 的 glob 模式之前,请确保自己熟悉它。
1 | module.exports = { |
关于正则:
参考 - webpack官网
当使用 glob 模式时,我们使用 glob-to-regexp 将其转为正则表达式,因此,在使用 watchOptions.ignored 的 glob 模式之前,请确保自己熟悉它。
1 | module.exports = { |
ProvidePlugin DefinePlugin
前者用于 全局引入 第三方包,省去每次 import 的麻烦;
后者用于 定义变量值,相当于一个传送门,将编译态的数据传送给业务代码使用;
黑知识二
为什么所有的loader可将文件写入内存
dev模式下,webpack-dev-server可以轻易将webpack的产物打包到内存中,
但比如file-loader 为什么也可以轻易将产物打包进入内存,
其原因可能有:
webpack源码中,有一套 fs 系统,在compiler初始化的时候,初始好的,
默认情况下,webpack使用 node 的fs库,初始化webpack的fs系统,
但是在webpack-dev-server中,使用了能写入内存的fs来初始化了webpack的fs系统,
所以后面所有的loader也好、plugin也好,使用的fs,其实就拥有了这个写入内存的能力。
webpack源码学习经验
阶段一:了解webpack的事件机制 tapable,如果有一定了解了,就不用看了:
找webpack-tapable 系列的视频快速刷一遍,对 tapable有个印象即可
阶段二:先大量刷视频,目的 了解webpack调试入门,以及loader与plugin的大致使用:
手摸手带你实现打包器 仅需 80 行代码理解 webpack 的核心 https://www.bilibili.com/video/BV1oL411V7BQ?spm_id_from=333.999.0.0
实现 loader视频: https://www.bilibili.com/video/BV1d5411d7kH?spm_id_from=333.999.0.0
实现 plugins视频: https://www.bilibili.com/video/BV1d5411d7kH?spm_id_from=333.999.0.0
webpack5 源码分析 视频: https://www.bilibili.com/video/BV12L411t7Pr?spm_id_from=333.999.0.0
任务14:webpack调试和阅读 https://www.bilibili.com/video/BV1N5411j74S?p=14
任务15:webpack自定义loader https://www.bilibili.com/video/BV1N5411j74S?p=14
阶段三:大量刷webpack源码相关的系列文档
[万字总结] 一文吃透 Webpack 核心原理
这个系列最经典,后期自己独自看源码时跟着这个教程走 — webpack4 源码分析系列 -里面去找webpack源码系列
webpack3 源码分析系列: 玩转webpack(一)上篇:webpack的基本架构和构建流程
webpack3 源码分析系列: 玩转webpack(一)下篇:webpack的基本架构和构建流程
webpack3 源码分析系列: 玩转webpack(二):webpack的核心对象
webpack3 源码分析系列: 玩转webpack 备用地址
阶段四:自己调试webpack源码
跟着这个教程走 — webpack4 源码分析系列 -里面去找webpack源码系列,
debug webpack源码。
阶段五:刷一遍webpack官网:
这里要注意刷中文官网顺序:
概念
api
指南
配置
loader
plugin
概念、api、指南、配置 要细看;
loader、plugin 扫了一眼,主要讲各个loader plugin的使用方法,看的价值不高,用的时候再看比较好,这两个只花了两个小时看完。
待研究
entry 多个属性对象与数组定义方法使用场景
一种是entry 数组的定义方式:
参考官网这里的 注解部分 【当你向 entry 传入一个数组时会发生什么?】 - 单个入口(简写)语法
一种是多属性的定义方式:1
2
3
4
5
6const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
参考官网 - 分离 应用程序(app) 和 第三方库(vendor) 入口
下面摘录上面官网链接的章节, 这部分描述同样值得关注
为什么?此设置允许你使用 CommonsChunkPlugin 从「应用程序 bundle」中提取 vendor 引用(vendor reference) 到 vendor bundle,并把引用 vendor 的部分替换为
__webpack_require__()调用。如果应用程序 bundle 中没有 vendor 代码,那么你可以在 webpack 中实现被称为长效缓存的通用模式。
我觉得在对于一些 cdn 等等, 可能会以 entry 数组的方式 引入。
目前好奇的是,这些适合什么样的场景。
学会适用场景为自己项目所用。