Skip to content

Commit

Permalink
feat(vue): support generating components using the path as provided
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo committed Oct 13, 2023
1 parent 8249ace commit ba1a9be
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
102 changes: 75 additions & 27 deletions packages/vue/src/generators/component/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
"<script setup lang="ts">
defineProps<{}>();
</script>
<template>
<p>Welcome to MyLibHello!</p>
</template>
<style scoped></style>
"
`);
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 () => {
Expand All @@ -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(`
"<script setup lang="ts">
defineProps<{}>();
</script>
<template>
<p>Welcome to MyLibHello!</p>
</template>
<style scoped></style>
"
`);
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 () => {
Expand All @@ -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');
});

Expand All @@ -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');
});

Expand All @@ -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();
});

Expand All @@ -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 () => {
Expand All @@ -114,7 +164,7 @@ describe('component', () => {

expect(
appTree.read(`${appName}/src/index.ts`, 'utf-8')
).toMatchSnapshot();
).toMatchInlineSnapshot(`null`);
});
});

Expand All @@ -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();
});
});
Expand All @@ -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();
});
});
Expand All @@ -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, {
Expand Down
15 changes: 8 additions & 7 deletions packages/vue/src/generators/component/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
generateFiles,
GeneratorCallback,
getProjects,
joinPathFragments,
runTasksInSerial,
toJS,
Tree,
Expand All @@ -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';

Expand All @@ -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,
Expand Down
57 changes: 37 additions & 20 deletions packages/vue/src/generators/component/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -19,17 +22,29 @@ export async function normalizeOptions(
): Promise<NormalizedSchema> {
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(
Expand All @@ -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.`
Expand All @@ -52,6 +65,7 @@ export async function normalizeOptions(

return {
...options,
filePath,
directory,
className,
fileName: componentFileName,
Expand Down Expand Up @@ -106,24 +120,27 @@ 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);
}
}
}

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) {
Expand Down
2 changes: 2 additions & 0 deletions packages/vue/src/generators/component/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 9 additions & 5 deletions packages/vue/src/generators/component/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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.",
Expand All @@ -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",
Expand Down Expand Up @@ -103,5 +107,5 @@
"x-prompt": "What unit test runner should be used?"
}
},
"required": ["name", "project"]
"required": ["name"]
}

0 comments on commit ba1a9be

Please sign in to comment.