Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 22 additions & 39 deletions libs/native-federation/src/builders/build/builder.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as path from 'path';
import * as fs from 'fs';
import * as mrmime from 'mrmime';
import * as path from 'path';

import { ApplicationBuilderOptions, buildApplication } from '@angular/build';
import {
buildApplicationInternal,
serveWithVite,
} from '@angular/build/private';
ApplicationBuilderOptions,
buildApplication,
executeDevServerBuilder,
DevServerBuilderOptions,
} from '@angular/build';

import {
BuilderContext,
Expand All @@ -15,8 +16,6 @@ import {
targetFromTargetString,
} from '@angular-devkit/architect';

import { normalizeOptions } from '@angular-devkit/build-angular/src/builders/dev-server/options';

import {
buildForFederation,
FederationOptions,
Expand Down Expand Up @@ -49,7 +48,6 @@ import { createSharedMappingsPlugin } from '../../utils/shared-mappings-plugin';
import { updateScriptTags } from '../../utils/updateIndexHtml';
import { federationBuildNotifier } from './federation-build-notifier';
import { NfBuilderSchema } from './schema';
import { Schema as DevServerSchema } from '@angular-devkit/build-angular/src/builders/dev-server/schema';

const originalWrite = process.stderr.write.bind(process.stderr);

Expand All @@ -74,18 +72,6 @@ process.stderr.write = function (
return originalWrite(chunk, encodingOrCallback as BufferEncoding, callback);
};

function _buildApplication(options, context, pluginsOrExtensions) {
let extensions;
if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) {
extensions = {
codePlugins: pluginsOrExtensions,
};
} else {
extensions = pluginsOrExtensions;
}
return buildApplicationInternal(options, context, extensions);
}

export async function* runBuilder(
nfOptions: NfBuilderSchema,
context: BuilderContext,
Expand Down Expand Up @@ -137,17 +123,18 @@ export async function* runBuilder(
builder,
)) as JsonObject & ApplicationBuilderOptions;

let serverOptions = null;
let serverOptions: DevServerBuilderOptions | null = null;

const write = true;
const watch = nfOptions.watch;

if (options['buildTarget']) {
serverOptions = await normalizeOptions(
context,
context.target.project,
options as unknown as DevServerSchema,
);
serverOptions = {
...options,
buildTarget: options['buildTarget'],
port: nfOptions.port || options['port'],
watch: watch,
} as DevServerBuilderOptions;

target = targetFromTargetString(options['buildTarget'] as string);
targetOptions = (await context.getTargetOptions(
Expand Down Expand Up @@ -355,22 +342,18 @@ export async function* runBuilder(

options.deleteOutputPath = false;

const appBuilderName = '@angular/build:application';
await new Promise((resolve) => {
setTimeout(resolve, nfOptions.initialDelay ?? 0);
});

const builderRun = runServer
? serveWithVite(
serverOptions,
appBuilderName,
_buildApplication,
context,
nfOptions.skipHtmlTransform
? executeDevServerBuilder(serverOptions, context, {
buildPlugins: plugins as any,
middleware,
...(nfOptions.skipHtmlTransform
? {}
: { indexHtml: transformIndexHtml(nfOptions) },
{
buildPlugins: plugins as any,
middleware,
},
)
: { indexHtmlTransformer: transformIndexHtml(nfOptions) }),
})
: buildApplication(options, context, {
codePlugins: plugins as any,
indexHtmlTransformer: transformIndexHtml(nfOptions),
Expand Down
1 change: 1 addition & 0 deletions libs/native-federation/src/builders/build/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface NfBuilderSchema extends JsonObject {
port: number;
open: boolean;
rebuildDelay: number;
initialDelay?: number;
buildNotifications?: BuildNotificationOptions;
shell: string;
watch: boolean;
Expand Down
102 changes: 92 additions & 10 deletions libs/native-federation/src/utils/angular-esbuild-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import * as esbuild from 'esbuild';

import {
createCompilerPlugin,
transformSupportedBrowsersToTargets,
getSupportedBrowsers,
generateSearchDirectories,
Expand All @@ -18,11 +17,6 @@ import {

import { createCompilerPluginOptions } from './create-compiler-options';
import { BuilderContext } from '@angular-devkit/architect';

import {
normalizeOptimization,
normalizeSourceMaps,
} from '@angular-devkit/build-angular/src/utils';
import { createRequire } from 'node:module';

import { ApplicationBuilderOptions } from '@angular/build';
Expand All @@ -42,6 +36,7 @@ import { RebuildEvents, RebuildHubs } from './rebuild-events';

import JSON5 from 'json5';
import { isDeepStrictEqual } from 'node:util';
import { createAwaitableCompilerPlugin } from './create-awaitable-compiler-plugin';

export type MemResultHandler = (
outfiles: esbuild.OutputFile[],
Expand Down Expand Up @@ -270,6 +265,11 @@ async function runEsbuild(

pluginOptions.styleOptions.externalDependencies = [];

const [compilerPlugin, compilerPluginDispose] = createAwaitableCompilerPlugin(
pluginOptions.pluginOptions,
pluginOptions.styleOptions,
);

const config: esbuild.BuildOptions = {
entryPoints: entryPoints.map((ep) => ({
in: ep.fileName,
Expand All @@ -294,10 +294,7 @@ async function runEsbuild(
target: target,
logLimit: kind === 'shared-package' ? 1 : 0,
plugins: (plugins as any) || [
createCompilerPlugin(
pluginOptions.pluginOptions,
pluginOptions.styleOptions,
),
compilerPlugin,
...(mappedPaths && mappedPaths.length > 0
? [createSharedMappingsPlugin(mappedPaths)]
: []),
Expand All @@ -317,6 +314,7 @@ async function runEsbuild(
const abortHandler = async () => {
await ctx.cancel();
await ctx.dispose();
await compilerPluginDispose;
};

if (signal) {
Expand All @@ -342,6 +340,7 @@ async function runEsbuild(
} else {
if (signal) signal.removeEventListener('abort', abortHandler);
await ctx.dispose();
await compilerPluginDispose;
}
return writtenFiles;
} catch (error) {
Expand Down Expand Up @@ -486,6 +485,89 @@ export function loadEsmModule<T>(modulePath: string | URL): Promise<T> {
) as Promise<T>;
}

type OptimizationInput =
| boolean
| {
scripts?: boolean;
styles?: boolean | { minify?: boolean; inlineCritical?: boolean };
fonts?: boolean | { inline?: boolean };
};

interface NormalizedOptimization {
scripts: boolean;
styles: { minify: boolean; inlineCritical: boolean };
fonts: { inline: boolean };
}

function normalizeOptimization(
optimization: OptimizationInput = false,
): NormalizedOptimization {
if (typeof optimization === 'boolean') {
return {
scripts: optimization,
styles: { minify: optimization, inlineCritical: optimization },
fonts: { inline: optimization },
};
}

const normalizeStyles = (
styles:
| boolean
| { minify?: boolean; inlineCritical?: boolean }
| undefined,
) => {
if (typeof styles === 'boolean') {
return { minify: styles, inlineCritical: styles };
}
return {
minify: styles?.minify ?? false,
inlineCritical: styles?.inlineCritical ?? false,
};
};

const normalizeFonts = (
fonts: boolean | { inline?: boolean } | undefined,
) => {
if (typeof fonts === 'boolean') {
return { inline: fonts };
}
return { inline: fonts?.inline ?? false };
};

return {
scripts: optimization.scripts ?? false,
styles: normalizeStyles(optimization.styles),
fonts: normalizeFonts(optimization.fonts),
};
}

function normalizeSourceMaps(
sourceMap:
| boolean
| {
scripts?: boolean;
styles?: boolean;
vendor?: boolean;
hidden?: boolean;
} = false,
) {
if (typeof sourceMap === 'boolean') {
return {
scripts: sourceMap,
styles: sourceMap,
vendor: sourceMap,
hidden: false,
};
}

return {
scripts: sourceMap.scripts ?? false,
styles: sourceMap.styles ?? false,
vendor: sourceMap.vendor ?? false,
hidden: sourceMap.hidden ?? false,
};
}

//
// Usually, ngServerMode is set during bundling. However, we need to infer this
// value at runtime as we are using the same shared bundle for @angular/core
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as esbuild from 'esbuild';
import { createCompilerPlugin } from '@angular/build/private';

export function createAwaitableCompilerPlugin(
pluginOptions: any,
styleOptions: any,
): [esbuild.Plugin, Promise<void>] {
const originalPlugin = createCompilerPlugin(pluginOptions, styleOptions);

let resolveDispose: () => void;
const onDisposePromise = new Promise<void>((resolve) => {
resolveDispose = resolve;
});

const wrappedPlugin: esbuild.Plugin = {
...originalPlugin,
setup(build: esbuild.PluginBuild) {
let onDisposeCallback: (() => void | Promise<void>) | undefined;

// Wrap the build object to intercept onDispose
const wrappedBuild = new Proxy(build, {
get(target, prop) {
if (prop === 'onDispose') {
return (callback: () => void | Promise<void>) => {
onDisposeCallback = callback;
return target.onDispose(async () => {
await callback();
resolveDispose();
});
};
}
return target[prop as keyof esbuild.PluginBuild];
},
});

// Call original setup with wrapped build
return originalPlugin.setup(wrappedBuild);
},
};

return [wrappedPlugin, onDisposePromise];
}