UNPKG

@module-federation/storybook-addon

Version:

Storybook addon to consume remote module federated apps/components

100 lines (98 loc) 4.43 kB
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