feat(react): refactored babel support so options are more easily customized (#3089)

This reverts commit 7679df22f5b90d085b5f32f2135d828036ee5aa7.
This commit is contained in:
Jack Hsu 2020-05-29 02:09:47 -04:00 committed by GitHub
parent a6220f7b0a
commit d593153a33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1478 additions and 921 deletions

View File

@ -16,7 +16,7 @@ List of static assets.
Type: `string`
Path to a function which takes a babel config and returns an updated babel config
(deprecated) Path to a function which takes a babel config and returns an updated babel config
### entryFile

View File

@ -17,7 +17,7 @@ List of static assets.
Type: `string`
Path to a function which takes a babel config and returns an updated babel config
(deprecated) Path to a function which takes a babel config and returns an updated babel config
### entryFile

View File

@ -199,7 +199,7 @@ forEachCli((currentCLIName) => {
);
}, 120000);
xit('should be able to use babel-jest', async () => {
it('should be able to use babel-jest', async () => {
ensureProject();
const appName = uniq('app');
const libName = uniq('lib');
@ -211,11 +211,6 @@ forEachCli((currentCLIName) => {
`generate @nrwl/react:lib ${libName} --no-interactive --babelJest`
);
checkFilesExist(
`apps/${appName}/babel-jest.config.json`,
`libs/${libName}/babel-jest.config.json`
);
const appTestResults = await runCLIAsync(`test ${appName}`);
expect(appTestResults.stderr).toContain('Test Suites: 1 passed, 1 total');
@ -308,7 +303,7 @@ forEachCli((currentCLIName) => {
}
checkFilesExist(...filesToCheck);
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
'var App = () => {'
'const App = () =>'
);
runCLI(`build ${appName} --prod --output-hashing none`);
filesToCheck = [

View File

@ -175,7 +175,7 @@ export function newProject(): void {
`@nrwl/storybook`,
`@nrwl/nx-plugin`,
];
yarnAdd(packages.join(` `));
yarnAdd([`@nrwl/eslint-plugin-nx`].concat(packages).join(` `));
packages
.filter((f) => f != '@nrwl/nx-plugin')
.forEach((p) => {

View File

@ -1,4 +1,3 @@
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import {
checkFilesExist,
ensureProject,
@ -6,7 +5,6 @@ import {
readFile,
runCLI,
runCLIAsync,
supportUi,
uniq,
updateFile,
} from './utils';
@ -54,72 +52,9 @@ forEachCli((currentCLIName) => {
const lintE2eResults = runCLI(`lint ${appName}-e2e`);
expect(lintE2eResults).toContain('All files pass linting.');
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}, 120000);
it('should support same syntax as TypeScript', () => {
ensureProject();
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
const mainPath = `apps/${appName}/src/app/app.element.ts`;
const content = readFile(mainPath);
updateFile(
mainPath,
content
// Testing decorators
.replace(
`export class AppElement extends HTMLElement`,
stripIndents`
function myDecorator(ctor) {
ctor.title = '${appName}';
}
@myDecorator
export class AppElement extends HTMLElement`
)
.replace('${title}', '${(AppElement as any).title}') +
// Testing const enums
stripIndents`
export const enum MyEnum {
a,
b,
b
};
`
);
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}
});
it('should support CSS modules', () => {
ensureProject();
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
updateFile(
`apps/${appName}/src/app/app.module.css`,
'.foo { color: red; }'
);
const mainPath = `apps/${appName}/src/app/app.element.ts`;
const content = readFile(mainPath);
updateFile(
mainPath,
`import styles from './app.module.css';\n${content}`
);
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}
});
});
describe('CLI - Environment Variables', () => {
@ -140,7 +75,7 @@ forEachCli((currentCLIName) => {
env: { ...process.env, NODE_ENV: 'test', NX_BUILD: '52', NX_API: 'QA' },
});
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
'var envVars = ["test", "52", "QA"];'
'const envVars = ["test", "52", "QA"];'
);
});
});

View File

@ -50,13 +50,13 @@
"@angular/router": "^9.1.0",
"@angular/service-worker": "^9.1.0",
"@angular/upgrade": "^9.1.0",
"@babel/core": "7.8.4",
"@babel/core": "7.9.6",
"@babel/preset-env": "7.9.6",
"@babel/plugin-proposal-class-properties": "7.8.3",
"@babel/plugin-proposal-decorators": "7.8.3",
"@babel/plugin-transform-regenerator": "7.8.3",
"@babel/preset-env": "7.8.4",
"@babel/preset-react": "7.8.3",
"@babel/preset-typescript": "7.8.3",
"@babel/plugin-transform-regenerator": "7.8.7",
"@babel/preset-typescript": "7.9.0",
"@babel/preset-react": "7.9.4",
"@bazel/bazel": "^1.2.0",
"@bazel/ibazel": "^0.10.3",
"@cypress/webpack-preprocessor": "^4.1.2",
@ -73,6 +73,7 @@
"@ngrx/store-devtools": "9.1.0",
"@ngtools/webpack": "~9.1.0",
"@reduxjs/toolkit": "1.3.2",
"@rollup/plugin-babel": "5.0.2",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-image": "2.0.4",
"@rollup/plugin-node-resolve": "7.1.1",
@ -110,12 +111,12 @@
"app-root-path": "^2.0.1",
"autoprefixer": "9.7.4",
"axios": "^0.19.0",
"babel-loader": "8.0.6",
"babel-plugin-const-enum": "^0.0.5",
"babel-loader": "8.1.0",
"babel-plugin-const-enum": "^1.0.1",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-async-to-promises": "^0.8.15",
"babel-plugin-emotion": "^10.0.29",
"babel-plugin-macros": "2.6.1",
"babel-plugin-styled-components": "^1.10.7",
"babel-plugin-transform-async-to-promises": "0.8.15",
"browserslist": "4.8.7",
"cacache": "12.0.2",
"caniuse-lite": "^1.0.30001030",
@ -126,7 +127,7 @@
"confusing-browser-globals": "^1.0.9",
"conventional-changelog-cli": "^2.0.23",
"copy-webpack-plugin": "5.1.1",
"core-js": "^2.6.9",
"core-js": "^3.6.5",
"cosmiconfig": "^4.0.0",
"css-loader": "3.4.2",
"cypress": "^4.1.0",
@ -197,7 +198,6 @@
"regenerator-runtime": "0.13.3",
"release-it": "^7.4.0",
"rollup": "1.31.1",
"rollup-plugin-babel": "4.3.3",
"rollup-plugin-copy": "3.3.0",
"rollup-plugin-filesize": "6.2.1",
"rollup-plugin-local-resolve": "1.0.7",

View File

@ -23,7 +23,6 @@ describe('init', () => {
expect(dependencies['@angular/platform-browser']).toBeDefined();
expect(dependencies['@angular/platform-browser-dynamic']).toBeDefined();
expect(dependencies['@angular/router']).toBeDefined();
expect(dependencies['core-js']).toBeDefined();
expect(dependencies['rxjs']).toBeDefined();
expect(dependencies['zone.js']).toBeDefined();
expect(devDependencies['@angular/compiler-cli']).toBeDefined();

View File

@ -34,7 +34,6 @@ const updateDependencies = addDepsToPackageJson(
'@angular/platform-browser': angularVersion,
'@angular/platform-browser-dynamic': angularVersion,
'@angular/router': angularVersion,
'core-js': '^2.5.4',
rxjs: rxjsVersion,
'zone.js': '^0.10.2',
},

View File

@ -1,6 +1,5 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
<% if (isLibrary) { %>
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
<% } %>
import 'zone.js/dist/zone-testing';

View File

@ -135,7 +135,6 @@ module.exports = function(config) {
appTree
);
const testTs = resultTree.read('libs/lib1/src/test.ts').toString();
expect(testTs).toContain("import 'core-js/es7/reflect';");
expect(testTs).toContain("import 'zone.js/dist/zone';");
});
});
@ -199,7 +198,6 @@ module.exports = function(config) {
appTree
);
const testTs = resultTree.read('apps/app1/src/test.ts').toString();
expect(testTs).not.toContain("import 'core-js/es7/reflect';");
expect(testTs).not.toContain("import 'zone.js/dist/zone';");
});
});

View File

