代码地址: github.com/chengliu050…
一、步骤
1.解析单个文件,传入路径filename
+ 给文件编号id
+ 读取filename对应的文件内容并解析为ast树
+ 根据ast树找到文件其他依赖存入数组dependencies(require的文件)
+ 根据规则转换代码(这里使用插件@babel/preset-env将es6转为es5)
var ID = 0
function createAsset(filename) { //解析单个文件
const content = fs.readFileSync(filename,'utf-8');
const ast = parser.parse(content,{
sourceType:'module'
})
const dependencies = []
traverse(ast,{
ImportDeclaration:({node})=>{
dependencies.push(node.source.value)
}
})
const {code} = babel.transformFromAstSync(ast,null,{
presets:['@babel/preset-env']
})
let id = ID++
return{
id,
filename,
code,
dependencies
}
}
返回如下格式的对象,这里简称为asset
{
id: 0,
filename: './src/index.js',
code: '
var _info = require("./info.js");
console.log(_info["default"]);
',
dependencies: [ './info.js' ]
}
2.得到各个文件转换的对象asset组成的数组queue
+ 从入口开始,得到一个如上的对象asset
+ 根据dependencies递归各个文件,转换的asset放到数组queue
+ 给每个asset加一个属性,将filename与id关联起来
asset.mapping[dependencies[i]] = child.id
function createGraph(entry){ //从入口开始,根据dependencies递归各个文件,将createAsset(filename)处理结果放到数组queue
const mainAsset = createAsset(entry);
const queue = []
queue.push(mainAsset)
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
}
3.将queue对象关联起来为可执行的代码
+ 将数组queue转换成对象,key为id,value为数组[]
+ 重写一个require函数
+ 从入口开始执行 require(0)
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(relativePath){
return require(mapping[relativePath])
}
const module = {
exports:{}
}
fn(localRequire,module,module.exports)
return module.exports;
}
require(0);
})({${modules}})
`
return result
}
二、细节
- 函数require 接受的是id,所以需要mapping对象,将路径转为id
- es6的子文件最终输出
module.export.default = xxx module.export.fn = xxx - 父函数重写require的module,module.exports,从而拿到default,fn的代码
三、用到的库
-
3.1 @babel/parser
-
3.2 @babel/traverse
-
3.3 @babel/core下的transformFromAstSync方法
欢迎关注我的前端自检清单,我和你一起成长