几天没见面试题,很焦虑。今天找找 webpack 的面试题看看。
Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术。
ES6 前,我们可以使用 CommonJS 引入模块:require(),这种引入是动态的,也意味着我们可以基于条件来导入需要的代码:
let dynamicModule;
// 动态导入
if (condition) {
myDynamicModule = require('foo');
} else {
myDynamicModule = require('bar');
}
但是 CommonJS 规范无法确定在实际运行前需要或者不需要某些模块,所以 CommonJS 不适合 tree-shaking 机制。
在 ES6 中,引入了完全静态的导入语法:import。这也意味着下面的导入是不可行的:
// 不可行,ES6 的import是完全静态的
if (condition) {
myDynamicModule = require('foo');
} else {
myDynamicModule = require('bar');
}
我们只能通过导入所有的包后再进行条件获取。如下:
import foo from 'foo';
import bar from 'bar';
if (condition) {
// foo.xxxx
} else {
// bar.xxx
}
我们只能通过导入所有的包后再进行条件获取。如下:
import foo from 'foo';
import bar from 'bar';
if (condition) {
// foo.xxxx
} else {
// bar.xxx
}
ES6 的 import 语法可以完美使用 tree shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。
因为 tree shaking 只能在静态 modules 下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在 ES6 中使用 tree shaking 是非常容易的。
CommonJS 是一种模块规范,最初被应用于 Nodejs,成为 Nodejs 的模块规范。运行在浏览器端的 JavaScript 由于也缺少类似的规范,在 ES6 出来之前,前端也实现了一套相同的模块规范 (例如: AMD),用来对前端模块进行管理。自 ES6 起,引入了一套新的 ES6 Module 规范,在语言标准的层面上实现了模块功能,而且实现得相当简单,有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对 ES6 Module 兼容还不太好,我们平时在 Webpack 中使用的 export 和 import,会经过 Babel 转换为 CommonJS 规范。在使用上的差别主要有:
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJs 是单个值导出,ES6 Module 可以导出多个
4。 CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
5。 CommonJs 的 this 是当前模块,ES6 Module 的 this 是 undefined
模块化是一种处理复杂系统分解为更好的可管理模块的方式,可以用来分割,组织和打包应用。
每个模块完成一个特定的子功能,所有模块按某种方法组装起来,成为一个整体(bundle)
模块打包工具除了 webpack 外,还有:
gulp 和 grunt 只是定义为构建工具,不参与类比
rollup 是一款 ES Modules 打包器,作用和 webpack 相似。不过比 webpack 要小巧一些,Vue、React 和 three.js 都在使用。
// ./src/messages.js
export default {
hi: 'Hey Guys, I am zce~',
};
// ./src/logger.js
export const log = (msg) => {
console.log('---------- INFO ----------');
console.log(msg);
console.log('--------------------------');
};
export const error = (msg) => {
console.error('---------- ERROR ----------');
console.error(msg);
console.error('---------------------------');
};
// ./src/index.js
import { log } from './logger';
import messages from './messages';
log(messages.hi);
rollup ./src/index.js –file ./dist/bundle.js
打包结果如下:
//bundle.js
const log = (msg) => {
console.log('---------- INFO ----------');
console.log(msg);
console.log('--------------------------');
};
var messages = {
hi: 'Hey Guys, I am zce~',
};
// 导入模块成员
// 使用模块成员
log(messages.hi);
可以看到代码很简洁,没有像 webpack 那样存在引导代码和模块。
error 方法没有被调用,输出结果中就没有 error 方法,这是 rollup 默认开启 Tree-shaking 优化了输出结果。
但是缺点在于
不适合开发应用使用。因为需要使用第三方模块,而且第三方模块大都使用 CommonJS 方式导出成员,并且 rollup 不支持 HMR,开发效率不如 webpack。
傻瓜式前端打包器,只需要简单的命令即可构建前端 app。
和 webpack 一样支持任意类型文件作为打包入口
但建议使用 HTML 文件为入口,该 HTML 文件像正常开发一样编写代码,引用资源。如下:
<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Parcel Tutorials</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
main.js 文件通过 ES Moudle 方法导入其他模块成员
// ./src/main.js
import { log } from './logger';
log('hello parcel');
// ./src/logger.js
export const log = (msg) => {
console.log('---------- INFO ----------');
console.log(msg);
};
使用打包命令:
parcel src/index.html
执行命令后,parcel 不仅会打包应用,同时也会开启一个开发服务器,和 webpack Dev Server 一样
支持 HMR,且用法简单
自动安装依赖。
webpack 开发阶段突然使用安装某个第三方依赖,必然会终止 dev server 然后安装再启动。而 Parcel 则免了这繁琐的工作流程。
Parcel 能够零配置加载其他类型的资源文件,无须像 webpack 那样配置对应的 loader
打包是多进程的,构建速度比 webpack 快。输出文件也会被默认压缩,css 会被单独提取到单个文件中。
较复杂的打包工具(如 Webpack 或 Parcel)的替代方案,利用 JavaScript 的本机模块系统,避免不必要的工作并保持流畅的开发体验。
开发阶段,每次保存单个文件时,Webpack 和 parcel 都需要重新构建和打包应用程序的整个 bundle。而 snowpack 为每个文件构建一次,就可以永久缓存,文件更改时,snowpack 会重新构建该单个文件。
下图为 webpack 与 snowpack 打包区别:

可以 snowpack 在重新构建每次变更时,几乎没有是将浪费,只需要在浏览器中进行 HMR 更新。
是一种新型前端构建工具,能够显著提升前端开发体验。
它主要由两部分组成:
其作用类似于 webpack+webpack dev server,其特点如下:
Vite 会直接启动开发服务器,不需要进行打包操作。所以它不需要分析依赖、不需要编译,因此启动速度非常快。
利用浏览器支持 ES Module 的特性,当浏览器请求到某个模块的时候,再根据需要对模块的内容进行编译,这种方式可以缩短编译时间。

HMR 时,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无需像 webpack 那样把该模块的相关依赖全部编译一次。
相比于上述的模块化工具,webpack 大而全,很多常用的功能开箱即用。
最大的特点是一切皆模块和按需加载
相比于其他构建工具,具有以下优势:
如果项目涉及到的页面越多,功能和业务代码不断增长,webpack 的构建时间也会加长,会影响开发效率。
所以有了以下常见的构建优化方法:
在使用 loader 时,可以通过配置 inclued、exclued、test 属性来匹配文件,通过 includ、exclude 规定哪些匹配应用 loader。
以 ES6 项目为例,在配置 babel-loader 时
module.exports = {
module: {
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
],
},
};
resolve 可以帮助 webpack 从每个 require/import 语句中,找到需要引入的合适的模块代码。
通过 resolve.extensions 是解析到文件时自动添加拓展名,默认情况如下:
module.exports = {
// ...
resolve: {
extensions: ['.warm', '.mjs', '.js', '.json'],
},
};
当我们引入文件时,若没有文件后缀名,则会根据数组内的值依次查找
当我们配置时,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,减慢打包速度。
resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认为[“node_modules”],所以默认会从 node_modules 中查找
当安装的第三方模块都放在项目根目录下的./node_modules 目录下时,可以指明第三方模块存放的绝对路径,减少寻找:
module.exports = {
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// 其中 __dirname 表示当前工作目录,也就是项目根目录
modules: [path.resolve(__dirname, 'node_modules')],
},
};
这在 webpack5 里已经是默认配置了吧…
alias 给一些常用的路径起了一个别名,特别时当项目目录层级深时时,某个文件的路径可能是./../../../……的形式
module.exports = {
// ...
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
};
DLL 动态链接库,是为软件在 windows 中实现共享函数库的一种实现方式,webpack 内置了 DLL 功能,可将共享的,不常改动的代码,抽离为一个共享的库。这个库在之后的编译中,会被引入到其他项目的代码中。
分两步
module.exports = {
...
plugins:[
new webpack.DllPlugin({
name:'dll_[name]',
path:path.resolve(__dirname,"./dll/[name].mainfest.json")
})
]
}
module.exports = {
...
// 对mainfest.json映射文件进行分析,获取要使用的DLL库
new webpack.DllReferencePlugin({
context:path.resolve(__dirname,"./dll/dll_react.js"),
mainfest:path.resolve(__dirname,"./dll/react.mainfest.json")
}),
// 将DLL引入HTML中
new AddAssetHtmlPlugin({
outputPath:"./auto",
filepath:path.resolve(__dirname,"./dll/dll_react.js")
})
}
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存到磁盘,显著提升二次构建速度
保存和读取缓存文件会有一些时间开销,所以只适用于性能开销较大的 loader
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢。

