jesuslove / minipack-demo Goto Github PK
View Code? Open in Web Editor NEWwebpack 打包器实现原理
License: MIT License
webpack 打包器实现原理
License: MIT License
仓库地址:minipack
一个 JS 编写的现代模块打包器的简化示例。
打包器如何将代码打包成 bundler 呢?下面一起来看看打包逻辑。
每一个 module 都会生成一个 Asset 对象,对象中保存资源的 id,filename, 依赖关系,以及 babel 转码后的 code 。
实现如下:
/ 创建 Asset
// 接收文件名
function createAsset(filename) {
// 1. 读取文件内容
const content = fs.readFileSync(filename, 'utf-8');
// 2. 生成 AST
const ast = babylon.parse(content, {
sourceType: 'module',
});
// 3. 获取依赖,读取 AST 中 ImportDeclaration
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value);
},
});
// 4. 唯一标识
const id = ID++;
// 5. 转义代码,适配不同版本浏览器
const {code} = transformFromAst(ast, null, {
presets: ['env'],
});
return {
id,
filename,
dependencies,
code,
};
}
// import aa from 'xxx.js' 的 AST JSON 结构。
{
"type": "Program",
"start": 0,
"end": 23,
"body": [
{
"type": "ImportDeclaration",
"start": 0,
"end": 23,
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"start": 7,
"end": 9,
"local": {
"type": "Identifier",
"start": 7,
"end": 9,
"name": "aa"
}
}
],
"source": {
"type": "Literal",
"start": 15,
"end": 23,
"value": "xxx.js", <------
"raw": "'xxx.js'"
}
}
],
"sourceType": "module"
}
dependencies
数组中。从主 mainAsset 开始,遍历所有 module 的依赖创建 childAsset 并将其推入队列中。
// 创建依赖图谱
function createGraph(entry) {
// 1. 入口元素生成 asset
const mainAsset = createAsset(entry);
// 2. 队列
const queue = [mainAsset];
// 3. 遍历队列
for (const asset of queue) {
// 3.1 保存子依赖和ID之间的映射,例如:{xxx.js : 1}
asset.mapping = {};
// 3.2 所在目录
const dirname = path.dirname(asset.filename);
// 3.3 处理依赖
asset.dependencies.forEach(relativePath => {
// 3.3.1 依赖绝对路径
const absolutePath = path.join(dirname, relativePath);
// 3.3.2 生成 childAsset
const child = createAsset(absolutePath);
asset.mapping[relativePath] = child.id;
// 3.3.3 推入 queue 中。
queue.push(child);
});
}
// 4. 返回 queue 队列,保存的是所有的 Asset 对象。
return queue;
}
返回一个自调用函数,浏览器可以直接执行。 自调用函数如下结构:
(function() {})()
下面看看具体的实现代码:
// 打包函数,返回一个自调用函数
// (function(){})()
function bundle(graph) {
// 1. 所有的依赖
let modules = '';
// 2. 创建映射关系
/*
id: [
function(require, module, exports) { code },
{xxxx.js: id}
],
*/
graph.forEach(mod => {
modules += `${mod.id}: [
function(require, module, exports) {
${mod.code}
},
${JSON.stringify(mod.mapping)}
]`;
});
// 创建自调用函数
// 1. 接收 modules 对象作为参数。上面的 modules 字符串包转为 modules 对象。
// 2. 创建 require 方法接收资源 ID 作为参数
// 3. 根据 id 读取 modules 中对应的 fn 和 mapping
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}})
`;
return result;
}
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;
// 创建 Asset
// 接收文件名
function createAsset(filename) {
// 1. 读取文件内容
const content = fs.readFileSync(filename, 'utf-8');
// 2. 生成 AST
const ast = babylon.parse(content, {
sourceType: 'module',
});
// 3. 获取依赖,读取 AST 中 ImportDeclaration
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value);
},
});
const id = ID++;
// 4. 转义代码,适配不同版本浏览器
const {code} = transformFromAst(ast, null, {
presets: ['env'],
});
return {
id,
filename,
dependencies,
code,
};
}
// 创建依赖图谱
function createGraph(entry) {
// 1. 入口元素生成 asset
const mainAsset = createAsset(entry);
// 2. 队列
const queue = [mainAsset];
// 3. 遍历队列
for (const asset of queue) {
// 3.1 保存子依赖和ID之间的映射
asset.mapping = {};
// 3.2 所在目录
const dirname = path.dirname(asset.filename);
// 3.3 处理依赖
asset.dependencies.forEach(relativePath => {
// 3.3.1 依赖绝对路径
const absolutePath = path.join(dirname, relativePath);
// 3.3.2 生成 childAsset
const child = createAsset(absolutePath);
asset.mapping[relativePath] = child.id;
queue.push(child);
});
}
return queue;
}
// 打包函数,返回一个自调用函数
// (function(){})()
function bundle(graph) {
// 自执行函数的参数
// key : value 映射 key 为模块 id,value 是一个 function 和依赖 mapping。
let modules = '';
graph.forEach(mod => {
modules += `${mod.id}: [
function(require, module, exports) {
${mod.code}
},
${JSON.stringify(mod.mapping)}
]`;
});
// 创建一个 自调用方法,接收 modules 对象
// 1. 内部创建 require 方法接收 id 作为参数
// 2. 结构 modules 对应 id 的 fn 和 mapping
// 3. localRequire 以对应路径文件的 id为参数获取module 内容。
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}})
`;
return result;
}
const graph = createGraph('./example/entry.js');
const result = bundle(graph);
console.log(result);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.