From 67a27774ac9e0aa906797fa3a02cccec40d71fcb Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 9 Jun 2022 10:16:57 +0100 Subject: [PATCH] fix(angular): support secondary entry points #10329 (#10615) --- e2e/angular-core/src/projects.test.ts | 18 ++++- packages/angular/src/utils/mfe/mfe-webpack.ts | 70 +++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/e2e/angular-core/src/projects.test.ts b/e2e/angular-core/src/projects.test.ts index 17649e6e37bb8..a1e3aec1ea18a 100644 --- a/e2e/angular-core/src/projects.test.ts +++ b/e2e/angular-core/src/projects.test.ts @@ -222,13 +222,14 @@ describe('Angular Projects', () => { expect(buildOutput).toContain('Successfully ran target build'); }); - it('MFE - should serve the host and remote apps successfully, even with a shared library between them', async () => { + it('MFE - should serve the host and remote apps successfully, even with a shared library with a secondary entry point between them', async () => { // ACT + ASSERT const port1 = 4200; const port2 = 4206; const hostApp = uniq('app'); const remoteApp1 = uniq('remote'); - const sharedLib = uniq('sharedLib'); + const sharedLib = uniq('shared-lib'); + const secondaryEntry = uniq('secondary'); // generate host app runCLI( @@ -241,7 +242,12 @@ describe('Angular Projects', () => { ); // generate a shared lib - runCLI(`generate @nrwl/angular:library ${sharedLib} --no-interactive`); + runCLI( + `generate @nrwl/angular:library ${sharedLib} --buildable --no-interactive` + ); + runCLI( + `generate @nrwl/angular:library-secondary-entry-point --library=${sharedLib} --name=${secondaryEntry} --no-interactive` + ); // update the files to use shared library updateFile( @@ -251,6 +257,9 @@ describe('Angular Projects', () => { import { ${ names(sharedLib).className }Module } from '@${proj}/${sharedLib}'; + import { ${ + names(secondaryEntry).className + }Module } from '@${proj}/${secondaryEntry}'; import { AppComponent } from './app.component'; import { NxWelcomeComponent } from './nx-welcome.component'; import { RouterModule } from '@angular/router'; @@ -285,6 +294,9 @@ describe('Angular Projects', () => { import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { ${names(sharedLib).className}Module } from '@${proj}/${sharedLib}'; + import { ${ + names(secondaryEntry).className + }Module } from '@${proj}/${secondaryEntry}'; import { RemoteEntryComponent } from './entry.component'; @NgModule({ diff --git a/packages/angular/src/utils/mfe/mfe-webpack.ts b/packages/angular/src/utils/mfe/mfe-webpack.ts index 46598b6868cfc..5dcacd41b6fc8 100644 --- a/packages/angular/src/utils/mfe/mfe-webpack.ts +++ b/packages/angular/src/utils/mfe/mfe-webpack.ts @@ -22,6 +22,65 @@ export interface SharedLibraryConfig { eager?: boolean; } +function traverseUpFileTreeAndPerformActionUntil( + dirPath: string, + endPath: string, + action: (dirname: string) => boolean +) { + while (dirPath !== endPath) { + if (action(dirPath)) { + break; + } + dirPath = dirname(dirPath); + } +} + +function collectWorkspaceLibrarySecondaryEntryPoints( + library: string, + libraryPath: string, + tsconfigPathAliases: Record +) { + let libraryRootDirPath = libraryPath; + traverseUpFileTreeAndPerformActionUntil( + dirname(libraryRootDirPath), + workspaceRoot, + (currentDirPath) => { + if (existsSync(join(currentDirPath, 'package.json'))) { + libraryRootDirPath = currentDirPath; + return true; + } + } + ); + + const aliasesUnderLibrary = Object.keys(tsconfigPathAliases).filter( + (libName) => libName.startsWith(library) && libName !== library + ); + const secondaryEntryPoints = []; + for (const alias of aliasesUnderLibrary) { + const pathToLib = dirname( + join(workspaceRoot, tsconfigPathAliases[alias][0]) + ); + let isSecondaryEntrypoint = false; + let searchDir = pathToLib; + traverseUpFileTreeAndPerformActionUntil( + searchDir, + libraryRootDirPath, + (currentDirPath) => { + if (existsSync(join(currentDirPath, 'ng-package.json'))) { + isSecondaryEntrypoint = true; + return true; + } + } + ); + + if (isSecondaryEntrypoint) { + secondaryEntryPoints.push({ name: alias, path: pathToLib }); + } + } + + return secondaryEntryPoints; +} + export function shareWorkspaceLibraries( libraries: string[], tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath() @@ -48,6 +107,17 @@ export function shareWorkspaceLibraries( for (const [key, paths] of Object.entries(tsconfigPathAliases)) { if (libraries && libraries.includes(key)) { const pathToLib = normalize(join(workspaceRoot, paths[0])); + collectWorkspaceLibrarySecondaryEntryPoints( + key, + pathToLib, + tsconfigPathAliases + ).forEach(({ name, path }) => + pathMappings.push({ + name, + path, + }) + ); + pathMappings.push({ name: key, path: pathToLib,