Skip to content
Merged
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
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,19 +527,19 @@ export default withNativeFederation({
requiredVersion: "auto",
})
.skip(["rxjs/ajax", "rxjs/fetch"])
.override({ "large-lib": { singleton: false } })
.get(),
.override({ "large-lib": { singleton: false } }),
});
```

The builder exposes:

| Method | Purpose |
| ------------------------ | -------------------------------------------------------------------- |
| `.filter(patterns)` | Narrow the shared dependencies to those matching the patterns. |
| `.skip(externals)` | Add packages to the skip list, on top of the ones seeded by default. |
| `.override(externals)` | Replace the sharing options for specific packages. |
| `.patch(externals, cfg)` | Merge a partial config into the given packages. |
| `.get()` | Resolve the builder into the shared config object. |
| `.get()` | Resolve the builder into the shared config object (optional). |

Unlike the core `fromPackageJson`, this adapter's version pre-seeds the Angular skip list (`NG_SKIP_LIST`) — the same list `shareAll` uses — so Angular-internal and localization packages are skipped for you out of the box.

Expand Down Expand Up @@ -572,18 +572,19 @@ export default withNativeFederation({
strictVersion: true,
})
.filter(["@my-org/ui/*", "@my-org/auth-lib"])
.patch(["@my-org/ui/*"], { singleton: false })
.get(),
.patch(["@my-org/ui/*"], { singleton: false }),
});
```

| Method | Purpose |
| ----------------------- | ------------------------------------------------------------------------------ |
| `.filter(patterns)` | Narrow the selection. Omit it to select every mapped path. |
| `.patch(patterns, cfg)` | Merge a partial config into the matching mappings; never widens the selection. |
| `.get()` | Resolve the builder into the `sharedMappings` array. |
| `.get()` | Resolve the builder into the `sharedMappings` array (optional). |

