feat(vue): init, app, component and lib generators (#19130)
Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
This commit is contained in:
parent
6a847190af
commit
769974b45a
@ -139,7 +139,7 @@ Package manager to use
|
|||||||
|
|
||||||
Type: `string`
|
Type: `string`
|
||||||
|
|
||||||
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||||
|
|
||||||
### routing
|
### routing
|
||||||
|
|
||||||
|
|||||||
@ -139,7 +139,7 @@ Package manager to use
|
|||||||
|
|
||||||
Type: `string`
|
Type: `string`
|
||||||
|
|
||||||
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "next", "nextjs-standalone", "react-native", "expo", "nest", "express", "react", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||||
|
|
||||||
### routing
|
### routing
|
||||||
|
|
||||||
|
|||||||
@ -2084,6 +2084,20 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "vue",
|
||||||
|
"id": "vue",
|
||||||
|
"description": "Vue package.",
|
||||||
|
"itemList": [
|
||||||
|
{
|
||||||
|
"id": "overview",
|
||||||
|
"path": "/packages/vue",
|
||||||
|
"name": "Overview of the Nx Vue Plugin",
|
||||||
|
"description": "The Nx Plugin for Vue contains generators for managing Vue applications and libraries within an Nx workspace. This page also explains how to configure Vue on your Nx workspace.",
|
||||||
|
"file": "shared/packages/vue/vue-plugin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "webpack",
|
"name": "webpack",
|
||||||
"id": "webpack",
|
"id": "webpack",
|
||||||
|
|||||||
@ -361,6 +361,16 @@
|
|||||||
"generators": ["init", "configuration", "vitest"]
|
"generators": ["init", "configuration", "vitest"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "vue",
|
||||||
|
"packageName": "vue",
|
||||||
|
"description": "The Vue plugin for Nx contains executors and generators for managing Vue applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, components, hooks, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
||||||
|
"path": "generated/packages/vite.json",
|
||||||
|
"schemas": {
|
||||||
|
"executors": [],
|
||||||
|
"generators": ["init", "library", "application", "component"]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"packageName": "web",
|
"packageName": "web",
|
||||||
|
|||||||
60
docs/shared/packages/vue/vue-plugin.md
Normal file
60
docs/shared/packages/vue/vue-plugin.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Overview of the Nx Vue Plugin
|
||||||
|
description: The Nx Plugin for Vue contains generators for managing Vue applications and libraries within an Nx workspace. This page also explains how to configure Vue on your Nx workspace.
|
||||||
|
---
|
||||||
|
|
||||||
|
The Nx plugin for [Vue](https://vuejs.org/).
|
||||||
|
|
||||||
|
## Setting up a new Nx workspace with Vue
|
||||||
|
|
||||||
|
You can create a new workspace that uses Vue with one of the following commands:
|
||||||
|
|
||||||
|
- Generate a new monorepo with a Vue app set up with Vue
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx create-nx-workspace@latest --preset=vue
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Vue to an existing workspace
|
||||||
|
|
||||||
|
There are a number of ways to use Vue in your existing workspace.
|
||||||
|
|
||||||
|
### Install the `@nx/vue` plugin
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="npm" %}
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install -D @nx/vue
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% tab label="yarn" %}
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add -D @nx/vue
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% tab label="pnpm" %}
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm install -D @nx/vue
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### Generate a new project using Vue
|
||||||
|
|
||||||
|
To generate a Vue application, run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nx/vue:app my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
To generate a Vue library, run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nx g @nx/vue:lib my-lib
|
||||||
|
```
|
||||||
@ -90,6 +90,7 @@ export function newProject({
|
|||||||
`@nx/rollup`,
|
`@nx/rollup`,
|
||||||
`@nx/react`,
|
`@nx/react`,
|
||||||
`@nx/storybook`,
|
`@nx/storybook`,
|
||||||
|
`@nx/vue`,
|
||||||
`@nx/vite`,
|
`@nx/vite`,
|
||||||
`@nx/web`,
|
`@nx/web`,
|
||||||
`@nx/webpack`,
|
`@nx/webpack`,
|
||||||
|
|||||||
54
e2e/vue/src/vue.test.ts
Normal file
54
e2e/vue/src/vue.test.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
cleanupProject,
|
||||||
|
killPorts,
|
||||||
|
newProject,
|
||||||
|
runCLI,
|
||||||
|
runE2ETests,
|
||||||
|
uniq,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('Vue Plugin', () => {
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
proj = newProject({
|
||||||
|
unsetProjectNameAndRootFormat: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => cleanupProject());
|
||||||
|
|
||||||
|
it('should serve application in dev mode', async () => {
|
||||||
|
const app = uniq('app');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/vue:app ${app} --unitTestRunner=vitest --e2eTestRunner=playwright`
|
||||||
|
);
|
||||||
|
let result = runCLI(`test ${app}`);
|
||||||
|
expect(result).toContain(`Successfully ran target test for project ${app}`);
|
||||||
|
|
||||||
|
result = runCLI(`build ${app}`);
|
||||||
|
expect(result).toContain(
|
||||||
|
`Successfully ran target build for project ${app}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (runE2ETests()) {
|
||||||
|
const e2eResults = runCLI(`e2e ${app}-e2e --no-watch`);
|
||||||
|
expect(e2eResults).toContain('Successfully ran target e2e');
|
||||||
|
expect(await killPorts()).toBeTruthy();
|
||||||
|
}
|
||||||
|
}, 200_000);
|
||||||
|
|
||||||
|
it('should build library', async () => {
|
||||||
|
const lib = uniq('lib');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/vue:lib ${lib} --bundler=vite --unitTestRunner=vitest`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = runCLI(`build ${lib}`);
|
||||||
|
expect(result).toContain(
|
||||||
|
`Successfully ran target build for project ${lib}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -420,6 +420,38 @@ describe('create-nx-workspace', () => {
|
|||||||
}, 90000);
|
}, 90000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a workspace with a single vue app at the root', () => {
|
||||||
|
const wsName = uniq('vue');
|
||||||
|
|
||||||
|
runCreateWorkspace(wsName, {
|
||||||
|
preset: 'vue-standalone',
|
||||||
|
appName: wsName,
|
||||||
|
style: 'css',
|
||||||
|
packageManager,
|
||||||
|
e2eTestRunner: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
checkFilesExist('package.json');
|
||||||
|
checkFilesExist('project.json');
|
||||||
|
checkFilesExist('index.html');
|
||||||
|
checkFilesExist('src/main.ts');
|
||||||
|
checkFilesExist('src/App.vue');
|
||||||
|
expectCodeIsFormatted();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to create an vue monorepo', () => {
|
||||||
|
const wsName = uniq('vue');
|
||||||
|
const appName = uniq('app');
|
||||||
|
runCreateWorkspace(wsName, {
|
||||||
|
preset: 'vue-monorepo',
|
||||||
|
appName,
|
||||||
|
style: 'css',
|
||||||
|
packageManager,
|
||||||
|
e2eTestRunner: 'none',
|
||||||
|
});
|
||||||
|
expectCodeIsFormatted();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create-nx-workspace parent folder', () => {
|
describe('create-nx-workspace parent folder', () => {
|
||||||
|
|||||||
@ -62,6 +62,15 @@ interface AngularArguments extends BaseArguments {
|
|||||||
e2eTestRunner: 'none' | 'cypress' | 'playwright';
|
e2eTestRunner: 'none' | 'cypress' | 'playwright';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VueArguments extends BaseArguments {
|
||||||
|
stack: 'vue';
|
||||||
|
workspaceType: 'standalone' | 'integrated';
|
||||||
|
appName: string;
|
||||||
|
// framework: 'none' | 'nuxt';
|
||||||
|
style: string;
|
||||||
|
e2eTestRunner: 'none' | 'cypress' | 'playwright';
|
||||||
|
}
|
||||||
|
|
||||||
interface NodeArguments extends BaseArguments {
|
interface NodeArguments extends BaseArguments {
|
||||||
stack: 'node';
|
stack: 'node';
|
||||||
workspaceType: 'standalone' | 'integrated';
|
workspaceType: 'standalone' | 'integrated';
|
||||||
@ -78,6 +87,7 @@ type Arguments =
|
|||||||
| NoneArguments
|
| NoneArguments
|
||||||
| ReactArguments
|
| ReactArguments
|
||||||
| AngularArguments
|
| AngularArguments
|
||||||
|
| VueArguments
|
||||||
| NodeArguments
|
| NodeArguments
|
||||||
| UnknownStackArguments;
|
| UnknownStackArguments;
|
||||||
|
|
||||||
@ -347,7 +357,7 @@ async function determineFolder(
|
|||||||
|
|
||||||
async function determineStack(
|
async function determineStack(
|
||||||
parsedArgs: yargs.Arguments<Arguments>
|
parsedArgs: yargs.Arguments<Arguments>
|
||||||
): Promise<'none' | 'react' | 'angular' | 'node' | 'unknown'> {
|
): Promise<'none' | 'react' | 'angular' | 'vue' | 'node' | 'unknown'> {
|
||||||
if (parsedArgs.preset) {
|
if (parsedArgs.preset) {
|
||||||
switch (parsedArgs.preset) {
|
switch (parsedArgs.preset) {
|
||||||
case Preset.Angular:
|
case Preset.Angular:
|
||||||
@ -360,7 +370,9 @@ async function determineStack(
|
|||||||
case Preset.NextJs:
|
case Preset.NextJs:
|
||||||
case Preset.NextJsStandalone:
|
case Preset.NextJsStandalone:
|
||||||
return 'react';
|
return 'react';
|
||||||
|
case Preset.VueStandalone:
|
||||||
|
case Preset.VueMonorepo:
|
||||||
|
return 'vue';
|
||||||
case Preset.Nest:
|
case Preset.Nest:
|
||||||
case Preset.NodeStandalone:
|
case Preset.NodeStandalone:
|
||||||
case Preset.Express:
|
case Preset.Express:
|
||||||
@ -379,7 +391,7 @@ async function determineStack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { stack } = await enquirer.prompt<{
|
const { stack } = await enquirer.prompt<{
|
||||||
stack: 'none' | 'react' | 'angular' | 'node';
|
stack: 'none' | 'react' | 'angular' | 'node' | 'vue';
|
||||||
}>([
|
}>([
|
||||||
{
|
{
|
||||||
name: 'stack',
|
name: 'stack',
|
||||||
@ -394,6 +406,10 @@ async function determineStack(
|
|||||||
name: `react`,
|
name: `react`,
|
||||||
message: `React: Configures a React application with your framework of choice.`,
|
message: `React: Configures a React application with your framework of choice.`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: `vue`,
|
||||||
|
message: `Vue: Configures a Vue application with modern tooling.`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: `angular`,
|
name: `angular`,
|
||||||
message: `Angular: Configures a Angular application with modern tooling.`,
|
message: `Angular: Configures a Angular application with modern tooling.`,
|
||||||
@ -419,6 +435,8 @@ async function determinePresetOptions(
|
|||||||
return determineReactOptions(parsedArgs);
|
return determineReactOptions(parsedArgs);
|
||||||
case 'angular':
|
case 'angular':
|
||||||
return determineAngularOptions(parsedArgs);
|
return determineAngularOptions(parsedArgs);
|
||||||
|
case 'vue':
|
||||||
|
return determineVueOptions(parsedArgs);
|
||||||
case 'node':
|
case 'node':
|
||||||
return determineNodeOptions(parsedArgs);
|
return determineNodeOptions(parsedArgs);
|
||||||
default:
|
default:
|
||||||
@ -589,6 +607,69 @@ async function determineReactOptions(
|
|||||||
return { preset, style, appName, bundler, nextAppDir, e2eTestRunner };
|
return { preset, style, appName, bundler, nextAppDir, e2eTestRunner };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function determineVueOptions(
|
||||||
|
parsedArgs: yargs.Arguments<VueArguments>
|
||||||
|
): Promise<Partial<Arguments>> {
|
||||||
|
let preset: Preset;
|
||||||
|
let style: undefined | string = undefined;
|
||||||
|
let appName: string;
|
||||||
|
let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined;
|
||||||
|
|
||||||
|
if (parsedArgs.preset) {
|
||||||
|
preset = parsedArgs.preset;
|
||||||
|
} else {
|
||||||
|
const workspaceType = await determineStandaloneOrMonorepo();
|
||||||
|
|
||||||
|
if (workspaceType === 'standalone') {
|
||||||
|
preset = Preset.VueStandalone;
|
||||||
|
} else {
|
||||||
|
preset = Preset.VueMonorepo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset === Preset.VueStandalone) {
|
||||||
|
appName = parsedArgs.appName ?? parsedArgs.name;
|
||||||
|
} else {
|
||||||
|
appName = await determineAppName(parsedArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
e2eTestRunner = await determineE2eTestRunner(parsedArgs);
|
||||||
|
|
||||||
|
if (parsedArgs.style) {
|
||||||
|
style = parsedArgs.style;
|
||||||
|
} else {
|
||||||
|
const reply = await enquirer.prompt<{ style: string }>([
|
||||||
|
{
|
||||||
|
name: 'style',
|
||||||
|
message: `Default stylesheet format`,
|
||||||
|
initial: 'css' as any,
|
||||||
|
type: 'autocomplete',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
name: 'css',
|
||||||
|
message: 'CSS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scss',
|
||||||
|
message: 'SASS(.scss) [ http://sass-lang.com ]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'less',
|
||||||
|
message: 'LESS [ http://lesscss.org ]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'none',
|
||||||
|
message: 'None',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
style = reply.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { preset, style, appName, e2eTestRunner };
|
||||||
|
}
|
||||||
|
|
||||||
async function determineAngularOptions(
|
async function determineAngularOptions(
|
||||||
parsedArgs: yargs.Arguments<AngularArguments>
|
parsedArgs: yargs.Arguments<AngularArguments>
|
||||||
): Promise<Partial<Arguments>> {
|
): Promise<Partial<Arguments>> {
|
||||||
@ -847,7 +928,9 @@ async function determineStandaloneOrMonorepo(): Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function determineAppName(
|
async function determineAppName(
|
||||||
parsedArgs: yargs.Arguments<ReactArguments | AngularArguments | NodeArguments>
|
parsedArgs: yargs.Arguments<
|
||||||
|
ReactArguments | AngularArguments | NodeArguments | VueArguments
|
||||||
|
>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (parsedArgs.appName) return parsedArgs.appName;
|
if (parsedArgs.appName) return parsedArgs.appName;
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,10 @@ export const presetOptions: { name: Preset; message: string }[] = [
|
|||||||
name: Preset.AngularMonorepo,
|
name: Preset.AngularMonorepo,
|
||||||
message: 'angular [a monorepo with a single Angular application]',
|
message: 'angular [a monorepo with a single Angular application]',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: Preset.VueMonorepo,
|
||||||
|
message: 'vue [a monorepo with a single Vue application]',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: Preset.NextJs,
|
name: Preset.NextJs,
|
||||||
message: 'next.js [a monorepo with a single Next.js application]',
|
message: 'next.js [a monorepo with a single Next.js application]',
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export enum Preset {
|
|||||||
AngularStandalone = 'angular-standalone',
|
AngularStandalone = 'angular-standalone',
|
||||||
ReactMonorepo = 'react-monorepo',
|
ReactMonorepo = 'react-monorepo',
|
||||||
ReactStandalone = 'react-standalone',
|
ReactStandalone = 'react-standalone',
|
||||||
|
VueMonorepo = 'vue-monorepo',
|
||||||
|
VueStandalone = 'vue-standalone',
|
||||||
NextJs = 'next',
|
NextJs = 'next',
|
||||||
NextJsStandalone = 'nextjs-standalone',
|
NextJsStandalone = 'nextjs-standalone',
|
||||||
ReactNative = 'react-native',
|
ReactNative = 'react-native',
|
||||||
|
|||||||
@ -63,6 +63,7 @@ describe('app', () => {
|
|||||||
'vitest/importMeta',
|
'vitest/importMeta',
|
||||||
'vite/client',
|
'vite/client',
|
||||||
'node',
|
'node',
|
||||||
|
'vitest',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -115,9 +115,8 @@ export async function applicationGeneratorInternal(
|
|||||||
addProject(host, options);
|
addProject(host, options);
|
||||||
|
|
||||||
if (options.bundler === 'vite') {
|
if (options.bundler === 'vite') {
|
||||||
const { viteConfigurationGenerator } = ensurePackage<
|
const { createOrEditViteConfig, viteConfigurationGenerator } =
|
||||||
typeof import('@nx/vite')
|
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||||
>('@nx/vite', nxVersion);
|
|
||||||
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
|
// 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
|
// See: https://vitejs.dev/guide/env-and-mode.html
|
||||||
if (
|
if (
|
||||||
@ -138,6 +137,28 @@ export async function applicationGeneratorInternal(
|
|||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
tasks.push(viteTask);
|
tasks.push(viteTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
host,
|
||||||
|
{
|
||||||
|
project: options.projectName,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [
|
||||||
|
options.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
} else if (options.bundler === 'webpack') {
|
} else if (options.bundler === 'webpack') {
|
||||||
const { webpackInitGenerator } = ensurePackage<
|
const { webpackInitGenerator } = ensurePackage<
|
||||||
typeof import('@nx/webpack')
|
typeof import('@nx/webpack')
|
||||||
@ -167,10 +188,9 @@ export async function applicationGeneratorInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||||
const { vitestGenerator } = ensurePackage<typeof import('@nx/vite')>(
|
const { createOrEditViteConfig, vitestGenerator } = ensurePackage<
|
||||||
'@nx/vite',
|
typeof import('@nx/vite')
|
||||||
nxVersion
|
>('@nx/vite', nxVersion);
|
||||||
);
|
|
||||||
|
|
||||||
const vitestTask = await vitestGenerator(host, {
|
const vitestTask = await vitestGenerator(host, {
|
||||||
uiFramework: 'react',
|
uiFramework: 'react',
|
||||||
@ -180,6 +200,28 @@ export async function applicationGeneratorInternal(
|
|||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
tasks.push(vitestTask);
|
tasks.push(vitestTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
host,
|
||||||
|
{
|
||||||
|
project: options.projectName,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [
|
||||||
|
options.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -80,6 +80,7 @@ describe('lib', () => {
|
|||||||
'vitest/importMeta',
|
'vitest/importMeta',
|
||||||
'vite/client',
|
'vite/client',
|
||||||
'node',
|
'node',
|
||||||
|
'vitest',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -69,9 +69,8 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
|
|
||||||
// Set up build target
|
// Set up build target
|
||||||
if (options.buildable && options.bundler === 'vite') {
|
if (options.buildable && options.bundler === 'vite') {
|
||||||
const { viteConfigurationGenerator } = ensurePackage<
|
const { viteConfigurationGenerator, createOrEditViteConfig } =
|
||||||
typeof import('@nx/vite')
|
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||||
>('@nx/vite', nxVersion);
|
|
||||||
const viteTask = await viteConfigurationGenerator(host, {
|
const viteTask = await viteConfigurationGenerator(host, {
|
||||||
uiFramework: 'react',
|
uiFramework: 'react',
|
||||||
project: options.name,
|
project: options.name,
|
||||||
@ -84,6 +83,28 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
testEnvironment: 'jsdom',
|
testEnvironment: 'jsdom',
|
||||||
});
|
});
|
||||||
tasks.push(viteTask);
|
tasks.push(viteTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
host,
|
||||||
|
{
|
||||||
|
project: options.name,
|
||||||
|
includeLib: true,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [
|
||||||
|
options.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
} else if (options.buildable && options.bundler === 'rollup') {
|
} else if (options.buildable && options.bundler === 'rollup') {
|
||||||
const rollupTask = await addRollupBuildTarget(host, options);
|
const rollupTask = await addRollupBuildTarget(host, options);
|
||||||
tasks.push(rollupTask);
|
tasks.push(rollupTask);
|
||||||
@ -120,10 +141,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
options.unitTestRunner === 'vitest' &&
|
options.unitTestRunner === 'vitest' &&
|
||||||
options.bundler !== 'vite' // tests are already configured if bundler is vite
|
options.bundler !== 'vite' // tests are already configured if bundler is vite
|
||||||
) {
|
) {
|
||||||
const { vitestGenerator } = ensurePackage<typeof import('@nx/vite')>(
|
const { vitestGenerator, createOrEditViteConfig } = ensurePackage<
|
||||||
'@nx/vite',
|
typeof import('@nx/vite')
|
||||||
nxVersion
|
>('@nx/vite', nxVersion);
|
||||||
);
|
|
||||||
const vitestTask = await vitestGenerator(host, {
|
const vitestTask = await vitestGenerator(host, {
|
||||||
uiFramework: 'react',
|
uiFramework: 'react',
|
||||||
project: options.name,
|
project: options.name,
|
||||||
@ -133,6 +153,24 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
testEnvironment: 'jsdom',
|
testEnvironment: 'jsdom',
|
||||||
});
|
});
|
||||||
tasks.push(vitestTask);
|
tasks.push(vitestTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
host,
|
||||||
|
{
|
||||||
|
project: options.name,
|
||||||
|
includeLib: true,
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [`import react from '@vitejs/plugin-react'`],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.component) {
|
if (options.component) {
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`@nx/vite:configuration library mode should add config for building library 1`] = `
|
exports[`@nx/vite:configuration library mode should add config for building library 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
||||||
import dts from 'vite-plugin-dts';
|
import dts from 'vite-plugin-dts';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
cacheDir: '../node_modules/.vite/my-lib',
|
cacheDir: '../node_modules/.vite/my-lib',
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
react(),
|
||||||
|
nxViteTsPaths(),
|
||||||
dts({
|
dts({
|
||||||
entryRoot: 'src',
|
entryRoot: 'src',
|
||||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||||
skipDiagnostics: true,
|
skipDiagnostics: true,
|
||||||
}),
|
}),
|
||||||
react(),
|
|
||||||
nxViteTsPaths(),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// Uncomment this if you are using workers.
|
// Uncomment this if you are using workers.
|
||||||
@ -48,24 +48,24 @@ export default defineConfig({
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`@nx/vite:configuration library mode should set up non buildable library correctly 1`] = `
|
exports[`@nx/vite:configuration library mode should set up non buildable library correctly 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
||||||
import dts from 'vite-plugin-dts';
|
import dts from 'vite-plugin-dts';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
cacheDir: '../../node_modules/.vite/react-lib-nonb-jest',
|
cacheDir: '../../node_modules/.vite/react-lib-nonb-jest',
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
react(),
|
||||||
|
nxViteTsPaths(),
|
||||||
dts({
|
dts({
|
||||||
entryRoot: 'src',
|
entryRoot: 'src',
|
||||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||||
skipDiagnostics: true,
|
skipDiagnostics: true,
|
||||||
}),
|
}),
|
||||||
react(),
|
|
||||||
nxViteTsPaths(),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// Uncomment this if you are using workers.
|
// Uncomment this if you are using workers.
|
||||||
@ -143,7 +143,7 @@ exports[`@nx/vite:configuration library mode should set up non buildable library
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Configuration for building your library.
|
// Configuration for building your library.
|
||||||
@ -165,12 +165,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
dts({
|
||||||
entryRoot: 'src',
|
entryRoot: 'src',
|
||||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||||
@ -180,9 +176,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
cache: {
|
cache: { dir: '../../node_modules/.vitest' },
|
||||||
dir: '../../node_modules/.vitest',
|
|
||||||
},
|
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
},
|
},
|
||||||
@ -286,7 +280,7 @@ exports[`@nx/vite:configuration transform React app to use Vite by providing cus
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`@nx/vite:configuration transform React app to use Vite should create vite.config file at the root of the app 1`] = `
|
exports[`@nx/vite:configuration transform React app to use Vite should create vite.config file at the root of the app 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
@ -427,7 +421,7 @@ exports[`@nx/vite:configuration transform React app to use Vite should transform
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`@nx/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = `
|
exports[`@nx/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
@ -555,7 +549,7 @@ exports[`@nx/vite:configuration transform Web app to use Vite should transform w
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`@nx/vite:configuration vitest should create a vitest configuration if "includeVitest" is true 1`] = `
|
exports[`@nx/vite:configuration vitest should create a vitest configuration if "includeVitest" is true 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|||||||
@ -350,7 +350,6 @@ describe('@nx/vite:configuration', () => {
|
|||||||
const { Confirm } = require('enquirer');
|
const { Confirm } = require('enquirer');
|
||||||
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
|
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
|
||||||
confirmSpy.mockResolvedValue(true);
|
confirmSpy.mockResolvedValue(true);
|
||||||
expect.assertions(2);
|
|
||||||
|
|
||||||
mockReactLibNonBuildableVitestRunnerGenerator(tree);
|
mockReactLibNonBuildableVitestRunnerGenerator(tree);
|
||||||
|
|
||||||
|
|||||||
@ -198,7 +198,32 @@ export async function viteConfigurationGenerator(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schema.uiFramework === 'react') {
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: schema.project,
|
||||||
|
includeLib: schema.includeLib,
|
||||||
|
includeVitest: schema.includeVitest,
|
||||||
|
inSourceTests: schema.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [
|
||||||
|
schema.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
createOrEditViteConfig(tree, schema, false, projectAlreadyHasViteTargets);
|
createOrEditViteConfig(tree, schema, false, projectAlreadyHasViteTargets);
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.includeVitest) {
|
if (schema.includeVitest) {
|
||||||
const vitestTask = await vitestGenerator(tree, {
|
const vitestTask = await vitestGenerator(tree, {
|
||||||
|
|||||||
@ -45,7 +45,7 @@ function checkDependenciesInstalled(host: Tree, schema: InitGeneratorSchema) {
|
|||||||
devDependencies['happy-dom'] = happyDomVersion;
|
devDependencies['happy-dom'] = happyDomVersion;
|
||||||
} else if (schema.testEnvironment === 'edge-runtime') {
|
} else if (schema.testEnvironment === 'edge-runtime') {
|
||||||
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
|
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
|
||||||
} else if (schema.testEnvironment !== 'node') {
|
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
|
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`vitest generator insourceTests should add the insourceSource option in the vite config 1`] = `
|
exports[`vitest generator insourceTests should add the insourceSource option in the vite config 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
@ -33,7 +33,7 @@ export default defineConfig({
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`vitest generator vite.config should create correct vite.config.ts file for apps 1`] = `
|
exports[`vitest generator vite.config should create correct vite.config.ts file for apps 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
@ -61,7 +61,7 @@ export default defineConfig({
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`vitest generator vite.config should create correct vite.config.ts file for non buildable libs 1`] = `
|
exports[`vitest generator vite.config should create correct vite.config.ts file for non buildable libs 1`] = `
|
||||||
"/// <reference types="vitest" />
|
"/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|||||||
@ -52,6 +52,26 @@ export async function vitestGenerator(
|
|||||||
tasks.push(initTask);
|
tasks.push(initTask);
|
||||||
|
|
||||||
if (!schema.skipViteConfig) {
|
if (!schema.skipViteConfig) {
|
||||||
|
if (schema.uiFramework === 'react') {
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: schema.project,
|
||||||
|
includeLib: projectType === 'library',
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: schema.inSourceTests,
|
||||||
|
rollupOptionsExternal: [
|
||||||
|
`'react'`,
|
||||||
|
`'react-dom'`,
|
||||||
|
`'react/jsx-runtime'`,
|
||||||
|
],
|
||||||
|
rollupOptionsExternalString: `"'react', 'react-dom', 'react/jsx-runtime'"`,
|
||||||
|
imports: [`import react from '@vitejs/plugin-react'`],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
} else {
|
||||||
createOrEditViteConfig(
|
createOrEditViteConfig(
|
||||||
tree,
|
tree,
|
||||||
{
|
{
|
||||||
@ -62,6 +82,7 @@ export async function vitestGenerator(
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createFiles(tree, schema, root);
|
createFiles(tree, schema, root);
|
||||||
updateTsConfig(tree, schema, root);
|
updateTsConfig(tree, schema, root);
|
||||||
@ -89,16 +110,11 @@ function updateTsConfig(
|
|||||||
options: VitestGeneratorSchema,
|
options: VitestGeneratorSchema,
|
||||||
projectRoot: string
|
projectRoot: string
|
||||||
) {
|
) {
|
||||||
updateJson(tree, joinPathFragments(projectRoot, 'tsconfig.json'), (json) => {
|
if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.spec.json'))) {
|
||||||
if (
|
updateJson(
|
||||||
json.references &&
|
tree,
|
||||||
!json.references.some((r) => r.path === './tsconfig.spec.json')
|
joinPathFragments(projectRoot, 'tsconfig.spec.json'),
|
||||||
) {
|
(json) => {
|
||||||
json.references.push({
|
|
||||||
path: './tsconfig.spec.json',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!json.compilerOptions?.types?.includes('vitest')) {
|
if (!json.compilerOptions?.types?.includes('vitest')) {
|
||||||
if (json.compilerOptions?.types) {
|
if (json.compilerOptions?.types) {
|
||||||
json.compilerOptions.types.push('vitest');
|
json.compilerOptions.types.push('vitest');
|
||||||
@ -108,7 +124,41 @@ function updateTsConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
updateJson(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectRoot, 'tsconfig.json'),
|
||||||
|
(json) => {
|
||||||
|
if (
|
||||||
|
json.references &&
|
||||||
|
!json.references.some((r) => r.path === './tsconfig.spec.json')
|
||||||
|
) {
|
||||||
|
json.references.push({
|
||||||
|
path: './tsconfig.spec.json',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updateJson(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectRoot, 'tsconfig.json'),
|
||||||
|
(json) => {
|
||||||
|
if (!json.compilerOptions?.types?.includes('vitest')) {
|
||||||
|
if (json.compilerOptions?.types) {
|
||||||
|
json.compilerOptions.types.push('vitest');
|
||||||
|
} else {
|
||||||
|
json.compilerOptions ??= {};
|
||||||
|
json.compilerOptions.types = ['vitest'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.inSourceTests) {
|
if (options.inSourceTests) {
|
||||||
const tsconfigLibPath = joinPathFragments(projectRoot, 'tsconfig.lib.json');
|
const tsconfigLibPath = joinPathFragments(projectRoot, 'tsconfig.lib.json');
|
||||||
|
|||||||
@ -96,6 +96,7 @@ describe('vitest generator', () => {
|
|||||||
"vitest/importMeta",
|
"vitest/importMeta",
|
||||||
"vite/client",
|
"vite/client",
|
||||||
"node",
|
"node",
|
||||||
|
"vitest",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should add build and test options if defineConfig is empty 1`] = `
|
exports[`ensureViteConfigIsCorrect should add build and test options if defineConfig is empty 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
|
|
||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
||||||
// Configuration for building your library.
|
// Configuration for building your library.
|
||||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||||
build: {
|
plugins: [react(),
|
||||||
|
nxViteTsPaths()],build: {
|
||||||
lib: {
|
lib: {
|
||||||
// Could also be a dictionary or array of multiple entry points.
|
// Could also be a dictionary or array of multiple entry points.
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
@ -27,18 +26,7 @@ import { joinPathFragments } from '@nx/devkit';
|
|||||||
// External packages that should not be bundled into your library.
|
// External packages that should not be bundled into your library.
|
||||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||||
}
|
}
|
||||||
},plugins: [
|
},test: {
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
react(),
|
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
test: {
|
|
||||||
globals: true,
|
globals: true,
|
||||||
cache: {
|
cache: {
|
||||||
dir: '../node_modules/.vitest'
|
dir: '../node_modules/.vitest'
|
||||||
@ -50,11 +38,10 @@ import { joinPathFragments } from '@nx/devkit';
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should add build option but not update test option if test already setup 1`] = `
|
exports[`ensureViteConfigIsCorrect should add build option but not update test option if test already setup 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
||||||
@ -74,19 +61,9 @@ import { defineConfig } from 'vite';
|
|||||||
// External packages that should not be bundled into your library.
|
// External packages that should not be bundled into your library.
|
||||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||||
}
|
}
|
||||||
},plugins: [
|
},plugins: [react(),
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
],
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
@ -102,11 +79,10 @@ import { defineConfig } from 'vite';
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should add build options if build options don't exist 1`] = `
|
exports[`ensureViteConfigIsCorrect should add build options if build options don't exist 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
||||||
@ -126,19 +102,9 @@ import { defineConfig } from 'vite';
|
|||||||
// External packages that should not be bundled into your library.
|
// External packages that should not be bundled into your library.
|
||||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||||
}
|
}
|
||||||
},plugins: [
|
},plugins: [react(),
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
],
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
@ -154,11 +120,10 @@ import { defineConfig } from 'vite';
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should add build options if defineConfig is not used 1`] = `
|
exports[`ensureViteConfigIsCorrect should add build options if defineConfig is not used 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Configuration for building your library.
|
// Configuration for building your library.
|
||||||
@ -185,19 +150,9 @@ import { defineConfig } from 'vite';
|
|||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [react(),
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
],
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@ -226,26 +181,15 @@ exports[`ensureViteConfigIsCorrect should add build options if it is using condi
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should add new build options if some build options already exist 1`] = `
|
exports[`ensureViteConfigIsCorrect should add new build options if some build options already exist 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [react(),
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
],
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
@ -257,10 +201,10 @@ import { defineConfig } from 'vite';
|
|||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
...{
|
'my': 'option',
|
||||||
my: 'option',
|
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
|
||||||
},
|
'rollupOptions': {"external":["'react', 'react-dom', 'react/jsx-runtime'"]},
|
||||||
...{"lib":{"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},"rollupOptions":{"external":["'react', 'react-dom', 'react/jsx-runtime'"]}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -271,25 +215,17 @@ exports[`ensureViteConfigIsCorrect should not do anything if cannot understand s
|
|||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should not do anything if project has everything setup already 1`] = `
|
exports[`ensureViteConfigIsCorrect should not do anything if project has everything setup already 1`] = `
|
||||||
"
|
"
|
||||||
/// <reference types="vitest" />
|
import { defineConfig } from 'vite';
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
import dts from 'vite-plugin-dts';
|
import dts from 'vite-plugin-dts';
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
import { joinPathFragments } from '@nx/devkit';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }),
|
||||||
dts({
|
react(),
|
||||||
entryRoot: 'src',
|
nxViteTsPaths(),
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
],
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
react(),
|
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Configuration for building your library.
|
// Configuration for building your library.
|
||||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||||
@ -322,39 +258,30 @@ exports[`ensureViteConfigIsCorrect should not do anything if project has everyth
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ensureViteConfigIsCorrect should update both test and build options - keep existing settings 1`] = `
|
exports[`ensureViteConfigIsCorrect should update both test and build options - keep existing settings 1`] = `
|
||||||
"import dts from 'vite-plugin-dts';
|
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [react(),
|
||||||
...[
|
nxViteTsPaths(),
|
||||||
react(),
|
],
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
...{
|
'my': 'option',
|
||||||
my: 'option',
|
'globals': true,
|
||||||
},
|
'cache': {"dir":"../node_modules/.vitest"},
|
||||||
...{"globals":true,"cache":{"dir":"../node_modules/.vitest"},"environment":"jsdom","include":["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"]}
|
'environment': "jsdom",
|
||||||
|
'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
...{
|
'my': 'option',
|
||||||
my: 'option',
|
'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},
|
||||||
},
|
'rollupOptions': {"external":["'react', 'react-dom', 'react/jsx-runtime'"]},
|
||||||
...{"lib":{"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},"rollupOptions":{"external":["'react', 'react-dom', 'react/jsx-runtime'"]}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -441,14 +441,14 @@ export function moveAndEditIndexHtml(
|
|||||||
const indexHtmlContent = tree.read(indexHtmlPath, 'utf8');
|
const indexHtmlContent = tree.read(indexHtmlPath, 'utf8');
|
||||||
if (
|
if (
|
||||||
!indexHtmlContent.includes(
|
!indexHtmlContent.includes(
|
||||||
`<script type="module" src="${mainPath}"></script>`
|
`<script type='module' src='${mainPath}'></script>`
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
tree.write(
|
tree.write(
|
||||||
`${projectConfig.root}/index.html`,
|
`${projectConfig.root}/index.html`,
|
||||||
indexHtmlContent.replace(
|
indexHtmlContent.replace(
|
||||||
'</body>',
|
'</body>',
|
||||||
`<script type="module" src="${mainPath}"></script>
|
`<script type='module' src='${mainPath}'></script>
|
||||||
</body>`
|
</body>`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -461,25 +461,37 @@ export function moveAndEditIndexHtml(
|
|||||||
tree.write(
|
tree.write(
|
||||||
`${projectConfig.root}/index.html`,
|
`${projectConfig.root}/index.html`,
|
||||||
`<!DOCTYPE html>
|
`<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang='en'>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset='UTF-8' />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel='icon' href='/favicon.ico' />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||||
<title>Vite</title>
|
<title>Vite</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id='root'></div>
|
||||||
<script type="module" src="${mainPath}"></script>
|
<script type='module' src='${mainPath}'></script>
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ViteConfigFileOptions {
|
||||||
|
project: string;
|
||||||
|
includeLib?: boolean;
|
||||||
|
includeVitest?: boolean;
|
||||||
|
inSourceTests?: boolean;
|
||||||
|
testEnvironment?: 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' | string;
|
||||||
|
rollupOptionsExternalString?: string;
|
||||||
|
rollupOptionsExternal?: string[];
|
||||||
|
imports?: string[];
|
||||||
|
plugins?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export function createOrEditViteConfig(
|
export function createOrEditViteConfig(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: ViteConfigurationGeneratorSchema,
|
options: ViteConfigFileOptions,
|
||||||
onlyVitest: boolean,
|
onlyVitest: boolean,
|
||||||
projectAlreadyHasViteTargets?: TargetFlags
|
projectAlreadyHasViteTargets?: TargetFlags
|
||||||
) {
|
) {
|
||||||
@ -505,33 +517,32 @@ export function createOrEditViteConfig(
|
|||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
// External packages that should not be bundled into your library.
|
// External packages that should not be bundled into your library.
|
||||||
external: [${
|
external: [${options.rollupOptionsExternal ?? ''}]
|
||||||
options.uiFramework === 'react'
|
|
||||||
? "'react', 'react-dom', 'react/jsx-runtime'"
|
|
||||||
: ''
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
},`
|
},`
|
||||||
: ``;
|
: ``;
|
||||||
|
|
||||||
const dtsPlugin = onlyVitest
|
const imports: string[] = options.imports ? options.imports : [];
|
||||||
? ''
|
|
||||||
: options.includeLib
|
|
||||||
? `dts({
|
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const dtsImportLine = onlyVitest
|
if (!onlyVitest && options.includeLib) {
|
||||||
? ''
|
imports.push(
|
||||||
: options.includeLib
|
`import dts from 'vite-plugin-dts'`,
|
||||||
? `import dts from 'vite-plugin-dts';\nimport * as path from 'path';`
|
`import * as path from 'path'`
|
||||||
: '';
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let viteConfigContent = '';
|
let viteConfigContent = '';
|
||||||
|
|
||||||
|
const plugins = options.plugins
|
||||||
|
? [...options.plugins, `nxViteTsPaths()`]
|
||||||
|
: [`nxViteTsPaths()`];
|
||||||
|
|
||||||
|
if (!onlyVitest && options.includeLib) {
|
||||||
|
plugins.push(
|
||||||
|
`dts({ entryRoot: 'src', tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true })`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const testOption = options.includeVitest
|
const testOption = options.includeVitest
|
||||||
? `test: {
|
? `test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
@ -554,15 +565,6 @@ export function createOrEditViteConfig(
|
|||||||
},`
|
},`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const reactPluginImportLine =
|
|
||||||
options.uiFramework === 'react'
|
|
||||||
? options.compiler === 'swc'
|
|
||||||
? `import react from '@vitejs/plugin-react-swc';`
|
|
||||||
: `import react from '@vitejs/plugin-react';`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const reactPlugin = options.uiFramework === 'react' ? `react(),` : '';
|
|
||||||
|
|
||||||
const devServerOption = onlyVitest
|
const devServerOption = onlyVitest
|
||||||
? ''
|
? ''
|
||||||
: options.includeLib
|
: options.includeLib
|
||||||
@ -583,14 +585,6 @@ export function createOrEditViteConfig(
|
|||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},`;
|
},`;
|
||||||
|
|
||||||
const pluginOption = `
|
|
||||||
plugins: [
|
|
||||||
${dtsPlugin}
|
|
||||||
${reactPlugin}
|
|
||||||
nxViteTsPaths(),
|
|
||||||
],
|
|
||||||
`;
|
|
||||||
|
|
||||||
const workerOption = `
|
const workerOption = `
|
||||||
// Uncomment this if you are using workers.
|
// Uncomment this if you are using workers.
|
||||||
// worker: {
|
// worker: {
|
||||||
@ -607,9 +601,8 @@ export function createOrEditViteConfig(
|
|||||||
viteConfigPath,
|
viteConfigPath,
|
||||||
options,
|
options,
|
||||||
buildOption,
|
buildOption,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
cacheDir,
|
cacheDir,
|
||||||
offsetFromRoot(projectConfig.root),
|
offsetFromRoot(projectConfig.root),
|
||||||
@ -619,17 +612,17 @@ export function createOrEditViteConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
viteConfigContent = `
|
viteConfigContent = `
|
||||||
/// <reference types="vitest" />
|
/// <reference types='vitest' />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
${reactPluginImportLine}
|
${imports.join(';\n')}${imports.length ? ';' : ''}
|
||||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
${dtsImportLine}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
${cacheDir}
|
${cacheDir}
|
||||||
${devServerOption}
|
${devServerOption}
|
||||||
${previewServerOption}
|
${previewServerOption}
|
||||||
${pluginOption}
|
|
||||||
|
plugins: [${plugins.join(',\n')}],
|
||||||
${workerOption}
|
${workerOption}
|
||||||
${buildOption}
|
${buildOption}
|
||||||
${defineOption}
|
${defineOption}
|
||||||
@ -774,21 +767,28 @@ export async function handleUnknownExecutors(projectName: string) {
|
|||||||
function handleViteConfigFileExists(
|
function handleViteConfigFileExists(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
viteConfigPath: string,
|
viteConfigPath: string,
|
||||||
options: ViteConfigurationGeneratorSchema,
|
options: ViteConfigFileOptions,
|
||||||
buildOption: string,
|
buildOption: string,
|
||||||
dtsPlugin: string,
|
imports: string[],
|
||||||
dtsImportLine: string,
|
plugins: string[],
|
||||||
pluginOption: string,
|
|
||||||
testOption: string,
|
testOption: string,
|
||||||
cacheDir: string,
|
cacheDir: string,
|
||||||
offsetFromRoot: string,
|
offsetFromRoot: string,
|
||||||
projectAlreadyHasViteTargets?: TargetFlags
|
projectAlreadyHasViteTargets?: TargetFlags
|
||||||
) {
|
) {
|
||||||
if (projectAlreadyHasViteTargets.build && projectAlreadyHasViteTargets.test) {
|
if (
|
||||||
|
projectAlreadyHasViteTargets?.build &&
|
||||||
|
projectAlreadyHasViteTargets?.test
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`vite.config.ts already exists for project ${options.project}.`);
|
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||||
|
logger.info(
|
||||||
|
`vite.config.ts already exists for project ${options.project}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const buildOptionObject = {
|
const buildOptionObject = {
|
||||||
lib: {
|
lib: {
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
@ -797,10 +797,7 @@ function handleViteConfigFileExists(
|
|||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external:
|
external: options.rollupOptionsExternal ?? [],
|
||||||
options.uiFramework === 'react'
|
|
||||||
? ['react', 'react-dom', 'react/jsx-runtime']
|
|
||||||
: [],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -818,13 +815,12 @@ function handleViteConfigFileExists(
|
|||||||
viteConfigPath,
|
viteConfigPath,
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
cacheDir,
|
cacheDir,
|
||||||
projectAlreadyHasViteTargets
|
projectAlreadyHasViteTargets ?? {}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
@ -835,9 +831,5 @@ function handleViteConfigFileExists(
|
|||||||
|
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
logger.info(`
|
|
||||||
Vite configuration file (${viteConfigPath}) has been updated with the required settings for the new target(s).
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,12 @@ export const noBuildOptions = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
@ -28,14 +26,12 @@ export const someBuildOptions = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
@ -58,7 +54,7 @@ export const noContentDefineConfig = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({});
|
export default defineConfig({});
|
||||||
`;
|
`;
|
||||||
@ -85,14 +81,12 @@ export const configNoDefineConfig = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
@ -101,14 +95,12 @@ export const noBuildOptionsHasTestOption = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
@ -127,14 +119,12 @@ export const someBuildOptionsSomeTestOption = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
@ -152,21 +142,15 @@ export const hasEverything = `
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
import dts from 'vite-plugin-dts';
|
import dts from 'vite-plugin-dts';
|
||||||
import { joinPathFragments } from '@nx/devkit';
|
import { joinPathFragments } from '@nx/devkit';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
dts({
|
dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }),
|
||||||
entryRoot: 'src',
|
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
|
||||||
skipDiagnostics: true,
|
|
||||||
}),
|
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
nxViteTsPaths(),
|
||||||
root: '../../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// Configuration for building your library.
|
// Configuration for building your library.
|
||||||
@ -246,19 +230,9 @@ export const testOptionObject = {
|
|||||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dtsPlugin = `dts({
|
export const imports = [
|
||||||
entryRoot: 'src',
|
`import dts from 'vite-plugin-dts'`,
|
||||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
`import { joinPathFragments } from '@nx/devkit'`,
|
||||||
skipDiagnostics: true,
|
];
|
||||||
}),`;
|
|
||||||
export const dtsImportLine = `import dts from 'vite-plugin-dts';\nimport { joinPathFragments } from '@nx/devkit';`;
|
|
||||||
|
|
||||||
export const pluginOption = `
|
export const plugins = [`react()`, `nxViteTsPaths()`];
|
||||||
plugins: [
|
|
||||||
${dtsPlugin}
|
|
||||||
react(),
|
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
`;
|
|
||||||
|
|||||||
@ -537,15 +537,13 @@ export function mockReactLibNonBuildableVitestRunnerGenerator(
|
|||||||
`/// <reference types="vitest" />
|
`/// <reference types="vitest" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
nxViteTsPaths(),
|
||||||
react(),
|
react(),
|
||||||
viteTsConfigPaths({
|
|
||||||
root: '../../',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
|
|||||||
@ -6,13 +6,12 @@ import {
|
|||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
conditionalConfig,
|
conditionalConfig,
|
||||||
configNoDefineConfig,
|
configNoDefineConfig,
|
||||||
dtsImportLine,
|
imports,
|
||||||
dtsPlugin,
|
|
||||||
hasEverything,
|
hasEverything,
|
||||||
noBuildOptions,
|
noBuildOptions,
|
||||||
noBuildOptionsHasTestOption,
|
noBuildOptionsHasTestOption,
|
||||||
noContentDefineConfig,
|
noContentDefineConfig,
|
||||||
pluginOption,
|
plugins,
|
||||||
someBuildOptions,
|
someBuildOptions,
|
||||||
someBuildOptionsSomeTestOption,
|
someBuildOptionsSomeTestOption,
|
||||||
testOption,
|
testOption,
|
||||||
@ -34,9 +33,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -59,9 +57,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -84,9 +81,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -109,9 +105,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -134,9 +129,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -159,9 +153,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -178,9 +171,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -197,9 +189,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
@ -216,9 +207,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
|||||||
'apps/my-app/vite.config.ts',
|
'apps/my-app/vite.config.ts',
|
||||||
buildOption,
|
buildOption,
|
||||||
buildOptionObject,
|
buildOptionObject,
|
||||||
dtsPlugin,
|
imports,
|
||||||
dtsImportLine,
|
plugins,
|
||||||
pluginOption,
|
|
||||||
testOption,
|
testOption,
|
||||||
testOptionObject,
|
testOptionObject,
|
||||||
'',
|
'',
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
import { applyChangesToString, ChangeType, Tree } from '@nx/devkit';
|
import { applyChangesToString, ChangeType, Tree } from '@nx/devkit';
|
||||||
import { findNodes } from '@nx/js';
|
import { findNodes } from '@nx/js';
|
||||||
import { TargetFlags } from './generator-utils';
|
import { TargetFlags } from './generator-utils';
|
||||||
import type { Node, ReturnStatement } from 'typescript';
|
import type {
|
||||||
|
ArrayLiteralExpression,
|
||||||
|
Node,
|
||||||
|
PropertyAssignment,
|
||||||
|
ReturnStatement,
|
||||||
|
} from 'typescript';
|
||||||
|
|
||||||
export function ensureViteConfigIsCorrect(
|
export function ensureViteConfigIsCorrect(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
path: string,
|
path: string,
|
||||||
buildConfigString: string,
|
buildConfigString: string,
|
||||||
buildConfigObject: {},
|
buildConfigObject: {},
|
||||||
dtsPlugin: string,
|
imports: string[],
|
||||||
dtsImportLine: string,
|
plugins: string[],
|
||||||
pluginOption: string,
|
|
||||||
testConfigString: string,
|
testConfigString: string,
|
||||||
testConfigObject: {},
|
testConfigObject: {},
|
||||||
cacheDir: string,
|
cacheDir: string,
|
||||||
@ -30,13 +34,6 @@ export function ensureViteConfigIsCorrect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!projectAlreadyHasViteTargets?.build && buildConfigString?.length) {
|
if (!projectAlreadyHasViteTargets?.build && buildConfigString?.length) {
|
||||||
updatedContent = handlePluginNode(
|
|
||||||
updatedContent ?? fileContent,
|
|
||||||
dtsPlugin,
|
|
||||||
dtsImportLine,
|
|
||||||
pluginOption
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedContent = handleBuildOrTestNode(
|
updatedContent = handleBuildOrTestNode(
|
||||||
updatedContent ?? fileContent,
|
updatedContent ?? fileContent,
|
||||||
buildConfigString,
|
buildConfigString,
|
||||||
@ -45,12 +42,17 @@ export function ensureViteConfigIsCorrect(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedContent =
|
||||||
|
handlePluginNode(updatedContent ?? fileContent, imports, plugins) ??
|
||||||
|
updatedContent;
|
||||||
|
|
||||||
if (cacheDir?.length) {
|
if (cacheDir?.length) {
|
||||||
updatedContent = handleCacheDirNode(
|
updatedContent = handleCacheDirNode(
|
||||||
updatedContent ?? fileContent,
|
updatedContent ?? fileContent,
|
||||||
cacheDir
|
cacheDir
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedContent) {
|
if (updatedContent) {
|
||||||
tree.write(path, updatedContent);
|
tree.write(path, updatedContent);
|
||||||
return true;
|
return true;
|
||||||
@ -66,20 +68,36 @@ function handleBuildOrTestNode(
|
|||||||
name: 'build' | 'test'
|
name: 'build' | 'test'
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||||
const buildNode = tsquery.query(
|
const buildOrTestNode = tsquery.query(
|
||||||
updatedFileContent,
|
updatedFileContent,
|
||||||
`PropertyAssignment:has(Identifier[name="${name}"])`
|
`PropertyAssignment:has(Identifier[name="${name}"])`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (buildNode.length) {
|
if (buildOrTestNode.length) {
|
||||||
return tsquery.replace(
|
return tsquery.replace(
|
||||||
updatedFileContent,
|
updatedFileContent,
|
||||||
`PropertyAssignment:has(Identifier[name="${name}"])`,
|
`PropertyAssignment:has(Identifier[name="${name}"])`,
|
||||||
(node: Node) => {
|
(node: PropertyAssignment) => {
|
||||||
const found = tsquery.query(node, 'ObjectLiteralExpression');
|
const existingProperties = tsquery.query(
|
||||||
|
node.initializer,
|
||||||
|
'PropertyAssignment'
|
||||||
|
) as PropertyAssignment[];
|
||||||
|
let updatedPropsString = '';
|
||||||
|
for (const prop of existingProperties) {
|
||||||
|
const propName = prop.name.getText();
|
||||||
|
if (!configContentObject[propName] && propName !== 'dir') {
|
||||||
|
updatedPropsString += `'${propName}': ${prop.initializer.getText()},\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [propName, propValue] of Object.entries(
|
||||||
|
configContentObject
|
||||||
|
)) {
|
||||||
|
updatedPropsString += `'${propName}': ${JSON.stringify(
|
||||||
|
propValue
|
||||||
|
)},\n`;
|
||||||
|
}
|
||||||
return `${name}: {
|
return `${name}: {
|
||||||
...${found?.[0].getText()},
|
${updatedPropsString}
|
||||||
...${JSON.stringify(configContentObject)}
|
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -173,7 +191,6 @@ function transformCurrentBuildObject(
|
|||||||
|
|
||||||
const currentBuildObjectStart = returnStatements[index].getStart();
|
const currentBuildObjectStart = returnStatements[index].getStart();
|
||||||
const currentBuildObjectEnd = returnStatements[index].getEnd();
|
const currentBuildObjectEnd = returnStatements[index].getEnd();
|
||||||
|
|
||||||
const newReturnObject = tsquery.replace(
|
const newReturnObject = tsquery.replace(
|
||||||
returnStatements[index].getText(),
|
returnStatements[index].getText(),
|
||||||
'ObjectLiteralExpression',
|
'ObjectLiteralExpression',
|
||||||
@ -209,7 +226,6 @@ function transformConditionalConfig(
|
|||||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||||
const { SyntaxKind } = require('typescript');
|
const { SyntaxKind } = require('typescript');
|
||||||
const functionBlock = tsquery.query(conditionalConfig[0], 'Block');
|
const functionBlock = tsquery.query(conditionalConfig[0], 'Block');
|
||||||
|
|
||||||
const ifStatement = tsquery.query(functionBlock?.[0], 'IfStatement');
|
const ifStatement = tsquery.query(functionBlock?.[0], 'IfStatement');
|
||||||
|
|
||||||
const binaryExpressions = tsquery.query(ifStatement?.[0], 'BinaryExpression');
|
const binaryExpressions = tsquery.query(ifStatement?.[0], 'BinaryExpression');
|
||||||
@ -235,7 +251,6 @@ function transformConditionalConfig(
|
|||||||
if (!buildExists) {
|
if (!buildExists) {
|
||||||
if (serveExists && elseKeywordExists) {
|
if (serveExists && elseKeywordExists) {
|
||||||
// build options live inside the else block
|
// build options live inside the else block
|
||||||
|
|
||||||
return (
|
return (
|
||||||
transformCurrentBuildObject(
|
transformCurrentBuildObject(
|
||||||
returnStatements?.length - 1,
|
returnStatements?.length - 1,
|
||||||
@ -278,12 +293,10 @@ function transformConditionalConfig(
|
|||||||
|
|
||||||
function handlePluginNode(
|
function handlePluginNode(
|
||||||
appFileContent: string,
|
appFileContent: string,
|
||||||
dtsPlugin: string,
|
imports: string[],
|
||||||
dtsImportLine: string,
|
plugins: string[]
|
||||||
pluginOption: string
|
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||||
|
|
||||||
const file = tsquery.ast(appFileContent);
|
const file = tsquery.ast(appFileContent);
|
||||||
const pluginsNode = tsquery.query(
|
const pluginsNode = tsquery.query(
|
||||||
file,
|
file,
|
||||||
@ -297,11 +310,29 @@ function handlePluginNode(
|
|||||||
file.getText(),
|
file.getText(),
|
||||||
'PropertyAssignment:has(Identifier[name="plugins"])',
|
'PropertyAssignment:has(Identifier[name="plugins"])',
|
||||||
(node: Node) => {
|
(node: Node) => {
|
||||||
const found = tsquery.query(node, 'ArrayLiteralExpression');
|
const found = tsquery.query(
|
||||||
return `plugins: [
|
node,
|
||||||
...${found?.[0].getText()},
|
'ArrayLiteralExpression'
|
||||||
${dtsPlugin}
|
) as ArrayLiteralExpression[];
|
||||||
]`;
|
let updatedPluginsString = '';
|
||||||
|
|
||||||
|
const existingPluginNodes = found?.[0].elements ?? [];
|
||||||
|
|
||||||
|
for (const plugin of existingPluginNodes) {
|
||||||
|
updatedPluginsString += `${plugin.getText()},\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
if (
|
||||||
|
!existingPluginNodes?.some((node) =>
|
||||||
|
node.getText().includes(plugin)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
updatedPluginsString += `${plugin},\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `plugins: [${updatedPluginsString}]`;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
writeFile = true;
|
writeFile = true;
|
||||||
@ -335,7 +366,7 @@ function handlePluginNode(
|
|||||||
{
|
{
|
||||||
type: ChangeType.Insert,
|
type: ChangeType.Insert,
|
||||||
index: propertyAssignments[0].getStart(),
|
index: propertyAssignments[0].getStart(),
|
||||||
text: pluginOption,
|
text: `plugins: [${plugins.join(',\n')}],`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
writeFile = true;
|
writeFile = true;
|
||||||
@ -344,7 +375,7 @@ function handlePluginNode(
|
|||||||
{
|
{
|
||||||
type: ChangeType.Insert,
|
type: ChangeType.Insert,
|
||||||
index: foundDefineConfig[0].getStart() + 14,
|
index: foundDefineConfig[0].getStart() + 14,
|
||||||
text: pluginOption,
|
text: `plugins: [${plugins.join(',\n')}],`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
writeFile = true;
|
writeFile = true;
|
||||||
@ -364,7 +395,7 @@ function handlePluginNode(
|
|||||||
{
|
{
|
||||||
type: ChangeType.Insert,
|
type: ChangeType.Insert,
|
||||||
index: startOfObject + 1,
|
index: startOfObject + 1,
|
||||||
text: pluginOption,
|
text: `plugins: [${plugins.join(',\n')}],`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
writeFile = true;
|
writeFile = true;
|
||||||
@ -373,14 +404,27 @@ function handlePluginNode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writeFile) {
|
if (writeFile) {
|
||||||
if (!appFileContent.includes(`import dts from 'vite-plugin-dts'`)) {
|
const filteredImports = filterImport(appFileContent, imports);
|
||||||
return dtsImportLine + '\n' + appFileContent;
|
return filteredImports.join(';') + '\n' + appFileContent;
|
||||||
}
|
}
|
||||||
return appFileContent;
|
}
|
||||||
}
|
|
||||||
return appFileContent;
|
function filterImport(appFileContent: string, imports: string[]): string[] {
|
||||||
|
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||||
|
const file = tsquery.ast(appFileContent);
|
||||||
|
const importNodes = tsquery.query(
|
||||||
|
file,
|
||||||
|
':matches(ImportDeclaration, VariableStatement)'
|
||||||
|
);
|
||||||
|
|
||||||
|
const importsArrayExisting = importNodes?.map((node) => {
|
||||||
|
return node.getText().slice(0, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return imports.filter((importString) => {
|
||||||
|
return !importsArrayExisting?.includes(importString);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCacheDirNode(appFileContent: string, cacheDir: string): string {
|
function handleCacheDirNode(appFileContent: string, cacheDir: string): string {
|
||||||
|
|||||||
@ -29,7 +29,12 @@
|
|||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"buildTargets": ["build-base"],
|
"buildTargets": ["build-base"],
|
||||||
"ignoredDependencies": ["nx", "typescript"]
|
"ignoredDependencies": [
|
||||||
|
"nx",
|
||||||
|
"typescript",
|
||||||
|
"@nx/cypress",
|
||||||
|
"@nx/playwright"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "Nx Vue",
|
"name": "Nx Vue",
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"generators": {}
|
"generators": {
|
||||||
|
"init": {
|
||||||
|
"factory": "./src/generators/init/init",
|
||||||
|
"schema": "./src/generators/init/schema.json",
|
||||||
|
"description": "Initialize the `@nx/vue` plugin.",
|
||||||
|
"aliases": ["ng-add"],
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"factory": "./src/generators/application/application",
|
||||||
|
"schema": "./src/generators/application/schema.json",
|
||||||
|
"aliases": ["app"],
|
||||||
|
"description": "Create a Vue application."
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"factory": "./src/generators/library/library",
|
||||||
|
"schema": "./src/generators/library/schema.json",
|
||||||
|
"aliases": ["lib"],
|
||||||
|
"x-type": "library",
|
||||||
|
"description": "Create a Vue library."
|
||||||
|
},
|
||||||
|
"component": {
|
||||||
|
"factory": "./src/generators/component/component",
|
||||||
|
"schema": "./src/generators/component/schema.json",
|
||||||
|
"aliases": ["c"],
|
||||||
|
"x-type": "component",
|
||||||
|
"description": "Create a Vue component."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
export * from './src/utils/versions';
|
||||||
|
export { applicationGenerator } from './src/generators/application/application';
|
||||||
|
export { libraryGenerator } from './src/generators/library/library';
|
||||||
|
export { componentGenerator } from './src/generators/component/component';
|
||||||
|
export { type InitSchema } from './src/generators/init/schema';
|
||||||
|
export { vueInitGenerator } from './src/generators/init/init';
|
||||||
@ -28,14 +28,19 @@
|
|||||||
"migrations": "./migrations.json"
|
"migrations": "./migrations.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0",
|
||||||
|
"@nx/devkit": "file:../devkit",
|
||||||
|
"@nx/jest": "file:../jest",
|
||||||
|
"@nx/js": "file:../js",
|
||||||
|
"@nx/linter": "file:../linter",
|
||||||
|
"@nx/vite": "file:../vite",
|
||||||
|
"@nx/web": "file:../web",
|
||||||
|
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {},
|
||||||
"nx": ">= 15 <= 17"
|
|
||||||
},
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.js",
|
".": "./index.js",
|
||||||
"./package.json": "./package.json",
|
"./package.json": "./package.json",
|
||||||
|
|||||||
@ -0,0 +1,226 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 1`] = `
|
||||||
|
"{
|
||||||
|
"root": true,
|
||||||
|
"ignorePatterns": ["**/*"],
|
||||||
|
"plugins": ["@nx"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"@nx/enforce-module-boundaries": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
"allow": [],
|
||||||
|
"depConstraints": [
|
||||||
|
{
|
||||||
|
"sourceTag": "*",
|
||||||
|
"onlyDependOnLibsWithTags": ["*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"extends": ["plugin:@nx/typescript"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"extends": ["plugin:@nx/javascript"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 2`] = `
|
||||||
|
"import vue from '@vitejs/plugin-vue';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
cacheDir: '../node_modules/.vite/test',
|
||||||
|
|
||||||
|
server: {
|
||||||
|
port: 4200,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
|
||||||
|
preview: {
|
||||||
|
port: 4300,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [nxViteTsPaths(), vue()],
|
||||||
|
|
||||||
|
// Uncomment this if you are using workers.
|
||||||
|
// worker: {
|
||||||
|
// plugins: [ nxViteTsPaths() ],
|
||||||
|
// },
|
||||||
|
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
cache: { dir: '../node_modules/.vitest' },
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 3`] = `
|
||||||
|
"{
|
||||||
|
"name": "test",
|
||||||
|
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "application",
|
||||||
|
"sourceRoot": "test/src",
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["test/**/*.{ts,tsx,js,jsx,vue}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"executor": "@nx/vite:build",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"defaultConfiguration": "production",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/test",
|
||||||
|
"skipTypeCheck": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"mode": "development"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"mode": "production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"executor": "@nx/vite:dev-server",
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "test:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "test:build:development",
|
||||||
|
"hmr": true
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "test:build:production",
|
||||||
|
"hmr": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"executor": "@nx/vite:preview-server",
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "test:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "test:build:development"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "test:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/vite:test",
|
||||||
|
"outputs": ["{options.reportsDirectory}"],
|
||||||
|
"options": {
|
||||||
|
"passWithNoTests": true,
|
||||||
|
"reportsDirectory": "../coverage/test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve-static": {
|
||||||
|
"executor": "@nx/web:file-server",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "test:build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 4`] = `
|
||||||
|
"{
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript",
|
||||||
|
"@vue/eslint-config-prettier/skip-formatting",
|
||||||
|
"../.eslintrc.json"
|
||||||
|
],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 5`] = `
|
||||||
|
"import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import App from '../App.vue';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(App, {});
|
||||||
|
expect(wrapper.text()).toContain('Welcome test 👋');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 6`] = `
|
||||||
|
[
|
||||||
|
".eslintignore",
|
||||||
|
".eslintrc.json",
|
||||||
|
".prettierignore",
|
||||||
|
".prettierrc",
|
||||||
|
"nx.json",
|
||||||
|
"package.json",
|
||||||
|
"test-e2e/.eslintrc.json",
|
||||||
|
"test-e2e/cypress.config.ts",
|
||||||
|
"test-e2e/project.json",
|
||||||
|
"test-e2e/src/e2e/app.cy.ts",
|
||||||
|
"test-e2e/src/fixtures/example.json",
|
||||||
|
"test-e2e/src/support/app.po.ts",
|
||||||
|
"test-e2e/src/support/commands.ts",
|
||||||
|
"test-e2e/src/support/e2e.ts",
|
||||||
|
"test-e2e/tsconfig.json",
|
||||||
|
"test/.eslintrc.json",
|
||||||
|
"test/index.html",
|
||||||
|
"test/project.json",
|
||||||
|
"test/src/__tests__/App.spec.ts",
|
||||||
|
"test/src/App.vue",
|
||||||
|
"test/src/components/NxWelcome.vue",
|
||||||
|
"test/src/main.ts",
|
||||||
|
"test/src/styles.css",
|
||||||
|
"test/tsconfig.app.json",
|
||||||
|
"test/tsconfig.json",
|
||||||
|
"test/tsconfig.spec.json",
|
||||||
|
"test/vite.config.ts",
|
||||||
|
"tsconfig.base.json",
|
||||||
|
]
|
||||||
|
`;
|
||||||
50
packages/vue/src/generators/application/application.spec.ts
Normal file
50
packages/vue/src/generators/application/application.spec.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { Tree, readProjectConfiguration } from '@nx/devkit';
|
||||||
|
|
||||||
|
import { applicationGenerator } from './application';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
|
||||||
|
describe('application generator', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
const options: Schema = { name: 'test' } as Schema;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run successfully', async () => {
|
||||||
|
await applicationGenerator(tree, options);
|
||||||
|
const config = readProjectConfiguration(tree, 'test');
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up project correctly with given options', async () => {
|
||||||
|
await applicationGenerator(tree, { ...options, unitTestRunner: 'vitest' });
|
||||||
|
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/project.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/__tests__/App.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(listFiles(tree)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use stylesheet if --style=none', async () => {
|
||||||
|
await applicationGenerator(tree, { ...options, style: 'none' });
|
||||||
|
|
||||||
|
expect(tree.exists('test/src/style.none')).toBeFalsy();
|
||||||
|
expect(tree.read('test/src/main.ts', 'utf-8')).not.toContain('styles.none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function listFiles(tree: Tree): string[] {
|
||||||
|
const files = new Set<string>();
|
||||||
|
tree.listChanges().forEach((change) => {
|
||||||
|
if (change.type !== 'DELETE') {
|
||||||
|
files.add(change.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(files).sort((a, b) => a.localeCompare(b));
|
||||||
|
}
|
||||||
75
packages/vue/src/generators/application/application.ts
Normal file
75
packages/vue/src/generators/application/application.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
runTasksInSerial,
|
||||||
|
toJS,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { Linter } from '@nx/linter';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
|
import { vueInitGenerator } from '../init/init';
|
||||||
|
import { addLinting } from '../../utils/add-linting';
|
||||||
|
import { addE2e } from './lib/add-e2e';
|
||||||
|
import { createApplicationFiles } from './lib/create-application-files';
|
||||||
|
import { addVite } from './lib/add-vite';
|
||||||
|
import { addJest } from './lib/add-jest';
|
||||||
|
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
||||||
|
|
||||||
|
export async function applicationGenerator(
|
||||||
|
tree: Tree,
|
||||||
|
_options: Schema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const options = await normalizeOptions(tree, _options);
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, options.name, {
|
||||||
|
root: options.appProjectRoot,
|
||||||
|
projectType: 'application',
|
||||||
|
sourceRoot: `${options.appProjectRoot}/src`,
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
await vueInitGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
skipFormat: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
extractTsConfigBase(tree);
|
||||||
|
|
||||||
|
createApplicationFiles(tree, options);
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
await addLinting(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
name: options.projectName,
|
||||||
|
projectRoot: options.appProjectRoot,
|
||||||
|
linter: options.linter ?? Linter.EsLint,
|
||||||
|
unitTestRunner: options.unitTestRunner,
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
},
|
||||||
|
'app'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.push(await addVite(tree, options));
|
||||||
|
|
||||||
|
if (options.unitTestRunner === 'jest')
|
||||||
|
tasks.push(await addJest(tree, options));
|
||||||
|
|
||||||
|
tasks.push(await addE2e(tree, options));
|
||||||
|
|
||||||
|
if (options.js) toJS(tree);
|
||||||
|
|
||||||
|
if (!options.skipFormat) await formatFiles(tree);
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default applicationGenerator;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><%= title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
<% if (routing) { %>
|
||||||
|
import { RouterLink, RouterView } from 'vue-router';
|
||||||
|
<% } %>
|
||||||
|
import NxWelcome from './components/NxWelcome.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<% if (routing) { %>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<RouterLink to="/">Home</RouterLink>
|
||||||
|
<RouterLink to="/about">About</RouterLink>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<RouterView />
|
||||||
|
<% } else { %>
|
||||||
|
<NxWelcome title="<%= title %>" />
|
||||||
|
<% } %>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<% if (routing && style !== 'none') { %>
|
||||||
|
<style scoped lang="<%= style %>">
|
||||||
|
header {
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > a {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
padding-right: calc(var(--section-gap) / 2);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 768px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<% } %>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<% if ( unitTestRunner === 'vitest' ) { %>
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
<% } %>
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import App from '../App.vue';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(App, {})
|
||||||
|
expect(wrapper.text()).toContain('Welcome <%= title %> 👋')
|
||||||
|
})
|
||||||
|
});
|
||||||
@ -0,0 +1,793 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div className="wrapper">
|
||||||
|
<div className="container">
|
||||||
|
<div id="welcome">
|
||||||
|
<h1>
|
||||||
|
<span> Hello there, </span>
|
||||||
|
Welcome {{ title }} 👋
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hero" className="rounded">
|
||||||
|
<div className="text-container">
|
||||||
|
<h2>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>You're up and running</span>
|
||||||
|
</h2>
|
||||||
|
<a href="#commands"> What's next? </a>
|
||||||
|
</div>
|
||||||
|
<div className="logo-container">
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="middle-content">
|
||||||
|
<div id="learning-materials" className="rounded shadow">
|
||||||
|
<h2>Learning materials</h2>
|
||||||
|
<a
|
||||||
|
href="https://nx.dev/getting-started/intro?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="list-item-link"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Documentation
|
||||||
|
<span> Everything is in there </span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://blog.nrwl.io/?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="list-item-link"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Blog
|
||||||
|
<span> Changelog, features & events </span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/@NxDevtools/videos?utm_source=nx-project&sub_confirmation=1"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="list-item-link"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<title>YouTube</title>
|
||||||
|
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
YouTube channel
|
||||||
|
<span> Nx Show, talks & tutorials </span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="list-item-link"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Interactive tutorials
|
||||||
|
<span> Create an app, step-by-step </span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://nxplaybook.com/?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="list-item-link"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 14l9-5-9-5-9 5 9 5z" />
|
||||||
|
<path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Video courses
|
||||||
|
<span> Nx custom courses </span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="other-links">
|
||||||
|
<a
|
||||||
|
className="button-pill nx-console rounded shadow"
|
||||||
|
href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<title>Visual Studio Code</title>
|
||||||
|
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Install Nx Console for VSCode
|
||||||
|
<span>The official VSCode plugin for Nx.</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="button-pill nx-console rounded shadow"
|
||||||
|
href="https://plugins.jetbrains.com/plugin/21060-nx-console"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<title>IntelliJ</title>
|
||||||
|
<path d="M0 0v24h24V0zm3.723 3.111h5v1.834h-1.39v6.277h1.39v1.834h-5v-1.834h1.444V4.945H3.723zm11.055 0H17v6.5c0 .612-.055 1.111-.222 1.556-.167.444-.39.777-.723 1.11-.277.279-.666.557-1.11.668a3.933 3.933 0 0 1-1.445.278c-.778 0-1.444-.167-1.944-.445a4.81 4.81 0 0 1-1.279-1.056l1.39-1.555c.277.334.555.555.833.722.277.167.611.278.945.278.389 0 .721-.111 1-.389.221-.278.333-.667.333-1.278zM2.222 19.5h9V21h-9z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Install Nx Console for JetBrains
|
||||||
|
<span>
|
||||||
|
Available for WebStorm, Intellij IDEA Ultimate and more!
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div id="nx-cloud" className="rounded shadow">
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
id="nx-cloud-logo"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="transparent"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M23 3.75V6.5c-3.036 0-5.5 2.464-5.5 5.5s-2.464 5.5-5.5 5.5-5.5 2.464-5.5 5.5H3.75C2.232 23 1 21.768 1 20.25V3.75C1 2.232 2.232 1 3.75 1h16.5C21.768 1 23 2.232 23 3.75Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M23 6v14.1667C23 21.7307 21.7307 23 20.1667 23H6c0-3.128 2.53867-5.6667 5.6667-5.6667 3.128 0 5.6666-2.5386 5.6666-5.6666C17.3333 8.53867 19.872 6 23 6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<h2>
|
||||||
|
NxCloud
|
||||||
|
<span>Enable faster CI & better DX</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
You can activate distributed tasks executions and caching by
|
||||||
|
running:
|
||||||
|
</p>
|
||||||
|
<pre>nx connect-to-nx-cloud</pre>
|
||||||
|
<a
|
||||||
|
href="https://nx.app/?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
What is Nx Cloud?{' '}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
id="nx-repo"
|
||||||
|
className="button-pill rounded shadow"
|
||||||
|
href="https://github.com/nrwl/nx?utm_source=nx-project"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Nx is open source
|
||||||
|
<span> Love Nx? Give us a star! </span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="commands" className="rounded shadow">
|
||||||
|
<h2>Next steps</h2>
|
||||||
|
<p>Here are some things you can do with Nx:</p>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Add UI library
|
||||||
|
</summary>
|
||||||
|
<pre>
|
||||||
|
<span># Generate UI lib</span>
|
||||||
|
nx g @nx/react:lib ui
|
||||||
|
<span># Add a component</span>
|
||||||
|
nx g @nx/react:component button --project ui
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
View interactive project graph
|
||||||
|
</summary>
|
||||||
|
<pre>nx graph</pre>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Run affected commands
|
||||||
|
</summary>
|
||||||
|
<pre>
|
||||||
|
<span># see what's been affected by changes</span>
|
||||||
|
nx affected:graph
|
||||||
|
<span># run tests for current changes</span>
|
||||||
|
nx affected:test
|
||||||
|
<span># run e2e tests for current changes</span>
|
||||||
|
nx affected:e2e
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="love">
|
||||||
|
Carefully crafted with
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
vertical-align: middle;
|
||||||
|
shape-rendering: auto;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: rgba(55, 65, 81, 1);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
color: rgba(229, 231, 235, 1);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
'Liberation Mono', 'Courier New', monospace;
|
||||||
|
overflow: scroll;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||||
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.rounded {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 768px;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
color: rgba(55, 65, 81, 1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#welcome {
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
#welcome h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
#welcome span {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.875rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
#hero {
|
||||||
|
align-items: center;
|
||||||
|
background-color: hsla(214, 62%, 21%, 1);
|
||||||
|
border: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: rgba(55, 65, 81, 1);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
}
|
||||||
|
#hero .text-container {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
}
|
||||||
|
#hero .text-container h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#hero .text-container h2 svg {
|
||||||
|
color: hsla(162, 47%, 50%, 1);
|
||||||
|
height: 2rem;
|
||||||
|
left: -0.25rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
#hero .text-container h2 span {
|
||||||
|
margin-left: 2.5rem;
|
||||||
|
}
|
||||||
|
#hero .text-container a {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
color: rgba(55, 65, 81, 1);
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
#hero .logo-container {
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
#hero .logo-container svg {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
width: 66.666667%;
|
||||||
|
}
|
||||||
|
#middle-content {
|
||||||
|
align-items: flex-start;
|
||||||
|
display: grid;
|
||||||
|
gap: 4rem;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
}
|
||||||
|
#learning-materials {
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
}
|
||||||
|
#learning-materials h2 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
.list-item-link {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.list-item-link svg:first-child {
|
||||||
|
margin-right: 1rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
.list-item-link > span {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-weight: 400;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
.list-item-link > span > span {
|
||||||
|
color: rgba(107, 114, 128, 1);
|
||||||
|
display: block;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1rem;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
.list-item-link svg:last-child {
|
||||||
|
height: 1rem;
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
.list-item-link:hover {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
background-color: hsla(162, 47%, 50%, 1);
|
||||||
|
}
|
||||||
|
.list-item-link:hover > span {}
|
||||||
|
.list-item-link:hover > span > span {
|
||||||
|
color: rgba(243, 244, 246, 1);
|
||||||
|
}
|
||||||
|
.list-item-link:hover svg:last-child {
|
||||||
|
transform: translateX(0.25rem);
|
||||||
|
}
|
||||||
|
#other-links {}
|
||||||
|
.button-pill {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.button-pill svg {
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
.button-pill > span {
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
.button-pill span span {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
.button-pill:hover svg,
|
||||||
|
.button-pill:hover {
|
||||||
|
color: rgba(255, 255, 255, 1) !important;
|
||||||
|
}
|
||||||
|
.nx-console:hover {
|
||||||
|
background-color: rgba(0, 122, 204, 1);
|
||||||
|
}
|
||||||
|
.nx-console svg {
|
||||||
|
color: rgba(0, 122, 204, 1);
|
||||||
|
}
|
||||||
|
#nx-repo:hover {
|
||||||
|
background-color: rgba(24, 23, 23, 1);
|
||||||
|
}
|
||||||
|
#nx-repo svg {
|
||||||
|
color: rgba(24, 23, 23, 1);
|
||||||
|
}
|
||||||
|
#nx-cloud {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
}
|
||||||
|
#nx-cloud > div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#nx-cloud > div svg {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
#nx-cloud > div h2 {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
#nx-cloud > div h2 span {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
#nx-cloud p {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#nx-cloud pre {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#nx-cloud a {
|
||||||
|
color: rgba(107, 114, 128, 1);
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#nx-cloud a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#commands {
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
}
|
||||||
|
#commands h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
#commands p {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
details {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
details pre > span {
|
||||||
|
color: rgba(181, 181, 181, 1);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition-property: background-color, border-color, color, fill, stroke,
|
||||||
|
opacity, box-shadow, transform, filter, backdrop-filter,
|
||||||
|
-webkit-backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
summary:hover {
|
||||||
|
background-color: rgba(243, 244, 246, 1);
|
||||||
|
}
|
||||||
|
summary svg {
|
||||||
|
height: 1.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
#love {
|
||||||
|
color: rgba(107, 114, 128, 1);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#love svg {
|
||||||
|
color: rgba(252, 165, 165, 1);
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
display: inline;
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
#hero {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
#hero .logo-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#middle-content {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
<% if (style !== 'none') { %>
|
||||||
|
import './styles.<%= style %>';
|
||||||
|
<% } %>
|
||||||
|
<% if (routing) { %>
|
||||||
|
import router from './router';
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
<% if (routing) { %>
|
||||||
|
app.use(router);
|
||||||
|
<% } %>
|
||||||
|
app.mount('#root');
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "<%= offsetFromRoot %>dist/out-tsc"
|
||||||
|
},
|
||||||
|
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.vue", "src/**/*.test.vue"],
|
||||||
|
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.vue"]
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/AboutView.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.about {
|
||||||
|
max-width: 768px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import NxWelcome from '../components/NxWelcome.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<NxWelcome />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
|
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||||
|
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||||
|
'Noto Color Emoji';
|
||||||
|
line-height: 1.5;
|
||||||
|
tab-size: 4;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: currentColor;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
'Liberation Mono', 'Courier New', monospace;
|
||||||
|
}
|
||||||
64
packages/vue/src/generators/application/lib/add-e2e.ts
Normal file
64
packages/vue/src/generators/application/lib/add-e2e.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import type { GeneratorCallback, Tree } from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
ensurePackage,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
joinPathFragments,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { webStaticServeGenerator } from '@nx/web';
|
||||||
|
|
||||||
|
import { nxVersion } from '../../../utils/versions';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
|
export async function addE2e(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
switch (options.e2eTestRunner) {
|
||||||
|
case 'cypress':
|
||||||
|
webStaticServeGenerator(tree, {
|
||||||
|
buildTarget: `${options.projectName}:build`,
|
||||||
|
targetName: 'serve-static',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { cypressProjectGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/cypress')
|
||||||
|
>('@nx/cypress', nxVersion);
|
||||||
|
|
||||||
|
return await cypressProjectGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
name: options.e2eProjectName,
|
||||||
|
directory: options.e2eProjectRoot,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
project: options.projectName,
|
||||||
|
bundler: 'vite',
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
case 'playwright':
|
||||||
|
const { configurationGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/playwright')
|
||||||
|
>('@nx/playwright', nxVersion);
|
||||||
|
addProjectConfiguration(tree, options.e2eProjectName, {
|
||||||
|
root: options.e2eProjectRoot,
|
||||||
|
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
|
||||||
|
targets: {},
|
||||||
|
implicitDependencies: [options.projectName],
|
||||||
|
});
|
||||||
|
return configurationGenerator(tree, {
|
||||||
|
project: options.e2eProjectName,
|
||||||
|
skipFormat: true,
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
directory: 'src',
|
||||||
|
js: false,
|
||||||
|
linter: options.linter,
|
||||||
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
|
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
|
||||||
|
options.name
|
||||||
|
}`,
|
||||||
|
webServerAddress: 'http://localhost:4200',
|
||||||
|
});
|
||||||
|
case 'none':
|
||||||
|
default:
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
}
|
||||||
44
packages/vue/src/generators/application/lib/add-jest.ts
Normal file
44
packages/vue/src/generators/application/lib/add-jest.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
ensurePackage,
|
||||||
|
GeneratorCallback,
|
||||||
|
runTasksInSerial,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
import { nxVersion, vueJest3Version } from '../../../utils/versions';
|
||||||
|
import { setupJestProject } from '../../../utils/setup-jest';
|
||||||
|
|
||||||
|
export async function addJest(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
const { configurationGenerator } = ensurePackage<typeof import('@nx/jest')>(
|
||||||
|
'@nx/jest',
|
||||||
|
nxVersion
|
||||||
|
);
|
||||||
|
tasks.push(
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: options.name,
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
compiler: 'babel',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setupJestProject(tree, options.appProjectRoot);
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@vue/vue3-jest': vueJest3Version,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
46
packages/vue/src/generators/application/lib/add-vite.ts
Normal file
46
packages/vue/src/generators/application/lib/add-vite.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
GeneratorCallback,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createOrEditViteConfig, viteConfigurationGenerator } from '@nx/vite';
|
||||||
|
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
|
export async function addVite(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
// Set up build target (and test target if using vitest)
|
||||||
|
const viteTask = await viteConfigurationGenerator(tree, {
|
||||||
|
uiFramework: 'none',
|
||||||
|
project: options.name,
|
||||||
|
newProject: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
});
|
||||||
|
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.name,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
imports: [`import vue from '@vitejs/plugin-vue'`],
|
||||||
|
plugins: ['vue()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update build to skip type checking since tsc won't work on .vue files.
|
||||||
|
// Need to use vue-tsc instead.
|
||||||
|
const projectConfig = readProjectConfiguration(tree, options.name);
|
||||||
|
projectConfig.targets.build.options.skipTypeCheck = true;
|
||||||
|
updateProjectConfiguration(tree, options.name, projectConfig);
|
||||||
|
|
||||||
|
return viteTask;
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { generateFiles, offsetFromRoot, Tree } from '@nx/devkit';
|
||||||
|
import { getRelativePathToRootTsConfig } from '@nx/js';
|
||||||
|
|
||||||
|
import { createTsConfig } from '../../../utils/create-ts-config';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
|
export function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
path.join(__dirname, '../files/common'),
|
||||||
|
options.appProjectRoot,
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
|
||||||
|
title: options.projectName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.style !== 'none') {
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
path.join(__dirname, '../files/stylesheet'),
|
||||||
|
options.appProjectRoot,
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
|
||||||
|
title: options.projectName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.routing) {
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
path.join(__dirname, '../files/routing'),
|
||||||
|
options.appProjectRoot,
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
|
||||||
|
title: options.projectName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createTsConfig(
|
||||||
|
tree,
|
||||||
|
options.appProjectRoot,
|
||||||
|
'app',
|
||||||
|
options,
|
||||||
|
getRelativePathToRootTsConfig(tree, options.appProjectRoot)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { Tree, extractLayoutDirectory, names } from '@nx/devkit';
|
||||||
|
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { NormalizedSchema, Schema } from '../schema';
|
||||||
|
|
||||||
|
export function normalizeDirectory(options: Schema) {
|
||||||
|
options.directory = options.directory?.replace(/\\{1,2}/g, '/');
|
||||||
|
const { projectDirectory } = extractLayoutDirectory(options.directory);
|
||||||
|
return projectDirectory
|
||||||
|
? `${names(projectDirectory).fileName}/${names(options.name).fileName}`
|
||||||
|
: names(options.name).fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function normalizeOptions(
|
||||||
|
host: Tree,
|
||||||
|
options: Schema,
|
||||||
|
callingGenerator = '@nx/vue:application'
|
||||||
|
): Promise<NormalizedSchema> {
|
||||||
|
const {
|
||||||
|
projectName: appProjectName,
|
||||||
|
projectRoot: appProjectRoot,
|
||||||
|
projectNameAndRootFormat,
|
||||||
|
} = await determineProjectNameAndRootOptions(host, {
|
||||||
|
name: options.name,
|
||||||
|
projectType: 'application',
|
||||||
|
directory: options.directory,
|
||||||
|
projectNameAndRootFormat: options.projectNameAndRootFormat,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
callingGenerator,
|
||||||
|
});
|
||||||
|
options.rootProject = appProjectRoot === '.';
|
||||||
|
options.projectNameAndRootFormat = projectNameAndRootFormat;
|
||||||
|
|
||||||
|
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
|
||||||
|
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
|
||||||
|
|
||||||
|
const parsedTags = options.tags
|
||||||
|
? options.tags.split(',').map((s) => s.trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const normalized = {
|
||||||
|
...options,
|
||||||
|
name: names(options.name).fileName,
|
||||||
|
projectName: appProjectName,
|
||||||
|
appProjectRoot,
|
||||||
|
e2eProjectName,
|
||||||
|
e2eProjectRoot,
|
||||||
|
parsedTags,
|
||||||
|
} as NormalizedSchema;
|
||||||
|
|
||||||
|
normalized.style = options.style ?? 'css';
|
||||||
|
normalized.routing = normalized.routing ?? false;
|
||||||
|
normalized.unitTestRunner ??= 'vitest';
|
||||||
|
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'cypress';
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
30
packages/vue/src/generators/application/schema.d.ts
vendored
Normal file
30
packages/vue/src/generators/application/schema.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import type { Linter } from '@nx/linter';
|
||||||
|
|
||||||
|
export interface Schema {
|
||||||
|
name: string;
|
||||||
|
style: 'none' | 'css' | 'scss' | 'less';
|
||||||
|
skipFormat?: boolean;
|
||||||
|
directory?: string;
|
||||||
|
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||||
|
tags?: string;
|
||||||
|
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||||
|
inSourceTests?: boolean;
|
||||||
|
e2eTestRunner: 'cypress' | 'playwright' | 'none';
|
||||||
|
linter: Linter;
|
||||||
|
routing?: boolean;
|
||||||
|
js?: boolean;
|
||||||
|
strict?: boolean;
|
||||||
|
setParserOptionsProject?: boolean;
|
||||||
|
skipPackageJson?: boolean;
|
||||||
|
rootProject?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedSchema extends Schema {
|
||||||
|
projectName: string;
|
||||||
|
appProjectRoot: string;
|
||||||
|
e2eProjectName: string;
|
||||||
|
e2eProjectRoot: string;
|
||||||
|
parsedTags: string[];
|
||||||
|
devServerPort?: number;
|
||||||
|
}
|
||||||
140
packages/vue/src/generators/application/schema.json
Normal file
140
packages/vue/src/generators/application/schema.json
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxVueApp",
|
||||||
|
"title": "Create a Vue Application",
|
||||||
|
"description": "Create a Vue application for Nx.",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "nx g app myapp --directory=myorg/myapp",
|
||||||
|
"description": "Generate `apps/myorg/myapp` and `apps/myorg/myapp-e2e`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx g app myapp --routing",
|
||||||
|
"description": "Set up Vue Router"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The name of the application.",
|
||||||
|
"type": "string",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the application?",
|
||||||
|
"pattern": "^[a-zA-Z][^:]*$"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"description": "The directory of the new application.",
|
||||||
|
"type": "string",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"description": "The file extension to be used for style files.",
|
||||||
|
"type": "string",
|
||||||
|
"default": "css",
|
||||||
|
"alias": "s",
|
||||||
|
"x-prompt": {
|
||||||
|
"message": "Which stylesheet format would you like to use?",
|
||||||
|
"type": "list",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "css",
|
||||||
|
"label": "CSS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "scss",
|
||||||
|
"label": "SASS(.scss) [ http://sass-lang.com ]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "less",
|
||||||
|
"label": "LESS [ http://lesscss.org ]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "none",
|
||||||
|
"label": "None"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"x-prompt": "Would you like to add Vue Router to this application?",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["jest", "vitest", "none"],
|
||||||
|
"description": "Test runner to use for unit tests.",
|
||||||
|
"x-prompt": "Which unit test runner would you like to use?",
|
||||||
|
"default": "none"
|
||||||
|
},
|
||||||
|
"inSourceTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files. Read more on the Vitest docs site: https://vitest.dev/guide/in-source.html"
|
||||||
|
},
|
||||||
|
"e2eTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["cypress", "playwright", "none"],
|
||||||
|
"description": "Test runner to use for end to end (E2E) tests.",
|
||||||
|
"x-prompt": "Which E2E test runner would you like to use?",
|
||||||
|
"default": "cypress"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the application (used for linting).",
|
||||||
|
"alias": "t"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"strict": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to enable tsconfig strict mode or not.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"setParserOptionsProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipPackageJson": {
|
||||||
|
"description": "Do not add dependencies to `package.json`.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"rootProject": {
|
||||||
|
"description": "Create a application at the root of the workspace",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"examplesFile": "../../../docs/application-examples.md"
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`component --export should add to index.ts barrel 1`] = `
|
||||||
|
"export { default as Hello } from './components/hello/hello.vue';
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`component should generate files with jest 1`] = `
|
||||||
|
"<script setup lang="ts">
|
||||||
|
defineProps<{}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Welcome to Hello!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
color: pink;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`component should generate files with jest 2`] = `
|
||||||
|
"import { mount } from '@vue/test-utils';
|
||||||
|
import Hello from '../hello.vue';
|
||||||
|
|
||||||
|
describe('Hello', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(Hello, {});
|
||||||
|
expect(wrapper.text()).toContain('Welcome to Hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`component should generate files with vitest 1`] = `
|
||||||
|
"<script setup lang="ts">
|
||||||
|
defineProps<{}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Welcome to Hello!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
color: pink;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`component should generate files with vitest 2`] = `
|
||||||
|
"import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import Hello from '../hello.vue';
|
||||||
|
|
||||||
|
describe('Hello', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(Hello, {});
|
||||||
|
expect(wrapper.text()).toContain('Welcome to Hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
207
packages/vue/src/generators/component/component.spec.ts
Normal file
207
packages/vue/src/generators/component/component.spec.ts
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import { logger, readJson, Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { componentGenerator } from './component';
|
||||||
|
import { createLib } from '../../utils/test-utils';
|
||||||
|
|
||||||
|
describe('component', () => {
|
||||||
|
let appTree: Tree;
|
||||||
|
let projectName: string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
projectName = 'my-lib';
|
||||||
|
appTree = createTreeWithEmptyWorkspace();
|
||||||
|
await createLib(appTree, projectName);
|
||||||
|
jest.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||||
|
jest.spyOn(logger, 'debug').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate files with vitest', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-lib/src/components/hello/hello.vue')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-lib/src/components/hello/__tests__/hello.spec.ts')
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
appTree.read('my-lib/src/components/hello/hello.vue', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
appTree.read(
|
||||||
|
'my-lib/src/components/hello/__tests__/hello.spec.ts',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate files with jest', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
unitTestRunner: 'jest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
appTree.read('my-lib/src/components/hello/hello.vue', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
appTree.read(
|
||||||
|
'my-lib/src/components/hello/__tests__/hello.spec.ts',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// we don't have app generator yet
|
||||||
|
xit('should generate files for an app', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: 'my-app',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-app/src/components/hello/hello.tsx')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-app/src/components/hello/hello.spec.ts')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-app/src/components/hello/hello.module.css')
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--export', () => {
|
||||||
|
it('should add to index.ts barrel', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
export: true,
|
||||||
|
});
|
||||||
|
expect(appTree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// no app generator yet
|
||||||
|
xit('should not export from an app', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: 'my-app',
|
||||||
|
export: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(appTree.read('my-app/src/index.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--pascalCaseFiles', () => {
|
||||||
|
it('should generate component files with upper case names', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
pascalCaseFiles: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-lib/src/components/hello/Hello.vue')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-lib/src/components/hello/__tests__/Hello.spec.ts')
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--pascalCaseDirectory', () => {
|
||||||
|
it('should generate component files with pascal case directories', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello-world',
|
||||||
|
project: projectName,
|
||||||
|
pascalCaseFiles: true,
|
||||||
|
pascalCaseDirectory: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
appTree.exists('my-lib/src/components/HelloWorld/HelloWorld.vue')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
appTree.exists(
|
||||||
|
'my-lib/src/components/HelloWorld/__tests__/HelloWorld.spec.ts'
|
||||||
|
)
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: figure out routing
|
||||||
|
xdescribe('--routing', () => {
|
||||||
|
it('should add routes to the component', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
routing: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = appTree
|
||||||
|
.read('my-lib/src/components/hello/hello.tsx')
|
||||||
|
.toString();
|
||||||
|
expect(content).toContain('react-router-dom');
|
||||||
|
expect(content).toMatch(/<Route\s*path="\/"/);
|
||||||
|
expect(content).toMatch(/<Link\s*to="\/"/);
|
||||||
|
|
||||||
|
const packageJSON = readJson(appTree, 'package.json');
|
||||||
|
expect(packageJSON.dependencies['react-router-dom']).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--directory', () => {
|
||||||
|
it('should create component under the directory', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
directory: 'components',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(appTree.exists('/my-lib/src/components/hello/hello.vue'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create with nested directories', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'helloWorld',
|
||||||
|
project: projectName,
|
||||||
|
directory: 'lib/foo',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
appTree.exists('/my-lib/src/components/foo/hello-world/hello-world.vue')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--flat', () => {
|
||||||
|
it('should create in project directory rather than in its own folder', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
flat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(appTree.exists('/my-lib/src/components/hello.vue'));
|
||||||
|
});
|
||||||
|
it('should work with custom directory path', async () => {
|
||||||
|
await componentGenerator(appTree, {
|
||||||
|
name: 'hello',
|
||||||
|
project: projectName,
|
||||||
|
flat: true,
|
||||||
|
directory: 'components',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(appTree.exists('/my-lib/src/components/hello.vue'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
161
packages/vue/src/generators/component/component.ts
Normal file
161
packages/vue/src/generators/component/component.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
applyChangesToString,
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
getProjects,
|
||||||
|
joinPathFragments,
|
||||||
|
logger,
|
||||||
|
names,
|
||||||
|
runTasksInSerial,
|
||||||
|
toJS,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { NormalizedSchema, Schema } from './schema';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { addImport } from '../../utils/ast-utils';
|
||||||
|
|
||||||
|
export async function componentGenerator(host: Tree, schema: Schema) {
|
||||||
|
const options = await normalizeOptions(host, schema);
|
||||||
|
createComponentFiles(host, options);
|
||||||
|
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
addExportsToBarrel(host, options);
|
||||||
|
|
||||||
|
if (!options.skipFormat) {
|
||||||
|
await formatFiles(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createComponentFiles(host: Tree, options: NormalizedSchema) {
|
||||||
|
const componentDir = joinPathFragments(
|
||||||
|
options.projectSourceRoot,
|
||||||
|
options.directory
|
||||||
|
);
|
||||||
|
|
||||||
|
generateFiles(host, join(__dirname, './files'), componentDir, {
|
||||||
|
...options,
|
||||||
|
tmpl: '',
|
||||||
|
unitTestRunner: options.unitTestRunner,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const c of host.listChanges()) {
|
||||||
|
let deleteFile = false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(options.skipTests || options.inSourceTests) &&
|
||||||
|
/.*spec.ts/.test(c.path)
|
||||||
|
) {
|
||||||
|
deleteFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteFile) {
|
||||||
|
host.delete(c.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.js) toJS(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
|
function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
const workspace = getProjects(host);
|
||||||
|
const isApp = workspace.get(options.project).projectType === 'application';
|
||||||
|
|
||||||
|
if (options.export && !isApp) {
|
||||||
|
const indexFilePath = joinPathFragments(
|
||||||
|
options.projectSourceRoot,
|
||||||
|
options.js ? 'index.js' : 'index.ts'
|
||||||
|
);
|
||||||
|
const indexSource = host.read(indexFilePath, 'utf-8');
|
||||||
|
if (indexSource !== null) {
|
||||||
|
const indexSourceFile = tsModule.createSourceFile(
|
||||||
|
indexFilePath,
|
||||||
|
indexSource,
|
||||||
|
tsModule.ScriptTarget.Latest,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const changes = applyChangesToString(
|
||||||
|
indexSource,
|
||||||
|
addImport(
|
||||||
|
indexSourceFile,
|
||||||
|
`export { default as ${options.className} } from './${options.directory}/${options.fileName}.vue';`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
host.write(indexFilePath, changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function normalizeOptions(
|
||||||
|
host: Tree,
|
||||||
|
options: Schema
|
||||||
|
): Promise<NormalizedSchema> {
|
||||||
|
assertValidOptions(options);
|
||||||
|
|
||||||
|
const { className, fileName } = names(options.name);
|
||||||
|
const componentFileName =
|
||||||
|
options.fileName ?? (options.pascalCaseFiles ? className : fileName);
|
||||||
|
const project = getProjects(host).get(options.project);
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot find the ${options.project} project. Please double check the project name.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sourceRoot: projectSourceRoot, projectType } = project;
|
||||||
|
|
||||||
|
const directory = await getDirectory(host, options);
|
||||||
|
|
||||||
|
if (options.export && projectType === 'application') {
|
||||||
|
logger.warn(
|
||||||
|
`The "--export" option should not be used with applications and will do nothing.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.routing = options.routing ?? false;
|
||||||
|
options.inSourceTests = options.inSourceTests ?? false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
directory,
|
||||||
|
className,
|
||||||
|
fileName: componentFileName,
|
||||||
|
projectSourceRoot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDirectory(host: Tree, options: Schema) {
|
||||||
|
if (options.directory) return options.directory;
|
||||||
|
if (options.flat) return 'components';
|
||||||
|
const { className, fileName } = names(options.name);
|
||||||
|
const nestedDir = options.pascalCaseDirectory === true ? className : fileName;
|
||||||
|
return joinPathFragments('components', nestedDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertValidOptions(options: Schema) {
|
||||||
|
const slashes = ['/', '\\'];
|
||||||
|
slashes.forEach((s) => {
|
||||||
|
if (options.name.indexOf(s) !== -1) {
|
||||||
|
const [name, ...rest] = options.name.split(s).reverse();
|
||||||
|
let suggestion = rest.map((x) => x.toLowerCase()).join(s);
|
||||||
|
if (options.directory) {
|
||||||
|
suggestion = `${options.directory}${s}${suggestion}`;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Found "${s}" in the component name. Did you mean to use the --directory option (e.g. \`nx g c ${name} --directory ${suggestion}\`)?`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default componentGenerator;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Welcome to <%= className %>!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
color: pink;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<% if ( unitTestRunner === 'vitest' ) { %>
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import <%= className %> from '../<%= fileName %>.vue';
|
||||||
|
|
||||||
|
describe('<%= className %>', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(<%= className %>, {})
|
||||||
|
expect(wrapper.text()).toContain('Welcome to <%= className %>')
|
||||||
|
})
|
||||||
|
});
|
||||||
22
packages/vue/src/generators/component/schema.d.ts
vendored
Normal file
22
packages/vue/src/generators/component/schema.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export interface Schema {
|
||||||
|
name: string;
|
||||||
|
project: string;
|
||||||
|
skipTests?: boolean;
|
||||||
|
directory?: string;
|
||||||
|
export?: boolean;
|
||||||
|
pascalCaseFiles?: boolean;
|
||||||
|
pascalCaseDirectory?: boolean;
|
||||||
|
routing?: boolean;
|
||||||
|
js?: boolean;
|
||||||
|
flat?: boolean;
|
||||||
|
fileName?: string;
|
||||||
|
inSourceTests?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedSchema extends Schema {
|
||||||
|
projectSourceRoot: string;
|
||||||
|
fileName: string;
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
107
packages/vue/src/generators/component/schema.json
Normal file
107
packages/vue/src/generators/component/schema.json
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxVueComponent",
|
||||||
|
"title": "Create a Vue Component",
|
||||||
|
"description": "Create a Vue Component for Nx.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "nx g component my-component --project=mylib",
|
||||||
|
"description": "Generate a component in the `mylib` library"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx g component my-component --project=mylib --classComponent",
|
||||||
|
"description": "Generate a class component in the `mylib` library"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"alias": "p",
|
||||||
|
"$default": {
|
||||||
|
"$source": "projectName"
|
||||||
|
},
|
||||||
|
"x-prompt": "What is the name of the project for this component?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the component.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the component?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "When true, does not create `spec.ts` test files for the new component.",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Create the component under this directory (can be nested).",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Create component at the source root rather than its own directory.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "When true, the component is exported from the project `index.ts` (if it exists).",
|
||||||
|
"alias": "e",
|
||||||
|
"default": false,
|
||||||
|
"x-prompt": "Should this component be exported in the project?"
|
||||||
|
},
|
||||||
|
"pascalCaseFiles": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use pascal case component file name (e.g. `App.tsx`).",
|
||||||
|
"alias": "P",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"pascalCaseDirectory": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use pascal case directory name (e.g. `App/App.tsx`).",
|
||||||
|
"alias": "R",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a library with routes."
|
||||||
|
},
|
||||||
|
"fileName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Create a component with this file name."
|
||||||
|
},
|
||||||
|
"inSourceTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files. Read more on the Vitest docs site: https://vitest.dev/guide/in-source.html"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["vitest", "jest", "none"],
|
||||||
|
"description": "Test runner to use for unit tests.",
|
||||||
|
"x-prompt": "What unit test runner should be used?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "project"]
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`init should add vue dependencies 1`] = `
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-router": "^4.2.4",
|
||||||
|
"vue-tsc": "^1.8.8",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nx/js": "0.0.1",
|
||||||
|
"@nx/vue": "0.0.1",
|
||||||
|
"@vitejs/plugin-vue": "^4.3.1",
|
||||||
|
"@vue/test-utils": "^2.4.1",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"typescript": "~5.1.3",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
20
packages/vue/src/generators/init/init.spec.ts
Normal file
20
packages/vue/src/generators/init/init.spec.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { readJson, Tree } from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { vueInitGenerator } from './init';
|
||||||
|
|
||||||
|
describe('init', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add vue dependencies', async () => {
|
||||||
|
await vueInitGenerator(tree, {
|
||||||
|
skipFormat: false,
|
||||||
|
routing: true,
|
||||||
|
});
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
expect(packageJson).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
67
packages/vue/src/generators/init/init.ts
Executable file
67
packages/vue/src/generators/init/init.ts
Executable file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
GeneratorCallback,
|
||||||
|
removeDependenciesFromPackageJson,
|
||||||
|
runTasksInSerial,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
|
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||||
|
import {
|
||||||
|
lessVersion,
|
||||||
|
nxVersion,
|
||||||
|
sassVersion,
|
||||||
|
vitePluginVueVersion,
|
||||||
|
vueRouterVersion,
|
||||||
|
vueTestUtilsVersion,
|
||||||
|
vueTsconfigVersion,
|
||||||
|
vueTscVersion,
|
||||||
|
vueVersion,
|
||||||
|
} from '../../utils/versions';
|
||||||
|
import { InitSchema } from './schema';
|
||||||
|
|
||||||
|
function updateDependencies(host: Tree, schema: InitSchema) {
|
||||||
|
removeDependenciesFromPackageJson(host, ['@nx/vue'], []);
|
||||||
|
|
||||||
|
let dependencies: { [key: string]: string } = {
|
||||||
|
vue: vueVersion,
|
||||||
|
'vue-tsc': vueTscVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
let devDependencies: { [key: string]: string } = {
|
||||||
|
'@nx/vue': nxVersion,
|
||||||
|
'@vue/tsconfig': vueTsconfigVersion,
|
||||||
|
'@vue/test-utils': vueTestUtilsVersion,
|
||||||
|
'@vitejs/plugin-vue': vitePluginVueVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (schema.routing) {
|
||||||
|
dependencies['vue-router'] = vueRouterVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.style === 'scss') {
|
||||||
|
devDependencies['sass'] = sassVersion;
|
||||||
|
} else if (schema.style === 'less') {
|
||||||
|
devDependencies['less'] = lessVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addDependenciesToPackageJson(host, dependencies, devDependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function vueInitGenerator(host: Tree, schema: InitSchema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
await jsInitGenerator(host, {
|
||||||
|
...schema,
|
||||||
|
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||||
|
skipFormat: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.push(updateDependencies(host, schema));
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default vueInitGenerator;
|
||||||
7
packages/vue/src/generators/init/schema.d.ts
vendored
Normal file
7
packages/vue/src/generators/init/schema.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface InitSchema {
|
||||||
|
skipFormat?: boolean;
|
||||||
|
js?: boolean;
|
||||||
|
rootProject?: boolean;
|
||||||
|
routing?: boolean;
|
||||||
|
style?: 'css' | 'scss' | 'less' | 'none';
|
||||||
|
}
|
||||||
37
packages/vue/src/generators/init/schema.json
Normal file
37
packages/vue/src/generators/init/schema.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "NxVueInit",
|
||||||
|
"title": "Init Vue Plugin",
|
||||||
|
"description": "Initialize a Vue Plugin.",
|
||||||
|
"cli": "nx",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use JavaScript instead of TypeScript",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"rootProject": {
|
||||||
|
"description": "Create a project at the root of the workspace",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate application with routes.",
|
||||||
|
"x-prompt": "Would you like to add React Router to this application?",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"description": "The file extension to be used for style files.",
|
||||||
|
"type": "string",
|
||||||
|
"default": "css"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`lib nested should create a local tsconfig.json 1`] = `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "vue",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
},
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add correct jest.config.ts and dependencies to package.json 1`] = `
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-tsc": "^1.8.8",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nx/cypress": "0.0.1",
|
||||||
|
"@nx/eslint-plugin": "0.0.1",
|
||||||
|
"@nx/jest": "0.0.1",
|
||||||
|
"@nx/js": "0.0.1",
|
||||||
|
"@nx/linter": "0.0.1",
|
||||||
|
"@nx/rollup": "0.0.1",
|
||||||
|
"@nx/vite": "0.0.1",
|
||||||
|
"@nx/vue": "0.0.1",
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
|
"@types/node": "16.11.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||||
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
|
"@vitejs/plugin-vue": "^4.3.1",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
|
"@vue/test-utils": "^2.4.1",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"@vue/vue3-jest": "^29.2.6",
|
||||||
|
"babel-jest": "^29.4.1",
|
||||||
|
"eslint": "~8.46.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.16.1",
|
||||||
|
"jest": "^29.4.1",
|
||||||
|
"jest-environment-jsdom": "^29.4.1",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"typescript": "~5.1.3",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add correct jest.config.ts and dependencies to package.json 2`] = `
|
||||||
|
{
|
||||||
|
"ignorePatterns": [
|
||||||
|
"**/*",
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
"*.vue",
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@nx/enforce-module-boundaries": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allow": [],
|
||||||
|
"depConstraints": [
|
||||||
|
{
|
||||||
|
"onlyDependOnLibsWithTags": [
|
||||||
|
"*",
|
||||||
|
],
|
||||||
|
"sourceTag": "*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/typescript",
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:@nx/javascript",
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"jest": true,
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"*.spec.ts",
|
||||||
|
"*.spec.tsx",
|
||||||
|
"*.spec.js",
|
||||||
|
"*.spec.jsx",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@nx",
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add correct jest.config.ts and dependencies to package.json 3`] = `
|
||||||
|
"/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'my-lib',
|
||||||
|
preset: '../jest.preset.js',
|
||||||
|
|
||||||
|
coverageDirectory: '../coverage/my-lib',
|
||||||
|
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+.[tj]sx?$': ['babel-jest'],
|
||||||
|
'^.+.vue$': [
|
||||||
|
'@vue/vue3-jest',
|
||||||
|
{
|
||||||
|
tsConfig: './tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
testMatch: ['**/__tests__/**/*.spec.ts?(x)', '**/__tests__/*.ts?(x)'],
|
||||||
|
};
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add correct jest.config.ts and dependencies to package.json 4`] = `
|
||||||
|
"{
|
||||||
|
"presets": ["@nx/js/babel"]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add vite types to tsconfigs and generate correct vite.config.ts file 1`] = `
|
||||||
|
"import vue from '@vitejs/plugin-vue';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import dts from 'vite-plugin-dts';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
cacheDir: '../node_modules/.vite/my-lib',
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
nxViteTsPaths(),
|
||||||
|
dts({
|
||||||
|
entryRoot: 'src',
|
||||||
|
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||||
|
skipDiagnostics: true,
|
||||||
|
}),
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Uncomment this if you are using workers.
|
||||||
|
// worker: {
|
||||||
|
// plugins: [ nxViteTsPaths() ],
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Configuration for building your library.
|
||||||
|
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||||
|
build: {
|
||||||
|
entry: 'src/index.ts',
|
||||||
|
name: 'my-lib',
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
external: [],
|
||||||
|
lib: {
|
||||||
|
entry: 'src/index.ts',
|
||||||
|
name: 'my-lib',
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
},
|
||||||
|
rollupOptions: { external: [] },
|
||||||
|
},
|
||||||
|
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
cache: { dir: '../node_modules/.vitest' },
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should add vue, vite and vitest to package.json 1`] = `
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-tsc": "^1.8.8",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nx/cypress": "0.0.1",
|
||||||
|
"@nx/eslint-plugin": "0.0.1",
|
||||||
|
"@nx/js": "0.0.1",
|
||||||
|
"@nx/linter": "0.0.1",
|
||||||
|
"@nx/rollup": "0.0.1",
|
||||||
|
"@nx/vite": "0.0.1",
|
||||||
|
"@nx/vue": "0.0.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||||
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
|
"@vitejs/plugin-vue": "^4.3.1",
|
||||||
|
"@vitest/coverage-c8": "~0.32.0",
|
||||||
|
"@vitest/ui": "~0.32.0",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
|
"@vue/test-utils": "^2.4.1",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"eslint": "~8.46.0",
|
||||||
|
"eslint-config-prettier": "8.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.16.1",
|
||||||
|
"jsdom": "~22.1.0",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"typescript": "~5.1.3",
|
||||||
|
"vite": "~4.3.9",
|
||||||
|
"vitest": "~0.32.0",
|
||||||
|
},
|
||||||
|
"name": "test-name",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should generate files 1`] = `
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript",
|
||||||
|
"@vue/eslint-config-prettier/skip-formatting",
|
||||||
|
"../.eslintrc.json",
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"!**/*",
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.ts",
|
||||||
|
"*.tsx",
|
||||||
|
"*.js",
|
||||||
|
"*.jsx",
|
||||||
|
"*.vue",
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`lib should ignore test files in tsconfig.lib.json 1`] = `
|
||||||
|
[
|
||||||
|
"src/**/__tests__/*",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.test.jsx",
|
||||||
|
]
|
||||||
|
`;
|
||||||
7
packages/vue/src/generators/library/files/README.md
Normal file
7
packages/vue/src/generators/library/files/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# <%= name %>
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test <%= name %>` to execute the unit tests via [Vitest](https://vitest.dev/).
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "<%= name %>",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "./index.js",
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./index.mjs",
|
||||||
|
"require": "./index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
const component: ReturnType<typeof defineComponent>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"types": [],
|
||||||
|
"lib": [
|
||||||
|
"ES2016",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/__tests__/*",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.test.jsx"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.js",
|
||||||
|
"src/**/*.jsx",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"lib": [],
|
||||||
|
"types": [
|
||||||
|
"vitest",
|
||||||
|
"vitest/globals",
|
||||||
|
"vitest/importMeta",
|
||||||
|
"vite/client",
|
||||||
|
"node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts",
|
||||||
|
"src/**/__tests__/*",
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
41
packages/vue/src/generators/library/lib/add-jest.ts
Normal file
41
packages/vue/src/generators/library/lib/add-jest.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
ensurePackage,
|
||||||
|
GeneratorCallback,
|
||||||
|
runTasksInSerial,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
|
import { nxVersion, vueJest3Version } from '../../../utils/versions';
|
||||||
|
import { setupJestProject } from '../../../utils/setup-jest';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
|
export async function addJest(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
const { configurationGenerator } = ensurePackage<typeof import('@nx/jest')>(
|
||||||
|
'@nx/jest',
|
||||||
|
nxVersion
|
||||||
|
);
|
||||||
|
const jestTask = await configurationGenerator(tree, {
|
||||||
|
project: options.name,
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
compiler: 'babel',
|
||||||
|
});
|
||||||
|
tasks.push(jestTask);
|
||||||
|
|
||||||
|
setupJestProject(tree, options.projectRoot);
|
||||||
|
tasks.push(
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@vue/vue3-jest': vueJest3Version,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
78
packages/vue/src/generators/library/lib/add-vite.ts
Normal file
78
packages/vue/src/generators/library/lib/add-vite.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
GeneratorCallback,
|
||||||
|
Tree,
|
||||||
|
ensurePackage,
|
||||||
|
runTasksInSerial,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
import { nxVersion } from '../../../utils/versions';
|
||||||
|
|
||||||
|
export async function addVite(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema
|
||||||
|
): Promise<GeneratorCallback> {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
// Set up build target
|
||||||
|
if (options.bundler === 'vite') {
|
||||||
|
const { viteConfigurationGenerator, createOrEditViteConfig } =
|
||||||
|
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||||
|
const viteTask = await viteConfigurationGenerator(tree, {
|
||||||
|
uiFramework: 'none',
|
||||||
|
project: options.name,
|
||||||
|
newProject: true,
|
||||||
|
includeLib: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
});
|
||||||
|
tasks.push(viteTask);
|
||||||
|
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.name,
|
||||||
|
includeLib: true,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
imports: [`import vue from '@vitejs/plugin-vue'`],
|
||||||
|
plugins: ['vue()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up test target
|
||||||
|
if (
|
||||||
|
options.unitTestRunner === 'vitest' &&
|
||||||
|
options.bundler !== 'vite' // tests are already configured if bundler is vite
|
||||||
|
) {
|
||||||
|
const { vitestGenerator, createOrEditViteConfig } = ensurePackage<
|
||||||
|
typeof import('@nx/vite')
|
||||||
|
>('@nx/vite', nxVersion);
|
||||||
|
const vitestTask = await vitestGenerator(tree, {
|
||||||
|
uiFramework: 'none',
|
||||||
|
project: options.name,
|
||||||
|
coverageProvider: 'c8',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
});
|
||||||
|
tasks.push(vitestTask);
|
||||||
|
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.name,
|
||||||
|
includeLib: true,
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
imports: [`import vue from '@vitejs/plugin-vue'`],
|
||||||
|
plugins: ['vue()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import type { Tree } from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
generateFiles,
|
||||||
|
joinPathFragments,
|
||||||
|
names,
|
||||||
|
offsetFromRoot,
|
||||||
|
toJS,
|
||||||
|
writeJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { getRelativePathToRootTsConfig } from '@nx/js';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
import { createTsConfig } from '../../../utils/create-ts-config';
|
||||||
|
|
||||||
|
export function createLibraryFiles(host: Tree, options: NormalizedSchema) {
|
||||||
|
const relativePathToRootTsConfig = getRelativePathToRootTsConfig(
|
||||||
|
host,
|
||||||
|
options.projectRoot
|
||||||
|
);
|
||||||
|
const substitutions = {
|
||||||
|
...options,
|
||||||
|
...names(options.name),
|
||||||
|
tmpl: '',
|
||||||
|
offsetFromRoot: offsetFromRoot(options.projectRoot),
|
||||||
|
fileName: options.fileName,
|
||||||
|
};
|
||||||
|
|
||||||
|
generateFiles(
|
||||||
|
host,
|
||||||
|
joinPathFragments(__dirname, '../files'),
|
||||||
|
options.projectRoot,
|
||||||
|
substitutions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!options.publishable && options.bundler === 'none') {
|
||||||
|
host.delete(`${options.projectRoot}/package.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.unitTestRunner !== 'vitest') {
|
||||||
|
host.delete(`${options.projectRoot}/tsconfig.spec.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.js) {
|
||||||
|
toJS(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
createTsConfig(
|
||||||
|
host,
|
||||||
|
options.projectRoot,
|
||||||
|
'lib',
|
||||||
|
options,
|
||||||
|
relativePathToRootTsConfig
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import type { Tree } from '@nx/devkit';
|
||||||
|
import { Linter } from '@nx/linter';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { normalizeOptions } from './normalize-options';
|
||||||
|
|
||||||
|
describe('normalizeOptions', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set unitTestRunner=jest and bundler=none by default', async () => {
|
||||||
|
const options = await normalizeOptions(tree, {
|
||||||
|
name: 'test',
|
||||||
|
linter: Linter.None,
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options).toMatchObject({
|
||||||
|
bundler: 'none',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set unitTestRunner=vitest by default when bundler is vite', async () => {
|
||||||
|
const options = await normalizeOptions(tree, {
|
||||||
|
name: 'test',
|
||||||
|
linter: Linter.None,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options).toMatchObject({
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set maintain unitTestRunner when bundler is vite', async () => {
|
||||||
|
const options = await normalizeOptions(tree, {
|
||||||
|
name: 'test',
|
||||||
|
linter: Linter.None,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options).toMatchObject({
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
77
packages/vue/src/generators/library/lib/normalize-options.ts
Normal file
77
packages/vue/src/generators/library/lib/normalize-options.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { getProjects, logger, normalizePath, Tree } from '@nx/devkit';
|
||||||
|
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import { NormalizedSchema, Schema } from '../schema';
|
||||||
|
|
||||||
|
export async function normalizeOptions(
|
||||||
|
host: Tree,
|
||||||
|
options: Schema
|
||||||
|
): Promise<NormalizedSchema> {
|
||||||
|
const {
|
||||||
|
projectName,
|
||||||
|
names: projectNames,
|
||||||
|
projectRoot,
|
||||||
|
importPath,
|
||||||
|
} = await determineProjectNameAndRootOptions(host, {
|
||||||
|
name: options.name,
|
||||||
|
projectType: 'library',
|
||||||
|
directory: options.directory,
|
||||||
|
importPath: options.importPath,
|
||||||
|
projectNameAndRootFormat: options.projectNameAndRootFormat,
|
||||||
|
callingGenerator: '@nx/vue:library',
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileName = projectNames.projectFileName;
|
||||||
|
|
||||||
|
const parsedTags = options.tags
|
||||||
|
? options.tags.split(',').map((s) => s.trim())
|
||||||
|
: [];
|
||||||
|
|
||||||
|
let bundler = options.bundler ?? 'none';
|
||||||
|
|
||||||
|
if (bundler === 'none') {
|
||||||
|
if (options.publishable) {
|
||||||
|
logger.warn(
|
||||||
|
`Publishable libraries cannot be used with bundler: 'none'. Defaulting to 'vite'.`
|
||||||
|
);
|
||||||
|
bundler = 'vite';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = {
|
||||||
|
...options,
|
||||||
|
bundler,
|
||||||
|
fileName,
|
||||||
|
routePath: `/${projectNames.projectFileName}`,
|
||||||
|
name: projectName,
|
||||||
|
projectRoot,
|
||||||
|
parsedTags,
|
||||||
|
importPath,
|
||||||
|
} as NormalizedSchema;
|
||||||
|
|
||||||
|
// Libraries with a bundler or is publishable must also be buildable.
|
||||||
|
normalized.bundler =
|
||||||
|
normalized.bundler !== 'none' || options.publishable ? 'vite' : 'none';
|
||||||
|
|
||||||
|
normalized.inSourceTests === normalized.minimal || normalized.inSourceTests;
|
||||||
|
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
409
packages/vue/src/generators/library/library.spec.ts
Normal file
409
packages/vue/src/generators/library/library.spec.ts
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
import {
|
||||||
|
getProjects,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { Linter } from '@nx/linter';
|
||||||
|
import { nxVersion } from '../../utils/versions';
|
||||||
|
import libraryGenerator from './library';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
|
||||||
|
describe('lib', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
let defaultSchema: Schema = {
|
||||||
|
name: 'myLib',
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
skipFormat: false,
|
||||||
|
skipTsConfig: false,
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
component: true,
|
||||||
|
strict: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
updateJson(tree, '/package.json', (json) => {
|
||||||
|
json.devDependencies = {
|
||||||
|
'@nx/cypress': nxVersion,
|
||||||
|
'@nx/rollup': nxVersion,
|
||||||
|
'@nx/vite': nxVersion,
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update project configuration', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const project = readProjectConfiguration(tree, 'my-lib');
|
||||||
|
expect(project.root).toEqual('my-lib');
|
||||||
|
expect(project.targets.build).toBeUndefined();
|
||||||
|
expect(project.targets.lint).toEqual({
|
||||||
|
executor: '@nx/linter:eslint',
|
||||||
|
outputs: ['{options.outputFile}'],
|
||||||
|
options: {
|
||||||
|
lintFilePatterns: ['my-lib/**/*.{ts,tsx,js,jsx,vue}'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add vite types to tsconfigs and generate correct vite.config.ts file', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
const tsconfigApp = readJson(tree, 'my-lib/tsconfig.lib.json');
|
||||||
|
expect(tsconfigApp.compilerOptions.types).toEqual(['vite/client']);
|
||||||
|
const tsconfigSpec = readJson(tree, 'my-lib/tsconfig.spec.json');
|
||||||
|
expect(tsconfigSpec.compilerOptions.types).toEqual([
|
||||||
|
'vitest/globals',
|
||||||
|
'vitest/importMeta',
|
||||||
|
'vite/client',
|
||||||
|
'node',
|
||||||
|
'vitest',
|
||||||
|
]);
|
||||||
|
expect(tree.read('my-lib/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update tags', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, tags: 'one,two' });
|
||||||
|
const project = readProjectConfiguration(tree, 'my-lib');
|
||||||
|
expect(project).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
tags: ['one', 'two'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add vue, vite and vitest to package.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
expect(readJson(tree, '/package.json')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add correct jest.config.ts and dependencies to package.json', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, unitTestRunner: 'jest' });
|
||||||
|
expect(readJson(tree, '/package.json')).toMatchSnapshot();
|
||||||
|
expect(readJson(tree, '.eslintrc.json')).toMatchSnapshot();
|
||||||
|
expect(tree.read('my-lib/jest.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('my-lib/.babelrc', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update root tsconfig.base.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
|
||||||
|
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
|
||||||
|
'my-lib/src/index.ts',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create tsconfig.base.json out of tsconfig.json', async () => {
|
||||||
|
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||||
|
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
|
||||||
|
expect(tree.exists('tsconfig.base.json')).toEqual(true);
|
||||||
|
const tsconfigJson = readJson(tree, 'tsconfig.base.json');
|
||||||
|
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
|
||||||
|
'my-lib/src/index.ts',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update root tsconfig.base.json (no existing path mappings)', async () => {
|
||||||
|
updateJson(tree, 'tsconfig.base.json', (json) => {
|
||||||
|
json.compilerOptions.paths = undefined;
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
|
||||||
|
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
|
||||||
|
'my-lib/src/index.ts',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a local tsconfig.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
|
||||||
|
const tsconfigJson = readJson(tree, 'my-lib/tsconfig.json');
|
||||||
|
expect(tsconfigJson.extends).toBe('../tsconfig.base.json');
|
||||||
|
expect(tsconfigJson.references).toEqual([
|
||||||
|
{
|
||||||
|
path: './tsconfig.lib.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: './tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extend the tsconfig.lib.json with tsconfig.spec.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const tsconfigJson = readJson(tree, 'my-lib/tsconfig.spec.json');
|
||||||
|
expect(tsconfigJson.extends).toEqual('./tsconfig.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extend ./tsconfig.json with tsconfig.lib.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const tsconfigJson = readJson(tree, 'my-lib/tsconfig.lib.json');
|
||||||
|
expect(tsconfigJson.extends).toEqual('./tsconfig.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore test files in tsconfig.lib.json', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
const tsconfigJson = readJson(tree, 'my-lib/tsconfig.lib.json');
|
||||||
|
expect(tsconfigJson.exclude).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate files', async () => {
|
||||||
|
await libraryGenerator(tree, defaultSchema);
|
||||||
|
expect(tree.exists('my-lib/package.json')).toBeFalsy();
|
||||||
|
expect(tree.exists('my-lib/src/index.ts')).toBeTruthy();
|
||||||
|
expect(tree.exists('my-lib/src/components/my-lib.vue')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.exists('my-lib/src/components/__tests__/my-lib.spec.ts')
|
||||||
|
).toBeTruthy();
|
||||||
|
const eslintJson = readJson(tree, 'my-lib/.eslintrc.json');
|
||||||
|
expect(eslintJson).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('nested', () => {
|
||||||
|
it('should update tags and implicitDependencies', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
directory: 'myDir',
|
||||||
|
tags: 'one',
|
||||||
|
});
|
||||||
|
const myLib = readProjectConfiguration(tree, 'my-dir-my-lib');
|
||||||
|
expect(myLib).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
tags: ['one'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
name: 'myLib2',
|
||||||
|
directory: 'myDir',
|
||||||
|
tags: 'one,two',
|
||||||
|
});
|
||||||
|
|
||||||
|
const myLib2 = readProjectConfiguration(tree, 'my-dir-my-lib2');
|
||||||
|
expect(myLib2).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
tags: ['one', 'two'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate files', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, directory: 'myDir' });
|
||||||
|
expect(tree.exists('my-dir/my-lib/src/index.ts')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.exists('my-dir/my-lib/src/components/my-dir-my-lib.vue')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.exists(
|
||||||
|
'my-dir/my-lib/src/components/__tests__/my-dir-my-lib.spec.ts'
|
||||||
|
)
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update project configurations', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, directory: 'myDir' });
|
||||||
|
const config = readProjectConfiguration(tree, 'my-dir-my-lib');
|
||||||
|
|
||||||
|
expect(config.root).toEqual('my-dir/my-lib');
|
||||||
|
expect(config.targets.lint).toEqual({
|
||||||
|
executor: '@nx/linter:eslint',
|
||||||
|
outputs: ['{options.outputFile}'],
|
||||||
|
options: {
|
||||||
|
lintFilePatterns: ['my-dir/my-lib/**/*.{ts,tsx,js,jsx,vue}'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update root tsconfig.base.json', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, directory: 'myDir' });
|
||||||
|
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
|
||||||
|
expect(tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib']).toEqual(
|
||||||
|
['my-dir/my-lib/src/index.ts']
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tsconfigJson.compilerOptions.paths['my-dir-my-lib/*']
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a local tsconfig.json', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, directory: 'myDir' });
|
||||||
|
|
||||||
|
const tsconfigJson = readJson(tree, 'my-dir/my-lib/tsconfig.json');
|
||||||
|
expect(tsconfigJson).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--no-component', () => {
|
||||||
|
it('should not generate components or styles', async () => {
|
||||||
|
await libraryGenerator(tree, { ...defaultSchema, component: false });
|
||||||
|
|
||||||
|
expect(tree.exists('my-lib/src/lib')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--unit-test-runner none', () => {
|
||||||
|
it('should not generate test configuration', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('my-lib/tsconfig.spec.json')).toBeFalsy();
|
||||||
|
const config = readProjectConfiguration(tree, 'my-lib');
|
||||||
|
expect(config.targets.test).toBeUndefined();
|
||||||
|
expect(config.targets.lint).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"executor": "@nx/linter:eslint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"my-lib/**/*.{ts,tsx,js,jsx,vue}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{options.outputFile}",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--publishable', () => {
|
||||||
|
it('should add build targets', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
publishable: true,
|
||||||
|
importPath: '@proj/my-lib',
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectsConfigurations = getProjects(tree);
|
||||||
|
|
||||||
|
expect(projectsConfigurations.get('my-lib').targets.build).toMatchObject({
|
||||||
|
executor: '@nx/vite:build',
|
||||||
|
outputs: ['{options.outputPath}'],
|
||||||
|
options: {
|
||||||
|
outputPath: 'dist/my-lib',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if no importPath is provided with publishable', async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
directory: 'myDir',
|
||||||
|
publishable: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add package.json and .babelrc', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
publishable: true,
|
||||||
|
importPath: '@proj/my-lib',
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = readJson(tree, '/my-lib/package.json');
|
||||||
|
expect(packageJson.name).toEqual('@proj/my-lib');
|
||||||
|
expect(tree.exists('/my-lib/.babelrc'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--js', () => {
|
||||||
|
it('should generate JS files', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
js: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('/my-lib/src/index.js')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--importPath', () => {
|
||||||
|
it('should update the package.json & tsconfig with the given import path', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
publishable: true,
|
||||||
|
directory: 'myDir',
|
||||||
|
importPath: '@myorg/lib',
|
||||||
|
});
|
||||||
|
const packageJson = readJson(tree, 'my-dir/my-lib/package.json');
|
||||||
|
const tsconfigJson = readJson(tree, '/tsconfig.base.json');
|
||||||
|
|
||||||
|
expect(packageJson.name).toBe('@myorg/lib');
|
||||||
|
expect(
|
||||||
|
tsconfigJson.compilerOptions.paths[packageJson.name]
|
||||||
|
).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if the same importPath has already been used', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
name: 'myLib1',
|
||||||
|
publishable: true,
|
||||||
|
importPath: '@myorg/lib',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
name: 'myLib2',
|
||||||
|
publishable: true,
|
||||||
|
importPath: '@myorg/lib',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toContain(
|
||||||
|
'You already have a library using the import path'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect.assertions(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--no-strict', () => {
|
||||||
|
it('should not add options for strict mode', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
strict: false,
|
||||||
|
});
|
||||||
|
const tsconfigJson = readJson(tree, '/my-lib/tsconfig.json');
|
||||||
|
|
||||||
|
expect(tsconfigJson.compilerOptions.strict).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--setParserOptionsProject', () => {
|
||||||
|
it('should set the parserOptions.project in the eslintrc.json file', async () => {
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultSchema,
|
||||||
|
setParserOptionsProject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = readJson(tree, 'my-lib/.eslintrc.json');
|
||||||
|
expect(eslintConfig.overrides[0].parserOptions.project).toEqual([
|
||||||
|
'my-lib/tsconfig.*?.json',
|
||||||
|
]);
|
||||||
|
expect(eslintConfig.overrides[0].files).toContain('*.vue');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
102
packages/vue/src/generators/library/library.ts
Normal file
102
packages/vue/src/generators/library/library.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
joinPathFragments,
|
||||||
|
runTasksInSerial,
|
||||||
|
toJS,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { addTsConfigPath } from '@nx/js';
|
||||||
|
import { vueInitGenerator } from '../init/init';
|
||||||
|
import { Schema } from './schema';
|
||||||
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
|
import { addLinting } from '../../utils/add-linting';
|
||||||
|
import { createLibraryFiles } from './lib/create-library-files';
|
||||||
|
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
||||||
|
import componentGenerator from '../component/component';
|
||||||
|
import { addVite } from './lib/add-vite';
|
||||||
|
import { addJest } from './lib/add-jest';
|
||||||
|
|
||||||
|
export async function libraryGenerator(tree: Tree, schema: Schema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
const options = await normalizeOptions(tree, schema);
|
||||||
|
if (options.publishable === true && !schema.importPath) {
|
||||||
|
throw new Error(
|
||||||
|
`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, options.name, {
|
||||||
|
root: options.projectRoot,
|
||||||
|
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
|
||||||
|
projectType: 'library',
|
||||||
|
tags: options.parsedTags,
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
await vueInitGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
skipFormat: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
extractTsConfigBase(tree);
|
||||||
|
|
||||||
|
tasks.push(await addLinting(tree, options, 'lib'));
|
||||||
|
|
||||||
|
createLibraryFiles(tree, options);
|
||||||
|
|
||||||
|
tasks.push(await addVite(tree, options));
|
||||||
|
|
||||||
|
if (options.unitTestRunner === 'jest')
|
||||||
|
tasks.push(await addJest(tree, options));
|
||||||
|
|
||||||
|
if (options.component) {
|
||||||
|
tasks.push(
|
||||||
|
await componentGenerator(tree, {
|
||||||
|
name: options.fileName,
|
||||||
|
project: options.name,
|
||||||
|
flat: true,
|
||||||
|
skipTests:
|
||||||
|
options.unitTestRunner === 'none' ||
|
||||||
|
(options.unitTestRunner === 'vitest' &&
|
||||||
|
options.inSourceTests == true),
|
||||||
|
export: true,
|
||||||
|
routing: options.routing,
|
||||||
|
js: options.js,
|
||||||
|
pascalCaseFiles: options.pascalCaseFiles,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
skipFormat: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.publishable || options.bundler !== 'none') {
|
||||||
|
updateJson(tree, `${options.projectRoot}/package.json`, (json) => {
|
||||||
|
json.name = options.importPath;
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.skipTsConfig) {
|
||||||
|
addTsConfigPath(tree, options.importPath, [
|
||||||
|
joinPathFragments(
|
||||||
|
options.projectRoot,
|
||||||
|
'./src',
|
||||||
|
'index.' + (options.js ? 'js' : 'ts')
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.js) toJS(tree);
|
||||||
|
|
||||||
|
if (!options.skipFormat) await formatFiles(tree);
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default libraryGenerator;
|
||||||
41
packages/vue/src/generators/library/schema.d.ts
vendored
Normal file
41
packages/vue/src/generators/library/schema.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||||
|
import type { Linter } from '@nx/linter';
|
||||||
|
import type { SupportedStyles } from '../../../typings/style';
|
||||||
|
|
||||||
|
export interface Schema {
|
||||||
|
appProject?: string;
|
||||||
|
bundler?: 'none' | 'vite';
|
||||||
|
component?: boolean;
|
||||||
|
directory?: string;
|
||||||
|
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||||
|
importPath?: string;
|
||||||
|
inSourceTests?: boolean;
|
||||||
|
js?: boolean;
|
||||||
|
linter: Linter;
|
||||||
|
name: string;
|
||||||
|
pascalCaseFiles?: boolean;
|
||||||
|
publishable?: boolean;
|
||||||
|
routing?: boolean;
|
||||||
|
setParserOptionsProject?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
skipPackageJson?: boolean;
|
||||||
|
skipTsConfig?: boolean;
|
||||||
|
strict?: boolean;
|
||||||
|
tags?: string;
|
||||||
|
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||||
|
minimal?: boolean;
|
||||||
|
e2eTestRunner?: 'cypress' | 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedSchema extends Schema {
|
||||||
|
js: boolean;
|
||||||
|
name: string;
|
||||||
|
linter: Linter;
|
||||||
|
fileName: string;
|
||||||
|
projectRoot: string;
|
||||||
|
routePath: string;
|
||||||
|
parsedTags: string[];
|
||||||
|
appMain?: string;
|
||||||
|
appSourceRoot?: string;
|
||||||
|
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||||
|
}
|
||||||
139
packages/vue/src/generators/library/schema.json
Normal file
139
packages/vue/src/generators/library/schema.json
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "NxVueLibrary",
|
||||||
|
"title": "Create a Vue Library",
|
||||||
|
"description": "Create a Vue Library for an Nx workspace.",
|
||||||
|
"type": "object",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"command": "nx g lib mylib --directory=libs/mylib",
|
||||||
|
"description": "Generate `libs/mylib`"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "nx g lib mylib --appProject=myapp",
|
||||||
|
"description": "Generate a library with routes and add them to `myapp`"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Library name",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the library?",
|
||||||
|
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A directory where the lib is placed.",
|
||||||
|
"alias": "dir",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"projectNameAndRootFormat": {
|
||||||
|
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["as-provided", "derived"]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"description": "The tool to use for running lint checks.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["eslint", "none"],
|
||||||
|
"default": "eslint"
|
||||||
|
},
|
||||||
|
"unitTestRunner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["vitest", "jest", "none"],
|
||||||
|
"description": "Test runner to use for unit tests.",
|
||||||
|
"x-prompt": "What unit test runner should be used?"
|
||||||
|
},
|
||||||
|
"inSourceTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files."
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Add tags to the library (used for linting).",
|
||||||
|
"alias": "t"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"skipTsConfig": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Do not update `tsconfig.json` for development experience.",
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"pascalCaseFiles": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use pascal case component file name (e.g. `App.tsx`).",
|
||||||
|
"alias": "P",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate library with routes."
|
||||||
|
},
|
||||||
|
"appProject": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The application project to add the library route to.",
|
||||||
|
"alias": "a"
|
||||||
|
},
|
||||||
|
"publishable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Create a publishable library."
|
||||||
|
},
|
||||||
|
"importPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The library name used to import it, like `@myorg/my-awesome-lib`."
|
||||||
|
},
|
||||||
|
"component": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate a default component.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"strict": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to enable tsconfig strict mode or not.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"setParserOptionsProject": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"bundler": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
|
||||||
|
"enum": ["none", "vite"],
|
||||||
|
"default": "none",
|
||||||
|
"x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"skipPackageJson": {
|
||||||
|
"description": "Do not add dependencies to `package.json`.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
},
|
||||||
|
"minimal": {
|
||||||
|
"description": "Create a Vue library with a minimal setup, no separate test files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"]
|
||||||
|
}
|
||||||
149
packages/vue/src/utils/add-linting.ts
Normal file
149
packages/vue/src/utils/add-linting.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { Tree } from 'nx/src/generators/tree';
|
||||||
|
import { Linter, lintProjectGenerator } from '@nx/linter';
|
||||||
|
import { joinPathFragments } from 'nx/src/utils/path';
|
||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
runTasksInSerial,
|
||||||
|
updateJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { extraEslintDependencies } from './lint';
|
||||||
|
import {
|
||||||
|
addExtendsToLintConfig,
|
||||||
|
isEslintConfigSupported,
|
||||||
|
} from '@nx/linter/src/generators/utils/eslint-file';
|
||||||
|
|
||||||
|
export async function addLinting(
|
||||||
|
host: Tree,
|
||||||
|
options: {
|
||||||
|
linter: Linter;
|
||||||
|
name: string;
|
||||||
|
projectRoot: string;
|
||||||
|
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||||
|
setParserOptionsProject?: boolean;
|
||||||
|
skipPackageJson?: boolean;
|
||||||
|
rootProject?: boolean;
|
||||||
|
},
|
||||||
|
projectType: 'lib' | 'app'
|
||||||
|
) {
|
||||||
|
if (options.linter === Linter.EsLint) {
|
||||||
|
const lintTask = await lintProjectGenerator(host, {
|
||||||
|
linter: options.linter,
|
||||||
|
project: options.name,
|
||||||
|
tsConfigPaths: [
|
||||||
|
joinPathFragments(options.projectRoot, `tsconfig.${projectType}.json`),
|
||||||
|
],
|
||||||
|
unitTestRunner: options.unitTestRunner,
|
||||||
|
eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx,vue}`],
|
||||||
|
skipFormat: true,
|
||||||
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isEslintConfigSupported(host)) {
|
||||||
|
addExtendsToLintConfig(host, options.projectRoot, [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
editEslintConfigFiles(host, options.projectRoot, options.rootProject);
|
||||||
|
|
||||||
|
let installTask = () => {};
|
||||||
|
if (!options.skipPackageJson) {
|
||||||
|
installTask = addDependenciesToPackageJson(
|
||||||
|
host,
|
||||||
|
extraEslintDependencies.dependencies,
|
||||||
|
extraEslintDependencies.devDependencies
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runTasksInSerial(lintTask, installTask);
|
||||||
|
} else {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editEslintConfigFiles(
|
||||||
|
tree: Tree,
|
||||||
|
projectRoot: string,
|
||||||
|
rootProject?: boolean
|
||||||
|
) {
|
||||||
|
if (tree.exists(joinPathFragments(projectRoot, 'eslint.config.js'))) {
|
||||||
|
const fileName = joinPathFragments(projectRoot, 'eslint.config.js');
|
||||||
|
updateJson(tree, fileName, (json) => {
|
||||||
|
let updated = false;
|
||||||
|
for (let override of json.overrides) {
|
||||||
|
if (override.parserOptions) {
|
||||||
|
if (!override.files.includes('*.vue')) {
|
||||||
|
override.files.push('*.vue');
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updated) {
|
||||||
|
json.overrides = [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const fileName = joinPathFragments(projectRoot, '.eslintrc.json');
|
||||||
|
updateJson(tree, fileName, (json) => {
|
||||||
|
let updated = false;
|
||||||
|
for (let override of json.overrides) {
|
||||||
|
if (override.parserOptions) {
|
||||||
|
if (!override.files.includes('*.vue')) {
|
||||||
|
override.files.push('*.vue');
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updated) {
|
||||||
|
json.overrides = [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit root config too
|
||||||
|
if (tree.exists('.eslintrc.base.json')) {
|
||||||
|
updateJson(tree, '.eslintrc.base.json', (json) => {
|
||||||
|
for (let override of json.overrides) {
|
||||||
|
if (
|
||||||
|
override.rules &&
|
||||||
|
'@nx/enforce-module-boundaries' in override.rules
|
||||||
|
) {
|
||||||
|
if (!override.files.includes('*.vue')) {
|
||||||
|
override.files.push('*.vue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
} else if (tree.exists('.eslintrc.json') && !rootProject) {
|
||||||
|
updateJson(tree, '.eslintrc.json', (json) => {
|
||||||
|
for (let override of json.overrides) {
|
||||||
|
if (
|
||||||
|
override.rules &&
|
||||||
|
'@nx/enforce-module-boundaries' in override.rules
|
||||||
|
) {
|
||||||
|
if (!override.files.includes('*.vue')) {
|
||||||
|
override.files.push('*.vue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
packages/vue/src/utils/ast-utils.ts
Normal file
35
packages/vue/src/utils/ast-utils.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type * as ts from 'typescript';
|
||||||
|
import { findNodes } from '@nx/js';
|
||||||
|
import { ChangeType, StringChange } from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
|
export function addImport(
|
||||||
|
source: ts.SourceFile,
|
||||||
|
statement: string
|
||||||
|
): StringChange[] {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
const allImports = findNodes(source, tsModule.SyntaxKind.ImportDeclaration);
|
||||||
|
if (allImports.length > 0) {
|
||||||
|
const lastImport = allImports[allImports.length - 1];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: ChangeType.Insert,
|
||||||
|
index: lastImport.end + 1,
|
||||||
|
text: `\n${statement}\n`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: ChangeType.Insert,
|
||||||
|
index: 0,
|
||||||
|
text: `\n${statement}\n`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
84
packages/vue/src/utils/create-ts-config.ts
Normal file
84
packages/vue/src/utils/create-ts-config.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Tree, updateJson, writeJson } from '@nx/devkit';
|
||||||
|
import * as shared from '@nx/js/src/utils/typescript/create-ts-config';
|
||||||
|
|
||||||
|
export function createTsConfig(
|
||||||
|
host: Tree,
|
||||||
|
projectRoot: string,
|
||||||
|
type: 'app' | 'lib',
|
||||||
|
options: {
|
||||||
|
strict?: boolean;
|
||||||
|
style?: string;
|
||||||
|
bundler?: string;
|
||||||
|
rootProject?: boolean;
|
||||||
|
unitTestRunner?: string;
|
||||||
|
},
|
||||||
|
relativePathToRootTsConfig: string
|
||||||
|
) {
|
||||||
|
const json = {
|
||||||
|
compilerOptions: {
|
||||||
|
allowJs: true,
|
||||||
|
esModuleInterop: false,
|
||||||
|
allowSyntheticDefaultImports: true,
|
||||||
|
strict: options.strict,
|
||||||
|
jsx: 'preserve',
|
||||||
|
jsxImportSource: 'vue',
|
||||||
|
moduleResolution: 'bundler',
|
||||||
|
resolveJsonModule: true,
|
||||||
|
verbatimModuleSyntax: options.unitTestRunner !== 'jest',
|
||||||
|
},
|
||||||
|
files: [],
|
||||||
|
include: [],
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
path: type === 'app' ? './tsconfig.app.json' : './tsconfig.lib.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (options.unitTestRunner === 'vitest') {
|
||||||
|
json.references.push({
|
||||||
|
path: './tsconfig.spec.json',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (options.bundler === 'vite' && 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 extractTsConfigBase(host: Tree) {
|
||||||
|
shared.extractTsConfigBase(host);
|
||||||
|
|
||||||
|
if (host.exists('vite.config.ts')) {
|
||||||
|
const vite = host.read('vite.config.ts').toString();
|
||||||
|
host.write(
|
||||||
|
'vite.config.ts',
|
||||||
|
vite.replace(`projects: []`, `projects: ['tsconfig.base.json']`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/vue/src/utils/lint.ts
Normal file
29
packages/vue/src/utils/lint.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
eslintPluginVueVersion,
|
||||||
|
vueEslintConfigPrettierVersion,
|
||||||
|
vueEslintConfigTypescriptVersion,
|
||||||
|
} from './versions';
|
||||||
|
|
||||||
|
export const extraEslintDependencies = {
|
||||||
|
dependencies: {},
|
||||||
|
devDependencies: {
|
||||||
|
'@vue/eslint-config-prettier': vueEslintConfigPrettierVersion,
|
||||||
|
'@vue/eslint-config-typescript': vueEslintConfigTypescriptVersion,
|
||||||
|
'eslint-plugin-vue': eslintPluginVueVersion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extendVueEslintJson = (json: any) => {
|
||||||
|
const { extends: pluginExtends, ...config } = json;
|
||||||
|
|
||||||
|
return {
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting',
|
||||||
|
...(pluginExtends || []),
|
||||||
|
],
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
};
|
||||||
124
packages/vue/src/utils/setup-jest.ts
Normal file
124
packages/vue/src/utils/setup-jest.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
applyChangesToString,
|
||||||
|
ChangeType,
|
||||||
|
joinPathFragments,
|
||||||
|
offsetFromRoot,
|
||||||
|
Tree,
|
||||||
|
writeJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { ObjectLiteralExpression } from 'typescript';
|
||||||
|
|
||||||
|
export function setupJestProject(tree: Tree, projectRoot: string) {
|
||||||
|
updateJestConfigTsFile(tree, projectRoot);
|
||||||
|
writeBabelRcFile(tree, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeBabelRcFile(tree: Tree, projectRoot: string) {
|
||||||
|
writeJson(tree, joinPathFragments(projectRoot, '.babelrc'), {
|
||||||
|
presets: ['@nx/js/babel'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateJestConfigTsFile(tree: Tree, projectRoot: string) {
|
||||||
|
const jestConfigTs = joinPathFragments(projectRoot, 'jest.config.ts');
|
||||||
|
if (tree.exists(jestConfigTs)) {
|
||||||
|
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||||
|
let fileContent = tree.read(jestConfigTs, 'utf-8');
|
||||||
|
const sourceFile = tsquery.ast(fileContent);
|
||||||
|
|
||||||
|
const settingsObject = tsquery.query(
|
||||||
|
sourceFile,
|
||||||
|
'ObjectLiteralExpression'
|
||||||
|
)?.[0] as ObjectLiteralExpression;
|
||||||
|
|
||||||
|
if (settingsObject) {
|
||||||
|
const moduleFileExtensions = tsquery.query(
|
||||||
|
sourceFile,
|
||||||
|
`PropertyAssignment:has(Identifier:has([name="moduleFileExtensions"]))`
|
||||||
|
)?.[0];
|
||||||
|
|
||||||
|
if (moduleFileExtensions) {
|
||||||
|
fileContent = applyChangesToString(fileContent, [
|
||||||
|
{
|
||||||
|
type: ChangeType.Delete,
|
||||||
|
start: moduleFileExtensions.getStart(),
|
||||||
|
length:
|
||||||
|
moduleFileExtensions.getEnd() -
|
||||||
|
moduleFileExtensions.getStart() +
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformProperty = tsquery.query(
|
||||||
|
sourceFile,
|
||||||
|
`PropertyAssignment:has(Identifier:has([name="transform"]))`
|
||||||
|
)?.[0];
|
||||||
|
|
||||||
|
if (transformProperty) {
|
||||||
|
fileContent = applyChangesToString(fileContent, [
|
||||||
|
{
|
||||||
|
type: ChangeType.Delete,
|
||||||
|
start: transformProperty.getStart(),
|
||||||
|
length:
|
||||||
|
transformProperty.getEnd() - transformProperty.getStart() + 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsObjectUpdated = tsquery.query(
|
||||||
|
fileContent,
|
||||||
|
'ObjectLiteralExpression'
|
||||||
|
)?.[0] as ObjectLiteralExpression;
|
||||||
|
|
||||||
|
fileContent = applyChangesToString(fileContent, [
|
||||||
|
{
|
||||||
|
type: ChangeType.Insert,
|
||||||
|
index: settingsObjectUpdated.getEnd() - 1,
|
||||||
|
text: `,
|
||||||
|
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': ['babel-jest'],
|
||||||
|
'^.+\\.vue$': [
|
||||||
|
'@vue/vue3-jest',
|
||||||
|
{
|
||||||
|
tsConfig: './tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
testMatch: ['**/__tests__/**/*.spec.ts?(x)', '**/__tests__/*.ts?(x)'],
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
tree.write(jestConfigTs, fileContent);
|
||||||
|
} else {
|
||||||
|
writeNewJestConfig(tree, projectRoot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writeNewJestConfig(tree, projectRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeNewJestConfig(tree: Tree, projectRoot: string) {
|
||||||
|
tree.write(
|
||||||
|
joinPathFragments(projectRoot, 'jest.config.js'),
|
||||||
|
`
|
||||||
|
module.exports = {
|
||||||
|
preset: '${offsetFromRoot}/jest.preset.js',
|
||||||
|
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': ['babel-jest'],
|
||||||
|
'^.+\\.vue$': [
|
||||||
|
'@vue/vue3-jest',
|
||||||
|
{
|
||||||
|
tsConfig: './tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
testMatch: ['**/__tests__/**/*.spec.ts?(x)', '**/__tests__/*.ts?(x)'],
|
||||||
|
};
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
29
packages/vue/src/utils/test-utils.ts
Normal file
29
packages/vue/src/utils/test-utils.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { addProjectConfiguration, names, Tree } from '@nx/devkit';
|
||||||
|
import { Linter } from '@nx/linter';
|
||||||
|
import applicationGenerator from '../generators/application/application';
|
||||||
|
|
||||||
|
export async function createApp(tree: Tree, appName: string): Promise<any> {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
e2eTestRunner: 'none',
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
skipFormat: true,
|
||||||
|
style: 'css',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
name: appName,
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createLib(tree: Tree, libName: string): Promise<any> {
|
||||||
|
const { fileName } = names(libName);
|
||||||
|
|
||||||
|
tree.write(`/${fileName}/src/index.ts`, ``);
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, fileName, {
|
||||||
|
tags: [],
|
||||||
|
root: `${fileName}`,
|
||||||
|
projectType: 'library',
|
||||||
|
sourceRoot: `${fileName}/src`,
|
||||||
|
targets: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
23
packages/vue/src/utils/versions.ts
Normal file
23
packages/vue/src/utils/versions.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export const nxVersion = require('../../package.json').version;
|
||||||
|
|
||||||
|
// vue core
|
||||||
|
export const vueVersion = '^3.3.4';
|
||||||
|
export const vueTscVersion = '^1.8.8';
|
||||||
|
export const vueRouterVersion = '^4.2.4';
|
||||||
|
|
||||||
|
// build deps
|
||||||
|
export const vueTsconfigVersion = '^0.4.0';
|
||||||
|
|
||||||
|
// test deps
|
||||||
|
export const vueTestUtilsVersion = '^2.4.1';
|
||||||
|
export const vitePluginVueVersion = '^4.3.1';
|
||||||
|
export const vueJest3Version = '^29.2.6';
|
||||||
|
|
||||||
|
// linting deps
|
||||||
|
export const vueEslintConfigPrettierVersion = '^8.0.0';
|
||||||
|
export const vueEslintConfigTypescriptVersion = '^11.0.3';
|
||||||
|
export const eslintPluginVueVersion = '^9.16.1';
|
||||||
|
|
||||||
|
// other deps
|
||||||
|
export const sassVersion = '1.62.1';
|
||||||
|
export const lessVersion = '3.12.2';
|
||||||
@ -184,10 +184,7 @@ describe('app', () => {
|
|||||||
path: './tsconfig.spec.json',
|
path: './tsconfig.spec.json',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(tsconfig.compilerOptions.types).toMatchObject([
|
expect(tsconfig.compilerOptions.types).toMatchObject(['vite/client']);
|
||||||
'vite/client',
|
|
||||||
'vitest',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(tree.exists('my-app-e2e/cypress.config.ts')).toBeTruthy();
|
expect(tree.exists('my-app-e2e/cypress.config.ts')).toBeTruthy();
|
||||||
expect(tree.exists('my-app/index.html')).toBeTruthy();
|
expect(tree.exists('my-app/index.html')).toBeTruthy();
|
||||||
@ -555,6 +552,7 @@ describe('app', () => {
|
|||||||
"vitest/importMeta",
|
"vitest/importMeta",
|
||||||
"vite/client",
|
"vite/client",
|
||||||
"node",
|
"node",
|
||||||
|
"vitest",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
expect(
|
expect(
|
||||||
@ -660,10 +658,7 @@ describe('app', () => {
|
|||||||
|
|
||||||
it('should create correct tsconfig compilerOptions', () => {
|
it('should create correct tsconfig compilerOptions', () => {
|
||||||
const tsconfigJson = readJson(viteAppTree, '/my-app/tsconfig.json');
|
const tsconfigJson = readJson(viteAppTree, '/my-app/tsconfig.json');
|
||||||
expect(tsconfigJson.compilerOptions.types).toMatchObject([
|
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
|
||||||
'vite/client',
|
|
||||||
'vitest',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create index.html and vite.config file at the root of the app', () => {
|
it('should create index.html and vite.config file at the root of the app', () => {
|
||||||
|
|||||||
@ -1134,6 +1134,136 @@ Nx comes with local caching already built-in (check your \`nx.json\`). On CI you
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for VueMonorepo preset 1`] = `
|
||||||
|
"# Proj
|
||||||
|
|
||||||
|
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
||||||
|
|
||||||
|
✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨
|
||||||
|
|
||||||
|
## Start the app
|
||||||
|
|
||||||
|
To start the development server run \`nx serve app1\`. Open your browser and navigate to http://localhost:4200/. Happy coding!
|
||||||
|
|
||||||
|
## Generate code
|
||||||
|
|
||||||
|
If you happen to use Nx plugins, you can leverage code generators that might come with it.
|
||||||
|
|
||||||
|
Run \`nx list\` to get a list of available plugins and whether they have generators. Then run \`nx list <plugin-name>\` to see what generators are available.
|
||||||
|
|
||||||
|
Learn more about [Nx generators on the docs](https://nx.dev/plugin-features/use-code-generators).
|
||||||
|
|
||||||
|
## Running tasks
|
||||||
|
|
||||||
|
To execute tasks with Nx use the following syntax:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx <target> <project> <...options>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
You can also run multiple targets:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx run-many -t <target1> <target2>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
..or add \`-p\` to filter specific projects
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx run-many -t <target1> <target2> -p <proj1> <proj2>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Targets can be defined in the \`package.json\` or \`projects.json\`. Learn more [in the docs](https://nx.dev/core-features/run-tasks).
|
||||||
|
|
||||||
|
## Want better Editor Integration?
|
||||||
|
|
||||||
|
Have a look at the [Nx Console extensions](https://nx.dev/nx-console). It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users.
|
||||||
|
|
||||||
|
## Ready to deploy?
|
||||||
|
|
||||||
|
Just run \`nx build demoapp\` to build the application. The build artifacts will be stored in the \`dist/\` directory, ready to be deployed.
|
||||||
|
|
||||||
|
## Set up CI!
|
||||||
|
|
||||||
|
Nx comes with local caching already built-in (check your \`nx.json\`). On CI you might want to go a step further.
|
||||||
|
|
||||||
|
- [Set up remote caching](https://nx.dev/core-features/share-your-cache)
|
||||||
|
- [Set up task distribution across multiple machines](https://nx.dev/core-features/distribute-task-execution)
|
||||||
|
- [Learn more how to setup CI](https://nx.dev/recipes/ci)
|
||||||
|
|
||||||
|
## Connect with us!
|
||||||
|
|
||||||
|
- [Join the community](https://nx.dev/community)
|
||||||
|
- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||||
|
- [Follow us on Twitter](https://twitter.com/nxdevtools)
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for VueStandalone preset 1`] = `
|
||||||
|
"# Proj
|
||||||
|
|
||||||
|
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
||||||
|
|
||||||
|
✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨
|
||||||
|
|
||||||
|
## Start the app
|
||||||
|
|
||||||
|
To start the development server run \`nx serve app1\`. Open your browser and navigate to http://localhost:4200/. Happy coding!
|
||||||
|
|
||||||
|
## Generate code
|
||||||
|
|
||||||
|
If you happen to use Nx plugins, you can leverage code generators that might come with it.
|
||||||
|
|
||||||
|
Run \`nx list\` to get a list of available plugins and whether they have generators. Then run \`nx list <plugin-name>\` to see what generators are available.
|
||||||
|
|
||||||
|
Learn more about [Nx generators on the docs](https://nx.dev/plugin-features/use-code-generators).
|
||||||
|
|
||||||
|
## Running tasks
|
||||||
|
|
||||||
|
To execute tasks with Nx use the following syntax:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx <target> <project> <...options>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
You can also run multiple targets:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx run-many -t <target1> <target2>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
..or add \`-p\` to filter specific projects
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
nx run-many -t <target1> <target2> -p <proj1> <proj2>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Targets can be defined in the \`package.json\` or \`projects.json\`. Learn more [in the docs](https://nx.dev/core-features/run-tasks).
|
||||||
|
|
||||||
|
## Want better Editor Integration?
|
||||||
|
|
||||||
|
Have a look at the [Nx Console extensions](https://nx.dev/nx-console). It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users.
|
||||||
|
|
||||||
|
## Ready to deploy?
|
||||||
|
|
||||||
|
Just run \`nx build demoapp\` to build the application. The build artifacts will be stored in the \`dist/\` directory, ready to be deployed.
|
||||||
|
|
||||||
|
## Set up CI!
|
||||||
|
|
||||||
|
Nx comes with local caching already built-in (check your \`nx.json\`). On CI you might want to go a step further.
|
||||||
|
|
||||||
|
- [Set up remote caching](https://nx.dev/core-features/share-your-cache)
|
||||||
|
- [Set up task distribution across multiple machines](https://nx.dev/core-features/distribute-task-execution)
|
||||||
|
- [Learn more how to setup CI](https://nx.dev/recipes/ci)
|
||||||
|
|
||||||
|
## Connect with us!
|
||||||
|
|
||||||
|
- [Join the community](https://nx.dev/community)
|
||||||
|
- [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||||||
|
- [Follow us on Twitter](https://twitter.com/nxdevtools)
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for WebComponents preset 1`] = `
|
exports[`@nx/workspace:generateWorkspaceFiles README.md should be created for WebComponents preset 1`] = `
|
||||||
"# Proj
|
"# Proj
|
||||||
|
|
||||||
|
|||||||
@ -119,6 +119,19 @@ function getPresetDependencies({
|
|||||||
case Preset.NextJsStandalone:
|
case Preset.NextJsStandalone:
|
||||||
return { dependencies: { '@nx/next': nxVersion }, dev: {} };
|
return { dependencies: { '@nx/next': nxVersion }, dev: {} };
|
||||||
|
|
||||||
|
case Preset.VueMonorepo:
|
||||||
|
case Preset.VueStandalone:
|
||||||
|
return {
|
||||||
|
dependencies: {},
|
||||||
|
dev: {
|
||||||
|
'@nx/vue': nxVersion,
|
||||||
|
'@nx/cypress': e2eTestRunner === 'cypress' ? nxVersion : undefined,
|
||||||
|
'@nx/playwright':
|
||||||
|
e2eTestRunner === 'playwright' ? nxVersion : undefined,
|
||||||
|
'@nx/vite': nxVersion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
case Preset.ReactMonorepo:
|
case Preset.ReactMonorepo:
|
||||||
case Preset.ReactStandalone:
|
case Preset.ReactStandalone:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -36,6 +36,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
|
|||||||
[
|
[
|
||||||
Preset.ReactMonorepo,
|
Preset.ReactMonorepo,
|
||||||
Preset.ReactStandalone,
|
Preset.ReactStandalone,
|
||||||
|
Preset.VueMonorepo,
|
||||||
|
Preset.VueStandalone,
|
||||||
Preset.AngularMonorepo,
|
Preset.AngularMonorepo,
|
||||||
Preset.AngularStandalone,
|
Preset.AngularStandalone,
|
||||||
Preset.Nest,
|
Preset.Nest,
|
||||||
|
|||||||
@ -67,6 +67,7 @@ function createAppsAndLibsFolders(tree: Tree, options: NormalizedSchema) {
|
|||||||
} else if (
|
} else if (
|
||||||
options.preset === Preset.AngularStandalone ||
|
options.preset === Preset.AngularStandalone ||
|
||||||
options.preset === Preset.ReactStandalone ||
|
options.preset === Preset.ReactStandalone ||
|
||||||
|
options.preset === Preset.VueStandalone ||
|
||||||
options.preset === Preset.NodeStandalone ||
|
options.preset === Preset.NodeStandalone ||
|
||||||
options.preset === Preset.NextJsStandalone ||
|
options.preset === Preset.NextJsStandalone ||
|
||||||
options.preset === Preset.TsStandalone ||
|
options.preset === Preset.TsStandalone ||
|
||||||
@ -127,6 +128,7 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
|
|||||||
const filesDirName =
|
const filesDirName =
|
||||||
options.preset === Preset.AngularStandalone ||
|
options.preset === Preset.AngularStandalone ||
|
||||||
options.preset === Preset.ReactStandalone ||
|
options.preset === Preset.ReactStandalone ||
|
||||||
|
options.preset === Preset.VueStandalone ||
|
||||||
options.preset === Preset.NodeStandalone ||
|
options.preset === Preset.NodeStandalone ||
|
||||||
options.preset === Preset.NextJsStandalone ||
|
options.preset === Preset.NextJsStandalone ||
|
||||||
options.preset === Preset.TsStandalone
|
options.preset === Preset.TsStandalone
|
||||||
@ -181,6 +183,7 @@ function addNpmScripts(tree: Tree, options: NormalizedSchema) {
|
|||||||
if (
|
if (
|
||||||
options.preset === Preset.AngularStandalone ||
|
options.preset === Preset.AngularStandalone ||
|
||||||
options.preset === Preset.ReactStandalone ||
|
options.preset === Preset.ReactStandalone ||
|
||||||
|
options.preset === Preset.VueStandalone ||
|
||||||
options.preset === Preset.NodeStandalone ||
|
options.preset === Preset.NodeStandalone ||
|
||||||
options.preset === Preset.NextJsStandalone
|
options.preset === Preset.NextJsStandalone
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -79,6 +79,26 @@ describe('new', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate necessary npm dependencies for vue preset', async () => {
|
||||||
|
await newGenerator(tree, {
|
||||||
|
...defaultOptions,
|
||||||
|
name: 'my-workspace',
|
||||||
|
directory: 'my-workspace',
|
||||||
|
appName: 'app',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
preset: Preset.VueMonorepo,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { devDependencies } = readJson(tree, 'my-workspace/package.json');
|
||||||
|
expect(devDependencies).toStrictEqual({
|
||||||
|
'@nx/vue': nxVersion,
|
||||||
|
'@nx/cypress': nxVersion,
|
||||||
|
'@nx/vite': nxVersion,
|
||||||
|
'@nx/workspace': nxVersion,
|
||||||
|
nx: nxVersion,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate necessary npm dependencies for angular preset', async () => {
|
it('should generate necessary npm dependencies for angular preset', async () => {
|
||||||
await newGenerator(tree, {
|
await newGenerator(tree, {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
|
|||||||
@ -75,3 +75,23 @@ exports[`preset should create files (preset = react-standalone bundler = webpack
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`preset should create files (preset = vue-standalone) 1`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "proj:build:development",
|
||||||
|
"hmr": true,
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "proj:build:production",
|
||||||
|
"hmr": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"executor": "@nx/vite:dev-server",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "proj:build",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@ -41,6 +41,17 @@ describe('preset', () => {
|
|||||||
expect(readProjectConfiguration(tree, 'proj').targets.serve).toBeDefined();
|
expect(readProjectConfiguration(tree, 'proj').targets.serve).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should create files (preset = ${Preset.VueMonorepo})`, async () => {
|
||||||
|
await presetGenerator(tree, {
|
||||||
|
name: 'proj',
|
||||||
|
preset: Preset.VueMonorepo,
|
||||||
|
style: 'css',
|
||||||
|
linter: 'eslint',
|
||||||
|
});
|
||||||
|
expect(tree.exists('apps/proj/src/main.ts')).toBe(true);
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.serve).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it(`should create files (preset = ${Preset.NextJs})`, async () => {
|
it(`should create files (preset = ${Preset.NextJs})`, async () => {
|
||||||
await presetGenerator(tree, {
|
await presetGenerator(tree, {
|
||||||
name: 'proj',
|
name: 'proj',
|
||||||
@ -99,4 +110,17 @@ describe('preset', () => {
|
|||||||
readProjectConfiguration(tree, 'proj').targets.serve
|
readProjectConfiguration(tree, 'proj').targets.serve
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should create files (preset = ${Preset.VueStandalone})`, async () => {
|
||||||
|
await presetGenerator(tree, {
|
||||||
|
name: 'proj',
|
||||||
|
preset: Preset.VueStandalone,
|
||||||
|
style: 'css',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
});
|
||||||
|
expect(tree.exists('vite.config.ts')).toBe(true);
|
||||||
|
expect(
|
||||||
|
readProjectConfiguration(tree, 'proj').targets.serve
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -76,6 +76,32 @@ async function createPreset(tree: Tree, options: Schema) {
|
|||||||
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
||||||
unitTestRunner: options.bundler === 'vite' ? 'vitest' : 'jest',
|
unitTestRunner: options.bundler === 'vite' ? 'vitest' : 'jest',
|
||||||
});
|
});
|
||||||
|
} else if (options.preset === Preset.VueMonorepo) {
|
||||||
|
const { applicationGenerator: vueApplicationGenerator } = require('@nx' +
|
||||||
|
'/vue');
|
||||||
|
|
||||||
|
return vueApplicationGenerator(tree, {
|
||||||
|
name: options.name,
|
||||||
|
directory: join('apps', options.name),
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
style: options.style,
|
||||||
|
linter: options.linter,
|
||||||
|
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
||||||
|
});
|
||||||
|
} else if (options.preset === Preset.VueStandalone) {
|
||||||
|
const { applicationGenerator: vueApplicationGenerator } = require('@nx' +
|
||||||
|
'/vue');
|
||||||
|
|
||||||
|
return vueApplicationGenerator(tree, {
|
||||||
|
name: options.name,
|
||||||
|
directory: '.',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
style: options.style,
|
||||||
|
linter: options.linter,
|
||||||
|
rootProject: true,
|
||||||
|
e2eTestRunner: options.e2eTestRunner ?? 'cypress',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
} else if (options.preset === Preset.NextJs) {
|
} else if (options.preset === Preset.NextJs) {
|
||||||
const { applicationGenerator: nextApplicationGenerator } = require('@nx' +
|
const { applicationGenerator: nextApplicationGenerator } = require('@nx' +
|
||||||
'/next');
|
'/next');
|
||||||
|
|||||||
@ -16,6 +16,8 @@ export enum Preset {
|
|||||||
ReactStandalone = 'react-standalone',
|
ReactStandalone = 'react-standalone',
|
||||||
NextJsStandalone = 'nextjs-standalone',
|
NextJsStandalone = 'nextjs-standalone',
|
||||||
ReactNative = 'react-native',
|
ReactNative = 'react-native',
|
||||||
|
VueMonorepo = 'vue-monorepo',
|
||||||
|
VueStandalone = 'vue-standalone',
|
||||||
Expo = 'expo',
|
Expo = 'expo',
|
||||||
NextJs = 'next',
|
NextJs = 'next',
|
||||||
Nest = 'nest',
|
Nest = 'nest',
|
||||||
|
|||||||
@ -101,7 +101,7 @@
|
|||||||
"@nx/typedoc-theme": ["typedoc-theme/src/index.ts"],
|
"@nx/typedoc-theme": ["typedoc-theme/src/index.ts"],
|
||||||
"@nx/vite": ["packages/vite"],
|
"@nx/vite": ["packages/vite"],
|
||||||
"@nx/vite/*": ["packages/vite/*"],
|
"@nx/vite/*": ["packages/vite/*"],
|
||||||
"@nx/vue": ["packages/vue/index.ts"],
|
"@nx/vue": ["packages/vue"],
|
||||||
"@nx/vue/*": ["packages/vue/*"],
|
"@nx/vue/*": ["packages/vue/*"],
|
||||||
"@nx/web": ["packages/web"],
|
"@nx/web": ["packages/web"],
|
||||||
"@nx/web/*": ["packages/web/*"],
|
"@nx/web/*": ["packages/web/*"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user