人话系列
打包:将多个小模块整合成一个大模块
优点是:方便,快捷,易整理
缺点: 体积变大
本质是递归调用
关键词
依赖关系,模块系统,入口文件,引导程序
科学点的叫话:模块捆绑器
入口文件
模块捆绑器具有 入口文件 的这种概念,而不是添加一些脚本标签在浏览器中并让它们运行,我们让 捆绑器 知道哪个文件 是我们应用程序的 主要文件, 该文件能引导我们的整个应用程序
依赖关系
我们的打包程序将从该 入口文件 开始,并尝试理解它依赖于那些文件。 然后, 它会尝试了解哪些文件依赖关系取决于它, 它会继续这样做,直到它发现我们应用程序中的 每个模块,以及它们如何 相互依赖 -- 这种对项目的理解被称为 依赖图
模块系统
每个导入项的code就是一个模块,每一个模块之间的相互依赖,构成依赖图
引导程序
解析依赖图,保证执行顺序跟预期相符
方法
- 读取入口文件,解析成
ast - 读取 ast,把import等导入文件,构建成依赖关系,(需要es互转的也可以在这里转换)
- 创建依赖图:根据第二步的ast 导入声明的依赖关系,再循环处理下 ast 解析,将其挂载到对应父集中
- 创建 bundle: 构建模块,填充解析过后的code, 并填充依赖mapping
- 添加引导程序:根据创建的bundle,顺序执行mapping 映射
实际生成的bundle 例子
(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);
})({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 = 'world';
// 这里是利用对象key定义方法,将 world 值传递到 1中的 name变量中去
},
{}
],})
基础转换函数例子
const fs = require('fs');
const path = require('path');
const babylong = require('babylon');
const traverse = require('babel-traverse').default;
const {transformFromAst} = require('babel-core');
let count = 0;
function createAsset(filename) {
const content = fs.readFileSync(filename, 'utf-8');
const ast = babylong.parse(content, {
sourceType: 'module',
});
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value);
},
});
const id = count++;
const {code} = transformFromAst(ast, null, {
presets: ['env'],
});
return {
id,
filename,
dependencies,
code,
};
}
function createGraph(entry) {
const mainAsset = createAsset(entry)
console.log('mainAsset', mainAsset);
const queue = [mainAsset];
for(const asset of queue) {
asset.mapping = {}
const dirname = path.dirname(asset.filename)
asset.dependencies.forEach(relativePath => {
const absolutePath = path.join(dirname, relativePath)
const child = createAsset(absolutePath)
asset.mapping[relativePath] = child.id
queue.push(child)
})
}
return queue
}
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];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports: {}};
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;
return result;
}
// console.log(createAsset(path.resolve(__dirname, '../example/entry.js')));
const graph = createGraph(path.resolve(__dirname, '../example/entry.js'));
const result = bundle(graph);
console.log(result);