實現一個簡易版Webpack
阿新 • • 發佈:2020-04-07
## 原理
* 1、解析一個檔案及其依賴
* 2、構建一個依賴關係圖
* 3、將所有東西打包成一個單檔案
## 程式碼實現
#### 檔案結構
#### 1、解析檔案及其依賴
通過babylon將檔案解析成AST
[線上解析器](https://astexplorer.net/):
![image](https://user-images.githubusercontent.com/3964466/78567304-0e8d9580-7853-11ea-98d2-c7ccdd7153ac.png)
程式碼實現:
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)
```
輸出結果:
![image](https://user-images.githubusercontent.com/3964466/78567360-22d19280-7853-11ea-8055-7d664c3343fa.png)
#### 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);
```
輸出結果:
![image](https://user-images.githubusercontent.com/3964466/78567385-2e24be00-7853-11ea-93c4-0bbe4199a67f.png)
#### 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';
},
{}
],
})
```
把程式碼複製到瀏覽器執行,執行成功!
![image](https://user-images.githubusercontent.com/3964466/78567412-3b41ad00-7853-11ea-88ec-fe6b1932f0cc.png)
一個簡易版的Webapck完成了。
## 相關連結
[例子原始碼](https://github.com/jiajunlin/Build-Your-Own-Webpack)
[視訊教程](https://www.youtube.com/watch?v=Gc9-7PBqOC8)
[babylon](https://www.npmjs.com/package/babylon)
[babel-traverse docs](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#babel-t