什么是 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
了。
版权声明:除文章开头有特殊声明的情况外,所有文章均可在遵从 CC BY 4.0 协议的情况下转载。