小白写第一个Webpack Plugin的曲折过程(听懂的都掉小珍珠了)

背景

一个monorepo项目,里面有子项目a和子项目sdk,a项目依赖于sdk(有代码引用),我们发现,当使用alias的时候,不仅要在tsconfig里的compilerOption的path配置,还要在webpack的resolve里面的alias配置,显然是非常的麻烦,于是我们打算复现这个最小demo,并写一个webpack插件,让代码在运行的时候,自动识别tsconfig里的alias,并注入到webpackconfig里面,这样我们就可以只管理tsconfig,大大节约了开发精力,减少出错概率

复现过程

技术选型 pnpm:

monorepo,我们肯定是首选pnpm,在 monorepo 中,pnpm提供了更好的开发支持,如TurborepoLerna 等工具原生支持 pnpm workspaces,无需额外适配;使用 workspace: 协议直接引用 Monorepo 中的其他包,开发过程中本地实时同步,无需发布到 npm。

步骤

首先我们构建一个如下图结构的项目,我们一步步来

先创建这样一个框架:

  1. a-project的tsconfig:

//这里重点使要配置依赖项

  1. sdk的tsconfig:

然后就是在sdk导出点小东西让a引入,创建好插件需要的背景:

sdk/src/index.ts:

project-a/src/index.ts:

配置好project-a的webpack.config.js:

曲折的路程

环境搭建好了接下来就是酸爽的写plugin环节了

第一版:

当时的思路就是,把tsconfig的alias里的key和value的/*去掉,然后拼接起来注入到webpack的alias里

相信有经验的小伙伴一看就知道问题在哪,就是beforRun实在是太晚了,webpack已经读取了选项并且进入构建流程了,这时候在读取tsconfig并注入webpack optopn无异于先冲水后拉*【手动狗头】。当然,这是我后面解决了问题的视角,当时我并没有怀疑到钩子头上,因为当时我在a-project项目下运行webpack并没有问题,只是webpack serve的时候会报错:

思考问题:

为什么webpack输出到dist就行,webpack serve就不行呢?我十分费解,其实一开始我就想到了,可能是钩子挂错了,但是webpack输出到dist又没问题,只是webpack serve报错,按理来说如果钩子挂错了,dist也不能正常输出才对,想了一晚上没得到答案,我决定启动最终大招:cosole.log(打这么多666是为了方便查找)

发现alias为空对象,好家伙,为什么alias为空webpack还能正常打包,他怎么识别得到@/sdk/index的?它长脑子了?简直是灵异事件。

解决问题

想了很久,最后实在没办法,启动了终极大招中的终极大招---GPT: 我把bundle放进GPT让他帮我看一下,alias为空,webpack是怎么识别成功@/sdk/index并且输出dist的,这种事情实在是太诡异了,没想到居然遇到了代码错了却能跑的事情。最后GPT给出了答案:

我直呼666,好你个eval,我都没允许,自己给自己加戏,搞得我想了一晚上,下面我们来解析eval的机制,看看它是怎么骗我的

eval() 模式的模块解析机制

(1) 记录模块的路径

Webpack 在构建时会解析每个模块的路径,记录在内部的模块表(module graph)中。例如:

  • @sdk/index : 如果 resolve.alias 配置正确,会解析为 ../sdk/src/index.ts。 如果 resolve.alias 为空,Webpack 会直接尝试解析文件系统路径。

Webpack 将这些路径直接写入 eval() 调用中,例如:

bash 复制代码
eval("\nvar sdk = __webpack_require__(/*! ../sdk/src/index.ts */ "../sdk/src/index.ts");\n");

(2) 如何解析路径

即使 alias 配置为空,Webpack 仍然能够通过以下规则解析到正确路径:

  1. 文件系统路径解析

    • 如果路径是 ../sdk/src/index.ts(文件系统路径),Webpack 直接查找目标文件。
    • eval() 模式将路径硬编码进模块中,所以即使没有 alias,也能解析成功。
  2. 模块路径回退 : 如果 @sdk/index 这种路径未找到,Webpack 会尝试回退到文件系统路径。例如:

    • 查找 ../sdk/src/index.ts
    • 如果找不到,再尝试 node_modules 或其他目录。

