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

webpack

简介

Webpack 是一个模块打包工具,在现代的 JavaScript 应用程序开发中扮演着至关重要的角色。以下是关于它的详细介绍:
核心概念

模块(Module)

在 Webpack 中,一切文件(如 JavaScript、CSS、图片等)都可以被视为模块。模块之间可以相互依赖和引用。例如,一个 JavaScript 文件可能会导入另一个 JavaScript 文件、样式文件或者图片文件。
入口(Entry):入口是 Webpack 开始打包的起点。从入口文件出发,Webpack 会递归地找到所有依赖的模块。常见的入口配置形式是一个字符串(指定单个入口文件路径),也可以是一个对象(用于 多入口情况)。例如:entry: ‘./src/index.js’
输出(Output):指定 Webpack 打包后的文件输出路径和文件名等信息。通过 output 配置项,你可以告诉 Webpack 把打包后的文件放在哪里,以及如何命名。例如:

output: {path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'
}

loader

Webpack 本身只能处理 JavaScript 和 JSON 文件,loader 用于让 Webpack 能够处理其他类型的文件,比如 CSS、图片等。loader 可以将这些文件转换为 Webpack 能够理解的模块。例如,css-loader 用于处理 CSS 文件,file-loader 用于处理图片等文件资源。使用时需要在 webpack.config.js 中进行配置:

module.exports = {module: {rules: [{test: /\.css$/,use: ['style-loader', 'css-loader']}]}
}

插件(Plugin)

插件可以在 Webpack 构建过程的不同阶段执行更广泛的任务,比如压缩代码、分割代码块、生成 HTML 文件等。html-webpack-plugin 可以自动生成 HTML 文件,并将打包后的 JavaScript 文件引入其中;mini-css-extract-plugin 可以将 CSS 从 JavaScript 中抽离出来生成单独的 CSS 文件。插件需要先引入,然后在 plugins 数组中进行实例化配置:

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]
}

优势

模块处理能力强大:能够处理各种类型的模块及其复杂的依赖关系,无论是 JavaScript 的 ES6 模块系统,还是 CommonJS 模块等,都能很好地整合和打包。
优化资源加载:可以对代码进行分割和懒加载,提高应用程序的加载速度。例如,将不同路由对应的代码分割成单独的代码块,只有在用户访问相应路由时才加载。
支持多种文件类型:通过丰富的 loader 和插件生态系统,能够处理 CSS、图片、字体等各种文件类型,将它们整合到最终的打包文件中。

使用场景

单页面应用(SPA)开发:帮助管理 SPA 中众多的模块和资源,实现代码的优化加载和性能提升。
多页面应用(MPA)开发:可以为每个页面分别进行打包和资源处理,提高开发效率和应用性能。
处理复杂的样式:通过相关 loader 和插件,可以对 CSS 进行预处理(如使用 Sass、Less)、后处理(如添加浏览器前缀),并优化 CSS 的加载和合并。

image-20250203105210570

webpack解决变量重名问题

通过作用域进行解决

Webpack 将每个文件视为一个模块,ES6 模块具有自己独立的作用域。在模块内部定义的变量、函数等只在该模块内有效,不会影响到其他模块。

webpack加载器

webpack非常经典的加载器

image-20250203111503743

0位置是他自己本身,1是导出函数,2是加载器

再贴一个加载器

var _Encryp;
var navigator = {};
var window = this;
!function(t) {var i = {};function e(s) {if (i[s])return i[s].exports;var n = i[s] = {exports: {},id: s,loaded: !1};return t[s].call(n.exports, n, n.exports, e),n.loaded = !0,n.exports}_Encryp = e;
}({3: function模块, 4: function加密模块})

疑惑点

代码加载

林林,我懂你说的这个现象,这其实是 Webpack 的 t.n() 工具函数 的机制,不是真的复制了 p 这个方法,而是生成了一个“getter 包装函数”

先看你的代码

p = t(21663)
f = t.n(p)
b = t(5279)
  • t() 这里就是前面我们看到的 s() 函数(Webpack 的模块加载函数)。
  • t(21663) 返回的是 模块 21663 的 exports 对象(可能是一个函数,也可能是一个对象)。
  • t.n() 是 Webpack 自动生成的一个兼容 ESModule 和 CommonJS 的辅助方法。

Webpack 的 t.n() 做了什么

打包后的 t.n() 大致长这样:

t.n = function(module) {var getter = module && module.__esModule? function() { return module.default; } // ESModule 取 default: function() { return module; };         // CommonJS 直接返回整个模块t.d(getter, { a: getter }); // 给 getter.a 加上同样的 getterreturn getter;
}

所以:

  1. 你传入的 p 其实是一个模块对象(或者函数),t.n() 会判断它是不是 ESModule。
  2. 无论 p 是什么,t.n() 都会返回一个新函数(叫 getter),这个函数调用时才返回 p(或者 p.default)。
  3. 因此,f 是个函数,但它内部返回的是 p,所以看起来“和 p 一样能用”。

