Skip to content

Commit

Permalink
Merge pull request #162 from seznam/ima-webpack-dev-server-refactor
Browse files Browse the repository at this point in the history
Ima webpack dev server refactor
  • Loading branch information
jsimck authored Feb 2, 2022
2 parents 590050d + 049c1f7 commit 46d785d
Show file tree
Hide file tree
Showing 34 changed files with 841 additions and 340 deletions.
13 changes: 13 additions & 0 deletions packages/cli/src/bin/ima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import path from 'path';
import chalk from 'chalk';
import yargs from 'yargs';

// Normalize NODE_ENV
switch (process.env.NODE_ENV) {
case 'prod':
process.env.NODE_ENV = 'production';
break;

case 'dev':
case undefined:
process.env.NODE_ENV = 'development';
break;
}

// Init CLI
yargs
.scriptName(chalk.green.bold('ima'))
.usage('Usage: $0 <command>')
Expand Down
46 changes: 20 additions & 26 deletions packages/cli/src/dev-server/devServer.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,38 @@
import http from 'http';
import path from 'path';

import hotMiddleware from '@gatsbyjs/webpack-hot-middleware';
import chalk from 'chalk';
import express, { Express } from 'express';
import express from 'express';
import prettyMs from 'pretty-ms';
import webpack from 'webpack';
import { MultiCompiler } from 'webpack';
import devMiddleware from 'webpack-dev-middleware';

import { IMA_CLI_RUN_SERVER_MESSAGE } from '../lib/cli';
import logger from '../lib/logger';
import { createWebpackConfig } from '../webpack/utils';
import { evalSourceMapMiddleware } from './evalSourceMapMiddleware';
import { openEditorMiddleware } from './openEditorMiddleware';

async function createDevServer(app: Express) {
const { config } = await createWebpackConfig(['client']);
const compiler = webpack(config);

// Override listen so we can react when server is ready
app.listen = function () {
const server = http.createServer(this);

// Inform cli that web server has started
/* eslint-disable */
// @ts-expect-error
return server.listen.apply(server, arguments).on('listening', () => {
/* eslint-enable */
process.send?.(IMA_CLI_RUN_SERVER_MESSAGE);
});
};
async function createDevServer(
compiler: MultiCompiler,
hostname: string,
port: number
) {
const app = express();

let isBuilding = false;
const isVerbose = process.argv.some(arg => arg.includes('--verbose=true'));

// Define dev middlewares
app
.use((req, res, next) => {
// Allow cors
res.header('Access-Control-Allow-Origin', '*');

next();
})
.use(
devMiddleware(compiler, {
index: false,
publicPath: '/',
writeToDisk: true,
...(!isVerbose ? { stats: 'none' } : undefined),
serverSideRender: true,
})
Expand All @@ -60,20 +53,20 @@ async function createDevServer(app: Express) {
// Used to prevent multiple building messages after each other
isBuilding = false;

logger.hmr(
logger.update(
`Built ${chalk.bold(bundle)} ${chalk.gray(
'[' + prettyMs(parseInt(time, 10)) + ']'
)}`
);
} else if (!isBuilding) {
logger.hmr('Building...');
logger.update('Building...');
isBuilding = true;
}
},
}
: undefined),
path: '/__webpack_hmr',
heartbeat: 10 * 1000,
heartbeat: 5000,
})
)
.use('/__get-internal-source', evalSourceMapMiddleware())
Expand All @@ -83,7 +76,8 @@ async function createDevServer(app: Express) {
express.static(
path.resolve(path.join(__dirname, '../../../error-overlay/dist/'))
)
);
)
.listen(port, hostname);
}

export { createDevServer };
14 changes: 6 additions & 8 deletions packages/cli/src/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { Arguments, CommandBuilder } from 'yargs';
import { CliArgs, HandlerFn, ImaCliCommand } from '../types';
import { requireImaConfig } from '../webpack/utils';

