R·ex / Zeng


音游狗、安全狗、攻城狮、业余设计师、段子手、苦学日语的少年。 MUGer, hacker, developer, amateur UI designer, punster, Japanese learner.

由 npm link 引发的一个坑

注意:本文发布于 878 天前,文章中的一些内容可能已经过时。

什么是 npm link

可能很多同学没有接触过这个命令,就先提一下吧。NPM 文档在 这里

假设我们开发了一个 NPM 的包 a,然后我们的项目使用了这个包,那么正常情况下,每次 a 有变动,我们都要更新到项目中。npmlink 命令可以解决这个问题,步骤如下:

  1. 进入 a 的根目录,执行 npm link,这会创建一个软链接 $PREFIX/lib/node_modules/a,指向 a 的根目录,也会将包里的 bin 指定的文件放到 $PREFIX/lib/bin 下;
  2. 进入项目的根目录,执行 npm link a,这会在项目的 node_modules 中创建一个软链接 a,指向 $PREFIX/lib/node_modules/a

需要注意三点:

  1. $PREFIXnpm config get prefix 命令输出的目录;
  2. 由于这个目录很可能需要管理员权限来访问,所以你可能需要 sudo
  3. yarn 命令也有 link 的功能,用法完全相同,以及 yarn 的目录在我本机是不需要管理员权限的(其它环境我没试过)。

这样,只要修改了 a 目录下的东西,项目中引用的代码也会及时更新。

问题的起因

我们维护了两个库和一个项目,分别是(由于是公司项目,所以名字用字母来替代):

  • a:样式库
  • b:基于 element-ui 的一个方便快速编写“中后台管理系统”前端的工具库
  • 项目 c:基于 b 的一个后台管理系统前端

两个库都还没有成型,经常会有一些修改,因此为了开发的方便,我们便使用 npm linkab 引用到了项目 c 中。

由于公司的业务涉及东南亚的多个国家,因此需要支持多语言切换。

多语言切换的实现

自己的业务代码倒是好说,但是 element-ui 就比较难办了,官方给出的切换方式是这样的:

// 完整引入
import ElementUI from 'element-ui';
Vue.use(ElementUI, { locale });

// 或者按需加载
import lang from 'element-ui/lib/locale/lang/en';
import locale from 'element-ui/lib/locale';
locale.use(lang);

但这样都是一次性的引入,无法做到实时切换语言。一个简单的方法是,自己写一个切换语言的函数,把当前语言存到 localStorage 之类的地方,然后刷新,读取刚才存的值,引入语言包。

作为一个单页面应用,刷新什么的……是最后才考虑的事情。于是我去翻 element-ui 的源代码,发现它实现国际化的原理是在 这个文件 中存了一个“全局”变量 lang(当然,只有文件内可见),然后每次渲染所选取的语言由该变量决定。于是有了一个大胆的想法:使用 locale.use 来设置语言,然后更新整个页面组件。这并不困难,只需要将 App 组件隐藏,然后在 $nextTick 中显示即可。

不过这在我们的项目里是不行的。如果你知道我们的项目是完整引入,就不难推断出原因:Webpack 打包时会引入 node_modules/element-ui/lib/element-ui.common.js 形成一个闭包,然而引入 locale 时,又打包了 node_modules/element-ui/lib/locale 形成了另一个闭包。lang 变量是存在后者中的,组件渲染却用的是前者的函数,这当然是不行的。

继续翻代码,在 这个文件 发现了一处暴露的接口,于是可以这么写了:

import { locale } from 'element-ui';

function setLocale(localeKey) {
    // 设置好业务所需的语言
    // ....
    // 获取 element-ui 对应的语言包
    const l = getElementLocaleByKey(localeKey);
    // 设置 element-ui 的语言
    locale(l);
    // 通知 App 组件刷新
    // ....
}

这样,引入的库文件就都是 node_modules/element-ui/lib/element-ui.common.js 了,Webpack 对于相同的文件只会形成一个闭包,应该没什么问题了吧?

问题出现

我们在库 b 中编写了样例项目,经测试这个方法是可行的,然而在项目 celement-ui 无论如何都无法切换语言,我们的业务代码切换的倒是很顺畅。

追代码追了好久,发现该执行的代码都执行到了,但就是不管用,后来突然脑洞一开,难不成还是跟刚才一样,因为切换语言和渲染用的不是同一个 element-ui

顺着这个思路想了想,确实很有道理:设置语言的函数是写在 b 中的,由于我们使用了 npm link,其原理是建立软链接,所以包含的是 b/node_modules/element-ui/lib/element-ui.common.js,然而渲染组件的代码却包含在 c/node_modules/element-ui/lib/element-ui.common.js 中!

解决方法

暂时没有。由于 element-ui 将当前语言存到了 Webpack 打包后形成的闭包内,只要使用了 npm link,就一定会出现两个不同的闭包,就一定无法切换语言。

值得注意的是,如果 npm 版本大于 5,并且没有用 npm link 引入,而是把包发布到了线上或者从 Git 直接安装,是不会有这个问题的,因为高版本的 npm 会将所有的依赖拍平,统一放到项目的 node_modules 文件夹中,这样就始终只有一个 element-ui 了。

版权声明:除文章开头有特殊声明的情况外,所有文章均可在遵从 CC BY 4.0 协议的情况下转载。
上一篇: 为了用户体验而做的事情
下一篇: 关于一个同学的事情

这是我们共同度过的

第 1763 天