diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts deleted file mode 100644 index a64630d6fb..0000000000 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as os from 'os'; -import * as path from 'path'; -import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; - -describe('LinkedPackageResolvePlugin', () => { - const root = os.platform() === 'win32' ? 'C:\\root' : '/root'; - - it('should re-write paths for external packages', () => { - const plugin = new LinkedPackageResolvePlugin( - path.resolve(root, 'repo/node_modules'), - [ - { - dir: path.resolve(root, 'external-a'), - packageJson: { - name: 'a', - version: '1.0.0', - }, - }, - { - dir: path.resolve(root, 'external-b'), - packageJson: { - name: '@s/b', - version: '1.0.0', - }, - }, - ], - ); - - const tapAsync = jest.fn(); - const doResolve = jest.fn(); - - const resolver = { - hooks: { resolve: { tapAsync } }, - doResolve, - }; - plugin.apply(resolver); - - expect(tapAsync).toHaveBeenCalledTimes(1); - expect(tapAsync).toHaveBeenCalledWith( - 'LinkedPackageResolvePlugin', - expect.any(Function), - ); - expect(doResolve).toHaveBeenCalledTimes(0); - - // Internal module resolution is not affected - const tap = tapAsync.mock.calls[0][1]; - const callbackX = jest.fn(); - tap( - { - request: path.resolve(root, 'repo/package/x/src/module.ts'), - path: path.resolve(root, 'repo/package/x/src'), - context: { - issuer: path.resolve(root, 'repo/package/x/src/index.ts'), - }, - }, - 'some-context', - callbackX, - ); - expect(callbackX).toHaveBeenCalledTimes(1); - expect(callbackX).toHaveBeenCalledWith(); - expect(doResolve).toHaveBeenCalledTimes(0); - - // Path is sometimes false - const callbackFalse = jest.fn(); - tap( - { - request: 'sample', - path: false, - }, - 'some-context', - callbackFalse, - ); - expect(callbackFalse).toHaveBeenCalledTimes(1); - expect(callbackFalse).toHaveBeenCalledWith(); - expect(doResolve).toHaveBeenCalledTimes(0); - - // Internal modules with a path prefix of an external module - const callbackY = jest.fn(); - tap( - { - request: path.resolve(root, 'external-aa/src/module.ts'), - path: path.resolve(root, 'external-aa/src'), - context: { - issuer: path.resolve(root, 'external-aa/src/index.ts'), - }, - }, - 'some-context', - callbackY, - ); - expect(callbackY).toHaveBeenCalledTimes(1); - expect(callbackY).toHaveBeenCalledWith(); - expect(doResolve).toHaveBeenCalledTimes(0); - - // External modules have their path and issuer context rewritten, but not the request - const callbackA = jest.fn(); - tap( - { - request: path.resolve(root, 'external-a/src/module.ts'), - path: path.resolve(root, 'external-a/src'), - context: { - issuer: path.resolve(root, 'external-a/src/index.ts'), - }, - }, - 'some-context', - callbackA, - ); - expect(callbackA).toHaveBeenCalledTimes(0); - expect(doResolve).toHaveBeenCalledTimes(1); - expect(doResolve).toHaveBeenCalledWith( - resolver.hooks.resolve, - { - request: path.resolve(root, 'external-a/src/module.ts'), - path: path.resolve(root, 'repo/node_modules/a/src'), - context: { - issuer: path.resolve(root, 'repo/node_modules/a/src/index.ts'), - }, - }, - `resolve ${path.resolve( - root, - 'external-a/src/module.ts', - )} in ${path.resolve(root, 'repo/node_modules/a')}`, - 'some-context', - callbackA, - ); - - // Also handles scoped packages correctly, and issuer is not required - const callbackB = jest.fn(); - tap( - { - request: path.resolve(root, 'external-b/src/module.ts'), - path: path.resolve(root, 'external-b/src'), - context: { - issuer: false, - }, - }, - 'some-context', - callbackB, - ); - expect(callbackB).toHaveBeenCalledTimes(0); - expect(doResolve).toHaveBeenCalledTimes(2); - expect(doResolve).toHaveBeenLastCalledWith( - resolver.hooks.resolve, - { - request: path.resolve(root, 'external-b/src/module.ts'), - path: path.resolve(root, 'repo/node_modules/@s/b/src'), - context: { - issuer: false, - }, - }, - `resolve ${path.resolve( - root, - 'external-b/src/module.ts', - )} in ${path.resolve(root, 'repo/node_modules/@s/b')}`, - 'some-context', - callbackB, - ); - - expect(tapAsync).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts deleted file mode 100644 index 4811afec6f..0000000000 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { resolve as resolvePath } from 'path'; -import { WebpackPluginInstance } from 'webpack'; -import { isChildPath } from '@backstage/cli-common'; -import { Package } from '@manypkg/get-packages'; - -// Enables proper resolution of packages when linking in external packages. -// Without this the packages would depend on dependencies in the node_modules -// of the external packages themselves, leading to module duplication -export class LinkedPackageResolvePlugin implements WebpackPluginInstance { - constructor( - private readonly targetModules: string, - private readonly packages: Package[], - ) {} - - apply(resolver: any) { - resolver.hooks.resolve.tapAsync( - 'LinkedPackageResolvePlugin', - ( - data: { - request: string; - path?: false | string; - context?: { issuer?: string }; - }, - context: unknown, - callback: () => void, - ) => { - const pkg = this.packages.find( - pkge => data.path && isChildPath(pkge.dir, data.path), - ); - if (!pkg) { - callback(); - return; - } - - // pkg here is an external package. We rewrite the context of any imports to resolve - // from the location of the package within the node_modules of the target root rather - // than the real location of the external package. - const modulesLocation = resolvePath( - this.targetModules, - pkg.packageJson.name, - ); - const newContext = data.context?.issuer - ? { - ...data.context, - issuer: data.context.issuer.replace(pkg.dir, modulesLocation), - } - : data.context; - - // Re-run resolution but this time from the point of view of our target monorepo rather - // than the location of the external package. By resolving modules using this method we avoid - // pulling in e.g. `react` from the external repo, which would otherwise lead to conflicts. - resolver.doResolve( - resolver.hooks.resolve, - { - ...data, - context: newContext, - path: data.path && data.path.replace(pkg.dir, modulesLocation), - }, - `resolve ${data.request} in ${modulesLocation}`, - context, - callback, - ); - }, - ); - } -} diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index c61c117452..0bca891090 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -25,13 +25,10 @@ import ESLintPlugin from 'eslint-webpack-plugin'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; -import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import { paths as cliPaths } from '../../lib/paths'; import fs from 'fs-extra'; -import { getPackages } from '@manypkg/get-packages'; -import { isChildPath } from '@backstage/cli-common'; import { optimization as optimizationConfig } from './optimization'; import pickBy from 'lodash/pickBy'; import { runPlain } from '../run'; @@ -130,8 +127,6 @@ export async function createConfig( const { plugins, loaders } = transforms(options); // Any package that is part of the monorepo but outside the monorepo root dir need // separate resolution logic. - const { packages } = await getPackages(cliPaths.targetDir); - const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir)); const validBaseUrl = resolveBaseUrl(frontendConfig, moduleFederation); let publicPath = validBaseUrl.pathname.replace(/\/$/, ''); @@ -406,7 +401,6 @@ export async function createConfig( // FIXME: see also https://github.com/web-infra-dev/rspack/issues/3408 ...(!rspack && { plugins: [ - new LinkedPackageResolvePlugin(paths.rootNodeModules, externalPkgs), new ModuleScopePlugin( [paths.targetSrc, paths.targetDev], [paths.targetPackageJson, ...reactRefreshFiles],