From e4e1976bd8d468ad97f2bc42af7bd56dc781e9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 00:43:01 +0100 Subject: [PATCH 01/21] feat(i18n): exposed entries built by ng application internal --- .../src/builders/build/builder.ts | 6 +- .../src/utils/angular-esbuild-adapter.ts | 172 ++++++++++++------ 2 files changed, 123 insertions(+), 55 deletions(-) diff --git a/libs/native-federation/src/builders/build/builder.ts b/libs/native-federation/src/builders/build/builder.ts index 05906309..eb7b8194 100644 --- a/libs/native-federation/src/builders/build/builder.ts +++ b/libs/native-federation/src/builders/build/builder.ts @@ -237,7 +237,11 @@ export async function* runBuilder( } if (write && !nfOptions.dev) { - updateIndexHtml(fedOptions); + updateIndexHtml(fedOptions); // TODO: pass it the output, and have it find all index.html-s to update + // TODO: at this point we can also copy remoteEntry.json from root next to each index.html + // TODO: remoteEntry.json-s that are copied should be transformed because they and exposed entries + // go to language folders, but shared files go to dist root. + // TODO:? import maps need to be copied and transformed like the remoteEntry-s. They're not used anywhere though. } if (first && runServer) { diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index c2e2634e..3118688a 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -27,7 +27,7 @@ import { import { createRequire } from 'node:module'; import { Schema as EsBuildBuilderOptions } from '@angular-devkit/build-angular/src/builders/browser-esbuild/schema'; -import { ApplicationBuilderOptions as AppBuilderSchema } from '@angular-devkit/build-angular/src/builders/application'; +import { ApplicationBuilderOptions as AppBuilderSchema, buildApplicationInternal } from '@angular-devkit/build-angular/src/builders/application'; import { createSharedMappingsPlugin } from './shared-mappings-plugin'; import * as fs from 'fs'; @@ -40,6 +40,8 @@ import { BuildResult, EntryPoint, } from 'libs/native-federation-core/src/lib/core/build-adapter'; +import { ApplicationBuilderInternalOptions } from '@angular-devkit/build-angular/src/builders/application/options'; +import { OutputHashing } from '@angular-devkit/build-angular'; // const fesmFolderRegExp = /[/\\]fesm\d+[/\\]/; @@ -72,62 +74,48 @@ export function createAngularBuildAdapter( hash, } = options; - const files = await runEsbuild( - builderOptions, - context, - entryPoints, - external, - outdir, - tsConfigPath, - mappedPaths, - watch, - rebuildRequested, - dev, - kind, - hash - ); - - if (kind === 'shared-package') { - const scriptFiles = files.filter( - (f) => f.endsWith('.js') || f.endsWith('.mjs') + if (kind.includes('shared')) { + const files = await runEsbuild( + builderOptions, + context, + entryPoints, + external, + outdir, + tsConfigPath, + mappedPaths, + watch, + rebuildRequested, + dev, + kind, + hash ); - for (const file of scriptFiles) { - link(file, dev); + + if (kind === 'shared-package') { + const scriptFiles = files.filter( + (f) => f.endsWith('.js') || f.endsWith('.mjs') + ); + for (const file of scriptFiles) { + link(file, dev); + } } - } - return files.map((fileName) => ({ fileName } as BuildResult)); - - // TODO: Do we still need rollup as esbuilt evolved? - // if (kind === 'shared-package') { - // await runRollup(entryPoint, external, outfile); - // } else { - - // if ( - // dev && - // kind === 'shared-package' && - // entryPoint.match(fesmFolderRegExp) - // ) { - // fs.copyFileSync(entryPoint, outfile); - // } else { - // await runEsbuild( - // builderOptions, - // context, - // entryPoint, - // external, - // outfile, - // tsConfigPath, - // mappedPaths, - // watch, - // rebuildRequested, - // dev, - // kind - // ); - // } - // if (kind === 'shared-package' && fs.existsSync(outfile)) { - // await link(outfile, dev); - // } - // } + return files.map((fileName) => ({ fileName } as BuildResult)); + } else { + return await runNgBuild( + builderOptions, + context, + entryPoints, + external, + outdir, + tsConfigPath, + mappedPaths, + watch, + rebuildRequested, + dev, + kind, + hash + ); + } }; async function link(outfile: string, dev: boolean) { @@ -427,3 +415,79 @@ export function loadEsmModule(modulePath: string | URL): Promise { modulePath ) as Promise; } + +async function runNgBuild( + builderOptions: AppBuilderSchema, + context: BuilderContext, + entryPoints: EntryPoint[], + external: string[], + outdir: string, + tsConfigPath: string, + mappedPaths: MappedPath[], + watch?: boolean, + rebuildRequested: RebuildEvents = new RebuildHubs(), + dev?: boolean, + kind?: BuildKind, + hash = false, + plugins: esbuild.Plugin[] | null = null, + absWorkingDir: string | undefined = undefined, + logLevel: esbuild.LogLevel = 'warning'): Promise { + + // unfortunately angular doesn't let us specify the out name of the enties. We'll have to map file names post-build. + const entries = new Set(); + for (const entryPoint of entryPoints) { + entries.add(entryPoint.fileName); + } + // if watch stays enabled then the build will hang at this point... + // watching build of exposed entries may not be necessary, because you host the app including any exposed things (if reachable) + const builderOpts: ApplicationBuilderInternalOptions = { + ... builderOptions, + watch: false, + entryPoints: entries, + outputHashing: hash ? OutputHashing.Bundles : OutputHashing.None + }; + if (builderOpts.browser) { // we're specifying entries instead of browser + delete builderOpts.browser; + } + + const inputPlugins = [ + ... plugins, + createSharedMappingsPlugin(mappedPaths), + { + name: 'fixExternalsAndSplitting', + setup(build: esbuild.PluginBuild) { + if (build.initialOptions.platform !== 'node') { + build.initialOptions.external = external.filter( + (e) => e !== 'tslib' + ); + } + build.initialOptions.splitting = false; + build.initialOptions.chunkNames = null; + } + } + ]; + const builderRun = await buildApplicationInternal(builderOpts, context, { write: false }, inputPlugins); + const result: BuildResult[] = []; + for await (const output of builderRun) { + if (!output.success) { + logger.error("Building exposed entries failed with: " + output.error); + throw new Error("Native federation failed building exposed entries"); + } + for (const outFile of output.outputFiles) { + // we were not able to tell angular builder that we expected the entrypoint's out name to be different + // therefore we must try and map files back, and do the transformation ourselves, when applicable. + const name = path.basename(outFile.path).replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); + const entry = entryPoints.find(ep => path.basename(ep.fileName) == name); + if (entry) { + // TODO: put hash back + const intendedFileName = path.join(path.dirname(outFile.path), entry.outName); + result.push({ fileName: intendedFileName }); + } else { + result.push({ fileName: outFile.path }); + } + } + } + // TODO: register for rebuild + // TODO: write result + return result; +} From 8acf3c4f4847b8422e49bdf33c2fd3e797d48f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 00:45:15 +0100 Subject: [PATCH 02/21] chore(beautify): this automatically modified indentations --- .../src/utils/angular-esbuild-adapter.ts | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index 3118688a..7637925f 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -27,7 +27,10 @@ import { import { createRequire } from 'node:module'; import { Schema as EsBuildBuilderOptions } from '@angular-devkit/build-angular/src/builders/browser-esbuild/schema'; -import { ApplicationBuilderOptions as AppBuilderSchema, buildApplicationInternal } from '@angular-devkit/build-angular/src/builders/application'; +import { + ApplicationBuilderOptions as AppBuilderSchema, + buildApplicationInternal, +} from '@angular-devkit/build-angular/src/builders/application'; import { createSharedMappingsPlugin } from './shared-mappings-plugin'; import * as fs from 'fs'; @@ -256,7 +259,7 @@ async function runEsbuild( 'async-await': false, 'object-rest-spread': false, }, - splitting: (kind === 'mapping-or-exposed'), + splitting: kind === 'mapping-or-exposed', platform: 'browser', format: 'esm', target: ['esnext'], @@ -431,8 +434,8 @@ async function runNgBuild( hash = false, plugins: esbuild.Plugin[] | null = null, absWorkingDir: string | undefined = undefined, - logLevel: esbuild.LogLevel = 'warning'): Promise { - + logLevel: esbuild.LogLevel = 'warning' +): Promise { // unfortunately angular doesn't let us specify the out name of the enties. We'll have to map file names post-build. const entries = new Set(); for (const entryPoint of entryPoints) { @@ -441,46 +444,57 @@ async function runNgBuild( // if watch stays enabled then the build will hang at this point... // watching build of exposed entries may not be necessary, because you host the app including any exposed things (if reachable) const builderOpts: ApplicationBuilderInternalOptions = { - ... builderOptions, + ...builderOptions, watch: false, entryPoints: entries, - outputHashing: hash ? OutputHashing.Bundles : OutputHashing.None + outputHashing: hash ? OutputHashing.Bundles : OutputHashing.None, }; - if (builderOpts.browser) { // we're specifying entries instead of browser + if (builderOpts.browser) { + // we're specifying entries instead of browser delete builderOpts.browser; } const inputPlugins = [ - ... plugins, + ...plugins, createSharedMappingsPlugin(mappedPaths), { name: 'fixExternalsAndSplitting', setup(build: esbuild.PluginBuild) { if (build.initialOptions.platform !== 'node') { - build.initialOptions.external = external.filter( - (e) => e !== 'tslib' - ); + build.initialOptions.external = external.filter((e) => e !== 'tslib'); } build.initialOptions.splitting = false; build.initialOptions.chunkNames = null; - } - } + }, + }, ]; - const builderRun = await buildApplicationInternal(builderOpts, context, { write: false }, inputPlugins); + const builderRun = await buildApplicationInternal( + builderOpts, + context, + { write: false }, + inputPlugins + ); const result: BuildResult[] = []; for await (const output of builderRun) { if (!output.success) { - logger.error("Building exposed entries failed with: " + output.error); - throw new Error("Native federation failed building exposed entries"); + logger.error('Building exposed entries failed with: ' + output.error); + throw new Error('Native federation failed building exposed entries'); } for (const outFile of output.outputFiles) { // we were not able to tell angular builder that we expected the entrypoint's out name to be different // therefore we must try and map files back, and do the transformation ourselves, when applicable. - const name = path.basename(outFile.path).replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); - const entry = entryPoints.find(ep => path.basename(ep.fileName) == name); + const name = path + .basename(outFile.path) + .replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); + const entry = entryPoints.find( + (ep) => path.basename(ep.fileName) == name + ); if (entry) { // TODO: put hash back - const intendedFileName = path.join(path.dirname(outFile.path), entry.outName); + const intendedFileName = path.join( + path.dirname(outFile.path), + entry.outName + ); result.push({ fileName: intendedFileName }); } else { result.push({ fileName: outFile.path }); From b085958f589d288f2875392f465cdf8c46ba7b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 09:27:00 +0100 Subject: [PATCH 03/21] feat(i18n): ng adapter writes files, maps exposed files, registers 4 rebuild --- .../src/utils/angular-esbuild-adapter.ts | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index 7637925f..8fd2bdaa 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -6,7 +6,7 @@ import { import * as esbuild from 'esbuild'; import { createCompilerPlugin } from '@angular-devkit/build-angular/src/tools/esbuild/angular/compiler-plugin'; -import { BuilderContext } from '@angular-devkit/architect'; +import { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; import { transformSupportedBrowsersToTargets } from './transform'; @@ -45,6 +45,7 @@ import { } from 'libs/native-federation-core/src/lib/core/build-adapter'; import { ApplicationBuilderInternalOptions } from '@angular-devkit/build-angular/src/builders/application/options'; import { OutputHashing } from '@angular-devkit/build-angular'; +import { BuildOutputFile } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; // const fesmFolderRegExp = /[/\\]fesm\d+[/\\]/; @@ -301,7 +302,7 @@ async function runEsbuild( const ctx = await esbuild.context(config); const result = await ctx.rebuild(); - + // always false const memOnly = dev && kind === 'mapping-or-exposed' && !!_memResultHandler; const writtenFiles = writeResult(result, outdir, memOnly); @@ -370,7 +371,7 @@ function createTsConfigForFederation( } function writeResult( - result: esbuild.BuildResult, + result: Pick, 'outputFiles'>, outdir: string, memOnly: boolean ) { @@ -468,40 +469,52 @@ async function runNgBuild( }, }, ]; - const builderRun = await buildApplicationInternal( - builderOpts, - context, - { write: false }, - inputPlugins - ); - const result: BuildResult[] = []; - for await (const output of builderRun) { - if (!output.success) { - logger.error('Building exposed entries failed with: ' + output.error); - throw new Error('Native federation failed building exposed entries'); - } - for (const outFile of output.outputFiles) { + + const memOnly = dev && kind === 'mapping-or-exposed' && !!_memResultHandler; + + async function run(): Promise { + const builderRun = await buildApplicationInternal( + builderOpts, + context, + { write: false }, + inputPlugins + ); + let output: BuilderOutput & { + outputFiles?: BuildOutputFile[]; + assetFiles?: { source: string; destination: string }[]; + }; + for await (output of builderRun) { + if (!output.success) { + logger.error('Building exposed entries failed with: ' + output.error); + throw new Error('Native federation failed building exposed entries'); + } // we were not able to tell angular builder that we expected the entrypoint's out name to be different // therefore we must try and map files back, and do the transformation ourselves, when applicable. - const name = path - .basename(outFile.path) - .replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); - const entry = entryPoints.find( - (ep) => path.basename(ep.fileName) == name - ); - if (entry) { - // TODO: put hash back - const intendedFileName = path.join( - path.dirname(outFile.path), - entry.outName + for (const outFile of output.outputFiles) { + const pathBasename = path.basename(outFile.path); + const name = pathBasename.replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); + const entry = entryPoints.find( + (ep) => path.basename(ep.fileName) == name ); - result.push({ fileName: intendedFileName }); - } else { - result.push({ fileName: outFile.path }); + if (entry) { + const nameHash = pathBasename.substring( + pathBasename.lastIndexOf('-'), + pathBasename.lastIndexOf('.') + ); + const originalOutName = entry.outName.substring(0, entry.outName.lastIndexOf('.')); + outFile.path = path.join( + path.dirname(outFile.path), + originalOutName + nameHash + '.js' + ); + } } } + // output's outFiles is marked optional. The Angular types aren't helping us here, but we know it's there + const writtenFiles = writeResult(output as any, outdir, memOnly); + return writtenFiles.map(file => ({ fileName: file })); } - // TODO: register for rebuild - // TODO: write result - return result; + rebuildRequested.rebuild.register(async () => { + await run(); + }); + return run(); } From cba5171ced3ad7afc3dbc99db821dc76adffc783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 11:31:04 +0100 Subject: [PATCH 04/21] feat(i18n): update all index.html-s --- .../src/builders/build/builder.ts | 9 +++------ .../src/utils/angular-esbuild-adapter.ts | 10 ++++++---- .../src/utils/prepare-bundles.ts | 20 +++++++++++++++++++ .../src/utils/updateIndexHtml.ts | 17 +++++++--------- 4 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 libs/native-federation/src/utils/prepare-bundles.ts diff --git a/libs/native-federation/src/builders/build/builder.ts b/libs/native-federation/src/builders/build/builder.ts index eb7b8194..d80783c0 100644 --- a/libs/native-federation/src/builders/build/builder.ts +++ b/libs/native-federation/src/builders/build/builder.ts @@ -37,7 +37,6 @@ import { startServer, } from '../../utils/dev-server'; import { RebuildHubs } from '../../utils/rebuild-events'; -import { updateIndexHtml, updateScriptTags } from '../../utils/updateIndexHtml'; import { existsSync, mkdirSync, rmSync } from 'fs'; import { EsBuildResult, @@ -49,6 +48,8 @@ import { createSharedMappingsPlugin } from '../../utils/shared-mappings-plugin'; import { Connect } from 'vite'; import { PluginBuild } from 'esbuild'; import { FederationInfo } from '@softarc/native-federation-runtime'; +import { prepareBundles } from '../../utils/prepare-bundles'; +import { updateScriptTags } from '../../utils/updateIndexHtml'; export async function* runBuilder( nfOptions: NfBuilderSchema, @@ -237,11 +238,7 @@ export async function* runBuilder( } if (write && !nfOptions.dev) { - updateIndexHtml(fedOptions); // TODO: pass it the output, and have it find all index.html-s to update - // TODO: at this point we can also copy remoteEntry.json from root next to each index.html - // TODO: remoteEntry.json-s that are copied should be transformed because they and exposed entries - // go to language folders, but shared files go to dist root. - // TODO:? import maps need to be copied and transformed like the remoteEntry-s. They're not used anywhere though. + prepareBundles(fedOptions, output); } if (first && runServer) { diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index 8fd2bdaa..a08212ef 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -60,6 +60,11 @@ export function setMemResultHandler(handler: MemResultHandler): void { _memResultHandler = handler; } +export type AngularBuildOutput = BuilderOutput & { + outputFiles?: BuildOutputFile[]; + assetFiles?: { source: string; destination: string }[]; +}; + export function createAngularBuildAdapter( builderOptions: AppBuilderSchema, context: BuilderContext, @@ -479,10 +484,7 @@ async function runNgBuild( { write: false }, inputPlugins ); - let output: BuilderOutput & { - outputFiles?: BuildOutputFile[]; - assetFiles?: { source: string; destination: string }[]; - }; + let output: AngularBuildOutput; for await (output of builderRun) { if (!output.success) { logger.error('Building exposed entries failed with: ' + output.error); diff --git a/libs/native-federation/src/utils/prepare-bundles.ts b/libs/native-federation/src/utils/prepare-bundles.ts new file mode 100644 index 00000000..1213e318 --- /dev/null +++ b/libs/native-federation/src/utils/prepare-bundles.ts @@ -0,0 +1,20 @@ +import { FederationOptions } from "@softarc/native-federation/build"; +import { AngularBuildOutput } from "./angular-esbuild-adapter"; +import { updateIndexHtml } from "./updateIndexHtml"; +import { BuildOutputFile } from "@angular-devkit/build-angular/src/tools/esbuild/bundler-context"; + +// assuming that the files have already been written +export function prepareBundles(fedOptions: FederationOptions, buildOutput: AngularBuildOutput): void { + // TODO: at this point we can also copy remoteEntry.json from root next to each index.html + // TODO: remoteEntry.json-s that are copied should be transformed because they and exposed entries + // go to language folders, but shared files go to dist root. + // TODO:? import maps need to be copied and transformed like the remoteEntry-s. They're not used anywhere though. + for (const indexFile of getIndexFiles(buildOutput)) { + updateIndexHtml(indexFile); + } +} + +function getIndexFiles(buildOutput: AngularBuildOutput): BuildOutputFile[] { + // TODO: filter also for these files to be in the browser area + return buildOutput.outputFiles.filter(file => file.path.endsWith('index.html')); +} \ No newline at end of file diff --git a/libs/native-federation/src/utils/updateIndexHtml.ts b/libs/native-federation/src/utils/updateIndexHtml.ts index 76e9fb95..e0f42422 100644 --- a/libs/native-federation/src/utils/updateIndexHtml.ts +++ b/libs/native-federation/src/utils/updateIndexHtml.ts @@ -1,21 +1,18 @@ import * as path from 'path'; import * as fs from 'fs'; -import { FederationOptions } from '@softarc/native-federation/build'; +import { BuildOutputFile } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; -export function updateIndexHtml(fedOptions: FederationOptions) { - const outputPath = path.join(fedOptions.workspaceRoot, fedOptions.outputPath); - const indexPath = path.join(outputPath, 'index.html'); +export function updateIndexHtml(file: BuildOutputFile) { + const dir = path.dirname(file.fullOutputPath); const mainName = fs - .readdirSync(outputPath) + .readdirSync(dir) .find((f) => f.startsWith('main') && f.endsWith('.js')); const polyfillsName = fs - .readdirSync(outputPath) + .readdirSync(dir) .find((f) => f.startsWith('polyfills') && f.endsWith('.js')); - let indexContent = fs.readFileSync(indexPath, 'utf-8'); - - indexContent = updateScriptTags(indexContent, mainName, polyfillsName); - fs.writeFileSync(indexPath, indexContent, 'utf-8'); + let indexContent = updateScriptTags(file.text, mainName, polyfillsName); + fs.writeFileSync(file.fullOutputPath, indexContent, 'utf-8'); } export function updateScriptTags( From d8af33d6d23a8d5627fd2c0a03dd0a5941fa79bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 15:43:10 +0100 Subject: [PATCH 05/21] feat(i18n): preparing bundle, correct external handling --- .../src/builders/build/builder.ts | 2 +- .../src/utils/angular-esbuild-adapter.ts | 17 ++-- .../src/utils/prepare-bundles.ts | 89 +++++++++++++++---- .../src/utils/updateIndexHtml.ts | 9 +- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/libs/native-federation/src/builders/build/builder.ts b/libs/native-federation/src/builders/build/builder.ts index d80783c0..df783120 100644 --- a/libs/native-federation/src/builders/build/builder.ts +++ b/libs/native-federation/src/builders/build/builder.ts @@ -238,7 +238,7 @@ export async function* runBuilder( } if (write && !nfOptions.dev) { - prepareBundles(fedOptions, output); + prepareBundles(options, fedOptions, output); } if (first && runServer) { diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index a08212ef..1d9858f4 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -454,6 +454,9 @@ async function runNgBuild( watch: false, entryPoints: entries, outputHashing: hash ? OutputHashing.Bundles : OutputHashing.None, + externalDependencies: [ + ...builderOptions.externalDependencies, + ...external].filter((e) => e !== 'tslib') }; if (builderOpts.browser) { // we're specifying entries instead of browser @@ -464,17 +467,14 @@ async function runNgBuild( ...plugins, createSharedMappingsPlugin(mappedPaths), { - name: 'fixExternalsAndSplitting', + name: 'fixSplitting', setup(build: esbuild.PluginBuild) { - if (build.initialOptions.platform !== 'node') { - build.initialOptions.external = external.filter((e) => e !== 'tslib'); - } build.initialOptions.splitting = false; build.initialOptions.chunkNames = null; }, }, ]; - + const memOnly = dev && kind === 'mapping-or-exposed' && !!_memResultHandler; async function run(): Promise { @@ -503,7 +503,10 @@ async function runNgBuild( pathBasename.lastIndexOf('-'), pathBasename.lastIndexOf('.') ); - const originalOutName = entry.outName.substring(0, entry.outName.lastIndexOf('.')); + const originalOutName = entry.outName.substring( + 0, + entry.outName.lastIndexOf('.') + ); outFile.path = path.join( path.dirname(outFile.path), originalOutName + nameHash + '.js' @@ -513,7 +516,7 @@ async function runNgBuild( } // output's outFiles is marked optional. The Angular types aren't helping us here, but we know it's there const writtenFiles = writeResult(output as any, outdir, memOnly); - return writtenFiles.map(file => ({ fileName: file })); + return writtenFiles.map((file) => ({ fileName: file })); } rebuildRequested.rebuild.register(async () => { await run(); diff --git a/libs/native-federation/src/utils/prepare-bundles.ts b/libs/native-federation/src/utils/prepare-bundles.ts index 1213e318..13e2a012 100644 --- a/libs/native-federation/src/utils/prepare-bundles.ts +++ b/libs/native-federation/src/utils/prepare-bundles.ts @@ -1,20 +1,77 @@ -import { FederationOptions } from "@softarc/native-federation/build"; -import { AngularBuildOutput } from "./angular-esbuild-adapter"; -import { updateIndexHtml } from "./updateIndexHtml"; -import { BuildOutputFile } from "@angular-devkit/build-angular/src/tools/esbuild/bundler-context"; +import { FederationOptions, writeFederationInfo, writeImportMap } from '@softarc/native-federation/build'; +import { AngularBuildOutput } from './angular-esbuild-adapter'; +import { updateIndexHtml } from './updateIndexHtml'; +import { BuildOutputFile, BuildOutputFileType } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; +import { JsonObject } from '@angular-devkit/core'; +import * as path from 'path'; +import * as fs from 'fs'; +import { Schema } from '@angular-devkit/build-angular/src/builders/application/schema'; +import { FederationInfo } from '@softarc/native-federation-runtime'; // assuming that the files have already been written -export function prepareBundles(fedOptions: FederationOptions, buildOutput: AngularBuildOutput): void { - // TODO: at this point we can also copy remoteEntry.json from root next to each index.html - // TODO: remoteEntry.json-s that are copied should be transformed because they and exposed entries - // go to language folders, but shared files go to dist root. - // TODO:? import maps need to be copied and transformed like the remoteEntry-s. They're not used anywhere though. - for (const indexFile of getIndexFiles(buildOutput)) { - updateIndexHtml(indexFile); - } +export function prepareBundles( + options: JsonObject & Schema, + fedOptions: FederationOptions, + buildOutput: AngularBuildOutput +): void { + + const metaDataPath = path.join( + fedOptions.workspaceRoot, + fedOptions.outputPath, + 'remoteEntry.json' + ); + const federationInfo = JSON.parse(fs.readFileSync(metaDataPath, 'utf-8')) as FederationInfo; + + for (const indexFile of getIndexFiles(options, buildOutput)) { + updateIndexHtml(fedOptions, indexFile); + addFederationInfoToBundle( + cloneFederationInfo(federationInfo), + fedOptions, + path.dirname(indexFile.path)); + } +} + +function getIndexFiles( + options: JsonObject & Schema, + buildOutput: AngularBuildOutput): BuildOutputFile[] { + + const indexName = typeof options.index == 'string' + ? path.basename(options.index) + : (options.index as any).output || 'index.html'; + + return buildOutput.outputFiles.filter((file) => + file.path.endsWith(indexName) && + file.type == BuildOutputFileType.Browser + ); } -function getIndexFiles(buildOutput: AngularBuildOutput): BuildOutputFile[] { - // TODO: filter also for these files to be in the browser area - return buildOutput.outputFiles.filter(file => file.path.endsWith('index.html')); -} \ No newline at end of file +function addFederationInfoToBundle(fedInfo: FederationInfo, fedOptions: FederationOptions, locale: string) { + // shared entries are not localized. We don't copy them to locale folders to save space + // but this means we have to map the items in federation info, to point up 1 level. + // exposed entries need no transformation, they were basenames, and they got localized + const newShared = fedInfo.shared.map((share) => ({ + ...share, + outFileName: path.join( + '..', + share.outFileName + ) + })); + fedInfo.shared = newShared; + const localizedFedOptions: FederationOptions = { + ...fedOptions, + outputPath: path.join( + fedOptions.outputPath, + locale + ) + } + writeFederationInfo(fedInfo, localizedFedOptions) + writeImportMap(newShared, localizedFedOptions); +} + +function cloneFederationInfo(fedInfo: FederationInfo): FederationInfo { + return { + name: fedInfo.name, + exposes: fedInfo.exposes.map(exp => ({ ...exp })), + shared: fedInfo.shared.map(share => ({ ...share })) + }; +} diff --git a/libs/native-federation/src/utils/updateIndexHtml.ts b/libs/native-federation/src/utils/updateIndexHtml.ts index e0f42422..790e5932 100644 --- a/libs/native-federation/src/utils/updateIndexHtml.ts +++ b/libs/native-federation/src/utils/updateIndexHtml.ts @@ -1,9 +1,14 @@ import * as path from 'path'; import * as fs from 'fs'; import { BuildOutputFile } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; +import { FederationOptions } from '@softarc/native-federation/build'; -export function updateIndexHtml(file: BuildOutputFile) { - const dir = path.dirname(file.fullOutputPath); +export function updateIndexHtml(fedOptions: FederationOptions, file: BuildOutputFile) { + const dir = path.join( + fedOptions.workspaceRoot, + fedOptions.outputPath, + path.dirname(file.path) + ); const mainName = fs .readdirSync(dir) .find((f) => f.startsWith('main') && f.endsWith('.js')); From 83281e72c1e242c9701b4b16d4be2824381b919e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Fri, 2 Feb 2024 15:43:49 +0100 Subject: [PATCH 06/21] beautify: whitespaces --- .../src/utils/angular-esbuild-adapter.ts | 3 +- .../src/utils/prepare-bundles.ts | 60 +++++++++++-------- .../src/utils/updateIndexHtml.ts | 5 +- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index 1d9858f4..de621f94 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -456,7 +456,8 @@ async function runNgBuild( outputHashing: hash ? OutputHashing.Bundles : OutputHashing.None, externalDependencies: [ ...builderOptions.externalDependencies, - ...external].filter((e) => e !== 'tslib') + ...external, + ].filter((e) => e !== 'tslib'), }; if (builderOpts.browser) { // we're specifying entries instead of browser diff --git a/libs/native-federation/src/utils/prepare-bundles.ts b/libs/native-federation/src/utils/prepare-bundles.ts index 13e2a012..507bc122 100644 --- a/libs/native-federation/src/utils/prepare-bundles.ts +++ b/libs/native-federation/src/utils/prepare-bundles.ts @@ -1,7 +1,14 @@ -import { FederationOptions, writeFederationInfo, writeImportMap } from '@softarc/native-federation/build'; +import { + FederationOptions, + writeFederationInfo, + writeImportMap, +} from '@softarc/native-federation/build'; import { AngularBuildOutput } from './angular-esbuild-adapter'; import { updateIndexHtml } from './updateIndexHtml'; -import { BuildOutputFile, BuildOutputFileType } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; +import { + BuildOutputFile, + BuildOutputFileType, +} from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; import { JsonObject } from '@angular-devkit/core'; import * as path from 'path'; import * as fs from 'fs'; @@ -14,64 +21,65 @@ export function prepareBundles( fedOptions: FederationOptions, buildOutput: AngularBuildOutput ): void { - const metaDataPath = path.join( fedOptions.workspaceRoot, fedOptions.outputPath, 'remoteEntry.json' ); - const federationInfo = JSON.parse(fs.readFileSync(metaDataPath, 'utf-8')) as FederationInfo; + const federationInfo = JSON.parse( + fs.readFileSync(metaDataPath, 'utf-8') + ) as FederationInfo; for (const indexFile of getIndexFiles(options, buildOutput)) { updateIndexHtml(fedOptions, indexFile); addFederationInfoToBundle( cloneFederationInfo(federationInfo), fedOptions, - path.dirname(indexFile.path)); + path.dirname(indexFile.path) + ); } } function getIndexFiles( options: JsonObject & Schema, - buildOutput: AngularBuildOutput): BuildOutputFile[] { + buildOutput: AngularBuildOutput +): BuildOutputFile[] { + const indexName = + typeof options.index == 'string' + ? path.basename(options.index) + : (options.index as any).output || 'index.html'; - const indexName = typeof options.index == 'string' - ? path.basename(options.index) - : (options.index as any).output || 'index.html'; - - return buildOutput.outputFiles.filter((file) => - file.path.endsWith(indexName) && - file.type == BuildOutputFileType.Browser + return buildOutput.outputFiles.filter( + (file) => + file.path.endsWith(indexName) && file.type == BuildOutputFileType.Browser ); } -function addFederationInfoToBundle(fedInfo: FederationInfo, fedOptions: FederationOptions, locale: string) { +function addFederationInfoToBundle( + fedInfo: FederationInfo, + fedOptions: FederationOptions, + locale: string +) { // shared entries are not localized. We don't copy them to locale folders to save space // but this means we have to map the items in federation info, to point up 1 level. // exposed entries need no transformation, they were basenames, and they got localized const newShared = fedInfo.shared.map((share) => ({ ...share, - outFileName: path.join( - '..', - share.outFileName - ) + outFileName: path.join('..', share.outFileName), })); fedInfo.shared = newShared; const localizedFedOptions: FederationOptions = { ...fedOptions, - outputPath: path.join( - fedOptions.outputPath, - locale - ) - } - writeFederationInfo(fedInfo, localizedFedOptions) + outputPath: path.join(fedOptions.outputPath, locale), + }; + writeFederationInfo(fedInfo, localizedFedOptions); writeImportMap(newShared, localizedFedOptions); } function cloneFederationInfo(fedInfo: FederationInfo): FederationInfo { return { name: fedInfo.name, - exposes: fedInfo.exposes.map(exp => ({ ...exp })), - shared: fedInfo.shared.map(share => ({ ...share })) + exposes: fedInfo.exposes.map((exp) => ({ ...exp })), + shared: fedInfo.shared.map((share) => ({ ...share })), }; } diff --git a/libs/native-federation/src/utils/updateIndexHtml.ts b/libs/native-federation/src/utils/updateIndexHtml.ts index 790e5932..e8af34ce 100644 --- a/libs/native-federation/src/utils/updateIndexHtml.ts +++ b/libs/native-federation/src/utils/updateIndexHtml.ts @@ -3,7 +3,10 @@ import * as fs from 'fs'; import { BuildOutputFile } from '@angular-devkit/build-angular/src/tools/esbuild/bundler-context'; import { FederationOptions } from '@softarc/native-federation/build'; -export function updateIndexHtml(fedOptions: FederationOptions, file: BuildOutputFile) { +export function updateIndexHtml( + fedOptions: FederationOptions, + file: BuildOutputFile +) { const dir = path.join( fedOptions.workspaceRoot, fedOptions.outputPath, From 1383f6e7249afe4e692bb9dea31ef6b98ce46ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Mon, 5 Feb 2024 12:42:43 +0100 Subject: [PATCH 07/21] feat(i18n) fixing up mistakes, whitespaces w/out package changes --- .../src/lib/utils/mapped-paths.js.map | 2 +- libs/native-federation/package.json | 3 +- .../src/builders/build/builder.ts | 11 ++-- .../src/utils/angular-esbuild-adapter.ts | 9 ++- .../src/utils/prepare-bundles.ts | 66 +++++++++++++++++-- .../src/utils/updateIndexHtml.ts | 2 +- 6 files changed, 75 insertions(+), 18 deletions(-) diff --git a/libs/native-federation-core/src/lib/utils/mapped-paths.js.map b/libs/native-federation-core/src/lib/utils/mapped-paths.js.map index ae59785f..4122da51 100644 --- a/libs/native-federation-core/src/lib/utils/mapped-paths.js.map +++ b/libs/native-federation-core/src/lib/utils/mapped-paths.js.map @@ -1 +1 @@ -{"version":3,"file":"mapped-paths.js","sourceRoot":"","sources":["mapped-paths.ts"],"names":[],"mappings":";;;;AAAA,mDAA6B;AAC7B,+CAAyB;AACzB,qDAA+B;AAa/B,SAAgB,cAAc,CAAC,EAC7B,gBAAgB,EAChB,cAAc,EACd,QAAQ,GACc;;IACtB,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE;QACtC,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;KACH;IAED,IAAI,CAAC,QAAQ,EAAE;QACb,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;KAC3D;IACD,MAAM,QAAQ,GAAG,CAAC,cAAc,CAAC;IAEjC,IAAI,CAAC,cAAc,EAAE;QACnB,cAAc,GAAG,EAAE,CAAC;KACrB;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAC1B,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CACzD,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,eAAe,0CAAE,KAAK,CAAC;IAElD,IAAI,CAAC,QAAQ,EAAE;QACb,OAAO,MAAM,CAAC;KACf;IAED,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEtE,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE;YAC5C,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG;gBACH,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;SACJ;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AA5CD,wCA4CC"} \ No newline at end of file +{"version":3,"file":"mapped-paths.js","sourceRoot":"","sources":["mapped-paths.ts"],"names":[],"mappings":";;;;AAAA,mDAA6B;AAC7B,+CAAyB;AACzB,qDAA+B;AAa/B,SAAgB,cAAc,CAAC,EAC7B,gBAAgB,EAChB,cAAc,EACd,QAAQ,GACc;;IACtB,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,cAAc,CAAC;IAEjC,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAC1B,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CACzD,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,eAAe,0CAAE,KAAK,CAAC;IAElD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEtE,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG;gBACH,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AA5CD,wCA4CC"} \ No newline at end of file diff --git a/libs/native-federation/package.json b/libs/native-federation/package.json index 18644c50..5052ac0a 100644 --- a/libs/native-federation/package.json +++ b/libs/native-federation/package.json @@ -25,6 +25,5 @@ "mrmime": "^1.0.1", "npmlog": "^6.0.2", "process": "0.11.10" - }, - "peerDependencies": {} + } } diff --git a/libs/native-federation/src/builders/build/builder.ts b/libs/native-federation/src/builders/build/builder.ts index df783120..07e72b92 100644 --- a/libs/native-federation/src/builders/build/builder.ts +++ b/libs/native-federation/src/builders/build/builder.ts @@ -50,6 +50,7 @@ import { PluginBuild } from 'esbuild'; import { FederationInfo } from '@softarc/native-federation-runtime'; import { prepareBundles } from '../../utils/prepare-bundles'; import { updateScriptTags } from '../../utils/updateIndexHtml'; +import { createI18nOptions } from '@angular-devkit/build-angular/src/utils/i18n-options'; export async function* runBuilder( nfOptions: NfBuilderSchema, @@ -62,7 +63,7 @@ export async function* runBuilder( )) as unknown as JsonObject & Schema; let builder = await context.getBuilderNameForTarget(target); - + if (builder === '@angular-devkit/build-angular:browser-esbuild') { logger.info('.: NATIVE FEDERATION - UPDATE NEEDED :.'); logger.info(''); @@ -104,6 +105,8 @@ export async function* runBuilder( options = (await context.validateOptions(_options, builder)) as JsonObject & Schema; } + const metadata = await context.getProjectMetadata(context.target.project); + const i18nOpts = createI18nOptions(metadata, options.localize); const runServer = !!nfOptions.port; const write = !runServer; @@ -117,7 +120,7 @@ export async function* runBuilder( setLogLevel(options.verbose ? 'verbose' : 'info'); - const outputPath = path.join(options.outputPath, 'browser'); + const outputPath = path.join(options.outputPath as string, 'browser'); const fedOptions: FederationOptions = { workspaceRoot: context.workspaceRoot, @@ -238,11 +241,11 @@ export async function* runBuilder( } if (write && !nfOptions.dev) { - prepareBundles(options, fedOptions, output); + prepareBundles(options, fedOptions, i18nOpts, output); } if (first && runServer) { - startServer(nfOptions, options.outputPath, memResults); + startServer(nfOptions, options.outputPath as string, memResults); } if (!first && runServer) { diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index de621f94..a1961543 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -442,6 +442,9 @@ async function runNgBuild( absWorkingDir: string | undefined = undefined, logLevel: esbuild.LogLevel = 'warning' ): Promise { + if (!entryPoints.length) { + return Promise.resolve([]); + } // unfortunately angular doesn't let us specify the out name of the enties. We'll have to map file names post-build. const entries = new Set(); for (const entryPoint of entryPoints) { @@ -465,13 +468,13 @@ async function runNgBuild( } const inputPlugins = [ - ...plugins, + ...(plugins ?? []), createSharedMappingsPlugin(mappedPaths), { name: 'fixSplitting', setup(build: esbuild.PluginBuild) { build.initialOptions.splitting = false; - build.initialOptions.chunkNames = null; + build.initialOptions.chunkNames = ''; }, }, ]; @@ -483,7 +486,7 @@ async function runNgBuild( builderOpts, context, { write: false }, - inputPlugins + { codePlugins: inputPlugins } ); let output: AngularBuildOutput; for await (output of builderRun) { diff --git a/libs/native-federation/src/utils/prepare-bundles.ts b/libs/native-federation/src/utils/prepare-bundles.ts index 507bc122..f70acb06 100644 --- a/libs/native-federation/src/utils/prepare-bundles.ts +++ b/libs/native-federation/src/utils/prepare-bundles.ts @@ -1,7 +1,7 @@ import { FederationOptions, writeFederationInfo, - writeImportMap, + writeImportMap } from '@softarc/native-federation/build'; import { AngularBuildOutput } from './angular-esbuild-adapter'; import { updateIndexHtml } from './updateIndexHtml'; @@ -14,11 +14,13 @@ import * as path from 'path'; import * as fs from 'fs'; import { Schema } from '@angular-devkit/build-angular/src/builders/application/schema'; import { FederationInfo } from '@softarc/native-federation-runtime'; +import { I18nOptions } from '@angular-devkit/build-angular/src/utils/i18n-options'; // assuming that the files have already been written export function prepareBundles( options: JsonObject & Schema, fedOptions: FederationOptions, + i18nOptions: I18nOptions, buildOutput: AngularBuildOutput ): void { const metaDataPath = path.join( @@ -29,8 +31,18 @@ export function prepareBundles( const federationInfo = JSON.parse( fs.readFileSync(metaDataPath, 'utf-8') ) as FederationInfo; - - for (const indexFile of getIndexFiles(options, buildOutput)) { + const indexFiles = getIndexFiles( + options, + fedOptions, + i18nOptions, + buildOutput + ); + // in case there is only a single locale, just update the index html, the rest is ok. + if (!i18nOptions.shouldInline) { + updateIndexHtml(fedOptions, indexFiles[0]); + return; + } + for (const indexFile of getIndexFiles(options, fedOptions, i18nOptions, buildOutput)) { updateIndexHtml(fedOptions, indexFile); addFederationInfoToBundle( cloneFederationInfo(federationInfo), @@ -42,6 +54,8 @@ export function prepareBundles( function getIndexFiles( options: JsonObject & Schema, + fedOptions: FederationOptions, + i18nOptions: I18nOptions, buildOutput: AngularBuildOutput ): BuildOutputFile[] { const indexName = @@ -49,10 +63,15 @@ function getIndexFiles( ? path.basename(options.index) : (options.index as any).output || 'index.html'; - return buildOutput.outputFiles.filter( - (file) => - file.path.endsWith(indexName) && file.type == BuildOutputFileType.Browser - ); + if (buildOutput.outputFiles) { + return buildOutput.outputFiles.filter( + (file) => + file.path.endsWith(indexName) && file.type == BuildOutputFileType.Browser + ); + } else { + return getIndexBuildOutput(indexName, i18nOptions, fedOptions); + } + } function addFederationInfoToBundle( @@ -83,3 +102,36 @@ function cloneFederationInfo(fedInfo: FederationInfo): FederationInfo { shared: fedInfo.shared.map((share) => ({ ...share })), }; } + +function getIndexBuildOutput(indexName: string, i18nOptions: I18nOptions, fedOptions: FederationOptions) { + + return Object.keys(i18nOptions.locales).map(locale => { + const pathSegments = [ + fedOptions.workspaceRoot, + fedOptions.outputPath + ]; + if (i18nOptions.shouldInline) { + pathSegments.push(locale); + } + pathSegments.push(indexName); + + return { + type: BuildOutputFileType.Browser, + path: i18nOptions.shouldInline + ? path.join(locale, indexName) + : indexName, + get text() { + return fs.readFileSync( + path.join(...pathSegments), + 'utf-8' + ); + }, + // the rest are unused anyway + contents: new Uint8Array(), + clone: function clone() { + return { ...this }; + }, + hash: '' + } + }); +} diff --git a/libs/native-federation/src/utils/updateIndexHtml.ts b/libs/native-federation/src/utils/updateIndexHtml.ts index e8af34ce..199ea190 100644 --- a/libs/native-federation/src/utils/updateIndexHtml.ts +++ b/libs/native-federation/src/utils/updateIndexHtml.ts @@ -20,7 +20,7 @@ export function updateIndexHtml( .find((f) => f.startsWith('polyfills') && f.endsWith('.js')); let indexContent = updateScriptTags(file.text, mainName, polyfillsName); - fs.writeFileSync(file.fullOutputPath, indexContent, 'utf-8'); + fs.writeFileSync(path.join(dir, path.basename(file.path)), indexContent, 'utf-8'); } export function updateScriptTags( From 1234e24a41671639bb67f0ab51f5cad91ddca84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sean=20=28J=C3=A1nos=20Mih=C3=A1ly=20Mena=29?= Date: Mon, 5 Feb 2024 12:44:33 +0100 Subject: [PATCH 08/21] stylistic changes --- .../src/builders/build/builder.ts | 18 +++---- .../src/utils/angular-esbuild-adapter.ts | 52 +++++++++--------- .../src/utils/prepare-bundles.ts | 54 +++++++++---------- .../src/utils/updateIndexHtml.ts | 12 +++-- 4 files changed, 70 insertions(+), 66 deletions(-) diff --git a/libs/native-federation/src/builders/build/builder.ts b/libs/native-federation/src/builders/build/builder.ts index 07e72b92..027b121c 100644 --- a/libs/native-federation/src/builders/build/builder.ts +++ b/libs/native-federation/src/builders/build/builder.ts @@ -54,16 +54,16 @@ import { createI18nOptions } from '@angular-devkit/build-angular/src/utils/i18n- export async function* runBuilder( nfOptions: NfBuilderSchema, - context: BuilderContext + context: BuilderContext, ): AsyncIterable { let target = targetFromTargetString(nfOptions.target); let _options = (await context.getTargetOptions( - target + target, )) as unknown as JsonObject & Schema; let builder = await context.getBuilderNameForTarget(target); - + if (builder === '@angular-devkit/build-angular:browser-esbuild') { logger.info('.: NATIVE FEDERATION - UPDATE NEEDED :.'); logger.info(''); @@ -87,7 +87,7 @@ export async function* runBuilder( let options = (await context.validateOptions( _options, - builder + builder, )) as JsonObject & Schema; const outerOptions = options as DevServerBuilderOptions; @@ -98,7 +98,7 @@ export async function* runBuilder( if (nfOptions.dev) { target = targetFromTargetString(outerOptions.buildTarget); _options = (await context.getTargetOptions( - target + target, )) as unknown as JsonObject & Schema; builder = await context.getBuilderNameForTarget(target); @@ -141,7 +141,7 @@ export async function* runBuilder( setup(build: PluginBuild) { if (build.initialOptions.platform !== 'node') { build.initialOptions.external = externals.filter( - (e) => e !== 'tslib' + (e) => e !== 'tslib', ); } }, @@ -153,7 +153,7 @@ export async function* runBuilder( const fileName = path.join( fedOptions.workspaceRoot, fedOptions.outputPath, - req.url + req.url, ); const exists = fs.existsSync(fileName); @@ -214,7 +214,7 @@ export async function* runBuilder( { buildPlugins: plugins, middleware, - } + }, ) : buildApplication(options, context, plugins); @@ -236,7 +236,7 @@ export async function* runBuilder( if (!write && output.assetFiles) { memResults.add( - output.assetFiles.map((file) => new NgCliAssetResult(file)) + output.assetFiles.map((file) => new NgCliAssetResult(file)), ); } diff --git a/libs/native-federation/src/utils/angular-esbuild-adapter.ts b/libs/native-federation/src/utils/angular-esbuild-adapter.ts index a1961543..607c4fb6 100644 --- a/libs/native-federation/src/utils/angular-esbuild-adapter.ts +++ b/libs/native-federation/src/utils/angular-esbuild-adapter.ts @@ -51,7 +51,7 @@ import { BuildOutputFile } from '@angular-devkit/build-angular/src/tools/esbuild export type MemResultHandler = ( outfiles: esbuild.OutputFile[], - outdir?: string + outdir?: string, ) => void; let _memResultHandler: MemResultHandler; @@ -68,7 +68,7 @@ export type AngularBuildOutput = BuilderOutput & { export function createAngularBuildAdapter( builderOptions: AppBuilderSchema, context: BuilderContext, - rebuildRequested: RebuildEvents = new RebuildHubs() + rebuildRequested: RebuildEvents = new RebuildHubs(), ): BuildAdapter { return async (options) => { const { @@ -96,19 +96,19 @@ export function createAngularBuildAdapter( rebuildRequested, dev, kind, - hash + hash, ); if (kind === 'shared-package') { const scriptFiles = files.filter( - (f) => f.endsWith('.js') || f.endsWith('.mjs') + (f) => f.endsWith('.js') || f.endsWith('.mjs'), ); for (const file of scriptFiles) { link(file, dev); } } - return files.map((fileName) => ({ fileName } as BuildResult)); + return files.map((fileName) => ({ fileName }) as BuildResult); } else { return await runNgBuild( builderOptions, @@ -122,7 +122,7 @@ export function createAngularBuildAdapter( rebuildRequested, dev, kind, - hash + hash, ); } }; @@ -132,7 +132,7 @@ export function createAngularBuildAdapter( try { const linkerEsm = await loadEsmModule<{ default: PluginItem }>( - '@angular/compiler-cli/linker/babel' + '@angular/compiler-cli/linker/babel', ); const linker = linkerEsm.default; @@ -178,7 +178,7 @@ async function runEsbuild( hash = false, plugins: esbuild.Plugin[] | null = null, absWorkingDir: string | undefined = undefined, - logLevel: esbuild.LogLevel = 'warning' + logLevel: esbuild.LogLevel = 'warning', ) { const projectRoot = path.dirname(tsConfigPath); const browsers = getSupportedBrowsers(projectRoot, context.logger as any); @@ -187,12 +187,12 @@ async function runEsbuild( const workspaceRoot = context.workspaceRoot; const optimizationOptions = normalizeOptimization( - builderOptions.optimization + builderOptions.optimization, ); const sourcemapOptions = normalizeSourceMaps(builderOptions.sourceMap); const tailwindConfigurationPath = await findTailwindConfigurationFile( workspaceRoot, - projectRoot + projectRoot, ); const fullProjectRoot = path.join(workspaceRoot, projectRoot); @@ -222,7 +222,7 @@ async function runEsbuild( tsConfigPath = createTsConfigForFederation( workspaceRoot, tsConfigPath, - entryPoints + entryPoints, ); const pluginOptions = createCompilerPluginOptions( @@ -242,7 +242,7 @@ async function runEsbuild( tailwindConfiguration, } as any, target, - undefined + undefined, ); pluginOptions.styleOptions.externalDependencies = []; @@ -273,7 +273,7 @@ async function runEsbuild( plugins: plugins || [ createCompilerPlugin( pluginOptions.pluginOptions, - pluginOptions.styleOptions + pluginOptions.styleOptions, // TODO: Once available, use helper functions // for creating these config objects: @@ -320,7 +320,7 @@ async function runEsbuild( entryPoints, outdir, hash, - memOnly + memOnly, ); } else { ctx.dispose(); @@ -340,7 +340,7 @@ function cleanUpTsConfigForFederation(tsConfigPath: string) { function createTsConfigForFederation( workspaceRoot: string, tsConfigPath: string, - entryPoints: EntryPoint[] + entryPoints: EntryPoint[], ) { const fullTsConfigPath = path.join(workspaceRoot, tsConfigPath); const tsconfigDir = path.dirname(fullTsConfigPath); @@ -348,14 +348,14 @@ function createTsConfigForFederation( const filtered = entryPoints .filter( (ep) => - !ep.fileName.includes('/node_modules/') && !ep.fileName.startsWith('.') + !ep.fileName.includes('/node_modules/') && !ep.fileName.startsWith('.'), ) .map((ep) => path.relative(tsconfigDir, ep.fileName).replace(/\\\\/g, '/')); const tsconfigAsString = fs.readFileSync(fullTsConfigPath, 'utf-8'); const tsconfigWithoutComments = tsconfigAsString.replace( /\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, - '' + '', ); const tsconfig = JSON.parse(tsconfigWithoutComments); @@ -378,7 +378,7 @@ function createTsConfigForFederation( function writeResult( result: Pick, 'outputFiles'>, outdir: string, - memOnly: boolean + memOnly: boolean, ) { const writtenFiles: string[] = []; @@ -409,7 +409,7 @@ function registerForRebuilds( entryPoints: EntryPoint[], outdir: string, hash: boolean, - memOnly: boolean + memOnly: boolean, ) { if (kind !== 'shared-package') { rebuildRequested.rebuild.register(async () => { @@ -421,7 +421,7 @@ function registerForRebuilds( export function loadEsmModule(modulePath: string | URL): Promise { return new Function('modulePath', `return import(modulePath);`)( - modulePath + modulePath, ) as Promise; } @@ -440,7 +440,7 @@ async function runNgBuild( hash = false, plugins: esbuild.Plugin[] | null = null, absWorkingDir: string | undefined = undefined, - logLevel: esbuild.LogLevel = 'warning' + logLevel: esbuild.LogLevel = 'warning', ): Promise { if (!entryPoints.length) { return Promise.resolve([]); @@ -486,7 +486,7 @@ async function runNgBuild( builderOpts, context, { write: false }, - { codePlugins: inputPlugins } + { codePlugins: inputPlugins }, ); let output: AngularBuildOutput; for await (output of builderRun) { @@ -500,20 +500,20 @@ async function runNgBuild( const pathBasename = path.basename(outFile.path); const name = pathBasename.replace(/(?:-[\dA-Z]{8})?\.[a-z]{2,3}$/, ''); const entry = entryPoints.find( - (ep) => path.basename(ep.fileName) == name + (ep) => path.basename(ep.fileName) == name, ); if (entry) { const nameHash = pathBasename.substring( pathBasename.lastIndexOf('-'), - pathBasename.lastIndexOf('.') + pathBasename.lastIndexOf('.'), ); const originalOutName = entry.outName.substring( 0, - entry.outName.lastIndexOf('.') + entry.outName.lastIndexOf('.'), ); outFile.path = path.join( path.dirname(outFile.path), - originalOutName + nameHash + '.js' + originalOutName + nameHash + '.js', ); } } diff --git a/libs/native-federation/src/utils/prepare-bundles.ts b/libs/native-federation/src/utils/prepare-bundles.ts index f70acb06..e887cd9f 100644 --- a/libs/native-federation/src/utils/prepare-bundles.ts +++ b/libs/native-federation/src/utils/prepare-bundles.ts @@ -1,7 +1,7 @@ import { FederationOptions, writeFederationInfo, - writeImportMap + writeImportMap, } from '@softarc/native-federation/build'; import { AngularBuildOutput } from './angular-esbuild-adapter'; import { updateIndexHtml } from './updateIndexHtml'; @@ -21,33 +21,38 @@ export function prepareBundles( options: JsonObject & Schema, fedOptions: FederationOptions, i18nOptions: I18nOptions, - buildOutput: AngularBuildOutput + buildOutput: AngularBuildOutput, ): void { const metaDataPath = path.join( fedOptions.workspaceRoot, fedOptions.outputPath, - 'remoteEntry.json' + 'remoteEntry.json', ); const federationInfo = JSON.parse( - fs.readFileSync(metaDataPath, 'utf-8') + fs.readFileSync(metaDataPath, 'utf-8'), ) as FederationInfo; const indexFiles = getIndexFiles( options, fedOptions, i18nOptions, - buildOutput + buildOutput, ); // in case there is only a single locale, just update the index html, the rest is ok. if (!i18nOptions.shouldInline) { updateIndexHtml(fedOptions, indexFiles[0]); return; } - for (const indexFile of getIndexFiles(options, fedOptions, i18nOptions, buildOutput)) { + for (const indexFile of getIndexFiles( + options, + fedOptions, + i18nOptions, + buildOutput, + )) { updateIndexHtml(fedOptions, indexFile); addFederationInfoToBundle( cloneFederationInfo(federationInfo), fedOptions, - path.dirname(indexFile.path) + path.dirname(indexFile.path), ); } } @@ -56,7 +61,7 @@ function getIndexFiles( options: JsonObject & Schema, fedOptions: FederationOptions, i18nOptions: I18nOptions, - buildOutput: AngularBuildOutput + buildOutput: AngularBuildOutput, ): BuildOutputFile[] { const indexName = typeof options.index == 'string' @@ -66,18 +71,18 @@ function getIndexFiles( if (buildOutput.outputFiles) { return buildOutput.outputFiles.filter( (file) => - file.path.endsWith(indexName) && file.type == BuildOutputFileType.Browser + file.path.endsWith(indexName) && + file.type == BuildOutputFileType.Browser, ); } else { return getIndexBuildOutput(indexName, i18nOptions, fedOptions); } - } function addFederationInfoToBundle( fedInfo: FederationInfo, fedOptions: FederationOptions, - locale: string + locale: string, ) { // shared entries are not localized. We don't copy them to locale folders to save space // but this means we have to map the items in federation info, to point up 1 level. @@ -103,13 +108,13 @@ function cloneFederationInfo(fedInfo: FederationInfo): FederationInfo { }; } -function getIndexBuildOutput(indexName: string, i18nOptions: I18nOptions, fedOptions: FederationOptions) { - - return Object.keys(i18nOptions.locales).map(locale => { - const pathSegments = [ - fedOptions.workspaceRoot, - fedOptions.outputPath - ]; +function getIndexBuildOutput( + indexName: string, + i18nOptions: I18nOptions, + fedOptions: FederationOptions, +) { + return Object.keys(i18nOptions.locales).map((locale) => { + const pathSegments = [fedOptions.workspaceRoot, fedOptions.outputPath]; if (i18nOptions.shouldInline) { pathSegments.push(locale); } @@ -117,21 +122,16 @@ function getIndexBuildOutput(indexName: string, i18nOptions: I18nOptions, fedOpt return { type: BuildOutputFileType.Browser, - path: i18nOptions.shouldInline - ? path.join(locale, indexName) - : indexName, + path: i18nOptions.shouldInline ? path.join(locale, indexName) : indexName, get text() { - return fs.readFileSync( - path.join(...pathSegments), - 'utf-8' - ); + return fs.readFileSync(path.join(...pathSegments), 'utf-8'); }, // the rest are unused anyway contents: new Uint8Array(), clone: function clone() { return { ...this }; }, - hash: '' - } + hash: '', + }; }); } diff --git a/libs/native-federation/src/utils/updateIndexHtml.ts b/libs/native-federation/src/utils/updateIndexHtml.ts index 199ea190..d1879095 100644 --- a/libs/native-federation/src/utils/updateIndexHtml.ts +++ b/libs/native-federation/src/utils/updateIndexHtml.ts @@ -5,12 +5,12 @@ import { FederationOptions } from '@softarc/native-federation/build'; export function updateIndexHtml( fedOptions: FederationOptions, - file: BuildOutputFile + file: BuildOutputFile, ) { const dir = path.join( fedOptions.workspaceRoot, fedOptions.outputPath, - path.dirname(file.path) + path.dirname(file.path), ); const mainName = fs .readdirSync(dir) @@ -20,13 +20,17 @@ export function updateIndexHtml( .find((f) => f.startsWith('polyfills') && f.endsWith('.js')); let indexContent = updateScriptTags(file.text, mainName, polyfillsName); - fs.writeFileSync(path.join(dir, path.basename(file.path)), indexContent, 'utf-8'); + fs.writeFileSync( + path.join(dir, path.basename(file.path)), + indexContent, + 'utf-8', + ); } export function updateScriptTags( indexContent: string, mainName: string, - polyfillsName: string + polyfillsName: string, ) { const htmlFragment = `