diff --git a/packages/devkit/src/generators/artifact-name-and-directory-utils.ts b/packages/devkit/src/generators/artifact-name-and-directory-utils.ts
index fcf2872ee42481..ac71f7d5c5f357 100644
--- a/packages/devkit/src/generators/artifact-name-and-directory-utils.ts
+++ b/packages/devkit/src/generators/artifact-name-and-directory-utils.ts
@@ -23,7 +23,7 @@ export type ArtifactGenerationOptions = {
name: string;
directory?: string;
disallowPathInNameForDerived?: boolean;
- fileExtension?: 'js' | 'jsx' | 'ts' | 'tsx';
+ fileExtension?: 'js' | 'jsx' | 'ts' | 'tsx' | 'vue';
fileName?: string;
flat?: boolean;
nameAndDirectoryFormat?: NameAndDirectoryFormat;
diff --git a/packages/vue/generators.json b/packages/vue/generators.json
index b601513ac50360..77c2beff5d8dd8 100644
--- a/packages/vue/generators.json
+++ b/packages/vue/generators.json
@@ -23,7 +23,7 @@
"description": "Create a Vue library."
},
"component": {
- "factory": "./src/generators/component/component",
+ "factory": "./src/generators/component/component#componentGeneratorInternal",
"schema": "./src/generators/component/schema.json",
"aliases": ["c"],
"x-type": "component",
diff --git a/packages/vue/src/generators/component/component.spec.ts b/packages/vue/src/generators/component/component.spec.ts
index 0fc89e4d16e4cb..b2be38e02c316d 100644
--- a/packages/vue/src/generators/component/component.spec.ts
+++ b/packages/vue/src/generators/component/component.spec.ts
@@ -29,12 +29,34 @@ describe('component', () => {
unitTestRunner: 'vitest',
});
- expect(
- appTree.read(`${libName}/src/components/hello/hello.vue`, 'utf-8')
- ).toMatchSnapshot();
- expect(
- appTree.read(`${libName}/src/components/hello/hello.spec.ts`, 'utf-8')
- ).toMatchSnapshot();
+ expect(appTree.read(`${libName}/src/lib/hello/hello.vue`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "
+
+
+ Welcome to MyLibHello!
+
+
+
+ "
+ `);
+ expect(appTree.read(`${libName}/src/lib/hello/hello.spec.ts`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { describe, it, expect } from 'vitest';
+
+ import { mount } from '@vue/test-utils';
+ import MyLibHello from './hello.vue';
+
+ describe('MyLibHello', () => {
+ it('renders properly', () => {
+ const wrapper = mount(MyLibHello, {});
+ expect(wrapper.text()).toContain('Welcome to MyLibHello');
+ });
+ });
+ "
+ `);
});
it('should generate files with jest', async () => {
@@ -44,12 +66,32 @@ describe('component', () => {
unitTestRunner: 'jest',
});
- expect(
- appTree.read(`${libName}/src/components/hello/hello.vue`, 'utf-8')
- ).toMatchSnapshot();
- expect(
- appTree.read(`${libName}/src/components/hello/hello.spec.ts`, 'utf-8')
- ).toMatchSnapshot();
+ expect(appTree.read(`${libName}/src/lib/hello/hello.vue`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "
+
+
+ Welcome to MyLibHello!
+
+
+
+ "
+ `);
+ expect(appTree.read(`${libName}/src/lib/hello/hello.spec.ts`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { mount } from '@vue/test-utils';
+ import MyLibHello from './hello.vue';
+
+ describe('MyLibHello', () => {
+ it('renders properly', () => {
+ const wrapper = mount(MyLibHello, {});
+ expect(wrapper.text()).toContain('Welcome to MyLibHello');
+ });
+ });
+ "
+ `);
});
it('should have correct component name based on directory', async () => {
@@ -61,7 +103,10 @@ describe('component', () => {
});
expect(
- appTree.read(`${libName}/src/foo/bar/hello-world.vue`, 'utf-8')
+ appTree.read(
+ `${libName}/src/foo/bar/hello-world/hello-world.vue`,
+ 'utf-8'
+ )
).toContain('FooBarHelloWorld');
});
@@ -74,7 +119,10 @@ describe('component', () => {
});
expect(
- appTree.read(`${libName}/src/foo/bar-baz/hello-world.vue`, 'utf-8')
+ appTree.read(
+ `${libName}/src/foo/bar-baz/hello-world/hello-world.vue`,
+ 'utf-8'
+ )
).toContain('FooBarBazHelloWorld');
});
@@ -86,10 +134,10 @@ describe('component', () => {
});
expect(
- appTree.read(`${appName}/src/components/hello/hello.vue`, 'utf-8')
+ appTree.read(`${appName}/src/app/hello/hello.vue`, 'utf-8')
).toContain('AppHello');
expect(
- appTree.exists(`${appName}/src/components/hello/hello.spec.ts`)
+ appTree.exists(`${appName}/src/app/hello/hello.spec.ts`)
).toBeTruthy();
});
@@ -100,9 +148,11 @@ describe('component', () => {
project: libName,
export: true,
});
- expect(
- appTree.read(`${libName}/src/index.ts`, 'utf-8')
- ).toMatchSnapshot();
+ expect(appTree.read(`${libName}/src/index.ts`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "export { default as MyLibHello } from './lib/hello/hello.vue';
+ "
+ `);
});
it('should not export from an app', async () => {
@@ -114,7 +164,7 @@ describe('component', () => {
expect(
appTree.read(`${appName}/src/index.ts`, 'utf-8')
- ).toMatchSnapshot();
+ ).toMatchInlineSnapshot(`null`);
});
});
@@ -127,10 +177,10 @@ describe('component', () => {
directory: 'foo/bar',
});
expect(
- appTree.read(`${libName}/src/foo/bar/Hello.vue`, 'utf-8')
+ appTree.read(`${libName}/src/foo/bar/hello/Hello.vue`, 'utf-8')
).toContain('FooBarHello');
expect(
- appTree.exists(`${libName}/src/foo/bar/Hello.spec.ts`)
+ appTree.exists(`${libName}/src/foo/bar/hello/Hello.spec.ts`)
).toBeTruthy();
});
});
@@ -144,12 +194,10 @@ describe('component', () => {
pascalCaseDirectory: true,
});
expect(
- appTree.exists(`${libName}/src/components/HelloWorld/HelloWorld.vue`)
+ appTree.exists(`${libName}/src/lib/HelloWorld/HelloWorld.vue`)
).toBeTruthy();
expect(
- appTree.exists(
- `${libName}/src/components/HelloWorld/HelloWorld.spec.ts`
- )
+ appTree.exists(`${libName}/src/lib/HelloWorld/HelloWorld.spec.ts`)
).toBeTruthy();
});
});
@@ -162,7 +210,7 @@ describe('component', () => {
flat: true,
});
- expect(appTree.exists(`${libName}/src/components/hello.vue`));
+ expect(appTree.exists(`${libName}/src/lib/hello.vue`));
});
it('should work with custom directory path', async () => {
await componentGenerator(appTree, {
diff --git a/packages/vue/src/generators/component/component.ts b/packages/vue/src/generators/component/component.ts
index b6a4c7f5b9b23d..67dfd0fd0fb520 100644
--- a/packages/vue/src/generators/component/component.ts
+++ b/packages/vue/src/generators/component/component.ts
@@ -3,7 +3,6 @@ import {
generateFiles,
GeneratorCallback,
getProjects,
- joinPathFragments,
runTasksInSerial,
toJS,
Tree,
@@ -13,6 +12,13 @@ import { join } from 'path';
import { addExportsToBarrel, normalizeOptions } from './lib/utils';
export async function componentGenerator(host: Tree, schema: Schema) {
+ return componentGeneratorInternal(host, {
+ nameAndDirectoryFormat: 'derived',
+ ...schema,
+ });
+}
+
+export async function componentGeneratorInternal(host: Tree, schema: Schema) {
const workspace = getProjects(host);
const isApp = workspace.get(schema.project).projectType === 'application';
@@ -32,12 +38,7 @@ export async function componentGenerator(host: Tree, schema: Schema) {
}
function createComponentFiles(host: Tree, options: NormalizedSchema) {
- const componentDir = joinPathFragments(
- options.projectSourceRoot,
- options.directory
- );
-
- generateFiles(host, join(__dirname, './files'), componentDir, {
+ generateFiles(host, join(__dirname, './files'), options.directory, {
...options,
tmpl: '',
unitTestRunner: options.unitTestRunner,
diff --git a/packages/vue/src/generators/component/lib/utils.ts b/packages/vue/src/generators/component/lib/utils.ts
index c9e4188eeb015d..1d141429e4e804 100644
--- a/packages/vue/src/generators/component/lib/utils.ts
+++ b/packages/vue/src/generators/component/lib/utils.ts
@@ -6,8 +6,11 @@ import {
names,
Tree,
} from '@nx/devkit';
-import { NormalizedSchema, Schema } from '../schema';
+import { parse, relative, dirname } from 'path';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
+import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
+
+import { NormalizedSchema, Schema } from '../schema';
import { addImport } from '../../../utils/ast-utils';
let tsModule: typeof import('typescript');
@@ -19,17 +22,29 @@ export async function normalizeOptions(
): Promise {
assertValidOptions(options);
- let { 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 {
+ artifactName: name,
+ directory,
+ fileName,
+ filePath,
+ project: projectName,
+ } = await determineArtifactNameAndDirectoryOptions(host, {
+ artifactType: 'component',
+ callingGenerator: '@nx/vue:component',
+ name: options.name,
+ directory: options.directory,
+ derivedDirectory: options.directory,
+ flat: options.flat,
+ nameAndDirectoryFormat: options.nameAndDirectoryFormat,
+ project: options.project,
+ fileExtension: 'vue',
+ pascalCaseFile: options.pascalCaseFiles,
+ pascalCaseDirectory: options.pascalCaseDirectory,
+ });
+ let { className } = names(fileName);
+ const componentFileName = fileName;
+ const project = getProjects(host).get(projectName);
const { sourceRoot: projectSourceRoot, projectType } = project;
className = getComponentClassName(
@@ -39,8 +54,6 @@ export async function normalizeOptions(
options.directory
);
- const directory = await getDirectory(options);
-
if (options.export && projectType === 'application') {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
@@ -52,6 +65,7 @@ export async function normalizeOptions(
return {
...options,
+ filePath,
directory,
className,
fileName: componentFileName,
@@ -106,11 +120,16 @@ export function addExportsToBarrel(
tsModule.ScriptTarget.Latest,
true
);
+
+ const relativePathFromIndex = getRelativeImportToFile(
+ indexFilePath,
+ options.filePath
+ );
const changes = applyChangesToString(
indexSource,
addImport(
indexSourceFile,
- `export { default as ${options.className} } from './${options.directory}/${options.fileName}.vue';`
+ `export { default as ${options.className} } from '${relativePathFromIndex}';`
)
);
host.write(indexFilePath, changes);
@@ -118,12 +137,10 @@ export function addExportsToBarrel(
}
}
-export async function getDirectory(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 getRelativeImportToFile(indexPath: string, filePath: string) {
+ const { base, dir } = parse(filePath);
+ const relativeDirToTarget = relative(dirname(indexPath), dir);
+ return `./${joinPathFragments(relativeDirToTarget, base)}`;
}
export function assertValidOptions(options: Schema) {
diff --git a/packages/vue/src/generators/component/schema.d.ts b/packages/vue/src/generators/component/schema.d.ts
index 42cae02cd4f3cf..1080d6b4f66b78 100644
--- a/packages/vue/src/generators/component/schema.d.ts
+++ b/packages/vue/src/generators/component/schema.d.ts
@@ -13,10 +13,12 @@ export interface Schema {
inSourceTests?: boolean;
skipFormat?: boolean;
unitTestRunner?: 'jest' | 'vitest' | 'none';
+ nameAndDirectoryFormat?: 'as-provided' | 'derived';
}
export interface NormalizedSchema extends Schema {
projectSourceRoot: string;
fileName: string;
className: string;
+ filePath: string;
}
diff --git a/packages/vue/src/generators/component/schema.json b/packages/vue/src/generators/component/schema.json
index 27a4305b959954..d9d4bbddcf9de7 100644
--- a/packages/vue/src/generators/component/schema.json
+++ b/packages/vue/src/generators/component/schema.json
@@ -22,9 +22,7 @@
"alias": "p",
"$default": {
"$source": "projectName"
- },
- "x-prompt": "What is the name of the project for this component?",
- "x-priority": "important"
+ }
},
"name": {
"type": "string",
@@ -36,6 +34,11 @@
"x-prompt": "What name would you like to use for the component?",
"x-priority": "important"
},
+ "nameAndDirectoryFormat": {
+ "description": "Whether to generate the component in the directory as provided, relative to the current working directory and ignoring the project (`as-provided`) or generate it using the project and directory relative to the workspace root (`derived`).",
+ "type": "string",
+ "enum": ["as-provided", "derived"]
+ },
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
@@ -56,7 +59,8 @@
"flat": {
"type": "boolean",
"description": "Create component at the source root rather than its own directory.",
- "default": false
+ "default": false,
+ "x-deprecated": "Provide the `directory` option instead and use the `as-provided` format. It will be removed in Nx v18."
},
"export": {
"type": "boolean",
@@ -103,5 +107,5 @@
"x-prompt": "What unit test runner should be used?"
}
},
- "required": ["name", "project"]
+ "required": ["name"]
}