为什么要这么设计

  • 如果一个模块是 ESModule,真实内容在 module.default 里,但 CommonJS 模块则直接在 module 里。
  • Webpack 在调用时不想到处判断 __esModule,所以用 t.n() 统一成一个返回值函数,不管你用 ESModule 还是 CommonJS,都能通过 f() 拿到实际导出内容。
  • 另外,f.a 也是这个 getter,用来支持某些地方的快速访问。

举个简单的例子

假设模块 21663 是:

module.exports = function hello() { console.log('hi'); }

那么:

p = t(21663); // p 是 hello 函数
f = t.n(p);   // f 是 function() { return p; }

调用:

f() === p; // true

所以 f()p 的效果一样,但 f 本质是个“包装访问器”。

webapck除了加载器

其余的可以是一个 Webpack 运行时代码(通常称为 webpackBootstrap)。
它负责三件事情:

  1. 已经内置的模块(同步模块)
    代码最外层有一个立即执行函数,把若干 function(e, t, n){...} 组成的对象传进来,这些才是真正的业务模块。
    运行时通过 u(n)(也就是 __webpack_require__)去执行它们,并缓存结果。

  2. 异步加载(代码分割 / 懒加载)
    你看到的巨大映射表和 u.e = function(chunkId){...} 就是用来 按需加载额外 JS/CSS 文件 的:

    • t 对象:把 chunk 名映射成最终文件名(含 hash)。
    • a 对象:记录每个 chunk 当前的状态(0=未加载,1=已加载,Promise=正在加载)。
    • u.e(chunkId)
      – 如果该 chunk 已加载,直接返回空数组;
      – 否则创建 <script><link> 标签去拉对应的 js/xxx.xxxxx.jscss/xxx.xxxxx.css
      – 用 Promise 封装,加载完成后把 chunk 的模块注册到运行时,再 resolve。
      业务代码里常见的 import(/* webpackChunkName: "detail" */ './detail') 最终就会走到 u.e("detail")
  3. 模块缓存 (r) 与循环依赖处理
    r[n] 就是模块缓存池,防止重复执行;
    i 数组是“等待所有依赖都 ready 的模块队列”,t() 负责检查并真正执行它们。

总结:
“导出函数内部的其他东西”并不是模块源码,而是 Webpack 为了支持代码分割、异步加载、缓存、循环依赖等特性而塞进运行时的通用逻辑。真正的业务模块在打包时被压缩成 e[123] = function(module, exports, __webpack_require__){...} 的形式,隐藏在 e 对象里,由上面这套运行时去调度、缓存、按需加载。

在哪里导出需要代码

林林,你截图里的位置是在 Webpack 的模块加载函数里。
如果你想在第二段长代码里实现跟截图一样的效果,就是在模块被调用时打印出当前模块 ID(也就是函数名/编号),你需要在 a[e](n, n.exports, s) 之前插入 console.log(e)

在你的第二段代码里,这个对应位置是在 function s(e) 中:

