feat(vite): add a preview-server executor for Vite (#14326)

This commit is contained in:
Victor Berchet 2023-01-13 10:45:35 -08:00 committed by GitHub
parent 00caf6ae5e
commit c94ac41f56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 447 additions and 36 deletions

View File

@ -6000,6 +6000,14 @@
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "preview-server",
"path": "/packages/vite/executors/preview-server",
"name": "preview-server",
"children": [],
"isExternal": false,
"disableCollapsible": false
}
],
"isExternal": false,

View File

@ -2655,6 +2655,15 @@
"originalFilePath": "/packages/vite/src/executors/test/schema.json",
"path": "/packages/vite/executors/test",
"type": "executor"
},
"/packages/vite/executors/preview-server": {
"description": "Vite preview server",
"file": "generated/packages/vite/executors/preview-server.json",
"hidden": false,
"name": "preview-server",
"originalFilePath": "/packages/vite/src/executors/preview-server/schema.json",
"path": "/packages/vite/executors/preview-server",
"type": "executor"
}
},
"generators": {

View File

@ -2624,6 +2624,15 @@
"originalFilePath": "/packages/vite/src/executors/test/schema.json",
"path": "vite/executors/test",
"type": "executor"
},
{
"description": "Vite preview server",
"file": "generated/packages/vite/executors/preview-server.json",
"hidden": false,
"name": "preview-server",
"originalFilePath": "/packages/vite/src/executors/preview-server/schema.json",
"path": "vite/executors/preview-server",
"type": "executor"
}
],
"generators": [

View File

@ -0,0 +1,55 @@
{
"name": "preview-server",
"implementation": "/packages/vite/src/executors/preview-server/preview-server.impl.ts",
"schema": {
"$schema": "http://json-schema.org/schema",
"version": 2,
"cli": "nx",
"title": "Vite Preview Server",
"description": "Preview Server for Vite.",
"type": "object",
"presets": [
{ "name": "Default minimum setup", "keys": ["buildTarget"] },
{ "name": "Using a Different Port", "keys": ["buildTarget", "port"] }
],
"properties": {
"buildTarget": {
"type": "string",
"description": "Target which builds the application."
},
"proxyConfig": {
"type": "string",
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
},
"port": { "type": "number", "description": "Port to listen on." },
"host": {
"description": "Specify which IP addresses the server should listen on.",
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
},
"https": { "type": "boolean", "description": "Serve using HTTPS." },
"open": {
"description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
},
"logLevel": {
"type": "string",
"description": "Adjust console output verbosity.",
"enum": ["info", "warn", "error", "silent"]
},
"mode": { "type": "string", "description": "Mode to run the server in." },
"clearScreen": {
"description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
"type": "boolean"
}
},
"definitions": {},
"required": ["buildTarget"],
"examplesFile": "`project.json`:\n\n```json\n//...\n\"my-app\": {\n \"targets\": {\n //...\n \"preview\": {\n \"executor\": \"@nrwl/vite:preview-server\",\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n },\n \"configurations\": {\n ...\n }\n },\n }\n}\n```\n\n```bash\nnx preview my-app\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Set up a custom port\" %}\n\nYou can always set the port in your `vite.config.ts` file. However, you can also set it directly in your `project.json` file, in the `preview` target options:\n\n```json\n//...\n\"my-app\": {\n \"targets\": {\n //...\n \"preview\": {\n \"executor\": \"@nrwl/vite:preview-server\",\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200,\n },\n \"configurations\": {\n ...\n }\n },\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Specify a proxyConfig\" %}\n\nYou can specify a proxy config by pointing to the path of your proxy configuration file:\n\n```json\n//...\n\"my-app\": {\n \"targets\": {\n //...\n \"preview\": {\n \"executor\": \"@nrwl/vite:preview-server\",\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"proxyConfig\": \"apps/my-app/proxy.conf.json\"\n },\n \"configurations\": {\n ...\n }\n },\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
},
"description": "Vite preview server",
"aliases": [],
"hidden": false,
"path": "/packages/vite/src/executors/preview-server/schema.json",
"type": "executor"
}

View File

@ -0,0 +1,80 @@
`project.json`:
```json
//...
"my-app": {
"targets": {
//...
"preview": {
"executor": "@nrwl/vite:preview-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-app:build",
},
"configurations": {
...
}
},
}
}
```
```bash
nx preview my-app
```
## Examples
{% tabs %}
{% tab label="Set up a custom port" %}
You can always set the port in your `vite.config.ts` file. However, you can also set it directly in your `project.json` file, in the `preview` target options:
```json
//...
"my-app": {
"targets": {
//...
"preview": {
"executor": "@nrwl/vite:preview-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-app:build",
"port": 4200,
},
"configurations": {
...
}
},
}
}
```
{% /tab %}
{% tab label="Specify a proxyConfig" %}
You can specify a proxy config by pointing to the path of your proxy configuration file:
```json
//...
"my-app": {
"targets": {
//...
"preview": {
"executor": "@nrwl/vite:preview-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-app:build",
"proxyConfig": "apps/my-app/proxy.conf.json"
},
"configurations": {
...
}
},
}
}
```
{% /tab %}
{% /tabs %}

View File

@ -14,6 +14,11 @@
"implementation": "./src/executors/test/compat",
"schema": "./src/executors/test/schema.json",
"description": "Test with Vitest"
},
"preview-server": {
"implementation": "./src/executors/preview-server/compat",
"schema": "./src/executors/preview-server/schema.json",
"description": "Vite preview server"
}
},
"executors": {
@ -31,6 +36,11 @@
"implementation": "./src/executors/test/vitest.impl",
"schema": "./src/executors/test/schema.json",
"description": "Test with Vitest"
},
"preview-server": {
"implementation": "./src/executors/preview-server/preview-server.impl",
"schema": "./src/executors/preview-server/schema.json",
"description": "Vite preview server"
}
}
}

