reboost
Version:
A super fast dev server for rapid web development
322 lines (321 loc) • 14.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = exports.DefaultContentServerOptions = exports.DefaultConfig = exports.builtInPlugins = void 0;
const tslib_1 = require("tslib");
const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
const portfinder_1 = (0, tslib_1.__importDefault)(require("portfinder"));
const open_1 = (0, tslib_1.__importDefault)(require("open"));
const fs_1 = (0, tslib_1.__importDefault)(require("fs"));
const path_1 = (0, tslib_1.__importDefault)(require("path"));
const content_server_1 = require("./content-server");
const utils_1 = require("./utils");
const cache_1 = require("./cache");
const core_plugins_1 = require("./core-plugins");
const esbuild_1 = require("./plugins/esbuild");
const css_1 = require("./plugins/css");
const resolver_1 = require("./core-plugins/resolver");
const proxy_server_1 = require("./proxy-server");
exports.builtInPlugins = (0, tslib_1.__importStar)(require("./plugins"));
const INCOMPATIBLE_BELOW = '0.19.0';
const DEFAULT_PORT = 7456;
exports.DefaultConfig = {
cacheDir: './.reboost_cache',
cacheOnMemory: false,
commonJSInterop: {
mode: 2,
include: /node_modules|\.cjs/,
exclude: () => false
},
contentServer: undefined,
entries: null,
externalHost: true,
hotReload: true,
includeDefaultPlugins: true,
log: {
info: true,
responseTime: false,
watchList: false,
},
mode: 'development',
plugins: [],
rootDir: process.cwd(),
resolve: {
alias: undefined,
aliasFields: ['browser'],
conditionNames: ['import', 'require', 'node', 'default'],
descriptionFiles: ['package.json'],
enforceExtension: false,
exportsFields: ['exports'],
extensions: ['.tsx', '.ts', '.jsx', '.mjs', '.js', '.es6', '.es', '.json'],
fallback: undefined,
importsFields: undefined,
mainFiles: ['index'],
mainFields: ['browser', 'module', 'main'],
modules: ['node_modules'],
plugins: undefined,
pnpApi: undefined,
preferRelative: undefined,
preferAbsolute: undefined,
restrictions: undefined,
roots: undefined,
symlinks: true
},
sourceMaps: {
include: /.*/,
exclude: /node_modules/
},
watchOptions: {
include: undefined,
exclude: /node_modules/,
chokidar: undefined
},
dumpCache: false,
debugMode: false
};
exports.DefaultContentServerOptions = {
name: undefined,
basePath: '/',
etag: true,
extensions: ['.html'],
hidden: false,
index: 'index.html',
middleware: undefined,
open: false,
port: undefined,
proxy: undefined,
root: undefined,
serveIndex: true
};
(0, utils_1.deepFreeze)(exports.DefaultConfig);
(0, utils_1.deepFreeze)(exports.DefaultContentServerOptions);
const createInstance = async (initialConfig) => {
const onStopCallbacks = [];
const it = {
exports: {},
proxyAddress: '',
config: {},
plugins: [],
contentServersOpt: [],
cache: {},
/** Returns false if no entry found. If it returns false, close the app without further execution */
Init: () => {
// Config initialization
it.config = (0, utils_1.merge)((0, utils_1.clone)(exports.DefaultConfig), initialConfig);
if (!it.config.entries) {
console.log(chalk_1.default.red('No entry found. Please add some entries first.'));
return false;
}
if (!path_1.default.isAbsolute(it.config.rootDir))
it.log('info', chalk_1.default.red('rootDir should be an absolute path'));
if (!path_1.default.isAbsolute(it.config.cacheDir))
it.config.cacheDir = path_1.default.join(it.config.rootDir, it.config.cacheDir);
if (!it.config.watchOptions.include)
it.config.watchOptions.include = /.*/;
if (it.config.contentServer) {
it.contentServersOpt = [].concat(it.config.contentServer);
it.contentServersOpt = it.contentServersOpt.map((contentServerOpt) => {
contentServerOpt = (0, utils_1.merge)((0, utils_1.clone)(exports.DefaultContentServerOptions), contentServerOpt);
if (!path_1.default.isAbsolute(contentServerOpt.root)) {
contentServerOpt.root = path_1.default.join(it.config.rootDir, contentServerOpt.root);
}
return contentServerOpt;
});
}
it.config.resolve.modules = [].concat(it.config.resolve.modules);
// Add absolute path for relative modules dir path, so that resolve can work
// for any plugins to load peerDependencies
it.config.resolve.modules.forEach((modDirName) => {
if (path_1.default.isAbsolute(modDirName))
return;
it.config.resolve.modules.push(path_1.default.join(it.config.rootDir, modDirName));
});
if (it.config.resolve.roots == null) {
it.config.resolve.roots = [it.config.rootDir];
}
const resolveAlias = it.config.resolve.alias;
if (resolveAlias) {
if (Array.isArray(resolveAlias)) {
resolveAlias.forEach((aliasData) => {
if (Array.isArray(aliasData.alias)) {
aliasData.alias = aliasData.alias.map((aPath) => (aPath.startsWith('.') ? path_1.default.join(it.config.rootDir, aPath) : aPath));
}
else if (aliasData.alias && aliasData.alias.startsWith('.')) {
aliasData.alias = path_1.default.join(it.config.rootDir, aliasData.alias);
}
});
}
else {
Object.keys(resolveAlias).forEach((key) => {
const aliasPath = resolveAlias[key];
if (Array.isArray(aliasPath)) {
resolveAlias[key] = aliasPath.map((aPath) => (aPath.startsWith('.') ? path_1.default.join(it.config.rootDir, aPath) : aPath));
}
else if (aliasPath && aliasPath.startsWith('.')) {
resolveAlias[key] = path_1.default.join(it.config.rootDir, aliasPath);
}
});
}
}
(0, utils_1.deepFreeze)(it.config);
// Flat the plugins array
it.plugins = [].concat(...it.config.plugins);
it.plugins.push(...(0, core_plugins_1.CorePlugins)(it));
if (it.config.includeDefaultPlugins) {
const pluginNames = it.plugins.map(({ name }) => name);
if (!pluginNames.includes(esbuild_1.PluginName)) {
it.plugins.push((0, esbuild_1.esbuildPlugin)());
}
if (!pluginNames.includes(css_1.PluginName)) {
it.plugins.push((0, css_1.CSSPlugin)());
}
}
// Cache initialization
it.cache = (0, cache_1.initCache)(it.config, it.plugins, it.log);
},
isLogEnabled: (type) => {
// Sorry for the extra negation *_*
return !(!it.config.log || !it.config.log[type]);
},
log: ((type, ...toLog) => {
if (it.isLogEnabled(type))
console.log(...toLog);
}),
onStop: (label, cb) => {
onStopCallbacks.push([cb, label]);
},
};
const startServer = (name, server, port, host = 'localhost') => new Promise((doneStart) => {
const httpServer = server.listen(port, host, () => doneStart());
const connections = new Map();
httpServer.on('connection', (connection) => {
const key = `${connection.remoteAddress}:${connection.remotePort}`;
connections.set(key, connection);
connection.on('close', () => connections.delete(key));
});
it.onStop(`Closes ${name}`, () => new Promise((doneClose) => {
connections.forEach((connection) => connection.destroy());
httpServer.close(() => doneClose());
}));
});
const stop = async () => {
for (const [onStop] of onStopCallbacks) {
await onStop();
}
for (const { stop: stopPlugin } of it.plugins) {
if (stopPlugin)
await stopPlugin();
}
};
// Initialize all properties
if (it.Init() === false)
return false;
// TODO: Remove it in v1.0
const oldCacheFile = path_1.default.join(it.config.cacheDir, 'cache_data.json');
if (fs_1.default.existsSync(oldCacheFile))
(0, utils_1.rmDir)(it.config.cacheDir);
if (it.config.dumpCache)
(0, utils_1.rmDir)(it.config.cacheDir);
if ((0, utils_1.isVersionLessThan)(it.cache.version, INCOMPATIBLE_BELOW)) {
it.log('info', chalk_1.default.cyan('Cache version is incompatible, clearing cached files...'));
(0, utils_1.rmDir)(it.config.cacheDir);
it.log('info', chalk_1.default.cyan('Clear cache complete'));
}
if (fs_1.default.existsSync(it.config.cacheDir)) {
it.log('info', chalk_1.default.cyan('Refreshing cache...'));
it.cache.verifyFiles();
it.log('info', chalk_1.default.cyan('Refresh cache complete'));
}
it.log('info', chalk_1.default.green('Starting proxy server...'));
const proxyServer = (0, proxy_server_1.createProxyServer)(it);
const contentServers = it.config.contentServer
? it.contentServersOpt.map((opt) => (0, content_server_1.createContentServer)(it, opt))
: undefined;
const externalHost = await (0, utils_1.getExternalHost)(it);
for (const { setup } of it.plugins) {
if (setup) {
await setup({
config: it.config,
proxyServer,
contentServers,
resolve: (...args) => (0, resolver_1.resolve)(it, ...args),
chalk: chalk_1.default,
instance: it
});
}
}
const proxyServerHost = externalHost || 'localhost';
const proxyServerPort = await portfinder_1.default.getPortPromise({
host: proxyServerHost,
port: DEFAULT_PORT
});
const fullAddress = it.proxyAddress = `http://${proxyServerHost}:${proxyServerPort}`;
for (const [input, output, libName] of it.config.entries) {
const outputPath = path_1.default.join(it.config.rootDir, output);
(0, utils_1.ensureDir)(path_1.default.dirname(outputPath));
if (!fs_1.default.existsSync(path_1.default.join(it.config.rootDir, input))) {
it.log('info', chalk_1.default.red(`The input file does not exist: ${JSON.stringify(input)}`));
continue;
}
let fileContent = `import '${fullAddress}/runtime';\n`;
fileContent += 'import';
if (libName)
fileContent += ' * as _$lib$_ from';
fileContent += ` '${fullAddress}/transformed?q=${encodeURIComponent(path_1.default.join(it.config.rootDir, input))}';\n`;
if (libName)
fileContent += `self[${JSON.stringify(libName)}] = _$lib$_;\n`;
fs_1.default.writeFileSync(outputPath, fileContent);
it.log('info', chalk_1.default.cyan(`Generated: ${input} -> ${output}`));
}
await startServer('Proxy server', proxyServer, proxyServerPort, proxyServerHost);
it.log('info', chalk_1.default.green('Proxy server started'));
it.exports = {
stop,
proxyServer: fullAddress
};
if (contentServers && contentServers.length) {
it.exports.contentServer = {};
for (let i = 0; i < contentServers.length; i++) {
const address = {
local: undefined,
external: undefined
};
const contentServer = contentServers[i];
const contentServerOpt = it.contentServersOpt[i];
const serverName = contentServerOpt.name || i + 1;
const contentServerPath = (host, port) => (`http://${host}:${port}${contentServerOpt.basePath}`.replace(/\/$/, ''));
const localPort = await portfinder_1.default.getPortPromise({ port: contentServerOpt.port });
address.local = contentServerPath('localhost', localPort);
await startServer('Local content server', contentServer, localPort);
const openOptions = contentServerOpt.open;
if (openOptions) {
(0, open_1.default)(address.local, typeof openOptions === 'object' ? openOptions : undefined);
}
if (externalHost) {
const externalPort = await portfinder_1.default.getPortPromise({
host: externalHost,
port: contentServerOpt.port
});
address.external = contentServerPath(externalHost, externalPort);
await startServer('External content server', contentServer, externalPort, externalHost);
}
Object.assign(it.exports.contentServer, address, { [i]: address });
it.log('info', chalk_1.default.green([
`Content server [${serverName}] is running on:`,
' Local - ' + chalk_1.default.blue(address.local),
...(address.external
? [' External - ' + chalk_1.default.blue(address.external)]
: [])
].join('\n')));
}
}
return it;
};
const start = async (config = {}) => {
const instance = await createInstance(config);
return instance && instance.exports;
};
exports.start = start;
if (fs_1.default.existsSync(path_1.default.join(__dirname, '../../src'))) {
// @ts-expect-error We don't need types for this
Promise.resolve().then(() => (0, tslib_1.__importStar)(require('source-map-support'))).then((mod) => mod.install());
}