fix(vite): environments api support in executor (#30183)
## Current Behavior `@nx/vite:build` executor does not support Vite 6 Environments API ## Expected Behavior `@nx/vite:build` executor builds all environments when Vite 6 is detected
This commit is contained in:
parent
320709f66f
commit
6fcb310e54
@ -58,6 +58,12 @@
|
||||
"skipPackageManager": {
|
||||
"type": "boolean",
|
||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||
},
|
||||
"useEnvironmentsApi": {
|
||||
"alias": "app",
|
||||
"type": "boolean",
|
||||
"description": "Use the new Environments API for building multiple environments at once. Only works with Vite 6.0.0 or higher.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"definitions": {},
|
||||
|
||||
@ -89,6 +89,103 @@ describe('Vite Plugin', () => {
|
||||
}, 200_000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up new React app with --bundler=vite option and use environments api', () => {
|
||||
let myApp;
|
||||
|
||||
beforeAll(() => {
|
||||
myApp = uniq('my-app');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${myApp} --bundler=vite --unitTestRunner=vitest`
|
||||
);
|
||||
updateJson(`${myApp}/project.json`, (json) => {
|
||||
json.targets.build.options.useEnvironmentsApi = true;
|
||||
return json;
|
||||
});
|
||||
updateFile(
|
||||
`${myApp}/vite.config.ts`,
|
||||
`/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir: './node_modules/.vite/${myApp}',
|
||||
server: {
|
||||
port: 4200,
|
||||
host: 'localhost',
|
||||
},
|
||||
preview: {
|
||||
port: 4300,
|
||||
host: 'localhost',
|
||||
},
|
||||
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
builder: {},
|
||||
environments: {
|
||||
ssr: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: '${myApp}/src/main.server.tsx'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: './dist/${myApp}',
|
||||
emptyOutDir: false,
|
||||
reportCompressedSize: true,
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory: './coverage/${myApp}',
|
||||
provider: 'v8',
|
||||
},
|
||||
},
|
||||
});
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`${myApp}/src/main.server.tsx`,
|
||||
`import React from 'react'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import App from './app/app';
|
||||
|
||||
export default async function render(_url: string, document: string) {
|
||||
const html = ReactDOMServer.renderToString(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
return document.replace('<!--app-html-->', html);
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmDist();
|
||||
});
|
||||
|
||||
it('should build application', async () => {
|
||||
runCLI(`build ${myApp}`);
|
||||
expect(readFile(`dist/${myApp}/favicon.ico`)).toBeDefined();
|
||||
expect(readFile(`dist/${myApp}/index.html`)).toBeDefined();
|
||||
expect(readFile(`dist/${myApp}/main.server.mjs`)).toBeDefined();
|
||||
}, 200_000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vite on Web apps', () => {
|
||||
|
||||
@ -20,7 +20,10 @@ import {
|
||||
} from '@nx/js';
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
import { relative, resolve } from 'path';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
} from '@nx/devkit/src/utils/async-iterable';
|
||||
import {
|
||||
createBuildableTsConfig,
|
||||
loadViteDynamicImport,
|
||||
@ -35,7 +38,8 @@ export async function* viteBuildExecutor(
|
||||
) {
|
||||
process.env.VITE_CJS_IGNORE_WARNING = 'true';
|
||||
// Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
|
||||
const { mergeConfig, build, resolveConfig } = await loadViteDynamicImport();
|
||||
const { mergeConfig, build, resolveConfig, createBuilder } =
|
||||
await loadViteDynamicImport();
|
||||
const projectRoot =
|
||||
context.projectsConfigurations.projects[context.projectName].root;
|
||||
const tsConfigForBuild = createBuildableTsConfig(
|
||||
@ -50,7 +54,7 @@ export async function* viteBuildExecutor(
|
||||
options.configFile
|
||||
);
|
||||
const root =
|
||||
projectRoot === '.'
|
||||
projectRoot === '.' || projectRoot === ''
|
||||
? process.cwd()
|
||||
: relative(context.cwd, joinPathFragments(context.root, projectRoot));
|
||||
|
||||
@ -100,108 +104,133 @@ export async function* viteBuildExecutor(
|
||||
});
|
||||
}
|
||||
|
||||
const watcherOrOutput = await build(buildConfig);
|
||||
const builder =
|
||||
createBuilder !== undefined && options.useEnvironmentsApi
|
||||
? await createBuilder(buildConfig)
|
||||
: // This is needed to ensure support for Vite 5
|
||||
{
|
||||
build: (inlineConfig) => build(inlineConfig),
|
||||
environments: { build: buildConfig },
|
||||
};
|
||||
|
||||
const libraryPackageJson = resolve(projectRoot, 'package.json');
|
||||
const rootPackageJson = resolve(context.root, 'package.json');
|
||||
let iterables: AsyncIterable<{ success: boolean; outfile?: string }>[] = [];
|
||||
for (const env of Object.values(builder.environments)) {
|
||||
// This is needed to overwrite the resolve build config with executor options in Vite 6
|
||||
if (env.config?.build) {
|
||||
env.config.build = {
|
||||
...env.config.build,
|
||||
...buildConfig.build,
|
||||
};
|
||||
}
|
||||
const watcherOrOutput = await builder.build(env as any);
|
||||
|
||||
// Here, we want the outdir relative to the workspace root.
|
||||
// So, we calculate the relative path from the workspace root to the outdir.
|
||||
const outDirRelativeToWorkspaceRoot = outDir.replaceAll('../', '');
|
||||
const distPackageJson = resolve(
|
||||
outDirRelativeToWorkspaceRoot,
|
||||
'package.json'
|
||||
);
|
||||
const libraryPackageJson = resolve(projectRoot, 'package.json');
|
||||
const rootPackageJson = resolve(context.root, 'package.json');
|
||||
|
||||
// Generate a package.json if option has been set.
|
||||
if (options.generatePackageJson) {
|
||||
if (context.projectGraph.nodes[context.projectName].type !== 'app') {
|
||||
logger.warn(
|
||||
stripIndents`The project ${context.projectName} is using the 'generatePackageJson' option which is deprecated for library projects. It should only be used for applications.
|
||||
// Here, we want the outdir relative to the workspace root.
|
||||
// So, we calculate the relative path from the workspace root to the outdir.
|
||||
const outDirRelativeToWorkspaceRoot = outDir.replaceAll('../', '');
|
||||
const distPackageJson = resolve(
|
||||
outDirRelativeToWorkspaceRoot,
|
||||
'package.json'
|
||||
);
|
||||
|
||||
// Generate a package.json if option has been set.
|
||||
if (options.generatePackageJson) {
|
||||
if (context.projectGraph.nodes[context.projectName].type !== 'app') {
|
||||
logger.warn(
|
||||
stripIndents`The project ${context.projectName} is using the 'generatePackageJson' option which is deprecated for library projects. It should only be used for applications.
|
||||
For libraries, configure the project to use the '@nx/dependency-checks' ESLint rule instead (https://nx.dev/nx-api/eslint-plugin/documents/dependency-checks).`
|
||||
);
|
||||
}
|
||||
|
||||
const builtPackageJson = createPackageJson(
|
||||
context.projectName,
|
||||
context.projectGraph,
|
||||
{
|
||||
target: context.targetName,
|
||||
root: context.root,
|
||||
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
||||
skipOverrides: options.skipOverrides,
|
||||
skipPackageManager: options.skipPackageManager,
|
||||
}
|
||||
);
|
||||
|
||||
builtPackageJson.type ??= 'module';
|
||||
|
||||
writeJsonFile(
|
||||
`${outDirRelativeToWorkspaceRoot}/package.json`,
|
||||
builtPackageJson
|
||||
);
|
||||
const packageManager = detectPackageManager(context.root);
|
||||
|
||||
const lockFile = createLockFile(
|
||||
builtPackageJson,
|
||||
context.projectGraph,
|
||||
packageManager
|
||||
);
|
||||
writeFileSync(
|
||||
`${outDirRelativeToWorkspaceRoot}/${getLockFileName(packageManager)}`,
|
||||
lockFile,
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
);
|
||||
}
|
||||
// For buildable libs, copy package.json if it exists.
|
||||
else if (
|
||||
options.generatePackageJson !== false &&
|
||||
!existsSync(distPackageJson) &&
|
||||
existsSync(libraryPackageJson) &&
|
||||
rootPackageJson !== libraryPackageJson
|
||||
) {
|
||||
await copyAssets(
|
||||
{
|
||||
outputPath: outDirRelativeToWorkspaceRoot,
|
||||
assets: [
|
||||
{
|
||||
input: projectRoot,
|
||||
output: '.',
|
||||
glob: 'package.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
const builtPackageJson = createPackageJson(
|
||||
context.projectName,
|
||||
context.projectGraph,
|
||||
{
|
||||
target: context.targetName,
|
||||
root: context.root,
|
||||
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
||||
skipOverrides: options.skipOverrides,
|
||||
skipPackageManager: options.skipPackageManager,
|
||||
const iterable = createAsyncIterable<{
|
||||
success: boolean;
|
||||
outfile?: string;
|
||||
}>(({ next, done }) => {
|
||||
if ('on' in watcherOrOutput) {
|
||||
let success = true;
|
||||
watcherOrOutput.on('event', (event) => {
|
||||
if (event.code === 'START') {
|
||||
success = true;
|
||||
} else if (event.code === 'ERROR') {
|
||||
success = false;
|
||||
} else if (event.code === 'END') {
|
||||
next({ success });
|
||||
}
|
||||
// result must be closed when present.
|
||||
// see https://rollupjs.org/guide/en/#rollupwatch
|
||||
if ('result' in event && event.result) {
|
||||
event.result.close();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const output =
|
||||
watcherOrOutput?.['output'] || watcherOrOutput?.[0]?.output;
|
||||
const fileName = output?.[0]?.fileName || 'main.cjs';
|
||||
const outfile = resolve(outDirRelativeToWorkspaceRoot, fileName);
|
||||
next({ success: true, outfile });
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
builtPackageJson.type ??= 'module';
|
||||
|
||||
writeJsonFile(
|
||||
`${outDirRelativeToWorkspaceRoot}/package.json`,
|
||||
builtPackageJson
|
||||
);
|
||||
const packageManager = detectPackageManager(context.root);
|
||||
|
||||
const lockFile = createLockFile(
|
||||
builtPackageJson,
|
||||
context.projectGraph,
|
||||
packageManager
|
||||
);
|
||||
writeFileSync(
|
||||
`${outDirRelativeToWorkspaceRoot}/${getLockFileName(packageManager)}`,
|
||||
lockFile,
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
);
|
||||
}
|
||||
// For buildable libs, copy package.json if it exists.
|
||||
else if (
|
||||
options.generatePackageJson !== false &&
|
||||
!existsSync(distPackageJson) &&
|
||||
existsSync(libraryPackageJson) &&
|
||||
rootPackageJson !== libraryPackageJson
|
||||
) {
|
||||
await copyAssets(
|
||||
{
|
||||
outputPath: outDirRelativeToWorkspaceRoot,
|
||||
assets: [
|
||||
{
|
||||
input: projectRoot,
|
||||
output: '.',
|
||||
glob: 'package.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
if ('on' in watcherOrOutput) {
|
||||
const iterable = createAsyncIterable<{ success: boolean }>(({ next }) => {
|
||||
let success = true;
|
||||
watcherOrOutput.on('event', (event) => {
|
||||
if (event.code === 'START') {
|
||||
success = true;
|
||||
} else if (event.code === 'ERROR') {
|
||||
success = false;
|
||||
} else if (event.code === 'END') {
|
||||
next({ success });
|
||||
}
|
||||
// result must be closed when present.
|
||||
// see https://rollupjs.org/guide/en/#rollupwatch
|
||||
if ('result' in event && event.result) {
|
||||
event.result.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
yield* iterable;
|
||||
} else {
|
||||
const output = watcherOrOutput?.['output'] || watcherOrOutput?.[0]?.output;
|
||||
const fileName = output?.[0]?.fileName || 'main.cjs';
|
||||
const outfile = resolve(outDirRelativeToWorkspaceRoot, fileName);
|
||||
yield { success: true, outfile };
|
||||
iterables.push(iterable);
|
||||
}
|
||||
return yield* combineAsyncIterables(iterables.shift(), ...(iterables ?? []));
|
||||
}
|
||||
|
||||
export async function getBuildExtraArgs(
|
||||
|
||||
@ -9,4 +9,5 @@ export interface ViteBuildExecutorOptions {
|
||||
skipTypeCheck?: boolean;
|
||||
tsConfig?: string;
|
||||
watch?: boolean;
|
||||
useEnvironmentsApi?: boolean;
|
||||
}
|
||||
|
||||
@ -67,6 +67,12 @@
|
||||
"skipPackageManager": {
|
||||
"type": "boolean",
|
||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
||||
},
|
||||
"useEnvironmentsApi": {
|
||||
"alias": "app",
|
||||
"type": "boolean",
|
||||
"description": "Use the new Environments API for building multiple environments at once. Only works with Vite 6.0.0 or higher.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"definitions": {},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user