当前位置: 首页 > news >正文

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

前言

先看一下webpack打包后的bundle.js,前边的直接扫一眼就过,可以发现这个立即执行函数的形参就是一个,key为引入文件路径,value为该模块代码的函数。
所以比较重要的就是通过webpack的配置文件中的entry的入口文件,递归去生成这个modules,并把代码中require变成__webpack_require__。

(function (modules) { // webpackBootstrap// The module cachevar installedModules = {}// The require functionfunction __webpack_require__ (moduleId) {// Check if module is in cacheif (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 functionmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__)// Flag the module as loadedmodule.l = true// Return the exports of the modulereturn 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 valueif ((mode & 4) && typeof value === 'object' && value && value.__esModule) return valuevar 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 exportsreturn __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

#! /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 (最重要的实现都在这个类里)

/*
* 编译我们的代码,生成 打包后的内容
* 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.jsthis.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 里面再次做处理,引入依赖的文件里面还有可能 requiredeps.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

(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

{"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"}
}
http://www.xdnf.cn/news/240715.html

相关文章:

  • Leetcode刷题记录23——最小覆盖子串
  • systemd和OpenSSH
  • DeepSeek V3重磅升级!
  • 联邦学习的收敛性分析(全设备参与,不同本地训练轮次)
  • LoRA、QLoRA、LoRA+、LongRA、DoRA、MaLoRA、GaLore
  • MySQL基础关键_002_DQL(一)
  • [AI]怎么计算中文被bert模型切分的tokens数量
  • TC8:SOMEIP_ETS_021-022
  • 产品VP简历模板案例
  • # 基于 Python 和 jieba 的中文文本自动摘要工具
  • ChipCN IDE KF32 导入工程后,无法编译的问题
  • 探秘明远智睿SSD2351开发板在HMI领域的独特魅力
  • 2025第八届数字中国峰会启幕 | 思特奇以数智力量,助推数字中国建设
  • 游戏性能测试
  • C 语 言 - - - 文 件 操 作
  • vue3 动态修改系统title
  • python查看指定的进程是否存在
  • 安凯微以创新之芯,赋能万物智能互联新时代
  • k8s术语值ReplicaSet
  • navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)
  • Ollama 安装 QWen3 及配置外网访问指南
  • 近期汇报
  • springboot框架常用配置
  • 在柯希霍夫积分法偏移成像中,消除数据采集和地下构造(如深浅孔径差异)导致的叠加次数不均匀会引起成像剖面强度差异
  • 【STM32单片机】#11.5 I2C通信(硬件读写)
  • TM1668芯片学习心得三
  • Qwen3-32B的幻觉问题
  • Windows系统安装Docker(Win10系统升级,然后安装)
  • UE 像素和线框盒子 材质
  • 【AI图像创作变现】08 变现渠道—间接获客:让客户主动找上门