Skip to content

Commit

Permalink
fix: workaround to self ref module error in prod (#1205)
Browse files Browse the repository at this point in the history
* fix: workaround to self ref module error in prod

* tests: update tests

* fix: bad remote imports

* chore(utils): release version 2.0.6

* chore(node): release version 1.0.7

* chore(nextjs-mf): release version 7.0.8

* chore(storybook-addon): release version 1.0.0
  • Loading branch information
ScriptedAlchemy authored Aug 14, 2023
1 parent 0233842 commit 1d88beb
Show file tree
Hide file tree
Showing 15 changed files with 545 additions and 28 deletions.
2 changes: 1 addition & 1 deletion apps/3000-home/remote-delegate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import importDelegatedModule from "@module-federation/nextjs-mf/importDelegatedModule";
import { importDelegatedModule } from "@module-federation/utilities/src/utils/importDelegatedModule";
/* eslint-disable no-undef */
// eslint-disable-next-line no-async-promise-executor
module.exports = new Promise(async (resolve, reject) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/3001-shop/remote-delegate.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { importDelegatedModule } from "@module-federation/utilities/src/utils/importDelegatedModule";
/* eslint-disable no-undef */
// eslint-disable-next-line no-async-promise-executor
module.exports = new Promise(async (resolve, reject) => {
//eslint-disable-next-line
const currentRequest = new URLSearchParams(__resourceQuery).get('remote');

const [global, url] = currentRequest.split('@');
const {importDelegatedModule} = await import("@module-federation/nextjs-mf/importDelegatedModule")

importDelegatedModule({
global,
Expand Down
18 changes: 6 additions & 12 deletions apps/3002-checkout/remote-delegate.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { importDelegatedModule } from "@module-federation/utilities/src/utils/importDelegatedModule";
/* eslint-disable no-undef */

// Delegates are currently not used in this example, but are left here for testing.
// eslint-disable-next-line no-async-promise-executor
module.exports = new Promise(async (resolve, reject) => {
//eslint-disable-next-line
// console.log('Delegate being called for', __resourceQuery);
//eslint-disable-next-line
const currentRequest = new URLSearchParams(__resourceQuery).get('remote');
const { importDelegatedModule } = await import(
'@module-federation/nextjs-mf/importDelegatedModule'
);

const [global, url] = currentRequest.split('@');

Expand All @@ -18,11 +12,11 @@ module.exports = new Promise(async (resolve, reject) => {
url: url + '?' + Date.now(),
})
.then(async (remote) => {
// console.log(
// __resourceQuery,
// 'resolved remote from',
// __webpack_runtime_id__
// );
console.log(
__resourceQuery,
'resolved remote from',
__webpack_runtime_id__
);

resolve(remote);
})
Expand Down
15 changes: 15 additions & 0 deletions packages/nextjs-mf/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).

## [7.0.8](https://github.com/module-federation/nextjs-mf/compare/nextjs-mf-7.0.7...nextjs-mf-7.0.8) (2023-08-14)

### Dependency Updates

* `utils` updated to version `2.0.6`
* `node` updated to version `1.0.7`
* `utils` updated to version `2.0.6`
* `node` updated to version `1.0.7`

### Bug Fixes

* workaround to self ref module error in prod ([e134caa](https://github.com/module-federation/nextjs-mf/commit/e134caa9a914da6a226e73dc877a108456b1053f))



## [7.0.7](https://github.com/module-federation/nextjs-mf/compare/nextjs-mf-7.0.6...nextjs-mf-7.0.7) (2023-08-11)

### Dependency Updates
Expand Down
3 changes: 0 additions & 3 deletions packages/nextjs-mf/importDelegatedModule.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/nextjs-mf/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@module-federation/nextjs-mf",
"version": "7.0.7",
"version": "7.0.8",
"license": "MIT",
"main": "src/index.js",
"types": "src/index.d.ts",
Expand Down
286 changes: 285 additions & 1 deletion packages/nextjs-mf/src/default-delegate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,288 @@
import { importDelegatedModule } from '@module-federation/utilities/src/utils/importDelegatedModule'
import {
AsyncContainer,
RemoteVars,
RuntimeRemote,
RuntimeRemotesMap,
WebpackRemoteContainer
} from '@module-federation/utilities/src/types/index';

let pure = {} as RemoteVars;
try {
// @ts-ignore
pure = process.env['REMOTES'] || {};
} catch (e) {
// not in webpack bundle
}
const remoteVars = pure as RemoteVars;

const extractUrlAndGlobal = (urlAndGlobal: string): [string, string] => {
const index = urlAndGlobal.indexOf('@');
if (index <= 0 || index === urlAndGlobal.length - 1) {
throw new Error(`Invalid request "${urlAndGlobal}"`);
}
return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)];
};

const loadScript = (keyOrRuntimeRemoteItem: string | RuntimeRemote) => {
const runtimeRemotes = getRuntimeRemotes();

// 1) Load remote container if needed
let asyncContainer: RuntimeRemote['asyncContainer'];
const reference =
typeof keyOrRuntimeRemoteItem === 'string'
? runtimeRemotes[keyOrRuntimeRemoteItem]
: keyOrRuntimeRemoteItem;

if (reference.asyncContainer) {
asyncContainer =
typeof reference.asyncContainer.then === 'function'
? reference.asyncContainer
: // @ts-ignore
reference.asyncContainer();
} else {
// This casting is just to satisfy typescript,
// In reality remoteGlobal will always be a string;
const remoteGlobal = reference.global as unknown as string;

// Check if theres an override for container key if not use remote global
const containerKey = reference.uniqueKey
? (reference.uniqueKey as unknown as string)
: remoteGlobal;

const __webpack_error__ = new Error() as Error & {
type: string;
request: string | null;
};

// @ts-ignore
if (!globalThis.__remote_scope__) {
// create a global scope for container, similar to how remotes are set on window in the browser
// @ts-ignore
globalThis.__remote_scope__ = {
// @ts-ignore
_config: {},
};
}
// @ts-ignore
const globalScope =
// @ts-ignore
typeof window !== 'undefined' ? window : globalThis.__remote_scope__;

if (typeof window === 'undefined') {
globalScope['_config'][containerKey] = reference.url;
} else {
// to match promise template system, can be removed once promise template is gone
if (!globalScope['remoteLoading']) {
globalScope['remoteLoading'] = {};
}
if (globalScope['remoteLoading'][containerKey]) {
return globalScope['remoteLoading'][containerKey];
}
}
// @ts-ignore
asyncContainer = new Promise(function (resolve, reject) {
function resolveRemoteGlobal() {
const asyncContainer = globalScope[
remoteGlobal
] as unknown as AsyncContainer;
return resolve(asyncContainer);
}

if (typeof globalScope[remoteGlobal] !== 'undefined') {
return resolveRemoteGlobal();
}

(__webpack_require__ as any).l(
reference.url,
function (event: Event) {
if (typeof globalScope[remoteGlobal] !== 'undefined') {
return resolveRemoteGlobal();
}

const errorType =
event && (event.type === 'load' ? 'missing' : event.type);
const realSrc =
event && event.target && (event.target as HTMLScriptElement).src;

__webpack_error__.message =
'Loading script failed.\n(' +
errorType +
': ' +
realSrc +
' or global var ' +
remoteGlobal +
')';

__webpack_error__.name = 'ScriptExternalLoadError';
__webpack_error__.type = errorType;
__webpack_error__.request = realSrc;

reject(__webpack_error__);
},
containerKey
);
}).catch(function (err) {
console.error('container is offline, returning fake remote');
console.error(err);

return {
fake: true,
// @ts-ignore
get: (arg) => {
console.warn('faking', arg, 'module on, its offline');

return Promise.resolve(() => {
return {
__esModule: true,
default: () => {
return null;
},
};
});
},
//eslint-disable-next-line
init: () => {},
};
});
if (typeof window !== 'undefined') {
globalScope['remoteLoading'][containerKey] = asyncContainer;
}
}

return asyncContainer;
};

const getRuntimeRemotes = () => {
try {
const runtimeRemotes = Object.entries(remoteVars).reduce(function (
acc,
item
) {
const [key, value] = item;
// if its an object with a thenable (eagerly executing function)
if (typeof value === 'object' && typeof value.then === 'function') {
acc[key] = { asyncContainer: value };
}
// if its a function that must be called (lazily executing function)
else if (typeof value === 'function') {
// @ts-ignore
acc[key] = { asyncContainer: value };
}
// if its a delegate module, skip it
else if (typeof value === 'string' && value.startsWith('internal ')) {
const [request, query] = value.replace('internal ', '').split('?');
if (query) {
const remoteSyntax = new URLSearchParams(query).get('remote');
if (remoteSyntax) {
const [url, global] = extractUrlAndGlobal(remoteSyntax);
acc[key] = { global, url };
}
}
}
// if its just a string (global@url)
else if (typeof value === 'string') {
const [url, global] = extractUrlAndGlobal(value);
acc[key] = { global, url };
}
// we dont know or currently support this type
else {
//@ts-ignore
console.warn('remotes process', process.env.REMOTES);
throw new Error(
`[mf] Invalid value received for runtime_remote "${key}"`
);
}
return acc;
},
{} as RuntimeRemotesMap);

return runtimeRemotes;
} catch (err) {
console.warn('Unable to retrieve runtime remotes: ', err);
}

return {} as RuntimeRemotesMap;
};



const importDelegatedModule = async (
keyOrRuntimeRemoteItem: string | RuntimeRemote
) => {
// @ts-ignore
return loadScript(keyOrRuntimeRemoteItem)
.then((asyncContainer: WebpackRemoteContainer) => {
return asyncContainer;
})
.then((asyncContainer: WebpackRemoteContainer) => {
// most of this is only needed because of legacy promise based implementation
// can remove proxies once we remove promise based implementations
if (typeof window === 'undefined') {
if (!Object.hasOwnProperty.call(keyOrRuntimeRemoteItem, 'globalThis')) {
return asyncContainer;
}

// return asyncContainer;

//TODO: need to solve chunk flushing with delegated modules
return {
get: function (arg: string) {
//@ts-ignore
return asyncContainer.get(arg).then((f) => {
const m = f();
const result = {
__esModule: m.__esModule,
};
for (const prop in m) {
if (typeof m[prop] === 'function') {
Object.defineProperty(result, prop, {
get: function () {
return function () {
//@ts-ignore
if (globalThis.usedChunks)
//@ts-ignore
globalThis.usedChunks.add(
//@ts-ignore
`${keyOrRuntimeRemoteItem.global}->${arg}`
);
//eslint-disable-next-line prefer-rest-params
return m[prop](...arguments);
};
},
enumerable: true,
});
} else {
Object.defineProperty(result, prop, {
get: () => {
//@ts-ignore
if (globalThis.usedChunks)
//@ts-ignore
globalThis.usedChunks.add(
//@ts-ignore
`${keyOrRuntimeRemoteItem.global}->${arg}`
);

return m[prop];
},
enumerable: true,
});
}
}

if (m.then) {
return Promise.resolve(() => result);
}

return () => result;
});
},
init: asyncContainer.init,
};
} else {
return asyncContainer;
}
});
};

// eslint-disable-next-line no-async-promise-executor
module.exports = new Promise(async (resolve, reject) => {
Expand Down
8 changes: 8 additions & 0 deletions packages/node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).

## [1.0.7](https://github.com/module-federation/nextjs-mf/compare/node-1.0.6...node-1.0.7) (2023-08-14)

### Dependency Updates

* `utils` updated to version `2.0.6`
* `utils` updated to version `2.0.6`


## [1.0.6](https://github.com/module-federation/nextjs-mf/compare/node-1.0.5...node-1.0.6) (2023-08-11)

### Dependency Updates
Expand Down
Loading

0 comments on commit 1d88beb

Please sign in to comment.