@ -133,38 +133,6 @@ describe('jestProject', () => {
});
});
describe('--babelJest', () => {
it('should have a babel config when true', async () => {
const resultTree = await runSchematic(
'jest-project',
{
project: 'lib1',
setupFile: 'none',
babelJest: true,
},
appTree
);
expect(
resultTree.exists('/libs/lib1/babel-jest.config.json')
).toBeTruthy();
});
it('should NOT have a babel config when false', async () => {
const resultTree = await runSchematic(
'jest-project',
{
project: 'lib1',
setupFile: 'none',
babelJest: false,
},
appTree
);
expect(
resultTree.exists('/libs/lib1/babel-jest.config.json')
).toBeFalsy();
});
});
describe('--setup-file', () => {
it('should generate src/test-setup.ts', async () => {
const resultTree = await runSchematic(

View File

@ -3,8 +3,8 @@ export const jestVersion = '25.2.3';
export const jestTypesVersion = '25.1.4';
export const tsJestVersion = '25.2.1';
export const babelCoreVersion = '7.8.4';
export const babelPresetEnvVersion = '7.8.4';
export const babelPresetTypescriptVersion = '7.8.3';
export const babelPresetReactVersion = '7.8.3';
export const babelCoreVersion = '7.9.6';
export const babelPresetEnvVersion = '7.9.6';
export const babelPresetTypescriptVersion = '7.9.0';
export const babelPresetReactVersion = '7.9.4';
export const babelJestVersion = '25.1.0';

View File

@ -1,4 +1,4 @@
{
"presets": ["next/babel"],
"plugins": [<% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %> ]
"plugins": [<% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %>]
}

10
packages/react/babel.ts Normal file
View File

@ -0,0 +1,10 @@
/*
* Babel preset to provide React support for Nx.
*/
module.exports = function (api: any, options: {}) {
api.assertVersion(7);
return {
presets: [[require.resolve('@babel/preset-react'), { useBuiltIns: true }]],
};
};

View File

@ -39,6 +39,16 @@
"version": "8.12.0-beta.1",
"description": "Ensure React correct webpack config and babel preset",
"factory": "./src/migrations/update-8-12-0/fix-react-files-8-12-0"
},
"update-9.4.0": {
"version": "9.4.0-beta.1",
"description": "Update libraries",
"factory": "./src/migrations/update-9-4-0/update-9-4-0"
},
"babelrc-9.4.0": {
"version": "9.4.0-beta.1",
"description": "Migrate to new babel setup for greater flexibility",
"factory": "./src/migrations/update-9-4-0/babelrc-9-4-0"
}
},
"packageJsonUpdates": {
@ -181,6 +191,79 @@
"alwaysAddToPackageJson": false
}
}
},
"9.4.0": {
"version": "9.4.0-beta.1",
"packages": {
"react": {
"version": "16.13.1",
"alwaysAddToPackageJson": false
},
"react-dom": {
"version": "16.13.1",
"alwaysAddToPackageJson": false
},
"react-is": {
"version": "16.13.1",
"alwaysAddToPackageJson": false
},
"@types/react": {
"version": "16.9.35",
"alwaysAddToPackageJson": false
},
"@types/react-dom": {
"version": "16.9.8",
"alwaysAddToPackageJson": false
},
"@types/react-is": {
"version": "16.7.1",
"alwaysAddToPackageJson": false
},
"styled-components": {
"version": "5.1.0",
"alwaysAddToPackageJson": false
},
"@types/styled-components": {
"version": "5.1.0",
"alwaysAddToPackageJson": false
},
"react-router-dom": {
"version": "5.2.0",
"alwaysAddToPackageJson": false
},
"@types/react-router-dom": {
"version": "5.1.5",
"alwaysAddToPackageJson": false
},
"@testing-library/react": {
"version": "10.0.4",
"alwaysAddToPackageJson": false
},
"@reduxjs/toolkit": {
"version": "1.3.6",
"alwaysAddToPackageJson": false
},
"react-redux": {
"version": "7.2.0",
"alwaysAddToPackageJson": false
},
"@types/react-redux": {
"version": "7.1.9",
"alwaysAddToPackageJson": false
},
"eslint-plugin-import": {
"version": "2.20.2",
"alwaysAddToPackageJson": false
},
"eslint-plugin-react": {
"version": "7.20.0",
"alwaysAddToPackageJson": false
},
"eslint-plugin-react-hooks": {
"version": "4.0.2",
"alwaysAddToPackageJson": false
}
}
}
}
}

View File

