女王控的博客

基于nodemon实现监听文件链接变化

项目架构

公司项目分为以下几种架构:

  1. 主项目-扩展项目:扩展项目前端独立,是以 npm 包的形式安装到主项目,后端可以独立编译,但不能独立运行,即后端与主项目共用一套,主项目需要对扩展项目编译出的后端代码进行监听来实现增量编译
  2. 主项目-子项目:微前端架构,子项目前、后端独立可运行

需求背景

需要解决主项目-扩展项目架构下主项目的后端不能增量编译的问题

技术选型

选型 优点 缺点
直接在 node_modules 目录下开发 主项目已实现对 node_modules 下扩展项目的监听 每次 npm install 都要重新新建项目
gulp 文件同步 有第三方插件 需要多运行一个文件同步脚本
npm link 不需要多运行一个脚本 需要每次手动重启主项目来让后端代码生效
nodemon + npm link 监听到扩展项目变化自动重启 每次 npm install 都要重新 npm link

解决过程

gulp 文件同步

核心逻辑:监听扩展项目编译出来的后端代码,发现改动时同步到 node_modules 下扩展项目的位置

./config.js

module.exports = {
  //同步文件更改到指定目录
  syncTo: '../主项目/node_modules/扩展项目/extend'
}

./gulpfile.js

const gulp = require('gulp')
const fileSync = require('gulp-file-sync')
const {syncTo} = require('./config')
const mkdirp = require('mkdirp')
const fs = require('fs')
const watch = require('gulp-watch')
const run = require('run-sequence')

gulp.task('sync', function() {
  try {
    let state = fs.statSync(syncTo)
    if (!state.isDirectory()) {
      mkdirp(syncTo)
    }
  } catch (e) {
    console.log(e)
    mkdirp(syncTo)
  }

  fileSync('extend', syncTo)
})

gulp.task('watch-extend', function() {
  watch([
    './extend/**',
  ], function() {
    run('sync')
  })
})

gulp.task('watch', ['watch-extend'])

gulp.task('default', ['sync', 'watch'])
  1. 对扩展项目进行 npm link 操作,然后在主项目中 npm link 扩展项目,可以在主项目的 node_modules 下看到指向扩展项目的软链接
  2. 主项目使用了 nodemon 来监听 node_modules 文件变化,鉴于每次重启主项目之后扩展项目的代码才会生效的问题,需要解决 nodemon 不能监听软链接下文件变化的问题

核心逻辑:

  1. 在不影响 nodemon 脚本调用逻辑的情况下,在 nodemon.json 添加 watchSymbolLink 属性,明确监听软链接的文件位置
  2. 利用 glob 三方库读取 watchSymbolLink 下的文件,当判断读到的文件是软链接文件时(isSymbolicLink),读取真实的文件路径(realpathSync)
  3. 在监听原来文件的基础上,调用 nodemon 监听另外真实的文件路径

package.json

{
  "scripts": {
    "dev:server": "node bin/nodemon.js --config nodemon.json ./bin/www",
  },
}

bin\nodemon.js

const nodemon = require('nodemon')
const nodemonCli = require('nodemon/lib/cli')
const options = nodemonCli.parse(process.argv)
const glob = require('glob')
const fs = require('fs')
const lodash = require('lodash')
const path = require('path')

// "watchSymbolLink": [
//   {
//     "url": "node_modules/sugo-analytics-extend-*",
//     "path": "/extend/app/**/*"
//   },
// ],
// 或
// "watchSymbolLink": [
//    "node_modules/sugo-analytics-extend-*",
// ],
const { configFile, ...optionsRest } = options

const nodemonJsonPath = path.join(process.cwd(), configFile)

const nodemonJson = require(nodemonJsonPath)

const { watch, watchSymbolLink = [], ...nodemonJsonRest } = nodemonJson

const dirs = []

watchSymbolLink.forEach(item => {
  const { url, path: linkPath = '' } = lodash.isObject(item) ? item : { url: item }
  const paths = glob
    .sync(url)
    .filter(a => fs.lstatSync(a).isSymbolicLink())
    .map(a => path.join(fs.realpathSync(a), linkPath))
  dirs.push(...paths)
})

const opts = {
  ...optionsRest,
  ...nodemonJsonRest,
  watch: [...watch, ...dirs]
}

// console.log(opts)

nodemon(opts)

nodemon.on('quit', function () {
  process.exit()
})

const packageJsonPath = path.join(process.cwd(), 'package.json')

// checks for available update and returns an instance
const pkg = JSON.parse(fs.readFileSync(packageJsonPath))

if (pkg.version.indexOf('0.0.0') !== 0 && options.noUpdateNotifier !== true) {
  require('update-notifier')({ pkg }).notify()
}

nodemon.json

{
  "restartable": "rs",
  "ignore": [".git", "node_modules", "node_modules/**/node_modules"],
  "verbose": true,
  "execMap": {
    "js": "node --harmony"
  },
  "events": {
    "restart": "osascript -e 'display notification \"App restarted due to:\n'$FILENAME'\" with title \"nodemon\"'"
  },
  "watch": ["src/server", "src/common", "config.default.js", "node_modules/sugo-analytics-extend-*/extend/app/**/*", "config.js"],
  "watchSymbolLink": [
    {
      "url": "node_modules/sugo-analytics-extend-*",
      "path": "extend/app/**/*"
    }
  ],
  "env": {
    "NODE_ENV": "development"
  },
  "delay": "1000",
  "ext": "js,json"
}

评论

阅读上一篇

Sequelize一对多查询解决方案
2021-01-05 13:55:57

阅读下一篇

Promise 实现原理
2020-12-25 10:59:33
0%