这里只是简单的对书中每章的重点进行了总结。
总结
第一章 webpack 简介
- 何为 webpack(一切皆模块)
打包工具,将任何一种资源都看作为一个模块
- 为什么需要 webpack
script 引入脚本的缺点
手动维护模块间(js 脚本)的依赖关系成本高
每一个 script 脚本都需要向服务器发送一个请求
每个 script 脚本的作用域都是全局的,所以会造成全局环境的污染
选择 webpack 的理由
支持多种模块语法 es6, commonjs...
code spliting
可以处理各种类型的资源 (图片,样式,文件...)
社区强大
- 安装
webpack webpack-cli(webpack 的命令行工具)
全局
局部(项目中 ---- 推荐)
打包命令
webpack --entry=./index.js --output-filename=bundle.js --mode=development
- webpack-dev-server 启动开发服务器 live-reload
在浏览器和服务器之间维护一个 websocket 监听文件变化,当打包结果发生变化时向浏览器发送消息
作用:
让 webpck 进行打包(并不会将打包结果输出到 output.path 目录下,而是存放在内存中),并处理打包结果的资源请求
作为 web server 处理对静态资源的请求
第二章 模块打包
- common js (node)
内部有一个 module 对象存储模块信息
导出
module.exports = exports
导入
require
动态导出 (支持条件导入)运行时才能确定依赖关系
值的拷贝
无法 tree shaking
第一次 require 的时候会执行这个模块的代码,后面每次 require 都会直接返回之前的执行结果(module 对象上有一个 loaded 属性来判断该模块是否已经被加载过)
无法解决循环依赖(产生依赖时导入的就是 module.exports 也就是 {})
- es6 module
导出
export
export default
导入
import
静态导入
值的引用,只读
可以进行 tree shaking 编译时即可确定依赖关系
可以通过 es6 module 导出的是值的引用来解决循环依赖的问题
导入语句只能在顶层作用域,不能在代码块中
- 模块打包原理
打包后的 bundle 最外层有一个匿名函数包裹,构成自己的作用域
installedChunks 存储已将被执行过的模块的结果
__webpack_require__ 实现模块加载的函数(require)
-
__webpack_modules__ 一个对象 以 key-value 的形式存放工程中所有产生依赖的模块
- key 为 module 的路径
- value 匿名函数包裹的模块实体,匿名函数的参数赋予了每个模块的导入导出的能力
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _index_ts__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.ts */ \"./src/index.ts\");\n/* harmony import */ var _index_ts__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_index_ts__WEBPACK_IMPORTED_MODULE_0__);\n\n__webpack_require__.e(/*! import() | bar */ \"bar\").then(__webpack_require__.bind(__webpack_require__, /*! ./app */ \"./src/app.js\")).then(function () {\n console.log(\"load app\");\n});\n\nvar fn = function fn() {\n return 1;\n};\n\nconsole.log(fn());\nconsole.log(_index_ts__WEBPACK_IMPORTED_MODULE_0__.sum);\n\n//# sourceURL=webpack://Chapter06/./src/index.js?");
/***/ }),
/***/ "./src/index.ts":
/*!**********************!*\
!*** ./src/index.ts ***!
\**********************/
/***/ (() => {
eval("throw new Error(\"Module build failed (from ../node_modules/happypack/loader.js):\\nError: You may be using an old version of webpack; please check you're using at least version 4\\n at Object.initializeInstance (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/ts-loader/dist/instances.js:275:19)\\n at successLoader (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/ts-loader/dist/index.js:26:17)\\n at Object.loader (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/ts-loader/dist/index.js:23:5)\\n at applySyncOrAsync (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:350:21)\\n at apply (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:277:5)\\n at /Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:135:7\\n at applyPitchLoader (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:188:14)\\n at applyPitchLoader (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:196:14)\\n at applyPitchLoaders (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:226:4)\\n at applyLoaders (/Users/guoxx03/Desktop/test/webpack-demo/note_Webpack实战 入门、进阶与调优/example/node_modules/happypack/lib/applyLoaders.js:120:3)\");\n\n//# sourceURL=webpack://Chapter06/./src/index.ts?");
/***/ }),
/***/ "dll-reference dllExample":
/*!*****************************!*\
!*** external "dllExample" ***!
\*****************************/
/***/ ((module) => {
"use strict";
module.exports = dllExample;
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(__webpack_module_cache__[moduleId]) {
/******/ return __webpack_module_cache__[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = __webpack_modules__;
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/ensure chunk */
/******/ (() => {
/******/ __webpack_require__.f = {};
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = (chunkId) => {
/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/ __webpack_require__.f[key](chunkId, promises);
/******/ return promises;
/******/ }, []));
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/get javascript chunk filename */
/******/ (() => {
/******/ // This function allow to reference async chunks
/******/ __webpack_require__.u = (chunkId) => {
/******/ // return url for filenames based on template
/******/ return "" + chunkId + ".js";
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/load script */
/******/ (() => {
/******/ var inProgress = {};
/******/ var dataWebpackPrefix = "Chapter06:";
/******/ // loadScript function to load a script via script tag
/******/ __webpack_require__.l = (url, done, key, chunkId) => {
/******/ if(inProgress[url]) { inProgress[url].push(done); return; }
/******/ var script, needAttach;
/******/ if(key !== undefined) {
/******/ var scripts = document.getElementsByTagName("script");
/******/ for(var i = 0; i < scripts.length; i++) {
/******/ var s = scripts[i];
/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
/******/ }
/******/ }
/******/ if(!script) {
/******/ needAttach = true;
/******/ script = document.createElement('script');
/******/
/******/ script.charset = 'utf-8';
/******/ script.timeout = 120;
/******/ if (__webpack_require__.nc) {
/******/ script.setAttribute("nonce", __webpack_require__.nc);
/******/ }
/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key);
/******/ script.src = url;
/******/ }
/******/ inProgress[url] = [done];
/******/ var onScriptComplete = (prev, event) => {
/******/ // avoid mem leaks in IE.
/******/ script.onerror = script.onload = null;
/******/ clearTimeout(timeout);
/******/ var doneFns = inProgress[url];
/******/ delete inProgress[url];
/******/ script.parentNode && script.parentNode.removeChild(script);
/******/ doneFns && doneFns.forEach((fn) => (fn(event)));
/******/ if(prev) return prev(event);
/******/ }
/******/ ;
/******/ var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
/******/ script.onerror = onScriptComplete.bind(null, script.onerror);
/******/ script.onload = onScriptComplete.bind(null, script.onload);
/******/ needAttach && document.head.appendChild(script);
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/publicPath */
/******/ (() => {
/******/ __webpack_require__.p = "/dist/";
/******/ })();
/******/
/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ // no baseURI
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "main": 0
/******/ };
/******/
/******/
/******/ __webpack_require__.f.j = (chunkId, promises) => {
/******/ // JSONP chunk loading for javascript
/******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/ if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/ // a Promise means "currently loading".
/******/ if(installedChunkData) {
/******/ promises.push(installedChunkData[2]);
/******/ } else {
/******/ if(true) { // all chunks have JS
/******/ // setup Promise in chunk cache
/******/ var promise = new Promise((resolve, reject) => {
/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/ });
/******/ promises.push(installedChunkData[2] = promise);
/******/
/******/ // start chunk loading
/******/ var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/ // create error before stack unwound to get useful stacktrace later
/******/ var error = new Error();
/******/ var loadingEnded = (event) => {
/******/ if(__webpack_require__.o(installedChunks, chunkId)) {
/******/ installedChunkData = installedChunks[chunkId];
/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
/******/ if(installedChunkData) {
/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/ var realSrc = event && event.target && event.target.src;
/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
/******/ error.name = 'ChunkLoadError';
/******/ error.type = errorType;
/******/ error.request = realSrc;
/******/ installedChunkData[1](error);
/******/ }
/******/ }
/******/ };
/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/ } else installedChunks[chunkId] = 0;
/******/ }
/******/ }
/******/ };
/******/
/******/ // no prefetching
/******/
/******/ // no preloaded
/******/
/******/ // no HMR
/******/
/******/ // no HMR manifest
/******/
/******/ // no deferred startup
/******/
/******/ // install a JSONP callback for chunk loading
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/ var [chunkIds, moreModules, runtime] = data;
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(__webpack_require__.o(moreModules, moduleId)) {
/******/ __webpack_require__.m[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(runtime) runtime(__webpack_require__);
/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ }
/******/
/******/ var chunkLoadingGlobal = self["webpackChunkChapter06"] = self["webpackChunkChapter06"] || [];
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/
/******/ // no deferred startup
/******/ })();
/******/
/************************************************************************/
/******/ // startup
/******/ // Load entry module
/******/ __webpack_require__("./src/index.js");
/******/ // This entry module used 'exports' so it can't be inlined
/******/ })()
- bundle 的执行顺序
和模块的加载顺序一致
最外面的匿名函数会初始化浏览器的环境
加载入口模块(每一个 bundle 有且只有一个入口模块)
-
执行模块代码
遇到 module.exports 则记录模块的导出值
遇到 require 函数交出执行权,进入__webpack_require__函数体内进行加载其他模块的逻辑;
-
在__webpack_require__函数体内会判断即将加载的模块是否存在于 installedChunks 中
若存在,直接取值
若不存在,执行该模块代码。获取到导出值
所有依赖都执行完毕,则回到入口模块中,当入口模块代码执行到结尾也就意味着 bundle 执行完毕
3,4 部是一个循环执行的过程,webpack 不会改变代码的执行顺序,代码和模块的加载顺序完全一直,且是同步的
第三章 资源输入和输出
- 资源处理流程
根据指定的一个/多个入口(entry),告诉 webpack 从哪个目录的哪个文件开始打包
webpack 会从入口模块开始检索,并将具有依赖关系的模块构成一个(树) chunk(一般来说一个入口及其依赖会产生一个 chunk)
最终会将 chunk 打包为 bundle
配置资源入口(entry,context)
context 入口的 path 是一个绝对路径,为了让 entry 更加简洁
-
entry
可以是字符串 chunk name -> main
可以是数组 chunk name -> main
会将多个资源进行合并,然后将最后一个模块作为入口
可以对象(多入口)对象的 value 可以是字符串或数组 chunk name -> 对象的 key
函数
函数的返回值可以是上面的任意值
-
配置资源出口(output)
output.path 绝对路径,打包后的目录 默认为 dist
output.filename 打包后的文件名
[name] -- chunk name
[hash] -- hash 值,每次 build 产生的 hash 值都不同
[chunkhash] -- hash 值, 由入口文件及其依赖的内容生成 -- 推荐使用
[id] -- chunk id
- output.publicPath 会为请求的静态资源加上这个前缀
建议 output.path 和 webpack-dev-server 的 publicPath 保持一致
第四章 预处理器
loader --- 就是一个函数,接受源码字符串
每个 loader 本质都是一个函数。output = loader(input);(loader 之间传递数据:webpack4 之前都为字符串,webpack4 之后支持抽象语法树 AST)
出现的原因是因为,webpack 默认只能处理 js 代码
具体配置可以直接参考官网,这里值写出,不同的资源需要的 loader
exclude/include 缩小 loader 的作用范围
exclude
的优先级更高enforce -- 设置 loader 的执行顺序 pre post normal inline
配置 module.rules
- js
- es6+ 转 es5
babel-loader @babel/core @babel/preset-env
'babel-loader?cacheDirectory'
exclude: /node_modules/
@babel/preset-env 会默认将 es6 module 转为 commonjs module,导致 webpack 的 tree shaking 失效,将@babel/preset-env 的 modules 配置项设为 false 禁用掉模块语句转换;
- css
use: ['style-loader','css-loader']
css-loader:处理 css 的各种加载语法(@import 和 url()函数等)
style-loader:把样式插入页面,将 css-loader 处理过后的字符串,创建一个 style 标签插入到 head 中
ts -- ts-loader
文件(图片) file-loader/url-loader(需要安装 file-loader)
less -- less & less-loader
sass -- node-sass & sass-loader
postcss-loader postcss.config.js 文件(必须,否则会报错)
autoprefixer 自动添加浏览器兼容前缀
postcss-cssnext 帮助我们可以使用最新的 css 语法
第五章 样式处理
- 分离样式
默认我们在 js 文件中 import 的样式会被打包到 js 中,而我们有事后希望可以将样式提取到单独的文件
extract-text-webpack-plugin webpack4 之前使用
mini-css-extract-plugin webpack4 及其之后的版本使用
- css module 直接配置 css-loader 的 option 就可以实现
隔离作用域,防止冲突
第六章 代码分片
实现高性能应用其中重要的一点就是尽可能地让用户每次只加载必要的资源,优先级不太高的资源则采用延迟加载等技术渐进式地获取,这样可以保证页面的首屏速度。
- 通过入口来划分
配置多个 entry(提取 vendor)
- CommonsChunkPlugin webpack4 之前使用
缺点:
一个 CommonsChunkPlugin 只能提取一个 vendor,假如我们想提取多个 vendor 则需要配置多个插件
会使浏览器多加载一个资源,这对于页面渲染速度不友好 manifest.js
CommonsChunkPlugin 在提取公共模块的时候会破坏掉原有 chunk 中的依赖关系,导致难以进行更多的优化
- hash 缓存
使用 CommonsChunkPlugin 提取公共模块是,提取后的资源内部不仅仅是模块的代码,往往还包含 Webpack 的运行时(runtime),即初始化环境的代码,如创建模块缓存对象、声明模块加载函数等,为了使缓存有效,需要单独提取
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
app: './src/app.js',
vendor: ['vue']
},
output: {
filename: '[name].js',
path: path.join(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest', // 必须出现在最后,否则webpack将无法正常提取模块
})
],
devServer: {
publicPath: './dist',
}
}
- optimization.SplitChunks webpack4 及其之后的版本使用 解决了 CommonsChunkPlugin 的问题
一般直接配置
splitChunks:{
chunks: 'all'
}
即可
- import() 异步加载资源,默认可以被提取公共模块
原理:通过 js 动态地在页面 head 标签内插入一个 script 标签,并通过 publicPath 拼接其 url
第七章 生产环境配置
- 环境变量
设置了 mode 属性之后
process.env.NODE_ENV 就是 mode 的值
webapck.DefinePlugin
new webpack.DefinePlugin ({
ENV: JSON.stringify('production'),
IS_PRODUCTION: true,
ENV_ID: 130912098 ,
CONSTANTS: JSON.stringify({ // JSON.stringify? DefinePlugin在替换环境变量时对于字符串类型的值进行的时完全替换。
TYPES: ['foo','bar']
})
})
- source map
devtool 属性
- 资源压缩
- js 压缩
webpack4 及其以上版本 mode: production 的时候会自动压缩打包后的 bundle 默认使用的是 terser
// mode 不是production时,不会自动压缩,需要我们手动配置
optimization: {
minimize: true
},
webpack4 之前的版本使用 uglifyJS (只支持 es5)
- css 压缩
需要先将 css 提取到单独的文件中
optimize-css-assets-webpck-plugin
- 缓存
以 hash 作为文件名的一部分
html-webpack-plugin 实现 js 自动注入
- bundle 大小监控
webpack-bundle-analyzer
vscode -- import cost
bundlesize 工具监控
第八章 打包优化
- happypack 多进程打包 thread-loader
适用于转换工作比较多的 loader (babel-loader/ts-loader)
工作原理:HappyPack 位于 webpack 和主要源文件(例如 JS 源)之间,在该文件中,大量的加载程序发生转换。每次 webpack 解析模块时,HappyPack 都会获取该模块及其所有依赖项,并将这些文件分发到多个工作程序“线程”。
- 减少作用域
exclude/include
noParse 不会解析,但是会打包
IgnorePlugin 不会被打包
Cache:有些 loader 会有一个 cache 配置项,通过配置 Cache,可以缓存编译后的代码,在下一次编译时直接使用上次缓存的结果,提升打包速度。
- 动态链接库 dll
-
Dllplugin 和 Code Splitting 的区别:
可以用来提取公共模块;
Code Splitting 是设置一些特定规则并在打包的过程中根据这些规则提取模块
DllPlugin 是将 vendor 完全拆出来,有自己的一整套 Webpack 配置并独立打包,在实际工程构建时不用再对它进行任何处理,直接取出即可。速度上更快,但也更复杂。
- tree shaking 只对 es module 生效
如果使用 babel-loader,一定要禁用它的模块依赖解析(因为他会将 es module 转换为 common js module 从而导致 tree shaking 失效),modules: false
tree shaking 只是标记,需要压缩工具去除死代码
第九章 开发环境调优
hmr 针对开发环境 需要 webpack-dev-server 配合
hmr 模块热替换 不需要刷新浏览器即可看到更改
开启 HMR
手动:入口文件添加 module.hot.accept(),则 HMR 对于入口文件和其依赖的所有模块都会生效;
借助工具:react-hot-loader、vue-loader 等;
- 原理 拉取的是 chunk diff 即 chunk 需要更新的部分
webpack-dev-server(WDS)与浏览器间维护一个 websocket,当本地资源发生变化时,WDS 会向浏览器推送更新事件,并带上本次构建的 hash,让客户端与上一次资源进行对比—防止冗余更新的出现(eg:添加一个文件末尾空行等);
-
客户端知道了两次构建的差异,就会向 WDS 发起一个请求来获取更改文件的列表;
请求:[hash].hot-update.json;
返回值:hash 值 + chunk name;
-
客户端获取了更新文件的信息,继续向 WDS 获取该 chunk 的增量更新;
请求:需要更新的 chunk name+版本信息;
返回值:增量更新内容;
客户端拿到了 chunk 的更新,利用 webpack 提供的 API 进行处理;
第十章 更多 js 打包工具
都是需要全局安装
rollup --- 专门打包 js 可选多种输出格式
parcel --- 零配置 而且可以以 html 作为入口文件 编译流程间通过传递 AST 节省大量时间(webpack4 之后 loader 之间也可以直接传递 AST,之前只能是字符串)