Skip to content

Commit

Permalink
feat(nf): improved version handling angular-architects#399
Browse files Browse the repository at this point in the history
  • Loading branch information
JBBianchi committed Dec 6, 2023
1 parent a1dc238 commit e56c593
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 58 deletions.
212 changes: 212 additions & 0 deletions libs/native-federation-runtime/import-shim.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
interface ESMSInitOptions {
/**
* Enable Shim Mode
*/
shimMode?: boolean;

/**
* Enable polyfill features.
* Currently supports ['css-modules', 'json-modules']
*/
polyfillEnable?: Array<'css-modules' | 'json-modules'>

/**
* #### Enforce Integrity
*
* Set to *true* to enable secure mode to not support loading modules without integrity (integrity is always verified though).
*
*/
enforceIntegrity?: boolean;

/**
* Nonce for CSP build
*/
nonce?: boolean;

/**
* Disable retriggering of document readystate
*/
noLoadEventRetriggers?: true;

/**
* #### Skip Processing Stability
*
* > Non-spec feature
*
* When loading modules that you know will only use baseline modules
* features, it is possible to set a rule to explicitly opt-out modules
* from rewriting. This improves performance because those modules then do
* not need to be processed or transformed at all, so that only local
* application code is handled and not library code.
*
* This can be configured by setting the importShim.skip URL regular
* expression:
*
* ```js
* importShim.skip = /^https:\/\/cdn\.com/;
* ```
*
* By default, this expression supports jspm.dev, dev.jspm.io and
* cdn.pika.dev.
*/
skip?: RegExp | string[] | string

/**
* #### Error hook
*
* Register a callback for any ES Module Shims module errors.
*
*/
onerror?: (e: any) => any;

/**
* #### Polyfill hook
*
* Register a callback invoked when polyfill mode first engages.
*
*/
onpolyfill?: () => void;

/**
* #### Resolve Hook
*
* Only supported in Shim Mode.
*
* Provide a custom resolver function.
*/
resolve?: (
id: string,
parentUrl: string,
resolve: (id: string, parentUrl: string) => string
) => string | Promise<string>;

/**
* #### Fetch Hook
*
* Only supported in Shim Mode.
*
* > Stability: Non-spec feature
*
* This is provided as a convenience feature since the pipeline handles
* the same data URL rewriting and circular handling of the module graph
* that applies when trying to implement any module transform system.
*
* The ES Module Shims fetch hook can be used to implement transform
* plugins.
*
* For example:
*
* ```js
* importShim.fetch = async function (url) {
* const response = await fetch(url);
* if (response.url.endsWith('.ts')) {
* const source = await response.body();
* const transformed = tsCompile(source);
* return new Response(new Blob([transformed], { type: 'application/javascript' }));
* }
* return response;
* };
* ```
*
* Because the dependency analysis applies by ES Module Shims takes care
* of ensuring all dependencies run through the same fetch hook, the above
* is all that is needed to implement custom plugins.
*
* Streaming support is also provided, for example here is a hook with
* streaming support for JSON:
*
* ```js
* importShim.fetch = async function (url) {
* const response = await fetch(url);
* if (!response.ok)
* throw new Error(`${response.status} ${response.statusText} ${response.url}`);
* const contentType = response.headers.get('content-type');
* if (!/^application\/json($|;)/.test(contentType))
* return response;
* const reader = response.body.getReader();
* return new Response(new ReadableStream({
* async start (controller) {
* let done, value;
* controller.enqueue(new Uint8Array([...'export default '].map(c => c.charCodeAt(0))));
* while (({ done, value } = await reader.read()) && !done) {
* controller.enqueue(value);
* }
* controller.close();
* }
* }), {
* status: 200,
* headers: {
* "Content-Type": "application/javascript"
* }
* });
* }
* ```
*/
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;

/**
* #### Revoke Blob URLs
*
* Set to *true* to cleanup blob URLs from memory after execution.
* Can cost some compute time for large loads.
*
*/
revokeBlobURLs?: boolean;

/**
* #### Map Overrides
*
* Set to *true* to permit overrides to import maps.
*
*/
mapOverrides?: boolean;

/**
* #### Meta hook
*
* Register a callback for import.meta construction.
*
*/
meta?: (meta: any, url: string) => void;

/**
* #### On import hook
*
* Register a callback for top-level imports.
*
*/
onimport?: (url: string, options: any, parentUrl: string) => void;
}

interface ImportMap {
imports: Record<string, string>;
scopes: Record<string, Record<string, string>>;
}

/**
* Dynamic import(...) within any modules loaded will be rewritten as
* importShim(...) automatically providing full support for all es-module-shims
* features through dynamic import.
*
* To load code dynamically (say from the browser console), importShim can be
* called similarly:
*
* ```js
* importShim('/path/to/module.js').then(x => console.log(x));
* ```
*/
declare function importShim<Default, Exports extends object>(
specifier: string,
parentUrl?: string
): Promise<{ default: Default } & Exports>;

