深入webpack打包原理及loader和plugin的实现(2)

入口文件内容被放到一个数组中,总共有六个 Node 节点,我们可以看到,每个节点有一个 type 属性,其中前两个的 type 属性是 ImportDeclaration ,这对应了我们入口文件的两条 import 语句,并且,每一个 type 属性是 ImportDeclaration 的节点,其 source.value

属性是引入这个模块的相对路径,这样我们就得到了入口文件中对打包有用的重要信息了。

接下来要对得到的ast做处理,返回一份结构化的数据,方便后续使用。

1.3 对模块内容做处理

对 ast.program.body 部分数据的获取和处理,本质上就是对这个数组的遍历,在循环中做数据处理,这里同样引入一个babel的模块 @babel/traverse 来完成这项工作。

安装 @babel/traverse ,演示时安装的版本号为 ^7.9.6

const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { sourceType: 'module' }) const deps = {} traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const absPath = './' + path.join(dirname, node.source.value) deps[node.source.value] = absPath } }) console.log(deps) } getModuleInfo('https://www.jb51.net/article/src/index.js')

创建一个对象 deps ,用来收集模块自身引入的依赖,使用 traverse 遍历 ast ,我们只需要对 ImportDeclaration 的节点做处理,注意我们做的处理实际上就是把相对路径转化为绝对路径,这里我使用的是 Mac 系统,如果是 windows 系统,注意斜杠的区别

获取依赖之后,我们需要对 ast 做语法转换,把 es6 的语法转化为 es5 的语法,使用 babel 核心模块 @babel/core 以及 @babel/preset-env 完成

安装 @babel/core @babel/preset-env ,演示时安装的版本号均为 ^7.9.6

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 getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { sourceType: 'module' }) const deps = {} traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const absPath = './' + path.join(dirname, node.source.value) deps[node.source.value] = absPath } }) const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"] }) const moduleInfo = { file, deps, code } console.log(moduleInfo) return moduleInfo } getModuleInfo('https://www.jb51.net/article/src/index.js')

如下图所示,我们最终把一个模块的代码,转化为一个对象形式的信息,这个对象包含文件的绝对路径,文件所依赖模块的信息,以及模块内部经过 babel 转化后的代码

深入webpack打包原理及loader和plugin的实现

2. 递归的获取所有模块的信息

这个过程,也就是获取 依赖图(dependency graph) 的过程,这个过程就是从入口模块开始,对每个模块以及模块的依赖模块都调用 getModuleInfo 方法就行分析,最终返回一个包含所有模块信息的对象

const parseModules = file => { // 定义依赖图 const depsGraph = {} // 首先获取入口的信息 const entry = getModuleInfo(file) const temp = [entry] for (let i = 0; i < temp.length; i++) { const item = temp[i] const deps = item.deps if (deps) { // 遍历模块的依赖,递归获取模块信息 for (const key in deps) { if (deps.hasOwnProperty(key)) { temp.push(getModuleInfo(deps[key])) } } } } temp.forEach(moduleInfo => { depsGraph[moduleInfo.file] = { deps: moduleInfo.deps, code: moduleInfo.code } }) console.log(depsGraph) return depsGraph } parseModules('https://www.jb51.net/article/src/index.js')

获得的depsGraph对象如下图:

深入webpack打包原理及loader和plugin的实现

我们最终得到的模块分析数据如上图所示,接下来,我们就要根据这里获得的模块分析数据,来生产最终浏览器运行的代码。

3. 生成最终代码

在我们实现之前,观察上一节最终得到的依赖图,可以看到,最终的code里包含exports以及require这样的语法,所以,我们在生成最终代码时,要对exports和require做一定的实现和处理

我们首先调用之前说的parseModules方法,获得整个应用的依赖图对象:

const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) }

接下来我们应该把依赖图对象中的内容,转换成能够执行的代码,以字符串形式输出。 我们把整个代码放在自执行函数中,参数是依赖图对象

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/206cf6e003a80567f228417a7509ef79.html