UNPKG

reboost

Version:

A super fast dev server for rapid web development

322 lines (321 loc) 14.5 kB
"use strict"; 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()); }