Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 5219355

Browse files
author
Je
committed
fix: fix useDeno hook signature in production mode
1 parent b335f25 commit 5219355

File tree

3 files changed

+125
-31
lines changed

3 files changed

+125
-31
lines changed

tsc/compile.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import ts from 'https://esm.sh/typescript'
22
import transformImportPathRewrite from './transform-import-path-rewrite.ts'
33
import transformReactJsx from './transform-react-jsx.ts'
44
import transformReactRefresh from './transform-react-refresh.ts'
5+
import transformReactUseDenoHook from './transform-react-use-deno-hook.ts'
56
import { CreatePlainTransformer, CreateTransformer } from './transformer.ts'
67

78
export interface CompileOptions {
8-
target: string
99
mode: 'development' | 'production'
10+
target: string
1011
reactRefresh: boolean
1112
rewriteImportPath: (importPath: string, async?: boolean) => string
1213
}
@@ -29,10 +30,11 @@ const allowTargets = [
2930
'es2020',
3031
]
3132

32-
export function compile(fileName: string, source: string, { target: targetName, mode, rewriteImportPath, reactRefresh }: CompileOptions) {
33+
export function compile(fileName: string, source: string, { mode, target: targetName, rewriteImportPath, reactRefresh }: CompileOptions) {
3334
const target = allowTargets.indexOf(targetName.toLowerCase())
3435
const transformers: ts.CustomTransformers = { before: [], after: [] }
3536
transformers.before!.push(CreatePlainTransformer(transformReactJsx, { mode, rewriteImportPath }))
37+
transformers.before!.push(CreateTransformer(transformReactUseDenoHook))
3638
if (reactRefresh) {
3739
transformers.before!.push(CreateTransformer(transformReactRefresh))
3840
}

tsc/transform-react-refresh.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,19 @@
55
*/
66

77
import ts from 'https://esm.sh/typescript'
8-
import { Sha1 } from '../std.ts'
98

109
const f = ts.factory
1110

1211
type TSFunctionLike = ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction
1312

1413
export class RefreshTransformer {
1514
#sf: ts.SourceFile
16-
#useDenoIndex: number
1715

1816
static refreshSig = '$RefreshSig$'
1917
static refreshReg = '$RefreshReg$'
2018

2119
constructor(sf: ts.SourceFile) {
2220
this.#sf = sf
23-
this.#useDenoIndex = 0
2421
}
2522

2623
transform() {
@@ -173,9 +170,6 @@ export class RefreshTransformer {
173170
const sig = this._getHookCallSignature(initializer)
174171
if (sig) {
175172
hookCalls.push(sig)
176-
if (sig.name === 'useDeno') {
177-
this._signUseDeno(initializer)
178-
}
179173
}
180174
}
181175
})
@@ -186,9 +180,6 @@ export class RefreshTransformer {
186180
const sig = this._getHookCallSignature(s.expression)
187181
if (sig) {
188182
hookCalls.push(sig)
189-
if (sig.name === 'useDeno') {
190-
this._signUseDeno(s.expression)
191-
}
192183
}
193184
}
194185
})
@@ -239,25 +230,6 @@ export class RefreshTransformer {
239230
}
240231
}
241232

