Skip to content

Commit 56b7920

Browse files
refactor: component generator enhancements
- style and test files support - "flat" option - ability to be generated for app or lib
1 parent 50d5524 commit 56b7920

File tree

11 files changed

+230
-46
lines changed

11 files changed

+230
-46
lines changed

packages/qwik-nx/src/generators/component/files/__fileName__.tsx__template__

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { component$<%= hasStyles ? ', useStylesScoped$' : '' %> } from '@builder.io/qwik';
2+
<% if(hasStyles) { %>
3+
import styles from './<%- fileName %>.<%- style %>?inline';
4+
<% } %>
5+
6+
interface <%= className %>Props {
7+
8+
}
9+
10+
const <%= className %> = component$((props: <%= className %>Props)=>{
11+
<% if(hasStyles) { %>
12+
useStylesScoped$(styles);
13+
<% } %>
14+
return <>
15+
16+
</>;
17+
});

packages/qwik-nx/src/generators/component/files/styles/__fileName__.__style__.template

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createDOM } from '@builder.io/qwik/testing';
2+
import { test, expect } from 'vitest';
3+
import { <%- className %> } from './<%- fileName %>';
4+
5+
test(`[<%- className %> Component]: Should render`, async () => {
6+
const { screen, render } = await createDOM();
7+
await render(<<%- className %> />);
8+
expect(screen.innerHTML).toBeTruthy();
9+
});
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
22
import { Tree } from '@nrwl/devkit';
33
import componentGenerator from './generator';
4+
import {createLib} from './../../utils/testing-generators';
45

56
describe('component generator', () => {
67
let appTree: Tree;
8+
const projectName = 'dummy-lib';
79

810
beforeEach(() => {
911
appTree = createTreeWithEmptyWorkspace();
12+
createLib(appTree, projectName);
1013
});
1114

1215
it('should generate a component file inside a given directory', async () => {
1316
await componentGenerator(appTree, {
1417
name: 'hello',
15-
directory: 'components'
18+
project: projectName
1619
});
1720

18-
expect(appTree.exists('libs/components/hello/hello.tsx')).toBeTruthy();
21+
expect(appTree.exists(`libs/${projectName}/src/lib/hello/hello.tsx`)).toBeTruthy();
1922

2023
});
2124
});

packages/qwik-nx/src/generators/component/generator.ts

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,88 @@ import {
22
addProjectConfiguration,
33
formatFiles,
44
generateFiles,
5-
getWorkspaceLayout,
5+
getProjects,
6+
joinPathFragments,
7+
logger,
68
names,
7-
offsetFromRoot,
89
Tree,
910
} from '@nrwl/devkit';
10-
import * as path from 'path';
11+
import { addStyledModuleDependencies } from '../../utils/add-styled-dependencies';
1112
import { ComponentGeneratorSchema } from './schema';
1213

1314
interface NormalizedSchema extends ComponentGeneratorSchema {
14-
projectName: string;
15+
directory: string;
16+
hasStyles: boolean;
1517
projectRoot: string;
16-
projectDirectory: string;
17-
parsedTags: string[];
1818
}
1919

