UNPKG

12.2 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3const fs = require('fs-extra')
4const path = require('path')
5const program = require('commander')
6const pm2 = require('pm2')
7const chalk = require('chalk')
8const npmRunScript = require('npm-run-script')
9const opn = require('opn')
10
11const __ = require('../utils/translate')
12const sleep = require('../utils/sleep')
13const getPort = require('../utils/get-port')
14const spinner = require('../utils/spinner')
15const readBuildConfigFile = require('../utils/read-build-config-file')
16const getAppType = require('../utils/get-app-type')
17const setEnvFromCommand = require('../utils/set-env-from-command')
18const getChunkmapPath = require('../utils/get-chunkmap-path')
19const initNodeEnv = require('../utils/init-node-env')
20
21program
22 .version(require('../package').version, '-v, --version')
23 .usage('[options]')
24 .option('-c, --client', 'Set STAGE to CLIENT')
25 .option('-s, --server', 'Set STAGE to SERVER')
26 .option('--stage <stage>', 'Set STAGE')
27 .option('--config <config-file-path>', 'Set config file')
28 .option('--type <project-type>', 'Set project type')
29 .parse(process.argv)
30
31/**
32 * 进入开发环境
33 * ****************************************************************************
34 * 同构 (isomorphic)
35 * 以 PM2 进程方式顺序执行以下流程
36 * 1. 启动 webpack-dev-server (STAGE: client)
37 * 2. 启动 webpack (watch mode) (STAGE: server)
38 * 3. 运行 /server/index.js
39 * ****************************************************************************
40 * 单页面应用 (SPA)
41 * 强制设置 STAGE 为 client,并启动 webpack-dev-server
42 * ****************************************************************************
43 */
44const run = async () => {
45 // 清空 log
46 process.stdout.write('\x1B[2J\x1B[0f')
47
48 const {
49 client, server,
50 stage: _stage,
51 config,
52 type,
53 } = program
54
55 initNodeEnv()
56 setEnvFromCommand({
57 config,
58 type,
59 })
60
61 let stage = _stage ? _stage : (client ? 'client' : (server ? 'server' : false))
62
63 // if (!stage) {
64 // console.log(
65 // chalk.redBright('× ')
66 // + __('dev.missing_stage', {
67 // example: 'super-dev ' + chalk.green('--client'),
68 // indent: ' '
69 // })
70 // )
71 // return
72 // }
73
74 // 读取项目信息
75 const appType = await getAppType()
76 const packageInfo = await fs.readJson(path.resolve(process.cwd(), 'package.json'))
77 const { dist, port } = await readBuildConfigFile()
78 const {
79 name
80 } = packageInfo
81
82 // 如果为 SPA,强制设置 STAGE
83 if (process.env.WEBPACK_BUILD_TYPE === 'spa') {
84 process.env.WEBPACK_BUILD_STAGE = 'client'
85 stage = 'client'
86 }
87
88 // 如果配置中存在 port,修改环境变量
89 if (typeof port !== 'undefined')
90 process.env.SERVER_PORT = getPort(port, 'dev')
91
92 // 如果设置了 stage,仅运行该 stage
93 if (stage) {
94 const cmd = `super-build --stage ${stage} --env dev`
95 const child = npmRunScript(cmd, {})
96 child.once('error', (error) => {
97 console.trace(error)
98 process.exit(1)
99 })
100 child.once('exit', (/*exitCode*/) => {
101 // console.trace('exit in', exitCode)
102 // process.exit(exitCode)
103 })
104 return
105 } else {
106 // 没有设置 STAGE,开始 PM2 进程
107
108 let waitingSpinner = false
109 // spinner(
110 // chalk.yellowBright('[super/build] ')
111 // + __('build.build_start', {
112 // type: chalk.cyanBright(appType),
113 // stage: chalk.green('client'),
114 // env: chalk.green('dev'),
115 // })
116 // )
117
118 const processes = []
119 const pathChunkmap = getChunkmapPath(dist)
120 const pathServerJS = path.resolve(dist, 'server/index.js')
121 const contentWaiting = '// WAITING FOR SERVER BUNDLING'
122
123 { // 在脚本进程关闭/结束时,同时关闭打开的 PM2 进程
124 process.stdin.resume()
125 const exitHandler = async (/*options, err*/) => {
126 // console.log(processes)
127 if (Array.isArray(processes) && processes.length) {
128 if (waitingSpinner) waitingSpinner.stop()
129 await sleep(300)
130 process.stdout.write('\x1B[2J\x1B[0f')
131 const w = spinner('WAITING FOR ENDING')
132 await Promise.all(processes.map(proc =>
133 new Promise((resolve/*, reject*/) => {
134 setTimeout(() => {
135 processes.splice(processes.indexOf(proc), 1)
136 resolve()
137 }, 500)
138 // console.log(proc)
139 pm2.delete(proc)
140 })
141 ))
142 await Promise.all(processes.map(proc =>
143 new Promise((resolve/*, reject*/) => {
144 setTimeout(() => resolve(), 500)
145 // console.log(proc)
146 pm2.delete(proc)
147 })
148 ))
149 // console.log(JSON.stringify(processes))
150 pm2.disconnect()
151 await sleep(300)
152 w.stop()
153 try {
154 // console.log(process.pid)
155 process.exit(1)
156 // process.kill(process.pid)
157 } catch (e) {
158 console.log(e)
159 }
160 } else {
161 process.removeListener('exit', exitHandler)
162 process.removeListener('SIGINT', exitHandler)
163 process.removeListener('SIGUSR1', exitHandler)
164 process.removeListener('SIGUSR2', exitHandler)
165 process.removeListener('uncaughtException', exitHandler)
166 // 清空 log
167 process.stdout.write('\x1B[2J\x1B[0f')
168 console.log('Press CTRL+C again to terminate.')
169 process.exit(1)
170 }
171 }
172 // do something when app is closing
173 process.on('exit', exitHandler);
174 // catches ctrl+c event
175 process.on('SIGINT', exitHandler);
176 // catches "kill pid" (for example: nodemon restart)
177 process.on('SIGUSR1', exitHandler);
178 process.on('SIGUSR2', exitHandler);
179 // catches uncaught exceptions
180 process.on('uncaughtException', exitHandler);
181 }
182
183 // 根据 stage 开启 PM2 进程
184 const start = (stage) => new Promise(async (resolve, reject) => {
185 const pathLogOut = path.resolve(process.cwd(), `logs/dev/${stage}.log`)
186 const pathLogErr = path.resolve(process.cwd(), `logs/dev/${stage}-error.log`)
187 if (fs.existsSync(pathLogOut)) await fs.remove(pathLogOut)
188 if (fs.existsSync(pathLogErr)) await fs.remove(pathLogErr)
189 const config = {
190 name: `dev-${stage}-${name}`,
191 script: path.resolve(__dirname, './build.js'),
192 args: `--stage ${stage} --env dev`,
193 cwd: process.cwd(),
194 output: pathLogOut,
195 error: pathLogErr,
196 autorestart: false,
197 }
198 if (stage === 'run') {
199 config.script = pathServerJS
200 config.watch = true
201 delete config.args
202 }
203 processes.push(config.name)
204 pm2.start(
205 config,
206 (err, proc) => {
207 // console.log(err)
208 if (err) return reject(err)
209 resolve(proc)
210 }
211 )
212 })
213
214 // 连接 PM2
215 pm2.connect(true, async (err) => {
216 if (err) {
217 // console.error(err)
218 process.exit(2)
219 }
220
221 console.log(
222 ` `
223 + chalk.yellowBright('[super/build] ')
224 + __('build.build_start', {
225 type: chalk.cyanBright(appType),
226 stage: chalk.green('client'),
227 env: chalk.green('dev'),
228 })
229 )
230
231 // 清空 chunkmap 文件
232 await fs.ensureFile(pathChunkmap)
233 await fs.writeFile(pathChunkmap, contentWaiting)
234
235 // 清空 server 打包结果文件
236 await fs.ensureFile(pathServerJS)
237 await fs.writeFile(pathServerJS, contentWaiting)
238
239 // 启动 client webpack-dev-server
240 await start('client')
241
242 // 监视 chunkmap 文件,如果修改,进入下一步
243 await new Promise(resolve => {
244 const waiting = () => setTimeout(async () => {
245 if (!fs.existsSync(pathChunkmap)) return waiting()
246 const content = await fs.readFile(pathChunkmap, 'utf-8')
247 if (!content || content === contentWaiting) return waiting()
248 await sleep(100)
249 resolve()
250 }, 500)
251 waiting()
252 })
253 // waitingSpinner.succeed()
254 console.log(
255 chalk.green('√ ')
256 + chalk.yellowBright('[super/build] ')
257 + __('build.build_complete', {
258 type: chalk.cyanBright(appType),
259 stage: chalk.green('client'),
260 env: chalk.green('dev'),
261 })
262 )
263
264 // 启动 server webpack
265 // waitingSpinner = spinner(
266 // chalk.yellowBright('[super/build] ')
267 // + __('build.build_start', {
268 // type: chalk.cyanBright(appType),
269 // stage: chalk.green('server'),
270 // env: chalk.green('dev'),
271 // })
272 // )
273 console.log(
274 ` `
275 + chalk.yellowBright('[super/build] ')
276 + __('build.build_start', {
277 type: chalk.cyanBright(appType),
278 stage: chalk.green('server'),
279 env: chalk.green('dev'),
280 })
281 )
282 await start('server')
283
284 // 监视 server.js 文件,如果修改,进入下一步
285 await new Promise(resolve => {
286 const waiting = () => setTimeout(async () => {
287 if (!fs.existsSync(pathServerJS)) return waiting()
288 const content = await fs.readFile(pathServerJS, 'utf-8')
289 if (!content || content === contentWaiting) return waiting()
290 await sleep(100)
291 resolve()
292 }, 500)
293 waiting()
294 })
295 // waitingSpinner.succeed()
296
297 // 执行
298 // waitingSpinner = spinner(
299 // chalk.yellowBright('[super/build] ')
300 // + 'waiting...'
301 // )
302 await start('run')
303 await sleep(500)
304
305 console.log(
306 chalk.green('√ ')
307 + chalk.yellowBright('[super/build] ')
308 + __('build.build_complete', {
309 type: chalk.cyanBright(appType),
310 stage: chalk.green('server'),
311 env: chalk.green('dev'),
312 })
313 )
314
315 // waitingSpinner.stop()
316 // waitingSpinner = undefined
317 npmRunScript(`pm2 logs`)
318 opn(`http://localhost:${process.env.SERVER_PORT}/`)
319 })
320 }
321}
322
323run()