• 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);
  • 相关阅读:
    Redis面试题
    spring boot错误: 找不到或无法加载主类
    JAVA的高并发编程
    Redis多机多节点集群实验
    Redis单机多节点集群实验
    Redis集群概述
    Redis的持久化之AOF方式
    Redis的持久化之RDB方式
    Redis持久化介绍
    Redis Keys的通用操作
  • 原文地址:https://www.cnblogs.com/wzndkj/p/10927826.html
Copyright © 2020-2023  润新知