feat: add --js and --pascalCaseFiles flags to core,node,express schematics (#3683)

This commit is contained in:
Martin Hochel 2020-11-17 18:29:20 +01:00 committed by GitHub
parent a1615e8346
commit 842455bbd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 829 additions and 135 deletions

View File

@ -21,6 +21,7 @@ module.exports = {
{ name: 'nextjs', description: 'anything Next specific' }, { name: 'nextjs', description: 'anything Next specific' },
{ name: 'nest', description: 'anything Nest specific' }, { name: 'nest', description: 'anything Nest specific' },
{ name: 'node', description: 'anything Node specific' }, { name: 'node', description: 'anything Node specific' },
{ name: 'express', description: 'anything Express specific' },
{ name: 'nx-plugin', description: 'anything Nx Plugin specific' }, { name: 'nx-plugin', description: 'anything Nx Plugin specific' },
{ name: 'react', description: 'anything React specific' }, { name: 'react', description: 'anything React specific' },
{ name: 'web', description: 'anything Web specific' }, { name: 'web', description: 'anything Web specific' },

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -66,6 +66,14 @@ Type: `string`
The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -82,6 +90,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### publishable ### publishable
Type: `boolean` Type: `boolean`

View File

@ -80,6 +80,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -66,6 +66,14 @@ Type: `string`
The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -82,6 +90,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### publishable ### publishable
Type: `boolean` Type: `boolean`

View File

@ -80,6 +80,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -48,6 +48,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. Frontend project that needs to access this application. This sets up proxy configuration.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -64,6 +72,16 @@ Type: `string`
The name of the application. The name of the application.
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -66,6 +66,14 @@ Type: `string`
The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name.
### js
Default: `false`
Type: `boolean`
Generate JavaScript files rather than TypeScript files.
### linter ### linter
Default: `eslint` Default: `eslint`
@ -82,6 +90,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### publishable ### publishable
Type: `boolean` Type: `boolean`

View File

@ -80,6 +80,16 @@ Type: `string`
Library name Library name
### pascalCaseFiles
Alias(es): P
Default: `false`
Type: `boolean`
Use pascal case file names.
### skipFormat ### skipFormat
Default: `false` Default: `false`

View File

@ -60,9 +60,7 @@ forEachCli('nx', () => {
expect(workspaceJson).not.toContain('libs/'); expect(workspaceJson).not.toContain('libs/');
const libTestResults = await runCLIAsync(`test ${expressLib}`); const libTestResults = await runCLIAsync(`test ${expressLib}`);
expect(libTestResults.stdout).toContain( expect(libTestResults.stdout).toContain(`nx run ${expressLib}:test`);
'No tests found, exiting with code 0'
);
const appBuildResults = await runCLIAsync(`build ${expressApp}`); const appBuildResults = await runCLIAsync(`build ${expressApp}`);
expect(appBuildResults.stdout).toContain(`nx run ${expressApp}:build`); expect(appBuildResults.stdout).toContain(`nx run ${expressApp}:build`);

View File

@ -3,6 +3,8 @@ import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { runSchematic } from '../../utils/testing'; import { runSchematic } from '../../utils/testing';
import { readJsonInTree } from '@nrwl/workspace'; import { readJsonInTree } from '@nrwl/workspace';
import { Schema } from './schema.d';
describe('app', () => { describe('app', () => {
let appTree: Tree; let appTree: Tree;
@ -12,26 +14,88 @@ describe('app', () => {
}); });
it('should generate files', async () => { it('should generate files', async () => {
const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); const tree = await runSchematic(
expect(tree.readContent('apps/my-node-app/src/main.ts')).toContain( 'app',
`import * as express from 'express';` { name: 'myNodeApp' } as Schema,
appTree
); );
const mainFile = tree.readContent('apps/my-node-app/src/main.ts');
expect(mainFile).toContain(`import * as express from 'express';`);
const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json');
expect(tsconfig).toMatchInlineSnapshot(`
Object {
"extends": "../../tsconfig.base.json",
"files": Array [],
"include": Array [],
"references": Array [
Object {
"path": "./tsconfig.app.json",
},
Object {
"path": "./tsconfig.spec.json",
},
],
}
`);
}); });
it('should add types to the tsconfig.app.json', async () => { it('should add types to the tsconfig.app.json', async () => {
const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); const tree = await runSchematic(
'app',
{ name: 'myNodeApp' } as Schema,
appTree
);
const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.app.json'); const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.app.json');
expect(tsconfig.compilerOptions.types).toContain('express'); expect(tsconfig.compilerOptions.types).toContain('express');
expect(tsconfig).toMatchInlineSnapshot(`
Object {
"compilerOptions": Object {
"outDir": "../../dist/out-tsc",
"types": Array [
"node",
"express",
],
},
"exclude": Array [
"**/*.spec.ts",
],
"extends": "./tsconfig.json",
"include": Array [
"**/*.ts",
],
}
`);
}); });
it('should update tsconfig', async () => { describe('--js flag', () => {
const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); it('should generate js files instead of ts files', async () => {
const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); const tree = await runSchematic(
expect(tsconfig.references).toContainEqual({ 'app',
path: './tsconfig.app.json', {
}); name: 'myNodeApp',
expect(tsconfig.references).toContainEqual({ js: true,
path: './tsconfig.spec.json', } as Schema,
appTree
);
expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy();
expect(tree.readContent('apps/my-node-app/src/main.js')).toContain(
`import * as express from 'express';`
);
const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json');
expect(tsConfig.compilerOptions).toEqual({
allowJs: true,
});
const tsConfigApp = readJsonInTree(
tree,
'apps/my-node-app/tsconfig.app.json'
);
expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']);
expect(tsConfigApp.exclude).toEqual(['**/*.spec.ts', '**/*.spec.js']);
}); });
}); });
}); });

