From 664df0e849fc69922630fff8423d01f67dc76006 Mon Sep 17 00:00:00 2001 From: Daniele Tortora <45793869+floroz@users.noreply.github.com> Date: Wed, 4 May 2022 17:58:34 +0200 Subject: [PATCH] feat(js): add `--includeBabelRc` flag for @nrwl/js:library generator (#8793, #8600) (#10055) * fix(js): fix(js): adding missing babelrc for tsc compiled libraries ISSUES CLOSED: #8600, #8793 ISSUES CLOSED: #8600, #8793 * feat(js): adds the --skipBabelConfig flag to prevent .babelrc file generation ISSUES CLOSED: #8600, #8793 ISSUES CLOSED: #8600, #8793 * feat(js): adds the --includeBabelRc flag to the library generator adds the --includeBabelRc flag to the library generator, and a mechanism to automatically detect the nrwl/web plugin, which requires auto-generation of the babelrc even when not explicitly set by the user ISSUES CLOSED: #8600, #8793 --- docs/generated/packages/js.json | 4 + e2e/js/src/js.test.ts | 22 ++++ .../js/src/generators/library/library.spec.ts | 113 ++++++++++++++++++ packages/js/src/generators/library/library.ts | 40 ++++++- .../js/src/generators/library/schema.json | 4 + packages/js/src/utils/schema.d.ts | 1 + 6 files changed, 183 insertions(+), 1 deletion(-) diff --git a/docs/generated/packages/js.json b/docs/generated/packages/js.json index e9639a6b57ac5..90a2d984a1374 100644 --- a/docs/generated/packages/js.json +++ b/docs/generated/packages/js.json @@ -59,6 +59,10 @@ "description": "Do not update tsconfig.json for development experience.", "default": false }, + "includeBabelRc": { + "type": "boolean", + "description": "Include a .babelrc configuration to compile TypeScript files" + }, "testEnvironment": { "type": "string", "enum": ["jsdom", "node"], diff --git a/e2e/js/src/js.test.ts b/e2e/js/src/js.test.ts index 1c6e0576a751f..955d5c21fd4d2 100644 --- a/e2e/js/src/js.test.ts +++ b/e2e/js/src/js.test.ts @@ -1,5 +1,6 @@ import { checkFilesExist, + checkFilesDoNotExist, newProject, readFile, readJson, @@ -46,6 +47,16 @@ describe('js e2e', () => { 'match the cache' ); + const packageJson = readJson('package.json'); + const devPackageNames = Object.keys(packageJson.devDependencies); + expect(devPackageNames).toContain('@nrwl/web'); + + const babelRc = readJson(`libs/${lib}/.babelrc`); + expect(babelRc.plugins).toBeUndefined(); + expect(babelRc.presets).toStrictEqual([ + ['@nrwl/web/babel', { useBuiltIns: 'usage' }], + ]); + expect(runCLI(`build ${lib}`)).toContain('Done compiling TypeScript files'); checkFilesExist( `dist/libs/${lib}/README.md`, @@ -181,6 +192,8 @@ describe('js e2e', () => { `dist/libs/${lib}/src/lib/${lib}.d.ts` ); + checkFilesDoNotExist(`libs/${lib}/.babelrc`); + const parentLib = uniq('parentlib'); runCLI(`generate @nrwl/js:lib ${parentLib} --buildable --compiler=swc`); const parentLibPackageJson = readJson(`libs/${parentLib}/package.json`); @@ -220,4 +233,13 @@ describe('js e2e', () => { expect(output).toContain('1 task(s) it depends on'); expect(output).toContain('Successfully compiled: 2 files with swc'); }, 120000); + + it('should not create a `.babelrc` file when creating libs with js executors (--compiler=tsc)', () => { + const lib = uniq('lib'); + runCLI( + `generate @nrwl/js:lib ${lib} --compiler=tsc --includeBabelRc=false` + ); + + checkFilesDoNotExist(`libs/${lib}/.babelrc`); + }); }); diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index 9068e51a16c6b..305a1404e551a 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -13,6 +13,7 @@ describe('lib', () => { let tree: Tree; const defaultOptions: Omit = { skipTsConfig: false, + includeBabelRc: false, unitTestRunner: 'jest', skipFormat: false, linter: 'eslint', @@ -850,5 +851,117 @@ describe('lib', () => { expect(tree.exists('tools/scripts/publish.mjs')).toBeTruthy(); }); }); + + describe('--includeBabelRc', () => { + it('should generate a .babelrc when flag is set to true', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + includeBabelRc: true, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeTruthy(); + }); + + it('should not generate a .babelrc when flag is set to false', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + includeBabelRc: false, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeFalsy(); + }); + + it('should not generate a .babelrc when compiler is swc (even if flag is set to true)', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + compiler: 'swc', + includeBabelRc: true, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeFalsy(); + }); + + it('should generate a .babelrc when flag is set to true (even if there is no `@nrwl/web` plugin installed)', async () => { + updateJson(tree, 'package.json', (json) => { + json.devDependencies = {}; + return json; + }); + + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + includeBabelRc: true, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeTruthy(); + + const babelRc = readJson(tree, 'libs/my-lib/.babelrc'); + expect(babelRc).toMatchInlineSnapshot(` + Object { + "presets": Array [ + Array [ + "@nrwl/web/babel", + Object { + "useBuiltIns": "usage", + }, + ], + ], + } + `); + }); + + it('should generate a .babelrc when flag is not set and there is a `@nrwl/web` package installed', async () => { + updateJson(tree, 'package.json', (json) => { + json.devDependencies = { + '@nrwl/web': '1.1.1', + '@nrwl/react': '1.1.1', + '@nrwl/next': '1.1.1', + }; + return json; + }); + + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + includeBabelRc: undefined, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeTruthy(); + + const babelRc = readJson(tree, 'libs/my-lib/.babelrc'); + expect(babelRc).toMatchInlineSnapshot(` + Object { + "presets": Array [ + Array [ + "@nrwl/web/babel", + Object { + "useBuiltIns": "usage", + }, + ], + ], + } + `); + }); + it('should not generate a .babelrc when flag is not set and there is NOT a `@nrwl/web` package installed', async () => { + updateJson(tree, 'package.json', (json) => { + json.devDependencies = { + '@nrwl/angular': '1.1.1', + '@nrwl/next': '1.1.1', + }; + return json; + }); + + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + includeBabelRc: undefined, + }); + + expect(tree.exists('libs/my-lib/.babelrc')).toBeFalsy(); + }); + }); }); }); diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index b1f4dfa8563a5..381389c3d273a 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -12,6 +12,8 @@ import { toJS, Tree, updateJson, + readJson, + writeJson, } from '@nrwl/devkit'; import { jestProjectGenerator } from '@nrwl/jest'; import { findRootJestPreset } from '@nrwl/jest/src/utils/config/find-root-jest-files'; @@ -177,6 +179,40 @@ function updateTsConfig(tree: Tree, options: NormalizedSchema) { }); } +/** + * Currently `@nrwl/js:library` TypeScript files can be compiled by most NX applications scaffolded via the Plugin system. However, `@nrwl/react:app` is an exception that due to its babel configuration, won't transpile external TypeScript files from packages/libs that do not contain a .babelrc. + * + * If a user doesn't explicitly set the flag, to prevent breaking the experience (they see the application failing, and they need to manually add the babelrc themselves), we want to detect whether they have the `@nrwl/web` plugin installed, and generate it automatically for them (even when they do not explicity request it). + * + * You can find more details on why this is necessary here: + * https://github.com/nrwl/nx/pull/10055 + */ +function shouldAddBabelRc(tree: Tree, options: NormalizedSchema) { + if (typeof options.includeBabelRc === 'undefined') { + const webPluginName = '@nrwl/web'; + + const packageJson = readJson(tree, 'package.json'); + + const hasNxWebPlugin = Object.keys( + packageJson.devDependencies as Record + ).includes(webPluginName); + + return hasNxWebPlugin; + } + + return options.includeBabelRc; +} + +function addBabelRc(tree: Tree, options: NormalizedSchema) { + const filename = '.babelrc'; + + const babelrc = { + presets: [['@nrwl/web/babel', { useBuiltIns: 'usage' }]], + }; + + writeJson(tree, join(options.projectRoot, filename), babelrc); +} + function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) { const { className, name, propertyName } = names(options.name); @@ -196,9 +232,11 @@ function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) { hasUnitTestRunner: options.unitTestRunner !== 'none', }); - if (options.buildable && options.compiler === 'swc') { + if (options.compiler === 'swc') { addSwcDependencies(tree); addSwcConfig(tree, options.projectRoot); + } else if (shouldAddBabelRc(tree, options)) { + addBabelRc(tree, options); } if (options.unitTestRunner === 'none') { diff --git a/packages/js/src/generators/library/schema.json b/packages/js/src/generators/library/schema.json index 612ce9ec156f3..57426a85821ea 100644 --- a/packages/js/src/generators/library/schema.json +++ b/packages/js/src/generators/library/schema.json @@ -52,6 +52,10 @@ "description": "Do not update tsconfig.json for development experience.", "default": false }, + "includeBabelRc": { + "type": "boolean", + "description": "Include a .babelrc configuration to compile TypeScript files" + }, "testEnvironment": { "type": "string", "enum": ["jsdom", "node"], diff --git a/packages/js/src/utils/schema.d.ts b/packages/js/src/utils/schema.d.ts index c0a3d7c7a7461..76c788cb1ff22 100644 --- a/packages/js/src/utils/schema.d.ts +++ b/packages/js/src/utils/schema.d.ts @@ -15,6 +15,7 @@ export interface LibraryGeneratorSchema { tags?: string; simpleModuleName?: boolean; skipTsConfig?: boolean; + includeBabelRc?: boolean; unitTestRunner?: 'jest' | 'none'; linter?: Linter; testEnvironment?: 'jsdom' | 'node';