@module-federation/storybook-addon
Version:
Storybook addon to consume remote module federated apps/components
100 lines (98 loc) • 4.43 kB
JavaScript
import { correctImportPath } from "../utils/correctImportPath.js";
import fs from "fs";
import { dirname, join } from "path";
import * as process from "process";
import VirtualModulesPlugin from "webpack-virtual-modules";
import { container } from "webpack";
import { normalizeStories } from "@storybook/core/common";
//#region src/lib/storybook-addon.ts
async function getLogger() {
try {
return (await import("storybook/internal/node-logger")).logger;
} catch {
return (await import("@storybook/node-logger")).logger;
}
}
const { ModuleFederationPlugin } = container;
const withModuleFederation = async (options) => {
return (await import("../utils/with-module-federation.js")).default(options);
};
const webpack = async (webpackConfig, options) => {
const { plugins = [], context: webpackContext } = webpackConfig;
const { moduleFederationConfig, presets, nxModuleFederationConfig } = options;
const context = webpackContext || process.cwd();
const logger = await getLogger();
const webpackVersion = await presets.apply("webpackVersion");
logger.info(`=> [MF] Webpack ${webpackVersion} version detected`);
if (webpackVersion !== "5") throw new Error("Webpack 5 required: Configure Storybook to use the webpack5 builder");
if (nxModuleFederationConfig) {
logger.info(`=> [MF] Detect NX configuration`);
const wmf = await withModuleFederation(nxModuleFederationConfig);
webpackConfig = {
...webpackConfig,
...wmf(webpackConfig)
};
}
if (moduleFederationConfig) {
logger.info(`=> [MF] Push Module Federation plugin`);
plugins.push(new ModuleFederationPlugin(moduleFederationConfig));
}
const bootstrap = (await presets.apply("entries")).map((entryFile) => `import '${correctImportPath(context, entryFile)}';`);
const index = plugins.findIndex((plugin) => plugin?.constructor.name === "VirtualModulesPlugin");
if (index !== -1) {
logger.info(`=> [MF] Detected plugin VirtualModulesPlugin`);
const virtualEntries = plugins[index].getModuleList("static");
const virtualEntriesPaths = Object.keys(virtualEntries);
logger.info(`=> [MF] Write files from VirtualModulesPlugin`);
for (const virtualEntryPath of virtualEntriesPaths) {
const nodeModulesPath = "/node_modules/";
const filePathFromProjectRootDir = virtualEntryPath.replace(context, "");
let sourceCode = virtualEntries[virtualEntryPath];
let finalPath = virtualEntryPath;
let finalDir = dirname(virtualEntryPath);
if (!filePathFromProjectRootDir.startsWith(nodeModulesPath)) {
finalPath = join(context, nodeModulesPath, ".cache", "storybook", filePathFromProjectRootDir);
finalDir = dirname(finalPath);
if (filePathFromProjectRootDir === "/generated-stories-entry.cjs" || filePathFromProjectRootDir === "/storybook-stories.js") {
const isStorybookVersion7 = filePathFromProjectRootDir === "/storybook-stories.js";
normalizeStories(await presets.apply("stories"), {
configDir: options.configDir,
workingDir: context
}).forEach((story) => {
const newDirectory = join("..", "..", "..", story.directory);
const oldSrc = isStorybookVersion7 ? `'${story.directory}/'` : `'${story.directory}'`;
const newSrc = isStorybookVersion7 ? `'${newDirectory}/'` : `'${newDirectory}'`;
sourceCode = sourceCode.replace(oldSrc, newSrc);
});
}
}
if (!fs.existsSync(finalDir)) fs.mkdirSync(finalDir, { recursive: true });
fs.writeFileSync(finalPath, sourceCode);
bootstrap.push(`import '${correctImportPath(context, finalPath)}';`);
}
}
/**
* Create a new VirtualModulesPlugin plugin to fix error "Shared module is not available for eager consumption"
* Entry file content is moved in bootstrap file. More details in the webpack documentation:
* https://webpack.js.org/concepts/module-federation/#uncaught-error-shared-module-is-not-available-for-eager-consumption
* */
const virtualModulePlugin = new VirtualModulesPlugin({
"./__entry.js": `import('./__bootstrap.js');`,
"./__bootstrap.js": bootstrap.join("\n")
});
let action = "Push";
if (index === -1) plugins.push(virtualModulePlugin);
else {
plugins[index] = virtualModulePlugin;
action = "Replace";
}
logger.info(`=> [MF] ${action} plugin VirtualModulesPlugin to bootstrap entry point`);
return {
...webpackConfig,
entry: ["./__entry.js"],
plugins
};
};
//#endregion
export { webpack, withModuleFederation };
//# sourceMappingURL=storybook-addon.js.map