重识 alias —— npm包开发的神器

本文基于 vite 和 pnpm workspace 的 monorepo 架构跟大家一起重识 alias 的原理和用法!(ps:为方便大家自己研究下案例代码的执行,完整代码已经上传 github,有需要的朋友可以自行 clone 下来玩玩。

前言

说起 alias 大家肯定不陌生,毕竟几乎在任何一个现代前端工程的项目中都有它的影子。常用的打包工具 webpack 和 vite 都有 alias 的配置,并且当我们使用脚手架生成一些基础项目的时候,大家都会默契地把 @ 通过 alias 配置指向了工程中的 src 目录。

比如这样,我们配置了 @ 指向当前目录下的 src:

js 复制代码
alias:[
  {
    find: '@',
    replacement: path.resolve('src'),
  },
]

我们在应用中便可以通过 @/xxx 来便捷的导入 src 下的任何模块。比如一个文件中的 import 如下:

当配置了 alias 后,我们可以改写成 import '@/App.css' 这样写法。从某种程度上来说,alias 别名代替一些相对、绝对路径的写法,在开发阶段给我们提供便利。

我举一个很常见的例子,我们开发过程中经常会因为一些调整来变更文件的位置 ,那此时如果我使用相对路径来写模块引用的话,当我的文件位置变更的时候,往往需要同步修改我的模块导入路径。比如当我的文件提升了一个层级的时候,我的相对路径 import '../App.css' 写法可能就要改成 import './App.css' 这样了。但是如果我是通过 '@/App.css' 的方式来引用,只要 App.css 的位置不变,导入其的文件无论换到哪里都不需要变更这个导入的路径。

好吧,相信这个用法大家都很熟悉了!但不知道大家有没有了解过它背后是怎么运作的呢?我们接着往下看。

alias 原理

我们看 vite 文档 对其的介绍可知,它的底层实现是一个 rollup 插件:@rollup/plugin-alias。当我们点进去这个插件的 源码 大概看了看,有写过 vite 或者 rollup 插件的同学可能就发现了两个熟悉的钩子:

buildStartresolveId。这两个钩子我们在 rollup 的插件文档中可以直接看到:

当然我们也同样在 vite 的插件文档中找到相关钩子的说明:

只要我们简单看看 @rollup/plugin-alias 的源码,大概就能猜到 alias 的底层实现原理了,如下图:

dev 和 build

基于上述的一些文档说明和源码阅读,我们大概知道了 alias 的实现原理是基于两点:

  1. 别名规则的匹配
  2. 路径替换

基于此我们可以简单拓展下,大家都知道 vite 的 dev mode 下是不打包文件的,通过直接启动开发服务器并借助浏览器原生支持的 import 能力进行模块的按需导入。那我们大概可以猜测到 dev 和 build 模式下的 alias 表象是有区别的。

比如我有一个 AliasTest 的组件通过 @ 的方式在入口组件中导入:

js 复制代码
const AliasTest = lazy(() => import('@/components/alias-test'));

当我运行 dev 的时候在浏览器下可以看到对应的路径截图如下:

有经验的都知道,此时我们的源文件的导入路径是不会变化的(也就是说不修改源文件),所以我们可以认为这是 dev 服务器的实现。紧接着我们对这个 react-demo 的单页应用进行打包,再看看 @ 这个别名的变化情况。(我特意使用了 lazy 引入,就为了分包出来看得清晰点)

产物结果如下,此时的 AliasTest 组件被打包成了 index-BsXGEvj6.js 文件:

此时我们再回到入口文件中看看我们源代码写的 '@/components/alias-test' 会变成什么?

确实已经正确转换为指向打包后的文件的相对路径了(当然源文件也随之被修改)。

巧妙的用途

除了我们项目中常见的通过配置 alias 实现 @ 指向 src 目录,这个配置还有没有其他妙用呢?答案是有的,比如我们常做 npm 包开发的同学应该就会用到。

这里我还是回到同一个 react 的 monorepo 工程中进行讲解。比如我要开发一个 npm 包:react-bundle,并且我通过 react-demo 这个单页应用的项目作为代码调试的工程,它提供一个 vite dev 的环境能让我们很方便的开发调试 react-bundle 的 js包:

回顾上一篇文章------从 Monorepo 重温 ESM 的模块化机制中提到的模块解析规则,package.json 中的入口字段决定着 node 的模块解析如何找到最终对应的文件,所以当我们需要在 react-demo 中启动 dev 能调试到 react-bundle 时,我们必须要正确地安装好依赖并且 react-bundle 的入口配置正确。

此时我们看看 react-bundle 中的 package.json 配置是怎么指向入口的:

json 复制代码
"exports": {
  ".": {
    "import": "./dist/index.js",
    "types": "./dist/react-bundle/index.d.ts"
  }
},

它的入口配置为 dist/index.js,也就是说我们真正在 react-demo 中调试的文件是一个打包后的文件:

那这样会有一个怎么样的问题?是不是意味着我们每在 react-bundle 中该一行代码,都需要重新 build 一次才能生效。这种情况对于 npm 包的开发者来说是不能容忍的,因为效率真的太低了。并且我们可能会因为忘记 build 了而发现自己的改动迟迟没有生效从而浪费时间去排查。那此时怎么办呢?

聪明的朋友一定想到了,那直接改掉 package.json 中的入口指向不就好了吗?比如我修改了一下 react-bundle 的 pkj 的入口指向。比如这里我把指向从 ./dist/index.js 改为了项目源码的入口文件: ./index.ts

json 复制代码
"exports": {
  ".": {
    "import": "./index.ts",
    "types": "./dist/react-bundle/index.d.ts"
  }
},

此时在 react-bundle 的每一行修改都实时反馈到 react-demo 的 dev 调试 web 中了,让我们这些 npm 包开发者感受到了无比的愉悦。比如我修改了一下 react-bundle 中的代码:

实时调试效果:

看似一切美好的解决方案,但当我们要把 npm包 发布到 npm 仓库并被用户安装到自己的项目时,问题就出现了。这里大家可以自己想一想问题是什么?但是如果说你的 npm 包本意就是提供源码文件给其他用户使用,那这样确实没毛病。

大多数情况下,npm 包的开发者都会提供"开箱即用"的代码包,他不需要要求用户的项目装什么 ts,用什么打包,反正他就提供一个 js 文件,只要是运行在有 js 引擎的环境中都能正常运行的产物。另外随着前端工程的不断发展,各种 npm包还会提供各式各样的产物模式,如 umd、iife、es module 规范、commonjs 规范等产物。

所以从大部分场景看,我们开发 npm包的 package.json 入口一般都是指向打包后的入口文件的,如果说我们开发的时候改了对应的入口指向,保不齐我们发包的时候没有改回来就会导致用这个 npm 包的同学出问题。当然有同学说可以多搞几个 package.json 不就好了?但此时我想说的是,我们可以配置 alias 来解决这个问题

比如此时我们的 package.json 的指向依然是打包后的地址,但是我们给调试用的应用配置了 alias 指向我们的 react-bundle 的源码文件。此时我们依然可以实时调试我们的 react-bundle,但是又不用担心发包后的入口配置不对从而引用其他开发者了。

于是乎,alias 在这里的妙用可以极大地降低我们 npm 包开发时的心智负担,我们可以配置好 alias 并借助 monorepo 的代码组织方式很丝滑地实时开发调试我们的 npm 包,又不用担心会影响到使用该 npm包 的用户们。

总结

配置别名 alias 的场景不仅仅是我们每个单页应用中的 src 映射,还可以应用到 npm 包的开发调试场景。当然,alias 的玩法肯定不局限在这两种场景,本文更多是借 npm包 开发调试妙用 alias 的引子让大家更多地 get 到这个配置的作用。相信本文可以拓宽我们对 alias 的理解,并拓宽我们在一些业务场景中解决问题的解题思路!

相关推荐
Mintopia7 小时前
🤖 AIGC在Web教育场景中的自适应学习技术设计
前端·javascript·aigc
Mintopia8 小时前
⚙️ Next.js 多环境部署全攻略
前端·javascript·全栈
cngm1108 小时前
若依分离版前端部署在tomcat刷新404的问题解决方法
java·前端·tomcat
摸鱼的春哥8 小时前
组合为啥比继承更高级?以构建buff系统为例
前端·javascript·后端
江城开朗的豌豆8 小时前
让TS函数"说到做到":返回值类型约束的实战心得
前端·javascript
晓得迷路了8 小时前
栗子前端技术周刊第 104 期 - Rspack 1.6、Turborepo 2.6、Chrome 142...
前端·javascript·chrome
亿元程序员8 小时前
Cocos安卓小游戏如何快速接入快手联盟变现?
前端
江城开朗的豌豆8 小时前
TS泛型:让类型也学会“套娃”,但这次很优雅
前端·javascript
西洼工作室8 小时前
vue2+vuex登录功能
前端·javascript·vue.js