View File

@ -11,6 +11,7 @@ import { updateJsonInTree } from '@nrwl/workspace';
import { toFileName, formatFiles } from '@nrwl/workspace'; import { toFileName, formatFiles } from '@nrwl/workspace';
import init from '../init/init'; import init from '../init/init';
import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; import { appsDir } from '@nrwl/workspace/src/utils/ast-utils';
import { maybeJs } from '@nrwl/workspace/src/utils/rules/to-js';
interface NormalizedSchema extends Schema { interface NormalizedSchema extends Schema {
appProjectRoot: Path; appProjectRoot: Path;
@ -24,10 +25,10 @@ function addTypes(options: NormalizedSchema): Rule {
}); });
} }
function addMainFile(options: NormalizedSchema): Rule { function addAppFiles(options: NormalizedSchema): Rule {
return (host: Tree) => { return (host: Tree) => {
host.overwrite( host.overwrite(
join(options.appProjectRoot, 'src/main.ts'), maybeJs(options, join(options.appProjectRoot, 'src/main.ts')),
`/** `/**
* This is not a production server yet! * This is not a production server yet!
* This is only a minimal backend to get started. * This is only a minimal backend to get started.
@ -57,7 +58,7 @@ export default function (schema: Schema): Rule {
return chain([ return chain([
init({ ...options, skipFormat: true }), init({ ...options, skipFormat: true }),
externalSchematic('@nrwl/node', 'application', schema), externalSchematic('@nrwl/node', 'application', schema),
addMainFile(options), addAppFiles(options),
addTypes(options), addTypes(options),
formatFiles(options), formatFiles(options),
])(host, context); ])(host, context);

View File

@ -11,4 +11,6 @@ export interface Schema {
linter: Linter; linter: Linter;
frontendProject?: string; frontendProject?: string;
babelJest?: boolean; babelJest?: boolean;
js: boolean;
pascalCaseFiles: boolean;
} }

View File

@ -52,6 +52,17 @@
"type": "boolean", "type": "boolean",
"description": "Use babel instead ts-jest", "description": "Use babel instead ts-jest",
"default": false "default": false
},
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
} }
}, },
"required": [] "required": []

View File

@ -8,7 +8,7 @@ export default function updatePackageJson(
options: NormalizedBuilderOptions, options: NormalizedBuilderOptions,
context: BuilderContext context: BuilderContext
) { ) {
const mainFile = basename(options.main, '.ts'); const mainFile = basename(options.main).replace(/\.[tj]s$/, '');
const typingsFile = `${mainFile}.d.ts`; const typingsFile = `${mainFile}.d.ts`;
const mainJsFile = `${mainFile}.js`; const mainJsFile = `${mainFile}.js`;
const packageJson = readJsonFile( const packageJson = readJsonFile(

View File

@ -1,6 +1,5 @@
import { Tree } from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import * as stripJsonComments from 'strip-json-comments'; import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { createEmptyWorkspace, getFileContent } from '@nrwl/workspace/testing';
import { runSchematic } from '../../utils/testing'; import { runSchematic } from '../../utils/testing';
import { NxJson, readJsonInTree } from '@nrwl/workspace'; import { NxJson, readJsonInTree } from '@nrwl/workspace';
// to break the dependency // to break the dependency
@ -24,7 +23,7 @@ describe('app', () => {
const project = workspaceJson.projects['my-node-app']; const project = workspaceJson.projects['my-node-app'];
expect(project.root).toEqual('apps/my-node-app'); expect(project.root).toEqual('apps/my-node-app');
expect(project.architect).toEqual( expect(project.architect).toEqual(
jasmine.objectContaining({ expect.objectContaining({
build: { build: {
builder: '@nrwl/node:build', builder: '@nrwl/node:build',
options: { options: {
@ -85,37 +84,31 @@ describe('app', () => {
expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy();
expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy(); expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy();
expect(tree.readContent('apps/my-node-app/tsconfig.json')) const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json');
.toMatchInlineSnapshot(` expect(tsconfig).toMatchInlineSnapshot(`
"{ Object {
\\"extends\\": \\"../../tsconfig.base.json\\", "extends": "../../tsconfig.base.json",
\\"files\\": [], "files": Array [],
\\"include\\": [], "include": Array [],
\\"references\\": [ "references": Array [
{ Object {
\\"path\\": \\"./tsconfig.app.json\\" "path": "./tsconfig.app.json",
}, },
{ Object {
\\"path\\": \\"./tsconfig.spec.json\\" "path": "./tsconfig.spec.json",
} },
] ],
} }
"
`); `);
const tsconfigApp = JSON.parse( const tsconfigApp = readJsonInTree(
stripJsonComments( tree,
getFileContent(tree, 'apps/my-node-app/tsconfig.app.json') 'apps/my-node-app/tsconfig.app.json'
)
); );
expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc'); expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc');
expect(tsconfigApp.extends).toEqual('./tsconfig.json'); expect(tsconfigApp.extends).toEqual('./tsconfig.json');
const eslintrc = JSON.parse( const eslintrc = readJsonInTree(tree, 'apps/my-node-app/.eslintrc.json');
stripJsonComments(
getFileContent(tree, 'apps/my-node-app/.eslintrc.json')
)
);
expect(eslintrc.extends).toEqual('../../.eslintrc.json'); expect(eslintrc.extends).toEqual('../../.eslintrc.json');
}); });
}); });
@ -162,8 +155,7 @@ describe('app', () => {
it('should generate files', async () => { it('should generate files', async () => {
const hasJsonValue = ({ path, expectedValue, lookupFn }) => { const hasJsonValue = ({ path, expectedValue, lookupFn }) => {
const content = getFileContent(tree, path); const config = readJsonInTree(tree, path);
const config = JSON.parse(stripJsonComments(content));
expect(lookupFn(config)).toEqual(expectedValue); expect(lookupFn(config)).toEqual(expectedValue);
}; };
@ -243,7 +235,7 @@ describe('app', () => {
); );
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy();
const serve = JSON.parse(tree.readContent('workspace.json')).projects[ const serve = readJsonInTree(tree, 'workspace.json').projects[
'my-frontend' 'my-frontend'
].architect.serve; ].architect.serve;
expect(serve.options.proxyConfig).toEqual( expect(serve.options.proxyConfig).toEqual(
@ -261,7 +253,7 @@ describe('app', () => {
); );
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy();
const serve = JSON.parse(tree.readContent('workspace.json')).projects[ const serve = readJsonInTree(tree, 'workspace.json').projects[
'my-frontend' 'my-frontend'
].architect.serve; ].architect.serve;
expect(serve.options.proxyConfig).toEqual( expect(serve.options.proxyConfig).toEqual(
@ -313,4 +305,75 @@ describe('app', () => {
`); `);
}); });
}); });
describe('--js flag', () => {
it('should generate js files instead of ts files', async () => {
const tree = await runSchematic(
'app',
{
name: 'myNodeApp',
js: true,
} as Schema,
appTree
);
expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy();
expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy();
const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json');
expect(tsConfig.compilerOptions).toEqual({
allowJs: true,
});
const tsConfigApp = readJsonInTree(
tree,
'apps/my-node-app/tsconfig.app.json'
);
expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']);
expect(tsConfigApp.exclude).toEqual(['**/*.spec.ts', '**/*.spec.js']);
});
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myNodeApp', js: true } as Schema,
appTree
);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
const project = workspaceJson.projects['my-node-app'];
const buildTarget = project.architect.build;
expect(buildTarget.options.main).toEqual('apps/my-node-app/src/main.js');
expect(buildTarget.configurations.production.fileReplacements).toEqual([
{
replace: 'apps/my-node-app/src/environments/environment.js',
with: 'apps/my-node-app/src/environments/environment.prod.js',
},
]);
});
it('should generate js files for nested libs as well', async () => {
const tree = await runSchematic(
'app',
{ name: 'myNodeApp', directory: 'myDir', js: true } as Schema,
appTree
);
expect(
tree.exists(`apps/my-dir/my-node-app/jest.config.js`)
).toBeTruthy();
expect(tree.exists('apps/my-dir/my-node-app/src/main.js')).toBeTruthy();
});
});
describe('--pascalCaseFiles', () => {
it(`should notify that this flag doesn't do anything`, async () => {
const tree = await runSchematic(
'app',
{ name: 'myNodeApp', pascalCaseFiles: true } as Schema,
appTree
);
// @TODO how to spy on context ?
// expect(contextLoggerSpy).toHaveBeenCalledWith('NOTE: --pascalCaseFiles is a noop')
});
});
}); });

View File

@ -25,6 +25,11 @@ import { getProjectConfig } from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace'; import { offsetFromRoot } from '@nrwl/workspace';
import init from '../init/init'; import init from '../init/init';
import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; import { appsDir } from '@nrwl/workspace/src/utils/ast-utils';
import {
toJS,
updateTsConfigsToJs,
maybeJs,
} from '@nrwl/workspace/src/utils/rules/to-js';
interface NormalizedSchema extends Schema { interface NormalizedSchema extends Schema {
appProjectRoot: Path; appProjectRoot: Path;
@ -48,7 +53,7 @@ function getBuildConfig(project: any, options: NormalizedSchema) {
builder: '@nrwl/node:build', builder: '@nrwl/node:build',
options: { options: {
outputPath: join(normalize('dist'), options.appProjectRoot), outputPath: join(normalize('dist'), options.appProjectRoot),
main: join(project.sourceRoot, 'main.ts'), main: maybeJs(options, join(project.sourceRoot, 'main.ts')),
tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'), tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'),
assets: [join(project.sourceRoot, 'assets')], assets: [join(project.sourceRoot, 'assets')],
}, },
@ -59,8 +64,14 @@ function getBuildConfig(project: any, options: NormalizedSchema) {
inspect: false, inspect: false,
fileReplacements: [ fileReplacements: [
{ {
replace: join(project.sourceRoot, 'environments/environment.ts'), replace: maybeJs(
with: join(project.sourceRoot, 'environments/environment.prod.ts'), options,
join(project.sourceRoot, 'environments/environment.ts')
),
with: maybeJs(
options,
join(project.sourceRoot, 'environments/environment.prod.ts')
),
}, },
], ],
}, },
@ -106,17 +117,26 @@ function updateWorkspaceJson(options: NormalizedSchema): Rule {
} }
function addAppFiles(options: NormalizedSchema): Rule { function addAppFiles(options: NormalizedSchema): Rule {
return mergeWith( return chain([
apply(url(`./files/app`), [ mergeWith(
template({ apply(url(`./files/app`), [
tmpl: '', template({
name: options.name, tmpl: '',
root: options.appProjectRoot, name: options.name,
offset: offsetFromRoot(options.appProjectRoot), root: options.appProjectRoot,
}), offset: offsetFromRoot(options.appProjectRoot),
move(options.appProjectRoot), }),
]) move(options.appProjectRoot),
); options.js ? toJS() : noop(),
])
),
options.pascalCaseFiles
? (tree, context) => {
context.logger.warn('NOTE: --pascalCaseFiles is a noop');
return tree;
}
: noop(),
]);
} }
function addProxy(options: NormalizedSchema): Rule { function addProxy(options: NormalizedSchema): Rule {
@ -147,6 +167,18 @@ function addProxy(options: NormalizedSchema): Rule {
}; };
} }
function addJest(options: NormalizedSchema) {
return options.unitTestRunner === 'jest'
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.name,
setupFile: 'none',
skipSerializers: true,
supportTsx: options.js,
babelJest: options.babelJest,
})
: noop();
}
export default function (schema: Schema): Rule { export default function (schema: Schema): Rule {
return (host: Tree, context: SchematicContext) => { return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema); const options = normalizeOptions(host, schema);
@ -157,16 +189,12 @@ export default function (schema: Schema): Rule {
}), }),
addLintFiles(options.appProjectRoot, options.linter), addLintFiles(options.appProjectRoot, options.linter),
addAppFiles(options), addAppFiles(options),
options.js
? updateTsConfigsToJs({ projectRoot: options.appProjectRoot })
: noop,
updateWorkspaceJson(options), updateWorkspaceJson(options),
updateNxJson(options), updateNxJson(options),
options.unitTestRunner === 'jest' addJest(options),
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.name,
setupFile: 'none',
skipSerializers: true,
babelJest: options.babelJest,
})
: noop(),
options.frontendProject ? addProxy(options) : noop(), options.frontendProject ? addProxy(options) : noop(),
formatFiles(options), formatFiles(options),
])(host, context); ])(host, context);

View File

@ -10,4 +10,6 @@ export interface Schema {
tags?: string; tags?: string;
frontendProject?: string; frontendProject?: string;
babelJest?: boolean; babelJest?: boolean;
js: boolean;
pascalCaseFiles: boolean;
} }

View File

@ -51,6 +51,17 @@
"type": "boolean", "type": "boolean",
"description": "Use babel instead ts-jest", "description": "Use babel instead ts-jest",
"default": false "default": false
},
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
} }
}, },
"required": [] "required": []

View File

@ -71,15 +71,21 @@ describe('lib', () => {
it('should create a local tsconfig.json', async () => { it('should create a local tsconfig.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree); const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json');
expect(tsconfigJson.extends).toEqual('../../tsconfig.base.json'); expect(tsconfigJson).toMatchInlineSnapshot(`
expect(tsconfigJson.references).toEqual([ Object {
{ "extends": "../../tsconfig.base.json",
path: './tsconfig.lib.json', "files": Array [],
}, "include": Array [],
{ "references": Array [
path: './tsconfig.spec.json', Object {
}, "path": "./tsconfig.lib.json",
]); },
Object {
"path": "./tsconfig.spec.json",
},
],
}
`);
}); });
it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { it('should extend the local tsconfig.json with tsconfig.spec.json', async () => {
@ -280,7 +286,21 @@ describe('lib', () => {
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); expect(workspaceJson.projects['my-lib'].architect.build)
.toMatchInlineSnapshot(`
Object {
"builder": "@nrwl/node:package",
"options": Object {
"assets": Array [
"libs/my-lib/*.md",
],
"main": "libs/my-lib/src/index.ts",
"outputPath": "dist/libs/my-lib",
"packageJson": "libs/my-lib/package.json",
"tsConfig": "libs/my-lib/tsconfig.lib.json",
},
}
`);
}); });
}); });
@ -413,4 +433,102 @@ describe('lib', () => {
`); `);
}); });
}); });
describe('--js flag', () => {
it('should generate js files instead of ts files', async () => {
const tree = await runSchematic(
'lib',
{
name: 'myLib',
js: true,
} as Schema,
appTree
);
expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy();
expect(
readJsonInTree(tree, 'libs/my-lib/tsconfig.json').compilerOptions
).toEqual({
allowJs: true,
});
expect(
readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').include
).toEqual(['**/*.ts', '**/*.js']);
expect(
readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').exclude
).toEqual(['**/*.spec.ts', '**/*.spec.js']);
});
it('should update root tsconfig.json with a js file path', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', js: true } as Schema,
appTree
);
const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json');
expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([
'libs/my-lib/src/index.js',
]);
});
it('should update architect builder when --buildable', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', buildable: true, js: true } as Schema,
appTree
);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(
workspaceJson.projects['my-lib'].architect.build.options.main
).toEqual('libs/my-lib/src/index.js');
});
it('should generate js files for nested libs as well', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir', js: true } as Schema,
appTree
);
expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js')
).toBeTruthy();
});
});
describe('--pascalCaseFiles', () => {
it('should generate files with upper case names', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', pascalCaseFiles: true } as Schema,
appTree
);
expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy();
});
it('should generate files with upper case names for nested libs as well', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir', pascalCaseFiles: true } as Schema,
appTree
);
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts')
).toBeTruthy();
});
});
}); });

View File

@ -25,6 +25,11 @@ import {
} from '@nrwl/workspace'; } from '@nrwl/workspace';
import { Schema } from './schema'; import { Schema } from './schema';
import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; import { libsDir } from '@nrwl/workspace/src/utils/ast-utils';
import {
toJS,
updateTsConfigsToJs,
maybeJs,
} from '@nrwl/workspace/src/utils/rules/to-js';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
name: string; name: string;
@ -51,6 +56,7 @@ export default function (schema: NormalizedSchema): Rule {
importPath: options.importPath, importPath: options.importPath,
}), }),
createFiles(options), createFiles(options),
options.js ? updateTsConfigsToJs(options) : noop(),
addProject(options), addProject(options),
formatFiles(options), formatFiles(options),
]); ]);
@ -103,6 +109,7 @@ function createFiles(options: NormalizedSchema): Rule {
options.publishable || options.buildable options.publishable || options.buildable
? noop() ? noop()
: filter((file) => !file.endsWith('package.json')), : filter((file) => !file.endsWith('package.json')),
options.js ? toJS() : noop(),
]), ]),
MergeStrategy.Overwrite MergeStrategy.Overwrite
); );
@ -122,7 +129,7 @@ function addProject(options: NormalizedSchema): Rule {
outputPath: `dist/${libsDir(host)}/${options.projectDirectory}`, outputPath: `dist/${libsDir(host)}/${options.projectDirectory}`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`, tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
packageJson: `${options.projectRoot}/package.json`, packageJson: `${options.projectRoot}/package.json`,
main: `${options.projectRoot}/src/index.ts`, main: maybeJs(options, `${options.projectRoot}/src/index.ts`),
assets: [`${options.projectRoot}/*.md`], assets: [`${options.projectRoot}/*.md`],
}, },
}; };

View File

@ -14,4 +14,6 @@ export interface Schema {
testEnvironment: 'jsdom' | 'node'; testEnvironment: 'jsdom' | 'node';
rootDir?: string; rootDir?: string;
babelJest?: boolean; babelJest?: boolean;
js: boolean;
pascalCaseFiles: boolean;
} }

View File

@ -79,6 +79,17 @@
"type": "boolean", "type": "boolean",
"description": "Use babel instead ts-jest", "description": "Use babel instead ts-jest",
"default": false "default": false
},
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": {
"type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files.",
"default": false
} }
}, },
"required": ["name"] "required": ["name"]

View File

@ -0,0 +1,7 @@
import { <%= propertyName %> } from './<%= fileName %>';
describe('<%= propertyName %>', () => {
it('should work', () => {
expect(<%= propertyName %>()).toEqual('<%= name %>');
})
})

View File

@ -0,0 +1,3 @@
export function <%= propertyName %>(): string {
return '<%= name %>';
}

View File

@ -70,14 +70,21 @@ describe('lib', () => {
it('should create a local tsconfig.json', async () => { it('should create a local tsconfig.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree); const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json');
expect(tsconfigJson.references).toEqual([ expect(tsconfigJson).toMatchInlineSnapshot(`
{ Object {
path: './tsconfig.lib.json', "extends": "../../tsconfig.base.json",
}, "files": Array [],
{ "include": Array [],
path: './tsconfig.spec.json', "references": Array [
}, Object {
]); "path": "./tsconfig.lib.json",
},
Object {
"path": "./tsconfig.spec.json",
},
],
}
`);
}); });
it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { it('should extend the local tsconfig.json with tsconfig.spec.json', async () => {
@ -126,6 +133,7 @@ describe('lib', () => {
`); `);
expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.ts')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/lib/my-lib.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/README.md')).toBeTruthy(); expect(tree.exists('libs/my-lib/README.md')).toBeTruthy();
const ReadmeContent = tree.readContent('libs/my-lib/README.md'); const ReadmeContent = tree.readContent('libs/my-lib/README.md');
@ -183,6 +191,9 @@ describe('lib', () => {
expect( expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts') tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts')
).toBeTruthy(); ).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.ts')
).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy(); expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy();
}); });
@ -255,14 +266,19 @@ describe('lib', () => {
}); });
describe('--unit-test-runner none', () => { describe('--unit-test-runner none', () => {
it('should not generate test configuration', async () => { it('should not generate test configuration nor spec file', async () => {
const resultTree = await runSchematic( const resultTree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', unitTestRunner: 'none' }, { name: 'myLib', unitTestRunner: 'none' },
appTree appTree
); );
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
expect(
resultTree.exists('libs/my-lib/src/lib/my-lib.spec.ts')
).toBeFalsy();
const workspaceJson = readJsonInTree(resultTree, 'workspace.json'); const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
expect(workspaceJson.projects['my-lib'].architect.lint) expect(workspaceJson.projects['my-lib'].architect.lint)
@ -334,6 +350,31 @@ describe('lib', () => {
expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy();
expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy();
});
it('should update tsconfig.json with compilerOptions.allowJs: true', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', js: true },
appTree
);
expect(
readJsonInTree(tree, 'libs/my-lib/tsconfig.json').compilerOptions
).toEqual({
allowJs: true,
});
});
it('should update tsconfig.lib.json include with **/*.js glob', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', js: true },
appTree
);
expect(
readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').include
).toEqual(['**/*.ts', '**/*.js']);
}); });
it('should update root tsconfig.json with a js file path', async () => { it('should update root tsconfig.json with a js file path', async () => {
@ -359,6 +400,9 @@ describe('lib', () => {
expect( expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js') tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js')
).toBeTruthy(); ).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js')
).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy();
}); });
}); });
@ -407,4 +451,29 @@ describe('lib', () => {
`); `);
}); });
}); });
describe('--pascalCaseFiles', () => {
it('should generate files with upper case names', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', pascalCaseFiles: true },
appTree
);
expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy();
});
it('should generate files with upper case names for nested libs as well', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir', pascalCaseFiles: true },
appTree
);
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts')
).toBeTruthy();
});
});
}); });

