fusion-cli
Version:
247 lines (211 loc) • 6.72 kB
JavaScript
/** Copyright (c) 2018 Uber Technologies, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-env node */
/* global __webpack_hash__ */
/* eslint-disable import/first */
import sourceMapSupport from 'source-map-support';
sourceMapSupport.install();
// $FlowFixMe[cannot-resolve-module]
import '__SECRET_I18N_MANIFEST_INSTRUMENTATION_LOADER__!'; // eslint-disable-line
import http from 'http';
import BaseApp, {
createPlugin,
HttpServerToken,
RoutePrefixToken,
SSRBodyTemplateToken,
SSRShellTemplateToken,
CriticalChunkIdsToken,
} from 'fusion-core';
import CriticalChunkIdsPlugin from '../plugins/critical-chunk-ids-plugin.js';
import AssetsFactory from '../plugins/assets-plugin';
import ContextPlugin from '../plugins/context-plugin';
import ServerErrorPlugin from '../plugins/server-error-plugin';
import {SSRBodyTemplate, getSSRShellTemplate} from '../plugins/ssr-plugin';
import {SSRModuleScriptsBodyTemplate} from '../plugins/ssr-module-scripts-plugin';
import stripRoutePrefix from '../lib/strip-prefix.js';
// $FlowFixMe[cannot-resolve-module]
import main from '__FUSION_ENTRY_PATH__'; // eslint-disable-line import/no-unresolved
let prefix = process.env.ROUTE_PREFIX;
let AssetsPlugin;
let server = null;
const state = {serve: null};
function getInitialize() {
return typeof main === 'function'
? main
: () => {
throw new Error('App should export a function');
};
}
let initialReloadOptions;
export async function start(
{port, dir = '.', useModuleScripts = false} /*: any */
) {
AssetsPlugin = AssetsFactory(dir);
// TODO(#21): support https.createServer(credentials, listener);
server = http.createServer();
initialReloadOptions = {useModuleScripts};
await reload(initialReloadOptions);
server.on('request', (req, res) => {
if (prefix) stripRoutePrefix(req, prefix);
// $FlowFixMe[not-a-function]
state.serve(req, res).catch((e) => {
// $FlowFixMe[prop-missing]
state.app.onerror(e);
});
});
return new Promise((resolve) => {
server &&
server.listen(port, () => {
resolve(server);
});
});
}
let prevApp = null;
async function reload(
{useModuleScripts} /* : { useModuleScripts?: boolean } */
) {
if (prevApp) {
await prevApp.cleanup();
prevApp = null;
}
const initialize = getInitialize();
const app = await initialize();
if (!(app instanceof BaseApp)) {
throw new Error('Application entry point did not return an App');
}
reverseRegister(app, ContextPlugin);
app.register(AssetsPlugin);
app.register(
SSRBodyTemplateToken,
useModuleScripts ? SSRModuleScriptsBodyTemplate : SSRBodyTemplate
);
app.register(
SSRShellTemplateToken,
getSSRShellTemplate(Boolean(useModuleScripts))
);
app.register(CriticalChunkIdsToken, CriticalChunkIdsPlugin);
if (prefix) {
app.register(RoutePrefixToken, prefix);
}
if (server) {
app.register(HttpServerToken, createPlugin({provides: () => server}));
}
if (__DEV__) {
reverseRegister(app, ServerErrorPlugin);
}
state.serve = app.callback();
// $FlowFixMe[prop-missing]
state.app = prevApp = app;
}
function reverseRegister(app, token, plugin) {
app.register(token, plugin);
const entries = Array.from(app.taskMap.entries());
entries.unshift(entries.pop());
app.taskMap = new Map(entries);
}
if (module.hot) {
let hasFailedUpdate = false;
// $FlowFixMe[cannot-resolve-name]
let latestServerBuildHash = __webpack_hash__;
const isUpToDate = () => {
return latestServerBuildHash === __webpack_hash__;
};
let needReload = false;
let reloadPromise = Promise.resolve();
const checkForUpdate = () => {
return (
module.hot
// $FlowFixMe[prop-missing]
.check(true)
.then(function (updatedModules) {
if (updatedModules && updatedModules.length) {
console.log('[HMR] Updated modules');
updatedModules.forEach(function (m) {
// Do not output internal modules
if (m.includes('/fusion-cli/')) {
return;
}
console.log('[HMR] - ', m);
});
}
if (!isUpToDate()) {
return checkForUpdate();
}
if (needReload) {
needReload = false;
const curReloadPromise = (reloadPromise = reloadPromise.then(
function () {
function skip() {
return curReloadPromise !== reloadPromise || !isUpToDate();
}
if (skip()) {
return false;
}
return reload(initialReloadOptions).then(function () {
return !skip();
});
}
));
return curReloadPromise;
}
return true;
})
);
};
const onProcessMessage = (data) => {
if (hasFailedUpdate) {
return;
}
if (data.event === 'update') {
latestServerBuildHash = data.serverBuildHash;
// $FlowFixMe[prop-missing]
if (module.hot.status() === 'idle') {
checkForUpdate()
.then((isReady) => {
if (!isReady) {
return;
}
// $FlowFixMe[not-a-function]
process.send({
event: 'ready',
serverBuildHash: latestServerBuildHash,
});
})
.catch(function (err) {
if (hasFailedUpdate) {
return;
}
hasFailedUpdate = true;
process.off('message', onProcessMessage);
process.nextTick(() => {
// $FlowFixMe[not-a-function]
process.send({event: 'update-failed'});
});
global.__DEV_RUNTIME_LOG_ERROR__(err);
});
}
}
};
process.on('message', onProcessMessage);
const onAcceptReload = () => {
// Defer reload until everything is up-to-date
needReload = true;
};
module.hot.accept('__FUSION_ENTRY_PATH__', onAcceptReload);
module.hot.accept(
'__SECRET_I18N_MANIFEST_INSTRUMENTATION_LOADER__!',
onAcceptReload
);
module.hot.accept('../plugins/ssr-plugin.js', onAcceptReload);
module.hot.accept('../plugins/ssr-module-scripts-plugin.js', onAcceptReload);
module.hot.accept('../plugins/assets-plugin.js', onAcceptReload);
module.hot.accept('../plugins/critical-chunk-ids-plugin.js', onAcceptReload);
module.hot.accept('../plugins/context-plugin.js', onAcceptReload);
// $FlowFixMe[prop-missing]
module.hot.decline();
}