实现一个简易版Webpack
- 2020 年 4 月 6 日
- 笔记
原理
- 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完成了。
相关链接
例子源码
视频教程
babylon
babel-traverse docs