前端面试宝典---webpack原理解析,并有简化版源码

前言

先看一下webpack打包后的bundle.js,前边的直接扫一眼就过,可以发现这个立即执行函数的形参就是一个,key为引入文件路径,value为该模块代码的函数。

所以比较重要的就是通过webpack的配置文件中的entry的入口文件,递归去生成这个modules,并把代码中require变成__webpack_require__。

javascript 复制代码
(function (modules) { // webpackBootstrap
  // The module cache
  var installedModules = {}

  // The require function
  function __webpack_require__ (moduleId) {

    // Check if module is in cache
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports

    }
    // Create a new module (and put it into the cache)
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}

    }

    // Execute the module function
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

    // Flag the module as loaded
    module.l = true

    // Return the exports of the module
    return module.exports

  }


  // expose the modules object (__webpack_modules__)
  __webpack_require__.m = modules

  // expose the module cache
  __webpack_require__.c = installedModules

  // define getter function for harmony exports
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter })

    }

  }

  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })

    }
    Object.defineProperty(exports, '__esModule', { value: true })

  }

  // create a fake namespace object
  // mode & 1: value is a module id, require it
  // mode & 2: merge all properties of value into the ns
  // mode & 4: return value when already ns object
  // mode & 8|1: behave like require
  __webpack_require__.t = function (value, mode) {
    if (mode & 1) value = __webpack_require__(value)
    if (mode & 8) return value
    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
    var ns = Object.create(null)
    __webpack_require__.r(ns)
    Object.defineProperty(ns, 'default', { enumerable: true, value: value })
    if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key] }.bind(null, key))
    return ns

  }

  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    var getter = module && module.__esModule ?
      function getDefault () { return module['default'] } :
      function getModuleExports () { return module }
    __webpack_require__.d(getter, 'a', getter)
    return getter

  }

  // Object.prototype.hasOwnProperty.call
  __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property) }

  // __webpack_public_path__
  __webpack_require__.p = ""


  // Load entry module and return exports
  return __webpack_require__(__webpack_require__.s = "./src/app.js")

})
  ({

    "./src/app.js":
      (function (module, __webpack_exports__, __webpack_require__) {

        "use strict"
        __webpack_require__.r(__webpack_exports__)
        var _module__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module */ "./src/module.js")
        var _module__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_module__WEBPACK_IMPORTED_MODULE_0__)

        console.log("Hello World")



      }),

    "./src/module.js":
      (function (module, exports) {

        module.exports = {
          name: 'module',
          description: 'module description',
          version: '1.0.0',
          dependencies: {
            'module-a': '1.0.0',
            'module-b': '1.0.0',
          },
          devDependencies: {
            'module-c': '1.0.0',
            'module-d': '1.0.0',
          },
        }


      })


  });
//# sourceMappingURL=bundle.js.map

实现思路

项目配置如下

  1. 读取webpack.config.js文件

  2. 对入口文件实现编译生成依赖对象modules

    2.1 根据入口文件递归去获取依赖及其代码,并通过ast抽象语法书,对require替换成__webpack_require__

    2.2 复制webpack打包生成打的bundles.js 将其改造成模板文件(bundlejsTemplate.ejs),通过ejs,把modules 插入模板中,生成代码

  3. 将替换后的模板代码生成到webpack.config.js配置的output路径下

具体实现

index.js

javascript 复制代码
#! /usr/bin/env node
/*
* 实现 webpack 打包功能
* 1. 配置文件的读取 webpack.config.js
*
* 2. 实现入口文件的编译,然后生成依赖对象 modules
*
*
* */
// console.log('jdpack打包功能');
// console.log(process.cwd()); // 打印当前命令所处的目录

/*
* 1. 配置文件的读取 webpack.config.js
* */
const Compiler = require('../lib/Compiler.js');

const path = require('path');
const configPath = path.resolve(process.cwd(), 'webpack.config.js');
const configObj = require(configPath);

// console.log(configObj); // 配置文件对象

/*
* 2. 实现入口文件的编译,然后生成依赖对象 modules
* */
const compile = new Compiler(configObj);
compile.run();
// console.log(compile.modules); // 模块依赖对象

Compiler.js (最重要的实现都在这个类里)

javascript 复制代码
/*
* 编译我们的代码,生成 打包后的内容
* 1. 根据配置文件 entry 入口文件,读取入口文件对象的代码
* 2. 生成依赖对象
* 3. 传递给 webpack的 自执行的匿名函数
*
* */
const fs = require('fs');
const ejs = require('ejs');
const path = require('path');

/*
* 导入ast相关的模块
* */
const {parse} = require('@babel/parser');
const generator = require('@babel/generator').default;
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');