terser 是一个用于 JS 压缩和混淆的 plugin。可以使得打包后输出的 bundle 更小。
production 模式下,webpack 默认使用的就是 TerserPlugin 来处理 JS
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 电脑cpu核数-1
}),
],
},
};
extractComments
默认值 true,表示会将注释抽取到一个单独的文件中,开发阶段可以设置为 false,不保留注释。
parallel
使用多进程并发进行构建,提高构建速度,默认为 true,线程数为 os.cup().length - 1.
terserOptions 其他配置
CSS 压缩通常都是去除一些无用的空格
在 webpack 里可以用 css-minimizer-webpack-plugin 插件来压缩 CSS 代码
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin({
parallel: true,
}),
],
},
};
同样的,可以用 HtmlWebpackPlugin 来对 HTML 问价进行压缩
module.exports = {
// ...
plugin: [
new HtmlwebpackPlugin({
//...
minify: {
minifyCSS: false, // 是否压缩css
collapseWhitespace: false, // 是否折叠空格
removeComments: true, // 是否移除注释
},
}),
],
};
除了设置 minify,还会使用到另一个插件 html-minifier-terser
new ComepressionPlugin({
test: /\.(css|js)$/, // 哪些文件需要压缩
threshold: 500, // 设置文件多大开始压缩
minRatio: 0.7, // 至少压缩的比例
algorithm: 'gzip', // 采用的压缩算法
});
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
},
},
{
loader: 'image-webpack-loader',
options: {
// 压缩 jpeg 的配置
mozjpeg: {
progressive: true,
quality: 65,
},
// 使用 imagemin**-optipng 压缩 png,enable: false 为关闭
optipng: {
enabled: false,
},
// 使用 imagemin-pngquant 压缩 png
pngquant: {
quality: '65-90',
speed: 4,
},
// 压缩 gif 的配置
gifsicle: {
interlaced: false,
},
// 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
webp: {
quality: 75,
},
},
},
],
},
];
}
说白了就是删除掉冗余代码
依赖于 ES modules 的静态语法分析
有两种方案
usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的 sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
module.exports = {
//...
optimization: {
usedExports,
},
};
使用之后,没被用上的代码在 webpack 打包中会加入 unused harmony export mul 注释,用来告知 Terser 在优化时,可以删除掉这段代码
配置方法是在 package.json 中设置 sideEffects 属性
如果 sideEffects 设置为 false,就是告知 webpack 可以安全的删除未用到的 exports
如果有些文件需要保留,可以设置为数组的形式
"sideEffecis":["./src/util/format.js", "*.css"] // 所有的css文件]
还可以对 CSS 进行 tree shaking
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgeCssPlugin = require('purgecss-webpack-plugin');
module.exports = {
//...
modules: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]'.css,
}),
new PurgecssPlugin({
// src里面的所有文件
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
satelist: function () {
return { standard: ['html'] };
},
}),
],
};
到这里我发现我目前来总结和回答 webpack 问题是不合理的,因为没有基础…
总结一定是在系统学习的基础上来进行的,这篇文章难产就是因为 webpack 其实并不了解
mark 一下,目前是 22 年 3 月 22 日 21 点 44 分,我决定先去学习 webpack 基础和原理 完成基础学习后再回头来看这些面试问题 PS: 今天收到了一家公司的入职邀请,可能有点小激动,博客要继续写下去,总结要继续做下去
今天是 2022 年 3 月 28 号,经过几天对 webpack 的研究,我又觉得自己站起来了,来,我们继续!
把代码分离到不同的 bundle 中
使用 splitChunksPlugin
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
splitChunks 有几个属性:
可以通过 InlineChunkHtmlPlugin 插件将一些 chunk 的模块内联到 html
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
plugins:[
new InlineChunkHtmlPlugin(
(HtmlWebpackPlugin, [/runtime.+\.js/)
)
]
}
const webpack = require('webpack');
module.exports = {
// ...
devServer: {
// 开启 HMR 特性
hot: true,
// hotOnly: true
},
};

