webpack源码简版

  1. Compiler.js:负责整个编译过程的控制和管理,包含compiler构造函数,在构造函数中获取webpack.config.js的出口、入口、参数等
  2. Bundler.js:引入webpack.config.js和compiler.js,创建compiler实例化对象并传入options。
  3. 拿到webpack配置参数后开始根据入口文件解析文件内容(fs.readFileSync读取文件内容)
    (1). @babel/parser:将文件内容的的源代码解析成AST语法树,AST语法树的节点包含了文件依赖的文件名称。(type,start,end,…)
    (2). @babel/traverse:提取依赖的文件名称到一个数组中
    (3). @babel/core:通过babel.transformFromAst方法将ast转换成浏览器可执行的代码;返回一个对象{文件名,依赖数组,可执行代码},作为依赖图的一个节点
    (4). 遍历入口文件的依赖数组,数组中是文件名,递归执行上述方法直到找到所有的依赖,然后返回依赖对象。
  4. Bundler.js使用compiler对象的run方法生成依赖对象,并通过bundle方法解析graph(依赖图对象),使用eval方法来调用code并输出分发代码
  5. Bundler.js使用fs.writeFileSync将分发代码输出到文件中,生成bundle。

complier.js

// 文件操作模块,读取文件内容
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");
module.exports = class Complier {
  constructor(options) {
    const { entry, output } = options;
    this.entry = entry;
    this.output = output;
  } // 拿到参数、执行、分析入口文件
  run() {
    const mainAsset = this.createAsset(this.entry);
    const queue = [mainAsset];
    for (const asset of queue) {
      Object.values(asset.dependencies).forEach((filename) => {
        const child = this.createAsset(filename);
        queue.push(child);
      });
    }
    console.log(queue);
    return queue;
  }
  // 开始编译,构建ast语法树 filename: ./src/index.js
  createAsset(filename) {
    const content = fs.readFileSync(filename, "utf-8");
    const ast = parser.parse(content, { sourceType: "module" });
    // 创建依赖
    const dependencies = {};
    traverse(ast, {
      ImportDeclaration: ({ node }) => {
        // 获取文件的路径名如 './src/index.js' dirname='./src'
        const dirname = path.dirname(filename);
        const absPath = path.join(dirname, node.source.value);
        dependencies[node.source.value] = absPath;
      },
    });
    // 将ast转换成代码 // https://www.babeljs.cn/docs/babel-core
    const { code } = babel.transformFromAst(ast, null, {
      presets: ["@babel/preset-env"],
    });
    return { filename, dependencies, code };
  }
};

bundler.js

// 引入配置
const fs = require('fs');
const path = require('path')
const options = require('../webpack.config')

const complier = require('./complier')

const { entry, output } = options

function bundle(graph){
  // 得到以依赖文件名的对象 
  let modules = {};
  graph.forEach(item => {
    modules[item.filename] = {
        dependencies: item.dependencies,
        code: item.code
    }
  })
  modules = JSON.stringify(modules)
  const result = `(function(graph){
      function require(filepath) {
        function localRequire(relativePath) {
            // 将代码中的require中的路径转换成dependencies存储的带文件夹名的路径
           return require(graph[filepath].dependencies[relativePath])
        }
        var exports = {}
        
        function fn(require, exports, code) {
            eval(code)
        }
        
        fn(localRequire, exports, graph[filepath].code)

        return exports
      }
      require('${entry}')
    })(${modules})`
  return result
}

function createFile(code) {
    fs.writeFileSync(path.join(output.path, output.filename), code)
}

const graph = new complier(options).run()
const result = bundle(graph)
createFile(result)

你可能感兴趣的:(前端,webpack,webpack,前端,node.js)