Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f19b6c7

Browse files
authored
fix(node/composePlugins): ensure correct semantic of PluginContext#resolveId of all hooks (#2050)
<!-- Thank you for contributing! --> ### Description > Fixes for other hooks would be raised later. Basically we need intercept all hooks's PluginContext for plugins that implemented resolveId hook. #2021 <!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
1 parent 9e461c2 commit f19b6c7

6 files changed

Lines changed: 268 additions & 88 deletions

File tree

packages/rolldown/src/plugin/plugin-context.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,21 @@ export class PluginContext {
4040
debug: LoggingFunction
4141
info: LoggingFunction
4242
warn: LoggingFunction
43-
error: (error: RollupError | string) => never
44-
resolve: (
43+
readonly error: (error: RollupError | string) => never
44+
readonly resolve: (
4545
source: string,
4646
importer?: string,
4747
options?: PluginContextResolveOptions,
4848
) => Promise<ResolvedId | null>
49-
emitFile: (file: EmittedAsset) => string
50-
getFileName: (referenceId: string) => string
51-
getModuleInfo: (id: string) => ModuleInfo | null
52-
getModuleIds: () => IterableIterator<string>
53-
addWatchFile: (id: string) => void
49+
readonly emitFile: (file: EmittedAsset) => string
50+
readonly getFileName: (referenceId: string) => string
51+
readonly getModuleInfo: (id: string) => ModuleInfo | null
52+
readonly getModuleIds: () => IterableIterator<string>
53+
readonly addWatchFile: (id: string) => void
5454
/**
5555
* @deprecated This rollup API won't be supported by rolldown. Using this API will cause runtime error.
5656
*/
57-
parse: (input: string, options?: any) => any
57+
readonly parse: (input: string, options?: any) => any
5858

5959
constructor(
6060
options: NormalizedInputOptions,

packages/rolldown/src/utils/compose-js-plugins.ts

Lines changed: 136 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import { TupleToUnion } from 'type-fest'
1111
import * as R from 'remeda'
1212
import { PluginHookNames } from '../constants/plugin'
1313
import { AssertNever } from './type-assert'
14-
import { PrivatePluginContextResolveOptions } from '../plugin/plugin-context'
14+
import {
15+
PluginContext,
16+
PrivatePluginContextResolveOptions,
17+
} from '../plugin/plugin-context'
1518
import { SYMBOL_FOR_RESOLVE_CALLER_THAT_SKIP_SELF } from '../constants/plugin-context'
1619
import { isPluginHookName } from './plugin'
1720

@@ -43,7 +46,7 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
4346

4447
const names: string[] = []
4548
const batchedHooks: {
46-
[K in SupportedHookNames]?: NonNullable<Plugin[K]>[]
49+
[K in SupportedHookNames]?: [NonNullable<Plugin[K]>, Plugin][]
4750
} = {}
4851

4952
plugins.forEach((plugin, index) => {
@@ -66,47 +69,47 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
6669
const handlers = batchedHooks.buildStart ?? []
6770
batchedHooks.buildStart = handlers
6871
if (plugin.buildStart) {
69-
handlers.push(plugin.buildStart)
72+
handlers.push([plugin.buildStart, plugin])
7073
}
7174
break
7275
}
7376
case 'load': {
7477
const handlers = batchedHooks.load ?? []
7578
batchedHooks.load = handlers
7679
if (plugin.load) {
77-
handlers.push(plugin.load)
80+
handlers.push([plugin.load, plugin])
7881
}
7982
break
8083
}
8184
case 'transform': {
8285
const handlers = batchedHooks.transform ?? []
8386
batchedHooks.transform = handlers
8487
if (plugin.transform) {
85-
handlers.push(plugin.transform)
88+
handlers.push([plugin.transform, plugin])
8689
}
8790
break
8891
}
8992
case 'resolveId': {
9093
const handlers = batchedHooks.resolveId ?? []
9194
batchedHooks.resolveId = handlers
9295
if (plugin.resolveId) {
93-
handlers.push(plugin.resolveId)
96+
handlers.push([plugin.resolveId, plugin])
9497
}
9598
break
9699
}
97100
case 'buildEnd': {
98101
const handlers = batchedHooks.buildEnd ?? []
99102
batchedHooks.buildEnd = handlers
100103
if (plugin.buildEnd) {
101-
handlers.push(plugin.buildEnd)
104+
handlers.push([plugin.buildEnd, plugin])
102105
}
103106
break
104107
}
105108
case 'renderChunk': {
106109
const handlers = batchedHooks.renderChunk ?? []
107110
batchedHooks.renderChunk = handlers
108111
if (plugin.renderChunk) {
109-
handlers.push(plugin.renderChunk)
112+
handlers.push([plugin.renderChunk, plugin])
110113
}
111114
break
112115
}
@@ -116,7 +119,7 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
116119
case 'outro': {
117120
const hook = plugin[pluginProp]
118121
if (hook) {
119-
;(batchedHooks[pluginProp] ??= []).push(hook)
122+
;(batchedHooks[pluginProp] ??= []).push([hook, plugin])
120123
}
121124
break
122125
}
@@ -133,16 +136,109 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
133136
name: `Composed(${names.join(', ')})`,
134137
}
135138

139+
const createFixedPluginResolveFnMap = new Map<
140+
Plugin,
141+
(ctx: PluginContext) => PluginContext['resolve']
142+
>()
143+
144+
function applyFixedPluginResolveFn(ctx: PluginContext, plugin: Plugin) {
145+
const createFixedPluginResolveFn = createFixedPluginResolveFnMap.get(plugin)
146+
147+
if (createFixedPluginResolveFn) {
148+
return {
149+
...ctx,
150+
resolve: createFixedPluginResolveFn(ctx),
151+
}
152+
}
153+
154+
return ctx
155+
}
156+
157+
if (batchedHooks.resolveId) {
158+
const batchedHandlers = batchedHooks.resolveId
159+
const handlerSymbols = batchedHandlers.map(([_handler, plugin]) =>
160+
Symbol(plugin.name ?? `Anonymous`),
161+
)
162+
for (
163+
let handlerIdx = 0;
164+
handlerIdx < batchedHandlers.length;
165+
handlerIdx++
166+
) {
167+
const [_handler, plugin] = batchedHandlers[handlerIdx]
168+
const handlerSymbol = handlerSymbols[handlerIdx]
169+
const createFixedPluginResolveFn = (
170+
ctx: PluginContext,
171+
): PluginContext['resolve'] => {
172+
return (source, importer, rawContextResolveOptions) => {
173+
const contextResolveOptions: PrivatePluginContextResolveOptions =
174+
rawContextResolveOptions ?? {}
175+
176+
if (contextResolveOptions.skipSelf) {
177+
contextResolveOptions[SYMBOL_FOR_RESOLVE_CALLER_THAT_SKIP_SELF] =
178+
handlerSymbol
179+
contextResolveOptions.skipSelf = false
180+
}
181+
182+
return ctx.resolve(source, importer, contextResolveOptions)
183+
}
184+
}
185+
createFixedPluginResolveFnMap.set(plugin, createFixedPluginResolveFn)
186+
}
187+
188+
composed.resolveId = async function (
189+
source,
190+
importer,
191+
rawHookResolveIdOptions,
192+
) {
193+
const hookResolveIdOptions: PrivateResolveIdExtraOptions =
194+
rawHookResolveIdOptions
195+
196+
const symbolForCallerThatSkipSelf =
197+
hookResolveIdOptions?.[SYMBOL_FOR_RESOLVE_CALLER_THAT_SKIP_SELF]
198+
199+
for (
200+
let handlerIdx = 0;
201+
handlerIdx < batchedHandlers.length;
202+
handlerIdx++
203+
) {
204+
const [handler, plugin] = batchedHandlers[handlerIdx]
205+
const handlerSymbol = handlerSymbols[handlerIdx]
206+
207+
if (symbolForCallerThatSkipSelf === handlerSymbol) {
208+
continue
209+
}
210+
211+
const { handler: handlerFn } = normalizeHook(handler)
212+
const result = await handlerFn.call(
213+
applyFixedPluginResolveFn(this, plugin),
214+
source,
215+
importer,
216+
rawHookResolveIdOptions,
217+
)
218+
if (!isNullish(result)) {
219+
return result
220+
}
221+
}
222+
}
223+
}
224+
136225
R.keys(batchedHooks).forEach((hookName) => {
137226
switch (hookName) {
227+
case 'resolveId': {
228+
// It's handled above
229+
break
230+
}
138231
case 'buildStart': {
139232
if (batchedHooks.buildStart) {
140233
const batchedHandlers = batchedHooks.buildStart
141234
composed.buildStart = async function (options) {
142235
await Promise.all(
143-
batchedHandlers.map((handler) => {
236+
batchedHandlers.map(([handler, plugin]) => {
144237
const { handler: handlerFn } = normalizeHook(handler)
145-
return handlerFn.call(this, options)
238+
return handlerFn.call(
239+
applyFixedPluginResolveFn(this, plugin),
240+
options,
241+
)
146242
}),
147243
)
148244
}
@@ -153,9 +249,12 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
153249
if (batchedHooks.load) {
154250
const batchedHandlers = batchedHooks.load
155251
composed.load = async function (id) {
156-
for (const handler of batchedHandlers) {
252+
for (const [handler, plugin] of batchedHandlers) {
157253
const { handler: handlerFn } = normalizeHook(handler)
158-
const result = await handlerFn.call(this, id)
254+
const result = await handlerFn.call(
255+
applyFixedPluginResolveFn(this, plugin),
256+
id,
257+
)
159258
if (!isNullish(result)) {
160259
return result
161260
}
@@ -178,9 +277,14 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
178277
code = newCode
179278
moduleSideEffects = newModuleSideEffects ?? undefined
180279
}
181-
for (const handler of batchedHandlers) {
280+
for (const [handler, plugin] of batchedHandlers) {
182281
const { handler: handlerFn } = normalizeHook(handler)
183-
const result = await handlerFn.call(this, code, id, moduleType)
282+
const result = await handlerFn.call(
283+
applyFixedPluginResolveFn(this, plugin),
284+
code,
285+
id,
286+
moduleType,
287+
)
184288
if (!isNullish(result)) {
185289
if (typeof result === 'string') {
186290
updateOutput(result)
@@ -199,73 +303,17 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
199303
}
200304
break
201305
}
202-
case 'resolveId': {
203-
if (batchedHooks.resolveId) {
204-
const batchedHandlers = batchedHooks.resolveId
205-
const handlerSymbols = batchedHandlers.map((_handler, idx) =>
206-
Symbol(idx),
207-
)
208-
composed.resolveId = async function (
209-
source,
210-
importer,
211-
rawHookResolveIdOptions,
212-
) {
213-
const hookResolveIdOptions: PrivateResolveIdExtraOptions =
214-
rawHookResolveIdOptions
215-
216-
const symbolForCallerThatSkipSelf =
217-
hookResolveIdOptions?.[SYMBOL_FOR_RESOLVE_CALLER_THAT_SKIP_SELF]
218-
219-
for (
220-
let handlerIdx = 0;
221-
handlerIdx < batchedHandlers.length;
222-
handlerIdx++
223-
) {
224-
const handler = batchedHandlers[handlerIdx]
225-
const handlerSymbol = handlerSymbols[handlerIdx]
226-
227-
if (symbolForCallerThatSkipSelf === handlerSymbol) {
228-
continue
229-
}
230-
231-
const { handler: handlerFn } = normalizeHook(handler)
232-
const result = await handlerFn.call(
233-
{
234-
...this,
235-
resolve: (source, importer, rawContextResolveOptions) => {
236-
const contextResolveOptions: PrivatePluginContextResolveOptions =
237-
rawContextResolveOptions ?? {}
238-
239-
if (contextResolveOptions.skipSelf) {
240-
contextResolveOptions[
241-
SYMBOL_FOR_RESOLVE_CALLER_THAT_SKIP_SELF
242-
] = handlerSymbol
243-
contextResolveOptions.skipSelf = false
244-
}
245-
246-
return this.resolve(source, importer, contextResolveOptions)
247-
},
248-
},
249-
source,
250-
importer,
251-
rawHookResolveIdOptions,
252-
)
253-
if (!isNullish(result)) {
254-
return result
255-
}
256-
}
257-
}
258-
}
259-
break
260-
}
261306
case 'buildEnd': {
262307
if (batchedHooks.buildEnd) {
263308
const batchedHandlers = batchedHooks.buildEnd
264309
composed.buildEnd = async function (err) {
265310
await Promise.all(
266-
batchedHandlers.map((handler) => {
311+
batchedHandlers.map(([handler, plugin]) => {
267312
const { handler: handlerFn } = normalizeHook(handler)
268-
return handlerFn.call(this, err)
313+
return handlerFn.call(
314+
applyFixedPluginResolveFn(this, plugin),
315+
err,
316+
)
269317
}),
270318
)
271319
}
@@ -276,9 +324,14 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
276324
if (batchedHooks.renderChunk) {
277325
const batchedHandlers = batchedHooks.renderChunk
278326
composed.renderChunk = async function (code, chunk, options) {
279-
for (const handler of batchedHandlers) {
327+
for (const [handler, plugin] of batchedHandlers) {
280328
const { handler: handlerFn } = normalizeHook(handler)
281-
const result = await handlerFn.call(this, code, chunk, options)
329+
const result = await handlerFn.call(
330+
applyFixedPluginResolveFn(this, plugin),
331+
code,
332+
chunk,
333+
options,
334+
)
282335
if (!isNullish(result)) {
283336
return result
284337
}
@@ -295,13 +348,16 @@ function createComposedPlugin(plugins: Plugin[]): Plugin {
295348
if (hooks?.length) {
296349
composed[hookName] = async function (chunk) {
297350
const ret: string[] = []
298-
for (const hook of hooks) {
351+
for (const [hook, plugin] of hooks) {
299352
{
300353
const { handler } = normalizeHook(hook)
301354
ret.push(
302355
typeof handler === 'string'
303356
? handler
304-
: await handler.call(this, chunk),
357+
: await handler.call(
358+
applyFixedPluginResolveFn(this, plugin),
359+
chunk,
360+
),
305361
)
306362
}
307363
}

0 commit comments

Comments
 (0)