(3) eval 模式下的动态加载

  • eval() 模式下,Webpack 打包文件中会包含模块的完整路径。
  • 在运行时,eval() 会使用这些路径动态加载模块。

eval解析步骤步骤

也就是说eval模式使得即使路径解释不成功,也会全局去查找文件 步骤是这样的:

Step 1:检查 resolve.alias 配置

  • 解析路径: @sdk/index 应被替换为:

    makefile 复制代码
    C:\Users\30984\Desktop\monorepo-project\packages\sdk\src\index.ts
  • 结果: 如果 alias 生效,Webpack 会直接找到 C:\Users\30984\Desktop\monorepo-project\packages\sdk\src\index.ts 并解析成功。

  • 为什么会失败? 如果 alias 配置未生效(例如插件在 webpack-dev-server 中未正确触发,在这里我是钩子挂错了),Webpack 会跳过 alias 解析。


Step 2:检查相对路径或绝对路径

如果 alias 不匹配,Webpack 会尝试解析文件系统路径。

  1. 解析为相对路径: Webpack 认为 @sdk/index 是一个模块名,而不是文件路径。

    • 它会从当前文件的目录 C:\Users\30984\Desktop\monorepo-project\packages\project-a\src 出发,尝试以下路径:

      perl 复制代码
      ./@sdk/index
      ../@sdk/index
      ../../@sdk/index

    结果: 找不到模块,继续下一步。

  2. 解析为绝对路径: 如果 alias 不存在且相对路径解析失败,Webpack 可能尝试从文件系统根目录解析:

    perl 复制代码
    C:\@sdk\index

    结果: 仍然找不到。


Step 3:检查 node_modules

如果文件系统路径解析失败,Webpack 会尝试在 node_modules 中查找。

  1. 模块搜索路径 : Webpack 会依次在以下目录中查找 @sdk

    java 复制代码
    C:\Users\30984\Desktop\monorepo-project\packages\project-a\node_modules\@sdk
    C:\Users\30984\Desktop\monorepo-project\node_modules\@sdk
    C:\Users\node_modules\@sdk
  2. 结果: 如果 node_modules 中不存在 @sdk,Webpack 报错。


Step 4:回退到文件系统路径解析

即使 alias 未生效,Webpack 的解析机制可能会直接找到文件系统中的模块。

  1. 动态路径硬编码(eval 模式特性)

    • Webpack 在 eval() 模式下,会记录模块的实际路径(例如 ../sdk/src/index.ts),并在构建时写入打包文件。

    • @sdk/index 被替换为:

      bash 复制代码
      ../sdk/src/index.ts
  2. 硬编码路径执行: 打包后的代码:

    bash 复制代码
    eval("var sdk = __webpack_require__(/*! ../sdk/src/index.ts */ "../sdk/src/index.ts");");

    这里 ../sdk/src/index.ts 是文件系统中的相对路径,Webpack 已经将其解析为硬编码路径。

  3. 结果: Webpack 能通过文件系统找到 ../sdk/src/index.ts,即使 alias 不存在。

说人话环节

是不是很抽象?举个生活举例子,eval就像是个饥渴男大,工学院找不到女朋友就去管理学院找,再不行就去艺术学院,最后还真给他找到了。

总结说起来,就是eval模式下,webpack打包起来,除了万不得已都不会报错,你有点错误他就给你弥补了,但是运行webpack serve的时候是没有eval机制的,自然就报错了,机制就是我上面说的。

总结

最后发现是钩子问题,换成initialize就好了,过程十分的曲折,搞得我是十分烦躁,不过好在问题最后还是解决了,这是最终的插件:

在a-project 的webpack.config.js导入并使用就好了:

泪目了兄弟萌 项目地址:

github.com/PaiduiXiaow...

相关推荐
迷雾漫步者34 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css
GISer_Jing5 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试