背景

《前端多个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