Requires `@softarc/native-federation` ≥ `4.4.0`. See the [core README](https://github.com/native-federation/native-federation-core#configuring-shared-mappings) for which `ExternalConfig` properties a mapping honours, how `includeSecondaries: { keepAll: true, resolveGlob: true }` keeps mappings nothing imports, and why only barrel imports can be shared as a mapped path.
Since `@softarc/native-federation` `4.7.0`, `shared` and `sharedMappings` accept the builders directly, so the trailing `.get()` can be dropped. Add `// @ts-check` at the top of `federation.config.mjs` to have your editor check the config against the exported `FederationConfig` type.

Requires `@softarc/native-federation` ≥ `4.4.0`. See the [core README](https://github.com/native-federation/native-federation-core#configuring-shared-mappings) for which `ExternalConfig` properties a mapping honours, how `includeSecondaries: { keepAll: true, resolveGlob: true }` keeps mappings nothing imports, and why only barrel imports can be shared as a mapped path. Note that with `ignoreUnusedDeps: false` a wildcard mapping (`@my-org/ui/*`) is dropped unless it sets `includeSecondaries: { resolveGlob: true }`: without the pruning scan nothing expands the wildcard into entry points.

### SSR and Hydration

Expand Down
9 changes: 6 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ export {
} from './config/share-utils.js';
// Nothing Angular-specific to add: the skip list NG_SKIP_LIST seeds applies to npm
// packages, not to workspace path mappings.
export {
mappingsFromWorkspace,
type FederationConfig,
export { mappingsFromWorkspace } from '@softarc/native-federation/config';
export type {
ExternalConfig,
FederationConfig,
SharedExternalsConfig,
SharedMappingEntry,
} from '@softarc/native-federation/config';
export { NG_SKIP_LIST } from './config/angular-skip-list.js';
export { shareAngularLocales } from './config/angular-locales.js';
13 changes: 13 additions & 0 deletions src/config/share-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ describe('withNativeFederation', () => {
expect(mockCoreWithNativeFederation.mock.calls[0]![0].platform).toBe('browser');
});

it('infers the platform from a shared builder passed without get()', () => {
const resolved = { '@angular/ssr': {} };
const builder = { get: vi.fn(() => resolved) };

withNativeFederation({ shared: builder } as never);

const passed = mockCoreWithNativeFederation.mock.calls[0]![0];
expect(passed.platform).toBe('node');
// resolved once here, so core receives the plain object rather than calling get() again
expect(passed.shared).toBe(resolved);
expect(builder.get).toHaveBeenCalledTimes(1);
});

it('does not override an explicitly configured platform', () => {
withNativeFederation({ platform: 'node', shared: { '@angular/core': {} } } as never);

Expand Down
34 changes: 26 additions & 8 deletions src/config/share-utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type {
ConfigBuilder,
FederationConfig,
PackageJsonExternalsBuilder,
ResolvedSharedExternalsConfig,
ShareAllExternalsOptions,
ShareExternalsOptions,
SkipList,
FederationConfig,
} from "@softarc/native-federation/domain";
import {
share as coreShare,
Expand All @@ -11,7 +14,10 @@ import {
withNativeFederation as coreWithNativeFederation,
} from "@softarc/native-federation/config";
import { NG_SKIP_LIST } from "./angular-skip-list.js";
import type { NormalizedSharedExternalsConfig } from "@softarc/native-federation/internal";
import type {
NormalizedFederationConfig,
NormalizedSharedExternalsConfig,
} from "@softarc/native-federation/internal";
import { existsSync, readFileSync } from "node:fs";
import * as path from "node:path";
import { cwd } from "node:process";
Expand All @@ -23,16 +29,16 @@ export function shareAll(
projectPath?: string;
overrides?: ShareExternalsOptions;
} = {},
) {
): ResolvedSharedExternalsConfig {
if (!opts.skipList) opts.skipList = NG_SKIP_LIST;
return coreShareAll(config, opts);
}

export function share(
configuredShareObjects: ShareExternalsOptions,
projectPath = "",
skipList = NG_SKIP_LIST,
) {
skipList: SkipList = NG_SKIP_LIST,
): ResolvedSharedExternalsConfig {
return coreShare(configuredShareObjects, projectPath, skipList);
}

Expand All @@ -45,13 +51,17 @@ export function share(
export function fromPackageJson(
baseCfg: ShareAllExternalsOptions,
projectPath = "",
) {
): PackageJsonExternalsBuilder {
return coreFromPackageJson(baseCfg, projectPath).skip(NG_SKIP_LIST);
}

export function withNativeFederation(cfg: FederationConfig) {
if (!cfg.platform)
export function withNativeFederation(
cfg: FederationConfig,
): NormalizedFederationConfig {
if (!cfg.platform) {
cfg.shared = fromBuilder(cfg.shared);
cfg.platform = getDefaultPlatform(Object.keys(cfg.shared ?? {}));
}

const normalized = coreWithNativeFederation(cfg);

Expand Down Expand Up @@ -148,6 +158,14 @@ export function autoShareScope(opts: PackageShareScopeOptions = {}): string {
return `${prefix}${major}.${minor}.${patch}`;
}

function fromBuilder<T>(
value: T | ConfigBuilder<T> | undefined,
): T | undefined {
return typeof (value as ConfigBuilder<T> | undefined)?.get === "function"
? (value as ConfigBuilder<T>).get()
: (value as T | undefined);
}

function removeNgLocales(
shared: NormalizedSharedExternalsConfig,
): NormalizedSharedExternalsConfig {
Expand Down
18 changes: 5 additions & 13 deletions src/schematics/init/files/federation.config.mjs__tmpl__
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { withNativeFederation, shareAll } from '@angular-architects/native-federation/config';
import { withNativeFederation, fromPackageJson } from '@angular-architects/native-federation/config';

export default withNativeFederation({
name: '<%=project%>',
Expand All @@ -9,18 +9,10 @@ export default withNativeFederation({
'./Component': './<%=appComponentPath%>',
},
<% } %>
shared: {
...shareAll(
{ singleton: true, strictVersion: true, requiredVersion: 'auto', build: 'package' },
{
overrides: {
// includeSecondaries is an opt-out of ignoreUnusedDeps, so all of
// @angular/core is shared to prevent mismatches.
'@angular/core': { singleton: true, strictVersion: true, requiredVersion: 'auto', build: 'package', includeSecondaries: { keepAll: true } },
},
},
),
},
shared: fromPackageJson({ singleton: true, strictVersion: true, requiredVersion: 'auto', build: 'package' })
// includeSecondaries is an opt-out of ignoreUnusedDeps, so all of
// @angular/core is shared to prevent mismatches.
.patch(['@angular/core'], { includeSecondaries: { keepAll: true } }),

skip: [
'rxjs/ajax',
Expand Down
Loading