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`
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -139,7 +139,7 @@ Package manager to use
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -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",
|
||||
"id": "webpack",
|
||||
|
||||
@ -361,6 +361,16 @@
|
||||
"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",
|
||||
"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/react`,
|
||||
`@nx/storybook`,
|
||||
`@nx/vue`,
|
||||
`@nx/vite`,
|
||||
`@nx/web`,
|
||||
`@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);
|
||||
}
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
||||
@ -62,6 +62,15 @@ interface AngularArguments extends BaseArguments {
|
||||
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 {
|
||||
stack: 'node';
|
||||
workspaceType: 'standalone' | 'integrated';
|
||||
@ -78,6 +87,7 @@ type Arguments =
|
||||
| NoneArguments
|
||||
| ReactArguments
|
||||
| AngularArguments
|
||||
| VueArguments
|
||||
| NodeArguments
|
||||
| UnknownStackArguments;
|
||||
|
||||
@ -347,7 +357,7 @@ async function determineFolder(
|
||||
|
||||
async function determineStack(
|
||||
parsedArgs: yargs.Arguments<Arguments>
|
||||
): Promise<'none' | 'react' | 'angular' | 'node' | 'unknown'> {
|
||||
): Promise<'none' | 'react' | 'angular' | 'vue' | 'node' | 'unknown'> {
|
||||
if (parsedArgs.preset) {
|
||||
switch (parsedArgs.preset) {
|
||||
case Preset.Angular:
|
||||
@ -360,7 +370,9 @@ async function determineStack(
|
||||
case Preset.NextJs:
|
||||
case Preset.NextJsStandalone:
|
||||
return 'react';
|
||||
|
||||
case Preset.VueStandalone:
|
||||
case Preset.VueMonorepo:
|
||||
return 'vue';
|
||||
case Preset.Nest:
|
||||
case Preset.NodeStandalone:
|
||||
case Preset.Express:
|
||||
@ -379,7 +391,7 @@ async function determineStack(
|
||||
}
|
||||
|
||||
const { stack } = await enquirer.prompt<{
|
||||
stack: 'none' | 'react' | 'angular' | 'node';
|
||||
stack: 'none' | 'react' | 'angular' | 'node' | 'vue';
|
||||
}>([
|
||||
{
|
||||
name: 'stack',
|
||||
@ -394,6 +406,10 @@ async function determineStack(
|
||||
name: `react`,
|
||||
message: `React: Configures a React application with your framework of choice.`,
|
||||
},
|
||||
{
|
||||
name: `vue`,
|
||||
message: `Vue: Configures a Vue application with modern tooling.`,
|
||||
},
|
||||
{
|
||||
name: `angular`,
|
||||
message: `Angular: Configures a Angular application with modern tooling.`,
|
||||
@ -419,6 +435,8 @@ async function determinePresetOptions(
|
||||
return determineReactOptions(parsedArgs);
|
||||
case 'angular':
|
||||
return determineAngularOptions(parsedArgs);
|
||||
case 'vue':
|
||||
return determineVueOptions(parsedArgs);
|
||||
case 'node':
|
||||
return determineNodeOptions(parsedArgs);
|
||||
default:
|
||||
@ -589,6 +607,69 @@ async function determineReactOptions(
|
||||
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(
|
||||
parsedArgs: yargs.Arguments<AngularArguments>
|
||||
): Promise<Partial<Arguments>> {
|
||||
@ -847,7 +928,9 @@ async function determineStandaloneOrMonorepo(): Promise<
|
||||
}
|
||||
|
||||
async function determineAppName(
|
||||
parsedArgs: yargs.Arguments<ReactArguments | AngularArguments | NodeArguments>
|
||||
parsedArgs: yargs.Arguments<
|
||||
ReactArguments | AngularArguments | NodeArguments | VueArguments
|
||||
>
|
||||
): Promise<string> {
|
||||
if (parsedArgs.appName) return parsedArgs.appName;
|
||||
|
||||
|
||||
@ -19,6 +19,10 @@ export const presetOptions: { name: Preset; message: string }[] = [
|
||||
name: Preset.AngularMonorepo,
|
||||
message: 'angular [a monorepo with a single Angular application]',
|
||||
},
|
||||
{
|
||||
name: Preset.VueMonorepo,
|
||||
message: 'vue [a monorepo with a single Vue application]',
|
||||
},
|
||||
{
|
||||
name: Preset.NextJs,
|
||||
message: 'next.js [a monorepo with a single Next.js application]',
|
||||
|
||||
@ -9,6 +9,8 @@ export enum Preset {
|
||||
AngularStandalone = 'angular-standalone',
|
||||
ReactMonorepo = 'react-monorepo',
|
||||
ReactStandalone = 'react-standalone',
|
||||
VueMonorepo = 'vue-monorepo',
|
||||
VueStandalone = 'vue-standalone',
|
||||
NextJs = 'next',
|
||||
NextJsStandalone = 'nextjs-standalone',
|
||||
ReactNative = 'react-native',
|
||||
|
||||
@ -63,6 +63,7 @@ describe('app', () => {
|
||||
'vitest/importMeta',
|
||||
'vite/client',
|
||||
'node',
|
||||
'vitest',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -115,9 +115,8 @@ export async function applicationGeneratorInternal(
|
||||
addProject(host, options);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
const { viteConfigurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/vite')
|
||||
>('@nx/vite', nxVersion);
|
||||
const { createOrEditViteConfig, viteConfigurationGenerator } =
|
||||
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||
// 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
|
||||
if (
|
||||
@ -138,6 +137,28 @@ export async function applicationGeneratorInternal(
|
||||
skipFormat: true,
|
||||
});
|
||||
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') {
|
||||
const { webpackInitGenerator } = ensurePackage<
|
||||
typeof import('@nx/webpack')
|
||||
@ -167,10 +188,9 @@ export async function applicationGeneratorInternal(
|
||||
}
|
||||
|
||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||
const { vitestGenerator } = ensurePackage<typeof import('@nx/vite')>(
|
||||
'@nx/vite',
|
||||
nxVersion
|
||||
);
|
||||
const { createOrEditViteConfig, vitestGenerator } = ensurePackage<
|
||||
typeof import('@nx/vite')
|
||||
>('@nx/vite', nxVersion);
|
||||
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
@ -180,6 +200,28 @@ export async function applicationGeneratorInternal(
|
||||
skipFormat: true,
|
||||
});
|
||||
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 (
|
||||
|
||||
@ -80,6 +80,7 @@ describe('lib', () => {
|
||||
'vitest/importMeta',
|
||||
'vite/client',
|
||||
'node',
|
||||
'vitest',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -69,9 +69,8 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
||||
|
||||
// Set up build target
|
||||
if (options.buildable && options.bundler === 'vite') {
|
||||
const { viteConfigurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/vite')
|
||||
>('@nx/vite', nxVersion);
|
||||
const { viteConfigurationGenerator, createOrEditViteConfig } =
|
||||
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||
const viteTask = await viteConfigurationGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.name,
|
||||
@ -84,6 +83,28 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
||||
testEnvironment: 'jsdom',
|
||||
});
|
||||
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') {
|
||||
const rollupTask = await addRollupBuildTarget(host, options);
|
||||
tasks.push(rollupTask);
|
||||
@ -120,10 +141,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
||||
options.unitTestRunner === 'vitest' &&
|
||||
options.bundler !== 'vite' // tests are already configured if bundler is vite
|
||||
) {
|
||||
const { vitestGenerator } = ensurePackage<typeof import('@nx/vite')>(
|
||||
'@nx/vite',
|
||||
nxVersion
|
||||
);
|
||||
const { vitestGenerator, createOrEditViteConfig } = ensurePackage<
|
||||
typeof import('@nx/vite')
|
||||
>('@nx/vite', nxVersion);
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.name,
|
||||
@ -133,6 +153,24 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
||||
testEnvironment: 'jsdom',
|
||||
});
|
||||
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) {
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`@nx/vite:configuration library mode should add config for building library 1`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
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: [
|
||||
react(),
|
||||
nxViteTsPaths(),
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
// 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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
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/react-lib-nonb-jest',
|
||||
|
||||
plugins: [
|
||||
react(),
|
||||
nxViteTsPaths(),
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
// 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 { defineConfig } from 'vite';
|
||||
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({
|
||||
// Configuration for building your library.
|
||||
@ -165,12 +165,8 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
...[
|
||||
nxViteTsPaths(),
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||
@ -180,9 +176,7 @@ export default defineConfig({
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
cache: {
|
||||
dir: '../../node_modules/.vitest',
|
||||
},
|
||||
cache: { dir: '../../node_modules/.vitest' },
|
||||
environment: 'jsdom',
|
||||
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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
@ -350,7 +350,6 @@ describe('@nx/vite:configuration', () => {
|
||||
const { Confirm } = require('enquirer');
|
||||
const confirmSpy = jest.spyOn(Confirm.prototype, 'run');
|
||||
confirmSpy.mockResolvedValue(true);
|
||||
expect.assertions(2);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (schema.includeVitest) {
|
||||
const vitestTask = await vitestGenerator(tree, {
|
||||
|
||||
@ -45,7 +45,7 @@ function checkDependenciesInstalled(host: Tree, schema: InitGeneratorSchema) {
|
||||
devDependencies['happy-dom'] = happyDomVersion;
|
||||
} else if (schema.testEnvironment === 'edge-runtime') {
|
||||
devDependencies['@edge-runtime/vm'] = edgeRuntimeVmVersion;
|
||||
} else if (schema.testEnvironment !== 'node') {
|
||||
} else if (schema.testEnvironment !== 'node' && schema.testEnvironment) {
|
||||
logger.info(
|
||||
`A custom environment was provided: ${schema.testEnvironment}. You need to install it manually.`
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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 react from '@vitejs/plugin-react';
|
||||
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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
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`] = `
|
||||
"/// <reference types="vitest" />
|
||||
"/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
@ -52,6 +52,26 @@ export async function vitestGenerator(
|
||||
tasks.push(initTask);
|
||||
|
||||
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(
|
||||
tree,
|
||||
{
|
||||
@ -62,6 +82,7 @@ export async function vitestGenerator(
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
createFiles(tree, schema, root);
|
||||
updateTsConfig(tree, schema, root);
|
||||
@ -89,16 +110,11 @@ function updateTsConfig(
|
||||
options: VitestGeneratorSchema,
|
||||
projectRoot: string
|
||||
) {
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.spec.json'))) {
|
||||
updateJson(
|
||||
tree,
|
||||
joinPathFragments(projectRoot, 'tsconfig.spec.json'),
|
||||
(json) => {
|
||||
if (!json.compilerOptions?.types?.includes('vitest')) {
|
||||
if (json.compilerOptions?.types) {
|
||||
json.compilerOptions.types.push('vitest');
|
||||
@ -108,7 +124,41 @@ function updateTsConfig(
|
||||
}
|
||||
}
|
||||
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) {
|
||||
const tsconfigLibPath = joinPathFragments(projectRoot, 'tsconfig.lib.json');
|
||||
|
||||
@ -96,6 +96,7 @@ describe('vitest generator', () => {
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest",
|
||||
],
|
||||
},
|
||||
"extends": "./tsconfig.json",
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ensureViteConfigIsCorrect should add build and test options if defineConfig is empty 1`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
|
||||
// Configuration for building your library.
|
||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||
build: {
|
||||
plugins: [react(),
|
||||
nxViteTsPaths()],build: {
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points.
|
||||
entry: 'src/index.ts',
|
||||
@ -27,18 +26,7 @@ import { joinPathFragments } from '@nx/devkit';
|
||||
// External packages that should not be bundled into your library.
|
||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||
}
|
||||
},plugins: [
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
},test: {
|
||||
globals: true,
|
||||
cache: {
|
||||
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`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
|
||||
@ -74,19 +61,9 @@ import { defineConfig } from 'vite';
|
||||
// External packages that should not be bundled into your library.
|
||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||
}
|
||||
},plugins: [
|
||||
...[
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
],
|
||||
},plugins: [react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
@ -102,11 +79,10 @@ import { defineConfig } from 'vite';
|
||||
`;
|
||||
|
||||
exports[`ensureViteConfigIsCorrect should add build options if build options don't exist 1`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
|
||||
@ -126,19 +102,9 @@ import { defineConfig } from 'vite';
|
||||
// External packages that should not be bundled into your library.
|
||||
external: ["'react', 'react-dom', 'react/jsx-runtime'"]
|
||||
}
|
||||
},plugins: [
|
||||
...[
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
],
|
||||
},plugins: [react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
@ -154,11 +120,10 @@ import { defineConfig } from 'vite';
|
||||
`;
|
||||
|
||||
exports[`ensureViteConfigIsCorrect should add build options if defineConfig is not used 1`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
export default {
|
||||
// Configuration for building your library.
|
||||
@ -185,19 +150,9 @@ import { defineConfig } from 'vite';
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
},
|
||||
plugins: [
|
||||
...[
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
],
|
||||
plugins: [react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
};
|
||||
"
|
||||
`;
|
||||
@ -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`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
...[
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
],
|
||||
plugins: [react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
@ -257,10 +201,10 @@ import { defineConfig } from 'vite';
|
||||
},
|
||||
|
||||
build: {
|
||||
...{
|
||||
my: 'option',
|
||||
},
|
||||
...{"lib":{"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},"rollupOptions":{"external":["'react', 'react-dom', 'react/jsx-runtime'"]}}
|
||||
'my': 'option',
|
||||
'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`] = `
|
||||
"
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig } from 'vite';
|
||||
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 { joinPathFragments } from '@nx/devkit';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../../',
|
||||
}),
|
||||
],
|
||||
plugins: [dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }),
|
||||
react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
// Configuration for building your library.
|
||||
// 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`] = `
|
||||
"import dts from 'vite-plugin-dts';
|
||||
import { joinPathFragments } from '@nx/devkit';
|
||||
"import dts from 'vite-plugin-dts';import { joinPathFragments } from '@nx/devkit'
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
...[
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
],
|
||||
plugins: [react(),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
...{
|
||||
my: 'option',
|
||||
},
|
||||
...{"globals":true,"cache":{"dir":"../node_modules/.vitest"},"environment":"jsdom","include":["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"]}
|
||||
'my': 'option',
|
||||
'globals': true,
|
||||
'cache': {"dir":"../node_modules/.vitest"},
|
||||
'environment': "jsdom",
|
||||
'include': ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||
|
||||
},
|
||||
|
||||
build: {
|
||||
...{
|
||||
my: 'option',
|
||||
},
|
||||
...{"lib":{"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es","cjs"]},"rollupOptions":{"external":["'react', 'react-dom', 'react/jsx-runtime'"]}}
|
||||
'my': 'option',
|
||||
'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');
|
||||
if (
|
||||
!indexHtmlContent.includes(
|
||||
`<script type="module" src="${mainPath}"></script>`
|
||||
`<script type='module' src='${mainPath}'></script>`
|
||||
)
|
||||
) {
|
||||
tree.write(
|
||||
`${projectConfig.root}/index.html`,
|
||||
indexHtmlContent.replace(
|
||||
'</body>',
|
||||
`<script type="module" src="${mainPath}"></script>
|
||||
`<script type='module' src='${mainPath}'></script>
|
||||
</body>`
|
||||
)
|
||||
);
|
||||
@ -461,25 +461,37 @@ export function moveAndEditIndexHtml(
|
||||
tree.write(
|
||||
`${projectConfig.root}/index.html`,
|
||||
`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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" />
|
||||
<meta charset='UTF-8' />
|
||||
<link rel='icon' href='/favicon.ico' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||
<title>Vite</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="${mainPath}"></script>
|
||||
<div id='root'></div>
|
||||
<script type='module' src='${mainPath}'></script>
|
||||
</body>
|
||||
</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(
|
||||
tree: Tree,
|
||||
options: ViteConfigurationGeneratorSchema,
|
||||
options: ViteConfigFileOptions,
|
||||
onlyVitest: boolean,
|
||||
projectAlreadyHasViteTargets?: TargetFlags
|
||||
) {
|
||||
@ -505,33 +517,32 @@ export function createOrEditViteConfig(
|
||||
},
|
||||
rollupOptions: {
|
||||
// External packages that should not be bundled into your library.
|
||||
external: [${
|
||||
options.uiFramework === 'react'
|
||||
? "'react', 'react-dom', 'react/jsx-runtime'"
|
||||
: ''
|
||||
}]
|
||||
external: [${options.rollupOptionsExternal ?? ''}]
|
||||
}
|
||||
},`
|
||||
: ``;
|
||||
|
||||
const dtsPlugin = onlyVitest
|
||||
? ''
|
||||
: options.includeLib
|
||||
? `dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),`
|
||||
: '';
|
||||
const imports: string[] = options.imports ? options.imports : [];
|
||||
|
||||
const dtsImportLine = onlyVitest
|
||||
? ''
|
||||
: options.includeLib
|
||||
? `import dts from 'vite-plugin-dts';\nimport * as path from 'path';`
|
||||
: '';
|
||||
if (!onlyVitest && options.includeLib) {
|
||||
imports.push(
|
||||
`import dts from 'vite-plugin-dts'`,
|
||||
`import * as path from 'path'`
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
? `test: {
|
||||
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
|
||||
? ''
|
||||
: options.includeLib
|
||||
@ -583,14 +585,6 @@ export function createOrEditViteConfig(
|
||||
host: 'localhost',
|
||||
},`;
|
||||
|
||||
const pluginOption = `
|
||||
plugins: [
|
||||
${dtsPlugin}
|
||||
${reactPlugin}
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
`;
|
||||
|
||||
const workerOption = `
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
@ -607,9 +601,8 @@ export function createOrEditViteConfig(
|
||||
viteConfigPath,
|
||||
options,
|
||||
buildOption,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
cacheDir,
|
||||
offsetFromRoot(projectConfig.root),
|
||||
@ -619,17 +612,17 @@ export function createOrEditViteConfig(
|
||||
}
|
||||
|
||||
viteConfigContent = `
|
||||
/// <reference types="vitest" />
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
${reactPluginImportLine}
|
||||
${imports.join(';\n')}${imports.length ? ';' : ''}
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
${dtsImportLine}
|
||||
|
||||
export default defineConfig({
|
||||
${cacheDir}
|
||||
${devServerOption}
|
||||
${previewServerOption}
|
||||
${pluginOption}
|
||||
|
||||
plugins: [${plugins.join(',\n')}],
|
||||
${workerOption}
|
||||
${buildOption}
|
||||
${defineOption}
|
||||
@ -774,21 +767,28 @@ export async function handleUnknownExecutors(projectName: string) {
|
||||
function handleViteConfigFileExists(
|
||||
tree: Tree,
|
||||
viteConfigPath: string,
|
||||
options: ViteConfigurationGeneratorSchema,
|
||||
options: ViteConfigFileOptions,
|
||||
buildOption: string,
|
||||
dtsPlugin: string,
|
||||
dtsImportLine: string,
|
||||
pluginOption: string,
|
||||
imports: string[],
|
||||
plugins: string[],
|
||||
testOption: string,
|
||||
cacheDir: string,
|
||||
offsetFromRoot: string,
|
||||
projectAlreadyHasViteTargets?: TargetFlags
|
||||
) {
|
||||
if (projectAlreadyHasViteTargets.build && projectAlreadyHasViteTargets.test) {
|
||||
if (
|
||||
projectAlreadyHasViteTargets?.build &&
|
||||
projectAlreadyHasViteTargets?.test
|
||||
) {
|
||||
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 = {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
@ -797,10 +797,7 @@ function handleViteConfigFileExists(
|
||||
formats: ['es', 'cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external:
|
||||
options.uiFramework === 'react'
|
||||
? ['react', 'react-dom', 'react/jsx-runtime']
|
||||
: [],
|
||||
external: options.rollupOptionsExternal ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
@ -818,13 +815,12 @@ function handleViteConfigFileExists(
|
||||
viteConfigPath,
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
cacheDir,
|
||||
projectAlreadyHasViteTargets
|
||||
projectAlreadyHasViteTargets ?? {}
|
||||
);
|
||||
|
||||
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" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
@ -28,14 +26,12 @@ export const someBuildOptions = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
@ -58,7 +54,7 @@ export const noContentDefineConfig = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({});
|
||||
`;
|
||||
@ -85,14 +81,12 @@ export const configNoDefineConfig = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
};
|
||||
`;
|
||||
@ -101,14 +95,12 @@ export const noBuildOptionsHasTestOption = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
@ -127,14 +119,12 @@ export const someBuildOptionsSomeTestOption = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
test: {
|
||||
@ -152,21 +142,15 @@ export const hasEverything = `
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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 { joinPathFragments } from '@nx/devkit';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),
|
||||
dts({ entryRoot: 'src', tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true }),
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../../',
|
||||
}),
|
||||
nxViteTsPaths(),
|
||||
],
|
||||
|
||||
// Configuration for building your library.
|
||||
@ -246,19 +230,9 @@ export const testOptionObject = {
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
};
|
||||
|
||||
export const dtsPlugin = `dts({
|
||||
entryRoot: 'src',
|
||||
tsConfigFilePath: joinPathFragments(__dirname, 'tsconfig.lib.json'),
|
||||
skipDiagnostics: true,
|
||||
}),`;
|
||||
export const dtsImportLine = `import dts from 'vite-plugin-dts';\nimport { joinPathFragments } from '@nx/devkit';`;
|
||||
export const imports = [
|
||||
`import dts from 'vite-plugin-dts'`,
|
||||
`import { joinPathFragments } from '@nx/devkit'`,
|
||||
];
|
||||
|
||||
export const pluginOption = `
|
||||
plugins: [
|
||||
${dtsPlugin}
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
`;
|
||||
export const plugins = [`react()`, `nxViteTsPaths()`];
|
||||
|
||||
@ -537,15 +537,13 @@ export function mockReactLibNonBuildableVitestRunnerGenerator(
|
||||
`/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
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({
|
||||
|
||||
plugins: [
|
||||
nxViteTsPaths(),
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
|
||||
test: {
|
||||
|
||||
@ -6,13 +6,12 @@ import {
|
||||
buildOptionObject,
|
||||
conditionalConfig,
|
||||
configNoDefineConfig,
|
||||
dtsImportLine,
|
||||
dtsPlugin,
|
||||
imports,
|
||||
hasEverything,
|
||||
noBuildOptions,
|
||||
noBuildOptionsHasTestOption,
|
||||
noContentDefineConfig,
|
||||
pluginOption,
|
||||
plugins,
|
||||
someBuildOptions,
|
||||
someBuildOptionsSomeTestOption,
|
||||
testOption,
|
||||
@ -34,9 +33,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -59,9 +57,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -84,9 +81,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -109,9 +105,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -134,9 +129,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -159,9 +153,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -178,9 +171,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -197,9 +189,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
@ -216,9 +207,8 @@ describe('ensureViteConfigIsCorrect', () => {
|
||||
'apps/my-app/vite.config.ts',
|
||||
buildOption,
|
||||
buildOptionObject,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption,
|
||||
imports,
|
||||
plugins,
|
||||
testOption,
|
||||
testOptionObject,
|
||||
'',
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { applyChangesToString, ChangeType, Tree } from '@nx/devkit';
|
||||
import { findNodes } from '@nx/js';
|
||||
import { TargetFlags } from './generator-utils';
|
||||
import type { Node, ReturnStatement } from 'typescript';
|
||||
import type {
|
||||
ArrayLiteralExpression,
|
||||
Node,
|
||||
PropertyAssignment,
|
||||
ReturnStatement,
|
||||
} from 'typescript';
|
||||
|
||||
export function ensureViteConfigIsCorrect(
|
||||
tree: Tree,
|
||||
path: string,
|
||||
buildConfigString: string,
|
||||
buildConfigObject: {},
|
||||
dtsPlugin: string,
|
||||
dtsImportLine: string,
|
||||
pluginOption: string,
|
||||
imports: string[],
|
||||
plugins: string[],
|
||||
testConfigString: string,
|
||||
testConfigObject: {},
|
||||
cacheDir: string,
|
||||
@ -30,13 +34,6 @@ export function ensureViteConfigIsCorrect(
|
||||
}
|
||||
|
||||
if (!projectAlreadyHasViteTargets?.build && buildConfigString?.length) {
|
||||
updatedContent = handlePluginNode(
|
||||
updatedContent ?? fileContent,
|
||||
dtsPlugin,
|
||||
dtsImportLine,
|
||||
pluginOption
|
||||
);
|
||||
|
||||
updatedContent = handleBuildOrTestNode(
|
||||
updatedContent ?? fileContent,
|
||||
buildConfigString,
|
||||
@ -45,12 +42,17 @@ export function ensureViteConfigIsCorrect(
|
||||
);
|
||||
}
|
||||
|
||||
updatedContent =
|
||||
handlePluginNode(updatedContent ?? fileContent, imports, plugins) ??
|
||||
updatedContent;
|
||||
|
||||
if (cacheDir?.length) {
|
||||
updatedContent = handleCacheDirNode(
|
||||
updatedContent ?? fileContent,
|
||||
cacheDir
|
||||
);
|
||||
}
|
||||
|
||||
if (updatedContent) {
|
||||
tree.write(path, updatedContent);
|
||||
return true;
|
||||
@ -66,20 +68,36 @@ function handleBuildOrTestNode(
|
||||
name: 'build' | 'test'
|
||||
): string | undefined {
|
||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||
const buildNode = tsquery.query(
|
||||
const buildOrTestNode = tsquery.query(
|
||||
updatedFileContent,
|
||||
`PropertyAssignment:has(Identifier[name="${name}"])`
|
||||
);
|
||||
|
||||
if (buildNode.length) {
|
||||
if (buildOrTestNode.length) {
|
||||
return tsquery.replace(
|
||||
updatedFileContent,
|
||||
`PropertyAssignment:has(Identifier[name="${name}"])`,
|
||||
(node: Node) => {
|
||||
const found = tsquery.query(node, 'ObjectLiteralExpression');
|
||||
(node: PropertyAssignment) => {
|
||||
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}: {
|
||||
...${found?.[0].getText()},
|
||||
...${JSON.stringify(configContentObject)}
|
||||
${updatedPropsString}
|
||||
}`;
|
||||
}
|
||||
);
|
||||
@ -173,7 +191,6 @@ function transformCurrentBuildObject(
|
||||
|
||||
const currentBuildObjectStart = returnStatements[index].getStart();
|
||||
const currentBuildObjectEnd = returnStatements[index].getEnd();
|
||||
|
||||
const newReturnObject = tsquery.replace(
|
||||
returnStatements[index].getText(),
|
||||
'ObjectLiteralExpression',
|
||||
@ -209,7 +226,6 @@ function transformConditionalConfig(
|
||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||
const { SyntaxKind } = require('typescript');
|
||||
const functionBlock = tsquery.query(conditionalConfig[0], 'Block');
|
||||
|
||||
const ifStatement = tsquery.query(functionBlock?.[0], 'IfStatement');
|
||||
|
||||
const binaryExpressions = tsquery.query(ifStatement?.[0], 'BinaryExpression');
|
||||
@ -235,7 +251,6 @@ function transformConditionalConfig(
|
||||
if (!buildExists) {
|
||||
if (serveExists && elseKeywordExists) {
|
||||
// build options live inside the else block
|
||||
|
||||
return (
|
||||
transformCurrentBuildObject(
|
||||
returnStatements?.length - 1,
|
||||
@ -278,12 +293,10 @@ function transformConditionalConfig(
|
||||
|
||||
function handlePluginNode(
|
||||
appFileContent: string,
|
||||
dtsPlugin: string,
|
||||
dtsImportLine: string,
|
||||
pluginOption: string
|
||||
imports: string[],
|
||||
plugins: string[]
|
||||
): string | undefined {
|
||||
const { tsquery } = require('@phenomnomnominal/tsquery');
|
||||
|
||||
const file = tsquery.ast(appFileContent);
|
||||
const pluginsNode = tsquery.query(
|
||||
file,
|
||||
@ -297,11 +310,29 @@ function handlePluginNode(
|
||||
file.getText(),
|
||||
'PropertyAssignment:has(Identifier[name="plugins"])',
|
||||
(node: Node) => {
|
||||
const found = tsquery.query(node, 'ArrayLiteralExpression');
|
||||
return `plugins: [
|
||||
...${found?.[0].getText()},
|
||||
${dtsPlugin}
|
||||
]`;
|
||||
const found = tsquery.query(
|
||||
node,
|
||||
'ArrayLiteralExpression'
|
||||
) 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;
|
||||
@ -335,7 +366,7 @@ function handlePluginNode(
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: propertyAssignments[0].getStart(),
|
||||
text: pluginOption,
|
||||
text: `plugins: [${plugins.join(',\n')}],`,
|
||||
},
|
||||
]);
|
||||
writeFile = true;
|
||||
@ -344,7 +375,7 @@ function handlePluginNode(
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: foundDefineConfig[0].getStart() + 14,
|
||||
text: pluginOption,
|
||||
text: `plugins: [${plugins.join(',\n')}],`,
|
||||
},
|
||||
]);
|
||||
writeFile = true;
|
||||
@ -364,7 +395,7 @@ function handlePluginNode(
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: startOfObject + 1,
|
||||
text: pluginOption,
|
||||
text: `plugins: [${plugins.join(',\n')}],`,
|
||||
},
|
||||
]);
|
||||
writeFile = true;
|
||||
@ -373,14 +404,27 @@ function handlePluginNode(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writeFile) {
|
||||
if (!appFileContent.includes(`import dts from 'vite-plugin-dts'`)) {
|
||||
return dtsImportLine + '\n' + appFileContent;
|
||||
const filteredImports = filterImport(appFileContent, imports);
|
||||
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 {
|
||||
|
||||
@ -29,7 +29,12 @@
|
||||
"error",
|
||||
{
|
||||
"buildTargets": ["build-base"],
|
||||
"ignoredDependencies": ["nx", "typescript"]
|
||||
"ignoredDependencies": [
|
||||
"nx",
|
||||
"typescript",
|
||||
"@nx/cypress",
|
||||
"@nx/playwright"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,5 +1,33 @@
|
||||
{
|
||||
"name": "Nx Vue",
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
"access": "public"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nx": ">= 15 <= 17"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./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',
|
||||
},
|
||||
]);
|
||||
expect(tsconfig.compilerOptions.types).toMatchObject([
|
||||
'vite/client',
|
||||
'vitest',
|
||||
]);
|
||||
expect(tsconfig.compilerOptions.types).toMatchObject(['vite/client']);
|
||||
|
||||
expect(tree.exists('my-app-e2e/cypress.config.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app/index.html')).toBeTruthy();
|
||||
@ -555,6 +552,7 @@ describe('app', () => {
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest",
|
||||
]
|
||||
`);
|
||||
expect(
|
||||
@ -660,10 +658,7 @@ describe('app', () => {
|
||||
|
||||
it('should create correct tsconfig compilerOptions', () => {
|
||||
const tsconfigJson = readJson(viteAppTree, '/my-app/tsconfig.json');
|
||||
expect(tsconfigJson.compilerOptions.types).toMatchObject([
|
||||
'vite/client',
|
||||
'vitest',
|
||||
]);
|
||||
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
|
||||
});
|
||||
|
||||
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`] = `
|
||||
"# Proj
|
||||
|
||||
|
||||
@ -119,6 +119,19 @@ function getPresetDependencies({
|
||||
case Preset.NextJsStandalone:
|
||||
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.ReactStandalone:
|
||||
return {
|
||||
|
||||
@ -36,6 +36,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
|
||||
[
|
||||
Preset.ReactMonorepo,
|
||||
Preset.ReactStandalone,
|
||||
Preset.VueMonorepo,
|
||||
Preset.VueStandalone,
|
||||
Preset.AngularMonorepo,
|
||||
Preset.AngularStandalone,
|
||||
Preset.Nest,
|
||||
|
||||
@ -67,6 +67,7 @@ function createAppsAndLibsFolders(tree: Tree, options: NormalizedSchema) {
|
||||
} else if (
|
||||
options.preset === Preset.AngularStandalone ||
|
||||
options.preset === Preset.ReactStandalone ||
|
||||
options.preset === Preset.VueStandalone ||
|
||||
options.preset === Preset.NodeStandalone ||
|
||||
options.preset === Preset.NextJsStandalone ||
|
||||
options.preset === Preset.TsStandalone ||
|
||||
@ -127,6 +128,7 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
|
||||
const filesDirName =
|
||||
options.preset === Preset.AngularStandalone ||
|
||||
options.preset === Preset.ReactStandalone ||
|
||||
options.preset === Preset.VueStandalone ||
|
||||
options.preset === Preset.NodeStandalone ||
|
||||
options.preset === Preset.NextJsStandalone ||
|
||||
options.preset === Preset.TsStandalone
|
||||
@ -181,6 +183,7 @@ function addNpmScripts(tree: Tree, options: NormalizedSchema) {
|
||||
if (
|
||||
options.preset === Preset.AngularStandalone ||
|
||||
options.preset === Preset.ReactStandalone ||
|
||||
options.preset === Preset.VueStandalone ||
|
||||
options.preset === Preset.NodeStandalone ||
|
||||
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 () => {
|
||||
await newGenerator(tree, {
|
||||
...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();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
@ -99,4 +110,17 @@ describe('preset', () => {
|
||||
readProjectConfiguration(tree, 'proj').targets.serve
|
||||
).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',
|
||||
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) {
|
||||
const { applicationGenerator: nextApplicationGenerator } = require('@nx' +
|
||||
'/next');
|
||||
|
||||
@ -16,6 +16,8 @@ export enum Preset {
|
||||
ReactStandalone = 'react-standalone',
|
||||
NextJsStandalone = 'nextjs-standalone',
|
||||
ReactNative = 'react-native',
|
||||
VueMonorepo = 'vue-monorepo',
|
||||
VueStandalone = 'vue-standalone',
|
||||
Expo = 'expo',
|
||||
NextJs = 'next',
|
||||
Nest = 'nest',
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
"@nx/typedoc-theme": ["typedoc-theme/src/index.ts"],
|
||||
"@nx/vite": ["packages/vite"],
|
||||
"@nx/vite/*": ["packages/vite/*"],
|
||||
"@nx/vue": ["packages/vue/index.ts"],
|
||||
"@nx/vue": ["packages/vue"],
|
||||
"@nx/vue/*": ["packages/vue/*"],
|
||||
"@nx/web": ["packages/web"],
|
||||
"@nx/web/*": ["packages/web/*"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user