View File

@ -63,7 +63,7 @@ export default async function* viteDevServerExecutor(
}
// This Promise intentionally never resolves, leaving the process running
await new Promise<{ success: boolean }>(() => {});
await new Promise(() => {});
}
async function runViteDevServer(server: ViteDevServer): Promise<void> {

View File

@ -1,4 +1,3 @@
import type { FileReplacement } from '../../plugins/rollup-replace-files.plugin';
export interface ViteDevServerExecutorOptions {
buildTarget: string;
proxyConfig?: string;
@ -8,7 +7,7 @@ export interface ViteDevServerExecutorOptions {
hmr?: boolean;
open?: string | boolean;
cors?: boolean;
logLevel?: info | warn | error | silent;
logLevel?: 'info' | 'warn' | 'error' | 'silent';
mode?: string;
clearScreen?: boolean;
force?: boolean;

View File

@ -0,0 +1,4 @@
import { convertNxExecutor } from '@nrwl/devkit';
import vitePreviewServerExecutor from './preview-server.impl';
export default convertNxExecutor(vitePreviewServerExecutor);

View File

@ -0,0 +1,88 @@
import { ExecutorContext, parseTargetString, runExecutor } from '@nrwl/devkit';
import { InlineConfig, mergeConfig, preview } from 'vite';
import {
getNxTargetOptions,
getViteSharedConfig,
getViteBuildOptions,
getVitePreviewOptions,
} from '../../utils/options-utils';
import { ViteBuildExecutorOptions } from '../build/schema';
import { VitePreviewServerExecutorOptions } from './schema';
export default async function* vitePreviewServerExecutor(
options: VitePreviewServerExecutorOptions,
context: ExecutorContext
) {
// Retrieve the option for the configured buildTarget.
const buildTargetOptions: ViteBuildExecutorOptions = getNxTargetOptions(
options.buildTarget,
context
);
// Merge the options from the build and preview-serve targets.
// The latter takes precedence.
const mergedOptions = {
...buildTargetOptions,
...options,
};
// Launch the build target.
const target = parseTargetString(options.buildTarget, context.projectGraph);
const build = await runExecutor(target, mergedOptions, context);
for await (const result of build) {
if (!result.success) {
return result;
}
}
// Launch the server.
const serverConfig: InlineConfig = mergeConfig(
getViteSharedConfig(mergedOptions, options.clearScreen, context),
{
build: getViteBuildOptions(mergedOptions, context),
preview: getVitePreviewOptions(mergedOptions, context),
}
);
if (serverConfig.mode === 'production') {
console.warn('WARNING: preview is not meant to be run in production!');
}
try {
const server = await preview(serverConfig);
server.printUrls();
const processOnExit = async () => {
const { httpServer } = server;
// closeAllConnections was added in Node v18.2.0
httpServer.closeAllConnections && httpServer.closeAllConnections();
httpServer.close(() => {
process.off('SIGINT', processOnExit);
process.off('SIGTERM', processOnExit);
process.off('exit', processOnExit);
});
};
process.on('SIGINT', processOnExit);
process.on('SIGTERM', processOnExit);
process.on('exit', processOnExit);
const resolvedUrls = [
...server.resolvedUrls.local,
...server.resolvedUrls.network,
];
yield {
success: true,
baseUrl: resolvedUrls[0] ?? '',
};
} catch (e) {
console.error(e);
yield {
success: false,
baseUrl: '',
};
}
await new Promise(() => {});
}

View File

@ -0,0 +1,11 @@
export interface VitePreviewServerExecutorOptions {
buildTarget: string;
proxyConfig?: string;
port?: number;
host?: string | boolean;
https?: boolean;
open?: string | boolean;
logLevel?: 'info' | 'warn' | 'error' | 'silent';
mode?: string;
clearScreen?: boolean;
}

View File

@ -0,0 +1,75 @@
{
"$schema": "http://json-schema.org/schema",
"version": 2,
"cli": "nx",
"title": "Vite Preview Server",
"description": "Preview Server for Vite.",
"type": "object",
"presets": [
{
"name": "Default minimum setup",
"keys": ["buildTarget"]
},
{
"name": "Using a Different Port",
"keys": ["buildTarget", "port"]
}
],
"properties": {
"buildTarget": {
"type": "string",
"description": "Target which builds the application."
},
"proxyConfig": {
"type": "string",
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
},
"port": {
"type": "number",
"description": "Port to listen on."
},
"host": {
"description": "Specify which IP addresses the server should listen on.",
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"https": {
"type": "boolean",
"description": "Serve using HTTPS."
},
"open": {
"description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"logLevel": {
"type": "string",
"description": "Adjust console output verbosity.",
"enum": ["info", "warn", "error", "silent"]
},
"mode": {
"type": "string",
"description": "Mode to run the server in."
},
"clearScreen": {
"description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
"type": "boolean"
}
},
"definitions": {},
"required": ["buildTarget"],
"examplesFile": "../../../docs/preview-server-examples.md"
}

View File

@ -11,13 +11,57 @@ import {
BuildOptions,
InlineConfig,
PluginOption,
PreviewOptions,
searchForWorkspaceRoot,
ServerOptions,
} from 'vite';
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import replaceFiles from '../../plugins/rollup-replace-files.plugin';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
/**
* Returns the path to the vite config file or undefined when not found.
*/
export function normalizeViteConfigFilePath(
projectRoot: string,
configFile?: string
): string | undefined {
return configFile && existsSync(joinPathFragments(configFile))
? configFile
: existsSync(joinPathFragments(`${projectRoot}/vite.config.ts`))
? joinPathFragments(`${projectRoot}/vite.config.ts`)
: existsSync(joinPathFragments(`${projectRoot}/vite.config.js`))
? joinPathFragments(`${projectRoot}/vite.config.js`)
: undefined;
}
/**
* Returns the path to the proxy configuration file or undefined when not found.
*/
export function getViteServerProxyConfigPath(
nxProxyConfig: string | undefined,
context: ExecutorContext
): string | undefined {
if (nxProxyConfig) {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
const proxyConfigPath = nxProxyConfig
? join(context.root, nxProxyConfig)
: join(projectRoot, 'proxy.conf.json');
if (existsSync(proxyConfigPath)) {
return proxyConfigPath;
}
}
}
/**
* Builds the shared options for vite.
*
* Most shared options are derived from the build target.
*/
export function getViteSharedConfig(
options: ViteBuildExecutorOptions,
clearScreen: boolean | undefined,
@ -38,19 +82,9 @@ export function getViteSharedConfig(
};
}
export function normalizeViteConfigFilePath(
projectRoot: string,
configFile?: string
): string {
return configFile && existsSync(joinPathFragments(configFile))
? configFile
: existsSync(joinPathFragments(`${projectRoot}/vite.config.ts`))
? joinPathFragments(`${projectRoot}/vite.config.ts`)
: existsSync(joinPathFragments(`${projectRoot}/vite.config.js`))
? joinPathFragments(`${projectRoot}/vite.config.js`)
: undefined;
}
/**
* Builds the options for the vite dev server.
*/
export function getViteServerOptions(
options: ViteDevServerExecutorOptions,
context: ExecutorContext
@ -64,33 +98,29 @@ export function getViteServerOptions(
hmr: options.hmr,
open: options.open,
cors: options.cors,
};
if (options.proxyConfig) {
const proxyConfigPath = options.proxyConfig
? join(context.root, options.proxyConfig)
: join(projectRoot, 'proxy.conf.json');
if (existsSync(proxyConfigPath)) {
logger.info(`Loading proxy configuration from: ${proxyConfigPath}`);
serverOptions.proxy = require(proxyConfigPath);
serverOptions.fs = {
fs: {
allow: [
searchForWorkspaceRoot(joinPathFragments(projectRoot)),
joinPathFragments(context.root, 'node_modules/vite'),
],
},
};
}
const proxyConfigPath = getViteServerProxyConfigPath(
options.proxyConfig,
context
);
if (proxyConfigPath) {
logger.info(`Loading proxy configuration from: ${proxyConfigPath}`);
serverOptions.proxy = require(proxyConfigPath);
}
return serverOptions;
}
export function getNxTargetOptions(target: string, context: ExecutorContext) {
const targetObj = parseTargetString(target, context.projectGraph);
return readTargetOptions(targetObj, context);
}
/**
* Builds the build options for the vite.
*/
export function getViteBuildOptions(
options: ViteBuildExecutorOptions,
context: ExecutorContext
@ -114,3 +144,36 @@ export function getViteBuildOptions(
ssr: options.ssr,
};
}
/**
* Builds the options for the vite preview server.
*/
export function getVitePreviewOptions(
options: VitePreviewServerExecutorOptions,
context: ExecutorContext
): PreviewOptions {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
const serverOptions: ServerOptions = {
host: options.host,
port: options.port,
https: options.https,
open: options.open,
};
const proxyConfigPath = getViteServerProxyConfigPath(
options.proxyConfig,
context
);
if (proxyConfigPath) {
logger.info(`Loading proxy configuration from: ${proxyConfigPath}`);
serverOptions.proxy = require(proxyConfigPath);
}
return serverOptions;
}
export function getNxTargetOptions(target: string, context: ExecutorContext) {
const targetObj = parseTargetString(target, context.projectGraph);
return readTargetOptions(targetObj, context);
}