feat(vite): nodes for build, serve, test, preview targets (#20086)
This commit is contained in:
parent
5a47eaf684
commit
83db767b27
59
e2e/vite/src/vite-pcv3.test.ts
Normal file
59
e2e/vite/src/vite-pcv3.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils';
|
||||
|
||||
const myApp = uniq('my-app');
|
||||
|
||||
describe('@nx/vite/plugin', () => {
|
||||
let proj: string;
|
||||
let originalEnv: string;
|
||||
|
||||
beforeAll(() => {
|
||||
originalEnv = process.env.NX_PCV3;
|
||||
process.env.NX_PCV3 = 'true';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
|
||||
describe('build and test React Vite app', () => {
|
||||
beforeAll(() => {
|
||||
proj = newProject();
|
||||
runCLI(
|
||||
`generate @nx/react:app ${myApp} --bundler=vite --unitTestRunner=vitest`
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should build application', () => {
|
||||
const result = runCLI(`build ${myApp}`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
}, 200_000);
|
||||
|
||||
it('should test application', () => {
|
||||
const result = runCLI(`test ${myApp}`);
|
||||
expect(result).toContain('Successfully ran target test');
|
||||
}, 200_000);
|
||||
});
|
||||
|
||||
describe('build and test Vue app', () => {
|
||||
beforeAll(() => {
|
||||
proj = newProject();
|
||||
runCLI(`generate @nx/vue:app ${myApp} --unitTestRunner=vitest`);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should build application', () => {
|
||||
const result = runCLI(`build ${myApp}`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
}, 200_000);
|
||||
|
||||
it('should test application', () => {
|
||||
const result = runCLI(`test ${myApp}`);
|
||||
expect(result).toContain('Successfully ran target test');
|
||||
}, 200_000);
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,6 @@
|
||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
||||
import {
|
||||
getProjects,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
@ -33,6 +34,7 @@ describe('react app generator (PCv3)', () => {
|
||||
const nxJson = readNxJson(appTree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push('@nx/webpack/plugin');
|
||||
nxJson.plugins.push('@nx/vite/plugin');
|
||||
updateNxJson(appTree, nxJson);
|
||||
});
|
||||
|
||||
@ -57,4 +59,17 @@ describe('react app generator (PCv3)', () => {
|
||||
`assets: ['./src/favicon.ico', './src/assets']`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add targets for vite', async () => {
|
||||
await applicationGenerator(appTree, {
|
||||
...schema,
|
||||
name: 'my-vite-app',
|
||||
bundler: 'vite',
|
||||
});
|
||||
const projects = getProjects(appTree);
|
||||
expect(projects.get('my-vite-app').targets.build).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.serve).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.preview).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.test).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ import { webStaticServeGenerator } from '@nx/web';
|
||||
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
|
||||
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
|
||||
export async function addE2e(
|
||||
@ -17,7 +18,10 @@ export async function addE2e(
|
||||
): Promise<GeneratorCallback> {
|
||||
switch (options.e2eTestRunner) {
|
||||
case 'cypress': {
|
||||
if (!hasWebpackPlugin(tree)) {
|
||||
if (
|
||||
(options.bundler === 'webpack' && !hasWebpackPlugin(tree)) ||
|
||||
(options.bundler === 'vite' && !hasVitePlugin(tree))
|
||||
) {
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: `${options.projectName}:build`,
|
||||
targetName: 'serve-static',
|
||||
|
||||
10
packages/react/src/utils/has-vite-plugin.ts
Normal file
10
packages/react/src/utils/has-vite-plugin.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { readNxJson, Tree } from '@nx/devkit';
|
||||
|
||||
export function hasVitePlugin(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
return !!nxJson.plugins?.some((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin'
|
||||
);
|
||||
}
|
||||
@ -3,6 +3,16 @@ import json = require('./migrations.json');
|
||||
import { assertValidMigrationPaths } from '@nx/devkit/internal-testing-utils';
|
||||
import { MigrationsJson } from '@nx/devkit';
|
||||
|
||||
jest.mock('vite', () => ({
|
||||
loadConfigFromFile: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
path: 'vite.config.ts',
|
||||
config: {},
|
||||
dependencies: [],
|
||||
});
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('vite migrations', () => {
|
||||
assertValidMigrationPaths(json as MigrationsJson, __dirname);
|
||||
});
|
||||
|
||||
@ -29,10 +29,10 @@
|
||||
"migrations": "./migrations.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@swc/helpers": "~0.5.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/js": "file:../js",
|
||||
"tsconfig-paths": "^4.1.2"
|
||||
},
|
||||
@ -45,6 +45,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./plugin": "./plugin.js",
|
||||
"./package.json": "./package.json",
|
||||
"./migrations.json": "./migrations.json",
|
||||
"./generators.json": "./generators.json",
|
||||
|
||||
5
packages/vite/plugin.ts
Normal file
5
packages/vite/plugin.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export {
|
||||
createNodes,
|
||||
VitePluginOptions,
|
||||
createDependencies,
|
||||
} from './src/plugins/plugin';
|
||||
@ -2,6 +2,7 @@ import {
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
@ -164,19 +165,27 @@ export async function viteConfigurationGenerator(
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
if (!projectAlreadyHasViteTargets.build) {
|
||||
addOrChangeBuildTarget(tree, schema, buildTargetName);
|
||||
}
|
||||
const nxJson = readNxJson(tree);
|
||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin'
|
||||
);
|
||||
|
||||
if (!schema.includeLib) {
|
||||
if (!projectAlreadyHasViteTargets.serve) {
|
||||
addOrChangeServeTarget(tree, schema, serveTargetName);
|
||||
if (!hasPlugin) {
|
||||
if (!projectAlreadyHasViteTargets.build) {
|
||||
addOrChangeBuildTarget(tree, schema, buildTargetName);
|
||||
}
|
||||
if (!projectAlreadyHasViteTargets.preview) {
|
||||
addPreviewTarget(tree, schema, serveTargetName);
|
||||
|
||||
if (!schema.includeLib) {
|
||||
if (!projectAlreadyHasViteTargets.serve) {
|
||||
addOrChangeServeTarget(tree, schema, serveTargetName);
|
||||
}
|
||||
if (!projectAlreadyHasViteTargets.preview) {
|
||||
addPreviewTarget(tree, schema, serveTargetName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === 'library') {
|
||||
// update tsconfig.lib.json to include vite/client
|
||||
updateJson(
|
||||
@ -225,7 +234,8 @@ export async function viteConfigurationGenerator(
|
||||
],
|
||||
plugins: ['react()'],
|
||||
},
|
||||
false
|
||||
false,
|
||||
undefined
|
||||
);
|
||||
} else {
|
||||
createOrEditViteConfig(tree, schema, false, projectAlreadyHasViteTargets);
|
||||
|
||||
@ -1,82 +1,13 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
logger,
|
||||
readJson,
|
||||
readNxJson,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { readNxJson, runTasksInSerial, Tree, updateNxJson } from '@nx/devkit';
|
||||
|
||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||
|
||||
import {
|
||||
edgeRuntimeVmVersion,
|
||||
happyDomVersion,
|
||||
jsdomVersion,
|
||||
nxVersion,
|
||||
vitePluginDtsVersion,
|
||||
vitePluginReactSwcVersion,
|
||||
vitePluginReactVersion,
|
||||
vitestVersion,
|
||||
viteVersion,
|
||||
} from '../../utils/versions';
|
||||
import { InitGeneratorSchema } from './schema';
|
||||
|
||||
function checkDependenciesInstalled(host: Tree, schema: InitGeneratorSchema) {
|
||||
const packageJson = readJson(host, 'package.json');
|
||||
const devDependencies = {};
|
||||
const dependencies = {};
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
|
||||
// base deps
|
||||
devDependencies['@nx/vite'] = nxVersion;
|
||||
devDependencies['vite'] = viteVersion;
|
||||
devDependencies['vitest'] = vitestVersion;
|
||||
devDependencies['@vitest/ui'] = vitestVersion;
|
||||
|
||||
if (schema.testEnvironment === 'jsdom') {
|
||||
devDependencies['jsdom'] = jsdomVersion;
|
||||
} else if (schema.testEnvironment === 'happy-dom') {
|
||||
devDependencies['happy-dom'] = happyDomVersion;
|
||||
} else if (schema.testEnvironment === 'edge-runtime') {
|
||||
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
|
||||
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
|
||||
logger.info(
|
||||
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.uiFramework === 'react') {
|
||||
if (schema.compiler === 'swc') {
|
||||
devDependencies['@vitejs/plugin-react-swc'] = vitePluginReactSwcVersion;
|
||||
} else {
|
||||
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.includeLib) {
|
||||
devDependencies['vite-plugin-dts'] = vitePluginDtsVersion;
|
||||
}
|
||||
|
||||
return addDependenciesToPackageJson(host, dependencies, devDependencies);
|
||||
}
|
||||
|
||||
function moveToDevDependencies(tree: Tree) {
|
||||
updateJson(tree, 'package.json', (packageJson) => {
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
|
||||
if (packageJson.dependencies['@nx/vite']) {
|
||||
packageJson.devDependencies['@nx/vite'] =
|
||||
packageJson.dependencies['@nx/vite'];
|
||||
delete packageJson.dependencies['@nx/vite'];
|
||||
}
|
||||
return packageJson;
|
||||
});
|
||||
}
|
||||
import {
|
||||
addPlugin,
|
||||
checkDependenciesInstalled,
|
||||
moveToDevDependencies,
|
||||
} from './lib/utils';
|
||||
|
||||
export function updateNxJsonSettings(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
@ -127,7 +58,10 @@ export async function initGenerator(tree: Tree, schema: InitGeneratorSchema) {
|
||||
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||
})
|
||||
);
|
||||
|
||||
const addPlugins = process.env.NX_PCV3 === 'true';
|
||||
if (addPlugins) {
|
||||
addPlugin(tree);
|
||||
}
|
||||
tasks.push(checkDependenciesInstalled(tree, schema));
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
122
packages/vite/src/generators/init/lib/utils.ts
Normal file
122
packages/vite/src/generators/init/lib/utils.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
logger,
|
||||
readJson,
|
||||
readNxJson,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateNxJson,
|
||||
} from '@nx/devkit';
|
||||
|
||||
import {
|
||||
edgeRuntimeVmVersion,
|
||||
happyDomVersion,
|
||||
jsdomVersion,
|
||||
nxVersion,
|
||||
vitePluginDtsVersion,
|
||||
vitePluginReactSwcVersion,
|
||||
vitePluginReactVersion,
|
||||
vitestVersion,
|
||||
viteVersion,
|
||||
} from '../../../utils/versions';
|
||||
import { InitGeneratorSchema } from '../schema';
|
||||
|
||||
export function checkDependenciesInstalled(
|
||||
host: Tree,
|
||||
schema: InitGeneratorSchema
|
||||
) {
|
||||
const packageJson = readJson(host, 'package.json');
|
||||
const devDependencies = {};
|
||||
const dependencies = {};
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
|
||||
// base deps
|
||||
devDependencies['@nx/vite'] = nxVersion;
|
||||
devDependencies['vite'] = viteVersion;
|
||||
devDependencies['vitest'] = vitestVersion;
|
||||
devDependencies['@vitest/ui'] = vitestVersion;
|
||||
|
||||
if (schema.testEnvironment === 'jsdom') {
|
||||
devDependencies['jsdom'] = jsdomVersion;
|
||||
} else if (schema.testEnvironment === 'happy-dom') {
|
||||
devDependencies['happy-dom'] = happyDomVersion;
|
||||
} else if (schema.testEnvironment === 'edge-runtime') {
|
||||
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
|
||||
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
|
||||
logger.info(
|
||||
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.uiFramework === 'react') {
|
||||
if (schema.compiler === 'swc') {
|
||||
devDependencies['@vitejs/plugin-react-swc'] = vitePluginReactSwcVersion;
|
||||
} else {
|
||||
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.includeLib) {
|
||||
devDependencies['vite-plugin-dts'] = vitePluginDtsVersion;
|
||||
}
|
||||
|
||||
return addDependenciesToPackageJson(host, dependencies, devDependencies);
|
||||
}
|
||||
|
||||
export function moveToDevDependencies(tree: Tree) {
|
||||
updateJson(tree, 'package.json', (packageJson) => {
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
|
||||
if (packageJson.dependencies['@nx/vite']) {
|
||||
packageJson.devDependencies['@nx/vite'] =
|
||||
packageJson.dependencies['@nx/vite'];
|
||||
delete packageJson.dependencies['@nx/vite'];
|
||||
}
|
||||
return packageJson;
|
||||
});
|
||||
}
|
||||
|
||||
export function createVitestConfig(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
|
||||
const productionFileSet = nxJson.namedInputs?.production;
|
||||
if (productionFileSet) {
|
||||
productionFileSet.push(
|
||||
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||
'!{projectRoot}/tsconfig.spec.json'
|
||||
);
|
||||
|
||||
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
|
||||
}
|
||||
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
|
||||
export function addPlugin(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
|
||||
for (const plugin of nxJson.plugins) {
|
||||
if (
|
||||
typeof plugin === 'string'
|
||||
? plugin === '@nx/vite/plugin'
|
||||
: plugin.plugin === '@nx/vite/plugin'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/vite/plugin',
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
previewTargetName: 'preview',
|
||||
testTargetName: 'test',
|
||||
serveTargetName: 'serve',
|
||||
serveStaticTargetName: 'serve-static',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
@ -5,6 +5,7 @@ import {
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
offsetFromRoot,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
@ -42,10 +43,20 @@ export async function vitestGenerator(
|
||||
findExistingTargetsInProject(targets).validFoundTargetName.test ??
|
||||
'test';
|
||||
|
||||
if (!hasPlugin) {
|
||||
const nxJson = readNxJson(tree);
|
||||
const hasPluginCheck = nxJson.plugins?.some(
|
||||
(p) =>
|
||||
(typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin') || hasPlugin
|
||||
);
|
||||
if (!hasPluginCheck) {
|
||||
const testTarget =
|
||||
schema.testTarget ??
|
||||
findExistingTargetsInProject(targets).validFoundTargetName.test ??
|
||||
'test';
|
||||
addOrChangeTestTarget(tree, schema, testTarget);
|
||||
}
|
||||
|
||||
const initTask = await initGenerator(tree, {
|
||||
uiFramework: schema.uiFramework,
|
||||
testEnvironment: schema.testEnvironment,
|
||||
|
||||
@ -36,24 +36,26 @@ function findAllProjectsWithViteConfig(tree: Tree): void {
|
||||
);
|
||||
let startOfProjects, endOfProjects;
|
||||
|
||||
defineConfig?.[0]?.getChildren().forEach((defineConfigContentNode) => {
|
||||
// Make sure it's the one we are looking for
|
||||
// We cannot assume that it's called tsConfigPaths
|
||||
// So make sure it includes `projects` and `root`
|
||||
if (
|
||||
defineConfigContentNode.getText().includes('projects') &&
|
||||
defineConfigContentNode.getText().includes('root')
|
||||
) {
|
||||
findNodes(defineConfigContentNode, [
|
||||
ts.SyntaxKind.PropertyAssignment,
|
||||
]).forEach((nodePA) => {
|
||||
if (nodePA.getText().startsWith('projects')) {
|
||||
startOfProjects = nodePA.getStart();
|
||||
endOfProjects = nodePA.getEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
defineConfig?.[0]
|
||||
?.getChildren()
|
||||
.forEach((defineConfigContentNode: any) => {
|
||||
// Make sure it's the one we are looking for
|
||||
// We cannot assume that it's called tsConfigPaths
|
||||
// So make sure it includes `projects` and `root`
|
||||
if (
|
||||
defineConfigContentNode.getText().includes('projects') &&
|
||||
defineConfigContentNode.getText().includes('root')
|
||||
) {
|
||||
findNodes(defineConfigContentNode, [
|
||||
ts.SyntaxKind.PropertyAssignment,
|
||||
]).forEach((nodePA) => {
|
||||
if (nodePA.getText().startsWith('projects')) {
|
||||
startOfProjects = nodePA.getStart();
|
||||
endOfProjects = nodePA.getEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (startOfProjects && endOfProjects) {
|
||||
newContents = applyChangesToString(newContents, [
|
||||
|
||||
@ -2,7 +2,6 @@ import { Tree, getProjects, joinPathFragments } from '@nx/devkit';
|
||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||
import { ViteBuildExecutorOptions } from '../../executors/build/schema';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import { ImportDeclaration } from 'typescript';
|
||||
|
||||
export default function update(tree: Tree) {
|
||||
const projects = getProjects(tree);
|
||||
@ -20,7 +19,7 @@ export default function update(tree: Tree) {
|
||||
const configContents = tree.read(config, 'utf-8');
|
||||
|
||||
const oldTsConfigPathPlugin =
|
||||
tsquery.query<ImportDeclaration>(
|
||||
tsquery.query(
|
||||
configContents,
|
||||
'ImportDeclaration:has(StringLiteral[value="vite-tsconfig-paths"])'
|
||||
) ?? [];
|
||||
@ -30,7 +29,7 @@ export default function update(tree: Tree) {
|
||||
}
|
||||
|
||||
const importName =
|
||||
oldTsConfigPathPlugin[0]?.importClause?.name?.text ??
|
||||
oldTsConfigPathPlugin[0]?.['importClause']?.name?.text ??
|
||||
'viteTsConfigPaths';
|
||||
const updatedContent = tsquery.replace(
|
||||
configContents,
|
||||
|
||||
137
packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap
Normal file
137
packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap
Normal file
@ -0,0 +1,137 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`@nx/vite/plugin not root project should create nodes 1`] = `
|
||||
{
|
||||
"projects": {
|
||||
"my-app": {
|
||||
"root": "my-app",
|
||||
"targets": {
|
||||
"build-something": {
|
||||
"cache": true,
|
||||
"command": "vite build",
|
||||
"inputs": [
|
||||
"production",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"vite",
|
||||
],
|
||||
},
|
||||
],
|
||||
"options": {
|
||||
"cwd": "my-app",
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputPath}",
|
||||
],
|
||||
},
|
||||
"my-serve": {
|
||||
"command": "vite serve",
|
||||
"options": {
|
||||
"cwd": "my-app",
|
||||
},
|
||||
},
|
||||
"preview-site": {
|
||||
"command": "vite preview",
|
||||
"options": {
|
||||
"cwd": "my-app",
|
||||
},
|
||||
},
|
||||
"serve-static": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "build-something",
|
||||
},
|
||||
},
|
||||
"vitest": {
|
||||
"cache": true,
|
||||
"command": "vitest run",
|
||||
"inputs": [
|
||||
"production",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"vitest",
|
||||
],
|
||||
},
|
||||
],
|
||||
"options": {
|
||||
"cwd": "my-app",
|
||||
},
|
||||
"outputs": [
|
||||
"{options.reportsDirectory}",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`@nx/vite/plugin root project should create nodes 1`] = `
|
||||
{
|
||||
"projects": {
|
||||
".": {
|
||||
"root": ".",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "vite build",
|
||||
"inputs": [
|
||||
"production",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"vite",
|
||||
],
|
||||
},
|
||||
],
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{options.outputPath}",
|
||||
],
|
||||
},
|
||||
"preview": {
|
||||
"command": "vite preview",
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
},
|
||||
"serve": {
|
||||
"command": "vite serve",
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
},
|
||||
"serve-static": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "build",
|
||||
},
|
||||
},
|
||||
"test": {
|
||||
"cache": true,
|
||||
"command": "vitest run",
|
||||
"inputs": [
|
||||
"production",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"vitest",
|
||||
],
|
||||
},
|
||||
],
|
||||
"options": {
|
||||
"cwd": ".",
|
||||
},
|
||||
"outputs": [
|
||||
"{options.reportsDirectory}",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
93
packages/vite/src/plugins/plugin.spec.ts
Normal file
93
packages/vite/src/plugins/plugin.spec.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { CreateNodesContext } from '@nx/devkit';
|
||||
import { createNodes } from './plugin';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
|
||||
jest.mock('vite', () => ({
|
||||
loadConfigFromFile: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
path: 'vite.config.ts',
|
||||
config: {},
|
||||
dependencies: [],
|
||||
});
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('@nx/vite/plugin', () => {
|
||||
let createNodesFunction = createNodes[1];
|
||||
let context: CreateNodesContext;
|
||||
describe('root project', () => {
|
||||
beforeEach(async () => {
|
||||
context = {
|
||||
nxJsonConfiguration: {
|
||||
namedInputs: {
|
||||
default: ['{projectRoot}/**/*'],
|
||||
production: ['!{projectRoot}/**/*.spec.ts'],
|
||||
},
|
||||
},
|
||||
workspaceRoot: '',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should create nodes', async () => {
|
||||
const nodes = await createNodesFunction(
|
||||
'vite.config.ts',
|
||||
{
|
||||
buildTargetName: 'build',
|
||||
serveTargetName: 'serve',
|
||||
previewTargetName: 'preview',
|
||||
testTargetName: 'test',
|
||||
serveStaticTargetName: 'serve-static',
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
expect(nodes).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// some issue wiht the tempfs
|
||||
describe('not root project', () => {
|
||||
const tempFs = new TempFs('test');
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
nxJsonConfiguration: {
|
||||
namedInputs: {
|
||||
default: ['{projectRoot}/**/*'],
|
||||
production: ['!{projectRoot}/**/*.spec.ts'],
|
||||
},
|
||||
},
|
||||
workspaceRoot: tempFs.tempDir,
|
||||
};
|
||||
|
||||
tempFs.createFileSync(
|
||||
'my-app/project.json',
|
||||
JSON.stringify({ name: 'my-app' })
|
||||
);
|
||||
tempFs.createFileSync('my-app/vite.config.ts', '');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should create nodes', async () => {
|
||||
const nodes = await createNodesFunction(
|
||||
'my-app/vite.config.ts',
|
||||
{
|
||||
buildTargetName: 'build-something',
|
||||
serveTargetName: 'my-serve',
|
||||
previewTargetName: 'preview-site',
|
||||
testTargetName: 'vitest',
|
||||
serveStaticTargetName: 'serve-static',
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
expect(nodes).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
296
packages/vite/src/plugins/plugin.ts
Normal file
296
packages/vite/src/plugins/plugin.ts
Normal file
@ -0,0 +1,296 @@
|
||||
import {
|
||||
CreateDependencies,
|
||||
CreateNodes,
|
||||
CreateNodesContext,
|
||||
TargetConfiguration,
|
||||
detectPackageManager,
|
||||
joinPathFragments,
|
||||
readJsonFile,
|
||||
workspaceRoot,
|
||||
writeJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
||||
|
||||
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||
import { UserConfig, loadConfigFromFile } from 'vite';
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { getLockFileName } from '@nx/js';
|
||||
export interface VitePluginOptions {
|
||||
buildTargetName?: string;
|
||||
testTargetName?: string;
|
||||
serveTargetName?: string;
|
||||
previewTargetName?: string;
|
||||
serveStaticTargetName?: string;
|
||||
}
|
||||
|
||||
const cachePath = join(projectGraphCacheDirectory, 'vite.hash');
|
||||
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
|
||||
|
||||
const calculatedTargets: Record<
|
||||
string,
|
||||
Record<string, TargetConfiguration>
|
||||
> = {};
|
||||
|
||||
function readTargetsCache(): Record<
|
||||
string,
|
||||
Record<string, TargetConfiguration>
|
||||
> {
|
||||
return readJsonFile(cachePath);
|
||||
}
|
||||
|
||||
function writeTargetsToCache(
|
||||
targets: Record<string, Record<string, TargetConfiguration>>
|
||||
) {
|
||||
writeJsonFile(cachePath, targets);
|
||||
}
|
||||
|
||||
export const createDependencies: CreateDependencies = () => {
|
||||
writeTargetsToCache(calculatedTargets);
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createNodes: CreateNodes<VitePluginOptions> = [
|
||||
'**/vite.config.{js,ts}',
|
||||
async (configFilePath, options, context) => {
|
||||
const projectRoot = dirname(configFilePath);
|
||||
// Do not create a project if package.json and project.json isn't there.
|
||||
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||
if (
|
||||
!siblingFiles.includes('package.json') &&
|
||||
!siblingFiles.includes('project.json')
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
options = normalizeOptions(options);
|
||||
|
||||
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
|
||||
getLockFileName(detectPackageManager(context.workspaceRoot)),
|
||||
]);
|
||||
const targets = targetsCache[hash]
|
||||
? targetsCache[hash]
|
||||
: await buildViteTargets(configFilePath, projectRoot, options, context);
|
||||
|
||||
calculatedTargets[hash] = targets;
|
||||
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: {
|
||||
root: projectRoot,
|
||||
targets,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
async function buildViteTargets(
|
||||
configFilePath: string,
|
||||
projectRoot: string,
|
||||
options: VitePluginOptions,
|
||||
context: CreateNodesContext
|
||||
) {
|
||||
const viteConfig = await loadConfigFromFile(
|
||||
{
|
||||
command: 'build',
|
||||
mode: 'production',
|
||||
},
|
||||
configFilePath
|
||||
);
|
||||
|
||||
const { buildOutputs, testOutputs } = getOutputs(
|
||||
projectRoot,
|
||||
viteConfig?.config
|
||||
);
|
||||
|
||||
const namedInputs = getNamedInputs(projectRoot, context);
|
||||
|
||||
const targets: Record<string, TargetConfiguration> = {};
|
||||
|
||||
targets[options.buildTargetName] = await buildTarget(
|
||||
context,
|
||||
namedInputs,
|
||||
buildOutputs,
|
||||
options,
|
||||
projectRoot
|
||||
);
|
||||
|
||||
targets[options.serveTargetName] = serveTarget(projectRoot);
|
||||
|
||||
targets[options.previewTargetName] = previewTarget(projectRoot);
|
||||
|
||||
targets[options.testTargetName] = await testTarget(
|
||||
context,
|
||||
namedInputs,
|
||||
testOutputs,
|
||||
options,
|
||||
projectRoot
|
||||
);
|
||||
|
||||
targets[options.serveStaticTargetName] = serveStaticTarget(options) as {};
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
async function buildTarget(
|
||||
context: CreateNodesContext,
|
||||
namedInputs: {
|
||||
[inputName: string]: any[];
|
||||
},
|
||||
outputs: string[],
|
||||
options: VitePluginOptions,
|
||||
projectRoot: string
|
||||
) {
|
||||
const targetDefaults = readTargetDefaultsForTarget(
|
||||
options.buildTargetName,
|
||||
context.nxJsonConfiguration.targetDefaults
|
||||
);
|
||||
|
||||
const targetConfig: TargetConfiguration = {
|
||||
command: `vite build`,
|
||||
options: {
|
||||
cwd: joinPathFragments(projectRoot),
|
||||
},
|
||||
};
|
||||
|
||||
if (targetDefaults?.outputs === undefined) {
|
||||
targetConfig.outputs = outputs;
|
||||
}
|
||||
|
||||
if (targetDefaults?.cache === undefined) {
|
||||
targetConfig.cache = true;
|
||||
}
|
||||
|
||||
if (targetDefaults?.inputs === undefined) {
|
||||
targetConfig.inputs = [
|
||||
...('production' in namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default']),
|
||||
{
|
||||
externalDependencies: ['vite'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return targetConfig;
|
||||
}
|
||||
|
||||
function serveTarget(projectRoot: string) {
|
||||
const targetConfig: TargetConfiguration = {
|
||||
command: `vite serve`,
|
||||
options: {
|
||||
cwd: joinPathFragments(projectRoot),
|
||||
},
|
||||
};
|
||||
|
||||
return targetConfig;
|
||||
}
|
||||
|
||||
function previewTarget(projectRoot: string) {
|
||||
const targetConfig: TargetConfiguration = {
|
||||
command: `vite preview`,
|
||||
options: {
|
||||
cwd: joinPathFragments(projectRoot),
|
||||
},
|
||||
};
|
||||
|
||||
return targetConfig;
|
||||
}
|
||||
|
||||
async function testTarget(
|
||||
context: CreateNodesContext,
|
||||
namedInputs: {
|
||||
[inputName: string]: any[];
|
||||
},
|
||||
outputs: string[],
|
||||
options: VitePluginOptions,
|
||||
projectRoot: string
|
||||
) {
|
||||
const targetDefaults = readTargetDefaultsForTarget(
|
||||
options.testTargetName,
|
||||
context.nxJsonConfiguration.targetDefaults
|
||||
);
|
||||
|
||||
const targetConfig: TargetConfiguration = {
|
||||
command: `vitest run`,
|
||||
options: {
|
||||
cwd: joinPathFragments(projectRoot),
|
||||
},
|
||||
};
|
||||
|
||||
if (targetDefaults?.outputs === undefined) {
|
||||
targetConfig.outputs = outputs;
|
||||
}
|
||||
|
||||
if (targetDefaults?.cache === undefined) {
|
||||
targetConfig.cache = true;
|
||||
}
|
||||
|
||||
if (targetDefaults?.inputs === undefined) {
|
||||
targetConfig.inputs = [
|
||||
...('production' in namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default']),
|
||||
{
|
||||
externalDependencies: ['vitest'],
|
||||
},
|
||||
];
|
||||
}
|
||||
return targetConfig;
|
||||
}
|
||||
|
||||
function serveStaticTarget(options: VitePluginOptions) {
|
||||
const targetConfig: TargetConfiguration = {
|
||||
executor: '@nx/web:file-server',
|
||||
options: {
|
||||
buildTarget: `${options.buildTargetName}`,
|
||||
},
|
||||
};
|
||||
|
||||
return targetConfig;
|
||||
}
|
||||
|
||||
function getOutputs(
|
||||
projectRoot: string,
|
||||
viteConfig: UserConfig
|
||||
): {
|
||||
buildOutputs: string[];
|
||||
testOutputs: string[];
|
||||
} {
|
||||
const { build, test } = viteConfig;
|
||||
const buildOutputs = ['{options.outputPath}'];
|
||||
const testOutputs = ['{options.reportsDirectory}'];
|
||||
|
||||
function getOutput(path: string, projectRoot: string): string {
|
||||
if (path.startsWith('..')) {
|
||||
return join('{workspaceRoot}', join(projectRoot, path));
|
||||
} else if (isAbsolute(resolve(path))) {
|
||||
return `{workspaceRoot}/${relative(workspaceRoot, path)}`;
|
||||
} else {
|
||||
return join('{projectRoot}', path);
|
||||
}
|
||||
}
|
||||
|
||||
if (build?.outDir) {
|
||||
buildOutputs.push(getOutput(build.outDir, projectRoot));
|
||||
}
|
||||
|
||||
if (test?.coverage?.reportsDirectory) {
|
||||
testOutputs.push(getOutput(test.coverage.reportsDirectory, projectRoot));
|
||||
}
|
||||
|
||||
return { buildOutputs, testOutputs };
|
||||
}
|
||||
|
||||
function normalizeOptions(options: VitePluginOptions): VitePluginOptions {
|
||||
options ??= {};
|
||||
options.buildTargetName ??= 'build';
|
||||
options.serveTargetName ??= 'serve';
|
||||
options.previewTargetName ??= 'preview';
|
||||
options.testTargetName ??= 'test';
|
||||
options.serveStaticTargetName ??= 'serve-static';
|
||||
return options;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {
|
||||
ensurePackage,
|
||||
getPackageManagerCommand,
|
||||
joinPathFragments,
|
||||
readNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { webStaticServeGenerator } from '@nx/web';
|
||||
|
||||
@ -16,10 +17,18 @@ export async function addE2e(
|
||||
): Promise<GeneratorCallback> {
|
||||
switch (options.e2eTestRunner) {
|
||||
case 'cypress': {
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: `${options.projectName}:build`,
|
||||
targetName: 'serve-static',
|
||||
});
|
||||
const nxJson = readNxJson(tree);
|
||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin'
|
||||
);
|
||||
if (!hasPlugin) {
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: `${options.projectName}:build`,
|
||||
targetName: 'serve-static',
|
||||
});
|
||||
}
|
||||
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/cypress')
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
||||
import {
|
||||
getProjects,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
@ -28,6 +29,7 @@ describe('web app generator (PCv3)', () => {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push('@nx/webpack/plugin');
|
||||
nxJson.plugins.push('@nx/vite/plugin');
|
||||
updateNxJson(tree, nxJson);
|
||||
});
|
||||
|
||||
@ -50,4 +52,16 @@ describe('web app generator (PCv3)', () => {
|
||||
`assets: ['./src/favicon.ico', './src/assets']`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add targets for vite', async () => {
|
||||
await applicationGenerator(tree, {
|
||||
name: 'my-vite-app',
|
||||
bundler: 'vite',
|
||||
});
|
||||
const projects = getProjects(tree);
|
||||
expect(projects.get('my-vite-app').targets.build).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.serve).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.preview).toBeUndefined();
|
||||
expect(projects.get('my-vite-app').targets.test).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -16,7 +16,6 @@ import {
|
||||
workspaceRoot,
|
||||
logger,
|
||||
readJsonFile,
|
||||
ProjectGraphProjectNode,
|
||||
joinPathFragments,
|
||||
} from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user