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` 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 ### entryFile

View File

@ -17,7 +17,7 @@ List of static assets.
Type: `string` 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 ### entryFile

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { import {
checkFilesExist, checkFilesExist,
ensureProject, ensureProject,
@ -6,7 +5,6 @@ import {
readFile, readFile,
runCLI, runCLI,
runCLIAsync, runCLIAsync,
supportUi,
uniq, uniq,
updateFile, updateFile,
} from './utils'; } from './utils';
@ -54,72 +52,9 @@ forEachCli((currentCLIName) => {
const lintE2eResults = runCLI(`lint ${appName}-e2e`); const lintE2eResults = runCLI(`lint ${appName}-e2e`);
expect(lintE2eResults).toContain('All files pass linting.'); expect(lintE2eResults).toContain('All files pass linting.');
if (supportUi()) { const e2eResults = runCLI(`e2e ${appName}-e2e`);
const e2eResults = runCLI(`e2e ${appName}-e2e`); expect(e2eResults).toContain('All specs passed!');
expect(e2eResults).toContain('All specs passed!');
}
}, 120000); }, 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', () => { describe('CLI - Environment Variables', () => {
@ -140,7 +75,7 @@ forEachCli((currentCLIName) => {
env: { ...process.env, NODE_ENV: 'test', NX_BUILD: '52', NX_API: 'QA' }, env: { ...process.env, NODE_ENV: 'test', NX_BUILD: '52', NX_API: 'QA' },
}); });
expect(readFile(`dist/apps/${appName}/main.js`)).toContain( 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/router": "^9.1.0",
"@angular/service-worker": "^9.1.0", "@angular/service-worker": "^9.1.0",
"@angular/upgrade": "^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-class-properties": "7.8.3",
"@babel/plugin-proposal-decorators": "7.8.3", "@babel/plugin-proposal-decorators": "7.8.3",
"@babel/plugin-transform-regenerator": "7.8.3", "@babel/plugin-transform-regenerator": "7.8.7",
"@babel/preset-env": "7.8.4", "@babel/preset-typescript": "7.9.0",
"@babel/preset-react": "7.8.3", "@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.8.3",
"@bazel/bazel": "^1.2.0", "@bazel/bazel": "^1.2.0",
"@bazel/ibazel": "^0.10.3", "@bazel/ibazel": "^0.10.3",
"@cypress/webpack-preprocessor": "^4.1.2", "@cypress/webpack-preprocessor": "^4.1.2",
@ -73,6 +73,7 @@
"@ngrx/store-devtools": "9.1.0", "@ngrx/store-devtools": "9.1.0",
"@ngtools/webpack": "~9.1.0", "@ngtools/webpack": "~9.1.0",
"@reduxjs/toolkit": "1.3.2", "@reduxjs/toolkit": "1.3.2",
"@rollup/plugin-babel": "5.0.2",
"@rollup/plugin-commonjs": "11.0.2", "@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-image": "2.0.4", "@rollup/plugin-image": "2.0.4",
"@rollup/plugin-node-resolve": "7.1.1", "@rollup/plugin-node-resolve": "7.1.1",
@ -110,12 +111,12 @@
"app-root-path": "^2.0.1", "app-root-path": "^2.0.1",
"autoprefixer": "9.7.4", "autoprefixer": "9.7.4",
"axios": "^0.19.0", "axios": "^0.19.0",
"babel-loader": "8.0.6", "babel-loader": "8.1.0",
"babel-plugin-const-enum": "^0.0.5", "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-emotion": "^10.0.29",
"babel-plugin-macros": "2.6.1",
"babel-plugin-styled-components": "^1.10.7", "babel-plugin-styled-components": "^1.10.7",
"babel-plugin-transform-async-to-promises": "0.8.15",
"browserslist": "4.8.7", "browserslist": "4.8.7",
"cacache": "12.0.2", "cacache": "12.0.2",
"caniuse-lite": "^1.0.30001030", "caniuse-lite": "^1.0.30001030",
@ -126,7 +127,7 @@
"confusing-browser-globals": "^1.0.9", "confusing-browser-globals": "^1.0.9",
"conventional-changelog-cli": "^2.0.23", "conventional-changelog-cli": "^2.0.23",
"copy-webpack-plugin": "5.1.1", "copy-webpack-plugin": "5.1.1",
"core-js": "^2.6.9", "core-js": "^3.6.5",
"cosmiconfig": "^4.0.0", "cosmiconfig": "^4.0.0",
"css-loader": "3.4.2", "css-loader": "3.4.2",
"cypress": "^4.1.0", "cypress": "^4.1.0",
@ -197,7 +198,6 @@
"regenerator-runtime": "0.13.3", "regenerator-runtime": "0.13.3",
"release-it": "^7.4.0", "release-it": "^7.4.0",
"rollup": "1.31.1", "rollup": "1.31.1",
"rollup-plugin-babel": "4.3.3",
"rollup-plugin-copy": "3.3.0", "rollup-plugin-copy": "3.3.0",
"rollup-plugin-filesize": "6.2.1", "rollup-plugin-filesize": "6.2.1",
"rollup-plugin-local-resolve": "1.0.7", "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']).toBeDefined();
expect(dependencies['@angular/platform-browser-dynamic']).toBeDefined(); expect(dependencies['@angular/platform-browser-dynamic']).toBeDefined();
expect(dependencies['@angular/router']).toBeDefined(); expect(dependencies['@angular/router']).toBeDefined();
expect(dependencies['core-js']).toBeDefined();
expect(dependencies['rxjs']).toBeDefined(); expect(dependencies['rxjs']).toBeDefined();
expect(dependencies['zone.js']).toBeDefined(); expect(dependencies['zone.js']).toBeDefined();
expect(devDependencies['@angular/compiler-cli']).toBeDefined(); expect(devDependencies['@angular/compiler-cli']).toBeDefined();

View File

@ -34,7 +34,6 @@ const updateDependencies = addDepsToPackageJson(
'@angular/platform-browser': angularVersion, '@angular/platform-browser': angularVersion,
'@angular/platform-browser-dynamic': angularVersion, '@angular/platform-browser-dynamic': angularVersion,
'@angular/router': angularVersion, '@angular/router': angularVersion,
'core-js': '^2.5.4',
rxjs: rxjsVersion, rxjs: rxjsVersion,
'zone.js': '^0.10.2', '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 // This file is required by karma.conf.js and loads recursively all the .spec and framework files
<% if (isLibrary) { %> <% if (isLibrary) { %>
import 'core-js/es7/reflect';
import 'zone.js/dist/zone'; import 'zone.js/dist/zone';
<% } %> <% } %>
import 'zone.js/dist/zone-testing'; import 'zone.js/dist/zone-testing';

View File

@ -135,7 +135,6 @@ module.exports = function(config) {
appTree appTree
); );
const testTs = resultTree.read('libs/lib1/src/test.ts').toString(); 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';"); expect(testTs).toContain("import 'zone.js/dist/zone';");
}); });
}); });
@ -199,7 +198,6 @@ module.exports = function(config) {
appTree appTree
); );
const testTs = resultTree.read('apps/app1/src/test.ts').toString(); 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';"); 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', () => { describe('--setup-file', () => {
it('should generate src/test-setup.ts', async () => { it('should generate src/test-setup.ts', async () => {
const resultTree = await runSchematic( const resultTree = await runSchematic(

View File

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

View File

@ -1,4 +1,4 @@
{ {
"presets": ["next/babel"], "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", "version": "8.12.0-beta.1",
"description": "Ensure React correct webpack config and babel preset", "description": "Ensure React correct webpack config and babel preset",
"factory": "./src/migrations/update-8-12-0/fix-react-files-8-12-0" "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": { "packageJsonUpdates": {
@ -181,6 +191,79 @@
"alwaysAddToPackageJson": false "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": "*" "@nrwl/workspace": "*"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.8.4", "@babel/core": "7.9.6",
"@babel/preset-react": "^7.8.3", "@babel/preset-react": "7.9.4",
"@nrwl/cypress": "*", "@nrwl/cypress": "*",
"@nrwl/jest": "*", "@nrwl/jest": "*",
"@nrwl/web": "*", "@nrwl/web": "*",
"@angular-devkit/schematics": "~9.1.0", "@angular-devkit/schematics": "~9.1.0",
"@svgr/webpack": "^5.2.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", "confusing-browser-globals": "^1.0.9",
"eslint-plugin-import": "^2.20.1", "eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-jsx-a11y": "^6.2.3",

View File

@ -1,12 +1,7 @@
import { Configuration } from 'webpack'; import { Configuration } from 'webpack';
import { updateBabelOptions } from '../src/utils/babel-utils';
// Add React-specific configuration // Add React-specific configuration
function getWebpackConfig(config: 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( config.module.rules.push(
{ {
test: /\.(png|jpe?g|gif|webp)$/, 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 { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace } from '@nrwl/workspace/testing'; import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import * as stripJsonComments from 'strip-json-comments'; import * as stripJsonComments from 'strip-json-comments';
import { readJsonInTree, NxJson } from '@nrwl/workspace'; import { NxJson, readJsonInTree } from '@nrwl/workspace';
import { runSchematic } from '../../utils/testing'; import { runSchematic } from '../../utils/testing';
describe('app', () => { describe('app', () => {
@ -44,6 +44,7 @@ describe('app', () => {
it('should generate files', async () => { it('should generate files', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree); 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/main.tsx')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.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(); 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 { import {
apply,
chain, chain,
externalSchematic,
filter,
mergeWith,
move,
noop,
Rule, Rule,
SchematicContext, SchematicContext,
template,
Tree, Tree,
url,
} from '@angular-devkit/schematics'; } from '@angular-devkit/schematics';
import { import { addLintFiles, formatFiles } from '@nrwl/workspace';
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 { extraEslintDependencies, reactEslintJson } from '../../utils/lint'; 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 init from '../init/init';
import { Schema } from './schema'; import { Schema } from './schema';
import { createApplicationFiles } from './lib/create-application-files';
interface NormalizedSchema extends Schema { import { updateJestConfig } from './lib/update-jest-config';
projectName: string; import { normalizeOptions } from './lib/normalize-options';
appProjectRoot: Path; import { addProject } from './lib/add-project';
e2eProjectName: string; import { addCypress } from './lib/add-cypress';
parsedTags: string[]; import { addJest } from './lib/add-jest';
fileName: string; import { addRouting } from './lib/add-routing';
styledModule: null | string; import { setDefaults } from './lib/set-defaults';
hasStyles: boolean; import { updateNxJson } from './lib/update-nx-json';
} import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
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);
return chain([ return chain([
init({ init({
...options, ...options,
@ -73,283 +37,10 @@ export default function (schema: Schema): Rule {
addCypress(options), addCypress(options),
addJest(options), addJest(options),
updateJestConfig(options), updateJestConfig(options),
addStyledModuleDependencies(options), addStyledModuleDependencies(options.styledModule),
addRouting(options, context), addRouting(options, context),
setDefaults(options), setDefaults(options),
formatFiles(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 { Linter } from '@nrwl/workspace';
import { SupportedStyles } from 'packages/react/typings/style'; import { SupportedStyles } from 'packages/react/typings/style';
import { Path } from '@angular-devkit/core';
export interface Schema { export interface Schema {
name: string; name: string;
@ -17,3 +18,13 @@ export interface Schema {
skipWorkspaceJson?: boolean; skipWorkspaceJson?: boolean;
js?: 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, getProjectConfig,
insert, insert,
} from '@nrwl/workspace/src/utils/ast-utils'; } from '@nrwl/workspace/src/utils/ast-utils';
import { CSS_IN_JS_DEPENDENCIES } from '../../utils/styled';
import { import {
typesReactRouterDomVersion,
reactRouterDomVersion, reactRouterDomVersion,
typesReactRouterDomVersion,
} from '../../utils/versions'; } from '../../utils/versions';
import { assertValidStyle } from '../../utils/assertion'; import { assertValidStyle } from '../../utils/assertion';
import { toJS } from '@nrwl/workspace/src/utils/rules/to-js'; import { toJS } from '@nrwl/workspace/src/utils/rules/to-js';
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
interface NormalizedSchema extends Schema { interface NormalizedSchema extends Schema {
projectSourceRoot: Path; projectSourceRoot: Path;
@ -42,7 +42,7 @@ export default function (schema: Schema): Rule {
const options = await normalizeOptions(host, schema, context); const options = await normalizeOptions(host, schema, context);
return chain([ return chain([
createComponentFiles(options), createComponentFiles(options),
addStyledModuleDependencies(options), addStyledModuleDependencies(options.styledModule),
addExportsToBarrel(options), addExportsToBarrel(options),
options.routing options.routing
? addDepsToPackageJson( ? 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 { function addExportsToBarrel(options: NormalizedSchema): Rule {
return async (host: Tree) => { return async (host: Tree) => {
const workspace = await getWorkspace(host); const workspace = await getWorkspace(host);
@ -95,6 +84,7 @@ function addExportsToBarrel(options: NormalizedSchema): Rule {
options.projectSourceRoot, options.projectSourceRoot,
options.js ? 'index.js' : 'index.ts' options.js ? 'index.js' : 'index.ts'
); );
console.log('index', indexFilePath);
const buffer = host.read(indexFilePath); const buffer = host.read(indexFilePath);
if (!!buffer) { if (!!buffer) {
const indexSource = buffer!.toString('utf-8'); 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 { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace } from '@nrwl/workspace/testing'; import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace'; import { NxJson, readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
import { NxJson } from '@nrwl/workspace';
import { runSchematic } from '../../utils/testing'; import { runSchematic } from '../../utils/testing';
describe('lib', () => { describe('lib', () => {
@ -398,7 +397,7 @@ describe('lib', () => {
expect(workspaceJson.projects['my-lib'].architect.build).toMatchObject({ expect(workspaceJson.projects['my-lib'].architect.build).toMatchObject({
options: { 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( const tree = await runSchematic(
'lib', 'lib',
{ {
@ -454,8 +453,8 @@ describe('lib', () => {
); );
const packageJson = readJsonInTree(tree, '/libs/my-lib/package.json'); const packageJson = readJsonInTree(tree, '/libs/my-lib/package.json');
expect(packageJson.name).toEqual('@proj/my-lib'); expect(packageJson.name).toEqual('@proj/my-lib');
expect(tree.exists('/libs/my-lib/.babelrc'));
}); });
}); });

View File

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

View File

@ -1,7 +1,11 @@
import { import {
babelPluginStyledComponentsVersion,
emotionBabelPresetCssPropVersion,
emotionCoreVersion, emotionCoreVersion,
emotionStyledVersion, emotionStyledVersion,
reactIsVersion,
styledComponentsVersion, styledComponentsVersion,
typesReactIsVersion,
typesStyledComponentsVersion, typesStyledComponentsVersion,
} from './versions'; } from './versions';
import { PackageDependencies } from './dependencies'; import { PackageDependencies } from './dependencies';
@ -11,10 +15,13 @@ export const CSS_IN_JS_DEPENDENCIES: {
} = { } = {
'styled-components': { 'styled-components': {
dependencies: { dependencies: {
'react-is': reactIsVersion,
'styled-components': styledComponentsVersion, 'styled-components': styledComponentsVersion,
}, },
devDependencies: { devDependencies: {
'@types/styled-components': typesStyledComponentsVersion, '@types/styled-components': typesStyledComponentsVersion,
'@types/react-is': typesReactIsVersion,
'babel-plugin-styled-components': babelPluginStyledComponentsVersion,
}, },
}, },
'@emotion/styled': { '@emotion/styled': {
@ -22,6 +29,8 @@ export const CSS_IN_JS_DEPENDENCIES: {
'@emotion/styled': emotionStyledVersion, '@emotion/styled': emotionStyledVersion,
'@emotion/core': emotionCoreVersion, '@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 { Rule, Tree } from '@angular-devkit/schematics';
import { names } from '@nrwl/workspace/src/utils/name-utils'; import { names } from '@nrwl/workspace/src/utils/name-utils';
import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace'; import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace';
import { readJsonInTree } from '@nrwl/workspace';
const testRunner = new SchematicTestRunner( const testRunner = new SchematicTestRunner(
'@nrwl/react', '@nrwl/react',
@ -32,9 +33,48 @@ export function callRule(rule: Rule, tree: Tree) {
return testRunner.callRule(rule, tree).toPromise(); 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> { export function createApp(tree: Tree, appName: string): Promise<Tree> {
const { fileName } = names(appName); 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( return callRule(
updateWorkspace((workspace) => { updateWorkspace((workspace) => {
workspace.projects.add({ 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> { export function createLib(tree: Tree, libName: string): Promise<Tree> {
const { fileName } = names(libName); 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( return callRule(
updateWorkspace((workspace) => { updateWorkspace((workspace) => {

View File

@ -1,26 +1,32 @@
export const nxVersion = '*'; export const nxVersion = '*';
export const reactVersion = '16.12.0'; export const reactVersion = '16.13.1';
export const reactDomVersion = '16.12.0'; export const reactDomVersion = '16.13.1';
export const typesReactVersion = '16.9.17'; export const typesReactVersion = '16.9.35';
export const typesReactDomVersion = '16.9.4'; export const typesReactDomVersion = '16.9.8';
export const styledComponentsVersion = '5.0.1'; export const styledComponentsVersion = '5.1.0';
export const typesStyledComponentsVersion = '5.0.1'; 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 emotionStyledVersion = '10.0.27';
export const emotionCoreVersion = '10.0.27'; export const emotionCoreVersion = '10.0.27';
export const emotionBabelPresetCssPropVersion = '10.0.27';
export const reactRouterDomVersion = '5.1.2'; export const reactRouterDomVersion = '5.2.0';
export const typesReactRouterDomVersion = '5.1.3'; 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 reduxjsToolkitVersion = '1.3.6';
export const reactReduxVersion = '7.1.3'; export const reactReduxVersion = '7.2.0';
export const typesReactReduxVersion = '7.1.5'; 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 eslintPluginJsxA11yVersion = '6.2.3';
export const eslintPluginReactVersion = '7.17.0'; export const eslintPluginReactVersion = '7.20.0';
export const eslintPluginReactHooksVersion = '2.3.0'; export const eslintPluginReactHooksVersion = '4.0.2';
export const babelPluginStyledComponentsVersion = '1.10.7';

View File

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

View File

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

View File

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

View File

@ -48,6 +48,46 @@ describe('init', () => {
}, },
tree 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 { import {
addPackageWithInit, addPackageWithInit,
formatFiles, formatFiles,
@ -10,6 +10,7 @@ import {
documentRegisterElementVersion, documentRegisterElementVersion,
nxVersion, nxVersion,
} from '../../utils/versions'; } from '../../utils/versions';
import { initRootBabelConfig } from '../../utils/rules';
function updateDependencies(): Rule { function updateDependencies(): Rule {
return updateJsonInTree('package.json', (json) => { return updateJsonInTree('package.json', (json) => {
@ -36,6 +37,7 @@ export default function (schema: Schema) {
? addPackageWithInit('@nrwl/cypress') ? addPackageWithInit('@nrwl/cypress')
: noop(), : noop(),
updateDependencies(), updateDependencies(),
initRootBabelConfig(),
formatFiles(schema), 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( export function createBabelConfig(
context: string, context: string,
esm: boolean, esm: boolean,

View File

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

View File

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

View File

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

View File

@ -47,6 +47,8 @@ export function normalizeBuildOptions<T extends BuildBuilderOptions>(
): T { ): T {
return { return {
...options, ...options,
root,
sourceRoot,
main: resolve(root, options.main), main: resolve(root, options.main),
outputPath: resolve(root, options.outputPath), outputPath: resolve(root, options.outputPath),
tsConfig: resolve(root, options.tsConfig), 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 { FileReplacement } from './normalize';
import { JsonObject, Path } from '@angular-devkit/core'; import { JsonObject } from '@angular-devkit/core';
export interface OptimizationOptions { export interface OptimizationOptions {
scripts: boolean; scripts: boolean;
@ -37,7 +37,7 @@ export interface BuildBuilderOptions {
webpackConfig?: string; webpackConfig?: string;
root?: string; root?: string;
sourceRoot?: Path; sourceRoot?: string;
} }
export interface Globals { export interface Globals {

View File

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

View File

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

View File

@ -59,8 +59,6 @@ function checkFiles(files: string[]) {
r.package r.package
)} has new version ${chalk.bold(r.latest)} (current: ${r.prev})` )} 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 // 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';
import 'zone.js/dist/zone-testing'; import 'zone.js/dist/zone-testing';

712
yarn.lock

File diff suppressed because it is too large Load Diff