From 4a2da9843638452af59bdadf01406d00f485302b Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 7 Mar 2022 16:27:51 +0000 Subject: [PATCH 1/3] feat(angular): add mfe helpers for sharing workspace libraries --- packages/angular/package.json | 3 +- .../webpack-server/webpack-server.impl.ts | 5 ++ .../angular/src/utils/mfe-webpack.spec.ts | 67 +++++++++++++++++ packages/angular/src/utils/mfe-webpack.ts | 73 +++++++++++++++++++ 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 packages/angular/src/utils/mfe-webpack.spec.ts create mode 100644 packages/angular/src/utils/mfe-webpack.ts diff --git a/packages/angular/package.json b/packages/angular/package.json index 23e39f38f03c7..5263334765d25 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -21,7 +21,8 @@ "./generators": "./generators.js", "./executors": "./executors.js", "./tailwind": "./tailwind.js", - "./src/generators/utils": "./src/generators/utils/index.js" + "./src/generators/utils": "./src/generators/utils/index.js", + "./mfe-utils": "./src/utils/mfe-webpack.js" }, "author": "Victor Savkin", "license": "MIT", diff --git a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts index 1d6a663806ea9..3f3f7adeb90b6 100644 --- a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts +++ b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts @@ -13,6 +13,11 @@ import { normalizeOptions } from './lib'; import type { Schema } from './schema'; export function webpackServer(schema: Schema, context: BuilderContext) { + process.env.NX_TSCONFIG_PATH = joinPathFragments( + context.workspaceRoot, + 'tsconfig.base.json' + ); + const options = normalizeOptions(schema); const workspaceConfig = new Workspaces( context.workspaceRoot diff --git a/packages/angular/src/utils/mfe-webpack.spec.ts b/packages/angular/src/utils/mfe-webpack.spec.ts new file mode 100644 index 0000000000000..82b5346cdfbdc --- /dev/null +++ b/packages/angular/src/utils/mfe-webpack.spec.ts @@ -0,0 +1,67 @@ +jest.mock('fs'); +jest.mock('@nrwl/workspace'); +import * as fs from 'fs'; +import * as workspace from '@nrwl/workspace'; + +import { shareWorkspaceLibraries } from './mfe-webpack'; + +describe('MFE Webpack Utils', () => { + afterEach(() => jest.clearAllMocks()); + + it('should error when the tsconfig file does not exist', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(false); + + // ACT + try { + shareWorkspaceLibraries(['@myorg/shared']); + } catch (error) { + // ASSERT + expect(error.message).toEqual( + 'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)' + ); + } + }); + + it('should create an object with correct setup', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(true); + (workspace.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: { + '@myorg/shared': ['/libs/shared/src/index.ts'], + }, + }, + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); + // ASSERT + expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared'); + expect(sharedLibraries.getAliases()['@myorg/shared']).toContain( + 'libs/shared/src/index.ts' + ); + expect(sharedLibraries.getLibraries()).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: false, + }, + }); + }); + + it('should create an object with empty setup when tsconfig does not contain the shared lib', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(true); + (workspace.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: {}, + }, + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); + // ASSERT + expect(sharedLibraries.getAliases()).toEqual({}); + expect(sharedLibraries.getLibraries()).toEqual({}); + }); +}); diff --git a/packages/angular/src/utils/mfe-webpack.ts b/packages/angular/src/utils/mfe-webpack.ts new file mode 100644 index 0000000000000..6bd8364664ef6 --- /dev/null +++ b/packages/angular/src/utils/mfe-webpack.ts @@ -0,0 +1,73 @@ +import { readTsConfig } from '@nrwl/workspace'; +import { existsSync } from 'fs'; +import { NormalModuleReplacementPlugin } from 'webpack'; +import { appRootPath as rootPath } from '@nrwl/tao/src/utils/app-root'; +import { normalizePath, joinPathFragments } from '@nrwl/devkit'; +import { dirname } from 'path'; +import { ParsedCommandLine } from 'typescript'; + +export function shareWorkspaceLibraries( + libraries: string[], + tsConfigPath = process.env.NX_TSCONFIG_PATH +) { + if (!existsSync(tsConfigPath)) { + throw new Error( + `NX MFE: TsConfig Path for workspace libraries does not exist! (${tsConfigPath})` + ); + } + + const tsConfig: ParsedCommandLine = readTsConfig(tsConfigPath); + const tsconfigPathAliases = tsConfig.options?.paths; + + if (!tsconfigPathAliases) { + return { + getAliases: () => [], + getLibraries: () => [], + getReplacementPlugin: () => + new NormalModuleReplacementPlugin(/./, () => {}), + }; + } + + const pathMappings: { name: string; path: string }[] = []; + for (const [key, paths] of Object.entries(tsconfigPathAliases)) { + if (libraries && libraries.includes(key)) { + const pathToLib = normalizePath(joinPathFragments(rootPath, paths[0])); + pathMappings.push({ + name: key, + path: pathToLib, + }); + } + } + + return { + getAliases: () => + pathMappings.reduce( + (aliases, library) => ({ ...aliases, [library.name]: library.path }), + {} + ), + getLibraries: (eager?: boolean) => + pathMappings.reduce( + (libraries, library) => ({ + ...libraries, + [library.name]: { requiredVersion: false, eager }, + }), + {} + ), + getReplacementPlugin: () => + new NormalModuleReplacementPlugin(/./, (req) => { + if (!req.request.startsWith('.')) { + return; + } + + const from = req.context; + const to = normalizePath(joinPathFragments(req.context, req.request)); + + for (const library of pathMappings) { + const libFolder = normalizePath(dirname(library.path)); + if (!from.startsWith(libFolder) && to.startsWith(libFolder)) { + req.request = library.name; + } + } + }), + }; +} From 5cfb8695c62a1017b74fc51f3ae9a3bdc5e7a323 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 8 Mar 2022 11:04:08 +0000 Subject: [PATCH 2/3] feat(angular): add share packages helper --- .../angular/src/utils/mfe-webpack.spec.ts | 148 ++++++++++++------ packages/angular/src/utils/mfe-webpack.ts | 25 ++- 2 files changed, 127 insertions(+), 46 deletions(-) diff --git a/packages/angular/src/utils/mfe-webpack.spec.ts b/packages/angular/src/utils/mfe-webpack.spec.ts index 82b5346cdfbdc..1469d5a876af3 100644 --- a/packages/angular/src/utils/mfe-webpack.spec.ts +++ b/packages/angular/src/utils/mfe-webpack.spec.ts @@ -3,65 +3,123 @@ jest.mock('@nrwl/workspace'); import * as fs from 'fs'; import * as workspace from '@nrwl/workspace'; -import { shareWorkspaceLibraries } from './mfe-webpack'; +import { sharePackages, shareWorkspaceLibraries } from './mfe-webpack'; describe('MFE Webpack Utils', () => { afterEach(() => jest.clearAllMocks()); - it('should error when the tsconfig file does not exist', () => { - // ARRANGE - (fs.existsSync as jest.Mock).mockReturnValue(false); + describe('ShareWorkspaceLibraries', () => { + it('should error when the tsconfig file does not exist', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(false); - // ACT - try { - shareWorkspaceLibraries(['@myorg/shared']); - } catch (error) { + // ACT + try { + shareWorkspaceLibraries(['@myorg/shared']); + } catch (error) { + // ASSERT + expect(error.message).toEqual( + 'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)' + ); + } + }); + + it('should create an object with correct setup', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(true); + (workspace.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: { + '@myorg/shared': ['/libs/shared/src/index.ts'], + }, + }, + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); // ASSERT - expect(error.message).toEqual( - 'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)' + expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared'); + expect(sharedLibraries.getAliases()['@myorg/shared']).toContain( + 'libs/shared/src/index.ts' ); - } - }); - - it('should create an object with correct setup', () => { - // ARRANGE - (fs.existsSync as jest.Mock).mockReturnValue(true); - (workspace.readTsConfig as jest.Mock).mockReturnValue({ - options: { - paths: { - '@myorg/shared': ['/libs/shared/src/index.ts'], + expect(sharedLibraries.getLibraries()).toEqual({ + '@myorg/shared': { + eager: undefined, + requiredVersion: false, }, - }, + }); }); - // ACT - const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); - // ASSERT - expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared'); - expect(sharedLibraries.getAliases()['@myorg/shared']).toContain( - 'libs/shared/src/index.ts' - ); - expect(sharedLibraries.getLibraries()).toEqual({ - '@myorg/shared': { - eager: undefined, - requiredVersion: false, - }, + it('should create an object with empty setup when tsconfig does not contain the shared lib', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(true); + (workspace.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: {}, + }, + }); + + // ACT + const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); + // ASSERT + expect(sharedLibraries.getAliases()).toEqual({}); + expect(sharedLibraries.getLibraries()).toEqual({}); }); }); - it('should create an object with empty setup when tsconfig does not contain the shared lib', () => { - // ARRANGE - (fs.existsSync as jest.Mock).mockReturnValue(true); - (workspace.readTsConfig as jest.Mock).mockReturnValue({ - options: { - paths: {}, - }, + describe('SharePackages', () => { + it('should throw when it cannot find root package.json', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(false); + + // ACT + try { + sharePackages(['@angular/core']); + } catch (error) { + // ASSERT + expect(error.message).toEqual( + 'NX MFE: Could not find root package.json to determine dependency versions.' + ); + } }); - // ACT - const sharedLibraries = shareWorkspaceLibraries(['@myorg/shared']); - // ASSERT - expect(sharedLibraries.getAliases()).toEqual({}); - expect(sharedLibraries.getLibraries()).toEqual({}); + it('should correctly map the shared packages to objects', () => { + // ARRANGE + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue( + JSON.stringify({ + dependencies: { + '@angular/core': '~13.2.0', + '@angular/common': '~13.2.0', + rxjs: '~7.4.0', + }, + }) + ); + + // ACT + const packages = sharePackages([ + '@angular/core', + '@angular/common', + 'rxjs', + ]); + // ASSERT + expect(packages).toEqual({ + '@angular/core': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + '@angular/common': { + singleton: true, + strictVersion: true, + requiredVersion: '~13.2.0', + }, + rxjs: { + singleton: true, + strictVersion: true, + requiredVersion: '~7.4.0', + }, + }); + }); }); }); diff --git a/packages/angular/src/utils/mfe-webpack.ts b/packages/angular/src/utils/mfe-webpack.ts index 6bd8364664ef6..57a6ecdd1dfa2 100644 --- a/packages/angular/src/utils/mfe-webpack.ts +++ b/packages/angular/src/utils/mfe-webpack.ts @@ -1,5 +1,5 @@ import { readTsConfig } from '@nrwl/workspace'; -import { existsSync } from 'fs'; +import { existsSync, readFileSync } from 'fs'; import { NormalModuleReplacementPlugin } from 'webpack'; import { appRootPath as rootPath } from '@nrwl/tao/src/utils/app-root'; import { normalizePath, joinPathFragments } from '@nrwl/devkit'; @@ -71,3 +71,26 @@ export function shareWorkspaceLibraries( }), }; } + +export function sharePackages(packages: string[]) { + const pkgJsonPath = joinPathFragments(rootPath, 'package.json'); + if (!existsSync(pkgJsonPath)) { + throw new Error( + 'NX MFE: Could not find root package.json to determine dependency versions.' + ); + } + + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')); + + return packages.reduce( + (shared, pkgName) => ({ + ...shared, + [pkgName]: { + singleton: true, + strictVersion: true, + requiredVersion: pkgJson.dependencies[pkgName], + }, + }), + {} + ); +} From 82b30172c9b4784dd1ee58589868cf941ac88b7f Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 8 Mar 2022 11:50:20 +0000 Subject: [PATCH 3/3] chore(angular): add webpack dep --- packages/angular/ng-package.json | 3 ++- packages/angular/package.json | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/angular/ng-package.json b/packages/angular/ng-package.json index 811a8eb6dcf9b..f0f89458bb2a8 100644 --- a/packages/angular/ng-package.json +++ b/packages/angular/ng-package.json @@ -15,7 +15,8 @@ "webpack-merge", "ts-node", "tsconfig-paths", - "semver" + "semver", + "webpack" ], "keepLifecycleScripts": true } diff --git a/packages/angular/package.json b/packages/angular/package.json index 5263334765d25..e7bd942fbc4e0 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -21,8 +21,7 @@ "./generators": "./generators.js", "./executors": "./executors.js", "./tailwind": "./tailwind.js", - "./src/generators/utils": "./src/generators/utils/index.js", - "./mfe-utils": "./src/utils/mfe-webpack.js" + "./src/generators/utils": "./src/generators/utils/index.js" }, "author": "Victor Savkin", "license": "MIT", @@ -51,6 +50,7 @@ "webpack-merge": "5.7.3", "ts-node": "~9.1.1", "tsconfig-paths": "^3.9.0", - "semver": "7.3.4" + "semver": "7.3.4", + "webpack": "^5.58.1" } }