From 0d868d28b3bdf7563cfe6fafdb3d6d260fbb236b Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Tue, 10 Dec 2024 16:20:01 -0500 Subject: [PATCH] feat(vue): update vue app and lib generators to support TS solutions --- .../packages/nuxt/generators/application.json | 17 +- .../packages/vue/generators/application.json | 17 +- .../packages/vue/generators/library.json | 8 +- .../bin/create-nx-workspace.ts | 33 ++- .../application/application.spec.ts | 181 ++++++++++++++- .../src/generators/application/application.ts | 65 ++++-- .../application/lib/normalize-options.ts | 2 + .../src/generators/application/schema.d.ts | 2 + .../src/generators/application/schema.json | 17 +- packages/nuxt/src/generators/init/init.ts | 3 - .../generators/configuration/configuration.ts | 7 +- .../application/application.spec.ts | 2 + .../application/application.impl.spec.ts | 2 + .../__snapshots__/application.spec.ts.snap | 1 + .../application/application.spec.ts | 185 ++++++++++++++++ .../src/generators/application/application.ts | 74 +++++-- .../files/common/src/vue-shims.d.ts.template | 5 + .../application/lib/normalize-options.ts | 2 + .../src/generators/application/schema.d.ts | 3 + .../src/generators/application/schema.json | 17 +- packages/vue/src/generators/init/init.ts | 3 - .../library/files/package.json__tmpl__ | 12 - .../library/lib/create-library-files.ts | 18 +- .../library/lib/normalize-options.ts | 2 + .../src/generators/library/library.spec.ts | 206 ++++++++++++++++++ .../vue/src/generators/library/library.ts | 86 ++++++-- .../vue/src/generators/library/schema.d.ts | 1 + .../vue/src/generators/library/schema.json | 8 +- packages/vue/src/utils/create-ts-config.ts | 85 ++++++++ .../new/generate-workspace-files.ts | 4 +- .../workspace/src/generators/preset/preset.ts | 2 + 31 files changed, 962 insertions(+), 108 deletions(-) create mode 100644 packages/vue/src/generators/application/files/common/src/vue-shims.d.ts.template delete mode 100644 packages/vue/src/generators/library/files/package.json__tmpl__ diff --git a/docs/generated/packages/nuxt/generators/application.json b/docs/generated/packages/nuxt/generators/application.json index 16e17b76a4f830..4877abbc541066 100644 --- a/docs/generated/packages/nuxt/generators/application.json +++ b/docs/generated/packages/nuxt/generators/application.json @@ -22,24 +22,27 @@ "pattern": "^[a-zA-Z][^:]*$", "x-priority": "important" }, - "linter": { - "description": "The tool to use for running lint checks.", - "type": "string", - "enum": ["eslint"], - "default": "eslint" - }, "skipFormat": { "description": "Skip formatting files.", "type": "boolean", "default": false, "x-priority": "internal" }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" + }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", "x-prompt": "Which unit test runner would you like to use?", - "default": "none" + "default": "none", + "x-priority": "important" }, "e2eTestRunner": { "type": "string", diff --git a/docs/generated/packages/vue/generators/application.json b/docs/generated/packages/vue/generators/application.json index 70212ea56f8e9c..d545250bec98a1 100644 --- a/docs/generated/packages/vue/generators/application.json +++ b/docs/generated/packages/vue/generators/application.json @@ -54,12 +54,6 @@ ] } }, - "linter": { - "description": "The tool to use for running lint checks.", - "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" - }, "routing": { "type": "boolean", "description": "Generate application with routes.", @@ -72,12 +66,21 @@ "default": false, "x-priority": "internal" }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" + }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", "x-prompt": "Which unit test runner would you like to use?", - "default": "vitest" + "default": "none", + "x-priority": "important" }, "inSourceTests": { "type": "boolean", diff --git a/docs/generated/packages/vue/generators/library.json b/docs/generated/packages/vue/generators/library.json index 664b2b97a13076..418e359c36a427 100644 --- a/docs/generated/packages/vue/generators/library.json +++ b/docs/generated/packages/vue/generators/library.json @@ -36,13 +36,17 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", - "x-prompt": "What unit test runner should be used?" + "x-prompt": "What unit test runner should be used?", + "default": "none", + "x-priority": "important" }, "inSourceTests": { "type": "boolean", diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 1fe2eace4247ea..86fb208d971eb8 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -32,6 +32,9 @@ import { printSocialInformation } from '../src/utils/social-information'; interface BaseArguments extends CreateWorkspaceOptions { preset: Preset; + linter?: 'none' | 'eslint'; + formatter?: 'none' | 'prettier'; + workspaces?: boolean; } interface NoneArguments extends BaseArguments { @@ -39,7 +42,6 @@ interface NoneArguments extends BaseArguments { workspaceType?: 'package-based' | 'integrated' | 'standalone'; js?: boolean; appName?: string | undefined; - formatter?: 'none' | 'prettier'; } interface ReactArguments extends BaseArguments { @@ -52,9 +54,6 @@ interface ReactArguments extends BaseArguments { nextAppDir: boolean; nextSrcDir: boolean; e2eTestRunner: 'none' | 'cypress' | 'playwright'; - linter?: 'none' | 'eslint'; - formatter?: 'none' | 'prettier'; - workspaces?: boolean; } interface AngularArguments extends BaseArguments { @@ -730,6 +729,10 @@ async function determineVueOptions( let style: undefined | string = undefined; let appName: string; let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined; + let linter: undefined | 'none' | 'eslint'; + let formatter: undefined | 'none' | 'prettier'; + + const workspaces = parsedArgs.workspaces ?? false; if (parsedArgs.preset && parsedArgs.preset !== Preset.Vue) { preset = parsedArgs.preset; @@ -741,7 +744,9 @@ async function determineVueOptions( } else { const framework = await determineVueFramework(parsedArgs); - const workspaceType = await determineStandaloneOrMonorepo(); + const workspaceType = workspaces + ? 'monorepo' + : await determineStandaloneOrMonorepo(); if (workspaceType === 'standalone') { appName = parsedArgs.appName ?? parsedArgs.name; } else { @@ -798,7 +803,23 @@ async function determineVueOptions( style = reply.style; } - return { preset, style, appName, e2eTestRunner }; + if (workspaces) { + linter = await determineLinterOptions(parsedArgs); + formatter = await determineFormatterOptions(parsedArgs); + } else { + linter = 'eslint'; + formatter = 'prettier'; + } + + return { + preset, + style, + appName, + e2eTestRunner, + linter, + formatter, + workspaces, + }; } async function determineAngularOptions( diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index f26160511b18b1..387ea579e63111 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -1,7 +1,13 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { Tree, readJson, readProjectConfiguration } from '@nx/devkit'; +import { + Tree, + readJson, + readProjectConfiguration, + updateJson, + writeJson, +} from '@nx/devkit'; import { applicationGenerator } from './application'; describe('app', () => { @@ -194,4 +200,177 @@ describe('app', () => { }); } ); + + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + e2eTestRunner: 'playwright', + unitTestRunner: 'vitest', + linter: 'eslint', + }); + + expect(tree.read('myapp/vite.config.ts', 'utf-8')).toMatchInlineSnapshot( + `null` + ); + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./myapp-e2e", + }, + { + "path": "./myapp", + }, + ] + `); + expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": {}, + "extends": "../tsconfig.base.json", + "files": [], + "include": [ + ".nuxt/nuxt.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.app.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "composite": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "out-tsc/myapp", + "resolveJsonModule": true, + "rootDir": "src", + }, + "exclude": [ + "dist", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + ".nuxt/nuxt.d.ts", + "src/**/*", + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "composite": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./out-tsc/vitest", + "resolveJsonModule": true, + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + ".nuxt/nuxt.d.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + ], + } + `); + expect(readJson(tree, 'myapp-e2e/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "allowJs": true, + "outDir": "dist", + "sourceMap": false, + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", + }, + "exclude": [ + "dist", + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "**/*.ts", + "**/*.js", + "playwright.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.js", + "src/**/*.test.ts", + "src/**/*.test.js", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "../myapp", + }, + ], + } + `); + }); + }); }); diff --git a/packages/nuxt/src/generators/application/application.ts b/packages/nuxt/src/generators/application/application.ts index 0ce521f9139e92..cff6c382d0de53 100644 --- a/packages/nuxt/src/generators/application/application.ts +++ b/packages/nuxt/src/generators/application/application.ts @@ -9,6 +9,7 @@ import { runTasksInSerial, toJS, Tree, + writeJson, } from '@nx/devkit'; import { Schema } from './schema'; import nuxtInitGenerator from '../init/init'; @@ -18,7 +19,6 @@ import { getRelativePathToRootTsConfig, initGenerator as jsInitGenerator, } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { updateGitIgnore } from '../../utils/update-gitignore'; import { Linter } from '@nx/eslint'; import { addE2e } from './lib/add-e2e'; @@ -32,12 +32,20 @@ import { getNxCloudAppOnBoardingUrl, createNxCloudOnboardingURLForWelcomeApp, } from 'nx/src/nx-cloud/utilities/onboarding'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function applicationGenerator(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'nuxt', 'application'); - const tasks: GeneratorCallback[] = []; + const jsInitTask = await jsInitGenerator(tree, { + ...schema, + tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', + skipFormat: true, + addTsPlugin: schema.useTsSolution, + }); + tasks.push(jsInitTask); + const options = await normalizeOptions(tree, schema); const projectOffsetFromRoot = offsetFromRoot(options.appProjectRoot); @@ -51,20 +59,29 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { onBoardingStatus === 'unclaimed' && (await getNxCloudAppOnBoardingUrl(options.nxCloudToken)); - const jsInitTask = await jsInitGenerator(tree, { - ...schema, - tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', - skipFormat: true, - }); - tasks.push(jsInitTask); tasks.push(ensureDependencies(tree, options)); - addProjectConfiguration(tree, options.projectName, { - root: options.appProjectRoot, - projectType: 'application', - sourceRoot: `${options.appProjectRoot}/src`, - targets: {}, - }); + if (options.isUsingTsSolutionConfig) { + writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { + name: getImportPath(tree, options.name), + version: '0.0.1', + private: true, + nx: { + name: options.name, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + }, + }); + } else { + addProjectConfiguration(tree, options.projectName, { + root: options.appProjectRoot, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + targets: {}, + }); + } generateFiles( tree, @@ -168,6 +185,24 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { } }); + if (options.isUsingTsSolutionConfig) { + updateTsconfigFiles( + tree, + options.appProjectRoot, + 'tsconfig.app.json', + { + jsx: 'preserve', + jsxImportSource: 'vue', + module: 'esnext', + moduleResolution: 'bundler', + resolveJsonModule: true, + }, + options.linter === 'eslint' + ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs'] + : undefined + ); + } + tasks.push(() => { logShowProjectCommand(options.projectName); }); diff --git a/packages/nuxt/src/generators/application/lib/normalize-options.ts b/packages/nuxt/src/generators/application/lib/normalize-options.ts index d35182f9416244..4fdee758de7a61 100644 --- a/packages/nuxt/src/generators/application/lib/normalize-options.ts +++ b/packages/nuxt/src/generators/application/lib/normalize-options.ts @@ -4,6 +4,7 @@ import { ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, @@ -35,6 +36,7 @@ export async function normalizeOptions( e2eProjectRoot, parsedTags, style: options.style ?? 'none', + isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), } as NormalizedSchema; normalized.unitTestRunner ??= 'vitest'; diff --git a/packages/nuxt/src/generators/application/schema.d.ts b/packages/nuxt/src/generators/application/schema.d.ts index a758930a817ad2..14095fb47e2d4f 100644 --- a/packages/nuxt/src/generators/application/schema.d.ts +++ b/packages/nuxt/src/generators/application/schema.d.ts @@ -14,6 +14,7 @@ export interface Schema { setParserOptionsProject?: boolean; style?: 'css' | 'scss' | 'less' | 'none'; nxCloudToken?: string; + useTsSolution?: boolean; } export interface NormalizedSchema extends Schema { @@ -22,4 +23,5 @@ export interface NormalizedSchema extends Schema { e2eProjectName: string; e2eProjectRoot: string; parsedTags: string[]; + isUsingTsSolutionConfig: boolean; } diff --git a/packages/nuxt/src/generators/application/schema.json b/packages/nuxt/src/generators/application/schema.json index effd221b7efeae..75f4fd461a4e8b 100644 --- a/packages/nuxt/src/generators/application/schema.json +++ b/packages/nuxt/src/generators/application/schema.json @@ -22,24 +22,27 @@ "pattern": "^[a-zA-Z][^:]*$", "x-priority": "important" }, - "linter": { - "description": "The tool to use for running lint checks.", - "type": "string", - "enum": ["eslint"], - "default": "eslint" - }, "skipFormat": { "description": "Skip formatting files.", "type": "boolean", "default": false, "x-priority": "internal" }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" + }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", "x-prompt": "Which unit test runner would you like to use?", - "default": "none" + "default": "none", + "x-priority": "important" }, "e2eTestRunner": { "type": "string", diff --git a/packages/nuxt/src/generators/init/init.ts b/packages/nuxt/src/generators/init/init.ts index 5eef7f78ea4771..1bc971e19b9049 100644 --- a/packages/nuxt/src/generators/init/init.ts +++ b/packages/nuxt/src/generators/init/init.ts @@ -1,14 +1,11 @@ import { createProjectGraphAsync, GeneratorCallback, Tree } from '@nx/devkit'; import { addPluginV1 } from '@nx/devkit/src/utils/add-plugin'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { createNodes } from '../../plugins/plugin'; import { InitSchema } from './schema'; import { updateDependencies } from './lib/utils'; export async function nuxtInitGenerator(host: Tree, schema: InitSchema) { - assertNotUsingTsSolutionSetup(host, 'nuxt', 'init'); - await addPluginV1( host, await createProjectGraphAsync(), diff --git a/packages/playwright/src/generators/configuration/configuration.ts b/packages/playwright/src/generators/configuration/configuration.ts index e245e8c4d22962..5e9d6030a5f209 100644 --- a/packages/playwright/src/generators/configuration/configuration.ts +++ b/packages/playwright/src/generators/configuration/configuration.ts @@ -97,7 +97,12 @@ export async function configurationGeneratorInternal( if (isTsSolutionSetup) { // skip eslint from typechecking since it extends from root file that is outside rootDir if (options.linter === 'eslint') { - tsconfig.exclude = ['dist', 'eslint.config.js']; + tsconfig.exclude = [ + 'dist', + 'eslint.config.js', + 'eslint.config.mjs', + 'eslint.config.cjs', + ]; } tsconfig.compilerOptions.outDir = 'dist'; diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 874abcde483763..1ea17f678b9c12 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -1398,6 +1398,8 @@ describe('app', () => { "exclude": [ "dist", "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs", ], "extends": "../tsconfig.base.json", "include": [ diff --git a/packages/remix/src/generators/application/application.impl.spec.ts b/packages/remix/src/generators/application/application.impl.spec.ts index cc510d65b853ae..9737ca910b0e40 100644 --- a/packages/remix/src/generators/application/application.impl.spec.ts +++ b/packages/remix/src/generators/application/application.impl.spec.ts @@ -507,6 +507,8 @@ describe('Remix Application', () => { "exclude": [ "dist", "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs", ], "extends": "../tsconfig.base.json", "include": [ diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap index 84f0aba45dcf71..2944d649a680a3 100644 --- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap @@ -361,6 +361,7 @@ exports[`application generator should set up project correctly with given option "test/src/app/NxWelcome.vue", "test/src/main.ts", "test/src/styles.css", + "test/src/vue-shims.d.ts", "test/tsconfig.app.json", "test/tsconfig.json", "test/tsconfig.spec.json", diff --git a/packages/vue/src/generators/application/application.spec.ts b/packages/vue/src/generators/application/application.spec.ts index 244728a729667b..fcddea4516e19a 100644 --- a/packages/vue/src/generators/application/application.spec.ts +++ b/packages/vue/src/generators/application/application.spec.ts @@ -6,6 +6,9 @@ import { readProjectConfiguration, readNxJson, updateNxJson, + updateJson, + writeJson, + readJson, } from '@nx/devkit'; import * as devkitExports from 'nx/src/devkit-exports'; @@ -96,6 +99,188 @@ describe('application generator', () => { expect(tree.exists('test/src/style.none')).toBeFalsy(); expect(tree.read('test/src/main.ts', 'utf-8')).not.toContain('styles.none'); }); + + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(tree, { + ...options, + style: 'none', + linter: 'eslint', + }); + + expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(` + "/// + import { defineConfig } from 'vite'; + import vue from '@vitejs/plugin-vue'; + + export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/test', + server: { + port: 4200, + host: 'localhost', + }, + preview: { + port: 4300, + host: 'localhost', + }, + plugins: [vue()], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: './dist', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../coverage/test', + provider: 'v8', + }, + }, + }); + " + `); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./test-e2e", + }, + { + "path": "./test", + }, + ] + `); + expect(readJson(tree, 'test/tsconfig.json')).toMatchInlineSnapshot(` + { + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'test/tsconfig.app.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "out-tsc/test", + "resolveJsonModule": true, + "rootDir": "src", + "types": [ + "vite/client", + ], + }, + "exclude": [ + "dist", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.vue", + "src/**/*.test.vue", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.js", + "src/**/*.jsx", + "src/**/*.ts", + "src/**/*.vue", + ], + } + `); + expect(readJson(tree, 'test/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./out-tsc/vitest", + "resolveJsonModule": true, + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + ], + } + `); + }); + }); }); function listFiles(tree: Tree): string[] { diff --git a/packages/vue/src/generators/application/application.ts b/packages/vue/src/generators/application/application.ts index 3b85e58abfd9cf..b76078b87115cd 100644 --- a/packages/vue/src/generators/application/application.ts +++ b/packages/vue/src/generators/application/application.ts @@ -2,14 +2,15 @@ import { addProjectConfiguration, formatFiles, GeneratorCallback, + joinPathFragments, readNxJson, runTasksInSerial, toJS, Tree, + writeJson, } from '@nx/devkit'; import { Linter } from '@nx/eslint'; import { initGenerator as jsInitGenerator } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { Schema } from './schema'; import { normalizeOptions } from './lib/normalize-options'; import { vueInitGenerator } from '../init/init'; @@ -20,6 +21,8 @@ import { addVite } from './lib/add-vite'; import { extractTsConfigBase } from '../../utils/create-ts-config'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; export function applicationGenerator(tree: Tree, options: Schema) { return applicationGeneratorInternal(tree, { addPlugin: false, ...options }); @@ -29,7 +32,18 @@ export async function applicationGeneratorInternal( tree: Tree, _options: Schema ): Promise { - assertNotUsingTsSolutionSetup(tree, 'vue', 'application'); + const tasks: GeneratorCallback[] = []; + tasks.push( + await jsInitGenerator(tree, { + ..._options, + tsConfigName: _options.rootProject + ? 'tsconfig.json' + : 'tsconfig.base.json', + skipFormat: true, + addTsPlugin: _options.useTsSolution, + formatter: _options.formatter, + }) + ); const options = await normalizeOptions(tree, _options); const nxJson = readNxJson(tree); @@ -38,24 +52,28 @@ export async function applicationGeneratorInternal( process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; - const tasks: GeneratorCallback[] = []; - - addProjectConfiguration(tree, options.projectName, { - root: options.appProjectRoot, - projectType: 'application', - sourceRoot: `${options.appProjectRoot}/src`, - targets: {}, - }); + if (options.isUsingTsSolutionConfig) { + writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { + name: getImportPath(tree, options.name), + version: '0.0.1', + private: true, + nx: { + name: options.name, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + }, + }); + } else { + addProjectConfiguration(tree, options.projectName, { + root: options.appProjectRoot, + projectType: 'application', + sourceRoot: `${options.appProjectRoot}/src`, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + targets: {}, + }); + } - tasks.push( - await jsInitGenerator(tree, { - ...options, - tsConfigName: options.rootProject - ? 'tsconfig.json' - : 'tsconfig.base.json', - skipFormat: true, - }) - ); tasks.push( await vueInitGenerator(tree, { ...options, @@ -97,6 +115,24 @@ export async function applicationGeneratorInternal( if (!options.skipFormat) await formatFiles(tree); + if (options.isUsingTsSolutionConfig) { + updateTsconfigFiles( + tree, + options.appProjectRoot, + 'tsconfig.app.json', + { + jsx: 'preserve', + jsxImportSource: 'vue', + module: 'esnext', + moduleResolution: 'bundler', + resolveJsonModule: true, + }, + options.linter === 'eslint' + ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs'] + : undefined + ); + } + tasks.push(() => { logShowProjectCommand(options.projectName); }); diff --git a/packages/vue/src/generators/application/files/common/src/vue-shims.d.ts.template b/packages/vue/src/generators/application/files/common/src/vue-shims.d.ts.template new file mode 100644 index 00000000000000..798e8fcfac4afa --- /dev/null +++ b/packages/vue/src/generators/application/files/common/src/vue-shims.d.ts.template @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { defineComponent } from 'vue'; + const component: ReturnType; + export default component; +} diff --git a/packages/vue/src/generators/application/lib/normalize-options.ts b/packages/vue/src/generators/application/lib/normalize-options.ts index bfefcd687d59f8..5a8e8752b519dc 100644 --- a/packages/vue/src/generators/application/lib/normalize-options.ts +++ b/packages/vue/src/generators/application/lib/normalize-options.ts @@ -4,6 +4,7 @@ import { ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, @@ -39,6 +40,7 @@ export async function normalizeOptions( normalized.routing = normalized.routing ?? false; normalized.unitTestRunner ??= 'vitest'; normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright'; + normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); return normalized; } diff --git a/packages/vue/src/generators/application/schema.d.ts b/packages/vue/src/generators/application/schema.d.ts index 46d4eb6e28715b..b98873f749af33 100644 --- a/packages/vue/src/generators/application/schema.d.ts +++ b/packages/vue/src/generators/application/schema.d.ts @@ -10,6 +10,7 @@ export interface Schema { inSourceTests?: boolean; e2eTestRunner: 'cypress' | 'playwright' | 'none'; linter: Linter | LinterType; + formatter?: 'none' | 'prettier'; routing?: boolean; js?: boolean; strict?: boolean; @@ -18,6 +19,7 @@ export interface Schema { rootProject?: boolean; addPlugin?: boolean; nxCloudToken?: string; + useTsSolution?: boolean; } export interface NormalizedSchema extends Schema { @@ -27,4 +29,5 @@ export interface NormalizedSchema extends Schema { e2eProjectRoot: string; parsedTags: string[]; devServerPort?: number; + isUsingTsSolutionConfig: boolean; } diff --git a/packages/vue/src/generators/application/schema.json b/packages/vue/src/generators/application/schema.json index a063888fd10117..a18386d23db6bf 100644 --- a/packages/vue/src/generators/application/schema.json +++ b/packages/vue/src/generators/application/schema.json @@ -60,12 +60,6 @@ ] } }, - "linter": { - "description": "The tool to use for running lint checks.", - "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" - }, "routing": { "type": "boolean", "description": "Generate application with routes.", @@ -78,12 +72,21 @@ "default": false, "x-priority": "internal" }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" + }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", "x-prompt": "Which unit test runner would you like to use?", - "default": "vitest" + "default": "none", + "x-priority": "important" }, "inSourceTests": { "type": "boolean", diff --git a/packages/vue/src/generators/init/init.ts b/packages/vue/src/generators/init/init.ts index d3bcdb0ed90cd7..dc258ca2f940d3 100755 --- a/packages/vue/src/generators/init/init.ts +++ b/packages/vue/src/generators/init/init.ts @@ -6,7 +6,6 @@ import { runTasksInSerial, Tree, } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { nxVersion, vueVersion } from '../../utils/versions'; import { InitSchema } from './schema'; @@ -28,8 +27,6 @@ function updateDependencies(host: Tree, schema: InitSchema) { } export async function vueInitGenerator(host: Tree, schema: InitSchema) { - assertNotUsingTsSolutionSetup(host, 'vue', 'init'); - let installTask: GeneratorCallback = () => {}; if (!schema.skipPackageJson) { installTask = updateDependencies(host, schema); diff --git a/packages/vue/src/generators/library/files/package.json__tmpl__ b/packages/vue/src/generators/library/files/package.json__tmpl__ deleted file mode 100644 index 507420ee308347..00000000000000 --- a/packages/vue/src/generators/library/files/package.json__tmpl__ +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "<%= name %>", - "version": "0.0.1", - "main": "./index.js", - "types": "./index.d.ts", - "exports": { - ".": { - "import": "./index.mjs", - "require": "./index.js" - } - } -} diff --git a/packages/vue/src/generators/library/lib/create-library-files.ts b/packages/vue/src/generators/library/lib/create-library-files.ts index 9e2c6ccb3b2608..ae1d352b756ba8 100644 --- a/packages/vue/src/generators/library/lib/create-library-files.ts +++ b/packages/vue/src/generators/library/lib/create-library-files.ts @@ -31,8 +31,22 @@ export function createLibraryFiles(host: Tree, options: NormalizedSchema) { substitutions ); - if (!options.publishable && options.bundler === 'none') { - host.delete(`${options.projectRoot}/package.json`); + if ( + !options.isUsingTsSolutionConfig && + (options.publishable || options.bundler !== 'none') + ) { + writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), { + name: options.name, + version: '0.0.1', + main: './index.js', + types: './index.d.ts', + exports: { + '.': { + import: './index.mjs', + require: './index.js', + }, + }, + }); } if (options.unitTestRunner !== 'vitest') { diff --git a/packages/vue/src/generators/library/lib/normalize-options.ts b/packages/vue/src/generators/library/lib/normalize-options.ts index 2a91e17a1c7342..eabe320f65238f 100644 --- a/packages/vue/src/generators/library/lib/normalize-options.ts +++ b/packages/vue/src/generators/library/lib/normalize-options.ts @@ -10,6 +10,7 @@ import { ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, @@ -60,6 +61,7 @@ export async function normalizeOptions( projectRoot, parsedTags, importPath, + isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), } as NormalizedSchema; // Libraries with a bundler or is publishable must also be buildable. diff --git a/packages/vue/src/generators/library/library.spec.ts b/packages/vue/src/generators/library/library.spec.ts index 94092f41301e2b..620a57f34dc238 100644 --- a/packages/vue/src/generators/library/library.spec.ts +++ b/packages/vue/src/generators/library/library.spec.ts @@ -5,6 +5,7 @@ import { readProjectConfiguration, Tree, updateJson, + writeJson, } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Linter } from '@nx/eslint'; @@ -438,4 +439,209 @@ module.exports = [ expect(eslintConfig.overrides[0].files).toContain('*.vue'); }); }); + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + setParserOptionsProject: true, + linter: 'eslint', + }); + + expect(tree.read('my-lib/vite.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import vue from '@vitejs/plugin-vue'; + import { defineConfig } from 'vite'; + + export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/my-lib', + plugins: [vue()], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { reportsDirectory: '../coverage/my-lib', provider: 'v8' }, + }, + }); + " + `); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./my-lib", + }, + ] + `); + expect(readJson(tree, 'my-lib/tsconfig.json')).toMatchInlineSnapshot(` + { + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'my-lib/tsconfig.lib.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "out-tsc/my-lib", + "resolveJsonModule": true, + "rootDir": "src", + "types": [ + "vite/client", + ], + }, + "exclude": [ + "dist", + "src/**/__tests__/*", + "src/**/*.spec.vue", + "src/**/*.test.vue", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.js", + "src/**/*.jsx", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + ], + } + `); + expect(readJson(tree, 'my-lib/tsconfig.spec.json')) + .toMatchInlineSnapshot(` + { + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./out-tsc/vitest", + "resolveJsonModule": true, + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + ], + } + `); + }); + + it('should exclude non-buildable libraries from TS plugin registration', async () => { + updateJson(tree, 'nx.json', (json) => { + json.plugins = ['@nx/js/typescript']; + return json; + }); + await libraryGenerator(tree, { + ...defaultSchema, + addPlugin: true, + setParserOptionsProject: true, + linter: 'eslint', + bundler: 'none', + }); + + const nxJson = readJson(tree, 'nx.json'); + expect(nxJson.plugins).toMatchInlineSnapshot(` + [ + { + "exclude": [ + "my-lib/*", + ], + "plugin": "@nx/js/typescript", + }, + { + "options": { + "targetName": "lint", + }, + "plugin": "@nx/eslint/plugin", + }, + { + "options": { + "buildTargetName": "build", + "previewTargetName": "preview", + "serveStaticTargetName": "serve-static", + "serveTargetName": "serve", + "testTargetName": "test", + "typecheckTargetName": "typecheck", + }, + "plugin": "@nx/vite/plugin", + }, + ] + `); + }); + }); }); diff --git a/packages/vue/src/generators/library/library.ts b/packages/vue/src/generators/library/library.ts index 25a721b9da1e8f..4e6e943d1eb2df 100644 --- a/packages/vue/src/generators/library/library.ts +++ b/packages/vue/src/generators/library/library.ts @@ -2,14 +2,17 @@ import { addProjectConfiguration, formatFiles, GeneratorCallback, + installPackagesTask, joinPathFragments, + readNxJson, runTasksInSerial, toJS, Tree, updateJson, + updateNxJson, + writeJson, } from '@nx/devkit'; import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { vueInitGenerator } from '../init/init'; import { Schema } from './schema'; import { normalizeOptions } from './lib/normalize-options'; @@ -22,16 +25,19 @@ import { ensureDependencies } from '../../utils/ensure-dependencies'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils'; import { relative } from 'path'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { ensureProjectIsExcludedFromPluginRegistrations } from '@nx/js/src/utils/typescript/plugin'; export function libraryGenerator(tree: Tree, schema: Schema) { return libraryGeneratorInternal(tree, { addPlugin: false, ...schema }); } export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'vue', 'library'); - const tasks: GeneratorCallback[] = []; + tasks.push(await jsInitGenerator(tree, { ...schema, skipFormat: true })); + const options = await normalizeOptions(tree, schema); if (options.publishable === true && !schema.importPath) { throw new Error( @@ -39,15 +45,37 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { ); } - addProjectConfiguration(tree, options.name, { - root: options.projectRoot, - sourceRoot: joinPathFragments(options.projectRoot, 'src'), - projectType: 'library', - tags: options.parsedTags, - targets: {}, - }); + if (options.isUsingTsSolutionConfig) { + const sourceEntry = + options.bundler === 'none' + ? options.js + ? './src/index.js' + : './src/index.ts' + : undefined; + writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), { + name: getImportPath(tree, options.name), + version: '0.0.1', + private: true, + main: sourceEntry, + types: sourceEntry, + files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, + nx: { + name: options.name, + projectType: 'application', + sourceRoot: `${options.projectRoot}/src`, + tags: options.parsedTags?.length ? options.parsedTags : undefined, + }, + }); + } else { + addProjectConfiguration(tree, options.name, { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: 'library', + tags: options.parsedTags, + targets: {}, + }); + } - tasks.push(await jsInitGenerator(tree, { ...schema, skipFormat: true })); tasks.push( await vueInitGenerator(tree, { ...options, @@ -86,14 +114,23 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { }); } - if (options.publishable || options.bundler !== 'none') { + if ( + !options.isUsingTsSolutionConfig && + (options.publishable || options.bundler !== 'none') + ) { updateJson(tree, `${options.projectRoot}/package.json`, (json) => { json.name = options.importPath; return json; }); } - if (!options.skipTsConfig) { + if (options.bundler === 'none' && options.addPlugin) { + const nxJson = readNxJson(tree); + ensureProjectIsExcludedFromPluginRegistrations(nxJson, options.projectRoot); + updateNxJson(tree, nxJson); + } + + if (!options.skipTsConfig && !options.isUsingTsSolutionConfig) { addTsConfigPath(tree, options.importPath, [ joinPathFragments( options.projectRoot, @@ -107,6 +144,29 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { if (!options.skipFormat) await formatFiles(tree); + if (options.isUsingTsSolutionConfig) { + updateTsconfigFiles( + tree, + options.projectRoot, + 'tsconfig.lib.json', + { + jsx: 'preserve', + jsxImportSource: 'vue', + module: 'esnext', + moduleResolution: 'bundler', + resolveJsonModule: true, + }, + options.linter === 'eslint' + ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs'] + : undefined + ); + } + + // Always run install to link packages. + if (options.isUsingTsSolutionConfig) { + tasks.push(() => installPackagesTask(tree)); + } + tasks.push(() => { logShowProjectCommand(options.name); }); diff --git a/packages/vue/src/generators/library/schema.d.ts b/packages/vue/src/generators/library/schema.d.ts index 198e8cd5840ba7..ed8dc8249f096a 100644 --- a/packages/vue/src/generators/library/schema.d.ts +++ b/packages/vue/src/generators/library/schema.d.ts @@ -35,4 +35,5 @@ export interface NormalizedSchema extends Schema { appMain?: string; appSourceRoot?: string; unitTestRunner?: 'vitest' | 'none'; + isUsingTsSolutionConfig: boolean; } diff --git a/packages/vue/src/generators/library/schema.json b/packages/vue/src/generators/library/schema.json index 7ca310f6b938dd..dda7e5bbb558cb 100644 --- a/packages/vue/src/generators/library/schema.json +++ b/packages/vue/src/generators/library/schema.json @@ -36,13 +36,17 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["vitest", "none"], "description": "Test runner to use for unit tests.", - "x-prompt": "What unit test runner should be used?" + "x-prompt": "What unit test runner should be used?", + "default": "none", + "x-priority": "important" }, "inSourceTests": { "type": "boolean", diff --git a/packages/vue/src/utils/create-ts-config.ts b/packages/vue/src/utils/create-ts-config.ts index 421303d8991b29..9179cac644843e 100644 --- a/packages/vue/src/utils/create-ts-config.ts +++ b/packages/vue/src/utils/create-ts-config.ts @@ -1,7 +1,92 @@ import { Tree, updateJson, writeJson } from '@nx/devkit'; import * as shared from '@nx/js/src/utils/typescript/create-ts-config'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export function createTsConfig( + host: Tree, + projectRoot: string, + type: 'app' | 'lib', + options: { + strict?: boolean; + style?: string; + bundler?: string; + rootProject?: boolean; + unitTestRunner?: string; + }, + relativePathToRootTsConfig: string +) { + if (isUsingTsSolutionSetup(host)) { + createTsConfigForTsSolution( + host, + projectRoot, + type, + options, + relativePathToRootTsConfig + ); + } else { + createTsConfigForNonTsSolution( + host, + projectRoot, + type, + options, + relativePathToRootTsConfig + ); + } +} + +export function createTsConfigForTsSolution( + host: Tree, + projectRoot: string, + type: 'app' | 'lib', + options: { + strict?: boolean; + style?: string; + rootProject?: boolean; + unitTestRunner?: string; + }, + relativePathToRootTsConfig: string +) { + const json = { + files: [], + include: [], + references: [ + { + path: type === 'app' ? './tsconfig.app.json' : './tsconfig.lib.json', + }, + ], + } as any; + + // inline tsconfig.base.json into the project + if (options.rootProject) { + json.compileOnSave = false; + json.compilerOptions = { + ...shared.tsConfigBaseOptions, + ...json.compilerOptions, + }; + json.exclude = ['node_modules', 'tmp']; + } else { + json.extends = relativePathToRootTsConfig; + } + + writeJson(host, `${projectRoot}/tsconfig.json`, json); + + const tsconfigProjectPath = `${projectRoot}/tsconfig.${type}.json`; + if (host.exists(tsconfigProjectPath)) { + updateJson(host, tsconfigProjectPath, (json) => { + json.compilerOptions ??= {}; + + const types = new Set(json.compilerOptions.types ?? []); + types.add('vite/client'); + + json.compilerOptions.types = Array.from(types); + + return json; + }); + } else { + } +} + +export function createTsConfigForNonTsSolution( host: Tree, projectRoot: string, type: 'app' | 'lib', diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index da8bb757206806..9698efba5da38b 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -421,7 +421,9 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) { options.preset === Preset.NextJs || options.preset === Preset.ReactMonorepo || options.preset === Preset.ReactNative || - options.preset === Preset.RemixMonorepo) && + options.preset === Preset.RemixMonorepo || + options.preset === Preset.VueMonorepo || + options.preset === Preset.Nuxt) && options.workspaces) ) { const workspaces = options.workspaceGlobs ?? ['packages/**']; diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index d5f633d91444e5..d9d8a278f7490f 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -135,6 +135,7 @@ async function createPreset(tree: Tree, options: Schema) { e2eTestRunner: options.e2eTestRunner ?? 'playwright', addPlugin, nxCloudToken: options.nxCloudToken, + useTsSolution: options.workspaces, }); } else if (options.preset === Preset.VueStandalone) { const { applicationGenerator: vueApplicationGenerator } = require('@nx' + @@ -163,6 +164,7 @@ async function createPreset(tree: Tree, options: Schema) { e2eTestRunner: options.e2eTestRunner ?? 'playwright', addPlugin, nxCloudToken: options.nxCloudToken, + useTsSolution: options.workspaces, }); } else if (options.preset === Preset.NuxtStandalone) { const { applicationGenerator: nuxtApplicationGenerator } = require('@nx' +