|
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