入口文件cli,利用Commander获取命令行参数
#!/usr/bin/env node
const { Command } = require("commander");
const path = require("path");
const fs = require("fs");
const webpack = require("./webpack.js");
const program = new Command();
program.option("-c, --config <filename>", "config file").action((options) => {
let option = {};
if (options.config) {
option = require(path.join(process.cwd(), options.config));
}
webpack(option);
});
program.parse();
webpack 主体
-
合并默认参数
-
创建Compiler对象
-
初始化可用的hooks
-
遍历plugins注册
-
执行run方法,触发不同hooks
-
获取入口文件,获取文件代码
-
遍历loader根据正则匹配
-
将模块地址转为相对于根目录,内容是上面loader编译后内容
-
替换require为__webpack_require
-
esj拼接模板
const path = require("path");
const fs = require("fs");
const { SyncHook } = require("tapable");
const babel = require("@babel/core");
const generate = require("@babel/generator");
const ejs = require("ejs");
class Compiler {
constructor(config) {
this.config = config;
this.entry = config.entry;
this.root = process.cwd();
this.modules = {};
this.hooks = {
run: new SyncHook(["compiler"]),
make: new SyncHook(["compiler"]),
emit: new SyncHook(["compiler"]),
done: new SyncHook(["compiler"]),
};
}
run() {
this.hooks.run.call(this);
this.hooks.make.call(this);
this.buildModule(path.resolve(this.root, this.entry));
this.hooks.emit.call(this);
this.buildTemplate();
this.hooks.done.call(this);
}
buildModule(modulePath) {
let source = this.getSource(modulePath);
let moduleName =
"./" + path.relative(this.root, modulePath).replace("\\", "/");
if (!this.modules[moduleName]) {
const parentPath = path.join(modulePath, "../");
const { sourceCode, dependenes } = this.parse(source, parentPath);
this.modules[moduleName] = sourceCode
dependenes.forEach((dependent) => {
this.buildModule(dependent);
});
}
}
getSource(modulePath) {
let source = fs.readFileSync(modulePath, "utf-8");
this.config.module.rules.forEach((it) => {
if (it.test.test(modulePath)) {
it.use.forEach((loader) => {
source = require(loader)(source);
});
}
});
return source;
}
parse(code, parentPath) {
const ast = babel.parse(code, {
sourceType: "module",
presets: ["@babel/preset-env"],
});
const dependenes = [];
const root = this.root;
babel.traverse(ast, {
CallExpression(p) {
if (p.node.callee.name == "require") {
p.node.callee.name = "__webpack_require";
const relativePath = path
.relative(
root,
path.join(parentPath, "./" + p.node.arguments[0].value)
)
.replace("\\", "/");
dependenes.push(path.join(root, relativePath));
p.node.arguments[0].value = "./" + relativePath;
}
},
});
const sourceCode = generate.default(ast).code;
return {
sourceCode,
dependenes,
};
}
buildTemplate() {
const templateText = fs.readFileSync(
path.resolve(__dirname, "./template.ejs"),
"utf-8"
);
const output = ejs.render(templateText, {
entry: this.entry,
modules: this.modules,
});
// console.log(output);
fs.writeFileSync(
path.join(this.config.output.path, this.config.output.filename),
output,
{
encoding: "utf8",
}
);
}
}
function webpack(option) {
const config = mergeExports(option);
const complier = new Compiler(config);
complier.run();
}
function mergeExports(option) {
return Object.assign(
{
devtool: "source-map",
},
option
);
}
module.exports = webpack;
模板
(function (modules) {
var installedModules = {};
function __webpack_require(moduleId) {
if (installedModules[moduleId]) {
return installedModules[installedModules].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
exports: {},
});
modules[moduleId].call(
module.exports,
__webpack_require,
module,
module.exports
);
return module.exports;
}
__webpack_require("<%= entry %>");
})({
<% for(var key in modules) { %>
"<%= key %>" : function (__webpack_require, module, exports) {
<%- modules[key] %>
},
<% } %>
});