小白写第一个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...

相关推荐
天天打码11 分钟前
Rspack:字节跳动自研 Web 构建工具-基于 Rust打造高性能前端工具链
开发语言·前端·javascript·rust·开源
AA-代码批发V哥13 分钟前
正则表达式: 从基础到进阶的语法指南
java·开发语言·javascript·python·正则表达式
字节高级特工16 分钟前
【C++】”如虎添翼“:模板初阶
java·c语言·前端·javascript·c++·学习·算法
小冻梨!!!40 分钟前
Spark,在shell中运行RDD程序
大数据·javascript·spark
大猫会长1 小时前
lenis滑动插件的笔记
javascript
db_lnn_20211 小时前
【vue】全局组件及组件模块抽离
前端·javascript·vue.js
Qin_jiangshan1 小时前
vue实现进度条带指针
前端·javascript·vue.js
天高任鸟飞dyz1 小时前
tabs切换#
javascript·vue.js·elementui
菜鸟una1 小时前
【layout组件 与 路由镶嵌】vue3 后台管理系统
前端·vue.js·elementui·typescript
小张快跑。1 小时前
【Vue3】使用vite创建Vue3工程、Vue3基本语法讲解
前端·前端框架·vue3·vite