const IMA_CLI_RUN_SERVER_MESSAGE = 'ima-cli-run-server-message';

/**
* Initializes cli script handler function, which takes cli arguments,
* parses them and defines defaults. Should be used to initialize any
Expand All @@ -17,10 +15,15 @@ function handlerFactory(handlerFn: HandlerFn) {
return async (yargs: Arguments): Promise<void> => {
const [command] = yargs._ || [];

// Force development env for dev
process.env.NODE_ENV =
command === 'dev' ? 'development' : process.env.NODE_ENV ?? 'production';

return await handlerFn({
...yargs,
rootDir: process.cwd(),
command: command.toString(),
environment: process.env.NODE_ENV,
} as unknown as CliArgs);
};
}
Expand Down Expand Up @@ -88,9 +91,4 @@ function sharedArgsFactory(command: ImaCliCommand): CommandBuilder {
};
}

export {
IMA_CLI_RUN_SERVER_MESSAGE,
handlerFactory,
resolveCliPluginArgs,
sharedArgsFactory,
};
export { handlerFactory, resolveCliPluginArgs, sharedArgsFactory };
5 changes: 2 additions & 3 deletions packages/cli/src/lib/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ async function runCompiler(
async function watchCompiler(
compiler: MultiCompiler,
args: CliArgs,
imaConfig: ImaConfig,
watchOptions: Configuration['watchOptions'] = {}
imaConfig: ImaConfig
): Promise<MultiCompiler> {
let elapsed: ReturnType<typeof time> | null = null;
let firstStats: MultiStats | undefined | null;
Expand All @@ -161,7 +160,7 @@ async function watchCompiler(
);

return new Promise<MultiCompiler>((resolve, reject) => {
compiler.watch(watchOptions, (error, stats) => {
compiler.watch(imaConfig.watchOptions, (error, stats) => {
// Print elapsed time for first run
if (elapsed) {
elapsed && logger.write(chalk.gray(` [${elapsed()}]`));
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export default {
success: printFnFactory('success', chalk.bold.green),
error: printFnFactory('error', chalk.bold.red),
warn: printFnFactory('warn', chalk.bold.yellow),
hmr: printFnFactory('hmr', chalk.bold.magenta),
update: printFnFactory('update', chalk.bold.magenta),
plugin: printFnFactory('plugin', chalk.bold.blue),
};
3 changes: 0 additions & 3 deletions packages/cli/src/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import { createWebpackConfig } from '../webpack/utils';
* @returns {Promise<void>}
*/
const build: HandlerFn = async args => {
// Set NODE_ENV to production if not defined
process.env.NODE_ENV = process.env.NODE_ENV ?? 'production';

try {
const { config, imaConfig } = await createWebpackConfig(
['client', 'server'],
Expand Down
118 changes: 66 additions & 52 deletions packages/cli/src/scripts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,66 @@ import nodemon from 'nodemon';
import webpack from 'webpack';
import { CommandBuilder } from 'yargs';

import { createDevServer } from '..';
import {
handlerFactory,
IMA_CLI_RUN_SERVER_MESSAGE,
resolveCliPluginArgs,
sharedArgsFactory,
} from '../lib/cli';
import { watchCompiler, handleError } from '../lib/compiler';
import logger from '../lib/logger';
import { CliArgs, HandlerFn } from '../types';
import { createWebpackConfig, resolveEnvironment } from '../webpack/utils';

let nodemonInitialized = false;
let serverHasStarted = false;
import {
createDevServerConfig,
createWebpackConfig,
resolveEnvironment,
} from '../webpack/utils';

/**
* Starts ima server with nodemon to watch for server-side changes
* (all changes in server/ folder), to automatically restart application.
*/
function startNodemon(args: CliArgs) {
if (!nodemonInitialized) {
nodemon({
script: path.join(args.rootDir, 'server/server.js'),
watch: [`${path.join(args.rootDir, 'server')}`],
args: [`--verbose=${args.verbose}`],
cwd: args.rootDir,
});

nodemon.on('start', () => {
logger.info(
`${serverHasStarted ? 'Restarting' : 'Starting'} application server${
!serverHasStarted && (args.forceSPA || args.forceSPAWithHMR)
? ` in ${chalk.black.bgCyan(
args.forceSPAWithHMR ? 'SPA mode with HMR' : 'SPA mode'
)}`
: ''
}...`
);
});

nodemon.on('crash', error => {
logger.error('Application watcher unexpectedly crashed.');
logger.error(error);
});

nodemon.once('message', message => {
if (message === IMA_CLI_RUN_SERVER_MESSAGE) {
serverHasStarted = true;

if (args.open) {
const imaEnvironment = resolveEnvironment(args.rootDir);
const port = imaEnvironment?.$Server?.port ?? 3001;

try {
open(`http://localhost:${port}`);
} catch (error) {
logger.error(
`Could not open http://localhost:${port} inside a browser, ${error}`
);
}
}
let serverHasStarted = false;

nodemon({
script: path.join(args.rootDir, 'server/server.js'),
watch: [`${path.join(args.rootDir, 'server')}`],
args: [`--verbose=${args.verbose}`],
cwd: args.rootDir,
});

nodemon.on('start', () => {
logger.info(
`${serverHasStarted ? 'Restarting' : 'Starting'} application server${
!serverHasStarted && (args.forceSPA || args.forceSPAWithHMR)
? ` in ${chalk.black.bgCyan(
args.forceSPAWithHMR ? 'SPA mode with HMR' : 'SPA mode'
)}`
: ''
}...`
);

if (args.open && !serverHasStarted) {
const imaEnvironment = resolveEnvironment(args.rootDir);
const port = imaEnvironment?.$Server?.port ?? 3001;
serverHasStarted = true;

try {
open(`http://localhost:${port}`);
} catch (error) {
logger.error(
`Could not open http://localhost:${port} inside a browser, ${error}`
);
}
});
}
});

nodemonInitialized = true;
}
nodemon.on('crash', error => {
logger.error('Application watcher unexpectedly crashed.');
logger.error(error);
process.exit(1);
});
}

/**
Expand All @@ -81,9 +76,6 @@ function startNodemon(args: CliArgs) {
* @returns {Promise<void>}
*/
const dev: HandlerFn = async args => {
// Force NODE_ENV as development
process.env.NODE_ENV = 'development';

// Set force SPA flag so server can react accordingly
if (args.forceSPA || args.forceSPAWithHMR) {
args.legacy = true; // SPA only supports es5 versions
Expand All @@ -101,6 +93,16 @@ const dev: HandlerFn = async args => {
// Start watch compiler
await watchCompiler(compiler, args, imaConfig);

/**
* Set public env variable which is used to load assets in the SSR error view correctly.
* CLI Args should always override the config values.
*/
const devServerConfig = createDevServerConfig({ imaConfig, args });
process.env.IMA_CLI_DEV_SERVER_PUBLIC = devServerConfig.public;

// Create dev server for HMR
createDevServer(compiler, devServerConfig.hostname, devServerConfig.port);

// Start nodemon and application server
startNodemon(args);
} catch (error) {
Expand Down Expand Up @@ -140,5 +142,17 @@ export const builder: CommandBuilder = {
type: 'boolean',
default: false,
},
port: {
desc: 'Dev server port (overrides ima.config.js settings)',
type: 'number',
},
hostname: {
desc: 'Dev server hostname (overrides ima.config.js settings)',
type: 'string',
},
public: {
desc: 'Dev server public (overrides ima.config.js settings)',
type: 'string',
},
...resolveCliPluginArgs(CMD),
};
Loading

0 comments on commit 46d785d

Please sign in to comment.