Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remote subappV1 with webpack5 module federation #1846

Merged
merged 3 commits into from
Apr 9, 2021
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
3 changes: 2 additions & 1 deletion packages/subapp-web/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ export function lazyLoadSubApp({ name, id, timeout = 15000, onLoad, onError, fal
}

if (timeout > 50 && Date.now() - startTime > timeout) {
return onError(new Error("lazyLoadSubApp Timeout"));
const msg = "lazyLoadSubApp Timeout: " + name;
return onError ? onError(new Error(msg)) : console.error(msg);
}

return load(50);
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-config-composer/lib/partial.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Partial {
compose(options) {
options = Object.assign({}, this.options, options);

const config = this.config;
const config = this.config.webpackPartial || this.config;
const configType = typeof config;

let ret;
Expand Down
69 changes: 69 additions & 0 deletions packages/xarc-app-dev/src/config/opt2/remote-federation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Options for specifying a shared module for webpack5's ModuleFederationPlugin
*
* See docs at https://webpack.js.org/concepts/module-federation/
*/
export type ModuleShareOptions = {
requiredVersion?: string;
import?: string;
shareKey?: string;
shareScope?: string;
singleton?: boolean;
eager?: boolean;
};

/**
* Options for exposing or consuming remote subapps using webpack5 ModuleFederationPlugin
*
* See docs at https://webpack.js.org/concepts/module-federation/
*/
export type RemoteSubAppOptions = {
/**
* Name of the remote entry.
*
* The name must only contain characters valid for a JavaScript variable name (identifier),
* which are `_$0-9A-Za-z`, and it can't start with numbers.
*
* Invalid characters are automatically replaced with `_`.
*
* We add `__remote_` to the beginning if you expose some modules.
*
*/
name: string;
/**
* Name the remote entry JS file
*
* If it's not specified, then one is generated like this:
*
* `_remote_~.${name}.js`
*
* Where name is the original name after replacing invalid chars with `_`.
*
*/
filename?: string;
/**
* Name of the subapps to expose
*
* Each subapp will be exposed remotely and available as `'./Name'`
* - For example, the subapp `'Deal'` would be exposed as a module `'./Deal'`
*
*/
subAppsToExpose?: string[];

/**
* Directly specify `exposes` config according to webpack5 ModuleFederationPlugin options.
*
* See https://webpack.js.org/concepts/module-federation/
*/
exposes?: Record<string, string>;
/**
* Specify share modules according to webpack5 ModuleFederationPlugin options
*
* See https://webpack.js.org/concepts/module-federation/
*
* @remark - If any modules is exposed remotely, then the option `eager` can't be true.
* So it will be set to `false`, otherwise it will be set to `true`.
*
*/
shared?: Record<string, ModuleShareOptions>;
};
15 changes: 15 additions & 0 deletions packages/xarc-app-dev/src/config/opt2/webpack-options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// webpack config collected from env-webpack.ts

import { RemoteSubAppOptions } from "./remote-federation";

/**
* User configurable options that are related to Webpack
*/
Expand Down Expand Up @@ -170,4 +172,17 @@ export type WebpackOptions = {
* You need to install ts-node package and have your tsconfig setup for your typescript.
*/
useAppWebpackConfig?: boolean;

/**
* Specify Module Federation options to expose or consume remote V1 subapps through webpack5's
* ModuleFederationPlugin. See docs at https://webpack.js.org/concepts/module-federation/
*
* TODO: Exposing SubApps remotely has these limitations:
* 1. `publicPath` must be `"auto"` to expose subapps remotely.
* 2. Due to 1, cannot use CDN for assets.
* 3. Cannot use `"single"` shared webpack `runtimeChunk` optimization.
*
* @remark this is only for subappV1
*/
v1RemoteSubApps?: RemoteSubAppOptions;
};
1 change: 1 addition & 0 deletions packages/xarc-webpack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const partials = {
dllReference: genPartials["_dll-reference"],
dllLoad: genPartials["_dll-load"],
dll: genPartials._dll,

/**
* setup a plugin to do simple text base compile progress reporting
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/xarc-webpack/src/partials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const Partial = require("webpack-config-composer/lib/partial");
const orders = [
"_base-options",
"_entry",
"_subapp-chunks",
"_node",
"_output",
"_resolve",
Expand All @@ -37,6 +36,7 @@ const orders = [
"_fail",
"_coverage",
"_dev",
"_subapp-chunks",
"_dll-entry",
"_dll-output",
"_dll-reference",
Expand All @@ -57,6 +57,7 @@ const partials = files.reduce((a, p) => {
const k = `_${p}`;
assert(orders.indexOf(k) >= 0, `No default order specified for partial ${p}`);
a[k] = new Partial(k, { config: require(`./${p}`) });
a[k].options.order = (orders.indexOf(k) + 1) * 100;
return a;
}, {});

Expand Down
2 changes: 1 addition & 1 deletion packages/xarc-webpack/src/partials/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as Path from "path";
import { loadXarcOptions } from "../util/load-xarc-options";
import * as mkdirp from "mkdirp";

module.exports = () => {
export const webpackPartial = () => {
const { babel, namespace } = loadXarcOptions();

const inspectpack = process.env.INSPECTPACK_DEBUG === "true";
Expand Down
62 changes: 56 additions & 6 deletions packages/xarc-webpack/src/partials/subapp-chunks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */

/* eslint-disable global-require, no-magic-numbers */
/* eslint-disable global-require, no-magic-numbers, max-statements */

import * as Crypto from "crypto";
import { loadXarcOptions } from "../util/load-xarc-options";
import { container } from "webpack";
import * as _ from "lodash";

const splitMap = {};

Expand All @@ -22,7 +22,7 @@ function hashChunks(mod, chunks, key) {
return `${key}.~${digest}`;
}

function makeConfig() {
function makeConfig(options) {
const { AppMode, webpack } = loadXarcOptions();

const config: any = {};
Expand All @@ -31,9 +31,59 @@ function makeConfig() {
return config;
}

let runtimeChunk = "single";

if (webpack.v1RemoteSubApps) {
let exposeRemote = 0;
const modFedPlugins = [].concat(webpack.v1RemoteSubApps).map(remote => {
const missing = [];
const subAppsToExpose = []
.concat(remote.subAppsToExpose)
.filter(x => x)
.reduce((exp, x) => {
if (!AppMode.subApps[x]) {
missing.push(x);
} else {
const subapp = AppMode.subApps[x];
exp[`./${subapp.name}`] = `./${subapp.subAppDir}/${subapp.entry}`;
}
return exp;
}, {});
if (missing.length > 0) {
throw new Error(`v1RemoteSubApps exposed subapp not found: ${missing.join(", ")}`);
}
const exposes = { ...remote.exposes, ...subAppsToExpose };
const eager = _.isEmpty(exposes);
if (!eager) {
exposeRemote++;
}
const shared = Object.keys(remote.shared).reduce((sh, x) => {
sh[x] = { ...remote.shared[x], eager };
return sh;
}, {});

const idName = remote.name.replace(/[^_\$0-9A-Za-z]/g, "_");
const name = !eager ? `__remote_${idName}` : idName;

return new container.ModuleFederationPlugin({
name,
filename: remote.filename || `_remote_~.${idName}.js`,
exposes,
shared
});
});
config.plugins = [].concat(config.plugins, modFedPlugins).filter(x => x);

// if app is exposing modules for remote loading, then we must set following
if (exposeRemote > 0) {
options.currentConfig.output.publicPath = "auto";
runtimeChunk = undefined;
}
}

if (webpack.minimizeSubappChunks) {
config.optimization = {
runtimeChunk: "single",
runtimeChunk,
splitChunks: {
cacheGroups: {
common: {
Expand All @@ -52,7 +102,7 @@ function makeConfig() {
// The filename has the pattern of hex-sum.bundle1~bundle2~bundle#.js
// https://webpack.js.org/plugins/split-chunks-plugin/
config.optimization = {
runtimeChunk: "single",
runtimeChunk,
splitChunks: {
chunks: "all",
minSize: 30 * 1024,
Expand Down
8 changes: 5 additions & 3 deletions packages/xarc-webpack/src/profile.base.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// https://stackoverflow.com/questions/40900791/cannot-redeclare-block-scoped-variable-in-unrelated-files
export {};

const profile = {
partials: {
"_base-options": { order: 100 },
_entry: { order: 200 },
"_subapp-chunks": { order: 210 },
_output: { order: 300 },
_resolve: { order: 400 },
"_resolve-loader": { order: 500 },
Expand All @@ -18,9 +16,13 @@ const profile = {
_stats: { order: 2400 },
_isomorphic: { order: 2500 },
_pwa: { order: 2600 },
// ensure this is after _dev (development profile) and _output
// because it needs to modify output.publicPath to "auto"
// for remote entry to work
"_subapp-chunks": { order: 19000 },
"_dll-load": { order: 20000 },
_node: { order: 30000 }
}
};

module.exports = profile;
export = profile;
4 changes: 3 additions & 1 deletion samples/poc-subapp-min-fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
"@xarc/app": "^8.0.0",
"@xarc/fastify-server": "^2.0.0",
"electrode-confippet": "^1.5.0",
"history": "^4.10.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "^5.1.0",
"react-router-dom": "^5.1.0",
"subapp-react": "../../packages/subapp-react",
"subapp-server": "../../packages/subapp-server",
"subapp-util": "../../packages/subapp-util"
"subapp-util": "../../packages/subapp-util",
"subapp-web": "^1.0.47"
},
"devDependencies": {
"@xarc/app-dev": "^8.0.0"
Expand Down
Loading