记一次错:Vue 构建项目后使用 npm link 失败

不想看过程可以直接看最后的总结 :)

起因

前天的时候就遇到了一个非常棘手的问题,在高高兴兴写完 Vue 项目后,使用 npm link 在别的项目里导入自己的包报错(这里的变量都用 xxxx 或者 yyyy 来表示啦):

"export 'default' (imported as 'xxx') was not found in 'xxxx

但是如果我从 npm 上下载自己的包是不报错的,而使用 npm link 本地调试的时候就报上面的错。而且我还自己点进原文件看了下,npm 下载的和本地 npm linkxxx.umd.js 根本就是一毛一样啊,为什么两边的结果不一样呢?

谷歌了一波后发现别人也有有同样的问题

下面他就说了两边出现不同结果的问题:

尤大也回复了,但是他说这不是 vue-cli 的问题,是 webpack 自己没有将文件转成 ES6 module 的形式。真的是看的我一脸问号。

为什么报错

没办法,他不说解决方法,我自己总得解决吧。首先将谷歌一下报错信息,得到的解答是: 同时使用了 importmodule.exports 语法,所以才报错。什么?这怎么可能,两边的项目肯定都没这样用过。除非是。。。。。我引入的那个打包后的文件 xxx.umd.js 是有 module.exports 语句的。

难道真的是 Webpack 没有转成 ES6 的问题?

坑:官方文档

第一反应是官方文档应该有教怎么打包项目的,里面说不定有答案,所以回去认真读了下文档,可是:

woc,Webpack 真的不能转成 ES6 Module 的啊。所以现在问题变成了:Webpack 将原来的项目打包成 ES5 的版本,然后在新项目里用 import 里引入了带有 module.exports 语句的文件,从而导致上面的报错。

坑:Vue CookBook

这时我注意到上面出现同样问题的大哥说到可以用 Rollup.js 来打包,这就可以打包出 ES6 语法的 JS了。马上开搞,然后查到了 Vue CookBook:

果然可以打包成 xxxx.esm.js 的方法,但是原来的项目是用 Webpack + Vue 来构建的,使用两套打包工具真的好吗?虽然不太好,我试了下,奈何在全局引入 .scss 文件的地方试了一万个方法都不行,只能放弃这个方法。从头开始分析。

Webpack 是真的坑

说实话那天我就放弃了,本地调试不行就算了吧,不就搞很多个 npm 版本么。后面还是 Jetbrains 给了我灵感。

今天我想删除某个文件的时候发现了这个选项:

Exclude?嗯。。。曾经的我就在 webpack.config.js 里看到过这个选项,好像说是可以不对某些文件进行编译,这样就能在 yarn run build 的时候提高性能。再结合一下前面分析的“没有转成 ES6 语法”的报错,好像有点说通了。非常有可能 Webpack 对从 npm 下载下来的文件进行预先编译,将其转成 ES6,而本地引入的话没有预先编译

后面我做了如下测试:

  1. 将 /dist 下所有文件拷到新项目的 /src 里,直接本地引入,同样报错。
  2. 将 /dist 下所有文件拷贝到新项目的 /node_modules 里,直接本地使用 import '/node_modules/xxx/xxx.umd.js' 引入,成功!
  3. 在新项目使用 npm link xxx 后,在 /node_modules 里用上面的 import '/node_modules/xxx/xxx.umd.js' 引入,失败。

再次分析

上面 1 和 2 的测试足以说明我的猜想是对的,Webpack 会对 /node_modules 下的文件进行预先编译,再引入到真实项目中,这样就没有 module.exports 的语句了,所以也就不会报错了。

但是为什么 3 也失败呢?我在命令行里输入 ls -a 也发现自己的 xxx 包呀,说明我的原来的项目包也在 /node_modules 下呀。后面想到 Mac 的 link 命令,npm link 说不定是创建软链接而已,所以用 Finder 打开新项目的 /node_modules ,果然这是个软链接:

而这个软链接指向的真实地址是本机的别的地址,也就是说这个包不在项目的 /node_modules 文件夹下。所以不会预先被编译,再次印证上面的猜想。

现在终于真相大白了。

总结

为什么报错

如果使用下载的 npm package,那么 Webpack 在项目引入前将代码编译成 ES6 模块语法,所以这时候不会报错。

如果使用 npm link 会将 npm 包放在本机的全局 /node_modules 下,新项目的 /node_modules 下只是一个软链接(快捷方式)。而不在新项目 /node_modules 下的文件都不会预先编译成 ES6 模块方法。在项目里引入也就等同于下面代码:

// B.js
module.exports = {
}

// A.js
import "B.js"

而这两种语法混合使用就会报错:

"export 'default' (imported as 'xxx') was not found in 'xxxx

解决方法

我简单搜索了一下没找到什么解决方法(真的不知道要怎么搜这种问题了)。所以现在最笨的方法就是每次 yarn run build 后将 /dist 目录拷到别的项目的 /node_modules 下,然后在那个项目引入就可以了。