declare namespace importShim {
const resolve: (id: string, parentURL?: string) => string;
const addImportMap: (importMap: Partial<ImportMap>) => void;
const getImportMap: () => ImportMap;
}

interface Window {
esmsInitOptions?: ESMSInitOptions;
importShim: typeof importShim;
}
38 changes: 22 additions & 16 deletions libs/native-federation-runtime/src/lib/init-federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import {
ImportMap,
mergeImportMaps,
} from './model/import-map';
import { getExternalUrl, setExternalUrl } from './model/externals';
import { joinPaths, getDirectory } from './utils/path-utils';
import { addRemote } from './model/remotes';
import { appendImportMap } from './utils/add-import-map';
import { FederationInfo } from './model/federation-info';
import * as semver from 'semver';

export async function initFederation(
remotesOrManifestUrl: Record<string, string> | string = {}
Expand All @@ -19,12 +18,10 @@ export async function initFederation(
: remotesOrManifestUrl;

const hostImportMap = await processHostInfo();
importShim.addImportMap(hostImportMap);
const remotesImportMap = await processRemoteInfos(remotes);

const importMap = mergeImportMaps(hostImportMap, remotesImportMap);
appendImportMap(importMap);

return importMap;
importShim.addImportMap(remotesImportMap);
return importShim.getImportMap();
}

async function loadManifest(remotes: string): Promise<Record<string, string>> {
Expand Down Expand Up @@ -92,12 +89,24 @@ function processRemoteImports(
): Scopes {
const scopes: Scopes = {};
const scopedImports: Imports = {};
const importMap = importShim.getImportMap();

for (const shared of remoteInfo.shared) {
const outFileName =
getExternalUrl(shared) ?? joinPaths(baseUrl, shared.outFileName);
setExternalUrl(shared, outFileName);
scopedImports[shared.packageName] = outFileName;
let isImported = false;
if (shared.singleton) {
try {
const importedURL = new URL(importMap.imports?.[shared.packageName]);
const version = importedURL.searchParams.get('version');
if (version) {
isImported = semver.satisfies(version, shared.requiredVersion);
}
}
catch {}
}
if (!isImported) {
const outFileName = joinPaths(baseUrl, shared.outFileName);
scopedImports[shared.packageName] = outFileName;
}
}

scopes[baseUrl + '/'] = scopedImports;
Expand All @@ -124,12 +133,9 @@ async function processHostInfo(): Promise<ImportMap> {
const hostInfo = await loadFederationInfo('./remoteEntry.json');

const imports = hostInfo.shared.reduce(
(acc, cur) => ({ ...acc, [cur.packageName]: './' + cur.outFileName }),
(acc, cur) => ({ ...acc, [cur.packageName]: cur.version ? `./${cur.outFileName}?version=${cur.version}` : `./${cur.outFileName}` }),
{}
) as Imports;

for (const shared of hostInfo.shared) {
setExternalUrl(shared, './' + shared.outFileName);
}

return { imports, scopes: {} };
}
13 changes: 5 additions & 8 deletions libs/native-federation-runtime/src/lib/load-remote-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { appendImportMap } from './utils/add-import-map';
import { processRemoteInfo } from './init-federation';
import {
getRemote,
Expand All @@ -8,22 +7,20 @@ import {
} from './model/remotes';
import { getDirectory, joinPaths } from './utils/path-utils';

declare function importShim<T>(url: string): T;

export type LoadRemoteModuleOptions = {
remoteEntry?: string;
remoteName?: string;
exposedModule: string;
};

export async function loadRemoteModule<T = any>(
export async function loadRemoteModule<T extends object = any>(
options: LoadRemoteModuleOptions
): Promise<T>;
export async function loadRemoteModule<T = any>(
export async function loadRemoteModule<T extends object = any>(
remoteName: string,
exposedModule: string
): Promise<T>;
export async function loadRemoteModule<T = any>(
export async function loadRemoteModule<T extends object = any>(
optionsOrRemoteName: LoadRemoteModuleOptions | string,
exposedModule?: string
): Promise<T> {
Expand All @@ -47,7 +44,7 @@ export async function loadRemoteModule<T = any>(
}

const url = joinPaths(remote.baseUrl, exposed.outFileName);
const module = await importShim<T>(url);
const module = await importShim<T, T>(url);

return module;
}
Expand Down Expand Up @@ -80,7 +77,7 @@ async function ensureRemoteInitialized(
!isRemoteInitialized(getDirectory(options.remoteEntry))
) {
const importMap = await processRemoteInfo(options.remoteEntry);
appendImportMap(importMap);
importShim.addImportMap(importMap);
}
}

Expand Down
17 changes: 0 additions & 17 deletions libs/native-federation-runtime/src/lib/model/externals.ts

This file was deleted.

10 changes: 0 additions & 10 deletions libs/native-federation-runtime/src/lib/utils/add-import-map.ts

This file was deleted.

9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e56c593

Please sign in to comment.