Merge pull request #20331 from backstage/blam/vite

[cli] Added `EXPERIMENTAL_VITE` flag to start `devServer` with `vite` instead of `webpack`
This commit is contained in:
Ben Lambert
2023-10-20 19:31:03 +02:00
committed by GitHub
9 changed files with 641 additions and 264 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Added `EXPERIMENTAL_VITE` flag for using [vite](https://vitejs.dev) as dev server instead of Webpack
+1
View File
@@ -430,6 +430,7 @@ Valentina
validator
validators
varchar
vite
VMware
Vodafone
VPCs
+4
View File
@@ -93,12 +93,16 @@
"@roadiehq/backstage-plugin-github-insights": "^2.0.5",
"@roadiehq/backstage-plugin-github-pull-requests": "^2.2.7",
"@roadiehq/backstage-plugin-travis-ci": "^2.0.5",
"@vitejs/plugin-react": "^4.0.4",
"history": "^5.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-use": "^17.2.4",
"vite": "^4.4.9",
"vite-plugin-html": "^3.2.0",
"vite-plugin-node-polyfills": "^0.14.1",
"zen-observable": "^0.10.0"
},
"devDependencies": {
+18 -1
View File
@@ -153,6 +153,7 @@
"@backstage/theme": "workspace:^",
"@types/cross-spawn": "^6.0.2",
"@types/diff": "^5.0.0",
"@types/ejs": "^3.1.3",
"@types/express": "^4.17.6",
"@types/fs-extra": "^9.0.1",
"@types/http-proxy": "^1.17.4",
@@ -174,11 +175,27 @@
"type-fest": "^2.19.0"
},
"peerDependencies": {
"@microsoft/api-extractor": "^7.21.2"
"@microsoft/api-extractor": "^7.21.2",
"@vitejs/plugin-react": "^4.0.4",
"vite": "^4.4.9",
"vite-plugin-html": "^3.2.0",
"vite-plugin-node-polyfills": "^0.14.1"
},
"peerDependenciesMeta": {
"@microsoft/api-extractor": {
"optional": true
},
"@vitejs/plugin-react": {
"optional": true
},
"vite": {
"optional": true
},
"vite-plugin-html": {
"optional": true
},
"vite-plugin-node-polyfills": {
"optional": true
}
},
"files": [
+1 -9
View File
@@ -39,6 +39,7 @@ import { runPlain } from '../run';
import { transforms } from './transforms';
import { version } from '../../lib/version';
import yn from 'yn';
import { hasReactDomClient } from './hasReactDomClient';
const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE';
@@ -81,15 +82,6 @@ async function readBuildInfo() {
};
}
function hasReactDomClient() {
try {
require.resolve('react-dom/client');
return true;
} catch {
return false;
}
}
export async function createConfig(
paths: BundlingPaths,
options: BundlingOptions,
@@ -0,0 +1,23 @@
/*
* Copyright 2023 The Backstage Authors
*
* 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.
*/
export function hasReactDomClient() {
try {
require.resolve('react-dom/client');
return true;
} catch {
return false;
}
}
+106 -53
View File
@@ -34,6 +34,7 @@ import { createConfig, resolveBaseUrl } from './config';
import { createDetectedModulesEntryPoint } from './packageDetection';
import { resolveBundlingPaths } from './paths';
import { ServeOptions } from './types';
import { hasReactDomClient } from './hasReactDomClient';
export async function serveBundle(options: ServeOptions) {
const paths = resolveBundlingPaths(options);
@@ -77,7 +78,9 @@ export async function serveBundle(options: ServeOptions) {
const { name } = await fs.readJson(libPaths.resolveTarget('package.json'));
let server: WebpackDevServer | undefined = undefined;
let webpackServer: WebpackDevServer | undefined = undefined;
let viteServer: import('vite').ViteDevServer | undefined = undefined;
let latestFrontendAppConfigs: AppConfig[] = [];
const cliConfig = await loadCliConfig({
@@ -86,7 +89,9 @@ export async function serveBundle(options: ServeOptions) {
withFilteredKeys: true,
watch(appConfigs) {
latestFrontendAppConfigs = appConfigs;
server?.invalidate();
webpackServer?.invalidate();
viteServer?.restart();
},
});
latestFrontendAppConfigs = cliConfig.frontendAppConfigs;
@@ -123,7 +128,8 @@ export async function serveBundle(options: ServeOptions) {
config: fullConfig,
targetPath: paths.targetPath,
watch() {
server?.invalidate();
webpackServer?.invalidate();
viteServer?.restart();
},
});
@@ -139,64 +145,111 @@ export async function serveBundle(options: ServeOptions) {
additionalEntryPoints: detectedModulesEntryPoint,
});
const compiler = webpack(config);
server = new WebpackDevServer(
{
hot: !process.env.CI,
devMiddleware: {
publicPath: config.output?.publicPath as string,
stats: 'errors-warnings',
if (process.env.EXPERIMENTAL_VITE) {
const { default: vite } = await import('vite');
const { default: viteReact } = await import('@vitejs/plugin-react');
const { nodePolyfills: viteNodePolyfills } = await import(
'vite-plugin-node-polyfills'
);
const { createHtmlPlugin: viteHtml } = await import('vite-plugin-html');
viteServer = await vite.createServer({
define: {
global: 'window',
'process.argv': JSON.stringify(process.argv),
'process.env.APP_CONFIG': JSON.stringify(cliConfig.frontendAppConfigs),
// This allows for conditional imports of react-dom/client, since there's no way
// to check for presence of it in source code without module resolution errors.
'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify(hasReactDomClient()),
},
static: paths.targetPublic
? {
publicPath: config.output?.publicPath as string,
directory: paths.targetPublic,
}
: undefined,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
// The index needs to be rewritten relative to the new public path, including subroutes.
index: `${config.output?.publicPath}index.html`,
plugins: [
viteReact(),
viteNodePolyfills(),
viteHtml({
entry: paths.targetEntry,
// todo(blam): we should look at contributing to thPe plugin here
// to support absolute paths, but works in the interim at least.
template: 'public/index.html',
inject: {
data: {
config: frontendConfig,
publicPath: config.output?.publicPath,
},
},
}),
],
server: {
host,
port,
},
https:
url.protocol === 'https:'
? {
cert: fullConfig.getString('app.https.certificate.cert'),
key: fullConfig.getString('app.https.certificate.key'),
}
: false,
host,
port,
proxy: targetPkg.proxy,
// When the dev server is behind a proxy, the host and public hostname differ
allowedHosts: [url.hostname],
client: {
webSocketURL: 'auto://0.0.0.0:0/ws',
},
} as any,
compiler as any,
);
await new Promise<void>((resolve, reject) => {
server?.startCallback((err?: Error) => {
if (err) {
reject(err);
return;
}
openBrowser(url.href);
resolve();
publicDir: paths.targetPublic,
root: paths.targetPath,
});
} else {
const compiler = webpack(config);
webpackServer = new WebpackDevServer(
{
hot: !process.env.CI,
devMiddleware: {
publicPath: config.output?.publicPath as string,
stats: 'errors-warnings',
},
static: paths.targetPublic
? {
publicPath: config.output?.publicPath as string,
directory: paths.targetPublic,
}
: undefined,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
// The index needs to be rewritten relative to the new public path, including subroutes.
index: `${config.output?.publicPath}index.html`,
},
https:
url.protocol === 'https:'
? {
cert: fullConfig.getString('app.https.certificate.cert'),
key: fullConfig.getString('app.https.certificate.key'),
}
: false,
host,
port,
proxy: targetPkg.proxy,
// When the dev server is behind a proxy, the host and public hostname differ
allowedHosts: [url.hostname],
client: {
webSocketURL: 'auto://0.0.0.0:0/ws',
},
},
compiler,
);
}
await viteServer?.listen();
await new Promise<void>(async (resolve, reject) => {
if (webpackServer) {
webpackServer.startCallback((err?: Error) => {
if (err) {
reject(err);
return;
}
resolve();
});
} else {
resolve();
}
});
openBrowser(url.href);
const waitForExit = async () => {
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.on(signal, () => {
server?.close();
webpackServer?.close();
viteServer?.close();
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
process.exit();
});
@@ -64,9 +64,7 @@ import {
selectedTemplateRouteRef,
} from '../../routes';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
// typings are wrong for this library, so fallback to not parsing types.
const humanizeDuration = require('humanize-duration');
import humanizeDuration from 'humanize-duration';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
+482 -198
View File
File diff suppressed because it is too large Load Diff