首页 > 其他 > 详细

手写bundler

时间:2020-09-11 19:48:29      阅读:98      评论:0      收藏:0      [点我收藏+]

webpack的定位是一个bundler,解决的是将多个JS模块打包成可以在浏览器上运行的代码。接下来我们将实现一个简易的bundler:由入口文件对代码进行打包,打包成可以在浏览器运行的代码。

被打包项目介绍

整个演示项目的目录结构如下所示,其中src下的文件是bundler.js需要打包的代码。

├── bundler.js
├── package-lock.json
└── src
    ├── index.js
    ├── msg.js
    └── word.js

src下各文件内容如下:
word.js

const word = ‘miniWebpack‘;
export default word;

msg.js

import word from ‘./word.js‘
const msg = `hello ${word}`;
export default msg;

index.js

import msg from ‘./msg.js‘
console.log(msg)
export default index;

实现bundler.js

要实现bundler,我们需要实现3部分功能:
moduleAnalyser:模块分析
makeDependenciesGraph:生成依赖图谱
generateCode:生成可执行代码

模块分析

const moduleAnalyser = (fileName) => {
  // 1.fs模块根据路径读取到了入口文件的内容
  const content = fs.readFileSync(fileName, ‘utf-8‘);
  // 2.使用@babel/parser将文件内容转换成抽象语法树AST
  const ast = parser.parse(content, {
    sourceType: ‘module‘
  })
  // 3.使用@babel/traverse遍历了AST ,对每个ImportDeclaration节点(保存的相对于入口文件的路径)做映射,把依赖关系拼装在 dependencies对象里
  let dependencies = {};
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirName = path.dirname(fileName);
      const newFile = ‘./‘ + path.join(dirName, node.source.value);
      // key是相对于当前模块的路径,value为相对于bundler.js的路径。
      dependencies[node.source.value] = newFile;
    }
  })
  // 4.使用@babel/core结合@babel/preset-env预设,将AST转换成了浏览器可以执行的代码
  const { code } = babel.transformFromAst(ast, null, {
    presets: ["@babel/preset-env"]
  })
  return {
    fileName,
    dependencies,
    code
  }
}

模块分析流程图如下:
技术分享图片
调用console.log(moduleAnalyser(‘./src/index.js‘)),可以在控制台打印出以下内容:

{ fileName: ‘./src/index.js‘,
  dependencies: { ‘./msg.js‘: ‘./src/msg.js‘ },
  code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\n\nvar _msg = _interopRequireDefault(require("./msg.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_msg["default"]);\nvar _default = index;\nexports["default"] = _default;‘ }

import语法已经变成一个require函数了,export语法,也变成了在给一个exports变量赋值。

依赖图谱生成

调用moduleAnalyser(‘./src/index.js‘)拿到了入口文件的dependencies映射,接下来再把入口文件的依赖路径再一次做模块分析,再把依赖模板的依赖路径再一次做模块分析...... 其实就是广度优先遍历,可以很轻松得到这次打包所有需要的模块的分析结果。

//生成依赖图谱
const makeDependenciesGraph = (entry) => {
  //entryModule:入口文件的dependencies映射
  const entryModule = moduleAnalyser(entry);
  //graphArray:图谱动态数组,初始只有一个元素entryModule
  const graphArray = [entryModule];
  for (let i = 0; i < graphArray.length; i++) {
    const item = graphArray[i];
    //dependencies:当前模块的dependencies映射
    const { dependencies } = item;
    //如果当前模块有依赖文件,则遍历dependencies,调用moduleAnalyser,对依赖文件进行模板分析
    if (dependencies) {
      for (let j in dependencies) {
        graphArray.push(moduleAnalyser(dependencies[j]))
      }
    }
  }
  //graph:遍历graphArray生成更利于打包使用的graph。其中key为fileName,value为dependencies和code
  const graph = {};
  graphArray.forEach(item => {
    graph[item.fileName] = {
      dependencies: item.dependencies,
      code: item.code
    }

  })
  return graph;
}

生成依赖图谱流程图如下:
技术分享图片
调用console.log(makeDependenciesGraph(‘./src/index.js‘)),可以在控制台打印出以下内容

{ ‘./src/index.js‘: 
   { dependencies: { ‘./msg.js‘: ‘./src/msg.js‘ },
     code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\n\nvar _msg = _interopRequireDefault(require("./msg.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_msg["default"]);\nvar _default = index;\nexports["default"] = _default;‘ },
  ‘./src/msg.js‘: 
   { dependencies: { ‘./word.js‘: ‘./src/word.js‘ },
     code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\n\nvar _word = _interopRequireDefault(require("./word.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nvar msg = "hello ".concat(_word["default"]);\nvar _default = msg;\nexports["default"] = _default;‘ },
  ‘./src/word.js‘: 
   { dependencies: {},
     code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\nvar word = \‘miniWebpack\‘;\nvar _default = word;\nexports["default"] = _default;‘ } }

生成代码

我们需要开始生成最终可运行的代码了。
为了不污染全局作用域,我们使用立即执行函数来包装我们的代码,将依赖图谱graph作为参数传入:

手写bundler

原文:https://www.cnblogs.com/superlizhao/p/13653499.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!