UNPKG

thinkjs

Version:

ThinkJS - Use full ES6+ features to develop web applications

261 lines (253 loc) 7.18 kB
const path = require('path'); const cluster = require('cluster'); const helper = require('think-helper'); const thinkCluster = require('think-cluster'); const pm2 = require('think-pm2'); const http = require('http'); const assert = require('assert'); const mockHttp = require('think-mock-http'); const ThinkLoader = require('./loader.js'); /** * applition class */ module.exports = class Application { /** * constructor */ constructor(options = {}) { assert(options.ROOT_PATH, 'options.ROOT_PATH must be set'); if (!options.APP_PATH) { let appPath = path.join(options.ROOT_PATH, 'app'); if (!options.transpiler && !helper.isDirectory(appPath)) { appPath = path.join(options.ROOT_PATH, 'src'); } options.APP_PATH = appPath; } if (!options.RUNTIME_PATH) { options.RUNTIME_PATH = path.join(options.ROOT_PATH, 'runtime'); } this.options = options; } /** * notify error */ notifier(err) { if (!this.options.notifier) return; let notifier = this.options.notifier; if (!helper.isArray(notifier)) { notifier = [notifier]; } notifier[0](Object.assign({ title: 'ThinkJS Transpile Error', message: err.message }, notifier[1])); } /** * watcher callback */ _watcherCallBack(fileInfo) { let transpiler = this.options.transpiler; if (transpiler) { if (!helper.isArray(transpiler)) { transpiler = [transpiler]; } const ret = transpiler[0]({ srcPath: fileInfo.path, outPath: this.options.APP_PATH, file: fileInfo.file, options: transpiler[1] }); if (helper.isError(ret)) { console.error(ret.stack); this.notifier(ret); return false; } if (think.logger) { think.logger.info(`transpile file ${fileInfo.file} success`); } } // reload all workers if (this.masterInstance) { this.masterInstance.forceReloadWorkers(); } } /** * start watcher */ startWatcher() { const Watcher = this.options.watcher; if (!Watcher) return; const instance = new Watcher({ srcPath: path.join(this.options.ROOT_PATH, 'src'), diffPath: this.options.APP_PATH }, fileInfo => this._watcherCallBack(fileInfo)); instance.watch(); } /** * parse argv */ parseArgv() { const options = {}; const argv2 = process.argv[2]; const portRegExp = /^\d{2,5}$/; const pathRegExp = /^\/?[a-z]\w*/i; if (argv2) { if (pathRegExp.test(argv2)) { options.path = argv2; } else if (portRegExp.test(argv2)) { options.port = argv2; } } return options; } _getMasterInstance(argv) { const port = argv.port || think.config('port'); const host = think.config('host'); const instance = new thinkCluster.Master({ port, host, sticky: think.config('stickyCluster'), getRemoteAddress: socket => { return socket.remoteAddress || ''; }, workers: think.config('workers'), reloadSignal: think.config('reloadSignal') }); this.masterInstance = instance; think.logger.info(`Server running at http://${host || '127.0.0.1'}:${port}`); think.logger.info(`ThinkJS version: ${think.version}`); think.logger.info(`Environment: ${think.app.env}`); think.logger.info(`Workers: ${instance.options.workers}`); return instance; } /** * run in master */ runInMaster(argv) { return think.beforeStartServer().catch(err => { think.logger.error(err); }).then(() => { const instance = this._getMasterInstance(argv); return instance.startServer(); }).then(() => { think.app.emit('appReady'); }); } _getWorkerInstance(argv) { const port = argv.port || think.config('port'); const instance = new thinkCluster.Worker({ port, host: think.config('host'), sticky: think.config('stickyCluster'), createServer() { const createServerFn = think.config('createServer'); const callback = think.app.callback(); if (createServerFn) { assert(helper.isFunction(createServerFn), 'config.createServer must be a function'); } const server = createServerFn ? createServerFn(callback) : http.createServer(callback); think.app.server = server; return server; }, logger: think.logger.error.bind(think.logger), processKillTimeout: think.config('processKillTimeout'), onUncaughtException: think.config('onUncaughtException'), onUnhandledRejection: think.config('onUnhandledRejection') }); return instance; } /** * run in worker * @param {Object} argv */ runInWorker(argv) { return think.beforeStartServer().catch(err => { think.logger.error(err); }).then(() => { const instance = this._getWorkerInstance(argv); return instance.startServer(); }).then(() => { think.app.emit('appReady'); }); } /** * command line invoke */ runInCli(argv) { think.app.emit('appReady'); return mockHttp({ url: argv.path, method: 'CLI', exitOnEnd: true }, think.app); } /** * check test env */ _isRunInTest() { const { JEST_WORKER_ID, NODE_ENV, THINK_UNIT_TEST } = process.env; /** * https://github.com/facebook/jest/pull/5860 * provide process.env.JEST_WORKER_ID="1" for cases when Jest * runs the tests on the main process. */ const runInJest = JEST_WORKER_ID !== undefined; /** * https://github.com/avajs/ava/blob/master/docs/01-writing-tests.md * AVA will set process.env.NODE_ENV to test, unless the * NODE_ENV environment variable has been set. */ const runInAva = NODE_ENV === 'test'; /** * https://github.com/AndreasPizsa/detect-mocha/blob/master/index.js */ const runInMocha = [ 'afterEach', 'after', 'beforeEach', 'before', 'describe', 'it' ].every(name => global[name] instanceof Function); /** * Other Test framework we can't detect should add environment * by user */ const runInUserDefine = !!THINK_UNIT_TEST; return runInJest || runInAva || runInMocha || runInUserDefine; } /** * run */ run() { if (pm2.isClusterMode) { throw new Error('can not use pm2 cluster mode, please change exec_mode to fork'); } // start file watcher if (cluster.isMaster) this.startWatcher(); const instance = new ThinkLoader(this.options); const argv = this.parseArgv(); try { if (this._isRunInTest()) { instance.loadAll('worker', true); } else if (argv.path) { instance.loadAll('worker', true); return this.runInCli(argv); } else if (cluster.isMaster) { instance.loadAll('master'); return this.runInMaster(argv); } else { instance.loadAll('worker'); return this.runInWorker(argv); } } catch (e) { console.error(e.stack); } } }; /** * global think instance, mostly use in typescript */ module.exports.think = global.think;