webpack中bundler源码编写2
通过第一部分的学习,我们已经可以分析一个js的文件。这节课我们学习Dependencies Graph,也就是依赖图谱。对所有模块进行分析。先分析index.js。index.js里面引入了messgage.js。再去分析message,一层一层的去分析,要想实现这个效果,需要去写个函数。
const fs = require('fs'); // 帮助我们获取一些文件的信息 const path = require('path'); // 打包的时候需要绝对路径,借助path这个模块 const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件 const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以 const babel = require('@babel/core'); // babel的核心模块,转化代码,转化成浏览器认识的代码 // 分析模块 const moduleAnalyser = (filename) => { // 读取文件内容 const content = fs.readFileSync(filename, 'utf-8'); // 利用parser.parse获取到ast,抽象语法树 const ast = parser.parse(content, { sourceType: 'module' // 说明是es module的引入方式 }); // 利用traverse对代码进行一个分析 const dependencies = {};
traverse(ast, { // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点 ImportDeclaration({ node }){ // 拿到filename对应的文件夹路径 const dirname = path.dirname(filename); // 对这个文件夹的路径进行一个转化,将引入的模块转化成相对于bundler的相对路径 const newFile = './' + path.join(dirname, node.source.value); // 为了方便,把相对路径,绝对路径都存上,key是相对路径,value是绝对路径 dependencies[node.source.value] = newFile; } });
// 这个方法可以将抽象语法树转化成浏览器可以运行代码。 const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] // 把es6语法翻译成es5语法 });
// 返回入口文件和相对应的依赖,都可以分析出来了。 return { filename, dependencies, code } } // 依赖图谱函数,将所有分析好的模块放在这里 const makeDependenciesGraph = (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])); } } } // 打印出来,发现所有模块都分析好了。 console.log(graphArray); } const graphInfo = makeDependenciesGraph('./src/index.js');
运行node bundler.js | highlight。发现所有模块的文件,依赖和翻译的代码都分析好了打印出来。这个就是我们的依赖图谱。
在后面我们打包代码的时候,如果是个数组的话,打包起来不是特别的容易,所以对这个数组进行一个格式化对转化。
// 依赖图谱函数,将所有分析好的模块放在这里 const makeDependenciesGraph = (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 } })
console.log(graph);
}
这个时候数组就编程了一个对象。
通过这个函数makeDependenciesGraph,我们就把这个项目的所有文件的以来关系都表示出来了。所以所有代码如下。
const fs = require('fs'); // 帮助我们获取一些文件的信息 const path = require('path'); // 打包的时候需要绝对路径,借助path这个模块 const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件 const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以 const babel = require('@babel/core'); // babel的核心模块,转化代码,转化成浏览器认识的代码 // 分析模块 const moduleAnalyser = (filename) => { // 读取文件内容 const content = fs.readFileSync(filename, 'utf-8'); // 利用parser.parse获取到ast,抽象语法树 const ast = parser.parse(content, { sourceType: 'module' // 说明是es module的引入方式 }); // 利用traverse对代码进行一个分析 const dependencies = {}; traverse(ast, { // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点 ImportDeclaration({ node }){ // 拿到filename对应的文件夹路径 const dirname = path.dirname(filename); // 对这个文件夹的路径进行一个转化,将引入的模块转化成相对于bundler的相对路径 const newFile = './' + path.join(dirname, node.source.value); // 为了方便,把相对路径,绝对路径都存上,key是相对路径,value是绝对路径 dependencies[node.source.value] = newFile; } }); // 这个方法可以将抽象语法树转化成浏览器可以运行代码。 const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] // 把es6语法翻译成es5语法 }); // 返回入口文件和相对应的依赖,都可以分析出来了。 return { filename, dependencies, code } } // 依赖图谱函数,将所有分析好的模块放在这里 const makeDependenciesGraph = (entry) => { const entryModule = moduleAnalyser(entry); const graphArray = [entryModule]; // 循环入口文件 for(let i = 0; i < graphArray.length; i++) { const item = graphArray[i]; const { dependencies } = item; // 如果该模块有依赖文件,那么就对相应的依赖文件继续进行分析 if(Object.getOwnPropertyNames(dependencies).length) { 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 graphInfo = makeDependenciesGraph('./src/index.js'); console.log(graphInfo);
接下来我们就只要借助dependenciesGraph来生成真正可以在浏览器运行的代码。
const fs = require('fs'); // 帮助我们获取一些文件的信息 const path = require('path'); // 打包的时候需要绝对路径,借助path这个模块 const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件 const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以 const babel = require('@babel/core'); // babel的核心模块,转化代码,转化成浏览器认识的代码 // 分析模块 const moduleAnalyser = (filename) => { // 读取文件内容 const content = fs.readFileSync(filename, 'utf-8'); // 利用parser.parse获取到ast,抽象语法树 const ast = parser.parse(content, { sourceType: 'module' // 说明是es module的引入方式 }); // 利用traverse对代码进行一个分析 const dependencies = {}; traverse(ast, { // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点 ImportDeclaration({ node }){ // 拿到filename对应的文件夹路径 const dirname = path.dirname(filename); // 对这个文件夹的路径进行一个转化,将引入的模块转化成相对于bundler的相对路径 const newFile = './' + path.join(dirname, node.source.value); // 为了方便,把相对路径,绝对路径都存上,key是相对路径,value是绝对路径 dependencies[node.source.value] = newFile; } }); // 这个方法可以将抽象语法树转化成浏览器可以运行代码。 const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] // 把es6语法翻译成es5语法 }); // 返回入口文件和相对应的依赖,都可以分析出来了。 return { filename, dependencies, code } } // 依赖图谱函数,将所有分析好的模块放在这里 const makeDependenciesGraph = (entry) => { const entryModule = moduleAnalyser(entry); const graphArray = [entryModule]; // 循环入口文件 for(let i = 0; i < graphArray.length; i++) { const item = graphArray[i]; const { dependencies } = item; // 如果该模块有依赖文件,那么就对相应的依赖文件继续进行分析 if(Object.getOwnPropertyNames(dependencies).length) { 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; } // 这个函数结合dependenciesGraph来生成最后的代码 const generateCode = (entry) => { // 拿到生成的 graph对象 const graph = JSON.stringify(makeDependenciesGraph(entry)); /** * 1、避免污染全局,放在大的闭包里面 * 2、我们看到graph里面的源码有require,export这样的关键字,这个浏览器也是看不懂的, * 所以如果想去直接去执行每个模块的代码,会报错的。所以首先需要在里面构建require * 3、localRequire是相对路径转化的函数 */ return ` (function(graph){ function require(module){ function localRequire(relativePath){ return require(graph[module].dependencies[relativePath]) } var exports = {}; (function(require, exports, code){ eval(code); })(localRequire, exports, graph[module].code); return exports; }; require('${entry}') })(${graph}); `; } const code = generateCode('./src/index.js'); console.log(code);