diff --git a/docs/generated/packages/react.json b/docs/generated/packages/react.json index da3d285d6c..529e39aa04 100644 --- a/docs/generated/packages/react.json +++ b/docs/generated/packages/react.json @@ -48,6 +48,11 @@ "type": "boolean", "default": false }, + "skipBabelConfig": { + "description": "Do not generate a root babel.config.json (if babel is not needed).", + "type": "boolean", + "default": false + }, "js": { "type": "boolean", "default": false, @@ -244,7 +249,7 @@ "description": "The bundler to use.", "type": "string", "enum": ["vite", "webpack"], - "x-prompt": "Which bundler do you want to use?", + "x-prompt": "Which bundler do you want to use to build the application?", "default": "webpack" } }, @@ -339,8 +344,7 @@ "unitTestRunner": { "type": "string", "enum": ["jest", "vitest", "none"], - "description": "Test runner to use for unit tests.", - "default": "jest" + "description": "Test runner to use for unit tests." }, "inSourceTests": { "type": "boolean", @@ -384,7 +388,7 @@ "buildable": { "type": "boolean", "default": false, - "description": "Generate a buildable library." + "description": "Generate a buildable library. If a bundler is set then the library is buildable by default." }, "importPath": { "type": "string", @@ -419,11 +423,17 @@ "description": "Split the project configuration into `/project.json` rather than including it inside `workspace.json`.", "type": "boolean" }, + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["vite", "rollup"], + "x-prompt": "Which bundler would you like to use to build the library?" + }, "compiler": { "type": "string", "enum": ["babel", "swc"], "default": "babel", - "description": "Which compiler to use." + "description": "Which compiler to use. Does not apply if bundler is set to Vite." }, "skipPackageJson": { "description": "Do not add dependencies to `package.json`.", diff --git a/docs/generated/packages/vite.json b/docs/generated/packages/vite.json index 5d9932dd39..9fe8a35986 100644 --- a/docs/generated/packages/vite.json +++ b/docs/generated/packages/vite.json @@ -31,6 +31,11 @@ "enum": ["react", "none"], "default": "react", "x-prompt": "What UI framework plugin should Vite use?" + }, + "includeLib": { + "type": "boolean", + "description": "Add dependencies needed to build libraries.", + "default": false } }, "examplesFile": "This is a generator will initialize Vite.js in your workspace. It will install all the necessary dependencies. You can read more about how this generator works, in the [Vite package overview page](/packages/vite).\n\nYou can use it on its own like this:\n\n```bash\nnx g @nrwl/vite:configuration\n```\n\nHowever, this generator will be called when you are either converting an existing React or Web app to use Vite, using the [`@nrwl/vite:configuration` generator](/packages/vite/generators/configuration), or when you are creating a new React or Web app using the [`@nrwl/react:app`](/packages/react/generators/application) or [`@nrwl/web:app`](<(/packages/web/generators/application)>) generators, if you choose `vite` as the `bundler`.\n\n## Examples\n\n### Install all the necessary dependencies for Vite and the React plugin\n\n```bash\nnx g @nrwl/vite:init --uiFramework=react\n```\n\n### Install all the necessary dependencies for Vite\n\n```bash\nnx g @nrwl/vite:init --uiFramework=none\n```\n", @@ -60,6 +65,12 @@ "x-dropdown": "project", "x-prompt": "What is the name of the project to set up a webpack for?" }, + "includeLib": { + "type": "boolean", + "description": "Add a library build option.", + "default": false, + "x-prompt": "Does this project contain a buildable library?" + }, "uiFramework": { "type": "string", "description": "UI Framework to use for Vite.", diff --git a/docs/generated/packages/web.json b/docs/generated/packages/web.json index aba85caa4b..d614412071 100644 --- a/docs/generated/packages/web.json +++ b/docs/generated/packages/web.json @@ -53,6 +53,11 @@ "description": "Do not add dependencies to `package.json`.", "type": "boolean", "default": false + }, + "skipBabelConfig": { + "description": "Do not generate a root babel.config.json (if babel is not needed).", + "type": "boolean", + "default": false } }, "required": [], diff --git a/e2e/react/src/react-package.test.ts b/e2e/react/src/react-package.test.ts index 89e39cf70a..447744a060 100644 --- a/e2e/react/src/react-package.test.ts +++ b/e2e/react/src/react-package.test.ts @@ -97,7 +97,7 @@ describe('Build React libraries and apps', () => { afterEach(() => { killPorts(); - cleanupProject(); + // cleanupProject(); }); describe('Buildable libraries', () => { @@ -252,4 +252,21 @@ export async function h() { return 'c'; } }).toThrow(); }, 250000); }); + + it('should support bundling with Vite', async () => { + const libName = uniq('lib'); + + runCLI( + `generate @nrwl/react:lib ${libName} --bundler=vite --no-interactive` + ); + + await runCLIAsync(`build ${libName}`); + + checkFilesExist( + `dist/libs/${libName}/package.json`, + `dist/libs/${libName}/index.d.ts`, + `dist/libs/${libName}/index.js`, + `dist/libs/${libName}/index.mjs` + ); + }); }); diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts index dc5b3ffe7e..4e8c65110d 100644 --- a/e2e/vite/src/vite.test.ts +++ b/e2e/vite/src/vite.test.ts @@ -1,4 +1,5 @@ import { + checkFilesExist, cleanupProject, createFile, exists, @@ -32,38 +33,23 @@ describe('Vite Plugin', () => { `apps/${myApp}/index.html`, ` - + - + My App - + - - + + -
- +
+ ` ); - createFile( - `apps/${myApp}/src/environments/environment.prod.ts`, - `export const environment = { - production: true, - myTestVar: 'MyProductionValue', - };` - ); - createFile( - `apps/${myApp}/src/environments/environment.ts`, - `export const environment = { - production: false, - myTestVar: 'MyDevelopmentValue', - };` - ); - updateFile( `apps/${myApp}/src/app/app.tsx`, ` @@ -168,20 +154,6 @@ describe('Vite Plugin', () => { }); }); - it('should build application and replace files', async () => { - runCLI(`build ${myApp}`); - expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined(); - const fileArray = listFiles(`dist/apps/${myApp}/assets`); - const mainBundle = fileArray.find((file) => file.endsWith('.js')); - expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain( - 'MyProductionValue' - ); - expect( - readFile(`dist/apps/${myApp}/assets/${mainBundle}`) - ).not.toContain('MyDevelopmentValue'); - rmDist(); - }, 200000); - it('should serve application in dev mode', async () => { const port = 4212; const p = await runCommandUntil( @@ -206,76 +178,11 @@ describe('Vite Plugin', () => { }); }); - describe('set up new React app with --bundler=vite option', () => { - beforeEach(() => { - proj = newProject(); - runCLI(`generate @nrwl/react:app ${myApp} --bundler=vite`); - updateFile( - `apps/${myApp}/src/environments/environment.prod.ts`, - `export const environment = { - production: true, - myTestVar: 'MyProductionValue', - };` - ); - updateFile( - `apps/${myApp}/src/environments/environment.ts`, - `export const environment = { - production: false, - myTestVar: 'MyDevelopmentValue', - };` - ); - - updateFile( - `apps/${myApp}/src/app/app.tsx`, - ` - import { environment } from './../environments/environment'; - export function App() { - return ( - <> -

{environment.myTestVar}

-

Welcome ${myApp}!

- - ); - } - export default App; - ` - ); - }); - afterEach(() => cleanupProject()); - it('should build application and replace files', async () => { - runCLI(`build ${myApp}`); - expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined(); - const fileArray = listFiles(`dist/apps/${myApp}/assets`); - const mainBundle = fileArray.find((file) => file.endsWith('.js')); - expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain( - 'MyProductionValue' - ); - expect( - readFile(`dist/apps/${myApp}/assets/${mainBundle}`) - ).not.toContain('MyDevelopmentValue'); - rmDist(); - }, 200000); - }); - describe('convert React webpack app to vite using the vite:configuration generator', () => { beforeEach(() => { proj = newProject(); runCLI(`generate @nrwl/react:app ${myApp} --bundler=webpack`); runCLI(`generate @nrwl/vite:configuration ${myApp}`); - updateFile( - `apps/${myApp}/src/environments/environment.prod.ts`, - `export const environment = { - production: true, - myTestVar: 'MyProductionValue', - };` - ); - updateFile( - `apps/${myApp}/src/environments/environment.ts`, - `export const environment = { - production: false, - myTestVar: 'MyDevelopmentValue', - };` - ); updateFile( `apps/${myApp}/src/app/app.tsx`, @@ -294,18 +201,6 @@ describe('Vite Plugin', () => { ); }); afterEach(() => cleanupProject()); - it('should build application and replace files', async () => { - runCLI(`build ${myApp}`); - expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined(); - const fileArray = listFiles(`dist/apps/${myApp}/assets`); - const mainBundle = fileArray.find((file) => file.endsWith('.js')); - expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain( - 'MyProductionValue' - ); - expect( - readFile(`dist/apps/${myApp}/assets/${mainBundle}`) - ).not.toContain('MyDevelopmentValue'); - }, 200000); it('should serve application in dev mode', async () => { const port = 4212; @@ -348,7 +243,7 @@ describe('Vite Plugin', () => { readFile(`dist/apps/${myApp}/assets/${mainBundle}`) ).toBeDefined(); rmDist(); - }, 200000); + }, 200_000); }); describe('convert @nrwl/web webpack app to vite using the vite:configuration generator', () => { @@ -391,14 +286,16 @@ describe('Vite Plugin', () => { `Successfully ran target test for project ${myApp}` ); }); - }); + }), + 100_000; }); describe('should be able to create libs that use vitest', () => { const lib = uniq('my-lib'); beforeEach(() => { proj = newProject(); - }); + }), + 100_000; it('should be able to run tests', async () => { runCLI(`generate @nrwl/react:lib ${lib} --unitTestRunner=vitest`); @@ -408,7 +305,8 @@ describe('Vite Plugin', () => { expect(result.combinedOutput).toContain( `Successfully ran target test for project ${lib}` ); - }); + }), + 100_000; it('should be able to run tests with inSourceTests set to true', async () => { runCLI( @@ -432,6 +330,6 @@ describe('Vite Plugin', () => { const result = await runCLIAsync(`test ${lib}`); expect(result.combinedOutput).toContain(`1 passed`); - }); + }, 100_000); }); }); diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 8c612c41d1..f34524051f 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -19,7 +19,6 @@ describe('app', () => { compiler: 'babel', e2eTestRunner: 'cypress', skipFormat: false, - unitTestRunner: 'jest', name: 'myApp', linter: Linter.EsLint, style: 'css', @@ -381,6 +380,12 @@ describe('app', () => { expect(targetConfig.build.options).toEqual({ outputPath: 'dist/apps/my-app', }); + expect( + appTree.exists(`apps/my-app/environments/environment.ts`) + ).toBeFalsy(); + expect( + appTree.exists(`apps/my-app/environments/environment.prod.ts`) + ).toBeFalsy(); }); it('should setup the nrwl web dev server builder', async () => { diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index f50c528fb7..278383a317 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -80,6 +80,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) { const initTask = await reactInitGenerator(host, { ...options, skipFormat: true, + skipBabelConfig: options.bundler === 'vite', }); tasks.push(initTask); @@ -88,6 +89,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) { addProject(host, options); if (options.bundler === 'vite') { + // We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development. + // See: https://vitejs.dev/guide/env-and-mode.html + host.delete(joinPathFragments(options.appProjectRoot, 'src/environments')); + const viteTask = await viteConfigurationGenerator(host, { uiFramework: 'react', project: options.projectName, @@ -111,9 +116,11 @@ export async function applicationGenerator(host: Tree, schema: Schema) { const cypressTask = await addCypress(host, options); tasks.push(cypressTask); - const jestTask = await addJest(host, options); - tasks.push(jestTask); - updateSpecConfig(host, options); + if (options.unitTestRunner === 'jest') { + const jestTask = await addJest(host, options); + tasks.push(jestTask); + updateSpecConfig(host, options); + } const styledTask = addStyledModuleDependencies(host, options.styledModule); tasks.push(styledTask); const routingTask = addRouting(host, options); diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index 27e3a60ddd..cffc5efb7f 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -40,20 +40,7 @@ export function normalizeOptions( assertValidStyle(options.style); - if (options.bundler === 'vite') { - options.unitTestRunner = 'vitest'; - } - - options.routing = options.routing ?? false; - options.strict = options.strict ?? true; - options.classComponent = options.classComponent ?? false; - options.unitTestRunner = options.unitTestRunner ?? 'jest'; - options.e2eTestRunner = options.e2eTestRunner ?? 'cypress'; - options.compiler = options.compiler ?? 'babel'; - options.bundler = options.bundler ?? 'webpack'; - options.devServerPort ??= findFreePort(host); - - return { + const normalized = { ...options, name: names(options.name).fileName, projectName: appProjectName, @@ -63,5 +50,18 @@ export function normalizeOptions( fileName, styledModule, hasStyles: options.style !== 'none', - }; + } as NormalizedSchema; + + normalized.routing = normalized.routing ?? false; + normalized.strict = normalized.strict ?? true; + normalized.classComponent = normalized.classComponent ?? false; + normalized.compiler = normalized.compiler ?? 'babel'; + normalized.bundler = normalized.bundler ?? 'webpack'; + normalized.unitTestRunner = + normalized.unitTestRunner ?? + (normalized.bundler === 'vite' ? 'vitest' : 'jest'); + normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'cypress'; + normalized.devServerPort ??= findFreePort(host); + + return normalized; } diff --git a/packages/react/src/generators/application/lib/set-defaults.ts b/packages/react/src/generators/application/lib/set-defaults.ts index 435b9b1307..cf5bc880a4 100644 --- a/packages/react/src/generators/application/lib/set-defaults.ts +++ b/packages/react/src/generators/application/lib/set-defaults.ts @@ -28,8 +28,8 @@ export function setDefaults(host: Tree, options: NormalizedSchema) { ...prev, application: { style: options.style, - unitTestRunner: options.unitTestRunner, linter: options.linter, + bundler: options.bundler, ...prev.application, }, component: { @@ -38,7 +38,6 @@ export function setDefaults(host: Tree, options: NormalizedSchema) { }, library: { style: options.style, - unitTestRunner: options.unitTestRunner, linter: options.linter, ...prev.library, }, diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index 7d5f4e49c6..ead0831abf 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -7,7 +7,7 @@ export interface Schema { skipFormat: boolean; directory?: string; tags?: string; - unitTestRunner: 'jest' | 'vitest' | 'none'; + unitTestRunner?: 'jest' | 'vitest' | 'none'; inSourceTests?: boolean; /** * @deprecated @@ -41,4 +41,5 @@ export interface NormalizedSchema extends Schema { fileName: string; styledModule: null | SupportedStyles; hasStyles: boolean; + unitTestRunner: 'jest' | 'vitest' | 'none'; } diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json index dc0ff922fd..63b40df77f 100644 --- a/packages/react/src/generators/application/schema.json +++ b/packages/react/src/generators/application/schema.json @@ -185,7 +185,7 @@ "description": "The bundler to use.", "type": "string", "enum": ["vite", "webpack"], - "x-prompt": "Which bundler do you want to use?", + "x-prompt": "Which bundler do you want to use to build the application?", "default": "webpack" } }, diff --git a/packages/react/src/generators/init/schema.d.ts b/packages/react/src/generators/init/schema.d.ts index 7fccc4697c..65431d3fac 100644 --- a/packages/react/src/generators/init/schema.d.ts +++ b/packages/react/src/generators/init/schema.d.ts @@ -1,6 +1,7 @@ export interface InitSchema { unitTestRunner?: 'jest' | 'vitest' | 'none'; e2eTestRunner?: 'cypress' | 'none'; + skipBabelConfig?: boolean; skipFormat?: boolean; skipPackageJson?: boolean; js?: boolean; diff --git a/packages/react/src/generators/init/schema.json b/packages/react/src/generators/init/schema.json index 7fe840439b..458ee719ee 100644 --- a/packages/react/src/generators/init/schema.json +++ b/packages/react/src/generators/init/schema.json @@ -28,6 +28,11 @@ "type": "boolean", "default": false }, + "skipBabelConfig": { + "description": "Do not generate a root babel.config.json (if babel is not needed).", + "type": "boolean", + "default": false + }, "js": { "type": "boolean", "default": false, diff --git a/packages/react/src/generators/library/files/lib/.babelrc__tmpl__ b/packages/react/src/generators/library/files/common/.babelrc__tmpl__ similarity index 100% rename from packages/react/src/generators/library/files/lib/.babelrc__tmpl__ rename to packages/react/src/generators/library/files/common/.babelrc__tmpl__ diff --git a/packages/react/src/generators/library/files/lib/README.md b/packages/react/src/generators/library/files/common/README.md similarity index 100% rename from packages/react/src/generators/library/files/lib/README.md rename to packages/react/src/generators/library/files/common/README.md diff --git a/packages/react/src/generators/library/files/common/package.json__tmpl__ b/packages/react/src/generators/library/files/common/package.json__tmpl__ new file mode 100644 index 0000000000..12d37480df --- /dev/null +++ b/packages/react/src/generators/library/files/common/package.json__tmpl__ @@ -0,0 +1,12 @@ +{ + "name": "<%= name %>", + "version": "0.0.1", + "main": "./index.js", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + } + } +} diff --git a/packages/react/src/generators/library/files/lib/src/index.ts__tmpl__ b/packages/react/src/generators/library/files/common/src/index.ts__tmpl__ similarity index 100% rename from packages/react/src/generators/library/files/lib/src/index.ts__tmpl__ rename to packages/react/src/generators/library/files/common/src/index.ts__tmpl__ diff --git a/packages/react/src/generators/library/files/lib/tsconfig.json__tmpl__ b/packages/react/src/generators/library/files/common/tsconfig.json__tmpl__ similarity index 100% rename from packages/react/src/generators/library/files/lib/tsconfig.json__tmpl__ rename to packages/react/src/generators/library/files/common/tsconfig.json__tmpl__ diff --git a/packages/react/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ b/packages/react/src/generators/library/files/common/tsconfig.lib.json__tmpl__ similarity index 100% rename from packages/react/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ rename to packages/react/src/generators/library/files/common/tsconfig.lib.json__tmpl__ diff --git a/packages/react/src/generators/library/files/lib/package.json__tmpl__ b/packages/react/src/generators/library/files/lib/package.json__tmpl__ deleted file mode 100644 index fa518765a3..0000000000 --- a/packages/react/src/generators/library/files/lib/package.json__tmpl__ +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "<%= name %>", - "version": "0.0.1" -} diff --git a/packages/react/src/generators/library/files/vite/index.html__tmpl__ b/packages/react/src/generators/library/files/vite/index.html__tmpl__ new file mode 100644 index 0000000000..4c4c81ceb5 --- /dev/null +++ b/packages/react/src/generators/library/files/vite/index.html__tmpl__ @@ -0,0 +1,15 @@ + + + + + <%= className %> Demo + + + + + + +
+ + + diff --git a/packages/react/src/generators/library/files/vite/package.json__tmpl__ b/packages/react/src/generators/library/files/vite/package.json__tmpl__ new file mode 100644 index 0000000000..507420ee30 --- /dev/null +++ b/packages/react/src/generators/library/files/vite/package.json__tmpl__ @@ -0,0 +1,12 @@ +{ + "name": "<%= name %>", + "version": "0.0.1", + "main": "./index.js", + "types": "./index.d.ts", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + } + } +} diff --git a/packages/react/src/generators/library/files/vite/src/demo.tsx__tmpl__ b/packages/react/src/generators/library/files/vite/src/demo.tsx__tmpl__ new file mode 100644 index 0000000000..617f6a880e --- /dev/null +++ b/packages/react/src/generators/library/files/vite/src/demo.tsx__tmpl__ @@ -0,0 +1,19 @@ +/* + * This a a demo file that can be helpful when developing components by serving and interacting with them in the browser. + */ +<% if (component) { %> +import * as ReactDOM from 'react-dom/client'; +import { <%= className %> } from './index'; + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + <<%= className %> /> +); +<% } else { %> +import * as ReactDOM from 'react-dom/client'; + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( +

<%= className %> Demo

+); +<% } %> \ No newline at end of file diff --git a/packages/react/src/generators/library/lib/normalize-options.spec.ts b/packages/react/src/generators/library/lib/normalize-options.spec.ts new file mode 100644 index 0000000000..be9b4940b9 --- /dev/null +++ b/packages/react/src/generators/library/lib/normalize-options.spec.ts @@ -0,0 +1,60 @@ +import type { Tree } from '@nrwl/devkit'; +import { Linter } from '@nrwl/linter'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { normalizeOptions } from './normalize-options'; + +describe('normalizeOptions', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should set unitTestRunner=jest and bundler=rollup by default', async () => { + const options = normalizeOptions(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + }); + + expect(options).toMatchObject({ + buildable: false, + bundler: 'rollup', + compiler: 'babel', + unitTestRunner: 'jest', + }); + }); + + it('should set unitTestRunner=vitest by default when bundler is vite', async () => { + const options = normalizeOptions(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + bundler: 'vite', + }); + + expect(options).toMatchObject({ + buildable: true, + bundler: 'vite', + compiler: 'babel', + unitTestRunner: 'vitest', + }); + }); + + it('should set maintain unitTestRunner when bundler is vite', async () => { + const options = normalizeOptions(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + bundler: 'vite', + unitTestRunner: 'jest', + }); + + expect(options).toMatchObject({ + buildable: true, + bundler: 'vite', + compiler: 'babel', + unitTestRunner: 'jest', + }); + }); +}); diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts new file mode 100644 index 0000000000..8411af2ac1 --- /dev/null +++ b/packages/react/src/generators/library/lib/normalize-options.ts @@ -0,0 +1,79 @@ +import { + getImportPath, + getProjects, + getWorkspaceLayout, + joinPathFragments, + names, + normalizePath, + Tree, +} from '@nrwl/devkit'; +import { assertValidStyle } from '../../../utils/assertion'; +import { NormalizedSchema } from '../library'; +import { Schema } from '../schema'; + +export function normalizeOptions( + host: Tree, + options: Schema +): NormalizedSchema { + const name = names(options.name).fileName; + const projectDirectory = options.directory + ? `${names(options.directory).fileName}/${name}` + : name; + + const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + const fileName = projectName; + const { libsDir, npmScope } = getWorkspaceLayout(host); + const projectRoot = joinPathFragments(libsDir, projectDirectory); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const importPath = + options.importPath || getImportPath(npmScope, projectDirectory); + + const normalized = { + ...options, + compiler: options.compiler ?? 'babel', + bundler: options.bundler ?? 'rollup', + fileName, + routePath: `/${name}`, + name: projectName, + projectRoot, + projectDirectory, + parsedTags, + importPath, + } as NormalizedSchema; + + // Libraries with a bundler or is publishable must also be buildable. + normalized.buildable = Boolean( + options.bundler || options.buildable || options.publishable + ); + + normalized.unitTestRunner = + normalized.unitTestRunner ?? + (normalized.bundler === 'vite' ? 'vitest' : 'jest'); + + if (options.appProject) { + const appProjectConfig = getProjects(host).get(options.appProject); + + if (appProjectConfig.projectType !== 'application') { + throw new Error( + `appProject expected type of "application" but got "${appProjectConfig.projectType}"` + ); + } + + try { + normalized.appMain = appProjectConfig.targets.build.options.main; + normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); + } catch (e) { + throw new Error( + `Could not locate project main for ${options.appProject}` + ); + } + } + + assertValidStyle(normalized.style); + + return normalized; +} diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index ed3952176b..1fe21bf25a 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -225,7 +225,11 @@ describe('lib', () => { `); }); it('should update jest.config.ts for babel', async () => { - await libraryGenerator(appTree, { ...defaultSchema, compiler: 'babel' }); + await libraryGenerator(appTree, { + ...defaultSchema, + buildable: true, + compiler: 'babel', + }); expect(appTree.read('libs/my-lib/jest.config.ts', 'utf-8')).toContain( "['babel-jest', { presets: ['@nrwl/react/babel'] }]" ); @@ -280,6 +284,7 @@ describe('lib', () => { await libraryGenerator(appTree, { ...defaultSchema, directory: 'myDir', + buildable: true, compiler: 'babel', }); expect( @@ -725,6 +730,7 @@ describe('lib', () => { it('should install swc dependencies if needed', async () => { await libraryGenerator(appTree, { ...defaultSchema, + buildable: true, compiler: 'swc', }); const packageJson = readJson(appTree, 'package.json'); @@ -764,6 +770,7 @@ describe('lib', () => { await libraryGenerator(appTree, { ...defaultSchema, style, + compiler: 'babel', name: 'myLib', }); diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index 25d4c356b7..6a1ba1d390 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -44,10 +44,12 @@ import { typesReactRouterDomVersion, } from '../../utils/versions'; import componentGenerator from '../component/component'; -import init from '../init/init'; +import initGenerator from '../init/init'; import { Schema } from './schema'; import { updateJestConfigContent } from '../../utils/jest-utils'; -import { vitestGenerator } from '@nrwl/vite'; +import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite'; +import { normalizeOptions } from './lib/normalize-options'; + export interface NormalizedSchema extends Schema { name: string; fileName: string; @@ -57,6 +59,7 @@ export interface NormalizedSchema extends Schema { parsedTags: string[]; appMain?: string; appSourceRoot?: string; + unitTestRunner: 'jest' | 'vitest' | 'none'; } export async function libraryGenerator(host: Tree, schema: Schema) { @@ -72,10 +75,11 @@ export async function libraryGenerator(host: Tree, schema: Schema) { options.style = 'none'; } - const initTask = await init(host, { + const initTask = await initGenerator(host, { ...options, e2eTestRunner: 'none', skipFormat: true, + skipBabelConfig: options.bundler === 'vite', }); tasks.push(initTask); @@ -90,6 +94,18 @@ export async function libraryGenerator(host: Tree, schema: Schema) { updateBaseTsConfig(host, options); } + if (options.buildable && options.bundler === 'vite') { + const viteTask = await viteConfigurationGenerator(host, { + uiFramework: 'react', + project: options.name, + newProject: true, + includeLib: true, + inSourceTests: options.inSourceTests, + includeVitest: true, + }); + tasks.push(viteTask); + } + if (options.unitTestRunner === 'jest') { const jestTask = await jestProjectGenerator(host, { ...options, @@ -110,7 +126,10 @@ export async function libraryGenerator(host: Tree, schema: Schema) { ); host.write(jestConfigPath, updatedContent); } - } else if (options.unitTestRunner === 'vitest') { + } else if ( + options.unitTestRunner === 'vitest' && + options.bundler !== 'vite' // tests are already configured if bundler is vite + ) { const vitestTask = await vitestGenerator(host, { uiFramework: 'react', project: options.name, @@ -299,22 +318,34 @@ function updateBaseTsConfig(host: Tree, options: NormalizedSchema) { } function createFiles(host: Tree, options: NormalizedSchema) { + const substitutions = { + ...options, + ...names(options.name), + tmpl: '', + offsetFromRoot: offsetFromRoot(options.projectRoot), + rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot), + }; + generateFiles( host, - joinPathFragments(__dirname, './files/lib'), + joinPathFragments(__dirname, './files/common'), options.projectRoot, - { - ...options, - ...names(options.name), - tmpl: '', - offsetFromRoot: offsetFromRoot(options.projectRoot), - rootTsConfigPath: getRelativePathToRootTsConfig( - host, - options.projectRoot - ), - } + substitutions ); + if (options.bundler === 'vite') { + generateFiles( + host, + joinPathFragments(__dirname, './files/vite'), + options.projectRoot, + substitutions + ); + + if (host.exists(joinPathFragments(options.projectRoot, '.babelrc'))) { + host.delete(joinPathFragments(options.projectRoot, '.babelrc')); + } + } + if (!options.publishable && !options.buildable) { host.delete(`${options.projectRoot}/package.json`); } @@ -415,59 +446,6 @@ function readComponent( return { content, source }; } -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const name = names(options.name).fileName; - const projectDirectory = options.directory - ? `${names(options.directory).fileName}/${name}` - : name; - - const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); - const fileName = projectName; - const { libsDir, npmScope } = getWorkspaceLayout(host); - const projectRoot = joinPathFragments(libsDir, projectDirectory); - - const parsedTags = options.tags - ? options.tags.split(',').map((s) => s.trim()) - : []; - - const importPath = - options.importPath || getImportPath(npmScope, projectDirectory); - - const normalized: NormalizedSchema = { - ...options, - fileName, - routePath: `/${name}`, - name: projectName, - projectRoot, - projectDirectory, - parsedTags, - importPath, - }; - - if (options.appProject) { - const appProjectConfig = getProjects(host).get(options.appProject); - - if (appProjectConfig.projectType !== 'application') { - throw new Error( - `appProject expected type of "application" but got "${appProjectConfig.projectType}"` - ); - } - - try { - normalized.appMain = appProjectConfig.targets.build.options.main; - normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); - } catch (e) { - throw new Error( - `Could not locate project main for ${options.appProject}` - ); - } - } - - assertValidStyle(normalized.style); - - return normalized; -} - function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) { return updateJson(host, `${options.projectRoot}/package.json`, (json) => { json.name = options.importPath; diff --git a/packages/react/src/generators/library/schema.d.ts b/packages/react/src/generators/library/schema.d.ts index 9adde64ac3..2488380017 100644 --- a/packages/react/src/generators/library/schema.d.ts +++ b/packages/react/src/generators/library/schema.d.ts @@ -2,27 +2,28 @@ import { Linter } from '@nrwl/linter'; import { SupportedStyles } from '../../../typings/style'; export interface Schema { - name: string; - directory?: string; - style: SupportedStyles; - skipTsConfig: boolean; - skipFormat: boolean; - tags?: string; - pascalCaseFiles?: boolean; - routing?: boolean; appProject?: string; - unitTestRunner: 'jest' | 'vitest' | 'none'; - inSourceTests?: boolean; - linter: Linter; - component?: boolean; - publishable?: boolean; buildable?: boolean; - importPath?: string; - js?: boolean; - globalCss?: boolean; - strict?: boolean; - setParserOptionsProject?: boolean; - standaloneConfig?: boolean; + bundler?: 'rollup' | 'vite'; compiler?: 'babel' | 'swc'; + component?: boolean; + directory?: string; + globalCss?: boolean; + importPath?: string; + inSourceTests?: boolean; + js?: boolean; + linter: Linter; + name: string; + pascalCaseFiles?: boolean; + publishable?: boolean; + routing?: boolean; + setParserOptionsProject?: boolean; + skipFormat?: boolean; skipPackageJson?: boolean; + skipTsConfig?: boolean; + standaloneConfig?: boolean; + strict?: boolean; + style: SupportedStyles; + tags?: string; + unitTestRunner?: 'jest' | 'vitest' | 'none'; } diff --git a/packages/react/src/generators/library/schema.json b/packages/react/src/generators/library/schema.json index ff4ade4465..7827390213 100644 --- a/packages/react/src/generators/library/schema.json +++ b/packages/react/src/generators/library/schema.json @@ -81,8 +81,7 @@ "unitTestRunner": { "type": "string", "enum": ["jest", "vitest", "none"], - "description": "Test runner to use for unit tests.", - "default": "jest" + "description": "Test runner to use for unit tests." }, "inSourceTests": { "type": "boolean", @@ -126,7 +125,7 @@ "buildable": { "type": "boolean", "default": false, - "description": "Generate a buildable library." + "description": "Generate a buildable library. If a bundler is set then the library is buildable by default." }, "importPath": { "type": "string", @@ -161,11 +160,17 @@ "description": "Split the project configuration into `/project.json` rather than including it inside `workspace.json`.", "type": "boolean" }, + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["vite", "rollup"], + "x-prompt": "Which bundler would you like to use to build the library?" + }, "compiler": { "type": "string", "enum": ["babel", "swc"], "default": "babel", - "description": "Which compiler to use." + "description": "Which compiler to use. Does not apply if bundler is set to Vite." }, "skipPackageJson": { "description": "Do not add dependencies to `package.json`.", diff --git a/packages/vite/src/executors/build/build.impl.ts b/packages/vite/src/executors/build/build.impl.ts index 8cfd52e352..4c7d49642b 100644 --- a/packages/vite/src/executors/build/build.impl.ts +++ b/packages/vite/src/executors/build/build.impl.ts @@ -4,25 +4,42 @@ import 'dotenv/config'; import { getBuildConfig } from '../../utils/options-utils'; import { ViteBuildExecutorOptions } from './schema'; import { copyAssets } from '@nrwl/js'; +import { existsSync } from 'fs'; +import { join } from 'path'; export default async function viteBuildExecutor( options: ViteBuildExecutorOptions, context: ExecutorContext ) { - if (options.assets) { + const projectRoot = context.workspace.projects[context.projectName].root; + let assets = options.assets; + + // Copy package.json as an asset if it exists + if (existsSync(join(projectRoot, 'package.json'))) { + assets ??= []; + assets.push({ + input: '.', + output: '.', + glob: 'package.json', + }); + } + + logger.info(`NX Vite build starting ...`); + const buildResult = await runInstance(await getBuildConfig(options, context)); + logger.info(`NX Vite build finished ...`); + logger.info(`NX Vite files available in ${options.outputPath}`); + + // TODO(jack): handle watch once we add that option + if (assets) { await copyAssets( { outputPath: options.outputPath, - assets: options.assets, + assets: assets, }, context ); } - logger.info(`NX Vite build starting ...`); - await runInstance(await getBuildConfig(options, context)); - logger.info(`NX Vite build finished ...`); - logger.info(`NX Vite files available in ${options.outputPath}`); return { success: true }; } diff --git a/packages/vite/src/generators/configuration/configuration.spec.ts b/packages/vite/src/generators/configuration/configuration.spec.ts index c95a8b30d4..fce8b97188 100644 --- a/packages/vite/src/generators/configuration/configuration.spec.ts +++ b/packages/vite/src/generators/configuration/configuration.spec.ts @@ -1,4 +1,9 @@ -import { addDependenciesToPackageJson, readJson, Tree } from '@nrwl/devkit'; +import { + addDependenciesToPackageJson, + addProjectConfiguration, + readJson, + Tree, +} from '@nrwl/devkit'; import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; import { nxVersion } from '../../utils/versions'; @@ -116,4 +121,27 @@ describe('@nrwl/vite:configuration', () => { expect(viteConfig).toContain('test'); }); }); + + describe('library mode', () => { + beforeEach(async () => { + tree = createTreeWithEmptyV1Workspace(); + addProjectConfiguration(tree, 'my-lib', { + root: 'my-lib', + }); + }); + + it('should add config for building library', async () => { + await viteConfigurationGenerator(tree, { + uiFramework: 'react', + includeLib: true, + project: 'my-lib', + newProject: true, + }); + + const viteConfig = tree.read('my-lib/vite.config.ts').toString(); + + expect(viteConfig).toMatch('build: {'); + expect(viteConfig).toMatch("external: ['react'"); + }); + }); }); diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts index 97d0fe7354..17c12ac2a7 100644 --- a/packages/vite/src/generators/configuration/configuration.ts +++ b/packages/vite/src/generators/configuration/configuration.ts @@ -35,6 +35,7 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { const initTask = await initGenerator(tree, { uiFramework: schema.uiFramework, + includeLib: schema.includeLib, }); tasks.push(initTask); diff --git a/packages/vite/src/generators/configuration/schema.d.ts b/packages/vite/src/generators/configuration/schema.d.ts index 40b05c200d..9533d025cf 100644 --- a/packages/vite/src/generators/configuration/schema.d.ts +++ b/packages/vite/src/generators/configuration/schema.d.ts @@ -4,4 +4,5 @@ export interface Schema { newProject?: boolean; includeVitest?: boolean; inSourceTests?: boolean; + includeLib?: boolean; } diff --git a/packages/vite/src/generators/configuration/schema.json b/packages/vite/src/generators/configuration/schema.json index 1aab0f1df1..c48db6c5e7 100644 --- a/packages/vite/src/generators/configuration/schema.json +++ b/packages/vite/src/generators/configuration/schema.json @@ -16,6 +16,12 @@ "x-dropdown": "project", "x-prompt": "What is the name of the project to set up a webpack for?" }, + "includeLib": { + "type": "boolean", + "description": "Add a library build option.", + "default": false, + "x-prompt": "Does this project contain a buildable library?" + }, "uiFramework": { "type": "string", "description": "UI Framework to use for Vite.", diff --git a/packages/vite/src/generators/init/init.ts b/packages/vite/src/generators/init/init.ts index 3221ca0879..e2ba63f111 100644 --- a/packages/vite/src/generators/init/init.ts +++ b/packages/vite/src/generators/init/init.ts @@ -5,17 +5,17 @@ import { Tree, updateJson, } from '@nrwl/devkit'; -import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { + jsdomVersion, nxVersion, + vitePluginDtsVersion, vitePluginEslintVersion, vitePluginReactVersion, - viteVersion, vitestUiVersion, vitestVersion, viteTsConfigPathsVersion, - jsdomVersion, + viteVersion, } from '../../utils/versions'; import { Schema } from './schema'; @@ -39,6 +39,10 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) { devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion; } + if (schema.includeLib) { + devDependencies['vite-plugin-dts'] = vitePluginDtsVersion; + } + return addDependenciesToPackageJson(host, dependencies, devDependencies); } diff --git a/packages/vite/src/generators/init/schema.d.ts b/packages/vite/src/generators/init/schema.d.ts index a36fd7d79f..260e0d5cda 100644 --- a/packages/vite/src/generators/init/schema.d.ts +++ b/packages/vite/src/generators/init/schema.d.ts @@ -1,3 +1,4 @@ export interface Schema { uiFramework: 'react' | 'none'; + includeLib?: boolean; } diff --git a/packages/vite/src/generators/init/schema.json b/packages/vite/src/generators/init/schema.json index ed87671fc0..d4e8306d3d 100644 --- a/packages/vite/src/generators/init/schema.json +++ b/packages/vite/src/generators/init/schema.json @@ -11,6 +11,11 @@ "enum": ["react", "none"], "default": "react", "x-prompt": "What UI framework plugin should Vite use?" + }, + "includeLib": { + "type": "boolean", + "description": "Add dependencies needed to build libraries.", + "default": false } }, "examplesFile": "../../../docs/init-examples.md" diff --git a/packages/vite/src/generators/vitest/vitest.spec.ts b/packages/vite/src/generators/vitest/vitest.spec.ts index 20d2675b7f..d093a723a3 100644 --- a/packages/vite/src/generators/vitest/vitest.spec.ts +++ b/packages/vite/src/generators/vitest/vitest.spec.ts @@ -107,8 +107,10 @@ describe('vitest generator', () => { import react from '@vitejs/plugin-react'; import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths'; + export default defineConfig({ plugins: [ + react(), ViteTsConfigPathsPlugin({ root: '../../', @@ -116,6 +118,7 @@ describe('vitest generator', () => { }), ], + test: { globals: true, environment: 'jsdom', @@ -140,14 +143,17 @@ describe('vitest generator', () => { import react from '@vitejs/plugin-react'; import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths'; + export default defineConfig({ plugins: [ + react(), ViteTsConfigPathsPlugin({ root: '../../', projects: ['tsconfig.base.json'], }), ], + define: { 'import.meta.vitest': undefined }, diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 8f935d1796..9a7b7d1a37 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -150,21 +150,23 @@ export function addOrChangeBuildTarget( options: buildOptions, configurations: { development: {}, - production: { - fileReplacements: [ - { - replace: joinPathFragments( - project.sourceRoot, - 'environments/environment.ts' - ), - with: joinPathFragments( - project.sourceRoot, - 'environments/environment.prod.ts' - ), + production: options.includeLib + ? {} + : { + fileReplacements: [ + { + replace: joinPathFragments( + project.sourceRoot, + 'environments/environment.ts' + ), + with: joinPathFragments( + project.sourceRoot, + 'environments/environment.prod.ts' + ), + }, + ], + sourceMap: false, }, - ], - sourceMap: false, - }, }, }; } @@ -356,7 +358,8 @@ export function writeViteConfig(tree: Tree, options: Schema) { let viteConfigContent = ''; - const testOption = `test: { + const testOption = options.includeVitest + ? `test: { globals: true, environment: 'jsdom', include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], @@ -365,11 +368,45 @@ export function writeViteConfig(tree: Tree, options: Schema) { ? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']` : '' } - },`; + },` + : ''; - const defineOption = `define: { + const defineOption = options.inSourceTests + ? `define: { 'import.meta.vitest': undefined - },`; + },` + : ''; + + const dtsPlugin = `dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }),`; + + const buildOption = options.includeLib + ? ` + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: '${options.project}', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [${ + options.uiFramework === 'react' + ? "'react', 'react-dom', 'react/jsx-runtime'" + : '' + }] + } + },` + : ''; switch (options.uiFramework) { case 'react': @@ -378,17 +415,20 @@ ${options.includeVitest ? '/// ' : ''} import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths'; + ${options.includeLib ? "import dts from 'vite-plugin-dts';" : ''} export default defineConfig({ plugins: [ + ${options.includeLib ? dtsPlugin : ''} react(), ViteTsConfigPathsPlugin({ root: '${offsetFromRoot(projectConfig.root)}', projects: ['tsconfig.base.json'], }), ], - ${options.inSourceTests ? defineOption : ''} - ${options.includeVitest ? testOption : ''} + ${buildOption} + ${defineOption} + ${testOption} });`; break; case 'none': @@ -396,16 +436,19 @@ ${options.includeVitest ? '/// ' : ''} ${options.includeVitest ? '/// ' : ''} import { defineConfig } from 'vite'; import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths'; + ${options.includeLib ? "import dts from 'vite-plugin-dts';" : ''} export default defineConfig({ plugins: [ + ${options.includeLib ? dtsPlugin : ''} ViteTsConfigPathsPlugin({ root: '${offsetFromRoot(projectConfig.root)}', projects: ['tsconfig.base.json'], }), ], - ${options.inSourceTests ? defineOption : ''} - ${options.includeVitest ? testOption : ''} + ${buildOption} + ${defineOption} + ${testOption} });`; break; default: diff --git a/packages/vite/src/utils/versions.ts b/packages/vite/src/utils/versions.ts index f16ddd298e..8c357489d2 100644 --- a/packages/vite/src/utils/versions.ts +++ b/packages/vite/src/utils/versions.ts @@ -8,3 +8,4 @@ export const vitePluginVueVersion = '^3.2.0'; export const vitePluginVueJsxVersion = '^2.1.1'; export const viteTsConfigPathsVersion = '^3.5.2'; export const jsdomVersion = '~20.0.3'; +export const vitePluginDtsVersion = '~1.7.1'; diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 097c9ad018..9a6c258f1f 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -168,6 +168,12 @@ describe('app', () => { expect(tree.exists('apps/my-app-e2e/cypress.config.ts')).toBeTruthy(); expect(tree.exists('apps/my-app/index.html')).toBeTruthy(); expect(tree.exists('apps/my-app/vite.config.ts')).toBeTruthy(); + expect( + tree.exists(`apps/my-app/environments/environment.ts`) + ).toBeFalsy(); + expect( + tree.exists(`apps/my-app/environments/environment.prod.ts`) + ).toBeFalsy(); }); it('should extend from root tsconfig.json when no tsconfig.base.json', async () => { diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 3e658531b5..bd7cced40f 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -192,6 +192,8 @@ export async function applicationGenerator(host: Tree, schema: Schema) { const webTask = await webInitGenerator(host, { ...options, skipFormat: true, + // Vite does not use babel by default + skipBabelConfig: options.bundler === 'vite', }); tasks.push(webTask); @@ -199,6 +201,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) { await addProject(host, options); if (options.bundler === 'vite') { + // We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development. + // See: https://vitejs.dev/guide/env-and-mode.html + host.delete(joinPathFragments(options.appProjectRoot, 'src/environments')); + const viteTask = await viteConfigurationGenerator(host, { uiFramework: 'react', project: options.projectName, diff --git a/packages/web/src/generators/init/init.ts b/packages/web/src/generators/init/init.ts index 3df5faf8b4..dfa1ba2149 100644 --- a/packages/web/src/generators/init/init.ts +++ b/packages/web/src/generators/init/init.ts @@ -42,14 +42,16 @@ function updateDependencies(tree: Tree, schema: Schema) { ); } -function initRootBabelConfig(tree: Tree) { +function initRootBabelConfig(tree: Tree, schema: Schema) { if (tree.exists('/babel.config.json') || tree.exists('/babel.config.js')) { return; } - writeJson(tree, '/babel.config.json', { - babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo - }); + if (!schema.skipBabelConfig) { + writeJson(tree, '/babel.config.json', { + babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo + }); + } const workspaceConfiguration = readWorkspaceConfiguration(tree); @@ -80,7 +82,7 @@ export async function webInitGenerator(tree: Tree, schema: Schema) { const installTask = updateDependencies(tree, schema); tasks.push(installTask); } - initRootBabelConfig(tree); + initRootBabelConfig(tree, schema); if (!schema.skipFormat) { await formatFiles(tree); } diff --git a/packages/web/src/generators/init/schema.d.ts b/packages/web/src/generators/init/schema.d.ts index 00c2cc6a74..7276adf217 100644 --- a/packages/web/src/generators/init/schema.d.ts +++ b/packages/web/src/generators/init/schema.d.ts @@ -4,4 +4,5 @@ export interface Schema { e2eTestRunner?: 'cypress' | 'none'; skipFormat?: boolean; skipPackageJson?: boolean; + skipBabelConfig?: boolean; } diff --git a/packages/web/src/generators/init/schema.json b/packages/web/src/generators/init/schema.json index 93f02a32fe..8e7a8daab5 100644 --- a/packages/web/src/generators/init/schema.json +++ b/packages/web/src/generators/init/schema.json @@ -33,6 +33,11 @@ "description": "Do not add dependencies to `package.json`.", "type": "boolean", "default": false + }, + "skipBabelConfig": { + "description": "Do not generate a root babel.config.json (if babel is not needed).", + "type": "boolean", + "default": false } }, "required": []