class Compiler {
    /*
    * 配置文件
    * */
    constructor(config) {
        this.config = config;
        // 模块依赖对象,保存了代码里面的所有的模块依赖关系 key 依赖模块的路径 value 依赖模块对应代码的函数
        this.modules = {};
    }

    run() {
        // 1. 根据配置文件的入口文件生成依赖对象
        this.buildModules(this.config.entry);

        // 编译后,生成 bundle.js
        this.generatorBundlejs();


    }

    /*
    * moduleId 依赖模块的路径
    *
    * 如果在 代码里面有 require其他的模块代码
    * 1. 先生成模块依赖对象
    * 2. 将代码里面的 require 替换为 __webpack_require__ ast 实现
    * 3. 将依赖模块的路径加载入口文件的目录
    *
    * */
    buildModules(moduleId) {
        let code = this.getCode(moduleId);
        let {deps, newCode} = this.parseModule(code);
        // console.log(deps, newCode);
        this.modules[moduleId] = newCode;

        // 针对 deps 里面再次做处理,引入依赖的文件里面还有可能 require
        deps.forEach(item => {
            this.buildModules(item);
        })
    }
    /*
    * path 依赖模块的路径
    * */
    getCode(modulePath) {
        return fs.readFileSync(modulePath, 'utf8');
    }

    /*
    * 将代码里面的依赖做替换
    * */
    parseModule(code) {

        let mainRootPath = path.dirname(this.config.entry);
        // 存储了代码里面所有的依赖路径
        let deps = [];

        const ast = parse(code);
        /*
        * 1. 对 require 节点做处理,替换 __webpack_require__
        * */

        traverse(ast, {
            CallExpression(NodePath) {
                let node = NodePath.node;

                if (node.callee.name === 'require') {

                    node.callee.name = '__webpack_require__';

                    // 2. 对依赖路径做替换
                    let depPath = node.arguments[0].value;
                    depPath = '.\\' + path.join(mainRootPath, depPath);
                    depPath = depPath.replace(/\\/g, '/');

                    // 利用语法树将 require 里面依赖路径做修改
                    node.arguments[0] = t.stringLiteral(depPath);

                    deps.push(depPath);

                }
            }
        });

        let newCode = generator(ast).code;

        // console.log(newCode);

        return {deps, newCode};


    }

    /*
    * 先根据生成的入口文件的依赖对象,生成打包文件。然后在 分析入口文件里面的内容,如果有其他的 require 进行再次生成依赖对象,在生成打包的文件
    * */
    generatorBundlejs() {

        /*
        * 使用 ejs 根据依赖对象,生成打包后的 bundle.js 文件
        * 1. 读取模板
        *
        *
        * */
        let bundlePath = path.resolve(__dirname, 'bundlejsTemplate.ejs');
        let bundleTemplate = fs.readFileSync(bundlePath, 'utf-8');

        /*
        * 2. 使用 ejs 做模板的替换
        * */
        let renderCode = ejs.render(bundleTemplate, {moduleId: this.config.entry, modules: this.modules});

        /*
        * 3. 将打包后的内容根据 webpack.config.js 里面的 output 进行保存
        * */
        let outputPath = this.config.output.path;
        // 判断打包后的输出目录是否存在,如果不存在,则先创建目录
        if (!fs.existsSync(outputPath)) {
            fs.mkdirSync(outputPath);
        }

        let outputFilePath = path.resolve(outputPath, this.config.output.filename);

        fs.writeFileSync(outputFilePath, renderCode);

    }

}

module.exports = Compiler;

bundlejsTemplate.ejs

javascript 复制代码
(function(modules) { // webpackBootstrap
// The module cache
var installedModules = {};

// The require function
function __webpack_require__(moduleId) {

// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};

// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// Flag the module as loaded
module.l = true;

// Return the exports of the module
return module.exports;
}


// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;

// expose the module cache
__webpack_require__.c = installedModules;

// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};

// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};

// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
return ns;
};

// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};

// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

// __webpack_public_path__
__webpack_require__.p = "";


// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "<%- moduleId %>");
})
/************************************************************************/
({
<% for(let key in modules) { %>
    "<%- key %>": (function(module, exports, __webpack_require__) {

    <%- modules[key] %>

    }),
<% } %>

});

package.json

javascript 复制代码
{
  "name": "jdpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "bin": {
    "mypack": "./bin/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/generator": "^7.18.13",
    "@babel/parser": "^7.18.13",
    "@babel/traverse": "^7.18.13",
    "@babel/types": "^7.18.13",
    "ejs": "^3.1.8"
  }
}
相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax