feat(cli): backend webpack serve
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { serveBackend } from './backend';
|
||||
export { buildBundle } from './bundle';
|
||||
export { serveBundle } from './server';
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export type BundlingOptions = {
|
||||
isDev: boolean;
|
||||
config: Config;
|
||||
appConfigs: AppConfig[];
|
||||
isBackend?: boolean;
|
||||
};
|
||||
|
||||
export type ServeOptions = BundlingPathsOptions & {
|
||||
|
||||
Reference in New Issue
Block a user