feat(cli): backend webpack serve

This commit is contained in:
Ivan Shmidt
2020-06-10 21:49:55 +02:00
parent 7b123baf40
commit d2e25082df
10 changed files with 194 additions and 79 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "example-backend",
"version": "0.1.1-alpha.7",
"main": "dist",
"main": "dist/index.cjs",
"types": "src/index.ts",
"private": true,
"license": "Apache-2.0",
@@ -9,8 +9,8 @@
"node": ">=12"
},
"scripts": {
"build": "tsc",
"start": "backstage-cli watch-deps --build -- tsc-watch --onFirstSuccess \\\"nodemon -r esm\\\"",
"build": "backstage-cli build --outputs cjs",
"start": "backstage-cli watch-deps --build -- backstage-cli backend:dev",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean",
+4
View File
@@ -39,6 +39,8 @@
"@rollup/plugin-node-resolve": "^7.1.1",
"@spotify/eslint-config": "^7.0.1",
"@sucrase/webpack-loader": "^2.0.0",
"@types/start-server-webpack-plugin": "^2.2.0",
"@types/webpack-node-externals": "^1.7.1",
"bfj": "^7.0.2",
"chalk": "^4.0.0",
"chokidar": "^3.3.1",
@@ -72,6 +74,7 @@
"rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^3.1.1",
"rollup-plugin-typescript2": "^0.26.0",
"start-server-webpack-plugin": "^2.2.5",
"style-loader": "^1.2.1",
"sucrase": "^3.14.1",
"tar": "^6.0.1",
@@ -81,6 +84,7 @@
"url-loader": "^4.1.0",
"webpack": "^4.41.6",
"webpack-dev-server": "^3.10.3",
"webpack-node-externals": "^1.7.2",
"yaml": "^1.10.0",
"yml-loader": "^2.1.0",
"yn": "^4.0.0"
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
import { Command } from 'commander';
import { serveBackend } from '../../lib/bundler/backend';
export default async (cmd: Command) => {
const appConfigs = await loadConfig();
const waitForExit = await serveBackend({
entry: 'src/index',
checksEnabled: cmd.check,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
});
await waitForExit();
};
+6
View File
@@ -41,6 +41,12 @@ const main = (argv: string[]) => {
.option('--check', 'Enable type checking and linting')
.action(lazyAction(() => import('./commands/app/serve'), 'default'));
program
.command('backend:dev')
.description('Start local development server with HMR for the backend')
.option('--check', 'Enable type checking and linting')
.action(lazyAction(() => import('./commands/backend/dev'), 'default'));
program
.command('app:diff')
.option('--check', 'Fail if changes are required')
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import webpack from 'webpack';
import { createConfig } from './config';
import { resolveBundlingPaths } from './paths';
import { ServeOptions } from './types';
export async function serveBackend(options: ServeOptions) {
const paths = resolveBundlingPaths(options);
const config = createConfig(paths, {
...options,
isDev: true,
isBackend: true,
});
const compiler = webpack(config);
const watcher = compiler.watch(
{
poll: true,
},
(err: Error, _stats: any) => {
console.log('Build succeeded');
if (err) {
console.error(err);
}
},
);
const waitForExit = async () => {
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.on(signal, () => {
// server.close();
watcher.close(() => console.log('Watcher closed'));
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
process.exit();
});
}
// Block indefinitely and wait for the interrupt signal
return new Promise(() => {});
};
return waitForExit;
}
+62 -17
View File
@@ -14,13 +14,15 @@
* limitations under the License.
*/
import webpack from 'webpack';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
import StartServerPlugin from 'start-server-webpack-plugin';
import webpack from 'webpack';
import nodeExternals from 'webpack-node-externals';
import { optimization } from './optimization';
import { BundlingPaths } from './paths';
import { transforms } from './transforms';
import { optimization } from './optimization';
import { BundlingOptions } from './types';
// import checkRequiredFiles from 'react-dev-utils/checkRequiredFiles';
// import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin';
@@ -32,7 +34,7 @@ export function createConfig(
paths: BundlingPaths,
options: BundlingOptions,
): webpack.Configuration {
const { checksEnabled, isDev } = options;
const { checksEnabled, isDev, isBackend } = options;
const { plugins, loaders } = transforms(options);
@@ -80,16 +82,65 @@ export function createConfig(
return {
mode: isDev ? 'development' : 'production',
profile: false,
...(isBackend
? {
watch: true,
watchOptions: {
ignored: [/node_modules\/(?!\@backstage)/],
// poll: 1000
},
externals: [
nodeExternals({
modulesDir: paths.rootNodeModules,
whitelist: [
'webpack/hot/poll?100',
/\@backstage\/.*\/(?!node_modules)/,
],
}),
nodeExternals({
modulesDir: paths.targetNodeModules,
whitelist: [
'webpack/hot/poll?100',
/\@backstage\/.*\/(?!node_modules)/,
],
}),
],
target: 'node',
node: {
__dirname: true,
__filename: true,
global: true,
},
}
: {
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
optimization: optimization(options),
}),
bail: false,
performance: {
hints: false, // we check the gzip size instead
},
devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map',
context: paths.targetPath,
entry: [require.resolve('react-hot-loader/patch'), paths.targetEntry],
entry: [
...(isBackend
? ['webpack/hot/poll?100']
: [require.resolve('react-hot-loader/patch')]),
paths.targetEntry,
],
resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
mainFields: ['main:src', 'browser', 'module', 'main'],
modules: [paths.targetNodeModules, paths.rootNodeModules],
plugins: [
new ModuleScopePlugin(
[paths.targetSrc, paths.targetDev],
@@ -111,17 +162,11 @@ export function createConfig(
? '[name].chunk.js'
: '[name].[chunkhash:8].chunk.js',
},
optimization: optimization(options),
plugins,
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
plugins: isBackend
? [
new StartServerPlugin('main.js'),
new webpack.HotModuleReplacementPlugin(),
]
: plugins,
};
}
+1
View File
@@ -14,5 +14,6 @@
* limitations under the License.
*/
export { serveBackend } from './backend';
export { buildBundle } from './bundle';
export { serveBundle } from './server';
+2
View File
@@ -60,6 +60,8 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) {
targetTsConfig: paths.resolveTargetRoot('tsconfig.json'),
targetNodeModules: paths.resolveTarget('node_modules'),
targetPackageJson: paths.resolveTarget('package.json'),
rootNodeModules: paths.resolveTargetRoot('node_modules'),
root: paths.targetRoot,
};
}
+1
View File
@@ -22,6 +22,7 @@ export type BundlingOptions = {
isDev: boolean;
config: Config;
appConfigs: AppConfig[];
isBackend?: boolean;
};
export type ServeOptions = BundlingPathsOptions & {