当前位置: 首页 > 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"}
}

相关文章:

  • 突破传统!TTRL如何开启大模型无监督强化学习新篇章?
  • 使用Python和Pandas实现的Snowflake权限检查与SQL生成用于IT审计
  • windows电脑端SSH连接开termux的安卓端
  • 【经管数据】上市公司企业资本要素和劳动要素投入数据(2000-2022年)
  • Java大厂硬核面试:Flink流处理容错、Pomelo JVM调优、MyBatis二级缓存穿透防护与Kubernetes服务网格实战解析
  • 博主勇闯自媒体
  • 图像加密算法概述
  • python2反编译部分
  • 油气人工地震资料信号处理中,机器学习和AI应用
  • 深入理解虚拟机与容器:原理、对比与应用场景分析
  • linux学习——数据库API创建
  • Nginx Proxy Manager 中文版安装部署
  • 【电脑维修】MERCURY水星无线网卡导致 Windows 网络适配器无法连接的一种情况
  • 【Linux】VSCode用法
  • 数字智慧方案5961丨智慧能源与运维云平台解决方案(52页PPT)(文末有下载方式)
  • 贝叶斯算法实战:从原理到鸢尾花数据集分类
  • Vue 3 中通过 this. 调用 setup 暴露的函数
  • 【2025域适应科研日报】
  • 仿腾讯会议——服务器结构讲解
  • 前端面试常问问题[From CV]
  • 全国人大常委会关于授权国务院在中国(新疆)自由贸易试验区暂时调整适用《中华人民共和国种子法》有关规定的决定
  • 世界黄金协会:一季度全球黄金投资需求同比增170%
  • 关于新冠疫情防控与病毒溯源的中方行动和立场
  • 特朗普加征关税冲击波:美国零售、汽车、航空、科技企业纷纷预警业绩波动
  • 解放日报:上海深化改革开放,系统集成创新局
  • 建发股份:将于5月6日召开股东大会,审议提名林茂等为公司新一届董事等议案