mirror of
https://github.com/thinkjs/thinkjs.git
synced 2025-12-08 18:26:23 +00:00
261 lines
7.0 KiB
JavaScript
261 lines
7.0 KiB
JavaScript
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;
|