Jacleklm's Blog

Bundler源码编写

2019/09/30

实现代码

实现一个类似webpack的打包工具,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')

const moduleAnalyser = (filename) => {
const content = fs.readFileSync(filename, 'utf-8') // content为用fs得到的入口文件的源代码
const ast = parser.parse(content, { // ast为由源代码得到的抽象语法树
sourceType: 'module'
})
const dependencies = {}
traverse(ast, { // 用traverse在抽象语法树中找到有ImportDeclaration的部分(代码中的import的语句)
ImportDeclaration({ node }) {
const dirname = path.dirname(filename)
const newFile = './' + path.posix.join(dirname, node.source.value)
dependencies[node.source.value] = (newFile)
}
})
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return { // 这是分析结果
filename,
dependencies,
code
}
}

const makeDependencoesGraph = (entry) => {
const entryModule = moduleAnalyser(entry) // 拿到分析结果
const graphArray = [entryModule] // 用队列,遍历加递归,实现将各层依赖加进这个数组中,得到依赖图谱
for (let i = 0; i < graphArray.length; i++) {
const item = graphArray[i]
const { dependencies } = item
if (dependencies) {
for (let j in dependencies) {
graphArray.push(
moduleAnalyser(dependencies[j])
)
}
}
}
const graph = {} // 对图谱做一个简化,方便之后用
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}

const generateCode = (entry) => {
const graph = JSON.stringify(makeDependencoesGraph(entry))
// 依赖图谱中各个依赖的代码其实都有require和export,但是浏览器是无法识别的,所以我们要自己写
// require('${entry}')中要加单引号,因为我们的要拼接一些代码而不是真正去执行一些代码
return `
(function(graph) {
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath])
}
var exports = {};
(funcrion(require, exports, code) {
eval(code);
})(localRequire, exports, graph[module].code);
return exports;
};
require('${entry}')
})(${graph});
`;
}

代码解读

moduleAnalyser

要对入口文件进行打包,我们应该对入口文件进行分析:

  • 拿到入口文件的源代码。借助fs模块实现
  • 找到入口文件的依赖。借助@babel/parser,把源代码转换成AST,再用@babel/traverse找到源代码中的声明的语句而找到依赖的路径。创建一个对象来保存这些依赖即可
  • ES6转ES5。@babel/core,@babel/preset-env

makeDependencoesGraph

模块的依赖里又会引用其他依赖,想完全分析好必须弄清所有依赖的关系,创建依赖图谱

generateCode

生成最终打包完成的代码。return的应该是打包生成的代码(字符串),同时这些代码应该是一个闭包,避免污染全局环境

CATALOG
  1. 1. 实现代码
  2. 2. 代码解读
    1. 2.1. moduleAnalyser
    2. 2.2. makeDependencoesGraph
    3. 2.3. generateCode