不想看过程可以直接看最后的总结 :)
起因
前天的时候就遇到了一个非常棘手的问题,在高高兴兴写完 Vue 项目后,使用 npm link
在别的项目里导入自己的包报错(这里的变量都用 xxxx 或者 yyyy 来表示啦):
"export 'default' (imported as 'xxx') was not found in 'xxxx
但是如果我从 npm 上下载自己的包是不报错的,而使用 npm link
本地调试的时候就报上面的错。而且我还自己点进原文件看了下,npm 下载的和本地 npm link
的 xxx.umd.js
根本就是一毛一样啊,为什么两边的结果不一样呢?
谷歌了一波后发现别人也有有同样的问题:
下面他就说了两边出现不同结果的问题:
尤大也回复了,但是他说这不是 vue-cli 的问题,是 webpack 自己没有将文件转成 ES6 module 的形式。真的是看的我一脸问号。
为什么报错
没办法,他不说解决方法,我自己总得解决吧。首先将谷歌一下报错信息,得到的解答是: 同时使用了 import
和 module.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,而本地引入的话没有预先编译。
后面我做了如下测试:
- 将 /dist 下所有文件拷到新项目的 /src 里,直接本地引入,同样报错。
- 将 /dist 下所有文件拷贝到新项目的 /node_modules 里,直接本地使用
import '/node_modules/xxx/xxx.umd.js'
引入,成功! - 在新项目使用
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 下,然后在那个项目引入就可以了。