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": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"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": {},
|
"definitions": {},
|
||||||
|
|||||||
@ -89,6 +89,103 @@ describe('Vite Plugin', () => {
|
|||||||
}, 200_000);
|
}, 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', () => {
|
describe('Vite on Web apps', () => {
|
||||||
|
|||||||
@ -20,7 +20,10 @@ import {
|
|||||||
} from '@nx/js';
|
} from '@nx/js';
|
||||||
import { existsSync, writeFileSync } from 'fs';
|
import { existsSync, writeFileSync } from 'fs';
|
||||||
import { relative, resolve } from 'path';
|
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 {
|
import {
|
||||||
createBuildableTsConfig,
|
createBuildableTsConfig,
|
||||||
loadViteDynamicImport,
|
loadViteDynamicImport,
|
||||||
@ -35,7 +38,8 @@ export async function* viteBuildExecutor(
|
|||||||
) {
|
) {
|
||||||
process.env.VITE_CJS_IGNORE_WARNING = 'true';
|
process.env.VITE_CJS_IGNORE_WARNING = 'true';
|
||||||
// Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
|
// 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 =
|
const projectRoot =
|
||||||
context.projectsConfigurations.projects[context.projectName].root;
|
context.projectsConfigurations.projects[context.projectName].root;
|
||||||
const tsConfigForBuild = createBuildableTsConfig(
|
const tsConfigForBuild = createBuildableTsConfig(
|
||||||
@ -50,7 +54,7 @@ export async function* viteBuildExecutor(
|
|||||||
options.configFile
|
options.configFile
|
||||||
);
|
);
|
||||||
const root =
|
const root =
|
||||||
projectRoot === '.'
|
projectRoot === '.' || projectRoot === ''
|
||||||
? process.cwd()
|
? process.cwd()
|
||||||
: relative(context.cwd, joinPathFragments(context.root, projectRoot));
|
: 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');
|
let iterables: AsyncIterable<{ success: boolean; outfile?: string }>[] = [];
|
||||||
const rootPackageJson = resolve(context.root, 'package.json');
|
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.
|
const libraryPackageJson = resolve(projectRoot, 'package.json');
|
||||||
// So, we calculate the relative path from the workspace root to the outdir.
|
const rootPackageJson = resolve(context.root, 'package.json');
|
||||||
const outDirRelativeToWorkspaceRoot = outDir.replaceAll('../', '');
|
|
||||||
const distPackageJson = resolve(
|
|
||||||
outDirRelativeToWorkspaceRoot,
|
|
||||||
'package.json'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate a package.json if option has been set.
|
// Here, we want the outdir relative to the workspace root.
|
||||||
if (options.generatePackageJson) {
|
// So, we calculate the relative path from the workspace root to the outdir.
|
||||||
if (context.projectGraph.nodes[context.projectName].type !== 'app') {
|
const outDirRelativeToWorkspaceRoot = outDir.replaceAll('../', '');
|
||||||
logger.warn(
|
const distPackageJson = resolve(
|
||||||
stripIndents`The project ${context.projectName} is using the 'generatePackageJson' option which is deprecated for library projects. It should only be used for applications.
|
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).`
|
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(
|
const iterable = createAsyncIterable<{
|
||||||
context.projectName,
|
success: boolean;
|
||||||
context.projectGraph,
|
outfile?: string;
|
||||||
{
|
}>(({ next, done }) => {
|
||||||
target: context.targetName,
|
if ('on' in watcherOrOutput) {
|
||||||
root: context.root,
|
let success = true;
|
||||||
isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build.
|
watcherOrOutput.on('event', (event) => {
|
||||||
skipOverrides: options.skipOverrides,
|
if (event.code === 'START') {
|
||||||
skipPackageManager: options.skipPackageManager,
|
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;
|
iterables.push(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 };
|
|
||||||
}
|
}
|
||||||
|
return yield* combineAsyncIterables(iterables.shift(), ...(iterables ?? []));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBuildExtraArgs(
|
export async function getBuildExtraArgs(
|
||||||
|
|||||||
@ -9,4 +9,5 @@ export interface ViteBuildExecutorOptions {
|
|||||||
skipTypeCheck?: boolean;
|
skipTypeCheck?: boolean;
|
||||||
tsConfig?: string;
|
tsConfig?: string;
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
|
useEnvironmentsApi?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,12 @@
|
|||||||
"skipPackageManager": {
|
"skipPackageManager": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option."
|
"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": {},
|
"definitions": {},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user