UNPKG

tix-react-ssr

Version:

Tiket.com React Project Scripts

255 lines (220 loc) 7.62 kB
const path = require('path'); const express = require('express'); const browserSync = require('browser-sync'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const webpackHotMiddleware = require('webpack-hot-middleware'); const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); const paths = require('../config/paths'); const webpackConfig = require('./webpack.config'); const run = require('./run'); const clean = require('./clean'); const config = require('config').util.loadFileConfigs(paths.appPath + '/config'); const { format } = run; const isDebug = !process.argv.includes('--release'); process.env.DEV_SERVER = true; // https://webpack.js.org/configuration/watch/#watchoptions const watchOptions = { // Watching may not work with NFS and machines in VirtualBox // Uncomment next line if it is your case (use true or interval in milliseconds) // poll: true, // Decrease CPU or memory usage in some file systems // ignored: /node_modules/, }; function createCompilation(name, compiler, config) { return new Promise((resolve, reject) => { let timeStart = new Date(); compiler.plugin('compile', () => { timeStart = new Date(); console.info(`[${format(timeStart)}] Compiling '${name}'...`); }); compiler.plugin('done', stats => { console.info(stats.toString(config.stats)); const timeEnd = new Date(); const time = timeEnd.getTime() - timeStart.getTime(); if (stats.hasErrors()) { console.info( `[${format(timeEnd)}] Failed to compile '${name}' after ${time} ms`, ); reject(new Error('Compilation failed!')); } else { console.info( `[${format( timeEnd, )}] Finished '${name}' compilation after ${time} ms`, ); resolve(stats); } }); }); } let server; /** * Launches a development web server with "live reload" functionality - * synchronizing URLs, interactions and code changes across multiple devices. */ async function start() { if (server) { return server; } server = express(); server.use(errorOverlayMiddleware()); server.use(express.static(paths.appPublic)); // Configure client-side hot module replacement const clientConfig = webpackConfig.find(config => config.name === 'client'); clientConfig.entry.client = [path.resolve(__dirname + '/lib/webpackHotDevClient')] .concat(clientConfig.entry.client) .sort((a, b) => b.includes('polyfill') - a.includes('polyfill')); clientConfig.output.filename = clientConfig.output.filename.replace( 'chunkhash', 'hash', ); clientConfig.output.chunkFilename = clientConfig.output.chunkFilename.replace( 'chunkhash', 'hash', ); clientConfig.module.rules = clientConfig.module.rules.filter( x => x.loader !== 'null-loader', ); clientConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), new webpack.NamedModulesPlugin(), ); // Configure server-side hot module replacement const serverConfig = webpackConfig.find(config => config.name === 'server'); serverConfig.output.hotUpdateMainFilename = 'updates/[hash].hot-update.json'; serverConfig.output.hotUpdateChunkFilename = 'updates/[id].[hash].hot-update.js'; serverConfig.module.rules = serverConfig.module.rules.filter( x => x.loader !== 'null-loader', ); serverConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), new webpack.NamedModulesPlugin(), ); // Configure compilation await run(clean); const clientCompiler = webpack(clientConfig); const serverCompiler = webpack(serverConfig); const clientPromise = createCompilation( 'client', clientCompiler, clientConfig, ); const serverPromise = createCompilation( 'server', serverCompiler, serverConfig, ); // https://github.com/webpack/webpack-dev-middleware server.use( webpackDevMiddleware(clientCompiler, { publicPath: clientConfig.output.publicPath, logLevel: 'silent', watchOptions, }), ); // https://github.com/glenjamin/webpack-hot-middleware server.use(webpackHotMiddleware(clientCompiler, { log: false })); let appPromise; let appPromiseResolve; let appPromiseIsResolved = true; serverCompiler.plugin('compile', () => { if (!appPromiseIsResolved) { return; } appPromiseIsResolved = false; // eslint-disable-next-line no-return-assign appPromise = new Promise(resolve => (appPromiseResolve = resolve)); }); let app; server.use((req, res) => { appPromise .then(() => app.handle(req, res)) .catch(error => console.error(error)); }); function checkForUpdate(fromUpdate) { const hmrPrefix = '[\x1b[35mHMR\x1b[0m] '; if (!app.hot) { throw new Error(`${hmrPrefix}Hot Module Replacement is disabled.`); } if (app.hot.status() !== 'idle') { return Promise.resolve(); } return app.hot .check(true) .then(updatedModules => { if (!updatedModules) { if (fromUpdate) { console.info(`${hmrPrefix}Update applied.`); } return; } if (updatedModules.length === 0) { console.info(`${hmrPrefix}Nothing hot updated.`); } else { console.info(`${hmrPrefix}Updated modules:`); updatedModules.forEach(moduleId => console.info(`${hmrPrefix} - ${moduleId}`), ); checkForUpdate(true); } }) .catch(error => { if (['abort', 'fail'].includes(app.hot.status())) { console.warn(`${hmrPrefix}Cannot apply update.`); delete require.cache[require.resolve('../build/server')]; // eslint-disable-next-line global-require, import/no-unresolved app = require('../build/server').default; console.warn(`${hmrPrefix}App has been reloaded.`); } else { console.warn( `${hmrPrefix}Update failed: ${error.stack || error.message}`, ); } }); } serverCompiler.watch(watchOptions, (error, stats) => { if (app && !error && !stats.hasErrors()) { checkForUpdate().then(() => { appPromiseIsResolved = true; appPromiseResolve(); }); } }); // Wait until both client-side and server-side bundles are ready await Promise.all([ clientPromise, serverPromise ]); const timeStart = new Date(); console.info(`[${format(timeStart)}] Launching server...`); // Load compiled src/server.js as a middleware // eslint-disable-next-line global-require, import/no-unresolved app = require(paths.appBuild + '/server').default; appPromiseIsResolved = true; appPromiseResolve(); // Launch the development server with Browsersync and HMR await new Promise((resolve, reject) => browserSync.create().init( { // https://www.browsersync.io/docs/options server: 'src/server.js', middleware: [server], reloadOnRestart: true, port: config.port, open: false, ...(process.argv.includes('--ui') ? {} : { notify: false, ui: false }), ghostMode: process.argv.includes('--ghost') }, (error, bs) => (error ? reject(error) : resolve(bs)), ), ); const timeEnd = new Date(); const time = timeEnd.getTime() - timeStart.getTime(); console.info(`[${format(timeEnd)}] Server launched after ${time} ms`); return server; } module.exports = start;