UNPKG

koot

Version:

Koot.js - React isomorphic framework created by CMUX

354 lines (321 loc) 12.5 kB
#!/usr/bin/env node const fs = require('fs-extra') const path = require('path') const program = require('commander') const pm2 = require('pm2') const chalk = require('chalk') const npmRunScript = require('npm-run-script') const opn = require('opn') const checkFileUpdate = require('../libs/check-file-change') const contentWaiting = require('../defaults/content-waiting') const __ = require('../utils/translate') const sleep = require('../utils/sleep') const getPort = require('../utils/get-port') // const spinner = require('../utils/spinner') const readBuildConfigFile = require('../utils/read-build-config-file') const getAppType = require('../utils/get-app-type') const setEnvFromCommand = require('../utils/set-env-from-command') const getChunkmapPath = require('../utils/get-chunkmap-path') const initNodeEnv = require('../utils/init-node-env') const getCwd = require('../utils/get-cwd') const getPathnameDevServerStart = require('../utils/get-pathname-dev-server-start') // const terminate = require('../utils/terminate') program .version(require('../package').version, '-v, --version') .usage('[options]') .option('-c, --client', 'Set STAGE to CLIENT') .option('-s, --server', 'Set STAGE to SERVER') .option('-g, --global', 'Connect to global PM2') .option('--stage <stage>', 'Set STAGE') .option('--config <config-file-path>', 'Set config file') .option('--type <project-type>', 'Set project type') .option('--no-open', 'Don\'t open browser automatically') .parse(process.argv) /** * 进入开发环境 * **************************************************************************** * 同构 (isomorphic) * 以 PM2 进程方式顺序执行以下流程 * 1. 启动 webpack-dev-server (STAGE: client) * 2. 启动 webpack (watch mode) (STAGE: server) * 3. 运行 /server/index.js * **************************************************************************** * 单页面应用 (SPA) * 强制设置 STAGE 为 client,并启动 webpack-dev-server * **************************************************************************** */ const run = async () => { // 清空 log process.stdout.write('\x1B[2J\x1B[0f') const { client, server, stage: _stage, config, type, global = false, open = true, } = program initNodeEnv() setEnvFromCommand({ config, type, }) let stage = _stage ? _stage : (client ? 'client' : (server ? 'server' : false)) // if (!stage) { // console.log( // chalk.redBright('× ') // + __('dev.missing_stage', { // example: 'koot-dev ' + chalk.green('--client'), // indent: ' ' // }) // ) // return // } // 读取项目信息 const appType = await getAppType() const cwd = getCwd() const packageInfo = await fs.readJson(path.resolve(cwd, 'package.json')) const { dist, port } = await readBuildConfigFile() const { name } = packageInfo // 如果为 SPA,强制设置 STAGE if (process.env.WEBPACK_BUILD_TYPE === 'spa') { process.env.WEBPACK_BUILD_STAGE = 'client' stage = 'client' } // 如果配置中存在 port,修改环境变量 if (typeof port !== 'undefined') process.env.SERVER_PORT = getPort(port, 'dev') // 如果设置了 stage,仅运行该 stage if (stage) { const cmd = `koot-build --stage ${stage} --env dev` const child = npmRunScript(cmd, {}) child.once('error', (error) => { console.trace(error) process.exit(1) }) child.once('exit', (/*exitCode*/) => { // console.trace('exit in', exitCode) // process.exit(exitCode) }) return } // 没有设置 STAGE,开始 PM2 进程 let waitingSpinner = false // spinner( // chalk.yellowBright('[koot/build] ') // + __('build.build_start', { // type: chalk.cyanBright(appType), // stage: chalk.green('client'), // env: chalk.green('dev'), // }) // ) const processes = [] const pathChunkmap = getChunkmapPath(dist) const pathServerJS = path.resolve(dist, 'server/index.js') const pathServerStartFlag = getPathnameDevServerStart() { // 在脚本进程关闭/结束时,同时关闭打开的 PM2 进程 process.stdin.resume() const removeListeners = () => { process.removeListener('exit', exitHandler) process.removeListener('SIGINT', exitHandler) process.removeListener('SIGUSR1', exitHandler) process.removeListener('SIGUSR2', exitHandler) process.removeListener('uncaughtException', exitHandler) } const exitHandler = async (/*options, err*/) => { if (Array.isArray(processes) && processes.length) { if (waitingSpinner) waitingSpinner.stop() await sleep(300) process.stdout.write('\x1B[2J\x1B[0f') console.log(chalk.redBright('Waiting for killing processes...')) for (let process of processes) { await new Promise((resolve, reject) => { // console.log(process) pm2.delete(process.name, (err, proc) => { // console.log('err', err) // console.log('proc', proc) if (Array.isArray(proc) && proc.every(p => p.status === 'stopped')) return resolve(proc) if (err) return reject(err) reject('stop failed') }) }) } pm2.disconnect() await sleep(300) // w.stop() try { // console.log(process.pid) removeListeners() console.log('Press CTRL+C again to exit.') // process.kill(process.pid) process.exit(1) } catch (e) { console.log(e) } } else { removeListeners() // 清空 log process.stdout.write('\x1B[2J\x1B[0f') console.log('Press CTRL+C again to exit.') process.exit(1) } } // do something when app is closing process.on('exit', exitHandler); // catches ctrl+c event process.on('SIGINT', exitHandler); // catches "kill pid" (for example: nodemon restart) process.on('SIGUSR1', exitHandler); process.on('SIGUSR2', exitHandler); // catches uncaught exceptions process.on('uncaughtException', exitHandler); } // 根据 stage 开启 PM2 进程 const start = (stage) => new Promise(async (resolve, reject) => { const pathLogOut = path.resolve(cwd, `logs/dev/${stage}.log`) const pathLogErr = path.resolve(cwd, `logs/dev/${stage}-error.log`) if (fs.existsSync(pathLogOut)) await fs.remove(pathLogOut) if (fs.existsSync(pathLogErr)) await fs.remove(pathLogErr) await fs.ensureFile(pathLogOut) await fs.ensureFile(pathLogErr) const config = { name: `dev-${stage}-${name}`, script: path.resolve(__dirname, './build.js'), args: `--stage ${stage} --env dev`, cwd: cwd, output: pathLogOut, error: pathLogErr, autorestart: false, } if (stage === 'run') { Object.assign(config, { script: pathServerJS, watch: [pathServerJS], watch_options: { usePolling: true, }, // autorestart: true, }) delete config.args // console.log(config) // await fs.writeJson( // path.resolve(__dirname, '../1.json'), // config, // { // spaces: 4 // } // ) } // console.log(config) // processes.push(config.name) pm2.start( config, (err, proc) => { // console.log(err) if (err) return reject(err) processes.push({ ...proc, name: config.name, }) // console.log(JSON.stringify(proc)) // fs.writeJsonSync( // path.resolve(__dirname, '../2.json'), // proc, // { // spaces: 4 // } // ) resolve(proc) } ) }) // 连接 PM2 // console.log('noDaemon', !global) pm2.connect(!global, async (err) => { if (err) { // console.error(err) process.exit(2) } console.log( ` ` + chalk.yellowBright('[koot/build] ') + __('build.build_start', { type: chalk.cyanBright(appType), stage: chalk.green('client'), env: chalk.green('dev'), }) ) // 清空 chunkmap 文件 await fs.ensureFile(pathChunkmap) await fs.writeFile(pathChunkmap, contentWaiting) // 清空 server 打包结果文件 await fs.ensureFile(pathServerJS) await fs.writeFile(pathServerJS, contentWaiting) // 清空服务器启动成功标识文件 await fs.ensureFile(pathServerStartFlag) await fs.writeFile(pathServerStartFlag, contentWaiting) // 启动 client webpack-dev-server await start('client') // 监视 chunkmap 文件,如果修改,进入下一步 await checkFileUpdate(pathChunkmap, contentWaiting) // waitingSpinner.succeed() console.log( chalk.green('√ ') + chalk.yellowBright('[koot/build] ') + __('build.build_complete', { type: chalk.cyanBright(appType), stage: chalk.green('client'), env: chalk.green('dev'), }) ) // 启动 server webpack // waitingSpinner = spinner( // chalk.yellowBright('[koot/build] ') // + __('build.build_start', { // type: chalk.cyanBright(appType), // stage: chalk.green('server'), // env: chalk.green('dev'), // }) // ) console.log( ` ` + chalk.yellowBright('[koot/build] ') + __('build.build_start', { type: chalk.cyanBright(appType), stage: chalk.green('server'), env: chalk.green('dev'), }) ) await start('server') // 监视 server.js 文件,如果修改,进入下一步 await checkFileUpdate(pathServerJS, contentWaiting) // waitingSpinner.succeed() // 执行 // waitingSpinner = spinner( // chalk.yellowBright('[koot/build] ') // + 'waiting...' // ) await start('run') // 监视服务器启动标识文件,如果修改,进入下一步 await checkFileUpdate(pathServerStartFlag, contentWaiting) await sleep(100) console.log( chalk.green('√ ') + chalk.yellowBright('[koot/build] ') + __('build.build_complete', { type: chalk.cyanBright(appType), stage: chalk.green('server'), env: chalk.green('dev'), }) ) // waitingSpinner.stop() // waitingSpinner = undefined npmRunScript(`pm2 logs`) if (open) return opn(`http://localhost:${process.env.SERVER_PORT}/`) }) } run()