217 lines
5.6 KiB
TypeScript
217 lines
5.6 KiB
TypeScript
import * as ts from 'typescript';
|
|
import { Schema } from './schema';
|
|
import {
|
|
reactRouterDomVersion,
|
|
typesReactRouterDomVersion,
|
|
} from '../../utils/versions';
|
|
import { assertValidStyle } from '../../utils/assertion';
|
|
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
|
|
import {
|
|
addDependenciesToPackageJson,
|
|
applyChangesToString,
|
|
convertNxGenerator,
|
|
formatFiles,
|
|
generateFiles,
|
|
GeneratorCallback,
|
|
getProjects,
|
|
joinPathFragments,
|
|
logger,
|
|
names,
|
|
toJS,
|
|
Tree,
|
|
} from '@nrwl/devkit';
|
|
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
|
import { addImport } from '../../utils/ast-utils';
|
|
|
|
interface NormalizedSchema extends Schema {
|
|
projectSourceRoot: string;
|
|
fileName: string;
|
|
className: string;
|
|
styledModule: null | string;
|
|
hasStyles: boolean;
|
|
}
|
|
|
|
export async function componentGenerator(host: Tree, schema: Schema) {
|
|
const options = await normalizeOptions(host, schema);
|
|
createComponentFiles(host, options);
|
|
|
|
const tasks: GeneratorCallback[] = [];
|
|
|
|
const styledTask = addStyledModuleDependencies(host, options.styledModule);
|
|
tasks.push(styledTask);
|
|
|
|
addExportsToBarrel(host, options);
|
|
|
|
if (options.routing) {
|
|
const routingTask = addDependenciesToPackageJson(
|
|
host,
|
|
{ 'react-router-dom': reactRouterDomVersion },
|
|
{ '@types/react-router-dom': typesReactRouterDomVersion }
|
|
);
|
|
tasks.push(routingTask);
|
|
}
|
|
|
|
await formatFiles(host);
|
|
|
|
return runTasksInSerial(...tasks);
|
|
}
|
|
|
|
function createComponentFiles(host: Tree, options: NormalizedSchema) {
|
|
const componentDir = joinPathFragments(
|
|
options.projectSourceRoot,
|
|
options.directory
|
|
);
|
|
|
|
generateFiles(host, joinPathFragments(__dirname, './files'), componentDir, {
|
|
...options,
|
|
tmpl: '',
|
|
});
|
|
|
|
for (const c of host.listChanges()) {
|
|
let deleteFile = false;
|
|
|
|
if (options.skipTests && /.*spec.tsx/.test(c.path)) {
|
|
deleteFile = true;
|
|
}
|
|
|
|
if (
|
|
(options.styledModule || !options.hasStyles) &&
|
|
c.path.endsWith(`.${options.style}`)
|
|
) {
|
|
deleteFile = true;
|
|
}
|
|
|
|
if (options.globalCss && c.path.endsWith(`.module.${options.style}`)) {
|
|
deleteFile = true;
|
|
}
|
|
|
|
if (
|
|
!options.globalCss &&
|
|
c.path.endsWith(`${options.fileName}.${options.style}`)
|
|
) {
|
|
deleteFile = true;
|
|
}
|
|
|
|
if (deleteFile) {
|
|
host.delete(c.path);
|
|
}
|
|
}
|
|
|
|
if (options.js) {
|
|
toJS(host);
|
|
}
|
|
}
|
|
|
|
function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
|
|
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 buffer = host.read(indexFilePath);
|
|
if (!!buffer) {
|
|
const indexSource = buffer.toString('utf-8');
|
|
const indexSourceFile = ts.createSourceFile(
|
|
indexFilePath,
|
|
indexSource,
|
|
ts.ScriptTarget.Latest,
|
|
true
|
|
);
|
|
const changes = applyChangesToString(
|
|
indexSource,
|
|
addImport(
|
|
indexSourceFile,
|
|
`export * from './${options.directory}/${options.fileName}';`
|
|
)
|
|
);
|
|
host.write(indexFilePath, changes);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function normalizeOptions(
|
|
host: Tree,
|
|
options: Schema
|
|
): Promise<NormalizedSchema> {
|
|
assertValidOptions(options);
|
|
|
|
const { className, fileName } = names(options.name);
|
|
const componentFileName = options.pascalCaseFiles ? className : fileName;
|
|
const project = getProjects(host).get(options.project);
|
|
|
|
if (!project) {
|
|
logger.error(
|
|
`Cannot find the ${options.project} project. Please double check the project name.`
|
|
);
|
|
throw new Error();
|
|
}
|
|
|
|
const { sourceRoot: projectSourceRoot, projectType } = project;
|
|
|
|
const directory = await getDirectory(host, options);
|
|
|
|
const styledModule = /^(css|scss|less|styl|none)$/.test(options.style)
|
|
? null
|
|
: options.style;
|
|
|
|
if (options.export && projectType === 'application') {
|
|
logger.warn(
|
|
`The "--export" option should not be used with applications and will do nothing.`
|
|
);
|
|
}
|
|
|
|
options.classComponent = options.classComponent ?? false;
|
|
options.routing = options.routing ?? false;
|
|
options.globalCss = options.globalCss ?? false;
|
|
|
|
return {
|
|
...options,
|
|
directory,
|
|
styledModule,
|
|
hasStyles: options.style !== 'none',
|
|
className,
|
|
fileName: componentFileName,
|
|
projectSourceRoot,
|
|
};
|
|
}
|
|
|
|
async function getDirectory(host: Tree, options: Schema) {
|
|
const fileName = names(options.name).fileName;
|
|
const workspace = getProjects(host);
|
|
let baseDir: string;
|
|
if (options.directory) {
|
|
baseDir = options.directory;
|
|
} else {
|
|
baseDir =
|
|
workspace.get(options.project).projectType === 'application'
|
|
? 'app'
|
|
: 'lib';
|
|
}
|
|
return options.flat ? baseDir : joinPathFragments(baseDir, fileName);
|
|
}
|
|
|
|
function assertValidOptions(options: Schema) {
|
|
assertValidStyle(options.style);
|
|
|
|
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;
|
|
|
|
export const componentSchematic = convertNxGenerator(componentGenerator);
|