Webpack重学系列-从0到1实现一个webpack(一)

114 阅读1分钟

为了更明实现一个webpack本文会使用fasterpack来代替webpack关键字

实现一个最简fasterpack

#!/usr/bin/env node

const path = require("path");
const fs = require("fs");
const config = require(path.resolve("./fasterpack.config.js"));

class Fasterpack {
  constructor(config) {
    //缓存配置
    this.config = config;
    //保存入口
    this.entry = config.entry;
    //获取执行根目录
    this.root = process.cwd();
    //创建 modules
    this.modules = {};
  }

  /**
   *
   * @param {*} code 源码
   * @param {*} parent 文件父目录
   */
  pares(code, parent) {
    // console.log("parent", parent);
    const deps = [];
    const r = /require\(['"](.*)['"]\)/g;
    const r1 = /[\r\n]/g;
    //替换 require
    code = code.replace(r, function (match, arg) {
      const retPath = path.join(parent, arg.replace(/'|"/g, ""));
      // 添加 依赖路径
      deps.push(retPath);
      return `__fasterpack_require__("./${retPath}")`;
    });
    //替换换行
    code = code.replace(r1, function (match, arg) {
      return "\\n";
    });
    code = code.replace(/\"/g, '\\"');
    code = `"${code}"`;
    return { code, deps };
  }
  /**
   *
   * @param {*} modulePath 模块物理路径
   * @param {*} name 项目中文件唯一路径 Key
   */
  createModule(modulePath, name) {
    const modulePathKeys = Object.keys(this.modules);
    const fileContent = fs.readFileSync(modulePath, "utf-8");
    //递归判断 是否已经注册
    if (!modulePathKeys.includes(name)) {
      //返回解析后的源码和源码中的依赖
      const { code, deps } = this.pares(fileContent, path.dirname(name));
      this.modules[name] = `function(module,exports,__fasterpack_require__){
        eval(${code})
      }`;
      // 递归创建module
      deps.forEach((dep) => {
        this.createModule(path.join(this.root, dep), `./${dep}`);
      });
    }
  }
  generateModuleStr() {
    let fnTemp = "";
    Object.keys(this.modules).forEach((name) => {
      fnTemp += `"${name}":${this.modules[name]},`;
    });
    return `{${fnTemp}}`;
  }
  generateFile() {
    //参数注入模板
    this.tempalate = getTempalate(this.generateModuleStr(), this.entry);
    fs.writeFileSync(`./dist/main.js`, this.tempalate);
    console.log("写入完毕");
  }
  start() {
    console.log("开始解析");
    const entryPath = path.resolve(this.root, this.entry);
    //创建缓存 module
    this.createModule(entryPath, this.entry);
    //生成文件
    this.generateFile();
  }
}

const getTempalate = (__modules_content__, __entry__) => {
  return `(function (modules) {
  const installModules = {};
  function __faster_require__(moduleId) {
    //是否缓存
    if (installModules[moduleId]) {
      console.log(installModules, moduleId, installModules[moduleId].exports);
      return installModules[moduleId].exports;
    }
    var module = (installModules[moduleId] = {
      exports: {},
    });
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __faster_require__
    );
    return module.exports;
  }
  //入口
  return __faster_require__("${__entry__}");
})(${__modules_content__});`;
};

const fasterpack = new Fasterpack(config);
fasterpack.start();

接下来我们借助babel来改造一下

需要了解:

#!/usr/bin/env node

const path = require("path");
const fs = require("fs");
// 将源码转成 ast 语法树
const parser = require("@babel/parser");
// 解析ast语法书
const traverse = require("@babel/traverse").default;
// 解析转换源码
const { transformFromAst } = require("@babel/core");
const config = require(path.resolve("./fasterpack.config.js"));

class Fasterpack {
  constructor(config) {
    //缓存配置
    this.config = config;
    //保存入口
    this.entry = config.entry;
    //获取执行根目录
    this.root = process.cwd();
    //创建 modules
    this.modules = {};
  }

  pares(fileContent, parentDirname) {
    const deps = [];
    const ast = parser.parse(fileContent, {
      sourceType: "module",
    });
    traverse(ast, {
      ImportDeclaration({ node }) {
        deps.push(path.join(parentDirname, node.source.value));
      },
    });
    let { code } = transformFromAst(ast, null, {
        //转换标准
        presets: ["@babel/preset-env"],
    });

    const r = /require\(['"](.*)['"]\)/g;

    code = code.replace(r, function (match, arg) {
      const retPath = path.join(parentDirname, arg.replace(/'|"/g, ""));
      // 添加 依赖路径
      return `__fasterpack_require__("./${retPath}")`;
    });

    return { code, deps };
  }
  /**
   *
   * @param {*} modulePath 模块物理路径
   * @param {*} name 项目中文件唯一路径 Key
   */
  createModule(modulePath, name) {
    const modulePathKeys = Object.keys(this.modules);
    const fileContent = fs.readFileSync(modulePath, "utf-8");
    //递归判断 是否已经注册
    if (!modulePathKeys.includes(name)) {
      //返回解析后的源码和源码中的依赖
      const { code, deps } = this.pares(fileContent, path.dirname(name));
      this.modules[name] = `function(module,exports,__fasterpack_require__){
        eval(${JSON.stringify(code)})
      }`;
      // 递归创建module
      deps.forEach((dep) => {
        this.createModule(path.join(this.root, dep), `./${dep}`);
      });
    }
  }
  generateModuleStr() {
    let fnTemp = "";
    Object.keys(this.modules).forEach((name) => {
      fnTemp += `"${name}":${this.modules[name]},`;
    });
    return `{${fnTemp}}`;
  }
  generateFile() {
    //参数注入模板
    this.tempalate = getTempalate(this.generateModuleStr(), this.entry);
    fs.writeFileSync(`./dist/main.js`, this.tempalate);
    console.log("写入完毕", this.m);
  }
  start() {
    console.log("开始解析");
    const entryPath = path.resolve(this.root, this.entry);
    //创建缓存 module
    this.createModule(entryPath, this.entry);
    //生成文件
    this.generateFile();
  }
}

const getTempalate = (__modules_content__, __entry__) => {
  return `(function (modules) {
  const installModules = {};
  function __faster_require__(moduleId) {
    //是否缓存
    if (installModules[moduleId]) {
      console.log(installModules, moduleId, installModules[moduleId].exports);
      return installModules[moduleId].exports;
    }
    var module = (installModules[moduleId] = {
      exports: {},
    });
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __faster_require__
    );
    return module.exports;
  }
  //入口
  return __faster_require__("${__entry__}");
})(${__modules_content__});`;
};

const fasterpack = new Fasterpack(config);
fasterpack.start();