|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -interface NormalModuleFactoryRequest { |
| 9 | +import { Compiler } from 'webpack'; |
| 10 | + |
| 11 | +interface AfterResolveResult { |
10 | 12 | request: string;
|
| 13 | + resource: string; |
11 | 14 | context: {
|
12 | 15 | issuer: string;
|
13 | 16 | };
|
14 |
| - relativePath: string; |
15 |
| - path: string; |
16 |
| - descriptionFileData: { |
17 |
| - name?: string; |
18 |
| - version?: string; |
| 17 | + resourceResolveData: { |
| 18 | + relativePath: string; |
| 19 | + descriptionFileRoot: string; |
| 20 | + descriptionFilePath: string; |
| 21 | + descriptionFileData: { |
| 22 | + name?: string; |
| 23 | + version?: string; |
| 24 | + }; |
19 | 25 | };
|
20 |
| - descriptionFileRoot: string; |
21 |
| - descriptionFilePath: string; |
22 |
| - directory?: boolean; |
23 |
| - file?: boolean; |
24 | 26 | }
|
25 | 27 |
|
26 | 28 | export interface DedupeModuleResolvePluginOptions {
|
27 | 29 | verbose?: boolean;
|
28 | 30 | }
|
29 | 31 |
|
30 | 32 | /**
|
31 |
| - * DedupeModuleResolvePlugin is a webpack resolver plugin which dedupes modules with the same name and versions |
| 33 | + * DedupeModuleResolvePlugin is a webpack plugin which dedupes modules with the same name and versions |
32 | 34 | * that are laid out in different parts of the node_modules tree.
|
33 | 35 | *
|
34 | 36 | * This is needed because Webpack relies on package managers to hoist modules and doesn't have any deduping logic.
|
| 37 | + * |
| 38 | + * This is similar to how Webpack's 'NormalModuleReplacementPlugin' works |
| 39 | + * @see https://github.com/webpack/webpack/blob/4a1f068828c2ab47537d8be30d542cd3a1076db4/lib/NormalModuleReplacementPlugin.js#L9 |
35 | 40 | */
|
36 | 41 | export class DedupeModuleResolvePlugin {
|
37 |
| - modules = new Map<string, NormalModuleFactoryRequest>(); |
| 42 | + modules = new Map<string, { request: string, resource: string }>(); |
38 | 43 |
|
39 | 44 | constructor(private options?: DedupeModuleResolvePluginOptions) { }
|
40 | 45 |
|
41 | 46 | // tslint:disable-next-line: no-any
|
42 |
| - apply(resolver: any) { |
43 |
| - resolver |
44 |
| - .getHook('before-described-relative') |
45 |
| - .tapPromise('DedupeModuleResolvePlugin', async (request: NormalModuleFactoryRequest) => { |
46 |
| - if (request.relativePath !== '.') { |
| 47 | + apply(compiler: Compiler) { |
| 48 | + compiler.hooks.normalModuleFactory.tap('DedupeModuleResolvePlugin', nmf => { |
| 49 | + nmf.hooks.afterResolve.tap('DedupeModuleResolvePlugin', (result: AfterResolveResult | undefined) => { |
| 50 | + if (!result) { |
47 | 51 | return;
|
48 | 52 | }
|
49 | 53 |
|
50 |
| - // When either of these properties is undefined. It typically means it's a link. |
51 |
| - // In which case we shouldn't try to dedupe it. |
52 |
| - if (request.file === undefined || request.directory === undefined) { |
53 |
| - return; |
54 |
| - } |
| 54 | + const { resource, request, resourceResolveData } = result; |
| 55 | + const { descriptionFileData, relativePath } = resourceResolveData; |
55 | 56 |
|
56 |
| - // Empty name or versions are no valid primary entrypoints of a library |
57 |
| - if (!request.descriptionFileData.name || !request.descriptionFileData.version) { |
| 57 | + // Empty name or versions are no valid primary entrypoints of a library |
| 58 | + if (!descriptionFileData.name || !descriptionFileData.version) { |
58 | 59 | return;
|
59 | 60 | }
|
60 | 61 |
|
61 |
| - const moduleId = request.descriptionFileData.name + '@' + request.descriptionFileData.version; |
| 62 | + const moduleId = descriptionFileData.name + '@' + descriptionFileData.version + ':' + relativePath; |
62 | 63 | const prevResolvedModule = this.modules.get(moduleId);
|
63 | 64 |
|
64 | 65 | if (!prevResolvedModule) {
|
65 | 66 | // This is the first time we visit this module.
|
66 |
| - this.modules.set(moduleId, request); |
| 67 | + this.modules.set(moduleId, { |
| 68 | + resource, |
| 69 | + request, |
| 70 | + }); |
67 | 71 |
|
68 | 72 | return;
|
69 | 73 | }
|
70 | 74 |
|
71 |
| - const { |
72 |
| - path, |
73 |
| - descriptionFilePath, |
74 |
| - descriptionFileRoot, |
75 |
| - } = prevResolvedModule; |
76 |
| - |
77 |
| - if (request.path === path) { |
| 75 | + const { resource: prevResource, request: prevRequest } = prevResolvedModule; |
| 76 | + if (result.resource === prevResource) { |
78 | 77 | // No deduping needed.
|
79 | 78 | // Current path and previously resolved path are the same.
|
80 | 79 | return;
|
81 | 80 | }
|
82 | 81 |
|
83 | 82 | if (this.options?.verbose) {
|
84 | 83 | // tslint:disable-next-line: no-console
|
85 |
| - console.warn(`[DedupeModuleResolvePlugin]: ${request.path} -> ${path}`); |
| 84 | + console.warn(`[DedupeModuleResolvePlugin]: ${result.resource} -> ${prevResource}`); |
86 | 85 | }
|
87 | 86 |
|
88 | 87 | // Alter current request with previously resolved module.
|
89 |
| - request.path = path; |
90 |
| - request.descriptionFileRoot = descriptionFileRoot; |
91 |
| - request.descriptionFilePath = descriptionFilePath; |
| 88 | + result.request = prevRequest; |
92 | 89 | });
|
| 90 | + }); |
93 | 91 | }
|
94 | 92 | }
|
0 commit comments