背景
在《前端多个vue项目公共组件的三种方法(推荐npm file引入)》这一篇里讲了npm通过file方式引入公共包的方法,但在实际运用中,会遇到不少坑,这里就讲述笔者遇到的2个问题并给出解决方案。
问题一
通过file方式引入的包,npm不会自动安装该包的依赖。
例:项目A通过file方式引入了包B,如下所示。在
项目A的package.json:
{
"name": "A",
"version": "0.1.0",
"dependencies": {
"B": "file:../B"
}
//...
}
项目B的package.json:
{
"name": "B",
"version": "0.1.0",
"dependencies": {
"axios": "^0.19.2"
}
//...
}
如此在项目A中执行npm install时,不会安装axios包,node_modules中只有一个通过软链接引入的B。
解决方案
主要思路:
- 增加一个webpack插件,DepPlugin,在entryOption hook,也就是webpack刚处理完配置项之后,执行插件。
- 读取主项目package.json中dependencies中所有依赖,读取所有file引入npm包中dependencies中的依赖和版本号,两者取差集,也就是在file引入npm包中依赖的但主项目中没有的。
- 一次性安装上述所得的所有依赖,执行
npm install a@1.0 b@2.1 c@3.2 --no-save
(a b c为依赖包名)。
以下是代码:
vue.config.js
chainWebpack: config => {
const depPlugin = require('./plugin/DepPlugin')
config.plugin('DepPlugin').use(depPlugin).tap(args => args)
}
DepPlugin.js
'use strict'
const path = require('path')
const sep = path.sep
let cwd = process.cwd()
const execSync = require('child_process').execSync
function DepPlugin (options) {
this.options = options || {
}
this.done = false
}
DepPlugin.prototype.apply = function (compiler) {
const that = this
compiler.hooks.entryOption.tap('DepPlugin', compiler => {
if(that.done) return
const mainDependency = getDependency('.' + sep + 'package.json')
// 假设有file引入了2个项目,baqi和baqi-chat
let subDependency = {
...getSubDependency('baqi'),
...getSubDependency('baqi-chat')
}
const subDependencyKey = Object.keys(subDependency)
const mainDependencyKey = Object.keys(mainDependency)
// 获取当前包中的依赖
// 查找baqi,baqi-chat包中有的依赖,但主包中没有的
for (let i = 0; i < subDependencyKey.length; i++) {
if (mainDependencyKey.includes(subDependencyKey[i])) {
delete subDependency[subDependencyKey[i]]
}
}
// 安装这些依赖
execCmdSync('cd ' + cwd)
let str = ''
for (let dep in subDependency) {
// node_modules中没有这些包就安装
if (fs.existsSync(path.join(cwd, 'node_modules' + sep + dep)) === false) {
str += dep + '@' + subDependency[dep].replace(/\"|\^|\~/g, '') + ' '
}
}
if (str) {
execCmdSync('npm install ' + str + '--no-save')
}
})
}
function getSubDependency (moduleName) {
const filePath = path.join(cwd, '..' + sep + moduleName + sep + 'package.json')
const dependencies = getDependency(filePath)
return dependencies
}
function getDependency (packageJsonPath) {
const fileObj = JSON.parse(readPackageJson(packageJsonPath))
const dependency = fileObj.dependencies
return dependency
}
function readPackageJson (packageJsonPath) {
const fileStr = fs.readFileSync(packageJsonPath, 'utf8')
return fileStr
}
function execCmdSync (cmdStr) {
console.log('执行:' + cmdStr)
const out = execSync(cmdStr, {
encoding: 'utf8' })
return out
}
问题二
babel不会自动转义file引入的包,也就是子包中若有ES6写法,在低版本的浏览器中运行时会报错。
解决方案
vue官网这么描述的
所以,可以在vue.config.js中添加
transpileDependencies: [
/[/\\]node_modules[/\\]baqi[/\\]/,
/[/\\]node_modules[/\\]baqi-chat[/\\]/
]
此问题同样适用与npm link