View File

@ -11,20 +11,25 @@ import {
move, move,
noop, noop,
SchematicsException, SchematicsException,
filter,
} from '@angular-devkit/schematics'; } from '@angular-devkit/schematics';
import { join, normalize } from '@angular-devkit/core'; import { join, normalize } from '@angular-devkit/core';
import { Schema } from './schema'; import { Schema } from './schema';
import { updateWorkspaceInTree, getNpmScope } from '@nrwl/workspace'; import {
import { updateJsonInTree } from '@nrwl/workspace'; updateWorkspaceInTree,
import { toFileName, names } from '@nrwl/workspace'; getNpmScope,
import { formatFiles } from '@nrwl/workspace'; toFileName,
import { offsetFromRoot } from '@nrwl/workspace'; names,
updateJsonInTree,
formatFiles,
offsetFromRoot,
} from '@nrwl/workspace';
import { generateProjectLint, addLintFiles } from '../../utils/lint'; import { generateProjectLint, addLintFiles } from '../../utils/lint';
import { addProjectToNxJsonInTree, libsDir } from '../../utils/ast-utils'; import { addProjectToNxJsonInTree, libsDir } from '../../utils/ast-utils';
import { cliCommand } from '../../core/file-utils'; import { cliCommand } from '../../core/file-utils';
import { toJS } from '../../utils/rules/to-js'; import { toJS, updateTsConfigsToJs, maybeJs } from '../../utils/rules/to-js';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
name: string; name: string;
@ -82,17 +87,22 @@ function updateTsConfig(options: NormalizedSchema): Rule {
} }
function createFiles(options: NormalizedSchema): Rule { function createFiles(options: NormalizedSchema): Rule {
const { className, name, propertyName } = names(options.name);
return mergeWith( return mergeWith(
apply(url(`./files/lib`), [ apply(url(`./files/lib`), [
template({ template({
...options, ...options,
...names(options.name), className,
name,
propertyName,
cliCommand: cliCommand(), cliCommand: cliCommand(),
tmpl: '', tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot), offsetFromRoot: offsetFromRoot(options.projectRoot),
hasUnitTestRunner: options.unitTestRunner !== 'none', hasUnitTestRunner: options.unitTestRunner !== 'none',
}), }),
move(options.projectRoot), move(options.projectRoot),
addTestFiles(options),
options.js ? toJS() : noop(), options.js ? toJS() : noop(),
]) ])
); );
@ -102,6 +112,19 @@ function updateNxJson(options: NormalizedSchema): Rule {
return addProjectToNxJsonInTree(options.name, { tags: options.parsedTags }); return addProjectToNxJsonInTree(options.name, { tags: options.parsedTags });
} }
function addJest(options: NormalizedSchema) {
return options.unitTestRunner !== 'none'
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.name,
setupFile: 'none',
supportTsx: true,
babelJest: options.babelJest,
skipSerializers: true,
testEnvironment: options.testEnvironment,
})
: noop();
}
export default function (schema: Schema): Rule { export default function (schema: Schema): Rule {
return (host: Tree, context: SchematicContext) => { return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema); const options = normalizeOptions(host, schema);
@ -109,19 +132,11 @@ export default function (schema: Schema): Rule {
return chain([ return chain([
addLintFiles(options.projectRoot, options.linter), addLintFiles(options.projectRoot, options.linter),
createFiles(options), createFiles(options),
options.js ? updateTsConfigsToJs(options) : noop(),
!options.skipTsConfig ? updateTsConfig(options) : noop(), !options.skipTsConfig ? updateTsConfig(options) : noop(),
addProject(options), addProject(options),
updateNxJson(options), updateNxJson(options),
options.unitTestRunner !== 'none' addJest(options),
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.name,
setupFile: 'none',
supportTsx: true,
babelJest: options.babelJest,
skipSerializers: true,
testEnvironment: options.testEnvironment,
})
: noop(),
formatFiles(options), formatFiles(options),
])(host, context); ])(host, context);
}; };
@ -134,9 +149,11 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
: name; : name;
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = options.simpleModuleName ? name : projectName; const fileName = getCaseAwareFileName({
fileName: options.simpleModuleName ? name : projectName,
pascalCaseFiles: options.pascalCaseFiles,
});
// const projectRoot = `libs/${projectDirectory}`;
const projectRoot = `${libsDir(host)}/${projectDirectory}`; const projectRoot = `${libsDir(host)}/${projectDirectory}`;
const parsedTags = options.tags const parsedTags = options.tags
@ -157,8 +174,17 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
}; };
} }
function maybeJs(options: NormalizedSchema, path: string): string { function getCaseAwareFileName(options: {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) pascalCaseFiles: boolean;
? path.replace(/\.tsx?$/, '.js') fileName: string;
: path; }) {
const normalized = names(options.fileName);
return options.pascalCaseFiles ? normalized.className : normalized.fileName;
}
function addTestFiles(options: Pick<Schema, 'unitTestRunner'>) {
return options.unitTestRunner === 'none'
? filter((path) => !(path.endsWith('.ts') || path.endsWith('.tsx')))
: noop();
} }