在编写未经过 webpack 打包的源代码后,webpack compiler 将源代码和 HMR Runtime 一起编译成 bundle 文件,传输给 bundle server 静态资源服务器
当一个文件或资源发生变化时,webpack 监听到文件变化,对文件重新编译并打包,编译生成唯一的 hash 值,这个 hash 值用于作为下次 HMR 的标识。
根据变化的内容生成两个补丁文件:manifest(包含了 hash 和 chundId ,用来说明变化的内容)和 chunk.js 模块
由于 socket 服务器在 HMR Runtime 和 HMR Server 之间建立 websocket 链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的 hash 值,作为下一次热更细的标识。
在浏览器接受到这条消息之前,浏览器已经在上一次 socket 消息中已经记住了此时的 hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件
mainfest 文件包含重新 build 生成的 hash 值,以及变化的模块。
浏览器根据 manifest 文件获取模块变化的内容,从而触发 render 流程,实现局部模块更新。
webpack proxy,即 webpack 提供的代理服务。
webpack 中提供服务器的工具为 webpack-dev-server。它将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起,目的是为了提高开发者日常的开发效率,只适用在开发阶段。
// ./webpack.config.js
const path = require('path');
module.exports = {
// ...
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
proxy: {
'/api': {
target: 'https://api.github.com',
},
},
// ...
},
};
工作原理实质上是利用 http-proxy-middleware 这个 http 代理中间件,实现请求转发给其他服务器.
在开发阶段,本地地址为 http://localhost:3000,该浏览器发送一个前缀带有/api 标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中.
const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
app.use(
'/api',
proxy({ target: 'http://www.example.org', changeOrigin: true })
);
app.listen(3000);
// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
webpack 中的 plugin 也是如此,plugin 赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了 webpack 整个编译周期。其目的在于解决 loader 无法实现的其他事。
本质是一个具有 apply 方法的 js 对象
apply 会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象。
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建过程开始!');
});
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin;
编译生命周期钩子函数:
常见 plugin:
loader 用于对模块的“源代码”进行转换,在 import 或“加载”模块时预处理文件
webpack 做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。
在 webpack 内部中,任何文件都是模块,不仅仅只是 js 文件。默认情况下,在遇到 import 或者 load 加载模块的时候,webpack 只支持对 js 文件打包,像 css、sass、png 等这些类型的文件的时候,webpack 则无能为力,这时候就需要配置对应的 loader 进行文件内容的解析
当 webpack 碰到不识别的模块的时候,webpack 会在配置的中查找该文件解析规则
关于配置 loader 的方式有三种:
关于 loader 的配置,我们是写在 module.rules 属性中,属性介绍如下:
rules 是一个数组的形式,因此我们可以配置很多个 loader
每一个 loader 对应一个对象的形式,对象属性 test 为匹配的规则,一般情况为正则表达式
属性 use 针对匹配到文件类型,调用对应的 loader 进行处理
loader 支持链式调用,链中的每个 loader 会处理之前已处理过的资源,最终变为 js 代码。
常见的 loader 如下:
大体分为三个步骤
从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
webpack 将 webpack.config.js 中的各个配置项拷贝到 options 对象中,并加载用户配置的 plugins。
完成上述步骤之后,则开始初始化 Compiler 编译对象,该对象掌控着 webpack 生命周期,不执行具体的任务,只是进行一些调度工作
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
beforeCompile: new AsyncSeriesHook(['params']),
compile: new SyncHook(['params']),
afterCompile: new AsyncSeriesHook(['compilation']),
make: new AsyncParallelHook(['compilation']),
entryOption: new SyncBailHook(['context', 'entry']),
// 定义了很多不同类型的钩子
};
// ...
}
}
function webpack(options) {
var compiler = new Compiler();
//...// 检查options,若watch字段为true,则开启watch线程
return compiler;
}
// ...
根据配置中的 entry 找出所有的入口文件
初始化完成后会调用 Compiler 的 run 来真正启动 webpack 编译构建流程,主要流程如下:
执行了 run 方法后,首先会触发 compile,主要是构建一个 Compilation 对象
该对象是编译阶段的主要执行者,主要会依次下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对象
当完成了上述的 compilation 对象后,就开始从 Entry 入口文件开始读取,主要执行_addModuleChain()函数,如下:
_addModuleChain(context, dependency, onModule, callback) {
// ...
// 根据依赖查找对应的工厂函数
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);
// 调用工厂函数NormalModuleFactory的create来生成一个空的NormalModule对象
moduleFactory.create({
dependencies: [dependency]
// ...
}, (err, module) => {
// ...
const afterBuild = () => {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
};
this.buildModule(module, false, null, null, err => {
// ...
afterBuild();
})
})
}
过程如下:
_addModuleChain 中接收参数 dependency 传入的入口依赖,使用对应的工厂函数 NormalModuleFactory.create 方法生成一个空的 module 对象
回调中会把此 module 存入 compilation.modules 对象和 dependencies.module 对象中,由于是入口文件,也会存入 compilation.entries 中。
随后执行 buildModule 进入真正的构建模块 module 内容的过程
2022 年 3 月 28 日,刚刚觉得自己站起来了又跪在了这里…可恶啊!我去手撕一遍 webpack 实现,等回来继续写
手撕了一个简单版本,上传了 github 2022 年 3 月 19 日
这里主要调用配置的 loaders,将我们的模块转成标准的 JS 模块
在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST),以方便 Webpack 后面对代码的分析
从配置的入口模块开始,分析其 AST,当遇到 require 等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系
seal 方法主要是要生成 chunks,对 chunks 进行一系列的优化操作,并生成要输出的代码
webpack 中的 chunk ,可以理解为配置在 entry 中的模块,或者是动态引入的模块
根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表
在确定好输出内容后,根据配置确定输出的路径和文件名
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
}
在 Compiler 开始生成文件前,钩子 emit 会被执行,这是我们修改最终文件的最后一个机会

