diff --git a/README.md b/README.md index efe5bef..7018294 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ compiled remote types from other federated microapps into _src/@types/remotes_ f Global type definitions from _src/@types/*.d.ts_ are included in compilation. +Paths can be customized to meet your environment. + ## Feature comparison tables | Feature | @touk/
federated-types | ruanyl/dts-loader | ruanyl/webpack-remote-types-plugin | @module-federation/typescript | @cloudbeds/wmf-types-plugin | @@ -69,6 +71,13 @@ Or it can be added to `package.json`: } ``` +### CLI options + +| Option | Default value | Description | +|-------------------------------|---------------|--------------------------------------------------------------------------------| +| `--output-types-folder`, `-o` | `dist/@types` | Path to the output folder, absolute or relative to the working directory | +| `--global-types`, `-g` | `src/@types` | Path to project's global ambient type definitions, relative to the working dir | + ## Plugin Configuration ### Exposed modules @@ -136,14 +145,16 @@ To enable verbose logging add folowing in webpack config: ### Plugin Options -| Option | Value | Default | Description | -|-----------------------------------------:|:--------------------:|:-------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `disableTypeCompilation` | `boolean` | `false` | Disable compilation of types | -| `disableDownladingRemoteTypes` | `boolean` | `false` | Disable downloading of remote types | -| `cloudbedsRemoteManifestsBaseUrl` | `string` | `'/remotes/dev-ga'` | Base URL for remote manifest files (a.k.a remote entry configs) that is specific to Cloudbeds microapps

_ Examples:_
`http://localhost:4480/remotes/dev`
`https://cb-front.cloudbeds-dev.com/remotes/[env]`
`use-domain-name` | -| `doNotUseCloudbedsRemoteManifests` | `boolean` | `false` | Disable downloading default remote manifest files for Cloudbeds microapps (`mfd-common-remote-entry.json` and `mfd-remote-entries.json` files) and download only those provided via `remoteManifestUrls` | -| `downloadTypesWhenIdleIntervalInSeconds` | `number`, `-1` | `60` | Synchronize types continusouly - compile types after every compilation, download when idle with a specified delay value in seconds.

`-1` - disables continuous synchronization (compile and download will happen only on startup). | -| `remoteManifestUrls` | `RemoteManifestUrls` | `{}` | URLs to remote manifest files. A manifest contains a URL to a remote entry that is substituted in runtime. Multiple remote entries is supported via `registry` field.

More details available in [this section](#templated-remote-urls) | +| Setting | Value | Default | Description | +|-----------------------------------------:|:--------------------:|:--------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `dirEmittedTypes` | `string` | `@types` | Path to the output folder for emitted types, relative to the distribution folder | +| `dirGlobalTypes` | `string` | `src/@types` | Path to project's global ambient type definitions, relative to the working dir | +| `dirDownloadedTypes` | `string` | `src/@types/remotes` | Path to the output folder for downloaded types | +| `disableTypeCompilation` | `boolean` | `false` | Disable compilation of types | +| `disableDownladingRemoteTypes` | `boolean` | `false` | Disable downloading of remote types | +| `downloadTypesWhenIdleIntervalInSeconds` | `number`, `-1` | `60` | Synchronize types continusouly - compile types after every compilation, download when idle with a specified delay value in seconds.

`-1` - disables continuous synchronization (compile and download will happen only on startup). | +| `remoteManifestUrls` | `RemoteManifestUrls` | `{}` | URLs to remote manifest files. A manifest contains a URL to a remote entry that is substituted in runtime. Multiple remote entries is supported via `registry` field.

More details available in [this section](#templated-remote-urls) | +| `cloudbedsRemoteManifestsBaseUrl` | `string` | `'/remotes/dev-ga'` | Base URL for remote manifest files (a.k.a remote entry configs) that is specific to Cloudbeds microapps

_ Examples:_
`http://localhost:4480/remotes/dev`
`https://cb-front.cloudbeds-dev.com/remotes/[env]`
`use-domain-name`.

Following remote manifest files are downloaded: `mfd-common-remote-entry.json` and `mfd-remote-entries.json` files).

`remoteManifestUrls` is ignored when this setting has a value other than `undefined`. | ## Consuming remote types diff --git a/package-lock.json b/package-lock.json index 27da9a8..fd868d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "download": "^8.0.0", + "minimist": "^1.2.6", "mkdirp": "^1.0.4" }, "bin": { @@ -18,6 +19,7 @@ "devDependencies": { "@types/download": "^8.0.1", "@types/jest": "^28.1.6", + "@types/minimist": "^1.2.2", "@types/mkdirp": "^1.0.2", "jest": "^28.1.3", "ts-jest": "^28.0.7", @@ -1203,6 +1205,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "peer": true }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "node_modules/@types/mkdirp": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", @@ -3936,6 +3944,11 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -6373,6 +6386,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "peer": true }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "@types/mkdirp": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", @@ -8495,6 +8514,11 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", diff --git a/package.json b/package.json index 133794a..7316e2b 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,13 @@ }, "dependencies": { "download": "^8.0.0", + "minimist": "^1.2.6", "mkdirp": "^1.0.4" }, "devDependencies": { "@types/download": "^8.0.1", "@types/jest": "^28.1.6", + "@types/minimist": "^1.2.2", "@types/mkdirp": "^1.0.2", "jest": "^28.1.3", "ts-jest": "^28.0.7", diff --git a/src/constants.ts b/src/constants.ts index b29797c..b9b761a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,10 +1,10 @@ export const FEDERATION_CONFIG_FILE = 'federation.config.json'; -export const DIR_DIST = 'dist'; -export const DIR_EMITTED_TYPES = '@types'; +export const DEFAULT_DIR_DIST = 'dist'; +export const DEFAULT_DIR_EMITTED_TYPES = '@types'; -export const DIR_SRC_TYPES = 'src/@types'; -export const DIR_DOWNLOADED_TYPES = `${DIR_SRC_TYPES}/remotes`; +export const DEFAULT_DIR_GLOBAL_TYPES = 'src/@types'; +export const DEFAULT_DIR_DOWNLOADED_TYPES = `src/@types/remotes`; export const DEFAULT_DOWNLOAD_TYPES_INTERVAL_IN_SECONDS = 60; diff --git a/src/helpers/cloudbedsRemoteManifests.ts b/src/helpers/cloudbedsRemoteManifests.ts index 6777240..e612c65 100644 --- a/src/helpers/cloudbedsRemoteManifests.ts +++ b/src/helpers/cloudbedsRemoteManifests.ts @@ -7,7 +7,7 @@ import { import { ModuleFederationTypesPluginOptions, RemoteManifestUrls } from '../types'; export function getRemoteManifestUrls(options?: ModuleFederationTypesPluginOptions): RemoteManifestUrls | undefined { - if (!options?.doNotUseCloudbedsRemoteManifests) { + if (options?.cloudbedsRemoteManifestsBaseUrl !== undefined) { let baseUrl = options?.cloudbedsRemoteManifestsBaseUrl; if (!baseUrl || baseUrl === 'use-domain-name') { baseUrl = `${CLOUDBEDS_DEV_FRONTEND_ASSETS_DOMAIN}/remotes/dev-ga`; diff --git a/src/helpers/compileTypes.ts b/src/helpers/compileTypes.ts index f3adbf7..a13b5f7 100644 --- a/src/helpers/compileTypes.ts +++ b/src/helpers/compileTypes.ts @@ -5,7 +5,6 @@ import ts from 'typescript'; import { getLogger } from './logger'; -import { DIR_SRC_TYPES } from '../constants'; import { CompileTypesResult, FederationConfig } from '../types'; import { getAllFilePaths } from './files'; @@ -27,7 +26,7 @@ export function reportCompileDiagnostic(diagnostic: ts.Diagnostic): void { logger.log(' at', `${diagnostic.file!.fileName}:${line + 1}`, '\n'); } -export function compileTypes(exposedComponents: string[], outFile: string): CompileTypesResult { +export function compileTypes(exposedComponents: string[], outFile: string, dirGlobalTypes: string): CompileTypesResult { const logger = getLogger(); const exposedFileNames = Object.values(exposedComponents); @@ -44,8 +43,8 @@ export function compileTypes(exposedComponents: string[], outFile: string): Comp host.writeFile = (_fileName: string, contents: string) => fileContent = contents; // Including global type definitions from `src/@types` directory - if (fs.existsSync(DIR_SRC_TYPES)) { - exposedFileNames.push(...getAllFilePaths(`./${DIR_SRC_TYPES}`).filter(path => path.endsWith('.d.ts'))); + if (fs.existsSync(dirGlobalTypes)) { + exposedFileNames.push(...getAllFilePaths(`./${dirGlobalTypes}`).filter(path => path.endsWith('.d.ts'))); } logger.log('Including a set of root files in compilation', exposedFileNames); diff --git a/src/helpers/downloadTypes.ts b/src/helpers/downloadTypes.ts index b0feafb..a38af7e 100644 --- a/src/helpers/downloadTypes.ts +++ b/src/helpers/downloadTypes.ts @@ -3,7 +3,6 @@ import fs from 'fs'; import mkdirp from 'mkdirp'; import path from 'path'; -import { DIR_DOWNLOADED_TYPES, DIR_EMITTED_TYPES } from '../constants'; import { RemoteManifest, RemoteManifestUrls, RemotesRegistryManifest } from '../types'; import { getLogger } from './logger'; @@ -15,10 +14,10 @@ async function downloadRemoteEntryManifest(url: string): Promise { return JSON.parse(json); } -async function downloadRemoteEntryTypes(remoteName: string, dtsUrl: string): Promise { +async function downloadRemoteEntryTypes(remoteName: string, dtsUrl: string, dirDownloadedTypes: string): Promise { const logger = getLogger(); const types = (await download(dtsUrl, downloadOptions)).toString(); - const outDir = path.join(DIR_DOWNLOADED_TYPES, remoteName); + const outDir = path.join(dirDownloadedTypes, remoteName); const outFile = path.join(outDir, 'index.d.ts'); let shouldWriteFile = true; @@ -71,6 +70,8 @@ export async function downloadRemoteEntryURLsFromManifests(remoteManifestUrls?: } export async function downloadTypes( + dirEmittedTypes: string, + dirDownloadedTypes: string, remotes: Record, remoteManifestUrls?: RemoteManifestUrls, ): Promise { @@ -91,8 +92,13 @@ export async function downloadTypes( try { const remoteEntryUrl = remoteEntryURLs[remoteName] || remoteLocation.split('@')[1]; const remoteEntryBaseUrl = remoteEntryUrl.split('/').slice(0, -1).join('/'); + const promiseDownload = downloadRemoteEntryTypes( + remoteName, + `${remoteEntryBaseUrl}/${dirEmittedTypes}/index.d.ts`, + dirDownloadedTypes, + ) - promises.push(downloadRemoteEntryTypes(remoteName, `${remoteEntryBaseUrl}/${DIR_EMITTED_TYPES}/index.d.ts`)); + promises.push(promiseDownload); } catch (err) { logger.error(`${remoteName}: '${remoteLocation}' is not a valid remote federated module URL`); logger.log(err); diff --git a/src/make-federated-types.ts b/src/make-federated-types.ts index 1302be1..a2ecf03 100644 --- a/src/make-federated-types.ts +++ b/src/make-federated-types.ts @@ -1,21 +1,31 @@ #!/usr/bin/env node +import parseArgs from 'minimist'; import path from 'path'; -import { DIR_DIST, DIR_EMITTED_TYPES } from './constants'; +import { DEFAULT_DIR_DIST, DEFAULT_DIR_EMITTED_TYPES, DEFAULT_DIR_GLOBAL_TYPES } from './constants'; import { compileTypes, rewritePathsWithExposedFederatedModules } from './helpers/compileTypes'; import { assertRunningFromRoot, getFederationConfig } from './helpers/cli'; assertRunningFromRoot(); +const argv = parseArgs(process.argv.slice(2), { + alias: { + 'global-types': 'g', + 'output-types-folder': 'o', + }, +}); + const federationConfig = getFederationConfig(); const compileFiles = Object.values(federationConfig.exposes); -const outFile = path.join(DIR_DIST, DIR_EMITTED_TYPES, 'index.d.ts'); +const outDir = argv['output-types-folder'] || path.join(DEFAULT_DIR_DIST, DEFAULT_DIR_EMITTED_TYPES); +const outFile = path.join(outDir, 'index.d.ts'); +const dirGlobalTypes = argv['global-types'] || DEFAULT_DIR_GLOBAL_TYPES; console.log(`Emitting types for ${compileFiles.length} exposed module(s)`); -const { isSuccess, typeDefinitions } = compileTypes(compileFiles as string[], outFile); +const { isSuccess, typeDefinitions } = compileTypes(compileFiles as string[], outFile, dirGlobalTypes); if (!isSuccess) { process.exit(1); } diff --git a/src/plugin.spec.ts b/src/plugin.spec.ts index d5764f7..d4feea7 100644 --- a/src/plugin.spec.ts +++ b/src/plugin.spec.ts @@ -3,7 +3,7 @@ import webpack, { Compilation, Compiler } from 'webpack'; import { downloadTypes } from './helpers/downloadTypes'; import { ModuleFederationTypesPlugin } from './plugin'; import { ModuleFederationPluginOptions, ModuleFederationTypesPluginOptions } from './types'; -import { CloudbedsMicrofrontend } from './constants'; +import { CloudbedsMicrofrontend, DEFAULT_DIR_DOWNLOADED_TYPES, DEFAULT_DIR_EMITTED_TYPES } from './constants'; jest.mock('./helpers/downloadTypes'); @@ -64,6 +64,8 @@ describe('ModuleFederationTypesPlugin', () => { installPlugin(moduleFederationPluginOptions, typesPluginOptions); expect(mockDownloadTypes.mock.calls[0]).toEqual([ + DEFAULT_DIR_EMITTED_TYPES, + DEFAULT_DIR_DOWNLOADED_TYPES, moduleFederationPluginOptions.remotes, typesPluginOptions.remoteManifestUrls, ]); diff --git a/src/plugin.ts b/src/plugin.ts index 2316b0a..bba2042 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,9 +3,11 @@ import { Compiler, WebpackPluginInstance } from 'webpack'; import { CLOUDBEDS_DEPLOYMENT_ENV_WITH_DISABLED_REMOTE_TYPES_DOWNLOAD, + DEFAULT_DIR_DIST, + DEFAULT_DIR_DOWNLOADED_TYPES, + DEFAULT_DIR_EMITTED_TYPES, + DEFAULT_DIR_GLOBAL_TYPES, DEFAULT_DOWNLOAD_TYPES_INTERVAL_IN_SECONDS, - DIR_DIST, - DIR_EMITTED_TYPES } from './constants'; import { getRemoteManifestUrls } from './helpers/cloudbedsRemoteManifests'; import { compileTypes, rewritePathsWithExposedFederatedModules } from './helpers/compileTypes'; @@ -55,12 +57,18 @@ export class ModuleFederationTypesPlugin implements WebpackPluginInstance { // Define path for the emitted typings file const { exposes, remotes } = federationPluginOptions; - const distPath = compiler.options.devServer?.static?.directory || compiler.options.output?.path || DIR_DIST; - const outFile = path.join(distPath, DIR_EMITTED_TYPES, 'index.d.ts'); + + const dirDist = compiler.options.devServer?.static?.directory + || compiler.options.output?.path + || DEFAULT_DIR_DIST; + const dirEmittedTypes = this.options?.dirEmittedTypes || DEFAULT_DIR_EMITTED_TYPES; + const dirGlobalTypes = this.options?.dirGlobalTypes || DEFAULT_DIR_GLOBAL_TYPES; + const dirDownloadedTypes = this.options?.dirDownloadedTypes || DEFAULT_DIR_DOWNLOADED_TYPES; + const outFile = path.join(dirDist, dirEmittedTypes, 'index.d.ts'); // Create types for exposed modules const compileTypesHook = () => { - const { isSuccess, typeDefinitions } = compileTypes(exposes as string[], outFile); + const { isSuccess, typeDefinitions } = compileTypes(exposes as string[], outFile, dirGlobalTypes); if (isSuccess) { rewritePathsWithExposedFederatedModules(federationPluginOptions as FederationConfig, outFile, typeDefinitions); } else { @@ -70,7 +78,7 @@ export class ModuleFederationTypesPlugin implements WebpackPluginInstance { // Import types from remote modules const downloadTypesHook = async () => { - return downloadTypes(remotes as Record, remoteManifestUrls); + return downloadTypes(dirEmittedTypes, dirDownloadedTypes, remotes as Record, remoteManifestUrls); }; // Determine whether compilation of types should be performed continuously diff --git a/src/types.ts b/src/types.ts index 10d5a4c..ddce022 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,10 +26,14 @@ export type RemoteManifestUrls = Record<'registry' | string, string>; export type ModuleFederationPluginOptions = ConstructorParameters[0]; export type ModuleFederationTypesPluginOptions = { - cloudbedsRemoteManifestsBaseUrl?: string | 'use-domain-name', + dirEmittedTypes?: string, + dirGlobalTypes?: string; + dirDownloadedTypes?: string; + disableDownladingRemoteTypes?: boolean, disableTypeCompilation?: boolean, - doNotUseCloudbedsRemoteManifests?: boolean, - remoteManifestUrls?: RemoteManifestUrls, downloadTypesWhenIdleIntervalInSeconds?: number, + remoteManifestUrls?: RemoteManifestUrls, + + cloudbedsRemoteManifestsBaseUrl?: string | 'use-domain-name', }