@ -31,15 +31,13 @@
"@nrwl/workspace": "*"
},
"dependencies": {
"@babel/core": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"@babel/core": "7.9.6",
"@babel/preset-react": "7.9.4",
"@nrwl/cypress": "*",
"@nrwl/jest": "*",
"@nrwl/web": "*",
"@angular-devkit/schematics": "~9.1.0",
"@svgr/webpack": "^5.2.0",
"babel-plugin-emotion": "^10.0.29",
"babel-plugin-styled-components": "^1.10.7",
"confusing-browser-globals": "^1.0.9",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",

View File

@ -1,12 +1,7 @@
import { Configuration } from 'webpack';
import { updateBabelOptions } from '../src/utils/babel-utils';
// Add React-specific configuration
function getWebpackConfig(config: Configuration) {
const idx = config.module.rules.findIndex((r) => r.loader === 'babel-loader');
const babelRuleOptions = config.module.rules[idx].options as any;
updateBabelOptions(babelRuleOptions);
config.module.rules.push(
{
test: /\.(png|jpe?g|gif|webp)$/,

View File

@ -0,0 +1,74 @@
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import * as path from 'path';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { createApp, createLib, createWebApp } from '../../utils/testing';
describe('Migrate babel setup', () => {
let tree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(async () => {
tree = Tree.empty();
tree = createEmptyWorkspace(tree);
schematicRunner = new SchematicTestRunner(
'@nrwl/react',
path.join(__dirname, '../../../migrations.json')
);
tree.overwrite(
'package.json',
JSON.stringify({
dependencies: {
react: '16.13.1',
'react-dom': '16.13.1',
},
})
);
});
it(`should create .babelrc for projects without them`, async () => {
tree = await createApp(tree, 'demo');
tree = await createLib(tree, 'ui');
tree = await schematicRunner
.runSchematicAsync('babelrc-9.4.0', {}, tree)
.toPromise();
expect(tree.exists('/babel.config.json')).toBe(true);
expect(tree.exists('/apps/demo/.babelrc')).toBe(true);
expect(tree.exists('/libs/ui/.babelrc')).toBe(true);
});
it(`should not overwrite existing .babelrc files`, async () => {
tree = await createApp(tree, 'demo');
tree.create('/apps/demo/.babelrc', '{}');
tree = await schematicRunner
.runSchematicAsync('babelrc-9.4.0', {}, tree)
.toPromise();
const content = tree.read('/apps/demo/.babelrc').toString();
expect(content).toEqual('{}');
});
it(`should not migrate non-React projects`, async () => {
tree = await createWebApp(tree, 'demo');
tree = await schematicRunner
.runSchematicAsync('babelrc-9.4.0', {}, tree)
.toPromise();
expect(tree.exists('/apps/demo/.babelrc')).toBe(false);
});
it(`should not overwrite babel.config.js file`, async () => {
tree.create('/babel.config.js', 'module.exports = {};');
tree = await schematicRunner
.runSchematicAsync('babelrc-9.4.0', {}, tree)
.toPromise();
expect(tree.exists('/babel.config.js')).toBe(true);
expect(tree.exists('/babel.config.json')).toBe(false);
});
});

View File

@ -0,0 +1,110 @@
import {
chain,
Rule,
SchematicContext,
Tree,
} from '@angular-devkit/schematics';
import { getFullProjectGraphFromHost } from '@nrwl/workspace/src/utils/ast-utils';
import {
stripIndent,
stripIndents,
} from '@angular-devkit/core/src/utils/literals';
import { initRootBabelConfig } from '@nrwl/web/src/utils/rules';
/*
* This migration will do a few things:
*
* - Create the base babel.config.json file if it doesn't already exist
* - Create .babelrc files for each React project that doesn't already have one
* - For any projects that are not migrated, display a message so users are not surprised.
*/
export default function update(): Rule {
return (host: Tree, context: SchematicContext) => {
const updates = [];
const conflicts: Array<[string, string]> = [];
const projectGraph = getFullProjectGraphFromHost(host);
if (host.exists('/babel.config.json')) {
context.logger.info(
`
Found an existing babel.config.json file so we skipped creating it.
You may want to update it to include the Nx preset "@nrwl/web/babel".
`
);
} else if (host.exists('/babel.config.js')) {
context.logger.info(
`
Found an existing babel.config.js file so we skipped creating it.
You may want to update it to include the Nx preset "@nrwl/web/babel".
`
);
} else {
updates.push(initRootBabelConfig());
}
Object.keys(projectGraph.nodes).forEach((name) => {
const p = projectGraph.nodes[name];
const deps = projectGraph.dependencies[name];
const isReact = deps.some(
(d) =>
projectGraph.nodes[d.target].type === 'npm' &&
d.target.indexOf('react') !== -1
);
if (isReact) {
updates.push((host) => {
const babelrcPath = `${p.data.root}/.babelrc`;
if (host.exists(babelrcPath)) {
conflicts.push([name, babelrcPath]);
} else {
createBabelrc(host, context, babelrcPath, deps);
}
});
}
});
if (conflicts.length > 0) {
context.logger.info(stripIndent`
The following projects already have .babelrc so we did not create them:
${conflicts
.map(([name, babelrc]) => `${name} - ${babelrc}`)
.join('\n ')}
You may want to update them to include the Nx preset "@nrwl/react/babel".
`);
}
return chain(updates);
};
}
function createBabelrc(host, context, babelrcPath, deps) {
const babelrc = {
presets: ['@nrwl/react/babel'],
plugins: [],
};
let added = 0;
if (deps.some((d) => d.target === 'npm:styled-components')) {
babelrc.plugins.push(['styled-components', { pure: true, ssr: true }]);
added++;
}
if (deps.some((d) => d.target.startsWith('npm:@emotion'))) {
babelrc.presets.push('@emotion/babel-preset-css-prop');
added++;
}
if (added > 1) {
context.logger.warn(
stripIndents`We created a babel config at ${babelrcPath} with both styled-components and emotion plugins.
Only one should be used, please remove the unused plugin.
For example, if you don't use styled-components, then remove that plugin from the .babelrc file.
`
);
}
host.create(babelrcPath, JSON.stringify(babelrc, null, 2));
}

View File

@ -0,0 +1,47 @@
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { readJsonInTree } from '@nrwl/workspace';
import * as path from 'path';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('Update 9.4.0', () => {
let tree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(async () => {
tree = Tree.empty();
tree = createEmptyWorkspace(tree);
schematicRunner = new SchematicTestRunner(
'@nrwl/react',
path.join(__dirname, '../../../migrations.json')
);
});
it(`should update libs`, async () => {
tree.overwrite(
'package.json',
JSON.stringify({
dependencies: {
react: '16.12.1',
},
devDependencies: {
'@types/react': '16.9.1',
},
})
);
tree = await schematicRunner
.runSchematicAsync('update-9.4.0', {}, tree)
.toPromise();
const packageJson = readJsonInTree(tree, '/package.json');
expect(packageJson).toMatchObject({
dependencies: {
react: '16.13.1',
},
devDependencies: {
'@types/react': '16.9.35',
},
});
});
});

View File

@ -0,0 +1,13 @@
import { chain, Rule } from '@angular-devkit/schematics';
import { formatFiles, updatePackagesInPackageJson } from '@nrwl/workspace';
import * as path from 'path';
export default function update(): Rule {
return chain([
updatePackagesInPackageJson(
path.join(__dirname, '../../../', 'migrations.json'),
'9.4.0'
),
formatFiles(),
]);
}

View File

@ -0,0 +1,13 @@
import { noop, Rule } from '@angular-devkit/schematics';
import { addDepsToPackageJson } from '@nrwl/workspace';
import { CSS_IN_JS_DEPENDENCIES } from '../utils/styled';
export function addStyledModuleDependencies(styledModule: string): Rule {
const extraDependencies = CSS_IN_JS_DEPENDENCIES[styledModule];
return extraDependencies
? addDepsToPackageJson(
extraDependencies.dependencies,
extraDependencies.devDependencies
)
: noop();
}

View File

@ -1,7 +1,7 @@
import { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import * as stripJsonComments from 'strip-json-comments';
import { readJsonInTree, NxJson } from '@nrwl/workspace';
import { NxJson, readJsonInTree } from '@nrwl/workspace';
import { runSchematic } from '../../utils/testing';
describe('app', () => {
@ -44,6 +44,7 @@ describe('app', () => {
it('should generate files', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
expect(tree.exists('apps/my-app/.babelrc')).toBeTruthy();
expect(tree.exists('apps/my-app/src/main.tsx')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.tsx')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.spec.tsx')).toBeTruthy();

View File

@ -1,63 +1,27 @@
import { join, JsonObject, normalize, Path } from '@angular-devkit/core';
import {
apply,
chain,
externalSchematic,
filter,
mergeWith,
move,
noop,
Rule,
SchematicContext,
template,
Tree,
url,
} from '@angular-devkit/schematics';
import {
addLintFiles,
formatFiles,
generateProjectLint,
insert,
names,
NxJson,
offsetFromRoot,
toFileName,
updateJsonInTree,
updateWorkspace,
} from '@nrwl/workspace';
import {
addDepsToPackageJson,
appsDir,
updateWorkspaceInTree,
} from '@nrwl/workspace/src/utils/ast-utils';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import * as ts from 'typescript';
import { assertValidStyle } from '../../utils/assertion';
import { addInitialRoutes } from '../../utils/ast-utils';
import { updateJestConfigContent } from '../../utils/jest-utils';
import { addLintFiles, formatFiles } from '@nrwl/workspace';
import { extraEslintDependencies, reactEslintJson } from '../../utils/lint';
import { CSS_IN_JS_DEPENDENCIES } from '../../utils/styled';
import {
reactRouterDomVersion,
typesReactRouterDomVersion,
} from '../../utils/versions';
import init from '../init/init';
import { Schema } from './schema';
interface NormalizedSchema extends Schema {
projectName: string;
appProjectRoot: Path;
e2eProjectName: string;
parsedTags: string[];
fileName: string;
styledModule: null | string;
hasStyles: boolean;
}
import { createApplicationFiles } from './lib/create-application-files';
import { updateJestConfig } from './lib/update-jest-config';
import { normalizeOptions } from './lib/normalize-options';
import { addProject } from './lib/add-project';
import { addCypress } from './lib/add-cypress';
import { addJest } from './lib/add-jest';
import { addRouting } from './lib/add-routing';
import { setDefaults } from './lib/set-defaults';
import { updateNxJson } from './lib/update-nx-json';
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
export default function (schema: Schema): Rule {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
return chain([
init({
...options,
@ -73,283 +37,10 @@ export default function (schema: Schema): Rule {
addCypress(options),
addJest(options),
updateJestConfig(options),
addStyledModuleDependencies(options),
addStyledModuleDependencies(options.styledModule),
addRouting(options, context),
setDefaults(options),
formatFiles(options),
]);
};
}
function createApplicationFiles(options: NormalizedSchema): Rule {
return mergeWith(
apply(url(`./files/app`), [
template({
...names(options.name),
...options,
tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
}),
options.styledModule || !options.hasStyles
? filter((file) => !file.endsWith(`.${options.style}`))
: noop(),
options.unitTestRunner === 'none'
? filter((file) => file !== `/src/app/${options.fileName}.spec.tsx`)
: noop(),
move(options.appProjectRoot),
options.js ? toJS() : noop(),
])
);
}
function updateJestConfig(options: NormalizedSchema): Rule {
return options.unitTestRunner === 'none'
? noop()
: (host) => {
const configPath = `${options.appProjectRoot}/jest.config.js`;
const originalContent = host.read(configPath).toString();
const content = updateJestConfigContent(originalContent);
host.overwrite(configPath, content);
};
}
function updateNxJson(options: NormalizedSchema): Rule {
return updateJsonInTree<NxJson>('nx.json', (json) => {
json.projects[options.projectName] = { tags: options.parsedTags };
return json;
});
}
function addProject(options: NormalizedSchema): Rule {
return updateWorkspaceInTree((json) => {
const architect: { [key: string]: any } = {};
architect.build = {
builder: '@nrwl/web:build',
options: {
outputPath: join(normalize('dist'), options.appProjectRoot),
index: join(options.appProjectRoot, 'src/index.html'),
main: join(options.appProjectRoot, maybeJs(options, `src/main.tsx`)),
polyfills: join(
options.appProjectRoot,
maybeJs(options, 'src/polyfills.ts')
),
tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'),
assets: [
join(options.appProjectRoot, 'src/favicon.ico'),
join(options.appProjectRoot, 'src/assets'),
],
styles:
options.styledModule || !options.hasStyles
? []
: [join(options.appProjectRoot, `src/styles.${options.style}`)],
scripts: [],
webpackConfig: '@nrwl/react/plugins/webpack',
},
configurations: {
production: {
fileReplacements: [
{
replace: join(
options.appProjectRoot,
maybeJs(options, `src/environments/environment.ts`)
),
with: join(
options.appProjectRoot,
maybeJs(options, `src/environments/environment.prod.ts`)
),
},
],
optimization: true,
outputHashing: 'all',
sourceMap: false,
extractCss: true,
namedChunks: false,
extractLicenses: true,
vendorChunk: false,
budgets: [
{
type: 'initial',
maximumWarning: '2mb',
maximumError: '5mb',
},
],
},
},
};
architect.serve = {
builder: '@nrwl/web:dev-server',
options: {
buildTarget: `${options.projectName}:build`,
},
configurations: {
production: {
buildTarget: `${options.projectName}:build:production`,
},
},
};
architect.lint = generateProjectLint(
normalize(options.appProjectRoot),
join(normalize(options.appProjectRoot), 'tsconfig.app.json'),
options.linter
);
json.projects[options.projectName] = {
root: options.appProjectRoot,
sourceRoot: join(options.appProjectRoot, 'src'),
projectType: 'application',
schematics: {},
architect,
};
json.defaultProject = json.defaultProject || options.projectName;
return json;
});
}
function addCypress(options: NormalizedSchema): Rule {
return options.e2eTestRunner === 'cypress'
? externalSchematic('@nrwl/cypress', 'cypress-project', {
...options,
name: options.name + '-e2e',
directory: options.directory,
project: options.projectName,
})
: noop();
}
function addJest(options: NormalizedSchema): Rule {
return options.unitTestRunner === 'jest'
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.projectName,
supportTsx: true,
skipSerializers: true,
setupFile: 'none',
babelJest: options.babelJest,
})
: noop();
}
function addStyledModuleDependencies(options: NormalizedSchema): Rule {
const extraDependencies = CSS_IN_JS_DEPENDENCIES[options.styledModule];
return extraDependencies
? addDepsToPackageJson(
extraDependencies.dependencies,
extraDependencies.devDependencies
)
: noop();
}
function addRouting(
options: NormalizedSchema,
context: SchematicContext
): Rule {
return options.routing
? chain([
function addRouterToComponent(host: Tree) {
const appPath = join(
options.appProjectRoot,
maybeJs(options, `src/app/${options.fileName}.tsx`)
);
const appFileContent = host.read(appPath).toString('utf-8');
const appSource = ts.createSourceFile(
appPath,
appFileContent,
ts.ScriptTarget.Latest,
true
);
insert(host, appPath, addInitialRoutes(appPath, appSource, context));
},
addDepsToPackageJson(
{ 'react-router-dom': reactRouterDomVersion },
{ '@types/react-router-dom': typesReactRouterDomVersion }
),
])
: noop();
}
function setDefaults(options: NormalizedSchema): Rule {
return options.skipWorkspaceJson
? noop()
: updateWorkspace((workspace) => {
workspace.extensions.schematics = jsonIdentity(
workspace.extensions.schematics || {}
);
workspace.extensions.schematics['@nrwl/react'] =
workspace.extensions.schematics['@nrwl/react'] || {};
const prev = jsonIdentity(
workspace.extensions.schematics['@nrwl/react']
);
workspace.extensions.schematics = {
...workspace.extensions.schematics,
'@nrwl/react': {
...prev,
application: {
style: options.style,
linter: options.linter,
...jsonIdentity(prev.application),
},
component: {
style: options.style,
...jsonIdentity(prev.component),
},
library: {
style: options.style,
linter: options.linter,
...jsonIdentity(prev.library),
},
},
};
});
}
function jsonIdentity(x: any): JsonObject {
return x as JsonObject;
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appDirectory = options.directory
? `${toFileName(options.directory)}/${toFileName(options.name)}`
: toFileName(options.name);
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const e2eProjectName = `${appProjectName}-e2e`;
const appProjectRoot = normalize(`${appsDir(host)}/${appDirectory}`);
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const fileName = options.pascalCaseFiles ? 'App' : 'app';
const styledModule = /^(css|scss|less|styl|none)$/.test(options.style)
? null
: options.style;
assertValidStyle(options.style);
return {
...options,
name: toFileName(options.name),
projectName: appProjectName,
appProjectRoot,
e2eProjectName,
parsedTags,
fileName,
styledModule,
hasStyles: options.style !== 'none',
};
}
function maybeJs(options: NormalizedSchema, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
}

View File

@ -0,0 +1,9 @@
{
"presets": [
"@nrwl/react/babel",
<% if (style === '@emotion/styled') { %>"@emotion/babel-preset-css-prop"<% } %>
],
"plugins": [
<% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %>
]
}

View File

@ -0,0 +1,13 @@
import { externalSchematic, noop, Rule } from '@angular-devkit/schematics';
import { NormalizedSchema } from '../schema';
export function addCypress(options: NormalizedSchema): Rule {
return options.e2eTestRunner === 'cypress'
? externalSchematic('@nrwl/cypress', 'cypress-project', {
...options,
name: options.name + '-e2e',
directory: options.directory,
project: options.projectName,
})
: noop();
}

View File

@ -0,0 +1,14 @@
import { externalSchematic, noop, Rule } from '@angular-devkit/schematics';
import { NormalizedSchema } from '../schema';
export function addJest(options: NormalizedSchema): Rule {
return options.unitTestRunner === 'jest'
? externalSchematic('@nrwl/jest', 'jest-project', {
project: options.projectName,
supportTsx: true,
skipSerializers: true,
setupFile: 'none',
babelJest: options.babelJest,
})
: noop();
}

View File

@ -0,0 +1,100 @@
import { Rule } from '@angular-devkit/schematics';
import { generateProjectLint, updateWorkspaceInTree } from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core';
import { NormalizedSchema } from '../schema';
export function addProject(options: NormalizedSchema): Rule {
return updateWorkspaceInTree((json) => {
const architect: { [key: string]: any } = {};
architect.build = {
builder: '@nrwl/web:build',
options: {
outputPath: join(normalize('dist'), options.appProjectRoot),
index: join(options.appProjectRoot, 'src/index.html'),
main: join(options.appProjectRoot, maybeJs(options, `src/main.tsx`)),
polyfills: join(
options.appProjectRoot,
maybeJs(options, 'src/polyfills.ts')
),
tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'),
assets: [
join(options.appProjectRoot, 'src/favicon.ico'),
join(options.appProjectRoot, 'src/assets'),
],
styles:
options.styledModule || !options.hasStyles
? []
: [join(options.appProjectRoot, `src/styles.${options.style}`)],
scripts: [],
webpackConfig: '@nrwl/react/plugins/webpack',
},
configurations: {
production: {
fileReplacements: [
{
replace: join(
options.appProjectRoot,
maybeJs(options, `src/environments/environment.ts`)
),
with: join(
options.appProjectRoot,
maybeJs(options, `src/environments/environment.prod.ts`)
),
},
],
optimization: true,
outputHashing: 'all',
sourceMap: false,
extractCss: true,
namedChunks: false,
extractLicenses: true,
vendorChunk: false,
budgets: [
{
type: 'initial',
maximumWarning: '2mb',
maximumError: '5mb',
},
],
},
},
};
architect.serve = {
builder: '@nrwl/web:dev-server',
options: {
buildTarget: `${options.projectName}:build`,
},
configurations: {
production: {
buildTarget: `${options.projectName}:build:production`,
},
},
};
architect.lint = generateProjectLint(
normalize(options.appProjectRoot),
join(normalize(options.appProjectRoot), 'tsconfig.app.json'),
options.linter
);
json.projects[options.projectName] = {
root: options.appProjectRoot,
sourceRoot: join(options.appProjectRoot, 'src'),
projectType: 'application',
schematics: {},
architect,
};
json.defaultProject = json.defaultProject || options.projectName;
return json;
});
}
function maybeJs(options: NormalizedSchema, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
}

View File

@ -0,0 +1,51 @@
import * as ts from 'typescript';
import {
chain,
noop,
Rule,
SchematicContext,
Tree,
} from '@angular-devkit/schematics';
import { join } from '@angular-devkit/core';
import { addDepsToPackageJson, insert } from '@nrwl/workspace';
import { addInitialRoutes } from '../../../utils/ast-utils';
import { NormalizedSchema } from '../schema';
import {
reactRouterDomVersion,
typesReactRouterDomVersion,
} from '@nrwl/react/src/utils/versions';
export function addRouting(
options: NormalizedSchema,
context: SchematicContext
): Rule {
return options.routing
? chain([
function addRouterToComponent(host: Tree) {
const appPath = join(
options.appProjectRoot,
maybeJs(options, `src/app/${options.fileName}.tsx`)
);
const appFileContent = host.read(appPath).toString('utf-8');
const appSource = ts.createSourceFile(
appPath,
appFileContent,
ts.ScriptTarget.Latest,
true
);
insert(host, appPath, addInitialRoutes(appPath, appSource, context));
},
addDepsToPackageJson(
{ 'react-router-dom': reactRouterDomVersion },
{ '@types/react-router-dom': typesReactRouterDomVersion }
),
])
: noop();
}
function maybeJs(options: NormalizedSchema, path: string): string {
return options.js && (path.endsWith('.ts') || path.endsWith('.tsx'))
? path.replace(/\.tsx?$/, '.js')
: path;
}

View File

@ -0,0 +1,34 @@
import {
apply,
filter,
mergeWith,
move,
noop,
Rule,
template,
url,
} from '@angular-devkit/schematics';
import { names, offsetFromRoot } from '@nrwl/workspace';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import { NormalizedSchema } from '../schema';
export function createApplicationFiles(options: NormalizedSchema): Rule {
return mergeWith(
apply(url(`./files/app`), [
template({
...names(options.name),
...options,
tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
}),
options.styledModule || !options.hasStyles
? filter((file) => !file.endsWith(`.${options.style}`))
: noop(),
options.unitTestRunner === 'none'
? filter((file) => file !== `/src/app/${options.fileName}.spec.tsx`)
: noop(),
move(options.appProjectRoot),
options.js ? toJS() : noop(),
])
);
}

View File

@ -0,0 +1,44 @@
import { Tree } from '@angular-devkit/schematics';
import { normalize } from '@angular-devkit/core';
import { toFileName } from '@nrwl/workspace';
import { appsDir } from '@nrwl/workspace/src/utils/ast-utils';
import { NormalizedSchema, Schema } from '../schema';
import { assertValidStyle } from '../../../utils/assertion';
export function normalizeOptions(
host: Tree,
options: Schema
): NormalizedSchema {
const appDirectory = options.directory
? `${toFileName(options.directory)}/${toFileName(options.name)}`
: toFileName(options.name);
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const e2eProjectName = `${appProjectName}-e2e`;
const appProjectRoot = normalize(`${appsDir(host)}/${appDirectory}`);
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const fileName = options.pascalCaseFiles ? 'App' : 'app';
const styledModule = /^(css|scss|less|styl|none)$/.test(options.style)
? null
: options.style;
assertValidStyle(options.style);
return {
...options,
name: toFileName(options.name),
projectName: appProjectName,
appProjectRoot,
e2eProjectName,
parsedTags,
fileName,
styledModule,
hasStyles: options.style !== 'none',
};
}

View File

@ -0,0 +1,44 @@
import { noop, Rule } from '@angular-devkit/schematics';
import { updateWorkspace } from '@nrwl/workspace';
import { NormalizedSchema } from '../schema';
import { JsonObject } from '@angular-devkit/core';
export function setDefaults(options: NormalizedSchema): Rule {
return options.skipWorkspaceJson
? noop()
: updateWorkspace((workspace) => {
workspace.extensions.schematics = jsonIdentity(
workspace.extensions.schematics || {}
);
workspace.extensions.schematics['@nrwl/react'] =
workspace.extensions.schematics['@nrwl/react'] || {};
const prev = jsonIdentity(
workspace.extensions.schematics['@nrwl/react']
);
workspace.extensions.schematics = {
...workspace.extensions.schematics,
'@nrwl/react': {
...prev,
application: {
style: options.style,
linter: options.linter,
...jsonIdentity(prev.application),
},
component: {
style: options.style,
...jsonIdentity(prev.component),
},
library: {
style: options.style,
linter: options.linter,
...jsonIdentity(prev.library),
},
},
};
});
}
function jsonIdentity(x: any): JsonObject {
return x as JsonObject;
}

View File

@ -0,0 +1,14 @@
import { noop, Rule } from '@angular-devkit/schematics';
import { updateJestConfigContent } from '@nrwl/react/src/utils/jest-utils';
import { NormalizedSchema } from '../schema';
export function updateJestConfig(options: NormalizedSchema): Rule {
return options.unitTestRunner === 'none'
? noop()
: (host) => {
const configPath = `${options.appProjectRoot}/jest.config.js`;
const originalContent = host.read(configPath).toString();
const content = updateJestConfigContent(originalContent);
host.overwrite(configPath, content);
};
}

View File

@ -0,0 +1,10 @@
import { Rule } from '@angular-devkit/schematics';
import { NxJson, updateJsonInTree } from '@nrwl/workspace';
import { NormalizedSchema } from '../schema';
export function updateNxJson(options: NormalizedSchema): Rule {
return updateJsonInTree<NxJson>('nx.json', (json) => {
json.projects[options.projectName] = { tags: options.parsedTags };
return json;
});
}

View File

@ -1,5 +1,6 @@
import { Linter } from '@nrwl/workspace';
import { SupportedStyles } from 'packages/react/typings/style';
import { Path } from '@angular-devkit/core';
export interface Schema {
name: string;
@ -17,3 +18,13 @@ export interface Schema {
skipWorkspaceJson?: boolean;
js?: boolean;
}
export interface NormalizedSchema extends Schema {
projectName: string;
appProjectRoot: Path;
e2eProjectName: string;
parsedTags: string[];
fileName: string;
styledModule: null | string;
hasStyles: boolean;
}

View File

@ -21,13 +21,13 @@ import {
getProjectConfig,
insert,
} from '@nrwl/workspace/src/utils/ast-utils';
import { CSS_IN_JS_DEPENDENCIES } from '../../utils/styled';
import {
typesReactRouterDomVersion,
reactRouterDomVersion,
typesReactRouterDomVersion,
} from '../../utils/versions';
import { assertValidStyle } from '../../utils/assertion';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
interface NormalizedSchema extends Schema {
projectSourceRoot: Path;
@ -42,7 +42,7 @@ export default function (schema: Schema): Rule {
const options = await normalizeOptions(host, schema, context);
return chain([
createComponentFiles(options),
addStyledModuleDependencies(options),
addStyledModuleDependencies(options.styledModule),
addExportsToBarrel(options),
options.routing
? addDepsToPackageJson(
@ -73,17 +73,6 @@ function createComponentFiles(options: NormalizedSchema): Rule {
);
}
function addStyledModuleDependencies(options: NormalizedSchema): Rule {
const extraDependencies = CSS_IN_JS_DEPENDENCIES[options.styledModule];
return extraDependencies
? addDepsToPackageJson(
extraDependencies.dependencies,
extraDependencies.devDependencies
)
: noop();
}
function addExportsToBarrel(options: NormalizedSchema): Rule {
return async (host: Tree) => {
const workspace = await getWorkspace(host);
@ -95,6 +84,7 @@ function addExportsToBarrel(options: NormalizedSchema): Rule {
options.projectSourceRoot,
options.js ? 'index.js' : 'index.ts'
);
console.log('index', indexFilePath);
const buffer = host.read(indexFilePath);
if (!!buffer) {
const indexSource = buffer!.toString('utf-8');

View File

@ -0,0 +1,9 @@
{
"presets": [
"@nrwl/react/babel",
<% if (style === '@emotion/styled') { %>"@emotion/babel-preset-css-prop"<% } %>
],
"plugins": [
<% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %>
]
}

View File

@ -1,7 +1,6 @@
import { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
import { NxJson } from '@nrwl/workspace';
import { NxJson, readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
import { runSchematic } from '../../utils/testing';
describe('lib', () => {
@ -398,7 +397,7 @@ describe('lib', () => {
expect(workspaceJson.projects['my-lib'].architect.build).toMatchObject({
options: {
external: ['react', 'react-dom', 'styled-components'],
external: ['react', 'react-dom', 'react-is', 'styled-components'],
},
});
});
@ -443,7 +442,7 @@ describe('lib', () => {
});
});
it('should add package.json', async () => {
it('should add package.json and .babelrc', async () => {
const tree = await runSchematic(
'lib',
{
@ -454,8 +453,8 @@ describe('lib', () => {
);
const packageJson = readJsonInTree(tree, '/libs/my-lib/package.json');
expect(packageJson.name).toEqual('@proj/my-lib');
expect(tree.exists('/libs/my-lib/.babelrc'));
});
});

View File

@ -47,6 +47,7 @@ import {
} from '../../utils/versions';
import { Schema } from './schema';
import { libsDir } from '@nrwl/workspace/src/utils/ast-utils';
import { initRootBabelConfig } from '@nrwl/web/src/utils/rules';
export interface NormalizedSchema extends Schema {
name: string;
@ -99,6 +100,7 @@ export default function (schema: Schema): Rule {
: noop(),
options.publishable ? updateLibPackageNpmScope(options) : noop(),
updateAppRoutes(options, context),
initRootBabelConfig(),
formatFiles(options),
])(host, context);
};

View File

@ -1,7 +1,11 @@
import {
babelPluginStyledComponentsVersion,
emotionBabelPresetCssPropVersion,
emotionCoreVersion,
emotionStyledVersion,
reactIsVersion,
styledComponentsVersion,
typesReactIsVersion,
typesStyledComponentsVersion,
} from './versions';
import { PackageDependencies } from './dependencies';
@ -11,10 +15,13 @@ export const CSS_IN_JS_DEPENDENCIES: {
} = {
'styled-components': {
dependencies: {
'react-is': reactIsVersion,
'styled-components': styledComponentsVersion,
},
devDependencies: {
'@types/styled-components': typesStyledComponentsVersion,
'@types/react-is': typesReactIsVersion,
'babel-plugin-styled-components': babelPluginStyledComponentsVersion,
},
},
'@emotion/styled': {
@ -22,6 +29,8 @@ export const CSS_IN_JS_DEPENDENCIES: {
'@emotion/styled': emotionStyledVersion,
'@emotion/core': emotionCoreVersion,
},
devDependencies: {},
devDependencies: {
'@emotion/babel-preset-css-prop': emotionBabelPresetCssPropVersion,
},
},
};

View File

@ -3,6 +3,7 @@ import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { Rule, Tree } from '@angular-devkit/schematics';
import { names } from '@nrwl/workspace/src/utils/name-utils';
import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace';
import { readJsonInTree } from '@nrwl/workspace';
const testRunner = new SchematicTestRunner(
'@nrwl/react',
@ -32,9 +33,48 @@ export function callRule(rule: Rule, tree: Tree) {
return testRunner.callRule(rule, tree).toPromise();
}
export function updateNxJson(tree, update: (json: any) => any) {
const updated = update(readJsonInTree(tree, '/nx.json'));
tree.overwrite('/nx.json', JSON.stringify(updated));
}
export function createApp(tree: Tree, appName: string): Promise<Tree> {
const { fileName } = names(appName);
tree.create(
`/apps/${fileName}/src/main.tsx`,
`import ReactDOM from 'react-dom';\n`
);
updateNxJson(tree, (json) => {
json.projects[appName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
name: fileName,
root: `apps/${fileName}`,
projectType: 'application',
sourceRoot: `apps/${fileName}/src`,
targets: {},
});
}),
tree
);
}
export function createWebApp(tree: Tree, appName: string): Promise<Tree> {
const { fileName } = names(appName);
tree.create(`/apps/${fileName}/src/index.ts`, `\n`);
updateNxJson(tree, (json) => {
json.projects[appName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
@ -52,7 +92,12 @@ export function createApp(tree: Tree, appName: string): Promise<Tree> {
export function createLib(tree: Tree, libName: string): Promise<Tree> {
const { fileName } = names(libName);
tree.create(`/libs/${fileName}/src/index.ts`, `\n`);
tree.create(`/libs/${fileName}/src/index.ts`, `import React from 'react';\n`);
updateNxJson(tree, (json) => {
json.projects[libName] = { tags: [] };
return json;
});
return callRule(
updateWorkspace((workspace) => {

View File

@ -1,26 +1,32 @@
export const nxVersion = '*';
export const reactVersion = '16.12.0';
export const reactDomVersion = '16.12.0';
export const typesReactVersion = '16.9.17';
export const typesReactDomVersion = '16.9.4';
export const reactVersion = '16.13.1';
export const reactDomVersion = '16.13.1';
export const typesReactVersion = '16.9.35';
export const typesReactDomVersion = '16.9.8';
export const styledComponentsVersion = '5.0.1';
export const typesStyledComponentsVersion = '5.0.1';
export const styledComponentsVersion = '5.1.0';
export const typesStyledComponentsVersion = '5.1.0';
export const reactIsVersion = '16.13.1';
export const typesReactIsVersion = '16.7.1';
export const emotionStyledVersion = '10.0.27';
export const emotionCoreVersion = '10.0.27';
export const emotionBabelPresetCssPropVersion = '10.0.27';
export const reactRouterDomVersion = '5.1.2';
export const typesReactRouterDomVersion = '5.1.3';
export const reactRouterDomVersion = '5.2.0';
export const typesReactRouterDomVersion = '5.1.5';
export const testingLibraryReactVersion = '9.4.0';
export const testingLibraryReactVersion = '10.0.4';
export const reduxjsToolkitVersion = '1.3.2';
export const reactReduxVersion = '7.1.3';
export const typesReactReduxVersion = '7.1.5';
export const reduxjsToolkitVersion = '1.3.6';
export const reactReduxVersion = '7.2.0';
export const typesReactReduxVersion = '7.1.9';
export const eslintPluginImportVersion = '2.19.1';
export const eslintPluginImportVersion = '2.20.2';
export const eslintPluginJsxA11yVersion = '6.2.3';
export const eslintPluginReactVersion = '7.17.0';
export const eslintPluginReactHooksVersion = '2.3.0';
export const eslintPluginReactVersion = '7.20.0';
export const eslintPluginReactHooksVersion = '4.0.2';
export const babelPluginStyledComponentsVersion = '1.10.7';

View File

@ -32,7 +32,7 @@
},
"dependencies": {
"@nrwl/cypress": "*",
"core-js": "^3.2.1",
"core-js": "^3.6.5",
"tree-kill": "1.2.2",
"ts-loader": "5.3.1",
"tsconfig-paths-webpack-plugin": "3.2.0",

View File

@ -1,5 +1,5 @@
export const nxVersion = '*';
export const storybookVersion = '5.3.9';
export const babelCoreVersion = '7.8.3';
export const babelLoaderVersion = '8.0.6';
export const babelPresetTypescriptVersion = '7.8.3';
export const babelCoreVersion = '7.9.6';
export const babelLoaderVersion = '8.1.0';
export const babelPresetTypescriptVersion = '7.9.0';

53
packages/web/babel.ts Normal file
View File

@ -0,0 +1,53 @@
/*
* Babel preset to provide TypeScript support and module/nomodule for Nx.
*/
module.exports = function (api: any, options: {}) {
api.assertVersion(7);
return {
presets: [
// Support module/nomodule pattern.
[
require.resolve('@babel/preset-env'),
{
// Allow importing core-js in entrypoint and use browserlist to select polyfills.
// This is needed for differential loading as well.
useBuiltIns: 'entry',
corejs: 3,
// Do not transform modules to CJS
modules: false,
targets: api.env('legacy') ? undefined : { esmodules: true },
bugfixes: true,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol'],
},
],
require.resolve('@babel/preset-typescript'),
],
plugins: [
require.resolve('babel-plugin-macros'),
// Must use legacy decorators to remain compatible with TypeScript.
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
[
require.resolve('@babel/plugin-proposal-class-properties'),
{ loose: true },
],
],
overrides: [
// Convert `const enum` to `enum`. The former cannot be supported by babel
// but at least we can get it to not error out.
{
test: /\.tsx?$/,
plugins: [
[
require.resolve('babel-plugin-const-enum'),
{
transform: 'removeConst',
},
],
],
},
],
};
};

View File

@ -39,28 +39,29 @@
"@angular-devkit/build-webpack": "~0.901.0",
"@angular-devkit/core": "~9.1.0",
"@angular-devkit/schematics": "~9.1.0",
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"@babel/preset-typescript": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-transform-regenerator": "^7.8.3",
"@babel/core": "7.9.6",
"@babel/preset-env": "7.9.6",
"@babel/plugin-proposal-class-properties": "7.8.3",
"@babel/plugin-proposal-decorators": "7.8.3",
"@babel/plugin-transform-regenerator": "7.8.7",
"@babel/preset-typescript": "7.9.0",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-babel": "5.0.2",
"@rollup/plugin-image": "2.0.4",
"@rollup/plugin-node-resolve": "7.1.1",
"ajv": "6.10.2",
"autoprefixer": "9.7.4",
"babel-loader": "8.0.6",
"babel-plugin-const-enum": "0.0.5",
"babel-plugin-macros": "2.6.1",
"babel-plugin-transform-async-to-promises": "0.8.15",
"babel-loader": "8.1.0",
"babel-plugin-const-enum": "^1.0.1",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-async-to-promises": "^0.8.15",
"browserslist": "4.8.7",
"cacache": "12.0.2",
"caniuse-lite": "^1.0.30001030",
"circular-dependency-plugin": "5.2.0",
"clean-css": "4.2.1",
"copy-webpack-plugin": "5.1.1",
"core-js": "^3.2.1",
"core-js": "^3.6.5",
"css-loader": "3.4.2",
"file-loader": "4.2.0",
"find-cache-dir": "3.0.0",
@ -84,13 +85,12 @@
"rxjs": "^6.5.4",
"regenerator-runtime": "0.13.3",
"rollup": "1.31.1",
"rollup-plugin-babel": "4.3.3",
"rollup-plugin-copy": "3.3.0",
"rollup-plugin-filesize": "6.2.1",
"rollup-plugin-local-resolve": "1.0.7",
"rollup-plugin-peer-deps-external": "2.2.2",
"rollup-plugin-postcss": "2.1.1",
"rollup-plugin-typescript2": "0.26.0",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-filesize": "^9.0.0",
"rollup-plugin-local-resolve": "^1.0.7",
"rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^3.1.1",
"rollup-plugin-typescript2": "^0.27.1",
"sass": "1.22.9",
"sass-loader": "8.0.2",
"semver": "6.3.0",

View File

@ -7,28 +7,22 @@ import {
import { JsonObject } from '@angular-devkit/core';
import { from, Observable, of } from 'rxjs';
import { catchError, last, switchMap, tap } from 'rxjs/operators';
import { runRollup } from './run-rollup';
import { createBabelConfig as _createBabelConfig } from '../../utils/babel-config';
import { getBabelInputPlugin } from '@rollup/plugin-babel';
import * as autoprefixer from 'autoprefixer';
import * as rollup from 'rollup';
import * as babel from 'rollup-plugin-babel';
import * as peerDepsExternal from 'rollup-plugin-peer-deps-external';
import * as postcss from 'rollup-plugin-postcss';
import * as filesize from 'rollup-plugin-filesize';
import * as localResolve from 'rollup-plugin-local-resolve';
import { PackageBuilderOptions } from '../../utils/types';
import {
normalizePackageOptions,
NormalizedBundleBuilderOptions,
NormalizedCopyAssetOption,
} from '../../utils/normalize';
import { toClassName } from '@nrwl/workspace/src/utils/name-utils';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { BuildResult } from '@angular-devkit/build-webpack';
import {
readJsonFile,
writeJsonFile,
} from '@nrwl/workspace/src/utils/fileutils';
import { createProjectGraph } from '@nrwl/workspace/src/core/project-graph';
import {
calculateProjectDependencies,
checkDependentProjectsHaveBeenBuilt,
@ -36,8 +30,15 @@ import {
DependentBuildableProjectNode,
updateBuildableProjectPackageJsonDependencies,
} from '@nrwl/workspace/src/utils/buildable-libs-utils';
import { PackageBuilderOptions } from '../../utils/types';
import { runRollup } from './run-rollup';
import {
NormalizedBundleBuilderOptions,
NormalizedCopyAssetOption,
normalizePackageOptions,
} from '../../utils/normalize';
import { getSourceRoot } from '../../utils/source-root';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { createBabelConfig } from '../../utils/babel-config';
// These use require because the ES import isn't correct.
const resolve = require('@rollup/plugin-node-resolve');
@ -88,7 +89,8 @@ export function run(
options,
dependencies,
context,
packageJson
packageJson,
sourceRoot
);
if (options.watch) {
@ -155,7 +157,8 @@ function createRollupOptions(
options: NormalizedBundleBuilderOptions,
dependencies: DependentBuildableProjectNode[],
context: BuilderContext,
packageJson: any
packageJson: any,
sourceRoot: string
): rollup.InputOptions {
const compilerOptionPaths = computeCompilerOptionsPaths(
options.tsConfig,
@ -196,11 +199,20 @@ function createRollupOptions(
preferBuiltins: true,
extensions: fileExtensions,
}),
babel({
...createBabelConfig(options, options.projectRoot),
getBabelInputPlugin({
// TODO(jack): Remove this in Nx 10
...legacyCreateBabelConfig(options, options.projectRoot),
cwd: join(context.workspaceRoot, sourceRoot),
rootMode: 'upward',
babelrc: true,
extensions: fileExtensions,
externalHelpers: false,
exclude: 'node_modules/**',
babelHelpers: 'bundled',
exclude: /node_modules/,
plugins: [
'babel-plugin-transform-async-to-promises',
['@babel/plugin-transform-regenerator', { async: false }],
],
}),
commonjs(),
filesize(),
@ -237,45 +249,46 @@ function createRollupOptions(
: rollupConfig;
}
function createBabelConfig(
function legacyCreateBabelConfig(
options: PackageBuilderOptions,
projectRoot: string
) {
let babelConfig: any = _createBabelConfig(projectRoot, false, false);
if (options.babelConfig) {
let babelConfig: any = createBabelConfig(projectRoot, false, false);
babelConfig = require(options.babelConfig)(babelConfig, options);
// Ensure async functions are transformed to promises properly.
upsert(
'plugins',
'babel-plugin-transform-async-to-promises',
null,
babelConfig
);
upsert(
'plugins',
'@babel/plugin-transform-regenerator',
{ async: false },
babelConfig
);
} else {
return {};
}
// Ensure async functions are transformed to promises properly.
upsert(
'plugins',
'babel-plugin-transform-async-to-promises',
null,
babelConfig
);
upsert(
'plugins',
'@babel/plugin-transform-regenerator',
{ async: false },
babelConfig
);
return babelConfig;
}
function upsert(
type: 'presets' | 'plugins',
pluginOrPreset: string,
opts: null | JsonObject,
config: any
) {
if (
!config[type].some(
(p) =>
(Array.isArray(p) && p[0].indexOf(pluginOrPreset) !== -1) ||
p.indexOf(pluginOrPreset) !== -1
)
function upsert(
type: 'presets' | 'plugins',
pluginOrPreset: string,
opts: null | JsonObject,
config: any
) {
const fullPath = require.resolve(pluginOrPreset);
config[type] = config[type].concat([opts ? [fullPath, opts] : fullPath]);
if (
!config[type].some(
(p) =>
(Array.isArray(p) && p[0].indexOf(pluginOrPreset) !== -1) ||
p.indexOf(pluginOrPreset) !== -1
)
) {
const fullPath = require.resolve(pluginOrPreset);
config[type] = config[type].concat([opts ? [fullPath, opts] : fullPath]);
}
}
}

View File

@ -37,7 +37,7 @@
},
"babelConfig": {
"type": "string",
"description": "Path to a function which takes a babel config and returns an updated babel config"
"description": "(deprecated) Path to a function which takes a babel config and returns an updated babel config"
},
"umdName": {
"type": "string",

View File

@ -48,6 +48,46 @@ describe('init', () => {
},
tree
);
expect(result.exists('jest.config.js')).toEqual(false);
expect(result.exists('jest.config.js')).toBe(false);
});
describe('babel config', () => {
it('should create babel config if not present', async () => {
const result = await runSchematic(
'init',
{
unitTestRunner: 'none',
},
tree
);
expect(result.exists('babel.config.json')).toBe(true);
});
it('should not overwrite existing babel config', async () => {
tree.create('babel.config.json', '{ "preset": ["preset-awesome"] }');
const result = await runSchematic(
'init',
{
unitTestRunner: 'none',
},
tree
);
const existing = result.read('babel.config.json').toString();
expect(existing).toMatch('{ "preset": ["preset-awesome"] }');
});
it('should not overwrite existing babel config (.js)', async () => {
tree.create('/babel.config.js', 'module.exports = () => {};');
const result = await runSchematic(
'init',
{
unitTestRunner: 'none',
},
tree
);
expect(result.exists('babel.config.json')).toBe(false);
});
});
});

View File

@ -1,4 +1,4 @@
import { chain, noop, Rule } from '@angular-devkit/schematics';
import { chain, noop, Rule, Tree } from '@angular-devkit/schematics';
import {
addPackageWithInit,
formatFiles,
@ -10,6 +10,7 @@ import {
documentRegisterElementVersion,
nxVersion,
} from '../../utils/versions';
import { initRootBabelConfig } from '../../utils/rules';
function updateDependencies(): Rule {
return updateJsonInTree('package.json', (json) => {
@ -36,6 +37,7 @@ export default function (schema: Schema) {
? addPackageWithInit('@nrwl/cypress')
: noop(),
updateDependencies(),
initRootBabelConfig(),
formatFiles(schema),
]);
}

View File

@ -1,3 +1,5 @@
/** @deprecated We no longer use this function and will be removed in Nx 10. */
// TODO(jack): Remove this in Nx 10
export function createBabelConfig(
context: string,
esm: boolean,

View File

@ -5,6 +5,7 @@ import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import { ProgressPlugin } from 'webpack';
import { BuildBuilderOptions } from './types';
import { normalize } from '@angular-devkit/core';
import CircularDependencyPlugin = require('circular-dependency-plugin');
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
@ -19,6 +20,7 @@ describe('getBaseWebpackPartial', () => {
tsConfig: 'tsconfig.json',
fileReplacements: [],
root: '/root',
sourceRoot: normalize('/root/src'),
statsJson: false,
};
(<any>(
@ -164,12 +166,8 @@ describe('getBaseWebpackPartial', () => {
expect(
(result.module.rules.find((rule) => rule.loader === 'babel-loader')
.options as any).presets.find(
(p) => p[0].indexOf('@babel/preset-env') !== -1
)[1]
).toMatchObject({
targets: { esmodules: true },
});
.options as any).envName
).toMatch('modern');
});
it('should not override preset-env target for es5', () => {
@ -177,12 +175,8 @@ describe('getBaseWebpackPartial', () => {
expect(
(result.module.rules.find((rule) => rule.loader === 'babel-loader')
.options as any).presets.find(
(p) => p[0].indexOf('@babel/preset-env') !== -1
)[1]
).toMatchObject({
targets: undefined,
});
.options as any).envName
).toMatch('legacy');
});
});

View File

@ -1,6 +1,6 @@
import * as webpack from 'webpack';
import { Configuration, ProgressPlugin, Stats } from 'webpack';
import { dirname, resolve } from 'path';
import { join, resolve } from 'path';
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import * as TerserWebpackPlugin from 'terser-webpack-plugin';
@ -8,7 +8,6 @@ import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import { AssetGlobPattern, BuildBuilderOptions } from './types';
import { getOutputHashFormat } from './hash-format';
import { createBabelConfig } from './babel-config';
import CircularDependencyPlugin = require('circular-dependency-plugin');
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
@ -52,7 +51,10 @@ export function getBaseWebpackPartial(
loader: `babel-loader`,
exclude: /node_modules/,
options: {
...createBabelConfig(dirname(options.main), esm, options.verbose),
rootMode: 'upward',
cwd: join(options.root, options.sourceRoot),
envName: esm ? 'modern' : 'legacy',
babelrc: true,
cacheDirectory: true,
cacheCompression: false,
},
@ -153,7 +155,8 @@ export function createTerserPlugin(esm: boolean, sourceMap: boolean) {
cache: true,
sourceMap,
terserOptions: {
ecma: esm ? 6 : 5,
ecma: esm ? 8 : 5,
// Don't remove safari 10 workaround for ES modules
safari10: true,
output: {
ascii_only: true,

View File

@ -1,12 +1,13 @@
import { getDevServerConfig } from './devserver.config';
import { Logger } from '@angular-devkit/core/src/logger';
jest.mock('tsconfig-paths-webpack-plugin');
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import * as ts from 'typescript';
import * as fs from 'fs';
import { WebBuildBuilderOptions } from '../builders/build/build.impl';
import { WebDevServerOptions } from '../builders/dev-server/dev-server.impl';
import { join } from 'path';
jest.mock('tsconfig-paths-webpack-plugin');
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
describe('getDevServerConfig', () => {
@ -18,6 +19,8 @@ describe('getDevServerConfig', () => {
let sourceRoot: string;
beforeEach(() => {
root = join(__dirname, '../../../..');
sourceRoot = join(root, 'apps/app');
buildInput = {
main: 'main.ts',
index: 'index.html',
@ -39,9 +42,9 @@ describe('getDevServerConfig', () => {
outputPath: 'dist',
tsConfig: 'tsconfig.json',
fileReplacements: [],
root,
sourceRoot,
};
root = join(__dirname, '../../../..');
sourceRoot = join(root, 'apps/app');
serveInput = {
host: 'localhost',

View File

@ -47,6 +47,8 @@ export function normalizeBuildOptions<T extends BuildBuilderOptions>(
): T {
return {
...options,
root,
sourceRoot,
main: resolve(root, options.main),
outputPath: resolve(root, options.outputPath),
tsConfig: resolve(root, options.tsConfig),

View File

@ -0,0 +1,19 @@
import { Rule, Tree } from '@angular-devkit/schematics';
export function initRootBabelConfig(): Rule {
return (host: Tree) => {
if (host.exists('/babel.config.json') || host.exists('/babel.config.js'))
return;
host.create(
'/babel.config.json',
JSON.stringify(
{
presets: ['@nrwl/web/babel'],
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
},
null,
2
)
);
};
}

View File

@ -1,5 +1,5 @@
import { FileReplacement } from './normalize';
import { JsonObject, Path } from '@angular-devkit/core';
import { JsonObject } from '@angular-devkit/core';
export interface OptimizationOptions {
scripts: boolean;
@ -37,7 +37,7 @@ export interface BuildBuilderOptions {
webpackConfig?: string;
root?: string;
sourceRoot?: Path;
sourceRoot?: string;
}
export interface Globals {

View File

@ -1,5 +1,4 @@
import { getWebConfig as getWebPartial } from './web.config';
jest.mock('tsconfig-paths-webpack-plugin');
import TsConfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import { createConsoleLogger } from '@angular-devkit/core/node';
import { Logger } from '@angular-devkit/core/src/logger';
@ -7,6 +6,8 @@ import * as ts from 'typescript';
import { WebBuildBuilderOptions } from '../builders/build/build.impl';
import { join } from 'path';
jest.mock('tsconfig-paths-webpack-plugin');
describe('getWebConfig', () => {
let input: WebBuildBuilderOptions;
let logger: Logger;
@ -15,6 +16,8 @@ describe('getWebConfig', () => {
let mockCompilerOptions: any;
beforeEach(() => {
root = join(__dirname, '../../../..');
sourceRoot = join(root, 'apps/app');
input = {
main: 'main.ts',
index: 'index.html',
@ -36,9 +39,9 @@ describe('getWebConfig', () => {
outputPath: 'dist',
tsConfig: 'tsconfig.json',
fileReplacements: [],
root,
sourceRoot,
};
root = join(__dirname, '../../../..');
sourceRoot = join(root, 'apps/app');
logger = createConsoleLogger();
mockCompilerOptions = {

View File

@ -6,16 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {
Rule,
Tree,
SchematicContext,
DirEntry,
noop,
chain,
Source,
mergeWith,
apply,
chain,
DirEntry,
forEach,
mergeWith,
noop,
Rule,
SchematicContext,
Source,
Tree,
} from '@angular-devkit/schematics';
import * as ts from 'typescript';
import * as stripJsonComments from 'strip-json-comments';
@ -395,6 +395,10 @@ export function readJsonInTree<T = any>(host: Tree, path: string): T {
* Method for utilizing the project graph in schematics
*/
export function getProjectGraphFromHost(host: Tree): ProjectGraph {
return onlyWorkspaceProjects(getFullProjectGraphFromHost(host));
}
export function getFullProjectGraphFromHost(host: Tree): ProjectGraph {
const workspaceJson = readJsonInTree(host, getWorkspacePath(host));
const nxJson = readJsonInTree<NxJson>(host, '/nx.json');
@ -425,8 +429,12 @@ export function getProjectGraphFromHost(host: Tree): ProjectGraph {
);
});
return onlyWorkspaceProjects(
createProjectGraph(workspaceJson, nxJson, workspaceFiles, fileRead, false)
return createProjectGraph(
workspaceJson,
nxJson,
workspaceFiles,
fileRead,
false
);
}

View File

@ -59,8 +59,6 @@ function checkFiles(files: string[]) {
r.package
)} has new version ${chalk.bold(r.latest)} (current: ${r.prev})`
);
} else {
console.log(`${logContext} ✔️ ${r.package} is update to date`);
}
});
});

View File

@ -1,5 +1,5 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'core-js/proposals/reflect-metadata';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';

712
yarn.lock

File diff suppressed because it is too large Load Diff