From 1899a9ee35d25dd6f9395b17425a0a040259d405 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Wed, 11 Oct 2023 10:49:21 -0400 Subject: [PATCH] feat(vite): future-proof Vite plugin to support ESM-only version of Vite (#19533) --- e2e/vite/src/vite-esm.test.ts | 37 +++++++++++++++++++ packages/vite/src/executors/.gitkeep | 0 .../vite/src/executors/build/build.impl.ts | 14 +++---- .../executors/dev-server/dev-server.impl.ts | 13 +++++-- .../preview-server/preview-server.impl.ts | 9 ++++- .../vite/src/executors/test/vitest.impl.ts | 8 +++- packages/vite/src/generators/.gitkeep | 0 packages/vite/src/utils/options-utils.ts | 9 +++-- 8 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 e2e/vite/src/vite-esm.test.ts delete mode 100644 packages/vite/src/executors/.gitkeep delete mode 100644 packages/vite/src/generators/.gitkeep diff --git a/e2e/vite/src/vite-esm.test.ts b/e2e/vite/src/vite-esm.test.ts new file mode 100644 index 0000000000000..8a2c8e987451b --- /dev/null +++ b/e2e/vite/src/vite-esm.test.ts @@ -0,0 +1,37 @@ +import { + checkFilesExist, + newProject, + renameFile, + runCLI, + uniq, + updateJson, +} from '@nx/e2e/utils'; + +// TODO(jack): This test file can be removed when Vite goes ESM-only. +// This test ensures that when CJS is gone from the published `vite` package, Nx will continue to work. + +describe('Vite ESM tests', () => { + beforeAll(() => newProject({ unsetProjectNameAndRootFormat: false })); + + it('should build with Vite when it is ESM-only', async () => { + const appName = uniq('viteapp'); + runCLI(`generate @nx/react:app ${appName} --bundler=vite`); + + // .mts file is needed because Nx will transpile .ts files as CJS + renameFile(`${appName}/vite.config.ts`, `${appName}/vite.config.mts`); + + // Remove CJS entry point for Vite + updateJson('node_modules/vite/package.json', (json) => { + for (const [key, value] of Object.entries(json.exports['.'])) { + if (typeof value === 'string' && value.endsWith('.cjs')) { + delete json.exports['.'][key]; + } + } + return json; + }); + + runCLI(`build ${appName}`); + + checkFilesExist(`dist/${appName}/index.html`); + }); +}); diff --git a/packages/vite/src/executors/.gitkeep b/packages/vite/src/executors/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/packages/vite/src/executors/build/build.impl.ts b/packages/vite/src/executors/build/build.impl.ts index 099a7daea664e..922e508f04291 100644 --- a/packages/vite/src/executors/build/build.impl.ts +++ b/packages/vite/src/executors/build/build.impl.ts @@ -5,7 +5,6 @@ import { stripIndents, writeJsonFile, } from '@nx/devkit'; -import { build, InlineConfig, mergeConfig } from 'vite'; import { getProjectTsConfigPath, getViteBuildOptions, @@ -30,6 +29,11 @@ export async function* viteBuildExecutor( options: ViteBuildExecutorOptions, context: ExecutorContext ) { + // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future. + const { mergeConfig, build } = await (Function( + 'return import("vite")' + )() as Promise); + const projectRoot = context.projectsConfigurations.projects[context.projectName].root; @@ -52,7 +56,7 @@ export async function* viteBuildExecutor( }); } - const watcherOrOutput = await runInstance(buildConfig); + const watcherOrOutput = await build(buildConfig); const libraryPackageJson = resolve(projectRoot, 'package.json'); const rootPackageJson = resolve(context.root, 'package.json'); @@ -143,12 +147,6 @@ export async function* viteBuildExecutor( } } -function runInstance(options: InlineConfig) { - return build({ - ...options, - }); -} - function normalizeOptions(options: ViteBuildExecutorOptions) { const normalizedOptions = { ...options }; diff --git a/packages/vite/src/executors/dev-server/dev-server.impl.ts b/packages/vite/src/executors/dev-server/dev-server.impl.ts index 5e5456bc9f139..f3960b8e6129a 100644 --- a/packages/vite/src/executors/dev-server/dev-server.impl.ts +++ b/packages/vite/src/executors/dev-server/dev-server.impl.ts @@ -1,11 +1,11 @@ import { ExecutorContext } from '@nx/devkit'; -import { createServer, InlineConfig, mergeConfig, ViteDevServer } from 'vite'; +import type { InlineConfig, ViteDevServer } from 'vite'; import { - getViteSharedConfig, getNxTargetOptions, - getViteServerOptions, getViteBuildOptions, + getViteServerOptions, + getViteSharedConfig, } from '../../utils/options-utils'; import { ViteDevServerExecutorOptions } from './schema'; @@ -16,6 +16,11 @@ export async function* viteDevServerExecutor( options: ViteDevServerExecutorOptions, context: ExecutorContext ): AsyncGenerator<{ success: boolean; baseUrl: string }> { + // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future. + const { mergeConfig, createServer } = await (Function( + 'return import("vite")' + )() as Promise); + const projectRoot = context.projectsConfigurations.projects[context.projectName].root; @@ -39,7 +44,7 @@ export async function* viteDevServerExecutor( getViteSharedConfig(mergedOptions, options.clearScreen, context), { build: getViteBuildOptions(mergedOptions, context), - server: getViteServerOptions(mergedOptions, context), + server: await getViteServerOptions(mergedOptions, context), } ); diff --git a/packages/vite/src/executors/preview-server/preview-server.impl.ts b/packages/vite/src/executors/preview-server/preview-server.impl.ts index ff4d4e7911d72..3b08ffcfdcba7 100644 --- a/packages/vite/src/executors/preview-server/preview-server.impl.ts +++ b/packages/vite/src/executors/preview-server/preview-server.impl.ts @@ -1,10 +1,10 @@ import { ExecutorContext, parseTargetString, runExecutor } from '@nx/devkit'; -import { InlineConfig, mergeConfig, preview, PreviewServer } from 'vite'; +import type { InlineConfig, PreviewServer } from 'vite'; import { getNxTargetOptions, - getViteSharedConfig, getViteBuildOptions, getVitePreviewOptions, + getViteSharedConfig, } from '../../utils/options-utils'; import { ViteBuildExecutorOptions } from '../build/schema'; import { VitePreviewServerExecutorOptions } from './schema'; @@ -17,6 +17,11 @@ export async function* vitePreviewServerExecutor( options: VitePreviewServerExecutorOptions, context: ExecutorContext ) { + // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future. + const { mergeConfig, preview } = await (Function( + 'return import("vite")' + )() as Promise); + const target = parseTargetString(options.buildTarget, context); const targetConfiguration = context.projectsConfigurations.projects[target.project]?.targets[ diff --git a/packages/vite/src/executors/test/vitest.impl.ts b/packages/vite/src/executors/test/vitest.impl.ts index 692e9bdf247ee..1bb83afd94a39 100644 --- a/packages/vite/src/executors/test/vitest.impl.ts +++ b/packages/vite/src/executors/test/vitest.impl.ts @@ -6,8 +6,7 @@ import { stripIndents, workspaceRoot, } from '@nx/devkit'; -import { CoverageOptions, File, Reporter } from 'vitest'; -import { loadConfigFromFile } from 'vite'; +import type { CoverageOptions, File, Reporter } from 'vitest'; import { VitestExecutorOptions } from './schema'; import { join, relative, resolve } from 'path'; import { existsSync } from 'fs'; @@ -100,6 +99,11 @@ async function getSettings( context: ExecutorContext, projectRoot: string ) { + // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future. + const { loadConfigFromFile } = await (Function( + 'return import("vite")' + )() as Promise); + const packageJsonPath = join(workspaceRoot, 'package.json'); const packageJson = existsSync(packageJsonPath) ? readJsonFile(packageJsonPath) diff --git a/packages/vite/src/generators/.gitkeep b/packages/vite/src/generators/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/packages/vite/src/utils/options-utils.ts b/packages/vite/src/utils/options-utils.ts index 63502e87e4c5a..498153900b133 100644 --- a/packages/vite/src/utils/options-utils.ts +++ b/packages/vite/src/utils/options-utils.ts @@ -12,7 +12,6 @@ import { InlineConfig, PluginOption, PreviewOptions, - searchForWorkspaceRoot, ServerOptions, } from 'vite'; import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema'; @@ -109,10 +108,14 @@ export function getViteSharedConfig( /** * Builds the options for the vite dev server. */ -export function getViteServerOptions( +export async function getViteServerOptions( options: ViteDevServerExecutorOptions, context: ExecutorContext -): ServerOptions { +): Promise { + // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future. + const { searchForWorkspaceRoot } = await (Function( + 'return import("vite")' + )() as Promise); const projectRoot = context.projectsConfigurations.projects[context.projectName].root; const serverOptions: ServerOptions = {