node --inspect-brk ./node_modules/webpack-cli/bin/cli.js
然后打开 Chrome 浏览器控制台就可以调试了
launch.json
配置文件.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "debug webpack",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/webpack-cli/bin/cli.js"
}
]
}
const webpack = require("webpack");
const webpackOptions = require("./webpack.config");
const compiler = webpack(webpackOptions);
//4.执行对象的run方法开始执行编译
compiler.run((err, stats) => {
console.log(err);
console.log(
stats.toJson({
assets: true,
chunks: true,
modules: true,
})
);
});
class SyncHook {
constructor() {
this.taps = [];
}
tap(name, fn) {
this.taps.push(fn);
}
call() {
this.taps.forEach((tap) => tap());
}
}
let hook = new SyncHook();
hook.tap("some name", () => {
console.log("some name");
});
class Plugin {
apply() {
hook.tap("Plugin", () => {
console.log("Plugin ");
});
}
}
new Plugin().apply();
hook.call();
entry
找出入口文件Loader
对模块进行编译在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
debugger.js
// 引入自定义的webpack模块
const webpack = require('./my-webpack');
// 引入Node.js的文件系统模块,用于操作文件
const fs = require('fs');
// 引入webpack配置文件
const config = require('./webpack.config');
// 使用配置文件创建一个webpack编译器实例
const compiler = webpack(config);
// 运行编译器,开始构建过程
compiler.run((err, stats) => {
// 打印构建错误信息(如果有的话)
console.log(err);
// 将构建统计数据转换为JSON字符串,包括模块、代码块和资源信息
let statsString = JSON.stringify(stats.toJson({
modules: true,
chunks: true,
assets: true
}));
// 将统计数据字符串写入文件myStats.json,用于分析构建过程
fs.writeFileSync('./myStats.json', statsString);
});
webpack.config.js
const path = require("path");
const RunPlugin = require("./plugins/run-plugin");
const DonePlugin = require("./plugins/done-plugin");
module.exports = {
mode: "development",
devtool: false,
entry: {
entry1: "./src/entry1.js",
entry2: "./src/entry2.js",
},
output: {
path: path.resolve("dist"),
filename: "[name].js",
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
},
module: {
rules: [
{
test: /\.js$/,
use: [
path.resolve(__dirname, "loaders/logger1-loader.js"),
path.resolve(__dirname, "loaders/logger2-loader.js"),
],
},
],
},
plugins: [
new RunPlugin(),
new DonePlugin(),
],
};
my-webpack.js
const Compiler = require('./Compiler');
function webpack(config) {
const argv = process.argv.slice(2);
const shellOptions = argv.reduce((shellOptions, options) => {
const [key, value] = options.split('=');
shellOptions[key.slice(2)] = value;
return shellOptions;
}, {});
const finalOptions = { ...config,
...shellOptions
};
const compiler = new Compiler(finalOptions);
finalOptions.plugins.forEach(plugin => {
plugin.apply(compiler);
});
return compiler;
}
module.exports = webpack;
Compiler.js
// 引入tapable库中的SyncHook类,用于创建同步钩子
const {SyncHook} = require('tapable');
// 引入path模块,用于处理文件和目录路径
const path = require('path');
// 引入自定义的Complication模块
const Complication = require('./Complication');
// 引入Node.js的文件系统模块,用于操作文件
const fs = require('fs');
// 定义Compiler类
class Compiler {
// 构造函数,接收一个options参数
constructor(options) {
// 保存options配置
this.options = options;
// 初始化钩子对象,包含run和done两个同步钩子
this.hooks = {
run: new SyncHook(),
done: new SyncHook()
};
}
// run方法,接收一个回调函数callback
run(callback) {
// 调用run钩子
this.hooks.run.call();
// 定义onCompiled回调函数,用于处理编译结果
const onCompiled = (err, stats, fileDependencies) => {
// 获取编译生成的资源
const {assets} = stats;
// 遍历资源,将资源写入输出目录
for (let filename in assets) {
let filePath = path.posix.join(this.options.output.path, filename);
fs.writeFileSync(filePath, assets[filename], 'utf-8');
}
// 调用外部传入的回调函数
callback(err, {toJson: () => stats});
// 监听文件依赖的变化,重新编译
[...fileDependencies].forEach(file => {
fs.watch(file, () => this.compile(onCompiled));
});
};
// 开始编译
this.compile(onCompiled);
// 调用done钩子
this.hooks.done.call();
}
// 编译方法
compile(onCompiled) {
// 创建Complication实例
const complication = new Complication(this.options);
// 调用Complication的build方法开始构建
complication.build(onCompiled);
}
}
// 导出Compiler类
module.exports = Compiler;
Complication.js
// 导入Node.js内置的path模块,用于处理文件路径
const path = require('path');
// 导入Node.js内置的fs模块,用于操作文件系统
const fs = require('fs');
// 导入babel-types库,用于处理AST节点
const types = require('babel-types');
// 导入@babel/parser库,用于将源代码解析成抽象语法树(AST)
const parser = require('@babel/parser');
// 导入@babel/traverse库,用于遍历和操作AST
const traverse = require('@babel/traverse').default;
// 导入@babel/generator库,用于将修改过的AST重新生成源代码
const generator = require('@babel/generator').default;
// 定义toUnixSeq函数,将Windows风格的文件路径转换为Unix风格
function toUnixSeq(filePath) {
// 使用正则表达式替换所有的反斜杠(\)为正斜杠(/)
return filePath.replace(/\\/g, '/');
}
// 定义Complication类,用于处理编译过程
class Complication {
// 构造函数,接收一个options参数
constructor(options) {
// 将传入的options对象赋值给实例的options属性
this.options = options;
// 设置实例的context属性为传入的options.context值,如果未传入,则使用当前工作目录的Unix风格路径
this.options.context = this.options.context || toUnixSeq(process.cwd());
// 初始化一个Set,用于存储文件依赖,避免重复添加
this.fileDependencies = new Set();
// 初始化一个数组,用于存储模块信息
this.modules = [];
// 初始化一个数组,用于存储代码块信息
this.chunks = [];
// 初始化一个空对象,用于存储输出文件的资源信息
this.assets = {};
}
build(onCompiled) {
// 定义一个空对象,用于存储入口信息
let entry = {};
// 判断options.entry的类型
if (typeof this.options.entry === 'string') {
// 如果是字符串类型,将其作为默认入口文件,将main作为键名
entry.main = this.options.entry;
} else {
// 否则直接使用options.entry作为入口信息
entry = this.options.entry;
}
// 遍历entry对象,处理每个入口文件
for (let entryName in entry) {
// 获取入口文件的完整路径
let entryFilePath = path.posix.join(this.options.context, entry[entryName]);
// 添加入口文件路径到文件依赖集合
this.fileDependencies.add(entryFilePath);
// 调用buildModule方法构建入口模块
let entryModule = this.buildModule(entryName, entryFilePath);
// 创建一个chunk对象,包含名称、入口模块和与该入口关联的模块
let chunk = {
name: entryName,
entryModule,
modules: this.modules.filter(module => module.names.includes(entryName))
};
// 将chunk对象添加到chunks数组
this.chunks.push(chunk);
}
// 遍历chunks数组,为每个chunk生成输出文件
this.chunks.forEach(chunk => {
// 替换输出文件名模板中的[name]为chunk名称
let outputFilename = this.options.output.filename.replace('[name]', chunk.name);
// 调用getSourceCode方法获取chunk的源码,将其添加到assets对象
this.assets[outputFilename] = getSourceCode(chunk);
});
// 调用onCompiled回调函数,传入构建结果
onCompiled(null, {
modules: this.modules,
chunks: this.chunks,
assets: this.assets
}, this.fileDependencies);
}
buildModule(entryName, modulePath) {
// 读取模块文件的原始源代码
let rawSourceCode = fs.readFileSync(modulePath, 'utf8');
// 从options中获取模块规则
let { rules } = this.options.module;
// 定义一个空数组,用于存储模块加载器
let loaders = [];
// 遍历规则,根据匹配的规则将加载器添加到loaders数组中
rules.forEach(rule => {
if (modulePath.match(rule.test)) {
loaders.push(...rule.use);
}
});
// 使用reduceRight逐个应用加载器,将原始源代码转换为处理后的源代码
let transformedSourceCode = loaders.reduceRight((sourceCode, loaderPath) => {
const loaderFn = require(loaderPath);
return loaderFn(sourceCode);
}, rawSourceCode);
// 生成模块ID(相对路径)
let moduleId = './' + path.posix.relative(this.options.context, modulePath);
// 创建模块对象
let module = {
id: moduleId,
names: [entryName],
dependencies: []
};
// 将模块对象添加到模块数组中
this.modules.push(module);
// 使用@babel/parser解析处理后的源代码,生成AST(抽象语法树)
let ast = parser.parse(transformedSourceCode, {
sourceType: "module"
});
// 使用@babel/traverse遍历AST,处理require调用
traverse(ast, {
CallExpression: ({ node }) => {
if (node.callee.name === 'require') {
let depModuleName = node.arguments[0].value;
let dirName = path.posix.dirname(modulePath);
let depModulePath = path.posix.join(dirName, depModuleName);
let { extensions } = this.options.resolve;
depModulePath = tryExtensions(depModulePath, extensions);
this.fileDependencies.add(depModulePath);
let depModuleId = "./" + path.posix.relative(this.options.context, depModulePath);
node.arguments[0] = types.stringLiteral(depModuleId);
module.dependencies.push({
depModuleId,
depModulePath
});
}
}
});
// 使用@babel/generator将AST转换回源代码
const { code } = generator(ast);
// 将生成的源代码添加到模块对象中
module._source = code;
// 处理模块依赖
module.dependencies.forEach(({ depModuleId, depModulePath }) => {
let existModule = this.modules.find(item => item.id === depModuleId);
if (existModule) {
existModule.names.push(entryName);
} else {
this.buildModule(entryName, depModulePath);
}
});
return module;
}
}
// 定义tryExtensions函数,用于尝试不同的文件扩展名,找到对应的模块文件
function tryExtensions(modulePath, extensions) {
// 如果原始路径的文件存在,则直接返回该路径
if (fs.existsSync(modulePath)) {
return modulePath;
}
// 遍历所有提供的扩展名
for (let i = 0; i < extensions.length; i++) {
// 生成一个新的文件路径,将扩展名添加到原始路径后
let filePath = modulePath + extensions[i];
// 如果新路径的文件存在,则返回该路径
if (fs.existsSync(filePath)) {
return filePath;
}
}
// 如果尝试所有扩展名后都没有找到对应的文件,则抛出错误
throw new Error(`模块${modulePath}未找到`);
}
function getSourceCode(chunk) {
return `
(() => {
var modules = {
${chunk.modules.filter(module => module.id !== chunk.entryModule.id).map(module => `
"${module.id}": module => {
${module._source}
}
`)}
};
var cache = {};
function require(moduleId) {
var cachedModule = cache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = cache[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
var exports = {};
(() => {
${chunk.entryModule._source}
})();
})();
`;
}
module.exports = Complication;
plugins\RunPlugin.js
class RunPlugin{
apply(compiler){
compiler.hooks.run.tap('RunPlugin',()=>{
console.log('run 开始编译');
});
}
}
module.exports = RunPlugin;
plugins\DonePlugin.js
class DonePlugin{
apply(compiler){
compiler.hooks.done.tap('DonePlugin',()=>{
console.log('done 结束编译');
});
}
}
module.exports = DonePlugin;
loaders\logger1-loader.js
function loader(source){
console.log('logger1-loader');
return source+'//logger1';
}
module.exports = loader;
loaders\logger2-loader.js
function loader(source){
console.log('logger2-loader');
return source+'//logger2';
}
module.exports = loader;
src\entry1.js
const title = require('./title');
console.log('entry1 ' ,title);
src\entry2.js
const title = require('./title');
console.log('entry2',title);
src\title.js
const name = require('./name');
module.exports = 'title'+name;
src\name.js
module.exports = 'name';
Compilation.getStats()
,返回的是主要含有modules
、chunks
和assets
三个属性值的对象。字段 | 含义 |
---|---|
modules | 记录了所有解析后的模块 |
chunks | 记录了所有 chunk |
assets | 记录了所有要生成的文件 |
npx webpack --profile --json > stats.json
{
"hash": "780231fa9b9ce4460c8a", //编译使用的 hash
"version": "5.8.0", // 用来编译的 webpack 的版本
"time": 83, // 编译耗时 (ms)
"builtAt": 1606538839612, //编译的时间
"publicPath": "auto", //资源访问路径
"outputPath": "C:\\webpack5\\dist", //输出目录
"assetsByChunkName": {
//代码块和文件名的映射
"main": ["main.js"]
},
"assets": [
//资源数组
{
"type": "asset", //资源类型
"name": "main.js", //文件名称
"size": 2418, //文件大小
"chunkNames": [
//对应的代码块名称
"main"
],
"chunkIdHints": [],
"auxiliaryChunkNames": [],
"auxiliaryChunkIdHints": [],
"emitted": false,
"comparedForEmit": true,
"cached": false,
"info": {
"javascriptModule": false,
"size": 2418
},
"related": {},
"chunks": ["main"],
"auxiliaryChunks": [],
"isOverSizeLimit": false
}
],
"chunks": [
//代码块数组
{
"rendered": true,
"initial": true,
"entry": true,
"recorded": false,
"size": 80,
"sizes": {
"javascript": 80
},
"names": ["main"],
"idHints": [],
"runtime": ["main"],
"files": ["main.js"],
"auxiliaryFiles": [],
"hash": "d25ad7a8144077f69783",
"childrenByOrder": {},
"id": "main",
"siblings": [],
"parents": [],
"children": [],
"modules": [
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"nameForCondition": "C:\\webpack5\\src\\index.js",
"index": 0,
"preOrderIndex": 0,
"index2": 1,
"postOrderIndex": 1,
"size": 55,
"sizes": {
"javascript": 55
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"dependent": false,
"issuer": null,
"issuerName": null,
"issuerPath": null,
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js",
"issuerId": null,
"chunks": ["main"],
"assets": [],
"reasons": [
{
"moduleIdentifier": null,
"module": null,
"moduleName": null,
"resolvedModuleIdentifier": null,
"resolvedModule": null,
"type": "entry",
"active": true,
"explanation": "",
"userRequest": "./src/index.js",
"loc": "main",
"moduleId": null,
"resolvedModuleId": null
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [],
"depth": 0
},
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\title.js",
"name": "./src/title.js",
"nameForCondition": "C:\\webpack5\\src\\title.js",
"index": 1,
"preOrderIndex": 1,
"index2": 0,
"postOrderIndex": 0,
"size": 25,
"sizes": {
"javascript": 25
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"dependent": true,
"issuer": "C:\\webpack5\\src\\index.js",
"issuerName": "./src/index.js",
"issuerPath": [
{
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js"
}
],
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 0,
"resolving": 0,
"restoring": 0,
"building": 0,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 0,
"dependencies": 0
},
"id": "./src/title.js",
"issuerId": "./src/index.js",
"chunks": ["main"],
"assets": [],
"reasons": [
{
"moduleIdentifier": "C:\\webpack5\\src\\index.js",
"module": "./src/index.js",
"moduleName": "./src/index.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
"resolvedModule": "./src/index.js",
"type": "cjs require",
"active": true,
"explanation": "",
"userRequest": "./title.js",
"loc": "1:12-33",
"moduleId": "./src/index.js",
"resolvedModuleId": "./src/index.js"
},
{
"moduleIdentifier": "C:\\webpack5\\src\\title.js",
"module": "./src/title.js",
"moduleName": "./src/title.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
"resolvedModule": "./src/title.js",
"type": "cjs self exports reference",
"active": true,
"explanation": "",
"userRequest": null,
"loc": "1:0-14",
"moduleId": "./src/title.js",
"resolvedModuleId": "./src/title.js"
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [
"CommonJS bailout: module.exports is used directly at 1:0-14"
],
"depth": 1
}
],
"origins": [
{
"module": "",
"moduleIdentifier": "",
"moduleName": "",
"loc": "main",
"request": "./src/index.js"
}
]
}
],
"modules": [
//模块数组
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"nameForCondition": "C:\\webpack5\\src\\index.js",
"index": 0,
"preOrderIndex": 0,
"index2": 1,
"postOrderIndex": 1,
"size": 55,
"sizes": {
"javascript": 55
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"issuer": null,
"issuerName": null,
"issuerPath": null,
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js",
"issuerId": null,
"chunks": ["main"],
"assets": [],
"reasons": [
{
"moduleIdentifier": null,
"module": null,
"moduleName": null,
"resolvedModuleIdentifier": null,
"resolvedModule": null,
"type": "entry",
"active": true,
"explanation": "",
"userRequest": "./src/index.js",
"loc": "main",
"moduleId": null,
"resolvedModuleId": null
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [],
"depth": 0
},
{
"type": "module",
"moduleType": "javascript/auto",
"identifier": "C:\\webpack5\\src\\title.js",
"name": "./src/title.js",
"nameForCondition": "C:\\webpack5\\src\\title.js",
"index": 1,
"preOrderIndex": 1,
"index2": 0,
"postOrderIndex": 0,
"size": 25,
"sizes": {
"javascript": 25
},
"cacheable": true,
"built": true,
"codeGenerated": true,
"cached": false,
"optional": false,
"orphan": false,
"issuer": "C:\\webpack5\\src\\index.js",
"issuerName": "./src/index.js",
"issuerPath": [
{
"identifier": "C:\\webpack5\\src\\index.js",
"name": "./src/index.js",
"profile": {
"total": 38,
"resolving": 26,
"restoring": 0,
"building": 12,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 26,
"dependencies": 0
},
"id": "./src/index.js"
}
],
"failed": false,
"errors": 0,
"warnings": 0,
"profile": {
"total": 0,
"resolving": 0,
"restoring": 0,
"building": 0,
"integration": 0,
"storing": 0,
"additionalResolving": 0,
"additionalIntegration": 0,
"factory": 0,
"dependencies": 0
},
"id": "./src/title.js",
"issuerId": "./src/index.js",
"chunks": ["main"],
"assets": [],
"reasons": [
{
"moduleIdentifier": "C:\\webpack5\\src\\index.js",
"module": "./src/index.js",
"moduleName": "./src/index.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\index.js",
"resolvedModule": "./src/index.js",
"type": "cjs require",
"active": true,
"explanation": "",
"userRequest": "./title.js",
"loc": "1:12-33",
"moduleId": "./src/index.js",
"resolvedModuleId": "./src/index.js"
},
{
"moduleIdentifier": "C:\\webpack5\\src\\title.js",
"module": "./src/title.js",
"moduleName": "./src/title.js",
"resolvedModuleIdentifier": "C:\\webpack5\\src\\title.js",
"resolvedModule": "./src/title.js",
"type": "cjs self exports reference",
"active": true,
"explanation": "",
"userRequest": null,
"loc": "1:0-14",
"moduleId": "./src/title.js",
"resolvedModuleId": "./src/title.js"
}
],
"usedExports": null,
"providedExports": null,
"optimizationBailout": [
"CommonJS bailout: module.exports is used directly at 1:0-14"
],
"depth": 1
}
],
"entrypoints": {
//入口点
"main": {
"name": "main",
"chunks": ["main"],
"assets": [
{
"name": "main.js",
"size": 2418
}
],
"filteredAssets": 0,
"assetsSize": 2418,
"auxiliaryAssets": [],
"filteredAuxiliaryAssets": 0,
"auxiliaryAssetsSize": 0,
"children": {},
"childAssets": {},
"isOverSizeLimit": false
}
},
"namedChunkGroups": {
//命名代码块组
"main": {
"name": "main",
"chunks": ["main"],
"assets": [
{
"name": "main.js",
"size": 2418
}
],
"filteredAssets": 0,
"assetsSize": 2418,
"auxiliaryAssets": [],
"filteredAuxiliaryAssets": 0,
"auxiliaryAssetsSize": 0,
"children": {},
"childAssets": {},
"isOverSizeLimit": false
}
},
"errors": [],
"errorsCount": 0,
"warnings": [],
"warningsCount": 0,
"children": []
}