fix(vite): project conversion generator (#21646)

This commit is contained in:
Katerina Skroumpelou 2024-02-20 16:38:12 +02:00 committed by GitHub
parent 246fd1907b
commit fe17fc3287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 225 additions and 1104 deletions

File diff suppressed because one or more lines are too long

View File

@ -925,10 +925,7 @@ describe('app', () => {
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(viteAppTree, '/my-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject([
'vite/client',
'vitest',
]);
expect(tsconfigJson.compilerOptions.jsx).toBe('react-jsx');
});
it('should create index.html and vite.config file at the root of the app', () => {

View File

@ -3,9 +3,9 @@ title: Examples for the Vite configuration generator
description: This page contains examples for the Vite @nx/vite:configuration generator, which helps you set up Vite on your Nx workspace, or convert an existing project to use Vite.
---
This generator is used for converting an existing React or Web project to use [Vite.js](https://vitejs.dev/) and the [@nx/vite executors](/packages/vite#executors).
This generator is used for converting an existing React or Web project to use [Vite.js](https://vitejs.dev/).
It will change the `build` and `serve` targets to use the `@nx/vite` executors for serving and building the application. If you choose so, it will also change your `test` target to use the `@nx/vite:test` executor. It will create a `vite.config.ts` file at the root of your project with the correct settings, or if there's already a `vite.config.ts` file, it will modify it to include the correct settings.
It will create a `vite.config.ts` file at the root of your project with the correct settings, or if there's already a `vite.config.ts` file, it will modify it to include the correct settings.
{% callout type="caution" title="Your code will be modified!" %}
This generator will modify your code, so make sure to commit your changes before running it.
@ -22,15 +22,14 @@ When running this generator, you will be prompted to provide the following:
You must provide a `project` and a `uiFramework` for the generator to work.
You may also pass the `includeVitest` flag. This will also change your `test` target to use the `@nx/vite:test` executor, and configure your project for testing with [Vitest](https://vitest.dev/), by adding the `test` configuration in your `vite.config.ts` file.
You may also pass the `includeVitest` flag. This will also configure your project for testing with [Vitest](https://vitest.dev/), by adding the `test` configuration in your `vite.config.ts` file.
## Converting custom (specific) targets
## How to use
By default, the `@nx/vite:configuration` generator will search your project's configuration to find the targets for serving, building, and testing your project, and it will attempt to convert these targets to use the `@nx/vite` executors.
If you have an existing project that does not use Vite, you may want to convert it to use Vite. This can be a `webpack` project, a buildable JS library that uses the `@nx/js:babel`, the `@nx/js:swc` or the `@nx/rollup:rollup` executor, or even a non-buildable library.
By default, the `@nx/vite:configuration` generator will search your project to find the relevant configuration (either a `webpack.config.ts` file for example, or the `@nx/js` executors). If it determines that your project can be converted, then Nx will generate the configuration for you. If it cannot determine that your project can be converted, it will ask you if you want to convert it anyway or throw an error if it determines that it cannot be converted.
Your targets for building, serving and testing may not be named `build`, `serve` and `test`. Nx will try to infer the correct targets to convert, and it will attempt to convert the first one it finds for each of these actions if you have more than one. If you have more than one target for serving, building, or testing your project, you can pass the `--serveTarget`, `--buildTarget`, and `--testTarget` flags to the generator, to tell Nx specifically which targets to convert.
Nx will determine if the targets you provided (or the ones it inferred) are valid and can be converted to use the `@nx/vite` executors. If the targets are not valid, the generator will fail. If no targets are found - or recognized to be either supported or unsupported - Nx will ask you whether you want to convert your project anyway. If you choose to do so, Nx will configure your project to use Vite.js, creating new targets for you, and creating or modifying your `vite.config.ts` file. You can then test on your own if the result works or not, and modify the configuration as needed. It's suggested that if Nx does not recognize your targets automatically, you commit your changes before running the generator, so you can revert the changes if needed.
You can then test on your own if the result works or not, and modify the configuration as needed. It's suggested that you commit your changes before running the generator, so you can revert the changes if needed.
## Projects that can be converted to use the `@nx/vite` executors
@ -43,20 +42,10 @@ The list of executors for building, testing and serving that can be converted to
- `@nxext/vite:build`
- `@nx/js:babel`
- `@nx/js:swc`
- `@nx/webpack:webpack`
- `@nx/rollup:rollup`
- `@nx/webpack:webpack`
- `@nx/web:rollup`
### Supported `serve` executors
- `@nxext/vite:dev`
- `@nx/webpack:dev-server`
### Supported `test` executors
- `@nx/jest:jest`
- `@nxext/vitest:vitest`
### Unsupported executors
- `@nx/angular:ng-packagr-lite`
@ -72,34 +61,24 @@ The list of executors for building, testing and serving that can be converted to
- any executor _not_ listed in the lists of "supported executors"
- any project that does _not_ have a target for building, serving or testing
We **cannot** guarantee that projects using unsupported executors - _or any executor that is NOT listed in the list of "supported executors"_ - for either building, testing or serving will work correctly when converted to use the `@nx/vite` executors.
If you have a project that does _not_ use one of the supported executors you can try to [configure it to use the `@nx/vite` executors manually](/recipes/vite/configure-vite), but it may not work properly.
We **cannot** guarantee that projects using unsupported executors - _or any executor that is NOT listed in the list of "supported executors"_ - for either building, testing or serving will work correctly when converted to use Vite.
You can read more in the [Vite package overview page](/packages/vite).
## Examples
### Change a React app to use Vite
### Convert a React app to use Vite
```bash
nx g @nx/vite:configuration --project=my-react-app --uiFramework=react --includeVitest
```
This will change the `my-react-app` project to use the `@nx/vite` executors for building, serving and testing the application.
This will configure the `my-react-app` project to use Vite.
### Change a Web app to use Vite
### Convert a Web app to use Vite
```bash
nx g @nx/vite:configuration --project=my-web-app --uiFramework=none --includeVitest
```
This will change the `my-web-app` project to use the `@nx/vite` executors for building, serving and testing the application.
### Change only my custom provided targets to use Vite
```bash
nx g @nx/vite:configuration --project=my-react-app --uiFramework=react --includeVitest --buildTarget=my-build --serveTarget=my-serve --testTarget=my-test
```
This will change the `my-build`, `my-serve` and `my-test` targets to use the `@nx/vite` executors for building, serving and testing the application, even if you have other targets for these actions as well.
This will configure the `my-web-app` project to use Vite.

View File

@ -121,36 +121,6 @@ export default defineConfig({
"
`;
exports[`@nx/vite:configuration library mode should set up non buildable library correctly 2`] = `
"{
"projects": {
"react-lib-nonb-jest": {
"name": "react-lib-nonb-jest",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "libs/react-lib-nonb-jest",
"sourceRoot": "libs/react-lib-nonb-jest/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/react-lib-nonb-jest/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
}
}
"
`;
exports[`@nx/vite:configuration library mode should set up non buildable library which already has vite.config.ts correctly 1`] = `
"import dts from 'vite-plugin-dts';
import * as path from 'path';
@ -207,91 +177,6 @@ export default defineConfig({
"
`;
exports[`@nx/vite:configuration library mode should set up non buildable library which already has vite.config.ts correctly 2`] = `
"{
"projects": {
"react-lib-nonb-vitest": {
"name": "react-lib-nonb-vitest",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "libs/react-lib-nonb-vitest",
"sourceRoot": "libs/react-lib-nonb-vitest/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/vite:test",
"outputs": ["{projectRoot}/coverage"],
"options": {
"passWithNoTests": true,
"reportsDirectory": "{workspaceRoot}/coverage/{projectRoot}"
}
}
},
"tags": []
}
}
}
"
`;
exports[`@nx/vite:configuration transform React app to use Vite by providing custom targets transform React app if supported executor is provided should transform workspace.json project config 1`] = `
"{
"projects": {
"my-test-mixed-react-app": {
"name": "my-test-mixed-react-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "apps/my-test-mixed-react-app",
"sourceRoot": "apps/my-test-mixed-react-app/src",
"projectType": "application",
"targets": {
"invalid-build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"]
},
"valid-build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"]
},
"serve": {
"executor": "@nx/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-test-mixed-react-app:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "my-test-mixed-react-app:build:development"
},
"production": {
"buildTarget": "my-test-mixed-react-app:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-mixed-react-app/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
}
}
"
`;
exports[`@nx/vite:configuration transform React app to use Vite should create vite.config file at the root of the app 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
@ -349,96 +234,6 @@ exports[`@nx/vite:configuration transform React app to use Vite should move inde
"
`;
exports[`@nx/vite:configuration transform React app to use Vite should transform workspace.json project config 1`] = `
"{
"projects": {
"my-test-react-app": {
"name": "my-test-react-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "apps/my-test-react-app",
"sourceRoot": "apps/my-test-react-app/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/my-test-react-app",
"index": "apps/my-test-react-app/src/index.html",
"baseHref": "/",
"main": "apps/my-test-react-app/src/main.tsx",
"polyfills": "apps/my-test-react-app/src/polyfills.ts",
"tsConfig": "apps/my-test-react-app/tsconfig.app.json",
"assets": [
"apps/my-test-react-app/src/favicon.ico",
"apps/my-test-react-app/src/assets"
],
"styles": ["apps/my-test-react-app/src/styles.css"],
"scripts": [],
"webpackConfig": "@nx/react/plugins/webpack"
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true,
"vendorChunk": true
},
"production": {
"fileReplacements": [
{
"replace": "apps/my-test-react-app/src/environments/environment.ts",
"with": "apps/my-test-react-app/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-test-react-app:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "my-test-react-app:build:development"
},
"production": {
"buildTarget": "my-test-react-app:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-react-app/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
}
}
"
`;
exports[`@nx/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
@ -496,83 +291,6 @@ exports[`@nx/vite:configuration transform Web app to use Vite should move index.
"
`;
exports[`@nx/vite:configuration transform Web app to use Vite should transform workspace.json project config 1`] = `
"{
"projects": {
"my-test-web-app": {
"name": "my-test-web-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/my-test-web-app",
"sourceRoot": "apps/my-test-web-app/src",
"tags": [],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/my-test-web-app",
"compiler": "babel",
"main": "apps/my-test-web-app/src/main.ts",
"tsConfig": "apps/my-test-web-app/tsconfig.app.json",
"assets": [
"apps/my-test-web-app/src/favicon.ico",
"apps/my-test-web-app/src/assets"
],
"index": "apps/my-test-web-app/src/index.html",
"baseHref": "/",
"polyfills": "apps/my-test-web-app/src/polyfills.ts",
"styles": ["apps/my-test-web-app/src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"fileReplacements": [
{
"replace": "apps/my-test-web-app/src/environments/environment.ts",
"with": "apps/my-test-web-app/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "my-test-web-app:build"
},
"configurations": {
"production": {
"buildTarget": "my-test-web-app:build:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-web-app/jest.config.ts",
"passWithNoTests": true
}
}
}
}
}
}
"
`;
exports[`@nx/vite:configuration vitest should create a vitest configuration if "includeVitest" is true 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';

View File

@ -12,7 +12,6 @@ import {
mockUnknownAppGenerator,
mockWebAppGenerator,
} from '../../utils/test-utils';
import { ViteConfigurationGeneratorSchema } from './schema';
describe('@nx/vite:configuration', () => {
let tree: Tree;
@ -56,7 +55,7 @@ describe('@nx/vite:configuration', () => {
tree,
'apps/my-test-react-app/tsconfig.json'
);
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
expect(tsconfigJson.compilerOptions.jsx).toBe('react-jsx');
});
it('should create vite.config file at the root of the app', () => {
@ -65,10 +64,6 @@ describe('@nx/vite:configuration', () => {
tree.read('apps/my-test-react-app/vite.config.ts', 'utf-8')
).toMatchSnapshot();
});
it('should transform workspace.json project config', () => {
expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot();
});
});
describe('transform Web app to use Vite', () => {
@ -84,7 +79,6 @@ describe('@nx/vite:configuration', () => {
);
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-web-app',
});
@ -106,7 +100,7 @@ describe('@nx/vite:configuration', () => {
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(tree, 'apps/my-test-web-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
expect(tsconfigJson.compilerOptions.noImplicitReturns).toBeTruthy();
});
it('should create vite.config file at the root of the app', () => {
@ -115,10 +109,6 @@ describe('@nx/vite:configuration', () => {
tree.read('apps/my-test-web-app/vite.config.ts', 'utf-8')
).toMatchSnapshot();
});
it('should transform workspace.json project config', () => {
expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot();
});
});
describe('do not transform Angular app to use Vite', () => {
@ -132,14 +122,13 @@ describe('@nx/vite:configuration', () => {
try {
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-angular-app',
});
} catch (e) {
expect(e).toBeDefined();
expect(e.toString()).toContain(
'The project my-test-angular-app cannot be converted to use the @nx/vite executors'
'Nx cannot convert your project to use vite.'
);
}
});
@ -151,27 +140,6 @@ describe('@nx/vite:configuration', () => {
mockUnknownAppGenerator(tree);
});
it('should throw when trying to convert something unknown', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
confirmSpy.mockResolvedValue(true);
expect.assertions(2);
try {
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-random-app',
});
} catch (e) {
expect(e).toBeDefined();
expect(e.toString()).toContain(
'Error: Cannot find apps/my-test-random-app/tsconfig.json'
);
}
});
it('should throw when trying to convert something unknown and user denies conversion', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
@ -182,115 +150,18 @@ describe('@nx/vite:configuration', () => {
try {
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-random-app',
});
} catch (e) {
expect(e).toBeDefined();
expect(e.toString()).toContain(
'Nx could not verify that the executors you are using can be converted to the @nx/vite executors.'
'Nx could not verify that your project can be converted to use Vite.'
);
}
});
});
describe('transform React app to use Vite by providing custom targets', () => {
describe('transform React app if supported executor is provided', () => {
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
mockReactMixedAppGenerator(tree);
const existing = 'existing';
const existingVersion = '1.0.0';
addDependenciesToPackageJson(
tree,
{ '@nx/vite': nxVersion, [existing]: existingVersion },
{ [existing]: existingVersion }
);
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
project: 'my-test-mixed-react-app',
buildTarget: 'valid-build',
});
});
it('should add vite packages and react-related dependencies for vite', async () => {
const packageJson = readJson(tree, '/package.json');
expect(packageJson.devDependencies).toMatchObject({
vite: expect.any(String),
'@vitejs/plugin-react': expect.any(String),
});
});
it('should create vite.config file at the root of the app', () => {
expect(tree.exists('apps/my-test-mixed-react-app/vite.config.ts')).toBe(
true
);
});
it('should transform workspace.json project config', () => {
expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot();
});
});
describe('do NOT transform React app if unsupported executor is provided', () => {
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
mockReactMixedAppGenerator(tree);
const existing = 'existing';
const existingVersion = '1.0.0';
addDependenciesToPackageJson(
tree,
{ '@nx/vite': nxVersion, [existing]: existingVersion },
{ [existing]: existingVersion }
);
});
it('should throw when trying to convert and user denies', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
confirmSpy.mockResolvedValue(false);
expect.assertions(2);
try {
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-mixed-react-app',
buildTarget: 'invalid-build',
});
} catch (e) {
expect(e).toBeDefined();
expect(e.toString()).toContain(
'The build target invalid-build cannot be converted to use the @nx/vite:build executor'
);
}
});
it('should NOT throw error when trying to convert and user confirms', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
confirmSpy.mockResolvedValue(true);
expect.assertions(1);
try {
await viteConfigurationGenerator(tree, {
addPlugin: true,
uiFramework: 'react',
uiFramework: 'none',
project: 'my-test-mixed-react-app',
buildTarget: 'invalid-build',
});
expect(
tree.exists('apps/my-test-mixed-react-app/vite.config.ts')
).toBe(true);
} catch (e) {
throw new Error('Should not throw error');
}
});
});
});
describe('vitest', () => {
beforeAll(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
@ -351,8 +222,6 @@ describe('@nx/vite:configuration', () => {
expect(
tree.read('libs/react-lib-nonb-jest/vite.config.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot();
});
it('should set up non buildable library which already has vite.config.ts correctly', async () => {
@ -372,8 +241,6 @@ describe('@nx/vite:configuration', () => {
expect(
tree.read('libs/react-lib-nonb-vitest/vite.config.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('workspace.json', 'utf-8')).toMatchSnapshot();
} catch (e) {
throw new Error('Should not throw error');
}

View File

@ -11,24 +11,18 @@ import {
import { initGenerator as jsInitGenerator } from '@nx/js';
import {
addOrChangeBuildTarget,
addOrChangeServeTarget,
addBuildTarget,
addServeTarget,
addPreviewTarget,
createOrEditViteConfig,
deleteWebpackConfig,
editTsConfig,
findExistingTargetsInProject,
handleUnknownExecutors,
handleUnsupportedUserProvidedTargets,
moveAndEditIndexHtml,
TargetFlags,
UserProvidedTargetName,
} from '../../utils/generator-utils';
import initGenerator from '../init/init';
import vitestGenerator from '../vitest/vitest-generator';
import { ViteConfigurationGeneratorSchema } from './schema';
import { ensureDependencies } from '../../utils/ensure-dependencies';
import { convertNonVite } from './lib/convert-non-vite';
export function viteConfigurationGenerator(
host: Tree,
@ -49,16 +43,9 @@ export async function viteConfigurationGeneratorInternal(
schema.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false';
const projectConfig = readProjectConfiguration(tree, schema.project);
const {
targets,
root: projectRoot,
} = projectConfig;
const { targets, root: projectRoot } = projectConfig;
const projectType = projectConfig.projectType ?? 'library';
let buildTargetName = 'build';
let serveTargetName = 'serve';
let testTargetName = 'test';
schema.includeLib ??= projectType === 'library';
@ -73,104 +60,7 @@ export async function viteConfigurationGeneratorInternal(
let projectAlreadyHasViteTargets: TargetFlags = {};
if (!schema.newProject) {
const userProvidedTargetName: UserProvidedTargetName = {
build: schema.buildTarget,
serve: schema.serveTarget,
test: schema.testTarget,
};
const {
validFoundTargetName,
projectContainsUnsupportedExecutor,
userProvidedTargetIsUnsupported,
alreadyHasNxViteTargets,
} = findExistingTargetsInProject(targets, userProvidedTargetName);
projectAlreadyHasViteTargets = alreadyHasNxViteTargets;
/**
* This means that we only found unsupported build targets in that project.
* The only way that buildTarget is defined, means that it is supported.
*
* If the `unsupported` flag was false, it would mean that we did not find
* a build target at all, so we can create a new one.
*
* So we only throw if we found a target, but it is unsupported.
*/
if (!validFoundTargetName.build && projectContainsUnsupportedExecutor) {
throw new Error(
`The project ${schema.project} cannot be converted to use the @nx/vite executors.`
);
}
if (
alreadyHasNxViteTargets.build &&
(alreadyHasNxViteTargets.serve || projectType === 'library') &&
alreadyHasNxViteTargets.test
) {
throw new Error(
`The project ${schema.project} is already configured to use the @nx/vite executors.
Please try a different project, or remove the existing targets
and re-run this generator to reset the existing Vite Configuration.
`
);
}
/**
* This means that we did not find any supported executors
* so we don't have any valid target names.
*
* However, the executors that we may have found are not in the
* list of the specifically unsupported executors either.
*
* So, we should warn the user about it.
*/
if (
!projectContainsUnsupportedExecutor &&
!validFoundTargetName.build &&
!validFoundTargetName.serve &&
!validFoundTargetName.test
) {
await handleUnknownExecutors(schema.project);
}
/**
* There is a possibility at this stage that the user has provided
* targets with unsupported executors.
* We keep track here of which of the targets that the user provided
* are unsupported.
* We do this with the `userProvidedTargetIsUnsupported` object,
* which contains flags for each target (whether it is supported or not).
*
* We also keep track of the targets that we found in the project,
* through the findExistingTargetsInProject function, which returns
* targets for build/serve/test that use supported executors, and
* can be converted to use the vite executors. These are the
* kept in the validFoundTargetName object.
*/
await handleUnsupportedUserProvidedTargets(
userProvidedTargetIsUnsupported,
userProvidedTargetName,
validFoundTargetName
);
/**
* Once the user is at this stage, then they can go ahead and convert.
*/
buildTargetName = validFoundTargetName.build ?? buildTargetName;
serveTargetName = validFoundTargetName.serve ?? serveTargetName;
if (projectType === 'application') {
moveAndEditIndexHtml(tree, schema, buildTargetName);
}
deleteWebpackConfig(
tree,
projectRoot,
targets?.[buildTargetName]?.options?.webpackConfig
);
editTsConfig(tree, schema);
await convertNonVite(tree, schema, projectRoot, projectType, targets);
}
const jsInitTask = await jsInitGenerator(tree, {
@ -192,15 +82,15 @@ export async function viteConfigurationGeneratorInternal(
if (!hasPlugin) {
if (!projectAlreadyHasViteTargets.build) {
addOrChangeBuildTarget(tree, schema, buildTargetName);
addBuildTarget(tree, schema, 'build');
}
if (!schema.includeLib) {
if (!projectAlreadyHasViteTargets.serve) {
addOrChangeServeTarget(tree, schema, serveTargetName);
addServeTarget(tree, schema, 'serve');
}
if (!projectAlreadyHasViteTargets.preview) {
addPreviewTarget(tree, schema, serveTargetName);
addPreviewTarget(tree, schema, 'preview');
}
}
}
@ -267,7 +157,7 @@ export async function viteConfigurationGeneratorInternal(
inSourceTests: schema.inSourceTests,
coverageProvider: 'v8',
skipViteConfig: true,
testTarget: testTargetName,
testTarget: 'test',
skipFormat: true,
addPlugin: schema.addPlugin,
});

View File

@ -0,0 +1,100 @@
import {
TargetConfiguration,
Tree,
joinPathFragments,
logger,
} from '@nx/devkit';
import {
findViteConfig,
findWebpackConfig,
} from '../../../utils/find-vite-config';
import { ViteConfigurationGeneratorSchema } from '../schema';
import {
deleteWebpackConfig,
editTsConfig,
findExistingJsBuildTargetInProject,
handleUnknownConfiguration,
moveAndEditIndexHtml,
} from '../../../utils/generator-utils';
export async function convertNonVite(
tree: Tree,
schema: ViteConfigurationGeneratorSchema,
projectRoot: string,
projectType: string,
targets: {
[targetName: string]: TargetConfiguration<any>;
}
) {
// Check if it has vite
const hasViteConfig = findViteConfig(tree, projectRoot);
const hasIndexHtmlAtRoot = tree.exists(
joinPathFragments(projectRoot, 'index.html')
);
// Check if it has webpack
const hasWebpackConfig = findWebpackConfig(tree, projectRoot);
if (hasWebpackConfig) {
if (projectType === 'application') {
moveAndEditIndexHtml(tree, schema);
}
deleteWebpackConfig(tree, projectRoot, hasWebpackConfig);
editTsConfig(tree, schema);
return;
}
if (
projectType === 'application' &&
hasViteConfig &&
hasIndexHtmlAtRoot &&
!hasWebpackConfig
) {
throw new Error(
`The project ${schema.project} is already configured to use Vite.`
);
return;
}
if (projectType === 'library' && hasViteConfig) {
// continue anyway - it could need to be updated - only update vite.config.ts in any case
editTsConfig(tree, schema);
return;
}
// Does the project have js executors?
const { supported: jsTargetName, unsupported } =
findExistingJsBuildTargetInProject(targets);
if (jsTargetName) {
editTsConfig(tree, schema);
return;
}
if (unsupported) {
throw new Error(`
Nx cannot convert your project to use vite.
Please try again with a different project.
`);
}
// If it's a library, it's most possible it's non-buildable
// So fix the tsconfig and return, to continue with the rest of the setup
if (
projectType === 'library' &&
!hasViteConfig &&
!hasWebpackConfig &&
!jsTargetName
) {
editTsConfig(tree, schema);
return;
}
/**
* The project is an app.
* The project has no js executors, no webpack config, no vite config.
* We did not find any configuration that hints the project can
* definitely be converted.
* So, we should warn the user about it.
* They can choose whether to convert it or not
*/
await handleUnknownConfiguration(schema.project);
}

View File

@ -6,9 +6,6 @@ export interface ViteConfigurationGeneratorSchema {
includeVitest?: boolean;
inSourceTests?: boolean;
includeLib?: boolean;
buildTarget?: string;
serveTarget?: string;
testTarget?: string;
skipFormat?: boolean;
testEnvironment?: 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' | string;
addPlugin?: boolean;

View File

@ -44,18 +44,6 @@
"default": false,
"hidden": true
},
"buildTarget": {
"type": "string",
"description": "The build target of the project to be transformed to use the @nx/vite:build executor."
},
"serveTarget": {
"type": "string",
"description": "The serve target of the project to be transformed to use the @nx/vite:dev-server and @nx/vite:preview-server executors."
},
"testTarget": {
"type": "string",
"description": "The test target of the project to be transformed to use the @nx/vite:test executor."
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",

View File

@ -14,7 +14,6 @@ import {
import {
addOrChangeTestTarget,
createOrEditViteConfig,
findExistingTargetsInProject,
} from '../../utils/generator-utils';
import { VitestGeneratorSchema } from './schema';
@ -68,10 +67,7 @@ export async function vitestGeneratorInternal(
: p.plugin === '@nx/vite/plugin') || hasPlugin
);
if (!hasPluginCheck) {
const testTarget =
schema.testTarget ??
findExistingTargetsInProject(targets).validFoundTargetName.test ??
'test';
const testTarget = schema.testTarget ?? 'test';
addOrChangeTestTarget(tree, schema, testTarget);
}

View File

@ -2,6 +2,7 @@ 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 { findViteConfig } from '../../utils/find-vite-config';
export default function update(tree: Tree) {
const projects = getProjects(tree);
@ -51,13 +52,3 @@ export default function update(tree: Tree) {
}
);
}
function findViteConfig(tree: Tree, searchRoot: string) {
const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
for (const ext of allowsExt) {
if (tree.exists(joinPathFragments(searchRoot, `vite.config.${ext}`))) {
return joinPathFragments(searchRoot, `vite.config.${ext}`);
}
}
}

View File

@ -12,6 +12,7 @@ import { updateTestConfig } from './lib/edit-test-config';
import { addFileReplacements } from './lib/add-file-replacements';
import { tsquery } from '@phenomnomnominal/tsquery';
import ts = require('typescript');
import { findViteConfig } from '../../utils/find-vite-config';
export default async function updateBuildDir(tree: Tree) {
const projects = getProjects(tree);
@ -54,16 +55,6 @@ export default async function updateBuildDir(tree: Tree) {
await formatFiles(tree);
}
function findViteConfig(tree: Tree, searchRoot: string) {
const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
for (const ext of allowsExt) {
if (tree.exists(joinPathFragments(searchRoot, `vite.config.${ext}`))) {
return joinPathFragments(searchRoot, `vite.config.${ext}`);
}
}
}
export function getConfigNode(configFileContents: string): ts.Node | undefined {
if (!configFileContents) {
return;

View File

@ -9,3 +9,13 @@ export function findViteConfig(tree: Tree, searchRoot: string) {
}
}
}
export function findWebpackConfig(tree: Tree, searchRoot: string) {
const allowsExt = ['js', 'ts', 'mjs', 'cjs'];
for (const ext of allowsExt) {
if (tree.exists(joinPathFragments(searchRoot, `webpack.config.${ext}`))) {
return joinPathFragments(searchRoot, `webpack.config.${ext}`);
}
}
}

View File

@ -5,9 +5,8 @@ import {
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
findExistingTargetsInProject,
findExistingJsBuildTargetInProject,
getViteConfigPathForProject,
handleUnsupportedUserProvidedTargets,
} from './generator-utils';
import {
mockReactAppGenerator,
@ -97,105 +96,22 @@ describe('generator utils', () => {
});
});
describe('findExistingTargetsInProject', () => {
it('should return the correct targets', () => {
describe('findExistingJsBuildTargetInProject', () => {
it('should return no targets', () => {
mockReactAppGenerator(tree);
const { targets } = readProjectConfiguration(tree, 'my-test-react-app');
const existingTargets = findExistingTargetsInProject(targets);
expect(existingTargets).toMatchObject({
userProvidedTargetIsUnsupported: {},
validFoundTargetName: {
build: 'build',
serve: 'serve',
test: 'test',
},
projectContainsUnsupportedExecutor: false,
});
const existingTargets = findExistingJsBuildTargetInProject(targets);
expect(existingTargets).toMatchObject({});
});
it('should return the correct - undefined - targets for Angular apps', () => {
mockAngularAppGenerator(tree);
const { targets } = readProjectConfiguration(tree, 'my-test-angular-app');
const existingTargets = findExistingTargetsInProject(targets);
const existingTargets = findExistingJsBuildTargetInProject(targets);
expect(existingTargets).toMatchObject({
userProvidedTargetIsUnsupported: {},
validFoundTargetName: {
test: 'test',
},
projectContainsUnsupportedExecutor: true,
unsupported: 'build',
});
});
});
describe('handleUnsupportedUserProvidedTargets', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should throw error if unsupported and user does not want to confirm', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
confirmSpy.mockResolvedValueOnce(false);
const object = {
unsupportedUserProvidedTarget: {
build: true,
},
userProvidedTargets: {
build: 'my-build',
serve: 'my-serve',
test: 'my-test',
},
targets: {
build: 'build',
serve: 'serve',
test: 'test',
},
};
expect(async () => {
await handleUnsupportedUserProvidedTargets(
object.unsupportedUserProvidedTarget,
object.userProvidedTargets,
object.targets
);
}).rejects.toThrowErrorMatchingInlineSnapshot(`
"The build target my-build cannot be converted to use the @nx/vite:build executor.
Please try again, either by providing a different build target or by not providing a target at all (Nx will
convert the first one it finds, most probably this one: build)
Please note that converting a potentially non-compatible project to use Vite.js may result in unexpected behavior. Always commit
your changes before converting a project to use Vite.js, and test the converted project thoroughly before deploying it.
"
`);
});
it('should NOT throw error if unsupported and user confirms', async () => {
const { Confirm } = require('enquirer');
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
confirmSpy.mockResolvedValue(true);
const object = {
unsupportedUserProvidedTarget: {
build: true,
},
userProvidedTargets: {
build: 'my-build',
serve: 'my-serve',
test: 'my-test',
},
targets: {
build: 'build',
serve: 'serve',
test: 'test',
},
};
expect(async () => {
await handleUnsupportedUserProvidedTargets(
object.unsupportedUserProvidedTarget,
object.userProvidedTargets,
object.targets
);
}).not.toThrow();
});
});
});

View File

@ -10,7 +10,6 @@ import {
writeJson,
} from '@nx/devkit';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import { VitestExecutorOptions } from '../executors/test/schema';
import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema';
@ -22,45 +21,20 @@ export type TargetFlags = Partial<Record<Target, boolean>>;
export type UserProvidedTargetName = Partial<Record<Target, string>>;
export type ValidFoundTargetName = Partial<Record<Target, string>>;
export function findExistingTargetsInProject(
targets: {
[targetName: string]: TargetConfiguration;
},
userProvidedTargets?: UserProvidedTargetName
): {
validFoundTargetName: ValidFoundTargetName;
projectContainsUnsupportedExecutor: boolean;
userProvidedTargetIsUnsupported: TargetFlags;
alreadyHasNxViteTargets: TargetFlags;
export function findExistingJsBuildTargetInProject(targets: {
[targetName: string]: TargetConfiguration;
}): {
supported?: string;
unsupported?: string;
} {
const output: ReturnType<typeof findExistingTargetsInProject> = {
validFoundTargetName: {},
projectContainsUnsupportedExecutor: false,
userProvidedTargetIsUnsupported: {},
alreadyHasNxViteTargets: {},
};
const output: {
supported?: string;
unsupported?: string;
} = {};
const supportedExecutors = {
build: [
'@nxext/vite:build',
'@nx/js:babel',
'@nx/js:swc',
'@nx/webpack:webpack',
'@nx/rollup:rollup',
'@nrwl/js:babel',
'@nrwl/js:swc',
'@nrwl/webpack:webpack',
'@nrwl/rollup:rollup',
'@nrwl/web:rollup',
],
serve: [
'@nxext/vite:dev',
'@nx/webpack:dev-server',
'@nrwl/webpack:dev-server',
],
test: ['@nx/jest:jest', '@nrwl/jest:jest', '@nxext/vitest:vitest'],
build: ['@nx/js:babel', '@nx/js:swc', '@nx/rollup:rollup'],
};
const unsupportedExecutors = [
'@nx/angular:ng-packagr-lite',
'@nx/angular:package',
@ -73,7 +47,6 @@ export function findExistingTargetsInProject(
'@nx/react-native:build-android',
'@nx/react-native:bundle',
'@nx/next:build',
'@nx/next:server',
'@nx/js:tsc',
'@nrwl/angular:ng-packagr-lite',
'@nrwl/angular:package',
@ -86,81 +59,22 @@ export function findExistingTargetsInProject(
'@nrwl/react-native:build-android',
'@nrwl/react-native:bundle',
'@nrwl/next:build',
'@nrwl/next:server',
'@nrwl/js:tsc',
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:dev-server',
'@angular-devkit/build-angular:browser-esbuild',
'@angular-devkit/build-angular:application',
];
// First, we check if the user has provided a target
// If they have, we check if the executor the target is using is supported
// If it's not supported, then we set the unsupported flag to true for that target
function checkUserProvidedTarget(target: Target) {
if (userProvidedTargets?.[target]) {
if (
supportedExecutors[target].includes(
targets[userProvidedTargets[target]]?.executor
)
) {
output.validFoundTargetName[target] = userProvidedTargets[target];
} else {
output.userProvidedTargetIsUnsupported[target] = true;
}
}
}
checkUserProvidedTarget('build');
checkUserProvidedTarget('serve');
checkUserProvidedTarget('test');
// Returns early when we have a build, serve, and test targets.
if (
output.validFoundTargetName.build &&
output.validFoundTargetName.serve &&
output.validFoundTargetName.test
) {
return output;
}
// We try to find the targets that are using the supported executors
// for build, serve and test, since these are the ones we will be converting
// We try to find the target that is using the supported executors
// for build since this is the one we will be converting
for (const target in targets) {
const executorName = targets[target].executor;
const hasViteTargets = output.alreadyHasNxViteTargets;
hasViteTargets.build ||=
executorName === '@nx/vite:build' || executorName === '@nrwl/vite:build';
hasViteTargets.serve ||=
executorName === '@nx/vite:dev-server' ||
executorName === '@nrwl/vite:dev-server';
hasViteTargets.test ||=
executorName === '@nx/vite:test' || executorName === '@nrwl/vite:test';
hasViteTargets.preview ||=
executorName === '@nx/vite:preview-server' ||
executorName === '@nrwl/vite:preview-server';
const foundTargets = output.validFoundTargetName;
if (
!foundTargets.build &&
supportedExecutors.build.includes(executorName)
) {
foundTargets.build = target;
if (supportedExecutors.build.includes(executorName)) {
output.supported = target;
} else if (unsupportedExecutors.includes(executorName)) {
output.unsupported = target;
}
if (
!foundTargets.serve &&
supportedExecutors.serve.includes(executorName)
) {
foundTargets.serve = target;
}
if (!foundTargets.test && supportedExecutors.test.includes(executorName)) {
foundTargets.test = target;
}
output.projectContainsUnsupportedExecutor ||=
unsupportedExecutors.includes(executorName);
}
return output;
}
@ -196,51 +110,39 @@ export function addOrChangeTestTarget(
updateProjectConfiguration(tree, options.project, project);
}
export function addOrChangeBuildTarget(
export function addBuildTarget(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
target: string
) {
addBuildTargetDefaults(tree, '@nx/vite:build');
const project = readProjectConfiguration(tree, options.project);
const buildOptions: ViteBuildExecutorOptions = {
outputPath: joinPathFragments(
'dist',
project.root != '.' ? project.root : options.project
),
};
project.targets ??= {};
if (project.targets[target]) {
if (project.targets[target].executor === '@nxext/vite:build') {
buildOptions['base'] = project.targets[target].options?.baseHref;
buildOptions['sourcemap'] = project.targets[target].options?.sourcemaps;
}
project.targets[target].options = { ...buildOptions };
project.targets[target].executor = '@nx/vite:build';
} else {
project.targets[target] = {
executor: '@nx/vite:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
development: {
mode: 'development',
},
production: {
mode: 'production',
},
project.targets[target] = {
executor: '@nx/vite:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
development: {
mode: 'development',
},
};
}
production: {
mode: 'production',
},
},
};
updateProjectConfiguration(tree, options.project, project);
}
export function addOrChangeServeTarget(
export function addServeTarget(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
target: string
@ -249,35 +151,23 @@ export function addOrChangeServeTarget(
project.targets ??= {};
if (project.targets[target]) {
const serveTarget = project.targets[target];
const serveOptions: ViteDevServerExecutorOptions = {
project.targets[target] = {
executor: '@nx/vite:dev-server',
defaultConfiguration: 'development',
options: {
buildTarget: `${options.project}:build`,
};
if (serveTarget.executor === '@nxext/vite:dev') {
serveOptions.proxyConfig = project.targets[target].options.proxyConfig;
}
serveTarget.executor = '@nx/vite:dev-server';
serveTarget.options = serveOptions;
} else {
project.targets[target] = {
executor: '@nx/vite:dev-server',
defaultConfiguration: 'development',
options: {
buildTarget: `${options.project}:build`,
},
configurations: {
development: {
buildTarget: `${options.project}:build:development`,
hmr: true,
},
configurations: {
development: {
buildTarget: `${options.project}:build:development`,
hmr: true,
},
production: {
buildTarget: `${options.project}:build:production`,
hmr: false,
},
production: {
buildTarget: `${options.project}:build:production`,
hmr: false,
},
};
}
},
};
updateProjectConfiguration(tree, options.project, project);
}
@ -288,7 +178,6 @@ export function addOrChangeServeTarget(
* @param tree
* @param options
* @param serveTarget An existing serve target.
* @param previewTarget The preview target to create.
*/
export function addPreviewTarget(
tree: Tree,
@ -339,44 +228,26 @@ export function editTsConfig(
const config = readJson(tree, `${projectConfig.root}/tsconfig.json`);
const commonCompilerOptions = {
target: 'ESNext',
useDefineForClassFields: true,
module: 'ESNext',
strict: true,
moduleResolution: 'Node',
resolveJsonModule: true,
isolatedModules: true,
types: ['vite/client'],
noEmit: true,
};
switch (options.uiFramework) {
case 'react':
config.compilerOptions = {
...commonCompilerOptions,
lib: ['DOM', 'DOM.Iterable', 'ESNext'],
jsx: 'react-jsx',
allowJs: false,
esModuleInterop: false,
skipLibCheck: true,
allowSyntheticDefaultImports: true,
forceConsistentCasingInFileNames: true,
jsx: 'react-jsx',
strict: true,
};
config.include = [...config.include, 'src'];
break;
case 'none':
config.compilerOptions = {
...commonCompilerOptions,
lib: ['ESNext', 'DOM'],
skipLibCheck: true,
esModuleInterop: true,
module: 'commonjs',
forceConsistentCasingInFileNames: true,
strict: true,
noUnusedLocals: true,
noUnusedParameters: true,
noImplicitOverride: true,
noPropertyAccessFromIndexSignature: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
config.include = [...config.include, 'src'];
break;
default:
break;
@ -405,19 +276,14 @@ export function deleteWebpackConfig(
export function moveAndEditIndexHtml(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
buildTarget: string
options: ViteConfigurationGeneratorSchema
) {
const projectConfig = readProjectConfiguration(tree, options.project);
let indexHtmlPath =
projectConfig.targets?.[buildTarget]?.options?.index ??
`${projectConfig.root}/src/index.html`;
let mainPath =
projectConfig.targets?.[buildTarget]?.options?.main ??
`${projectConfig.root}/src/main.ts${
options.uiFramework === 'react' ? 'x' : ''
}`;
let indexHtmlPath = `${projectConfig.root}/src/index.html`;
let mainPath = `${projectConfig.root}/src/main.ts${
options.uiFramework === 'react' ? 'x' : ''
}`;
if (projectConfig.root !== '.') {
mainPath = mainPath.replace(projectConfig.root, '');
@ -763,21 +629,17 @@ async function handleUnsupportedUserProvidedTargetsErrors(
}
}
export async function handleUnknownExecutors(projectName: string) {
export async function handleUnknownConfiguration(projectName: string) {
if (process.env.NX_INTERACTIVE === 'false') {
return;
}
logger.warn(
`
We could not find any targets in project ${projectName} that use executors which
can be converted to the @nx/vite executors.
This either means that your project may not have a target
for building, serving, or testing at all, or that your targets are
using executors that are not known to Nx.
We could not find any configuration in project ${projectName} that
indicates whether we can definitely convert to Vite.
If you still want to convert your project to use the @nx/vite executors,
If you still want to convert your project to use Vite,
please make sure to commit your changes before running this generator.
`
);
@ -785,13 +647,13 @@ export async function handleUnknownExecutors(projectName: string) {
const { Confirm } = require('enquirer');
const prompt = new Confirm({
name: 'question',
message: `Should Nx convert your project to use the @nx/vite executors?`,
message: `Should Nx convert your project to use Vite?`,
initial: true,
});
const shouldConvert = await prompt.run();
if (!shouldConvert) {
throw new Error(`
Nx could not verify that the executors you are using can be converted to the @nx/vite executors.
Nx could not verify that your project can be converted to use Vite.
Please try again with a different project.
`);
}

View File

@ -1,82 +0,0 @@
{
"name": "my-test-react-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "apps/my-test-react-app",
"sourceRoot": "apps/my-test-react-app/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/my-test-react-app",
"index": "apps/my-test-react-app/src/index.html",
"baseHref": "/",
"main": "apps/my-test-react-app/src/main.tsx",
"polyfills": "apps/my-test-react-app/src/polyfills.ts",
"tsConfig": "apps/my-test-react-app/tsconfig.app.json",
"assets": [
"apps/my-test-react-app/src/favicon.ico",
"apps/my-test-react-app/src/assets"
],
"styles": ["apps/my-test-react-app/src/styles.css"],
"scripts": [],
"webpackConfig": "@nx/react/plugins/webpack"
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true,
"vendorChunk": true
},
"production": {
"fileReplacements": [
{
"replace": "apps/my-test-react-app/src/environments/environment.ts",
"with": "apps/my-test-react-app/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-test-react-app:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "my-test-react-app:build:development"
},
"production": {
"buildTarget": "my-test-react-app:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-react-app/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}

View File

@ -1,69 +0,0 @@
{
"name": "my-test-web-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/my-test-web-app",
"sourceRoot": "apps/my-test-web-app/src",
"tags": [],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/my-test-web-app",
"compiler": "babel",
"main": "apps/my-test-web-app/src/main.ts",
"tsConfig": "apps/my-test-web-app/tsconfig.app.json",
"assets": [
"apps/my-test-web-app/src/favicon.ico",
"apps/my-test-web-app/src/assets"
],
"index": "apps/my-test-web-app/src/index.html",
"baseHref": "/",
"polyfills": "apps/my-test-web-app/src/polyfills.ts",
"styles": ["apps/my-test-web-app/src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"fileReplacements": [
{
"replace": "apps/my-test-web-app/src/environments/environment.ts",
"with": "apps/my-test-web-app/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "my-test-web-app:build"
},
"configurations": {
"production": {
"buildTarget": "my-test-web-app:build:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-web-app/jest.config.ts",
"passWithNoTests": true
}
}
}
}

View File

@ -1,7 +1,5 @@
import { Tree, writeJson } from '@nx/devkit';
import * as reactAppConfig from './test-files/react-project.config.json';
import * as reactViteConfig from './test-files/react-vite-project.config.json';
import * as webAppConfig from './test-files/web-project.config.json';
import * as angularAppConfig from './test-files/angular-project.config.json';
import * as randomAppConfig from './test-files/unknown-project.config.json';
import * as mixedAppConfig from './test-files/react-mixed-project.config.json';
@ -147,6 +145,8 @@ export function mockReactAppGenerator(tree: Tree): Tree {
`import ReactDOM from 'react-dom';\n`
);
tree.write(`apps/${appName}/webpack.config.ts`, ``);
tree.write(
`apps/${appName}/tsconfig.json`,
`{
@ -221,18 +221,7 @@ export function mockReactAppGenerator(tree: Tree): Tree {
</html>`
);
writeJson(tree, 'workspace.json', {
projects: {
'my-test-react-app': {
...reactAppConfig,
root: `apps/${appName}`,
projectType: 'application',
},
},
});
writeJson(tree, `apps/${appName}/project.json`, {
...reactAppConfig,
root: `apps/${appName}`,
projectType: 'application',
});
@ -363,6 +352,8 @@ export function mockWebAppGenerator(tree: Tree): Tree {
`
);
tree.write(`apps/${appName}/webpack.config.ts`, ``);
tree.write(
`apps/${appName}/src/index.html`,
`<!DOCTYPE html>
@ -382,23 +373,16 @@ export function mockWebAppGenerator(tree: Tree): Tree {
`
);
writeJson(
tree,
'workspace.json',
{
projects: {
'my-test-web-app': {
...webAppConfig,
root: `apps/${appName}`,
projectType: 'application',
},
writeJson(tree, 'workspace.json', {
projects: {
'my-test-web-app': {
root: `apps/${appName}`,
projectType: 'application',
},
}
);
},
});
writeJson(tree, `apps/${appName}/project.json`, {
...webAppConfig,
root: `apps/${appName}`,
projectType: 'application',
});

View File

@ -179,8 +179,6 @@ describe('app', () => {
path: './tsconfig.spec.json',
},
]);
expect(tsconfig.compilerOptions.types).toMatchObject(['vite/client']);
expect(tree.exists('my-app-e2e/cypress.config.ts')).toBeTruthy();
expect(tree.exists('my-app/index.html')).toBeTruthy();
expect(tree.exists('my-app/vite.config.ts')).toBeTruthy();
@ -578,7 +576,7 @@ describe('app', () => {
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(viteAppTree, '/my-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
expect(tsconfigJson.compilerOptions.noImplicitReturns).toBeTruthy();
});
it('should create index.html and vite.config file at the root of the app', () => {