-
Notifications
You must be signed in to change notification settings - Fork 2
/
webpack.ts
149 lines (137 loc) · 5.04 KB
/
webpack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import path from 'node:path'
import type {Chunk, Compiler, Compilation} from 'webpack'
import webpack from 'webpack'
type ChunkGroup = Parameters<typeof Chunk.prototype.addGroup>[0]
// type Entrypoint = Parameters<typeof ChunkGraph.prototype.connectChunkAndEntryModule>[2]
const isOriginDynamicImported = (origin: {request: string}, _chunkGroup: ChunkGroup) => {
// check if origin is imported via import()
// for (const chunk of chunkGroup.chunks)
// for (const md of chunk.getModules())
// for (const {type, userRequest} of md.reasons)
// if (userRequest === origin.request && type === 'import()') return true
// return false
return !!origin.request
}
export interface LoadableManifest {
publicPath?: string
originToChunkGroups: Record<string, string[]>
chunkGroupAssets: Record<string, string[]>
preloadAssets: Record<string, string[] | undefined>
prefetchAssets: Record<string, string[] | undefined>
runtimeAssets: Record<string, string[] | undefined>
entryToId: Record<string, string>
}
const getAssetsOfChunkGroups = (chunkGroups?: ChunkGroup[]) => {
if (!chunkGroups) return
const assets = new Set<string>()
for (const chunkGroup of chunkGroups)
for (const asset of (chunkGroup as any).getFiles())
assets.add(asset)
return [...assets.values()]
}
const buildManifest = (
compilation: Compilation,
{
moduleNameTransform,
absPath,
}: {
moduleNameTransform?(moduleName: string): string
absPath?: boolean
}
): LoadableManifest => {
const entryToId: Record<string, string> = {}
const runtimeAssets: Record<string, string[]> = {}
const includedChunkGroups = new Set<string>()
// always add entries
for (const chunkGroup of compilation.chunkGroups)
if (chunkGroup.isInitial()) {
entryToId[chunkGroup.name] = chunkGroup.id
includedChunkGroups.add(chunkGroup.id)
runtimeAssets[chunkGroup.id] = [...(chunkGroup as any).getRuntimeChunk().files.values()]
}
// get map of origin to chunk groups
const originToChunkGroups: Record<string, string[]> = {}
for (const chunkGroup of compilation.chunkGroups)
for (const origin of chunkGroup.origins)
if (isOriginDynamicImported(origin, chunkGroup)) {
includedChunkGroups.add(chunkGroup.id)
const absModuleName = absPath && origin.request?.startsWith('./') && origin.module?.context
? path.resolve(origin.module.context, origin.request)
: origin.request
const moduleName = moduleNameTransform ? moduleNameTransform(absModuleName) : absModuleName
if (!originToChunkGroups[moduleName]) originToChunkGroups[moduleName] = []
if (!originToChunkGroups[moduleName].includes(chunkGroup.id))
originToChunkGroups[moduleName].push(chunkGroup.id)
}
const chunkGroupAssets: Record<string, string[]> = {}
const preloadAssets: Record<string, string[]> = {}
const prefetchAssets: Record<string, string[]> = {}
const chunkGroupSizes: Record<string, number> = {}
for (const chunkGroup of compilation.chunkGroups)
if (includedChunkGroups.has(chunkGroup.id)) {
//get map of chunk group to assets
chunkGroupAssets[chunkGroup.id] = chunkGroup.getFiles()
//get chunk group size
let size = 0
for (const chunk of chunkGroup.chunks) size += compilation.chunkGraph
? compilation.chunkGraph.getChunkSize(chunk)
: chunk.size()
chunkGroupSizes[chunkGroup.id] = size
//child assets
const {prefetch, preload} = chunkGroup.getChildrenByOrders(compilation.moduleGraph, compilation.chunkGraph)
preloadAssets[chunkGroup.id] = getAssetsOfChunkGroups(preload)
prefetchAssets[chunkGroup.id] = getAssetsOfChunkGroups(prefetch)
}
//sort for the greedy cover set algorithm
for (const chunkGroups of Object.values(originToChunkGroups))
chunkGroups.sort(
(cg1, cg2) => chunkGroupSizes[cg1] - chunkGroupSizes[cg2]
)
return {
publicPath: compilation.outputOptions.publicPath as string,
originToChunkGroups,
chunkGroupAssets,
preloadAssets,
prefetchAssets,
runtimeAssets,
entryToId,
}
}
const pluginName = '@react-loadable/revised'
export class ReactLoadablePlugin {
constructor(private options: {
callback(manifest: LoadableManifest): any
moduleNameTransform?(moduleName: string): string
absPath?: boolean
}) {}
apply(compiler: Compiler) {
const emit = async (compilation: Compilation) => {
try {
const manifest = buildManifest(compilation, {
moduleNameTransform: this.options.moduleNameTransform,
absPath: this.options.absPath,
})
await this.options.callback(manifest)
} catch (e) {
compilation.errors.push(e)
}
}
if (compiler.hooks) {
if (
webpack.version.slice(0, 2) === '4.'
) compiler.hooks.emit.tap(pluginName, emit)
// hooks.thisCompilation is recommended over hooks.compilation
// https://github.com/webpack/webpack/issues/11425#issuecomment-690547848
else compiler.hooks.thisCompilation.tap(pluginName, compilation => {
compilation.hooks.processAssets.tap(
{
name: pluginName,
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
() => emit(compilation)
)
})
compiler.hooks.emit.tap(pluginName, emit)
} else (compiler as any).plugin('emit', emit)
}
}