View File

@ -11,6 +11,7 @@ export interface Schema {
linter: Linter; linter: Linter;
testEnvironment: 'jsdom' | 'node'; testEnvironment: 'jsdom' | 'node';
importPath?: string; importPath?: string;
js?: boolean; js: boolean;
babelJest?: boolean; babelJest?: boolean;
pascalCaseFiles: boolean;
} }

View File

@ -47,8 +47,8 @@
}, },
"skipTsConfig": { "skipTsConfig": {
"type": "boolean", "type": "boolean",
"default": false, "description": "Do not update tsconfig.json for development experience.",
"description": "Do not update tsconfig.json for development experience." "default": false
}, },
"testEnvironment": { "testEnvironment": {
"type": "string", "type": "string",
@ -65,6 +65,12 @@
"description": "Use babel instead ts-jest", "description": "Use babel instead ts-jest",
"default": false "default": false
}, },
"pascalCaseFiles": {
"type": "boolean",
"description": "Use pascal case file names.",
"alias": "P",
"default": false
},
"js": { "js": {
"type": "boolean", "type": "boolean",
"description": "Generate JavaScript files rather than TypeScript files", "description": "Generate JavaScript files rather than TypeScript files",

View File

@ -1,23 +1,94 @@
import { transpile, JsxEmit, ScriptTarget } from 'typescript'; import { transpile, JsxEmit, ScriptTarget } from 'typescript';
import { forEach, Rule, when } from '@angular-devkit/schematics'; import {
forEach,
Rule,
when,
chain,
Tree,
SchematicsException,
} from '@angular-devkit/schematics';
import { normalize } from '@angular-devkit/core'; import { normalize } from '@angular-devkit/core';
import { updateJsonInTree } from '../ast-utils';
export function toJS(): Rule { export function toJS(): Rule {
return forEach( return chain([
when( forEach(
(path) => path.endsWith('.ts') || path.endsWith('.tsx'), when(
(entry) => { (path) => path.endsWith('.ts') || path.endsWith('.tsx'),
const original = entry.content.toString('utf-8'); (entry) => {
const result = transpile(original, { const original = entry.content.toString('utf-8');
allowJs: true, const result = transpile(original, {
jsx: JsxEmit.Preserve, allowJs: true,
target: ScriptTarget.ESNext, jsx: JsxEmit.Preserve,
}); target: ScriptTarget.ESNext,
return { });
content: Buffer.from(result, 'utf-8'), return {
path: normalize(entry.path.replace(/\.tsx?$/, '.js')), content: Buffer.from(result, 'utf-8'),
}; path: normalize(entry.path.replace(/\.tsx?$/, '.js')),
} };
) }
); )
),
]);
}
export function updateTsConfigsToJs(options: { projectRoot: string }): Rule {
const paths = {
tsConfig: normalize(`${options.projectRoot}/tsconfig.json`),
tsConfigLib: normalize(`${options.projectRoot}/tsconfig.lib.json`),
tsConfigApp: normalize(`${options.projectRoot}/tsconfig.app.json`),
};
const getProjectType = (tree: Tree) => {
if (tree.exists(paths.tsConfigApp)) {
return 'application';
}
if (tree.exists(paths.tsConfigLib)) {
return 'library';
}
throw new SchematicsException(
`project is missing tsconfig.lib.json or tsconfig.app.json`
);
};
const getConfigFileForUpdate = (tree: Tree) => {
const projectType = getProjectType(tree);
if (projectType === 'library') {
return paths.tsConfigLib;
}
if (projectType === 'application') {
return paths.tsConfigApp;
}
};
return chain([
updateJsonInTree(paths.tsConfig, (json) => {
if (json.compilerOptions) {
json.compilerOptions.allowJs = true;
} else {
json.compilerOptions = { allowJs: true };
}
return json;
}),
(tree) => {
const updateConfigPath = getConfigFileForUpdate(tree);
return updateJsonInTree(updateConfigPath, (json) => {
json.include = uniq([...json.include, '**/*.js']);
json.exclude = uniq([...json.exclude, '**/*.spec.js']);
return json;
});
},
]);
}
const uniq = <T extends string[]>(value: T) => [...new Set(value)] as T;
export function maybeJs(options: { js: boolean }, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
} }