diff --git a/packages/angular/package.json b/packages/angular/package.json index e7bd942fbc4e0..79d1567ed0dce 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -21,7 +21,8 @@ "./generators": "./generators.js", "./executors": "./executors.js", "./tailwind": "./tailwind.js", - "./src/generators/utils": "./src/generators/utils/index.js" + "./src/generators/utils": "./src/generators/utils/index.js", + "./module-federation": "./src/utils/mfe/with-module-federation.js" }, "author": "Victor Savkin", "license": "MIT", diff --git a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts index e2a41906e9340..2e026d72d7286 100644 --- a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts +++ b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts @@ -69,22 +69,21 @@ function buildAppWithCustomWebpackConfiguration( pathToWebpackConfig, options.tsConfig ); + // The extra Webpack configuration file can also export a Promise, for instance: + // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, + // then await will just resolve that object. + const config = await customWebpackConfiguration; + // The extra Webpack configuration file can export a synchronous or asynchronous function, // for instance: `module.exports = async config => { ... }`. - if (typeof customWebpackConfiguration === 'function') { + if (typeof config === 'function') { return customWebpackConfiguration( baseWebpackConfig, options, context.target ); } else { - return merge( - baseWebpackConfig, - // The extra Webpack configuration file can also export a Promise, for instance: - // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, - // then await will just resolve that object. - await customWebpackConfiguration - ); + return merge(baseWebpackConfig, config); } }, }); diff --git a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts index 1ae772c0dc436..3563c979dfd7c 100644 --- a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts +++ b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts @@ -55,22 +55,21 @@ export function webpackServer(schema: Schema, context: BuilderContext) { pathToWebpackConfig, buildTarget.options.tsConfig ); + // The extra Webpack configuration file can also export a Promise, for instance: + // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, + // then await will just resolve that object. + const config = await customWebpackConfiguration; + // The extra Webpack configuration file can export a synchronous or asynchronous function, // for instance: `module.exports = async config => { ... }`. - if (typeof customWebpackConfiguration === 'function') { - return customWebpackConfiguration( + if (typeof config === 'function') { + return config( baseWebpackConfig, selectedConfiguration, context.target ); } else { - return merge( - baseWebpackConfig, - // The extra Webpack configuration file can also export a Promise, for instance: - // `module.exports = new Promise(...)`. If it exports a single object, but not a Promise, - // then await will just resolve that object. - await customWebpackConfiguration - ); + return merge(baseWebpackConfig, config); } }, } diff --git a/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap b/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap new file mode 100644 index 0000000000000..9b085650596bf --- /dev/null +++ b/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`withModuleFederation should collect dependencies correctly 1`] = ` +Array [ + ModuleFederationPlugin { + "_options": Object { + "exposes": Object { + "./Module": "apps/remote1/src/module.ts", + }, + "filename": "remoteEntry.mjs", + "library": Object { + "type": "module", + }, + "name": "remote1", + "remotes": Object {}, + "shared": Object { + "@angular/core": Object { + "requiredVersion": "~13.2.0", + "singleton": true, + "strictVersion": true, + }, + "core": Object { + "eager": undefined, + "requiredVersion": false, + }, + "shared": Object { + "eager": undefined, + "requiredVersion": false, + }, + }, + }, + }, + NormalModuleReplacementPlugin { + "newResource": [Function], + "resourceRegExp": /\\./, + }, +] +`; + +exports[`withModuleFederation should create a host config correctly 1`] = ` +Array [ + ModuleFederationPlugin { + "_options": Object { + "exposes": undefined, + "filename": "remoteEntry.mjs", + "library": Object { + "type": "module", + }, + "name": "host", + "remotes": Object { + "remote1": "http:/localhost:4201/remoteEntry.mjs", + }, + "shared": Object { + "@angular/core": Object { + "requiredVersion": "~13.2.0", + "singleton": true, + "strictVersion": true, + }, + "shared": Object { + "eager": undefined, + "requiredVersion": false, + }, + }, + }, + }, + NormalModuleReplacementPlugin { + "newResource": [Function], + "resourceRegExp": /\\./, + }, +] +`; + +exports[`withModuleFederation should create a remote config correctly 1`] = ` +Array [ + ModuleFederationPlugin { + "_options": Object { + "exposes": Object { + "./Module": "apps/remote1/src/module.ts", + }, + "filename": "remoteEntry.mjs", + "library": Object { + "type": "module", + }, + "name": "remote1", + "remotes": Object {}, + "shared": Object { + "@angular/core": Object { + "requiredVersion": "~13.2.0", + "singleton": true, + "strictVersion": true, + }, + "shared": Object { + "eager": undefined, + "requiredVersion": false, + }, + }, + }, + }, + NormalModuleReplacementPlugin { + "newResource": [Function], + "resourceRegExp": /\\./, + }, +] +`; + +exports[`withModuleFederation should map dependencies from project name to import name 1`] = ` +Array [ + ModuleFederationPlugin { + "_options": Object { + "exposes": Object { + "./Module": "apps/remote1/src/module.ts", + }, + "filename": "remoteEntry.mjs", + "library": Object { + "type": "module", + }, + "name": "remote1", + "remotes": Object {}, + "shared": Object { + "@angular/core": Object { + "requiredVersion": "~13.2.0", + "singleton": true, + "strictVersion": true, + }, + "@myorg/core": Object { + "eager": undefined, + "requiredVersion": false, + }, + "shared": Object { + "eager": undefined, + "requiredVersion": false, + }, + }, + }, + }, + NormalModuleReplacementPlugin { + "newResource": [Function], + "resourceRegExp": /\\./, + }, +] +`; diff --git a/packages/angular/src/utils/mfe-webpack.spec.ts b/packages/angular/src/utils/mfe/mfe-webpack.spec.ts similarity index 98% rename from packages/angular/src/utils/mfe-webpack.spec.ts rename to packages/angular/src/utils/mfe/mfe-webpack.spec.ts index 66d9122722d1e..e1a5cbdb80e9f 100644 --- a/packages/angular/src/utils/mfe-webpack.spec.ts +++ b/packages/angular/src/utils/mfe/mfe-webpack.spec.ts @@ -18,8 +18,8 @@ describe('MFE Webpack Utils', () => { shareWorkspaceLibraries(['@myorg/shared']); } catch (error) { // ASSERT - expect(error.message).toEqual( - 'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)' + expect(error.message).toContain( + 'NX MFE: TsConfig Path for workspace libraries does not exist!' ); } }); diff --git a/packages/angular/src/utils/mfe-webpack.ts b/packages/angular/src/utils/mfe/mfe-webpack.ts similarity index 83% rename from packages/angular/src/utils/mfe-webpack.ts rename to packages/angular/src/utils/mfe/mfe-webpack.ts index 87d23cec47379..314680e0a6538 100644 --- a/packages/angular/src/utils/mfe-webpack.ts +++ b/packages/angular/src/utils/mfe/mfe-webpack.ts @@ -1,14 +1,24 @@ -import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { existsSync, readFileSync } from 'fs'; import { NormalModuleReplacementPlugin } from 'webpack'; import { appRootPath as rootPath } from 'nx/src/utils/app-root'; import { normalizePath, joinPathFragments } from '@nrwl/devkit'; import { dirname } from 'path'; import { ParsedCommandLine } from 'typescript'; +import { + getRootTsConfigPath, + readTsConfig, +} from '@nrwl/workspace/src/utilities/typescript'; + +export interface SharedLibraryConfig { + singleton: boolean; + strictVersion: boolean; + requiredVersion: string; + eager: boolean; +} export function shareWorkspaceLibraries( libraries: string[], - tsConfigPath = process.env.NX_TSCONFIG_PATH + tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath() ) { if (!existsSync(tsConfigPath)) { throw new Error( @@ -22,7 +32,7 @@ export function shareWorkspaceLibraries( if (!tsconfigPathAliases) { return { getAliases: () => [], - getLibraries: () => [], + getLibraries: () => {}, getReplacementPlugin: () => new NormalModuleReplacementPlugin(/./, () => {}), }; @@ -45,7 +55,7 @@ export function shareWorkspaceLibraries( (aliases, library) => ({ ...aliases, [library.name]: library.path }), {} ), - getLibraries: (eager?: boolean) => + getLibraries: (eager?: boolean): Record => pathMappings.reduce( (libraries, library) => ({ ...libraries, @@ -72,7 +82,9 @@ export function shareWorkspaceLibraries( }; } -export function sharePackages(packages: string[]) { +export function sharePackages( + packages: string[] +): Record { const pkgJsonPath = joinPathFragments(rootPath, 'package.json'); if (!existsSync(pkgJsonPath)) { throw new Error( diff --git a/packages/angular/src/utils/mfe/with-module-federation.spec.ts b/packages/angular/src/utils/mfe/with-module-federation.spec.ts new file mode 100644 index 0000000000000..d58c70bfce638 --- /dev/null +++ b/packages/angular/src/utils/mfe/with-module-federation.spec.ts @@ -0,0 +1,243 @@ +jest.mock('fs'); +jest.mock('@nrwl/workspace/src/core/project-graph'); +jest.mock('@nrwl/workspace/src/utilities/typescript'); +jest.mock('@nrwl/workspace/src/core/file-utils'); +jest.mock('nx/src/shared/workspace'); +import * as graph from '@nrwl/workspace/src/core/project-graph'; +import * as typescriptUtils from '@nrwl/workspace/src/utilities/typescript'; +import * as workspace from '@nrwl/workspace/src/core/file-utils'; +import * as taoWorkspace from 'nx/src/shared/workspace'; +import * as fs from 'fs'; + +import { withModuleFederation } from './with-module-federation'; + +describe('withModuleFederation', () => { + afterEach(() => jest.clearAllMocks()); + it('should create a host config correctly', async () => { + // ARRANGE + (graph.readCachedProjectGraph as jest.Mock).mockReturnValue({ + dependencies: { + host: [ + { target: 'npm:@angular/core' }, + { target: 'npm:zone.js' }, + { target: 'shared' }, + ], + }, + }); + + (workspace.readWorkspaceJson as jest.Mock).mockReturnValue({ + projects: { + remote1: { + targets: { + serve: { + options: { + publicHost: 'http://localhost:4201', + }, + }, + }, + }, + }, + }); + + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue( + JSON.stringify({ + dependencies: { + '@angular/core': '~13.2.0', + }, + }) + ); + + (typescriptUtils.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: { + shared: ['/libs/shared/src/index.ts'], + }, + }, + }); + + (taoWorkspace.Workspaces as jest.Mock).mockReturnValue({ + readWorkspaceConfiguration: () => ({ + projects: { + shared: { + sourceRoot: '/libs/shared/src/', + }, + }, + }), + }); + + // ACT + const config = ( + await withModuleFederation({ + name: 'host', + remotes: ['remote1'], + }) + )({}); + + // ASSERT + expect(config.plugins).toMatchSnapshot(); + }); + + it('should create a remote config correctly', async () => { + // ARRANGE + (graph.readCachedProjectGraph as jest.Mock).mockReturnValue({ + dependencies: { + remote1: [ + { target: 'npm:@angular/core' }, + { target: 'npm:zone.js' }, + { target: 'shared' }, + ], + }, + }); + + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue( + JSON.stringify({ + dependencies: { + '@angular/core': '~13.2.0', + }, + }) + ); + + (typescriptUtils.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: { + shared: ['/libs/shared/src/index.ts'], + }, + }, + }); + + (taoWorkspace.Workspaces as jest.Mock).mockReturnValue({ + readWorkspaceConfiguration: () => ({ + projects: { + shared: { + sourceRoot: '/libs/shared/src/', + }, + }, + }), + }); + + // ACT + const config = ( + await withModuleFederation({ + name: 'remote1', + exposes: { './Module': 'apps/remote1/src/module.ts' }, + }) + )({}); + + // ASSERT + expect(config.plugins).toMatchSnapshot(); + }); + + it('should collect dependencies correctly', async () => { + // ARRANGE + (graph.readCachedProjectGraph as jest.Mock).mockReturnValue({ + dependencies: { + remote1: [ + { target: 'npm:@angular/core' }, + { target: 'npm:zone.js' }, + { target: 'core' }, + ], + core: [{ target: 'shared' }], + }, + }); + + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue( + JSON.stringify({ + dependencies: { + '@angular/core': '~13.2.0', + }, + }) + ); + + (typescriptUtils.readTsConfig as jest.Mock).mockReturnValue({ + options: { + paths: { + shared: ['/libs/shared/src/index.ts'], + core: ['/libs/core/src/index.ts'], + }, + }, + }); + + (taoWorkspace.Workspaces as jest.Mock).mockReturnValue({ + readWorkspaceConfiguration: () => ({ + projects: { + shared: { + sourceRoot: '/libs/shared/src/', + }, + core: { + sourceRoot: '/libs/core/src/', + }, + }, + }), + }); + + // ACT + const config = ( + await withModuleFederation({ + name: 'remote1', + exposes: { './Module': 'apps/remote1/src/module.ts' }, + }) + )({}); + + // ASSERT + expect(config.plugins).toMatchSnapshot(); + }); + + it('should map dependencies from project name to import name', async () => { + // ARRANGE + (graph.readCachedProjectGraph as jest.Mock).mockReturnValue({ + dependencies: { + remote1: [ + { target: 'npm:@angular/core' }, + { target: 'npm:zone.js' }, + { target: 'core' }, + ], + core: [{ target: 'shared' }], + }, + }); + + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue( + JSON.stringify({ + dependencies: { + '@angular/core': '~13.2.0', + }, + }) + ); + + (typescriptUtils.readTsConfig as jest.Mock).mockImplementation(() => ({ + options: { + paths: { + shared: ['/libs/shared/src/index.ts'], + '@myorg/core': ['/libs/core/src/index.ts'], + }, + }, + })); + + (taoWorkspace.Workspaces as jest.Mock).mockReturnValue({ + readWorkspaceConfiguration: () => ({ + projects: { + shared: { + sourceRoot: '/libs/shared/src/', + }, + core: { + sourceRoot: '/libs/core/src/', + }, + }, + }), + }); + + // ACT + const config = ( + await withModuleFederation({ + name: 'remote1', + exposes: { './Module': 'apps/remote1/src/module.ts' }, + }) + )({}); + + // ASSERT + expect(config.plugins).toMatchSnapshot(); + }); +}); diff --git a/packages/angular/src/utils/mfe/with-module-federation.ts b/packages/angular/src/utils/mfe/with-module-federation.ts new file mode 100644 index 0000000000000..82df11f0cb335 --- /dev/null +++ b/packages/angular/src/utils/mfe/with-module-federation.ts @@ -0,0 +1,245 @@ +import { + SharedLibraryConfig, + sharePackages, + shareWorkspaceLibraries, +} from './mfe-webpack'; +import { + createProjectGraphAsync, + readCachedProjectGraph, +} from '@nrwl/workspace/src/core/project-graph'; +import { readWorkspaceJson } from '@nrwl/workspace/src/core/file-utils'; +import { joinPathFragments, ProjectGraph } from '@nrwl/devkit'; +import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); +import { Workspaces } from 'nx/src/shared/workspace'; +import { appRootPath } from 'nx/src/utils/app-root'; +import { + getRootTsConfigPath, + readTsConfig, +} from '@nrwl/workspace/src/utilities/typescript'; +import { ParsedCommandLine } from 'typescript'; + +export type MFERemotes = string[] | [remoteName: string, remoteUrl: string][]; + +export interface MFEConfig { + name: string; + remotes?: MFERemotes; + exposes?: Record; + shared?: ( + libraryName: string, + library: SharedLibraryConfig + ) => SharedLibraryConfig | false; +} + +function recursivelyResolveWorkspaceDependents( + projectGraph: ProjectGraph, + target: string, + seenTargets: Set = new Set() +) { + if (seenTargets.has(target)) { + return []; + } + let dependencies = [target]; + seenTargets.add(target); + + const workspaceDependencies = ( + projectGraph.dependencies[target] ?? [] + ).filter((dep) => !dep.target.startsWith('npm:')); + if (workspaceDependencies.length > 0) { + for (const dep of workspaceDependencies) { + dependencies = [ + ...dependencies, + ...recursivelyResolveWorkspaceDependents( + projectGraph, + dep.target, + seenTargets + ), + ]; + } + } + + return dependencies; +} + +function mapWorkspaceLibrariesToTsConfigImport(workspaceLibraries: string[]) { + const { projects } = new Workspaces(appRootPath).readWorkspaceConfiguration(); + const tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath(); + const tsConfig: ParsedCommandLine = readTsConfig(tsConfigPath); + + const tsconfigPathAliases: Record = tsConfig.options?.paths; + + if (!tsconfigPathAliases) { + return workspaceLibraries; + } + + const mappedLibraries = []; + for (const lib of workspaceLibraries) { + const sourceRoot = projects[lib].sourceRoot; + let found = false; + + for (const [key, value] of Object.entries(tsconfigPathAliases)) { + if (value.find((p) => p.startsWith(sourceRoot))) { + mappedLibraries.push(key); + found = true; + break; + } + } + + if (!found) { + mappedLibraries.push(lib); + } + } + + return mappedLibraries; +} + +async function getDependentPackagesForProject(name: string) { + let projectGraph: ProjectGraph; + try { + projectGraph = readCachedProjectGraph(); + } catch (e) { + projectGraph = await createProjectGraphAsync(); + } + + const deps = projectGraph.dependencies[name].reduce( + (dependencies, dependency) => { + const workspaceLibraries = new Set(dependencies.workspaceLibraries); + const npmPackages = new Set(dependencies.npmPackages); + + if (dependency.target.startsWith('npm:')) { + npmPackages.add(dependency.target.replace('npm:', '')); + } else { + workspaceLibraries.add(dependency.target); + } + + return { + workspaceLibraries: [...workspaceLibraries], + npmPackages: [...npmPackages], + }; + }, + { workspaceLibraries: [], npmPackages: [] } + ); + const seenWorkspaceLibraries = new Set(); + deps.workspaceLibraries = deps.workspaceLibraries.reduce( + (workspaceLibraryDeps, workspaceLibrary) => [ + ...workspaceLibraryDeps, + ...recursivelyResolveWorkspaceDependents( + projectGraph, + workspaceLibrary, + seenWorkspaceLibraries + ), + ], + [] + ); + + deps.workspaceLibraries = mapWorkspaceLibrariesToTsConfigImport( + deps.workspaceLibraries + ); + return deps; +} + +function determineRemoteUrl(remote: string) { + const workspace = readWorkspaceJson(); + let publicHost = ''; + try { + publicHost = workspace.projects[remote].targets.serve.options.publicHost; + } catch (error) { + throw new Error( + `Cannot automatically determine URL of remote (${remote}). Looked for property "publicHost" in the project's "serve" target.\n + You can also use the tuple syntax in your webpack config to configure your remotes. e.g. \`remotes: [['remote1', 'http://localhost:4201']]\`` + ); + } + return joinPathFragments(publicHost, 'remoteEntry.mjs'); +} + +function mapRemotes(remotes: MFERemotes) { + const mappedRemotes = {}; + + for (const remote of remotes) { + if (Array.isArray(remote)) { + mappedRemotes[remote[0]] = remote[1]; + } else if (typeof remote === 'string') { + mappedRemotes[remote] = determineRemoteUrl(remote); + } + } + + return mappedRemotes; +} + +export async function withModuleFederation(options: MFEConfig) { + const DEFAULT_NPM_PACKAGES_TO_AVOID = ['zone.js']; + + const dependencies = await getDependentPackagesForProject(options.name); + const sharedLibraries = shareWorkspaceLibraries( + dependencies.workspaceLibraries + ); + + const npmPackages = sharePackages( + dependencies.npmPackages.filter( + (pkg) => !DEFAULT_NPM_PACKAGES_TO_AVOID.includes(pkg) + ) + ); + + const sharedDependencies = { + ...sharedLibraries.getLibraries(), + ...npmPackages, + }; + + if (options.shared) { + for (const [libraryName, library] of Object.entries(sharedDependencies)) { + const mappedDependency = options.shared(libraryName, library); + if (mappedDependency === false) { + delete sharedDependencies[libraryName]; + continue; + } else if (!mappedDependency) { + continue; + } + + sharedDependencies[libraryName] = mappedDependency; + } + } + + const mappedRemotes = + !options.remotes || options.remotes.length === 0 + ? {} + : mapRemotes(options.remotes); + + return (config) => ({ + ...(config ?? {}), + output: { + ...(config.output ?? {}), + uniqueName: options.name, + publicPath: 'auto', + }, + optimization: { + ...(config.optimization ?? {}), + runtimeChunk: false, + }, + resolve: { + ...(config.resolve ?? {}), + alias: { + ...(config.resolve?.alias ?? {}), + ...sharedLibraries.getAliases(), + }, + }, + experiments: { + ...(config.experiments ?? {}), + outputModule: true, + }, + plugins: [ + ...(config.plugins ?? []), + new ModuleFederationPlugin({ + name: options.name, + filename: 'remoteEntry.mjs', + exposes: options.exposes, + remotes: mappedRemotes, + shared: { + ...sharedDependencies, + }, + library: { + type: 'module', + }, + }), + sharedLibraries.getReplacementPlugin(), + ], + }); +} diff --git a/yarn.lock b/yarn.lock index ba3d71311b969..820d435a90558 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5861,6 +5861,14 @@ "@types/eslint" "*" "@types/estree" "*" +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + "@types/eslint@*": version "8.2.0" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz" @@ -5887,6 +5895,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.28" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" @@ -6352,6 +6365,15 @@ anymatch "^3.0.0" source-map "^0.6.0" +"@types/webpack@^5.28.0": + version "5.28.0" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" + integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== + dependencies: + "@types/node" "*" + tapable "^2.2.0" + webpack "^5" + "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -11079,6 +11101,14 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.8.3: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enquirer@^2.3.6, enquirer@~2.3.6: version "2.3.6" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" @@ -24554,6 +24584,36 @@ webpack@5.67.0: watchpack "^2.3.1" webpack-sources "^3.2.3" +webpack@^5: + version "5.70.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.2" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + webpack@^5.37.0, webpack@^5.58.1: version "5.64.1" resolved "https://registry.npmjs.org/webpack/-/webpack-5.64.1.tgz"