什么是 npm link
可能很多同学没有接触过这个命令,就先提一下吧。NPM 文档在 这里。
假设我们开发了一个 NPM 的包 a,然后我们的项目使用了这个包,那么正常情况下,每次 a 有变动,我们都要更新到项目中。npm 的 link 命令可以解决这个问题,步骤如下:
- 进入
a的根目录,执行npm link,这会创建一个软链接$PREFIX/lib/node_modules/a,指向a的根目录,也会将包里的bin指定的文件放到$PREFIX/lib/bin下; - 进入项目的根目录,执行
npm link a,这会在项目的node_modules中创建一个软链接a,指向$PREFIX/lib/node_modules/a。
需要注意三点:
$PREFIX是npm config get prefix命令输出的目录;- 由于这个目录很可能需要管理员权限来访问,所以你可能需要
sudo; yarn命令也有link的功能,用法完全相同,以及yarn的目录在我本机是不需要管理员权限的(其它环境我没试过)。
这样,只要修改了 a 目录下的东西,项目中引用的代码也会及时更新。
问题的起因
我们维护了两个库和一个项目,分别是(由于是公司项目,所以名字用字母来替代):
- 库
a:样式库 - 库
b:基于element-ui的一个方便快速编写“中后台管理系统”前端的工具库 - 项目
c:基于b的一个后台管理系统前端
两个库都还没有成型,经常会有一些修改,因此为了开发的方便,我们便使用 npm link 将 a 和 b 引用到了项目 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 中编写了样例项目,经测试这个方法是可行的,然而在项目 c 中 element-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 了。