Skip to content

Commit b598bb5

Browse files
authored
feat(rsc): support regex directive for transformHoistInlineDirective (#527)
1 parent 0fc7fcd commit b598bb5

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

packages/plugin-rsc/src/transforms/hoist.test.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ import { debugSourceMap } from './test-utils'
66
describe(transformHoistInlineDirective, () => {
77
async function testTransform(
88
input: string,
9-
options?: { encode?: boolean; noExport?: boolean; directive?: string },
9+
options?: {
10+
encode?: boolean
11+
noExport?: boolean
12+
directive?: string | RegExp
13+
},
1014
) {
1115
const ast = await parseAstAsync(input)
1216
const { output } = transformHoistInlineDirective(input, ast, {
13-
runtime: (value, name) =>
14-
`$$register(${value}, "<id>", ${JSON.stringify(name)})`,
17+
runtime: (value, name, meta) =>
18+
`$$register(${value}, "<id>", ${JSON.stringify(name)}` +
19+
`${
20+
options?.directive instanceof RegExp
21+
? `, ${JSON.stringify(meta)}`
22+
: ''
23+
})`,
1524
directive: options?.directive ?? 'use server',
1625
encode: options?.encode ? (v) => `__enc(${v})` : undefined,
1726
decode: options?.encode ? (v) => `__dec(${v})` : undefined,
@@ -369,4 +378,55 @@ export async function test() {
369378
"
370379
`)
371380
})
381+
382+
it('directive pattern', async () => {
383+
const input = `
384+
export async function none() {
385+
"use cache";
386+
return "test";
387+
}
388+
389+
export async function fs() {
390+
"use cache: fs";
391+
return "test";
392+
}
393+
394+
export async function kv() {
395+
"use cache: kv";
396+
return "test";
397+
}
398+
`
399+
expect(
400+
await testTransform(input, {
401+
directive: /^use cache(: .+)?$/,
402+
noExport: true,
403+
}),
404+
).toMatchInlineSnapshot(`
405+
"
406+
export const none = /* #__PURE__ */ $$register($$hoist_0_none, "<id>", "$$hoist_0_none", {"directiveMatch":["use cache",null]});
407+
408+
export const fs = /* #__PURE__ */ $$register($$hoist_1_fs, "<id>", "$$hoist_1_fs", {"directiveMatch":["use cache: fs",": fs"]});
409+
410+
export const kv = /* #__PURE__ */ $$register($$hoist_2_kv, "<id>", "$$hoist_2_kv", {"directiveMatch":["use cache: kv",": kv"]});
411+
412+
;async function $$hoist_0_none() {
413+
"use cache";
414+
return "test";
415+
};
416+
/* #__PURE__ */ Object.defineProperty($$hoist_0_none, "name", { value: "none" });
417+
418+
;async function $$hoist_1_fs() {
419+
"use cache: fs";
420+
return "test";
421+
};
422+
/* #__PURE__ */ Object.defineProperty($$hoist_1_fs, "name", { value: "fs" });
423+
424+
;async function $$hoist_2_kv() {
425+
"use cache: kv";
426+
return "test";
427+
};
428+
/* #__PURE__ */ Object.defineProperty($$hoist_2_kv, "name", { value: "kv" });
429+
"
430+
`)
431+
})
372432
})

packages/plugin-rsc/src/transforms/hoist.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ import type { Program } from 'estree'
33
import { walk } from 'estree-walker'
44
import MagicString from 'magic-string'
55
import { analyze } from 'periscopic'
6-
import { hasDirective } from './utils'
76

87
export function transformHoistInlineDirective(
98
input: string,
109
ast: Program,
1110
{
1211
runtime,
13-
directive,
1412
rejectNonAsyncFunction,
1513
...options
1614
}: {
17-
runtime: (value: string, name: string) => string
18-
directive: string
15+
runtime: (
16+
value: string,
17+
name: string,
18+
meta: { directiveMatch: RegExpMatchArray },
19+
) => string
20+
directive: string | RegExp
1921
rejectNonAsyncFunction?: boolean
2022
encode?: (value: string) => string
2123
decode?: (value: string) => string
@@ -26,6 +28,10 @@ export function transformHoistInlineDirective(
2628
names: string[]
2729
} {
2830
const output = new MagicString(input)
31+
const directive =
32+
typeof options.directive === 'string'
33+
? exactRegex(options.directive)
34+
: options.directive
2935

3036
// re-export somehow confuses periscopic scopes so remove them before analysis
3137
walk(ast, {
@@ -48,9 +54,10 @@ export function transformHoistInlineDirective(
4854
(node.type === 'FunctionExpression' ||
4955
node.type === 'FunctionDeclaration' ||
5056
node.type === 'ArrowFunctionExpression') &&
51-
node.body.type === 'BlockStatement' &&
52-
hasDirective(node.body.body, directive)
57+
node.body.type === 'BlockStatement'
5358
) {
59+
const match = matchDirective(node.body.body, directive)
60+
if (!match) return
5461
if (!node.async && rejectNonAsyncFunction) {
5562
throw Object.assign(
5663
new Error(`"${directive}" doesn't allow non async function`),
@@ -116,7 +123,9 @@ export function transformHoistInlineDirective(
116123
output.move(node.start, node.end, input.length)
117124

118125
// replace original declartion with action register + bind
119-
let newCode = `/* #__PURE__ */ ${runtime(newName, newName)}`
126+
let newCode = `/* #__PURE__ */ ${runtime(newName, newName, {
127+
directiveMatch: match,
128+
})}`
120129
if (bindVars.length > 0) {
121130
const bindArgs = options.encode
122131
? options.encode('[' + bindVars.join(', ') + ']')
@@ -140,3 +149,24 @@ export function transformHoistInlineDirective(
140149
names,
141150
}
142151
}
152+
153+
const exactRegex = (s: string): RegExp =>
154+
new RegExp('^' + s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '$')
155+
156+
function matchDirective(
157+
body: Program['body'],
158+
directive: RegExp,
159+
): RegExpMatchArray | undefined {
160+
for (const stable of body) {
161+
if (
162+
stable.type === 'ExpressionStatement' &&
163+
stable.expression.type === 'Literal' &&
164+
typeof stable.expression.value === 'string'
165+
) {
166+
const match = stable.expression.value.match(directive)
167+
if (match) {
168+
return match
169+
}
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)