From 7deda846f80c2fb9ff54be86a5036fe2252fdad8 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Wed, 8 Nov 2023 17:31:28 +0200 Subject: [PATCH] feat(vite): nodes for build, serve, test, preview targets --- .../packages/vite/generators/vitest.json | 2 +- packages/vite/migrations.json | 5 + packages/vite/package.json | 3 +- packages/vite/plugin.ts | 1 + .../vite/src/executors/test/vitest.impl.ts | 11 +- .../generators/configuration/configuration.ts | 27 +- packages/vite/src/generators/init/init.ts | 126 +------ .../vite/src/generators/init/lib/utils.ts | 145 ++++++++ .../vite/src/generators/vitest/schema.d.ts | 2 +- .../vite/src/generators/vitest/schema.json | 2 +- .../src/generators/vitest/vitest-generator.ts | 23 +- .../update-vite-tsconfig-paths.ts | 38 ++- .../change-ts-paths-plugin.ts | 5 +- .../update-17-2-0/add-vite-plugin.spec.ts | 17 + .../update-17-2-0/add-vite-plugin.ts | 23 ++ packages/vite/src/plugins/plugin.spec.ts | 49 +++ packages/vite/src/plugins/plugin.ts | 314 ++++++++++++++++++ packages/vite/src/utils/versions.ts | 1 - .../src/utils/module-federation/share.ts | 1 - 19 files changed, 633 insertions(+), 162 deletions(-) create mode 100644 packages/vite/plugin.ts create mode 100644 packages/vite/src/generators/init/lib/utils.ts create mode 100644 packages/vite/src/migrations/update-17-2-0/add-vite-plugin.spec.ts create mode 100644 packages/vite/src/migrations/update-17-2-0/add-vite-plugin.ts create mode 100644 packages/vite/src/plugins/plugin.spec.ts create mode 100644 packages/vite/src/plugins/plugin.ts diff --git a/docs/generated/packages/vite/generators/vitest.json b/docs/generated/packages/vite/generators/vitest.json index f9b72140e3a37e..563473c9731df3 100644 --- a/docs/generated/packages/vite/generators/vitest.json +++ b/docs/generated/packages/vite/generators/vitest.json @@ -32,7 +32,7 @@ }, "coverageProvider": { "type": "string", - "enum": ["v8", "c8", "istanbul"], + "enum": ["v8", "c8", "custom"], "default": "v8", "description": "Coverage provider to use." }, diff --git a/packages/vite/migrations.json b/packages/vite/migrations.json index 1f3613f09a68ff..47f68d185e27b7 100644 --- a/packages/vite/migrations.json +++ b/packages/vite/migrations.json @@ -40,6 +40,11 @@ "version": "17.1.0-beta.2", "description": "Move target defaults", "implementation": "./src/migrations/update-17-1-0/move-target-defaults" + }, + "add-vite-plugin": { + "version": "17.2.0-beta.0", + "description": "Add @nx/vite/plugin", + "implementation": "./src/migrations/update-17-2-0/add-vite-plugin" } }, "packageJsonUpdates": { diff --git a/packages/vite/package.json b/packages/vite/package.json index 0af4bccffc27ea..ee8917a4ffb92c 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -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", diff --git a/packages/vite/plugin.ts b/packages/vite/plugin.ts new file mode 100644 index 00000000000000..75eda1084c000b --- /dev/null +++ b/packages/vite/plugin.ts @@ -0,0 +1 @@ +export { createNodes, VitePluginOptions } from './src/plugins/plugin'; diff --git a/packages/vite/src/executors/test/vitest.impl.ts b/packages/vite/src/executors/test/vitest.impl.ts index 7dd83239d9f193..5a658c1a5f11f6 100644 --- a/packages/vite/src/executors/test/vitest.impl.ts +++ b/packages/vite/src/executors/test/vitest.impl.ts @@ -108,13 +108,18 @@ async function getSettings( const packageJson = existsSync(packageJsonPath) ? readJsonFile(packageJsonPath) : undefined; - let provider: 'v8' | 'c8' = 'v8'; + + let provider: 'c8' | 'istanbul' | 'custom'; + if ( - packageJson?.dependencies?.['@vitest/coverage-c8'] || - packageJson?.devDependencies?.['@vitest/coverage-c8'] + packageJson?.dependencies?.['@vitest/coverage-istanbul'] || + packageJson?.devDependencies?.['@vitest/coverage-istanbul'] ) { + provider = 'istanbul'; + } else { provider = 'c8'; } + const offset = relative(workspaceRoot, context.cwd); // if reportsDirectory is not provided vitest will remove all files in the project root // when coverage is enabled in the vite.config.ts diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts index 1367f855469921..fc386bb043a917 100644 --- a/packages/vite/src/generators/configuration/configuration.ts +++ b/packages/vite/src/generators/configuration/configuration.ts @@ -2,6 +2,7 @@ import { formatFiles, GeneratorCallback, joinPathFragments, + readNxJson, readProjectConfiguration, runTasksInSerial, Tree, @@ -163,19 +164,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(tree, joinPathFragments(root, 'tsconfig.lib.json'), (json) => { diff --git a/packages/vite/src/generators/init/init.ts b/packages/vite/src/generators/init/init.ts index cafd8465ab1c01..ed1dc220fe4b80 100644 --- a/packages/vite/src/generators/init/init.ts +++ b/packages/vite/src/generators/init/init.ts @@ -1,122 +1,14 @@ -import { - addDependenciesToPackageJson, - logger, - readJson, - readNxJson, - runTasksInSerial, - Tree, - updateJson, - updateNxJson, -} from '@nx/devkit'; +import { Tree, runTasksInSerial } from '@nx/devkit'; import { initGenerator as jsInitGenerator } from '@nx/js'; -import { - edgeRuntimeVmVersion, - happyDomVersion, - jsdomVersion, - nxVersion, - vitePluginDtsVersion, - vitePluginReactSwcVersion, - vitePluginReactVersion, - vitestUiVersion, - 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; - - // Do not install latest version if vitest already exists - // because version 0.32 and newer versions break nuxt-vitest - // https://github.com/vitest-dev/vitest/issues/3540 - // https://github.com/danielroe/nuxt-vitest/issues/213#issuecomment-1588728111 - if ( - !packageJson.dependencies['vitest'] && - !packageJson.devDependencies['vitest'] - ) { - devDependencies['vitest'] = vitestVersion; - } - if ( - !packageJson.dependencies['@vitest/ui'] && - !packageJson.devDependencies['@vitest/ui'] - ) { - 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; - }); -} - -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)); - } - - nxJson.targetDefaults ??= {}; - nxJson.targetDefaults['@nx/vite:test'] ??= {}; - nxJson.targetDefaults['@nx/vite:test'].cache ??= true; - nxJson.targetDefaults['@nx/vite:test'].inputs ??= [ - 'default', - productionFileSet ? '^production' : '^default', - ]; - - updateNxJson(tree, nxJson); -} +import { + addPlugin, + checkDependenciesInstalled, + createVitestConfig, + moveToDevDependencies, +} from './lib/utils'; export async function initGenerator(tree: Tree, schema: InitGeneratorSchema) { moveToDevDependencies(tree); @@ -130,6 +22,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); diff --git a/packages/vite/src/generators/init/lib/utils.ts b/packages/vite/src/generators/init/lib/utils.ts new file mode 100644 index 00000000000000..eec1532e40363d --- /dev/null +++ b/packages/vite/src/generators/init/lib/utils.ts @@ -0,0 +1,145 @@ +import { + addDependenciesToPackageJson, + logger, + readJson, + readNxJson, + Tree, + updateJson, + updateNxJson, +} from '@nx/devkit'; + +import { + edgeRuntimeVmVersion, + happyDomVersion, + jsdomVersion, + nxVersion, + vitePluginDtsVersion, + vitePluginReactSwcVersion, + vitePluginReactVersion, + vitestUiVersion, + 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; + + // Do not install latest version if vitest already exists + // because version 0.32 and newer versions break nuxt-vitest + // https://github.com/vitest-dev/vitest/issues/3540 + // https://github.com/danielroe/nuxt-vitest/issues/213#issuecomment-1588728111 + if ( + !packageJson.dependencies['vitest'] && + !packageJson.devDependencies['vitest'] + ) { + devDependencies['vitest'] = vitestVersion; + } + if ( + !packageJson.dependencies['@vitest/ui'] && + !packageJson.devDependencies['@vitest/ui'] + ) { + 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)); + } + + nxJson.targetDefaults ??= {}; + nxJson.targetDefaults['@nx/vite:test'] ??= {}; + nxJson.targetDefaults['@nx/vite:test'].cache ??= true; + nxJson.targetDefaults['@nx/vite:test'].inputs ??= [ + 'default', + productionFileSet ? '^production' : '^default', + ]; + + 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', + }, + }); + updateNxJson(tree, nxJson); +} diff --git a/packages/vite/src/generators/vitest/schema.d.ts b/packages/vite/src/generators/vitest/schema.d.ts index 5b5039be218551..78d0731cc37f0f 100644 --- a/packages/vite/src/generators/vitest/schema.d.ts +++ b/packages/vite/src/generators/vitest/schema.d.ts @@ -1,7 +1,7 @@ export interface VitestGeneratorSchema { project: string; uiFramework: 'react' | 'none'; - coverageProvider: 'v8' | 'c8' | 'istanbul'; + coverageProvider: 'c8' | 'istanbul' | 'custom'; inSourceTests?: boolean; skipViteConfig?: boolean; testTarget?: string; diff --git a/packages/vite/src/generators/vitest/schema.json b/packages/vite/src/generators/vitest/schema.json index 6f98e0f49bcb28..f45863f7e4f5a2 100644 --- a/packages/vite/src/generators/vitest/schema.json +++ b/packages/vite/src/generators/vitest/schema.json @@ -31,7 +31,7 @@ }, "coverageProvider": { "type": "string", - "enum": ["v8", "c8", "istanbul"], + "enum": ["v8", "c8", "custom"], "default": "v8", "description": "Coverage provider to use." }, diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts index 67b2666ef2a691..917169248bc373 100644 --- a/packages/vite/src/generators/vitest/vitest-generator.ts +++ b/packages/vite/src/generators/vitest/vitest-generator.ts @@ -5,6 +5,7 @@ import { GeneratorCallback, joinPathFragments, offsetFromRoot, + readNxJson, readProjectConfiguration, runTasksInSerial, Tree, @@ -21,7 +22,6 @@ import initGenerator from '../init/init'; import { vitestCoverageC8Version, vitestCoverageIstanbulVersion, - vitestCoverageV8Version, } from '../../utils/versions'; import { addTsLibDependencies } from '@nx/js'; @@ -37,13 +37,20 @@ export async function vitestGenerator( tree, schema.project ); - let testTarget = - schema.testTarget ?? - findExistingTargetsInProject(targets).validFoundTargetName.test ?? - 'test'; - - addOrChangeTestTarget(tree, schema, testTarget); + const nxJson = readNxJson(tree); + const hasPlugin = nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/vite/plugin' + : p.plugin === '@nx/vite/plugin' + ); + if (!hasPlugin) { + const testTarget = + schema.testTarget ?? + findExistingTargetsInProject(targets).validFoundTargetName.test ?? + 'test'; + addOrChangeTestTarget(tree, schema, testTarget); + } const initTask = await initGenerator(tree, { uiFramework: schema.uiFramework, testEnvironment: schema.testEnvironment, @@ -212,7 +219,7 @@ function getCoverageProviderDependency( }; default: return { - '@vitest/coverage-v8': vitestCoverageV8Version, + '@vitest/coverage-c8': vitestCoverageC8Version, }; } } diff --git a/packages/vite/src/migrations/update-15-3-1/update-vite-tsconfig-paths.ts b/packages/vite/src/migrations/update-15-3-1/update-vite-tsconfig-paths.ts index d2bdfc2c38f00e..196b4ca531f997 100644 --- a/packages/vite/src/migrations/update-15-3-1/update-vite-tsconfig-paths.ts +++ b/packages/vite/src/migrations/update-15-3-1/update-vite-tsconfig-paths.ts @@ -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, [ diff --git a/packages/vite/src/migrations/update-16-6-0-change-ts-paths-plugin/change-ts-paths-plugin.ts b/packages/vite/src/migrations/update-16-6-0-change-ts-paths-plugin/change-ts-paths-plugin.ts index ffadb39c139a23..4f6c5ebd7f265d 100644 --- a/packages/vite/src/migrations/update-16-6-0-change-ts-paths-plugin/change-ts-paths-plugin.ts +++ b/packages/vite/src/migrations/update-16-6-0-change-ts-paths-plugin/change-ts-paths-plugin.ts @@ -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( + 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, diff --git a/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.spec.ts b/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.spec.ts new file mode 100644 index 00000000000000..17987e20be12e1 --- /dev/null +++ b/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.spec.ts @@ -0,0 +1,17 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { Tree } from '@nx/devkit'; + +import update from './add-vite-plugin'; + +describe('add-vite-plugin migration', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + }); + + it('should run successfully', async () => { + await update(tree); + // ... expect changes made + }); +}); diff --git a/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.ts b/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.ts new file mode 100644 index 00000000000000..1e9a10abff1928 --- /dev/null +++ b/packages/vite/src/migrations/update-17-2-0/add-vite-plugin.ts @@ -0,0 +1,23 @@ +import { formatFiles, getProjects, Tree } from '@nx/devkit'; +import { createNodes } from '../../plugins/plugin'; + +import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path'; +import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin'; + +export default async function update(tree: Tree) { + const proj = Object.fromEntries(getProjects(tree).entries()); + + const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj); + + replaceProjectConfigurationsWithPlugin( + tree, + rootMappings, + '@nx/vite/plugin', + createNodes, + { + targetName: 'TODO', + } + ); + + await formatFiles(tree); +} diff --git a/packages/vite/src/plugins/plugin.spec.ts b/packages/vite/src/plugins/plugin.spec.ts new file mode 100644 index 00000000000000..fbe349e32787ec --- /dev/null +++ b/packages/vite/src/plugins/plugin.spec.ts @@ -0,0 +1,49 @@ +import { CreateNodesContext } from '@nx/devkit'; + +import { createNodes } from './plugin'; + +describe('@nx/vite/plugin', () => { + let createNodesFunction = createNodes[1]; + let context: CreateNodesContext; + + beforeEach(async () => { + context = { + nxJsonConfiguration: { + namedInputs: { + default: ['{projectRoot}/**/*'], + production: ['!{projectRoot}/**/*.spec.ts'], + }, + }, + workspaceRoot: '', + }; + }); + + afterEach(() => { + jest.resetModules(); + }); + + it('should create nodes', () => { + mockViteConfig({}); + const nodes = createNodesFunction( + 'TODO', + { + targetName: 'target', + }, + context + ); + + expect(nodes).toMatchInlineSnapshot(); + }); +}); + +function mockViteConfig(config: any) { + jest.mock( + 'TODO', + () => ({ + default: config, + }), + { + virtual: true, + } + ); +} diff --git a/packages/vite/src/plugins/plugin.ts b/packages/vite/src/plugins/plugin.ts new file mode 100644 index 00000000000000..cf25f21dc28559 --- /dev/null +++ b/packages/vite/src/plugins/plugin.ts @@ -0,0 +1,314 @@ +import { + CreateNodes, + CreateNodesContext, + TargetConfiguration, + joinPathFragments, + offsetFromRoot, +} from '@nx/devkit'; +import { basename, dirname, extname, join, resolve } from 'path'; +import { registerTsProject } from '@nx/js/src/internal'; + +import { getRootTsConfigPath } from '@nx/js'; + +import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; +import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; +import { readdirSync } from 'fs'; +import { ExecutorOptions } from '@nx/js/src/utils/schema'; + +export interface VitePluginOptions { + buildTargetName?: string; + testTargetName?: string; + serveTargetName?: string; + previewTargetName?: string; +} + +export const createNodes: CreateNodes = [ + '**/vite.config.{js,ts}', + (configFilePath, options, context) => { + const projectRoot = dirname(configFilePath); + + // Do not create a project if package.json and project.json isn't there. + const siblingFiles = readdirSync(projectRoot); + if ( + !siblingFiles.includes('package.json') && + !siblingFiles.includes('project.json') + ) { + return {}; + } + + options = normalizeOptions(options); + const projectName = basename(projectRoot); + + return { + projects: { + [projectName]: { + root: projectRoot, + projectType: 'library', + targets: buildViteTargets( + configFilePath, + projectRoot, + options, + context, + projectName + ), + }, + }, + }; + }, +]; + +function buildViteTargets( + configFilePath: string, + projectRoot: string, + options: VitePluginOptions, + context: CreateNodesContext, + projectName: string +) { + // Is this needed for some reason? + getViteConfig(configFilePath, context); + + const namedInputs = getNamedInputs(projectRoot, context); + + const targets: Record> = {}; + + targets[options.buildTargetName] = buildTarget( + context, + namedInputs, + projectRoot + ); + + targets[options.serveTargetName] = serveTarget( + context, + namedInputs, + projectRoot, + projectName + ); + + targets[options.previewTargetName] = previewTarget( + context, + namedInputs, + projectRoot, + projectName + ); + + targets[options.testTargetName] = testTarget( + context, + namedInputs, + projectRoot, + projectName + ); + + return targets; +} + +function buildTarget( + context: CreateNodesContext, + namedInputs: { + [inputName: string]: any[]; + }, + projectRoot: string +) { + const targetDefaults = readTargetDefaultsForTarget( + 'build', + context.nxJsonConfiguration.targetDefaults, + '@nx/vite:build' + ); + + const baseTargetConfig: TargetConfiguration = { + executor: '@nx/vite:build', + outputs: ['{options.outputPath}'], + defaultConfiguration: 'production', + options: { + outputPath: joinPathFragments('dist', projectRoot), + ...targetDefaults?.options, + }, + configurations: { + development: { + mode: 'development', + }, + production: { + mode: 'production', + }, + }, + }; + return { + ...baseTargetConfig, + cache: targetDefaults?.cache ?? true, + inputs: + targetDefaults?.inputs ?? 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default'], + options: { + ...baseTargetConfig.options, + }, + }; +} + +function serveTarget( + context: CreateNodesContext, + namedInputs: { + [inputName: string]: any[]; + }, + projectRoot: string, + projectName: string +) { + const targetDefaults = readTargetDefaultsForTarget( + 'serve', + context.nxJsonConfiguration.targetDefaults, + '@nx/vite:dev-server' + ); + + const baseTargetConfig: TargetConfiguration = { + executor: '@nx/vite:dev-server', + defaultConfiguration: 'development', + options: { + buildTarget: `${projectName}:build`, + ...targetDefaults?.options, + }, + configurations: { + development: { + buildTarget: `${projectName}:build:development`, + hmr: true, + }, + production: { + buildTarget: `${projectName}:build:production`, + hmr: false, + }, + }, + }; + return { + ...baseTargetConfig, + cache: targetDefaults?.cache ?? true, + inputs: + targetDefaults?.inputs ?? 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default'], + options: { + ...baseTargetConfig.options, + }, + }; +} + +function previewTarget( + context: CreateNodesContext, + namedInputs: { + [inputName: string]: any[]; + }, + projectRoot: string, + projectName: string +) { + const targetDefaults = readTargetDefaultsForTarget( + 'preview', + context.nxJsonConfiguration.targetDefaults, + '@nx/vite:preview-server' + ); + + const baseTargetConfig: TargetConfiguration = { + executor: '@nx/vite:preview-server', + defaultConfiguration: 'development', + options: { + buildTarget: `${projectName}:build`, + ...targetDefaults?.options, + }, + configurations: { + development: { + buildTarget: `${projectName}:build:development`, + hmr: true, + }, + production: { + buildTarget: `${projectName}:build:production`, + hmr: false, + }, + }, + }; + return { + ...baseTargetConfig, + cache: targetDefaults?.cache ?? true, + inputs: + targetDefaults?.inputs ?? 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default'], + options: { + ...baseTargetConfig.options, + }, + }; +} + +function testTarget( + context: CreateNodesContext, + namedInputs: { + [inputName: string]: any[]; + }, + projectRoot: string, + projectName: string +) { + const targetDefaults = readTargetDefaultsForTarget( + 'test', + context.nxJsonConfiguration.targetDefaults, + '@nx/vite:test' + ); + + const coveragePath = joinPathFragments( + 'coverage', + projectRoot === '.' ? projectName : projectRoot + ); + + const baseTargetConfig: TargetConfiguration = { + executor: '@nx/vite:test', + outputs: ['{options.reportsDirectory}'], + options: { + passWithNoTests: true, + reportsDirectory: joinPathFragments( + offsetFromRoot(projectRoot), + coveragePath + ), + ...targetDefaults?.options, + }, + }; + return { + ...baseTargetConfig, + cache: targetDefaults?.cache ?? true, + inputs: + targetDefaults?.inputs ?? 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default'], + options: { + ...baseTargetConfig.options, + }, + }; +} + +function getViteConfig( + configFilePath: string, + context: CreateNodesContext +): any { + const resolvedPath = join(context.workspaceRoot, configFilePath); + + let module: any; + if (extname(configFilePath) === '.ts') { + const tsConfigPath = getRootTsConfigPath(); + + if (tsConfigPath) { + const unregisterTsProject = registerTsProject(tsConfigPath); + try { + module = require(resolvedPath); + } finally { + unregisterTsProject(); + } + } else { + module = require(resolvedPath); + } + } else { + module = require(resolvedPath); + } + return module.default ?? module; +} + +function normalizeOptions(options: VitePluginOptions): VitePluginOptions { + options ??= {}; + options.buildTargetName ??= 'build'; + options.serveTargetName ??= 'serve'; + options.previewTargetName ??= 'preview'; + options.testTargetName ??= 'test'; + return options; +} diff --git a/packages/vite/src/utils/versions.ts b/packages/vite/src/utils/versions.ts index ab7f9a103e3d02..e3e4ab39984eee 100644 --- a/packages/vite/src/utils/versions.ts +++ b/packages/vite/src/utils/versions.ts @@ -11,5 +11,4 @@ export const edgeRuntimeVmVersion = '~3.0.2'; // Coverage providers export const vitestCoverageC8Version = '~0.32.0'; -export const vitestCoverageV8Version = '~0.32.0'; export const vitestCoverageIstanbulVersion = '~0.32.0'; diff --git a/packages/webpack/src/utils/module-federation/share.ts b/packages/webpack/src/utils/module-federation/share.ts index b0188c909564d7..5ac0802a59bc2b 100644 --- a/packages/webpack/src/utils/module-federation/share.ts +++ b/packages/webpack/src/utils/module-federation/share.ts @@ -16,7 +16,6 @@ import { workspaceRoot, logger, readJsonFile, - ProjectGraphProjectNode, joinPathFragments, } from '@nx/devkit'; import { existsSync } from 'fs';