# 简易版本webpack
# 前置条件
npm init
生成package.json
- 添加依赖 后
npm install
{
"name": "easy-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.7.7",
"@babel/parser": "^7.7.7",
"@babel/preset-env": "^7.7.7",
"@babel/traverse": "^7.7.4"
}
}
# webpack.config.js
// 这里是传入 compiler的option哦
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js"
}
};
# index.js
- 执行
node index.js
会打包出 dist文件夹
const Complier = require("./lib/Compiler");
const options = require("./webpack.config");
new Complier(options).run();
# Parser
@babel/parser
解析入口,获取语法树ast
@babel/traverse
遍历维护ast
树状态,找出依赖@babel/core
and@babel/preset-env
将ast
转为浏览器可执行的代码
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
const Parser = {
getAst: path => {
const content = fs.readFileSync(path, "utf-8");// 入口文件
return parser.parse(content, {
sourceType: "module"
});
},
getDependecies: (ast, filename) => {
const dependecies = {};//依赖
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(filename);
// 保存依赖路径
const filepath = "./" + path.join(dirname, node.source.value);
dependecies[node.source.value] = filepath;
}
});
return dependecies;
},
getCode: ast => {
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});
return code;
}
};
module.exports = Parser;
# Complier
const fs = require("fs");
const path = require("path");
const Parser = require("./Parser");
class Compiler {
constructor(options) { // 读取配置
const { entry, output } = options;
this.entry = entry;
this.output = output;
this.modules = [];
}
build(filename) {
const { getAst, getDependecies, getCode } = Parser;
const ast = getAst(filename);
const dependecies = getDependecies(ast, filename);
const code = getCode(ast);
return {
filename,
dependecies,
code
};
}
// 构建启动
run() {
const info = this.build(this.entry);
this.modules.push(info);
this.modules.forEach(({ dependecies }) => { //遍历所有依赖
if (dependecies) {
for (const dependency in dependecies) {
this.modules.push(this.build(dependecies[dependency]));
}
}
});
// 依赖图
const dependencyGraph = this.modules.reduce(
(graph, item) => ({
...graph,
[item.filename]: { //唯一标识符
dependecies: item.dependecies,
code: item.code
}
}),
{}
);
this.generate(dependencyGraph);
}
// 重写require函数,输出bundle
generate(code) {
const filePath = path.join(this.output.path, this.output.filename);
const bundle = `(function(graph){
function require(moduleId){
function localRequire(relativePath){
return require(graph[moduleId].dependecies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[moduleId].code);
return exports;
}
require('${this.entry}')
})(${JSON.stringify(code)})`;
// 写入文件系统
fs.writeFileSync(filePath, bundle, "utf-8");
}
}
module.exports = Compiler;
# bundle实现
- 这是
node index.js
后你生成在dist
下的js文件
(function (graph) { // 立即执行函数
function require(moduleId) { //重写require
function localRequire(relativePath) { // 依赖
return require(graph[moduleId].dependecies[relativePath])
}
//出口对象
var exports = {};
(function (require, exports, code) {
eval(code)
})(localRequire, exports, graph[moduleId].code);
return exports; //暴露
}
require('./src/index.js') // 从入口文件开始执行
})({
"./src/index.js": {
"dependecies":
{ "./hello.js": "./src/hello.js" },
"code": "\"use strict\";\n\nvar _hello = require(\"./hello.js\");\n\ndocument.write((0, _hello.say)(\"webpack\"));"
},
"./src/hello.js": {
"dependecies": {},
"code": "\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return \"yiki \".concat(name);\n}"
}
})