242-
private _signUseDeno(call: ts.CallExpression) {
243-
const args = call.arguments as unknown as Array<any>
244-
if (args.length > 0) {
245-
const id = new Sha1().update(this.#sf.fileName + ':useDeno#' + (this.#useDenoIndex++)).hex().slice(0, 9)
246-
const arg3 = f.createStringLiteral(`useDeno.${id}`)
247-
if (args.length === 1) {
248-
args.push(f.createFalse())
249-
}
250-
if (args.length === 2) {
251-
args.push(f.createVoidZero())
252-
}
253-
if (args.length === 3) {
254-
args.push(arg3)
255-
} else {
256-
args[3] = arg3
257-
}
258-
}
259-
}
260-
261233
private _sign(fnNode: TSFunctionLike, fnName: string, sigId: ts.Identifier, key: string, forceReset: boolean, customHooks: string[]) {
262234
if (fnNode.body && ts.isBlock(fnNode.body)) {
263235
const _s = f.createExpressionStatement(ts.createCall(
@@ -347,7 +319,7 @@ function isComponentishName(name: string) {
347319
return c >= 'A' && c <= 'Z'
348320
}
349321

350-
function isHookName(name: string) {
322+
export function isHookName(name: string) {
351323
let c: string
352324
return name.startsWith('use') && (c = name.charAt(3)) && c >= 'A' && c <= 'Z'
353325
}

tsc/transform-react-use-deno-hook.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* TypeScript AST Transformer for react refresh.
3+
* @link https://github.com/facebook/react/issues/16604#issuecomment-528663101
4+
* @link https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js
5+
*/
6+
7+
import ts from 'https://esm.sh/typescript'
8+
import { Sha1 } from '../std.ts'
9+
import { isHookName } from './transform-react-refresh.ts'
10+
11+
const f = ts.factory
12+
13+
export class RefreshTransformer {
14+
#sf: ts.SourceFile
15+
#useDenoIndex: number
16+
17+
constructor(sf: ts.SourceFile) {
18+
this.#sf = sf
19+
this.#useDenoIndex = 0
20+
}
21+
22+
transform() {
23+
const statements: ts.Statement[] = []
24+
25+
this.#sf.statements.forEach(node => {
26+
if (ts.isFunctionDeclaration(node)) {
27+
this._getHookCallsSignature(node)
28+
} else if (ts.isVariableStatement(node)) {
29+
node.declarationList.declarations.forEach(({ name, initializer, modifiers }) => {
30+
if (
31+
initializer &&
32+
ts.isIdentifier(name) &&
33+
(ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer))
34+
) {
35+
this._getHookCallsSignature(initializer)
36+
}
37+
})
38+
}
39+
statements.push(node)
40+
})
41+
42+
return ts.updateSourceFileNode(
43+
this.#sf,
44+
ts.setTextRange(
45+
f.createNodeArray(statements),
46+
this.#sf.statements
47+
)
48+
)
49+
}
50+
51+
private _getHookCallsSignature(fnNode: ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction) {
52+
if (fnNode.body && ts.isBlock(fnNode.body)) {
53+
fnNode.body.statements.forEach(s => {
54+
if (ts.isVariableStatement(s)) {
55+
s.declarationList.declarations.forEach(({ initializer }) => {
56+
if (
57+
initializer &&
58+
ts.isCallExpression(initializer)
59+
) {
60+
const name = this._getHookCallSignature(initializer)
61+
if (name === 'useDeno') {
62+
this._signUseDeno(initializer)
63+
}
64+
}
65+
})
66+
} else if (
67+
ts.isExpressionStatement(s) &&
68+
ts.isCallExpression(s.expression)
69+
) {
70+
const name = this._getHookCallSignature(s.expression)
71+
if (name === 'useDeno') {
72+
this._signUseDeno(s.expression)
73+
}
74+
}
75+
})
76+
}
77+
}
78+
79+
private _getHookCallSignature(ctx: ts.CallExpression) {
80+
let name: string
81+
const { expression } = ctx
82+
if (ts.isIdentifier(expression)) {
83+
name = expression.text
84+
} else if (ts.isPropertyAccessExpression(expression)) {
85+
name = expression.name.text
86+
} else {
87+
return null
88+
}
89+
if (!isHookName(name)) {
90+
return null
91+
}
92+
return name
93+
}
94+
95+
private _signUseDeno(call: ts.CallExpression) {
96+
const args = call.arguments as unknown as Array<any>
97+
if (args.length > 0) {
98+
const id = new Sha1().update(this.#sf.fileName + ':useDeno#' + (this.#useDenoIndex++)).hex().slice(0, 9)
99+
const arg3 = f.createStringLiteral(`useDeno.${id}`)
100+
if (args.length === 1) {
101+
args.push(f.createFalse())
102+
}
103+
if (args.length === 2) {
104+
args.push(f.createVoidZero())
105+
}
106+
if (args.length === 3) {
107+
args.push(arg3)
108+
} else {
109+
args[3] = arg3
110+
}
111+
}
112+
}
113+
}
114+
115+
116+
117+
export default function transformReactUseDenoHook(ctx: ts.TransformationContext, sf: ts.SourceFile): ts.SourceFile {
118+
const t = new RefreshTransformer(sf)
119+
return t.transform()
120+
}

0 commit comments

Comments
 (0)