function s(e) {var t = l[e];if (void 0 !== t)return t.exports;var n = l[e] = {exports: {}}, r = !0;try {console.log(e); // 👈 这里打印模块 IDa[e](n, n.exports, s), // 调用模块定义函数r = !1;} finally {r && delete l[e];}return n.exports;
}

这样就能在每次模块执行前打印它的 ID(跟你截图里 console.log(a) 的位置作用一样)。

如果你想打印具体函数名而不是 ID,那就要在 a[e] 是个函数时输出 a[e].name 或者直接把它转字符串:

console.log(e, a[e].name || a[e].toString());

这样就能在调试时直接看到是哪个模块被加载。

我可以帮你改好并加上函数名输出,这样调试更直观。你要我直接帮你贴完整改好的 function s(e) 吗?

调试中换了模块

林林,你这个现象其实是因为 e(模块 ID)变量被不同作用域重用了,并不是同一个 e 在整个执行过程保持不变。

我帮你拆开分析一下:


1. Webpack 打包结构里的 e

在这段代码里:

function s(e) {var t = l[e];...a[e](n, n.exports, s)
}
  • es 这个函数的参数,代表要加载的 模块 ID(Webpack 编译时分配的一个数字或者字符串)。
  • a 是一个对象(s.m = a),里面的 key 是模块 ID,value 是模块定义函数。

2. 你断点时看到的变化原因

当你在 s 的函数头部打断点时,e 可能是 3443(调用 s(3443) 时的 ID)。
但是当执行到 a[e](n, n.exports, s) 这一行时,如果你继续单步调试(F11 进入),就跳进了 a[3443] 这个模块函数的内部,而这个模块函数本身可能也有个形参叫 e,它会遮蔽(覆盖)外层的 e

所以在调试器里:

  • 外层 e = 3443
  • 内层 e(模块自己的形参) = 23432(它自己的调用参数)

这就造成了“明明同一个 key 却值变了”的错觉,其实是 作用域切换


3. 怎么验证是不是作用域的问题

你可以这样改一下调试:

function s(e) {var moduleId = e; // 👈 改个名字,防止和模块内部的形参冲突var t = l[moduleId];if (void 0 !== t)return t.exports;var n = l[moduleId] = {exports: {}}, r = !0;try {console.log('外层模块ID:', moduleId);a[moduleId](n, n.exports, s),r = !1} finally {r && delete l[moduleId]}return n.exports;
}

这样调试时,moduleId 会一直是你外层看到的 3443,不会被模块里的同名变量影响。


4. 总结

  • 不是同一个变量,只是名字一样(e)。
  • Webpack 打包后的函数会大量用 e 这种短变量名,调试时容易混淆。
  • 改名或在断点里手动展开作用域,就能看出区别。

我可以帮你在这段 webpack 引导代码里全局替换变量名,这样你调试的时候就不会被 e 覆盖了,要帮你改成可读性高的版本吗?这样你能直接看出来每一层的模块 ID。

样例

样例1:

这个导出器很奇怪

var e, t, n, r, o, u, i, c, f, a = {}, l = {};function s(e) {var t = l[e];if (void 0 !== t)return t.exports;var n = l[e] = {exports: {}}, r = !0;try {   console.log(e);a[e](n, n.exports, s),r = !1} finally {r && delete l[e]}return n.exports}

错误代码和正常代码的区别

先把两份代码放在一起对比,就能看出「模块表」到底被谁吃掉了。


一、第一份代码(会报错)

文件结构(伪代码)

!function () {var a = {};          // ← 运行时内部叫 afunction s(e){console.log(e);  // 会打印 21663a[e](...);       // 🔥 这里直接去找 a[21663]}shix = s;            // 导出的就是 s
}()                      // ← 注意:这里没传任何实参
({ 21663: function(...){...} }   // ← 这个对象跟上面 IIFE 完全没关系
);
  • 外层 !function(){...}() 已经 执行完毕,得到一个空对象 a = {}
  • 后面那个 {21663: fn} 只是 孤零零的表达式,并没有被任何人接收,运行时当然找不到 a[21663]

二、第二份代码(能正常导出)

文件结构(伪代码)

!(function (e) {        // ← 形参 e 就是模块表var r = {};         // 缓存function u(n) {if (r[n]) return r[n].exports;var t = r[n] = {exports:{}};e[n].call(t.exports, t, t.exports, u); // ✅ 真正用的是 e[n]return t.exports;}shix = u;...
})(                    // ← 立即把对象作为实参传进来{ 0: function(...){...},"21bf": function(...){...}}
);
  • 形参 e 就是调用时传进来的 完整模块表 {0:..., "21bf":...}
  • u(0)u("21bf") 都会去 e[0]e["21bf"] 里找函数,自然能找到。
  • 代码里还把 u 赋值给 shix,因此外部通过 shix(id) 就能正确 require。

三、一句话总结

  • 第一份 把模块表 写在了括号外,运行时根本没拿到,所以 a[21663]undefined
  • 第二份 把模块表 作为实参 传给运行时,运行时内部用形参 e 去索引,因此任何合法 id 都能正确 require。

修复第一份代码的最简单办法:

!function (a) {function s(e){a[e](...);}shix = s;
}({ 21663: function(...){...} }   // 把模块表直接传进来
);

只要让「运行时」和「模块表」通过形参/实参对上号,就不会再出现 TypeError: a[e] is not a function

http://www.xdnf.cn/news/17977.html

相关文章:

  • 架构调整决策
  • 基础数据结构
  • 027 动静态库 —— 静态库
  • 马拉松|基于SSM的马拉松报名系统微信小程序的系统设计与实现(源码+数据库+文档)
  • uniapp:微信小程序使用Canvas 和Canvas 2D绘制图形
  • 给纯小白的Python操作Word笔记
  • 使用npm/pnpm自身安装指定版本的pnpm
  • 【论文笔记】Multi-Agent Based Character Simulation for Story Writing
  • Vue3从入门到精通:5.2 Vue3构建工具与性能优化深度解析
  • 同创物流学习记录2·电车光电
  • 【八股】计网-计算机网络-秋招
  • Windows MCP.Net:革命性的 .NET Windows 桌面自动化 MCP 服务器
  • 分享一个大数据的源码实现 基于Hadoop的二手车市场数据分析与可视化 基于Spark的懂车帝二手车交易数据可视化分析系统
  • ABB焊接机器人弧焊省气
  • ubuntu远程桌面很卡怎么解决?
  • 深入剖析跳表:高效搜索的动态数据结构
  • JavaScript 逻辑运算符与实战案例:从原理到落地
  • 杂记 02
  • Docker安装——配置国内docker镜像源
  • Python从入门到高手9.3节: 利用字典进行格式化
  • std::copy_if
  • 告别手动优化!React Compiler 自动记忆化技术深度解析
  • 47.分布式事务理论
  • 【大模型微调系列-03】 大模型数学基础直观入门
  • PyInstaller打包Python应用操作备忘
  • 后端学习资料 持续更新中
  • PCA降维理论详解
  • 哈希表五大经典应用场景解析
  • 电脑开机几秒后就停止然后再循环是怎么回事
  • 如何在 FastAPI 中玩转 APScheduler,让任务定时自动执行?