UNPKG

@module-federation/typescript

Version:

Webpack plugin to stream typescript for module federation apps/components

239 lines 11.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FederatedTypesPlugin = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const axios_1 = __importDefault(require("axios")); const server_1 = require("../lib/server"); const TypescriptCompiler_1 = require("../lib/TypescriptCompiler"); const normalizeOptions_1 = require("../lib/normalizeOptions"); const Caching_1 = require("../lib/Caching"); const download_1 = __importDefault(require("../lib/download")); const Logger_1 = require("../Logger"); const generateTypesStats_1 = require("../lib/generateTypesStats"); const PLUGIN_NAME = 'FederatedTypesPlugin'; const SUPPORTED_PLUGINS = ['ModuleFederationPlugin', 'NextFederationPlugin']; let isServe = false; let typeDownloadCompleted = false; class FederatedTypesPlugin { constructor(options) { this.options = options; } apply(compiler) { this.logger = Logger_1.Logger.setLogger(compiler.getInfrastructureLogger(PLUGIN_NAME)); if (!compiler.options.plugins.some((p) => SUPPORTED_PLUGINS.indexOf(p?.constructor.name ?? '') !== -1)) { this.logger.error('Unable to find the Module Federation Plugin, this is plugin no longer provides it by default. Please add it to your webpack config.'); throw new Error('Unable to find the Module Federation Plugin'); } this.normalizeOptions = (0, normalizeOptions_1.normalizeOptions)(this.options, compiler); const { disableDownloadingRemoteTypes, disableTypeCompilation } = this.normalizeOptions; // Bail if both 'disableDownloadingRemoteTypes' & 'disableTypeCompilation' are 'truthy' if (disableDownloadingRemoteTypes && disableTypeCompilation) { return; } compiler.options.watchOptions.ignored = this.normalizeOptions.ignoredWatchOptions; if (!disableTypeCompilation) { compiler.hooks.beforeCompile.tap(PLUGIN_NAME, (_) => { this.generateTypes({ outputPath: compiler.outputPath }); }); this.handleTypeServing(compiler, this.normalizeOptions.typeServeOptions); // TODO - this is not ideal, but it will repopulate types if clean is enabled if (compiler.options.output.clean) { compiler.hooks.afterEmit.tap(PLUGIN_NAME, (_) => { this.generateTypes({ outputPath: compiler.outputPath }); }); } } if (!disableDownloadingRemoteTypes) { compiler.hooks.beforeCompile.tapAsync(PLUGIN_NAME, async (_, callback) => { if (typeDownloadCompleted) { callback(); return; } try { this.logger.log('Preparing to download types from remotes on startup'); await this.importRemoteTypes(); callback(); } catch (error) { callback(this.getError(error)); } }); } } handleTypeServing(compiler, typeServeOptions) { if (typeServeOptions) { compiler.hooks.watchRun.tap(PLUGIN_NAME, () => { isServe = true; }); compiler.hooks.beforeCompile.tapAsync(PLUGIN_NAME, async (_, callback) => { this.logger.log('Preparing to serve types'); try { (0, normalizeOptions_1.validateTypeServeOptions)(typeServeOptions); } catch (error) { callback(error); return; } this.logger.log('Starting Federated Types server'); await (0, server_1.startServer)({ outputPath: compiler.outputPath, host: typeServeOptions.host, port: typeServeOptions.port, logger: this.logger, }); if (!isServe) { compiler.hooks.failed.tap(PLUGIN_NAME, () => { (0, server_1.stopServer)({ port: typeServeOptions.port, logger: this.logger }); }); compiler.hooks.done.tap(PLUGIN_NAME, () => { (0, server_1.stopServer)({ port: typeServeOptions.port, logger: this.logger }); }); } callback(); }); } } generateTypes({ outputPath }) { this.logger.log('Generating types'); const federatedTypesMap = this.compileTypes(); const { typesIndexJsonFilePath, publicPath } = this.normalizeOptions; const statsJson = { publicPath, files: (0, generateTypesStats_1.generateTypesStats)(federatedTypesMap, this.normalizeOptions), }; if (Object.entries(statsJson.files).length === 0) { return; } const dest = path_1.default.join(outputPath, typesIndexJsonFilePath); fs_1.default.writeFileSync(dest, JSON.stringify(statsJson)); } compileTypes() { const exposedComponents = this.options.federationConfig.exposes; if (!exposedComponents) { return {}; } // './Component': 'path/to/component' -> ['./Component', 'path/to/component'] const compiler = new TypescriptCompiler_1.TypescriptCompiler(this.normalizeOptions); try { return compiler.generateDeclarationFiles(exposedComponents, this.options.additionalFilesToCompile); } catch (error) { this.logger.error(error); throw error; } } async delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } parseRemoteUrls(remoteComponents) { if (!remoteComponents || (remoteComponents && (0, normalizeOptions_1.isObjectEmpty)(remoteComponents))) { this.logger.log('No Remote components configured'); return []; } return Object.entries(remoteComponents).map(([remote, entry]) => { let urlEndIndex = entry.length; if (entry.endsWith('.js')) { urlEndIndex = entry.lastIndexOf('/'); } const remoteUrl = entry.substring(0, urlEndIndex); const splitIndex = remoteUrl.indexOf('@'); const url = remoteUrl.substring(splitIndex + 1); return { origin: url ?? remoteUrl, remote, }; }); } async importRemoteTypes() { const remoteUrls = this.parseRemoteUrls(this.options.federationConfig.remotes); if (remoteUrls.length === 0) { return; } for (const { origin, remote } of remoteUrls) { const { typescriptFolderName, typeFetchOptions } = this.normalizeOptions; const { shouldRetryOnTypesNotFound, downloadRemoteTypesTimeout, retryDelay, maxRetryAttempts, shouldRetry, } = typeFetchOptions; const isRetrying = shouldRetry || shouldRetryOnTypesNotFound; const maxRetryCount = !isRetrying ? 0 : maxRetryAttempts; let retryCount = 0; let delay = retryDelay; while (retryCount < maxRetryCount) { try { await this.downloadTypesFromRemote(remote, origin, downloadRemoteTypesTimeout, shouldRetryOnTypesNotFound, typescriptFolderName); break; } catch (error) { this.logger.error(`Unable to download types from remote '${remote}'`); this.logger.log(error); if (isRetrying) { retryCount++; if (retryCount < maxRetryCount) { delay = retryDelay * retryCount; this.logger.log(`Retrying download of types from remote '${remote}' in ${delay}ms`); await this.delay(delay); } } } } typeDownloadCompleted = true; } } async downloadTypesFromRemote(remote, origin, downloadRemoteTypesTimeout, shouldRetryOnTypesNotFound, typescriptFolderName) { try { this.logger.log(`Getting types index for remote '${remote}'`); const indexTypesUrl = new URL(origin); indexTypesUrl.pathname = path_1.default.join(indexTypesUrl.pathname, this.normalizeOptions.typesIndexJsonFileName); const resp = await axios_1.default.get(indexTypesUrl.toString(), { timeout: downloadRemoteTypesTimeout, }); const statsJson = resp.data; if (statsJson?.files) { this.logger.log(`Checking with Cache entries`); const { filesToCacheBust, filesToDelete } = Caching_1.TypesCache.getCacheBustedFiles(remote, statsJson); this.logger.log('filesToCacheBust', filesToCacheBust); this.logger.log('filesToDelete', filesToDelete); if (filesToDelete.length > 0) { filesToDelete.forEach((file) => { fs_1.default.unlinkSync(path_1.default.resolve(this.normalizeOptions.webpackCompilerOptions.context, typescriptFolderName, remote, file)); }); } if (filesToCacheBust.length > 0) { await Promise.all(filesToCacheBust.filter(Boolean).map((file) => { const url = new URL(path_1.default.join(origin, typescriptFolderName, file)).toString(); const destination = path_1.default.join(this.normalizeOptions.webpackCompilerOptions.context, typescriptFolderName, remote); this.logger.log('Downloading types...'); return (0, download_1.default)({ url, destination, filename: file, }); })); this.logger.log('downloading complete'); } } else { this.logger.log(`No types index found for remote '${remote}'`); if (shouldRetryOnTypesNotFound) { throw new Error(`shouldRetryOnTypesNotFound is enabled, retrying...`); } } } catch (error) { this.logger.error(`Unable to download '${remote}' remote types index file: `, error.message); throw error; } } getError(error) { if (error instanceof Error) { return error; } return new Error(error); } } exports.FederatedTypesPlugin = FederatedTypesPlugin; //# sourceMappingURL=FederatedTypesPlugin.js.map