在开发过程中会遇到以下问题
webpack 解决了浏览器对于 ES6 的兼容问题,开发中的各种资源(JS、Html、CSS、图片,字体,文本等)模块化问题,对各种资源的管理问题,极大的提升了开发效率,软件兼容性和可维护性。
loader 是文件加载器,负责加载资源文件,并对文件进行一些处理,如编译、压缩等,最终一起打包到指定文件中。
plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
loader 运行在打包文件之前 plugins 在整个编译周期都起作用
在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果
对于 loader,实质是一个转换器,将 A 文件进行编译形成 B 文件,操作的是文件,比如将 A.scss 或 A.less 转变为 B.css,单纯的文件转换过程
其本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader 设为一个箭头函数
函数接受一个参数,为 webpack 传递给 loader 的文件源内容
函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息
函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer
// 导出一个函数,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; // 同步
};
由于 webpack 基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务
webpack 编译会创建两个核心对象:
如果自己要实现 plugin,也需要遵循一定的规范:
class MyPlugin {
// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);
// do something...
});
}
}
在 emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容
babel polyfill 有三种:
babel-polyfill 通过向全局对象和内置对象的 prototype 上添加方法来实现的。所以这会造成全局空间污染。
babel-polyfill 使用的两种方式:
webpack.config.js 中: 配置 webpack.config.js 里的 entry 设置为
entry: ['babel-polyfill', path.join(__dirname, 'index.js')];
业务 js 中: 在 webpack.config.js 配置的主入口 index.js 文件的最顶层键入
import 'babel-polyfill';
简单说 babel-runtime 更像是一种按需加载的实现
import Promise from 'babel-runtime/core-js/promise';
babel-plugin-transform-runtime 装了就不需要装 babel-runtime 了,因为前者依赖后者。 总的来说,babel-plugin-transform-runtime 就是可以在我们使用新 API 时 自动 import babel-runtime 里面的 polyfill,具体插件做了以下三件事情:
babel-plugin-transform-runtime 优点:
使用方式:
在 .babelrc 中配置:
plugins: [“tranform-runtime”]
WebPack 是一个模块打包工具,可以使用 WebPack 管理模块依赖,并编译输岀模块所需的静态文件。它能够很好地管理与打包 Web 开发中所用到的 HTML、 JavaScript 、CSS 以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源, WebPack 有对应的模块加载器。Web Pack 模块打包器会分析模块间的依赖关系,最后生成优化且合并后的静态资源。
勉强过了 webpack 进阶,就是 webpack 实践和源码分析上差点意思了。
评论0