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:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Added `EXPERIMENTAL_VITE` flag for using [vite](https://vitejs.dev) as dev server instead of Webpack
|
||||
@@ -430,6 +430,7 @@ Valentina
|
||||
validator
|
||||
validators
|
||||
varchar
|
||||
vite
|
||||
VMware
|
||||
Vodafone
|
||||
VPCs
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user