太长不看
function createAsset(filename) {
const content = readFile(filename)
const ast = parseAST(content)
const dependencies = collectDependencies(ast)
const code = transformCode(ast)
return {
id: generateId(),
filename,
dependencies,
code
}
}
function createGraph(entry) {
const mainAsset = createAsset(entry)
const queue = [mainAsset]
for (asset of queue) {
asset.mapping = {}
for (dependency of asset.dependencies) {
const child = createAsset(dependency)
asset.mapping[dependency] = child.id
queue.push(child)
}
}
return queue
}
function bundle(graph) {
const modules = graph.map(mod => {
return `${mod.id}: [
function(require, module, exports) { ${mod.code} },
${mod.mapping}
]`
})
return `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id]
function localRequire(name) {
return require(mapping[name])
}
const module = { exports: {} }
fn(localRequire, module, module.exports)
return module.exports
}
require(0)
})({${modules}})
`
}
核心概念
- ast 抽象语法树
- 树的遍历算法
- 构建依赖关系图
- IIFE
源码
const fs = require('fs');
const path = require('path');
const babylon = require('babylon');
const traverse = require('babel-traverse').default;
const {transformFromAst} = require('babel-core');
let ID = 0;
function createAsset(filename) {
console.log(`\n[创建资源] 开始处理文件: ${filename}`);
const content = fs.readFileSync(filename, 'utf-8');
console.log(`[创建资源] 文件内容长度: ${content.length} 字符`);
const ast = babylon.parse(content, {
sourceType: 'module',
});
console.log(`[创建资源] AST解析完成,节点数量: ${ast.program.body.length}`);
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value);
console.log(`[创建资源] 发现依赖: ${node.source.value}`);
},
});
const id = ID++;
console.log(`[创建资源] 分配模块ID: ${id}`);
console.log(`[创建资源] 依赖列表:`, dependencies);
const {code} = transformFromAst(ast, null, {
presets: ['env'],
});
console.log(`[创建资源] 代码转换完成,转换后代码长度: ${code.length}`);
console.log(`[创建资源] 转换后代码预览:\n${code.slice(0, 200)}...`);
return {
id,
filename,
dependencies,
code,
};
}
function createGraph(entry) {
console.log(`\n[创建依赖图] 开始处理入口文件: ${entry}`);
const mainAsset = createAsset(entry);
console.log(`[创建依赖图] 入口文件处理完成,ID: ${mainAsset.id}`);
console.log(`[创建依赖图] 入口文件依赖:`, mainAsset.dependencies);
const queue = [mainAsset];
console.log(`[创建依赖图] 初始化队列,当前长度: ${queue.length}`);
for (const asset of queue) {
console.log(`\n[创建依赖图] 处理模块: ${asset.filename} (ID: ${asset.id})`);
asset.mapping = {};
const dirname = path.dirname(asset.filename);
console.log(`[创建依赖图] 模块目录: ${dirname}`);
asset.dependencies.forEach(relativePath => {
const absolutePath = path.join(dirname, relativePath);
console.log(`[创建依赖图] 处理依赖: ${relativePath} -> ${absolutePath}`);
const child = createAsset(absolutePath);
asset.mapping[relativePath] = child.id;
console.log(`[创建依赖图] 添加依赖映射: ${relativePath} -> ${child.id}`);
console.log(`[创建依赖图] 当前模块映射:`, asset.mapping);
queue.push(child);
console.log(`[创建依赖图] 队列长度更新为: ${queue.length}`);
});
}
console.log(`\n[创建依赖图] 完成! 总共处理了 ${queue.length} 个模块`);
console.log(`[创建依赖图] 最终依赖图:`, queue.map(asset => ({
id: asset.id,
filename: asset.filename,
dependencies: asset.dependencies,
mapping: asset.mapping
})));
return queue;
}
function bundle(graph) {
console.log(`\n[打包] 开始打包 ${graph.length} 个模块`);
let modules = '';
graph.forEach(mod => {
console.log(`[打包] 处理模块 ${mod.id}: ${mod.filename}`);
console.log(`[打包] 模块依赖映射:`, mod.mapping);
console.log(`[打包] 模块代码长度: ${mod.code.length}`);
console.log(`[打包] 模块代码预览:\n${mod.code.slice(0, 200)}...`);
modules += `${mod.id}: [
function (require, module, exports) {
${mod.code}
},
${JSON.stringify(mod.mapping)},
],`;
});
console.log(`[打包] 所有模块处理完成,开始生成最终代码`);
console.log(`[打包] 模块字符串长度: ${modules.length}`);
const result = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;
console.log(`[打包] 打包完成! 最终代码长度: ${result.length} 字符`);
return result;
}
const graph = createGraph('./example/entry.js');
const result = bundle(graph);
const outputPath = path.join(__dirname, 'bundle.js');
fs.writeFileSync(outputPath, result);
console.log(`\n[执行] 打包结果已写入文件: ${outputPath}`);
console.log('\n[执行] 开始执行打包后的代码:');
require(outputPath);
日志

流程图

核心流程
核心流程说明
- 资源创建阶段
- 使用 babel 转换代码为 CommonJS 格式
- 依赖图构建阶段
- 打包阶段
项目
minipack