大致流程
- 读取配置的入口文件
- 通过@babel/parser转ast
- 通过@babel/traverse的traverse方法处理ast,将type为 ImportDeclaration 解析出来路径,生成deps【收集相互依赖的文件】
- @babel/core的transformFromAst方法处理ast,生成code
- 递归处理deps
- 通过套用模板,生成出可以执行的文件,内部用eval执行js代码
webpack编译原理
设置初始化环境
创建如下目录
.
├── package.json
├── src
│ ├── count.js
│ ├── index.js
│ └── utils.js
└── webpack.config.js
{
"name": "wpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"babel-loader": "^9.1.2"
}
}
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
//使用 babel-loader 处理es6语法
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 预设babel做怎样的兼容性处理
presets: ["@babel/preset-env"],
},
},
],
},
};
import { a, b } from "./count.js";
import { sum } from "./utils.js";
const res = sum(a, b);
console.log(res);
export const sum = (a, b) => {
return a + b;
};
export let a = 1;
export let b = 2;
export let c = 99;
生成出来打包结果文件dist/bundle.js;
(() => {
"use strict";
var __webpack_modules__ = {
"./src/count.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"a\": () => (/* binding */ a),\n/* harmony export */ \"b\": () => (/* binding */ b),\n/* harmony export */ \"c\": () => (/* binding */ c)\n/* harmony export */ });\nvar a = 1;\nvar b = 2;\nvar c = 99;\n\n//# sourceURL=webpack://wpack/./src/count.js?");
},
"./src/index.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _count__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./count */ \"./src/count.js\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \"./src/utils.js\");\n\n\nconsole.log(_count__WEBPACK_IMPORTED_MODULE_0__.a, _count__WEBPACK_IMPORTED_MODULE_0__.b, \"count\");\nvar res = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.sum)(_count__WEBPACK_IMPORTED_MODULE_0__.a, _count__WEBPACK_IMPORTED_MODULE_0__.b);\nconsole.log(res);\n\n//# sourceURL=webpack://wpack/./src/index.js?");
},
"./src/utils.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"sum\": () => (/* binding */ sum)\n/* harmony export */ });\nvar sum = function sum(a, b) {\n console.log(a, b, \"sum\");\n return a + b;\n};\n\n//# sourceURL=webpack://wpack/./src/utils.js?");
}
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.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_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
__webpack_require__.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = __webpack_require__("./src/index.js");
})();
该文件为可以执行的文件. 接下来开始分析webpack的执行原理。
1从入口文件开始,解析为ast
创建自己的打包文件wpack.js。
// 引入编译文件 Compiler
const Compiler = require('./package/Compiler')
// 引入配置文件 webpack.config.js
const options = require('./webpack.config.js')
const compiler = new Compiler(options)
// 执行 compiler 实例的run方法
compiler.run()
在package目录下创建 Compiler.js文件
class Compiler {
// 构造函数
constructor(options) {
this.options = options;
this.entry = options.entry;
}
run() {
console.log(this.entry);
}
}
module.exports = Compiler;
在控制台执行命令node wpack.js
, 可以看到输出 ./src/index.js
入口文件的路径地址。
完成了上面第一步,接下来开始使用parser.parse对从入口文件引入的的资源进行解析。
新增Compilation.js
文件
// 该文件是每次编译生成的资源文件实例
class Compilation {
// 构造函数传入 compiler实例
constructor(compiler) {
const { options, modules } = compiler;
this.options = options;
this.entryId = "";
// 当前命令执行的根路径
this.root = process.cwd();
this.modules = modules;
}
// 设置要编译文件的路径,以及该文件是否为入口文件
buildModule(absolutePath, isEntry){
console.log(absolutePath, this.root, "absolutePath");
let ast = Parser.ast(absolutePath);
}
}
module.exports = Compilation;
在Compiler.js中引入
// 引入Compilation文件
const Compilation = require("./Compilation");
// Compiler是配置文件实例的结果,只有一个
class Compiler {
// 构造函数
constructor(options) {
this.options = options;
this.entry = options.entry;
// 设置 modules,用来设置loader,
this.modules = [];
}
run() {
console.log(this.entry);
// +++新增内容+++
this.compile();
}
// +++新增内容+++
compile() {
const compilation = new Compilation(this);
compilation.buildModule(this.options.entry, true)
}
}
module.exports = Compiler;
新增Parser.js文件
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
// Parser解析文件
class Parser {
// 创建静态方法 ast,将指定的路径文件解析为ast对象
static ast(path) {
// 1.读取文件
const content = fs.readFileSync(path, "utf-8");
console.log("读取文件", content);
// 2.解析读取出来的文件内容,使用module类型及膝
const _ast = parser.parse(content, {
sourceType: "module",
});
console.log(_ast);
console.log("我是ast的body内容", _ast.program.body);
return _ast;
}
}
module.exports = Parser;
使用@babel/parser
进行解析,安装依赖文件yarn add -D @babel/parser
执行命令node wpack.js
读取文件 import { a, b } from "./count";
import { sum } from "./utils";
const res = sum(a, b);
console.log(res);
Node {
type: 'File',
start: 0,
end: 104,
loc: SourceLocation {
start: Position { line: 1, column: 0, index: 0 },
end: Position { line: 5, column: 0, index: 104 },
filename: undefined,
identifierName: undefined
},
errors: [],
program: Node {
type: 'Program',
start: 0,
end: 104,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
sourceType: 'module',
interpreter: null,
body: [ [Node], [Node], [Node], [Node] ],
directives: []
},
comments: []
}
我是ast的body内容 [
Node {
type: 'ImportDeclaration',
start: 0,
end: 31,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
specifiers: [ [Node], [Node] ],
source: Node {
type: 'StringLiteral',
start: 21,
end: 30,
loc: [SourceLocation],
extra: [Object],
value: './count'
}
},
Node {
type: 'ImportDeclaration',
start: 32,
end: 62,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
specifiers: [ [Node] ],
source: Node {
type: 'StringLiteral',
start: 52,
end: 61,
loc: [SourceLocation],
extra: [Object],
value: './utils'
}
},
Node {
type: 'VariableDeclaration',
start: 63,
end: 85,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
declarations: [ [Node] ],
kind: 'const'
},
Node {
type: 'ExpressionStatement',
start: 86,
end: 103,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
expression: Node {
type: 'CallExpression',
start: 86,
end: 102,
loc: [SourceLocation],
callee: [Node],
arguments: [Array]
}
}
]
2用traverse处理ast对象,查找出所有依赖的文件
给Parser类添加getDependecy方法
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
// Parser解析文件
class Parser {
// 创建静态方法 ast,将指定的路径文件解析为ast对象
static ast(path) {
// 1.读取文件
const content = fs.readFileSync(path, "utf-8");
console.log("读取文件", content);
// 2.解析读取出来的文件内容,使用module类型及膝
const _ast = parser.parse(content, {
sourceType: "module",
});
//console.log(_ast);
//console.log("我是ast的body内容", _ast.program.body);
return _ast;
}
// +++新增内容+++
// 收集依赖,通过入口文件src/index.js,把所有的依赖文件进行解析
static getDependecy(ast, file) {
// 存放项目所有依赖的资源文件
const dependencies = {};
traverse(ast, {
// 处理ast对象中为ImportDeclaration的值
ImportDeclaration: ({ node }) => {
const oldValue = node.source.value;
const dirname = path.dirname(file);
const relativepath = "./" + path.join(dirname, oldValue);
console.log("转换路径", oldValue, relativepath);
dependencies[oldValue] = relativepath;
node.source.value = relativepath; // 将 ./data.js 转化成 ./src/data.js
},
});
console.log(dependencies, "dependencies");
return dependencies;
}
}
module.exports = Parser;
添加依赖@babel/traverse
,yarn add -D @babel/traverse
修改 buildModule 方法
const fs = require("fs");
const path = require("path");
const Parser = require("./Parser");
// 该文件是每次编译生成的资源文件实例
class Compilation {
// 构造函数传入 compiler实例
constructor(compiler) {
const { options, modules } = compiler;
this.options = options;
this.entryId = "";
// 当前命令执行的根路径
this.root = process.cwd();
this.modules = modules;
}
buildModule(absolutePath, isEntry) {
console.log(absolutePath, this.root, "absolutePath");
let ast = Parser.ast(absolutePath);
// +++新增内容+++
// 将绝对路径转为相对路径
const relativePath = "./" + path.relative(this.root, absolutePath);
console.log(
"路径地址++++++",
absolutePath,
path.relative(this.root, absolutePath)
);
if (isEntry) {
this.entryId = relativePath;
}
const dependencies = Parser.getDependecy(ast, relativePath);
console.log("从入口开始,所有的依赖资源文件:", dependencies);
}
}
module.exports = Compilation;
执行node wpack.js命令后:
路径地址++++++ ./src/index.js src/index.js
转换路径 ./count ./src/count
转换路径 ./utils ./src/utils
{ './count': './src/count', './utils': './src/utils' } dependencies
从入口开始,所有的依赖资源文件: { './count': './src/count', './utils': './src/utils' }
成功解析出来入口文件依赖的2个文件,count.js和utils.js。
3处理过ast后,将ast生成制定版本的code
经过上面的ast对象,查找处理了所有依赖dependencies文件,接下来开始将ast对象转为指定版本的code代码。
Parser新增 transform 方法
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
// Parser解析文件
class Parser {
// 创建静态方法 ast,将指定的路径文件解析为ast对象
static ast(path) {
// 1.读取文件
const content = fs.readFileSync(path, "utf-8");
console.log("读取文件", content);
// 2.解析读取出来的文件内容,使用module类型及膝
const _ast = parser.parse(content, {
sourceType: "module",
});
// console.log(_ast);
// console.log("我是ast的body内容", _ast.program.body);
return _ast;
}
// +++新增内容+++
// 收集依赖,通过入口文件src/index.js,把所有的依赖文件进行解析
static getDependecy(ast, file) {
// 存放项目所有依赖的资源文件
const dependencies = {};
traverse(ast, {
// 处理ast对象中为ImportDeclaration的值
ImportDeclaration: ({ node }) => {
const oldValue = node.source.value;
const dirname = path.dirname(file);
const relativepath = "./" + path.join(dirname, oldValue);
console.log("转换路径", oldValue, relativepath);
dependencies[oldValue] = relativepath;
node.source.value = relativepath; // 将 ./data.js 转化成 ./src/data.js
},
});
console.log(dependencies, "dependencies");
return dependencies;
}
// +++新增内容+++
// 把ast数据转为制定版本的可以执行代码
static transform(ast) {
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"],
});
return code;
}
}
module.exports = Parser;
在Compilation的buildModule中引入新方法
新增第34和35行
const fs = require("fs");
const path = require("path");
const Parser = require("./Parser");
// 该文件是每次编译生成的资源文件实例
class Compilation {
// 构造函数传入 compiler实例
constructor(compiler) {
const { options, modules } = compiler;
this.options = options;
this.entryId = "";
// 当前命令执行的根路径
this.root = process.cwd();
this.modules = modules;
}
buildModule(absolutePath, isEntry) {
console.log(absolutePath, this.root, "absolutePath");
let ast = Parser.ast(absolutePath);
// +++新增内容+++
// 将绝对路径转为相对路径
const relativePath = "./" + path.relative(this.root, absolutePath);
console.log(
"路径地址++++++",
absolutePath,
path.relative(this.root, absolutePath)
);
if (isEntry) {
this.entryId = relativePath;
}
const dependencies = Parser.getDependecy(ast, relativePath);
//console.log("从入口开始,所有的依赖资源文件:", dependencies);
// +++新增内容+++
// 将ast转为低版本代码
const transformCode = Parser.transform(ast);
console.log("转换后的代码 ", transformCode);
// 将资源进行导出
return {
dependencies,
relativePath,
transformCode,
};
}
}
module.exports = Compilation;
安装依赖 yarn add -D @babel/core
执行命令 node wpack.js
, 打印出 转换后的代码
转换后的代码
"use strict";
var _count = require("./src/count");
var _utils = require("./src/utils");
var res = (0, _utils.sum)(_count.a, _count.b);
console.log(res);
4递归处理deps
修改Compiler
的 compile
方法
// 引入Compilation文件
const Compilation = require("./Compilation");
// Compiler是配置文件实例的结果,只有一个
class Compiler {
// 构造函数
constructor(options) {
this.options = options;
this.entry = options.entry;
// 设置 modules,用来设置loader,
this.modules = [];
}
run() {
console.log(this.entry);
this.compile();
}
// +++新增内容+++
compile() {
const compilation = new Compilation(this);
const entryModule = compilation.buildModule(this.options.entry, true);
/* +++新增内容+++ */
// 将解析处理的module资源添加到 modules中
this.modules.push(entryModule);
// 循环遍历多个modules
this.modules.forEach((_module) => {
const deps = _module.dependencies;
console.log(deps);
for (const dep in deps) {
if (deps.hasOwnProperty(dep)) {
// 递归遍历 modules,因为文件有相互之间的引入
this.modules.push(compilation.buildModule(deps[dep]), false);
}
}
});
}
}
module.exports = Compiler;
第22行后为新增加内容。
执行命令node wpack.js
可以看到3个被编译转换的代码资源,第4行、第13行、第27行,分别对应index.js、count.js、utils.js
./src/index.js
./src/index.js /Users/shuai/Desktop/code/nodeapp/selfpack/wpack absolutePath
路径地址++++++ ./src/index.js src/index.js
转换后的代码 "use strict";
var _count = require("./src/count.js");
var _utils = require("./src/utils.js");
var res = (0, _utils.sum)(_count.a, _count.b);
console.log(res);
{ './count.js': './src/count.js', './utils.js': './src/utils.js' }
./src/count.js /Users/shuai/Desktop/code/nodeapp/selfpack/wpack absolutePath
路径地址++++++ ./src/count.js src/count.js
转换后的代码 "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.c = exports.b = exports.a = void 0;
var a = 1;
exports.a = a;
var b = 2;
exports.b = b;
var c = 99;
exports.c = c;
./src/utils.js /Users/shuai/Desktop/code/nodeapp/selfpack/wpack absolutePath
路径地址++++++ ./src/utils.js src/utils.js
转换后的代码 "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sum = void 0;
var sum = function sum(a, b) {
return a + b;
};
exports.sum = sum;
5生成可执行文件
最后一步将上面3个转换后的代码,生成到一个可以执行的文件,进行代码合并拼接。
给Compilation
类新增加emitFiles
方法
const fs = require("fs");
const path = require("path");
const Parser = require("./Parser");
// 该文件是每次编译生成的资源文件实例
class Compilation {
// 构造函数传入 compiler实例
constructor(compiler) {
const { options, modules } = compiler;
this.options = options;
this.entryId = "";
// 当前命令执行的根路径
this.root = process.cwd();
this.modules = modules;
}
buildModule(absolutePath, isEntry) {
console.log(absolutePath, this.root, "absolutePath");
let ast = Parser.ast(absolutePath);
// +++新增内容+++
// 将绝对路径转为相对路径
const relativePath = "./" + path.relative(this.root, absolutePath);
console.log(
"路径地址++++++",
relativePath,
path.relative(this.root, absolutePath)
);
if (isEntry) {
this.entryId = relativePath;
}
const dependencies = Parser.getDependecy(ast, relativePath);
//console.log("从入口开始,所有的依赖资源文件:", dependencies);
// +++新增内容+++
// 将ast转为低版本代码
const transformCode = Parser.transform(ast);
console.log("转换后的代码 ", transformCode);
// 将资源进行导出
return {
dependencies,
relativePath,
transformCode,
};
}
/* +++新增内容+++ */
// 生成最后的打包资源
emitFiles() {
let _modules = "";
const outputPath = path.join(
this.options.output.path,
this.options.output.filename
);
this.modules.map((_module) => {
if (!_module.relativePath) return;
// 记得加引号
_modules += `'${_module.relativePath}': function(module, exports, require){
${_module.transformCode}
},`;
});
console.log(_modules, "路径和code拼接的_modules字符串");
}
}
module.exports = Compilation;
在Compiler类的compile方法中调用
新增第37行
// 引入Compilation文件
const Compilation = require("./Compilation");
// Compiler是配置文件实例的结果,只有一个
class Compiler {
// 构造函数
constructor(options) {
this.options = options;
this.entry = options.entry;
// 设置 modules,用来设置loader,
this.modules = [];
}
run() {
console.log(this.entry);
this.compile();
}
// +++新增内容+++
compile() {
const compilation = new Compilation(this);
const entryModule = compilation.buildModule(this.options.entry, true);
/* +++新增内容+++ */
// 将解析处理的module资源添加到 modules中
this.modules.push(entryModule);
// 循环遍历多个modules
this.modules.forEach((_module) => {
const deps = _module.dependencies;
console.log(deps);
for (const dep in deps) {
if (deps.hasOwnProperty(dep)) {
// 递归遍历 modules,因为文件有相互之间的引入
this.modules.push(compilation.buildModule(deps[dep]), false);
}
}
});
/* +++新增内容+++ */
compilation.emitFiles();
}
}
module.exports = Compiler;
执行node wpack.js查看到输出结果
//路径和code拼接的_modules字符串
'./src/index.js': function(module, exports, require){
"use strict";
var _count = require("./src/count.js");
var _utils = require("./src/utils.js");
var res = (0, _utils.sum)(_count.a, _count.b);
console.log(res);
},
'./src/count.js': function(module, exports, require){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.c = exports.b = exports.a = void 0;
var a = 1;
exports.a = a;
var b = 2;
exports.b = b;
var c = 99;
exports.c = c;
},
'./src/utils.js': function(module, exports, require){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sum = void 0;
var sum = function sum(a, b) {
return a + b;
};
exports.sum = sum;
},
在Compilation
类中设置webpack的处理资源模板
const fs = require("fs");
const path = require("path");
const Parser = require("./Parser");
const rmdir = require("rmdir");
// 该文件是每次编译生成的资源文件实例
class Compilation {
// 构造函数传入 compiler实例
constructor(compiler) {
const { options, modules } = compiler;
this.options = options;
this.entryId = "";
// 当前命令执行的根路径
this.root = process.cwd();
this.modules = modules;
}
buildModule(absolutePath, isEntry) {
console.log(absolutePath, this.root, "absolutePath");
let ast = Parser.ast(absolutePath);
// +++新增内容+++
// 将绝对路径转为相对路径
const relativePath = "./" + path.relative(this.root, absolutePath);
console.log("路径地址++++++",
relativePath,
path.relative(this.root, absolutePath)
);
if (isEntry) {
this.entryId = relativePath;
}
const dependencies = Parser.getDependecy(ast, relativePath);
//console.log("从入口开始,所有的依赖资源文件:", dependencies);
// +++新增内容+++
// 将ast转为低版本代码
const transformCode = Parser.transform(ast);
console.log("转换后的代码 ", transformCode);
// 将资源进行导出
return {
dependencies,
relativePath,
transformCode,
};
}
/* +++新增内容+++ */
// 生成最后的打包资源
emitFiles() {
let _modules = "";
const outputPath = path.join(
this.options.output.path,
this.options.output.filename
);
this.modules.map((_module) => {
if (!_module.relativePath) return;
// 记得加引号
_modules += `'${_module.relativePath}': function(module, exports, require){
${_module.transformCode}
},`;
});
// console.log("路径和code拼接的_modules字符串", _modules);
/* +++新增内容+++ */
// template模板文件,是使用webpack编译处理的文件,将引入资源的入口和处理的资源,替换为我们处理的数据
const template = `(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter,
});
}
};
__webpack_require__.r = function (exports) {
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
__webpack_require__.n = function (module) {
var getter =
module && module.__esModule
? function getDefault() {
return module["default"];
}
: function getModuleExports() {
return module;
};
__webpack_require__.d(getter, "a", getter);
return getter;
};
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";
return __webpack_require__((__webpack_require__.s = "${this.entryId}"));
})({
${_modules}
});
`;
console.log("加上webpack的模板代码", template);
const dist = path.dirname(outputPath);
if (fs.existsSync(dist)) {
//rmdir移除已经存在的目录
rmdir(dist, function (err, dirs, files) {
if (!err) {
fs.mkdirSync(dist);
fs.writeFileSync(outputPath, template, "utf-8");
}
});
} else {
fs.mkdirSync(dist);
fs.writeFileSync(outputPath, template, "utf-8");
}
}
}
module.exports = Compilation;
第62行到118行为webpack编译文件生成的模板文件,我们可以直接拿来使用,把入口文件和需要处理的资源替换为我们的数据即可。
执行node wpack.js命令
最后就可以在指定的输出路径中,看到输出的文件资源。
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter,
});
}
};
__webpack_require__.r = function (exports) {
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
__webpack_require__.n = function (module) {
var getter =
module && module.__esModule
? function getDefault() {
return module["default"];
}
: function getModuleExports() {
return module;
};
__webpack_require__.d(getter, "a", getter);
return getter;
};
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";
return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
'./src/index.js': function(module, exports, require){
"use strict";
var _count = require("./src/count.js");
var _utils = require("./src/utils.js");
var res = (0, _utils.sum)(_count.a, _count.b);
console.log(res);
},'./src/count.js': function(module, exports, require){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.c = exports.b = exports.a = void 0;
var a = 1;
exports.a = a;
var b = 2;
exports.b = b;
var c = 99;
exports.c = c;
},'./src/utils.js': function(module, exports, require){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sum = void 0;
var sum = function sum(a, b) {
return a + b;
};
exports.sum = sum;
},
});
自定义loader
loader的实现本质就是一个函数方法。是对代码或静态资源进行处理。
新建loader目录
创建test-loader.js
自定义loader
const loader = (content, map, meta) => {
console.log("执行了test-loader++++++++++++++++、", content, map, meta);
return content + '\n console.log("last line")';
};
module.exports = loader;
创建一个自定义loader,给每个文件代码最后添加一行注释。
在webpack.config.js中添加的test-loader
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [path.resolve(__dirname, "loader", "test-loader")],
options: {
// 预设babel做怎样的兼容性处理
presets: ["@babel/preset-env"],
},
},
],
}
};
修改Parser的ast方法
// 创建静态方法 ast,将指定的路径文件解析为ast对象
static ast(path, options) {
// 0,读取配置文件中的rules
let rules = options.module.rules;
// 1.读取文件
const content = fs.readFileSync(path, "utf-8");
/* 处理loader start */
let loaders = [];
rules.forEach((rule) => {
if (rule.test.test(path)) {
loaders = [...loaders, ...rule.use];
}
});
// 经过loader处理后的code
let targetCode = "";
for (let i = loaders.length - 1; i >= 0; i--) {
let loader = loaders[i];
targetCode = require(loader)(content);
}
/* 处理loader end */
console.log("读取文件", content);
// 2.解析读取出来的文件内容,使用module类型及膝
const _ast = parser.parse(targetCode, {
sourceType: "module",
});
// console.log(_ast);
// console.log("我是ast的body内容", _ast.program.body);
return _ast;
}
再次运行编译后的文件
执行node wpack.js进行编译打包,输出bundle.js
结果
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter,
});
}
};
__webpack_require__.r = function (exports) {
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
__webpack_require__.n = function (module) {
var getter =
module && module.__esModule
? function getDefault() {
return module["default"];
}
: function getModuleExports() {
return module;
};
__webpack_require__.d(getter, "a", getter);
return getter;
};
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";
return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
"./src/index.js": function (module, exports, __webpack_require__) {
"use strict";
var _count = __webpack_require__("./src/count.js");
var _utils = __webpack_require__("./src/utils.js");
var res = (0, _utils.sum)(_count.a, _count.b);
console.log(res);
console.log("last line");
},
"./src/count.js": function (module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.c = exports.b = exports.a = void 0;
var a = 1;
exports.a = a;
var b = 2;
exports.b = b;
var c = 99;
exports.c = c;
console.log("last line");
},
"./src/utils.js": function (module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.sum = void 0;
var sum = function sum(a, b) {
return a + b;
};
exports.sum = sum;
console.log("last line");
},
});
运行下该文件,可以看到
last line
last line
3
last line
自定义plugin
插件的实现依赖第三方库tapable,在不同的生命周期触发不同的方法执行。发生在打包编译时,不同阶段调用插件的apply方法内的不同钩子执行。
安装依赖yarn add tapable
先修改wpack.js入口文件
// 引入编译文件 Compiler
const Compiler = require("./package/Compiler");
// 引入配置文件 webpack.config.js
const options = require("./webpack.config.js");
const compiler = new Compiler(options);
/* 新增内容 */
//添加插件的执行
const plugins = options.plugins;
for (let plugin of plugins) {
plugin.apply(compiler);
}
// 执行 compiler 实例的run方法
compiler.run();
首先添加对插件的处理,插件的处理过程应该在实例对象run方法执行之前,这样才能把插件安装到comiler
实例对象内部。
Compiler类中添加hooks
hooks表示插件的不同执行时机,在构造函数中定义几个测试使用。
yarn add tapable
,新增run、emit、done三个测试钩子。正式版本中有很多个钩子的使用调用。
// 引入Compilation文件
const Compilation = require("./Compilation");
const { SyncHook } = require("tapable");
// Compiler是配置文件实例的结果,只有一个
class Compiler {
// 构造函数
constructor(options) {
this.options = options;
this.entry = options.entry;
// 设置 modules,用来设置loader,
this.modules = [];
/* 新增 处理插件的钩子 */
this.hooks = {
run: new SyncHook(),
// 增加
emit: new SyncHook(),
done: new SyncHook(),
};
}
run() {
console.log(this.entry);
this.compile();
}
// +++新增内容+++
compile() {
const compilation = new Compilation(this);
// 增加
this.hooks.run.call();
const entryModule = compilation.buildModule(this.options.entry, true);
/* +++新增内容+++ */
// 将解析处理的module资源添加到 modules中
this.modules.push(entryModule);
// 循环遍历多个modules
this.modules.forEach((_module) => {
const deps = _module.dependencies;
// console.log(deps);
for (const dep in deps) {
if (deps.hasOwnProperty(dep)) {
// 递归遍历 modules,因为文件有相互之间的引入
this.modules.push(compilation.buildModule(deps[dep]), false);
}
}
});
/* +++新增内容+++ */
compilation.emitFiles();
// 增加
this.hooks.emit.call();
this.hooks.done.call();
}
}
module.exports = Compiler;
新建plugins目录
创建consolelog测试插件
// ClgOnBuildWebpackPlugin.js
const pluginName = 'ClgOnBuildWebpackPlugin';
class ClgOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log('wpack开始执行打包!!!');
});
// 在文件打包结束后执行
compiler.hooks.done.tap(pluginName,(compilation)=> {
console.log("整个webpack打包结束")
})
// 在webpack输出文件的时候执行
compiler.hooks.emit.tap(pluginName,(compilation)=> {
console.log("文件开始发射")
})
}
}
module.exports = ClgOnBuildWebpackPlugin;
在webpack.config.js中引入插件
引入插件,并在plugins中创建实例
const path = require("path");
const babelLoader = require("babel-loader");
const lessLoader = require("./loader/less-loader.js");
const styleLoader = require("./loader/style-loader.js");
/* 新增插件 */
const ClgOnBuildWebpackPlugin = require("./plugins/ClgOnBuildWebpackPlugin.js");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader", path.resolve(__dirname, "loader", "test-loader")],
options: {
// 预设babel做怎样的兼容性处理
presets: ["@babel/preset-env"],
},
},
],
},
/* 新增插件 */
plugins: [new ClgOnBuildWebpackPlugin()],
};
执行node wpack.js
命令,可以看到插件的执行情况。
项目代码:gitee.com/shenshuai89…