原理
- 1、解析一個檔案及其依賴
- 2、構建一個依賴關係圖
- 3、將所有東西打包成一個單檔案
程式碼實現
檔案結構
1、解析檔案及其依賴
通過babylon將檔案解析成AST 線上解析器:
程式碼實現: bundle.js
const fs = require("fs");
const babylon = require("babylon");
const traverse = require("babel-traverse").default;
let ID = 0;
function createAsset(filename) {
const content = fs.readFileSync(filename, "utf-8");
// 解析檔案成AST
const ast = babylon.parse(content, {
sourceType: "module",
});
const dependencies = [];
// 根據AST獲取相關依賴
traverse(ast, {
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value);
},
});
const id = ID++;
return {
id,
filename,
dependencies,
};
}
const mainAssets = createAsset("./example/entry.js");
console.log(mainAssets)
複製程式碼
輸出結果:
2、構建一個依賴關係圖
// 構建一個依賴關係圖
function createGraph(entry) {
const mainAssets = createAsset(entry);
const queue = [mainAssets];
for (const asset of queue) {
const dirname = path.dirname(asset.filename);
asset.mapping = {};
asset.dependencies.forEach((relativePath) => {
const absolutePath = path.join(dirname, relativePath);
const child = createAsset(absolutePath);
asset.mapping[relativePath] = child.id;
queue.push(child);
});
}
return queue;
}
const graph = createGraph("./example/entry.js");
console.log(graph);
複製程式碼
輸出結果:
3、將所有東西打包成一個單檔案
在解析檔案時,使用babel對程式碼進行轉譯
// 解析一個檔案及其依賴
function createAsset(filename) {
const content = fs.readFileSync(filename, "utf-8");
const ast = babylon.parse(content, {
sourceType: "module",
});
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value);
},
});
const id = ID++;
// 使用babel對程式碼進行轉譯
const { code } = babel.transformFromAst(ast, null, {
presets: ["env"],
});
return {
id,
filename,
dependencies,
code,
};
}
複製程式碼
// 將所有東西打包成一個單檔案
function bundle(graph) {
let modules = "";
graph.forEach((mod) => {
modules += `${mod.id}:[
function(require,module,exports){
${mod.code}
},
${JSON.stringify(mod.mapping)}
],`;
});
const result = `
(function(modules){
function require(id){
const [fn, mapping] = modules[id];
// 因為程式碼引入檔案時根據相對路徑,所以需要把相對路徑跟id進行一個對映
function localRequire(relativePath){
return require(mapping[relativePath])
}
const module = {exports:{}};
fn(localRequire,module,module.exports)
return module.exports;
}
// 執行入口模組
require(0);
})({${modules}})
`;
return result;
}
const graph = createGraph("./example/entry.js");
const result = bundle(graph);
console.log(result);
複製程式碼
輸出結果:
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(relativePath) {
return require(mapping[relativePath])
}
const module = {
exports: {}
};
fn(localRequire, module, module.exports)
return module.exports;
}
require(0);
})({
0: [
function(require, module, exports) {
"use strict";
var _message = require("./message.js");
var _message2 = _interopRequireDefault(_message);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
console.log(_message2.default);
},
{
"./message.js": 1
}
],
1: [
function(require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _name = require("./name.js");
exports.default = "hello " + _name.name + "!";
},
{
"./name.js": 2
}
],
2: [
function(require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var name = exports.name = 'Aaron';
},
{}
],
})
複製程式碼
把程式碼複製到瀏覽器執行,執行成功!
一個簡易版的Webapck完成了。