20-
function normalizeOptions(tree: Tree, options: ComponentGeneratorSchema): NormalizedSchema {
21-
const name = names(options.name).fileName;
22-
const projectDirectory = options.directory
23-
? `${names(options.directory).fileName}/${name}`
24-
: name;
25-
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
26-
const projectRoot = `${getWorkspaceLayout(tree).libsDir}/${projectDirectory}`;
27-
const parsedTags = options.tags
28-
? options.tags.split(',').map((s) => s.trim())
29-
: [];
20+
function getDirectory(host: Tree, options: ComponentGeneratorSchema) {
21+
const workspace = getProjects(host);
22+
let baseDir: string;
23+
if (options.directory) {
24+
baseDir = options.directory;
25+
} else {
26+
baseDir =
27+
workspace.get(options.project).projectType === 'application'
28+
? 'app'
29+
: 'lib';
30+
}
31+
return options.flat ? baseDir : joinPathFragments(baseDir, names(options.name).fileName);
32+
}
33+
34+
function normalizeOptions(
35+
host: Tree,
36+
options: ComponentGeneratorSchema
37+
): NormalizedSchema {
38+
39+
const project = getProjects(host).get(options.project);
40+
41+
if (!project) {
42+
logger.error(
43+
`Cannot find the ${options.project} project. Please double check the project name.`
44+
);
45+
throw new Error();
46+
}
47+
48+
const { sourceRoot: projectRoot } = project;
49+
50+
const directory = getDirectory(host, options);
3051

3152
return {
3253
...options,
33-
projectName,
54+
directory,
55+
hasStyles: options.style !== 'none',
3456
projectRoot,
35-
projectDirectory,
36-
parsedTags,
3757
};
3858
}
3959

4060
function createComponentFiles(tree: Tree, options: NormalizedSchema) {
61+
const libNames = names(options.name);
62+
const hasStyles = options.style && options.style !== 'none';
4163
const templateOptions = {
4264
...options,
43-
...names(options.name),
44-
offsetFromRoot: offsetFromRoot(options.projectRoot),
45-
template: ''
65+
...libNames,
66+
hasStyles
4667
};
47-
generateFiles(tree, path.join(__dirname, 'files'), options.projectRoot, templateOptions);
68+
69+
const componentDir = joinPathFragments(
70+
options.projectRoot,
71+
options.directory
72+
);
73+
74+
generateFiles(tree, joinPathFragments(__dirname, 'files/common'), componentDir, templateOptions);
75+
if (hasStyles) {
76+
generateFiles(tree, joinPathFragments(__dirname, 'files/styles'), componentDir, templateOptions);
77+
}
78+
if (!options.skipTests) {
79+
generateFiles(tree, joinPathFragments(__dirname, 'files/tests'), componentDir, templateOptions);
80+
}
4881
}
4982

5083
export default async function componentGenerator(tree: Tree, options: ComponentGeneratorSchema) {
5184
const normalizedOptions = normalizeOptions(tree, options);
5285
createComponentFiles(tree, normalizedOptions);
5386
await formatFiles(tree);
87+
88+
return addStyledModuleDependencies(tree, normalizedOptions.style)
5489
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
export interface ComponentGeneratorSchema {
22
name: string;
3-
tags?: string;
3+
project: string;
44
directory?: string;
5+
style?: 'none' | 'css' | 'scss' | 'styl' | 'less';
6+
skipTests?: boolean;
7+
flat?: boolean;
58
}

packages/qwik-nx/src/generators/component/schema.json

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,68 @@
77
"properties": {
88
"name": {
99
"type": "string",
10-
"description": "",
10+
"description": "The name of the component.",
1111
"$default": {
1212
"$source": "argv",
1313
"index": 0
1414
},
15-
"x-prompt": "What name would you like to use?"
15+
"x-prompt": "What name would you like to use for the component?"
1616
},
17-
"tags": {
17+
"project": {
1818
"type": "string",
19-
"description": "Add tags to the project (used for linting)",
20-
"alias": "t"
21-
},
19+
"description": "The name of the project.",
20+
"alias": "p",
21+
"$default": {
22+
"$source": "projectName"
23+
},
24+
"x-prompt": "What is the name of the project for this component?"
25+
},
2226
"directory": {
2327
"type": "string",
24-
"description": "A directory where the project is placed"
28+
"description": "Create the component under this directory (can be nested)."
29+
},
30+
"style": {
31+
"description": "The file extension to be used for style files.",
32+
"type": "string",
33+
"default": "css",
34+
"alias": "s",
35+
"x-prompt": {
36+
"message": "Which stylesheet format would you like to use?",
37+
"type": "list",
38+
"items": [
39+
{
40+
"value": "css",
41+
"label": "CSS"
42+
},
43+
{
44+
"value": "scss",
45+
"label": "SASS(.scss) [ http://sass-lang.com ]"
46+
},
47+
{
48+
"value": "styl",
49+
"label": "Stylus(.styl) [ http://stylus-lang.com ]"
50+
},
51+
{
52+
"value": "less",
53+
"label": "LESS [ http://lesscss.org ]"
54+
},
55+
{
56+
"value": "none",
57+
"label": "none"
58+
}
59+
],
60+
"default": "css"
61+
}
62+
},
63+
"skipTests": {
64+
"description": "When true, does not create `spec.ts` test files for the new component.",
65+
"type": "boolean"
66+
},
67+
"flat": {
68+
"type": "boolean",
69+
"description": "Create component at the source root rather than its own directory.",
70+
"default": false
2571
}
2672
},
27-
"required": ["name"]
73+
"required": ["name", "project"]
2874
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
addDependenciesToPackageJson,
3+
GeneratorCallback,
4+
Tree,
5+
} from '@nrwl/devkit';
6+
import { lessVersion, sassVersion, stylusVersion } from './versions';
7+
8+
type PackageDependencies = Record<
9+
'dependencies' | 'devDependencies',
10+
Record<string, string>
11+
>;
12+
13+
export function addStyledModuleDependencies(
14+
host: Tree,
15+
styledModule: string
16+
): GeneratorCallback {
17+
const extraDependencies = STYLE_DEPENDENCIES.get(styledModule);
18+
19+
if (extraDependencies) {
20+
return addDependenciesToPackageJson(
21+
host,
22+
extraDependencies.dependencies,
23+
extraDependencies.devDependencies
24+
);
25+
} else {
26+
// eslint-disable-next-line @typescript-eslint/no-empty-function
27+
return () => {};
28+
}
29+
}
30+
31+
const STYLE_DEPENDENCIES = new Map<string, PackageDependencies>([
32+
[
33+
'scss',
34+
{
35+
dependencies: {},
36+
devDependencies: {
37+
sass: sassVersion,
38+
},
39+
},
40+
],
41+
[
42+
'less',
43+
{
44+
dependencies: {},
45+
devDependencies: {
46+
less: lessVersion,
47+
},
48+
},
49+
],
50+
[
51+
'styl',
52+
{
53+
dependencies: {},
54+
devDependencies: {
55+
stylus: stylusVersion,
56+
},
57+
},
58+
],
59+
]);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { addProjectConfiguration, names, Tree } from '@nrwl/devkit';
2+
3+
export function createLib(
4+
tree: Tree,
5+
libName: string,
6+
): void {
7+
const { fileName } = names(libName);
8+
9+
addProjectConfiguration(
10+
tree,
11+
fileName,
12+
{
13+
tags: [],
14+
root: `libs/${fileName}`,
15+
projectType: 'library',
16+
sourceRoot: `libs/${fileName}/src`,
17+
targets: {},
18+
},
19+
true
20+
);
21+
}

0 commit comments

Comments
 (0)