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 775bac8
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 193 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

This file was deleted.

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
Loading

0 comments on commit 775bac8

Please sign in to comment.