The original link

The project architecture

The company’s projects are divided into the following structures:

  1. Main project – Extension Project: The front end of the extension project is independent, which is installed into the main project in the form of NPM package. The back end can be compiled independently, but cannot run independently. That is, the back end and the main project share the same set
  2. Main project – Sub-project: micro-front-end architecture, sub-project currently and back-end can run independently

Demand background

Need to solve the problem that the back end of the main project cannot be incrementally compiled under the main project-extension project architecture

Technology selection

The selection advantages disadvantages
Develop directly in the node_modules directory The main project has implemented listening for extension projects under node_modules Each time NPM Install creates a new project
Gulp file synchronization There are third-party plug-ins You need to run one more file synchronization script
npm link You don’t need to run another script You need to manually restart the main project each time for the back-end code to take effect
nodemon + npm link Automatic restart when listening for extension project changes NPM link is re-installed each time NPM install

To solve the process

Gulp file synchronization

Core logic: listen to the back-end code compiled by the extension project and synchronize any changes to the extension project location under node_modules

./config.js

module.exports = {
  // Synchronize file changes to the specified directory
  syncTo: '.. / main project /node_modules/ extension project /extend'
}
Copy the code

./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'])
Copy the code

nodemon + npm link

  1. Do the NPM link operation on the extension project, and then in the main project NPM Link extension project, you can see the soft link pointing to the extension project under the main project node_modules
  2. The main project uses Nodemon to monitor the changes of the node_modules file. Considering that the code of the extension project will take effect only after restarting the main project every time, the problem that Nodemon cannot monitor the changes of the files under the soft link needs to be solved

Core logic:

  1. It can be added from nodemon.json without affecting the invocation logic of the Nodemon scriptwatchSymbolLinkProperty to specify the file location to listen for soft links
  2. Use gloB tripartite library to read the file under watchSymbolLink. When deciding that the file read is a soft link (isSymbolicLink), read the real file path (realpathSync).
  3. On the basis of listening for the original file, call Nodemon to listen for another real file path

package.json

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

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/**/*"
/ /},
/ /,
/ / or
// "watchSymbolLink": [
// "node_modules/sugo-analytics-extend-*",
/ /,
const{ configFile, ... optionsRest } = optionsconst nodemonJsonPath = path.join(process.cwd(), configFile)

const nodemonJson = require(nodemonJsonPath)

const{ watch, watchSymbolLink = [], ... nodemonJsonRest } = nodemonJsonconst 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) })constopts = { ... 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()
}
Copy the code

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"
}
Copy the code