Skip to content

Commit 1601148

Browse files
committed
feat: Pick away from Logger and SWC
1 parent 8ec8bcd commit 1601148

File tree

6 files changed

+226
-125
lines changed

6 files changed

+226
-125
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@codemirror/language": "^6.10.3",
3535
"@codemirror/state": "^6.4.1",
3636
"@lezer/highlight": "^1.2.1",
37+
"@swc/wasm-typescript-esm": "^1.10.1",
3738
"@swc/wasm-web": "^1.10.1",
3839
"@types/codemirror": "^5.60.15",
3940
"@types/node": "^22.9.1",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Preview/index.tsx

Lines changed: 9 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,34 @@
1-
import initSwc, { transformSync } from "@swc/wasm-web"
21
import type React from "react"
32
import { useCallback, useEffect, useRef } from "react"
43

54
import { useImportMap } from "@/hooks/impormap"
5+
import { transformCode } from "@/hooks/swc"
6+
import logger from "@/utils/logger"
67
import basicTemplate from "./basic.html?raw"
78

89
interface Props {
910
value: string
1011
}
1112

1213
const Preview: React.FC<Props> = ({ value }) => {
13-
const hasSwcInstance = useRef<boolean>(false)
1414
const iframeRef = useRef<HTMLIFrameElement>(null)
1515

1616
// function reload() {
1717
// iframeRef.current?.contentWindow?.location.reload()
1818
// }
1919

20-
async function swcMount() {
21-
try {
22-
// 使用更可靠的方式初始化 SWC
23-
await initSwc({
24-
async instantiateWasm(info, receiveInstance) {
25-
try {
26-
const response = await fetch(
27-
"/node_modules/@swc/wasm-web/wasm_bg.wasm",
28-
)
29-
const wasmBytes = await response.arrayBuffer()
30-
return WebAssembly.instantiate(wasmBytes, info).then((instance) => {
31-
receiveInstance(instance)
32-
return instance
33-
})
34-
} catch (e) {
35-
console.error("Failed to instantiate WebAssembly:", e)
36-
throw e
37-
}
38-
},
39-
})
40-
hasSwcInstance.current = true
41-
executeCode()
42-
} catch (error) {
43-
console.error("React-Repl: Error in load swc:", error)
44-
if (iframeRef.current?.contentDocument) {
45-
iframeRef.current.contentDocument.body.innerHTML = `<pre style="color: red;">Failed to initialize SWC: ${error}</pre>`
46-
}
47-
}
48-
}
49-
50-
function executeCode() {
20+
async function executeCode() {
5121
if (!iframeRef.current) {
52-
console.warn("React-Repl: iframe element is not yet available")
53-
return
54-
}
55-
56-
if (!hasSwcInstance.current) {
57-
console.warn("React-Repl: swc instance is not yet available")
22+
logger.warn("iframe element is not yet available")
5823
return
5924
}
6025

6126
try {
62-
const transformed = transformSync(value, {
63-
jsc: {
64-
parser: {
65-
syntax: "ecmascript",
66-
jsx: true,
67-
},
68-
target: "es2016",
69-
loose: false,
70-
minify: {
71-
compress: {
72-
arguments: false,
73-
arrows: true,
74-
booleans: true,
75-
booleans_as_integers: false,
76-
collapse_vars: true,
77-
comparisons: true,
78-
computed_props: true,
79-
conditionals: true,
80-
dead_code: true,
81-
directives: true,
82-
drop_console: false,
83-
drop_debugger: true,
84-
evaluate: true,
85-
expression: false,
86-
hoist_funs: false,
87-
hoist_props: true,
88-
hoist_vars: false,
89-
if_return: true,
90-
join_vars: true,
91-
keep_classnames: false,
92-
keep_fargs: true,
93-
keep_fnames: false,
94-
keep_infinity: false,
95-
loops: true,
96-
negate_iife: true,
97-
properties: true,
98-
reduce_funcs: false,
99-
reduce_vars: false,
100-
side_effects: true,
101-
switches: true,
102-
typeofs: true,
103-
unsafe: false,
104-
unsafe_arrows: false,
105-
unsafe_comps: false,
106-
unsafe_Function: false,
107-
unsafe_math: false,
108-
unsafe_symbols: false,
109-
unsafe_methods: false,
110-
unsafe_proto: false,
111-
unsafe_regexp: false,
112-
unsafe_undefined: false,
113-
unused: true,
114-
const_to_let: true,
115-
pristine_globals: true,
116-
},
117-
mangle: {
118-
toplevel: false,
119-
keep_classnames: false,
120-
keep_fnames: false,
121-
keep_private_props: false,
122-
ie8: false,
123-
safari10: false,
124-
},
125-
},
126-
},
127-
module: {
128-
type: "es6",
129-
},
130-
minify: true,
131-
isModule: true,
132-
}).code
133-
27+
const { transformedCode } = await transformCode(value)
13428
const iframeDoc = iframeRef.current.contentDocument
13529
if (!iframeDoc) {
136-
console.warn(
137-
"React-Repl: iframe contentDocument is not available - this might happen if the iframe is not loaded or due to cross-origin restrictions",
30+
logger.warn(
31+
"iframe contentDocument is not available - this might happen if the iframe is not loaded or due to cross-origin restrictions",
13832
)
13933
return
14034
}
@@ -146,15 +40,13 @@ const Preview: React.FC<Props> = ({ value }) => {
14640
.replace(
14741
"<!--PREVIEW-OPTIONS-PLACEHOLDER-HTML-->",
14842
`<div id="root"></div>
149-
<script type="module">${transformed}</script>`,
43+
<script type="module">${transformedCode}</script>`,
15044
)
15145

152-
console.log("htmlContent", htmlContent)
153-
15446
iframeDoc.write(htmlContent)
15547
iframeDoc.close()
15648
} catch (error) {
157-
console.error("React-Repl: Error in executeCode:", error)
49+
logger.error("Error in executeCode:", error)
15850
const iframeDoc = iframeRef.current?.contentDocument
15951
if (iframeDoc) {
16052
iframeDoc.body.innerHTML = `<pre style="color: red;">${error}</pre>`
@@ -169,13 +61,6 @@ const Preview: React.FC<Props> = ({ value }) => {
16961
}
17062
}, [])
17163

172-
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
173-
useEffect(() => {
174-
if (!hasSwcInstance.current) {
175-
swcMount()
176-
}
177-
}, [])
178-
17964
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
18065
useEffect(() => {
18166
executeCode()

src/hooks/swc.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import logger from "@/utils/logger"
2+
import * as swc from "@swc/wasm-web"
3+
import type { Options } from "@swc/wasm-web"
4+
5+
const SWC_COMPILER_CONFIG: Options = {
6+
jsc: {
7+
parser: {
8+
syntax: "ecmascript",
9+
jsx: true,
10+
},
11+
target: "es2016",
12+
loose: false,
13+
minify: {
14+
compress: false,
15+
mangle: false,
16+
},
17+
},
18+
module: {
19+
type: "es6",
20+
},
21+
minify: false,
22+
isModule: true,
23+
}
24+
25+
const SWC_PREVIEW_CONFIG: Options = {
26+
jsc: {
27+
parser: {
28+
syntax: "ecmascript",
29+
jsx: true,
30+
},
31+
target: "es5",
32+
loose: false,
33+
minify: {
34+
compress: {
35+
arguments: false,
36+
arrows: true,
37+
booleans: true,
38+
booleans_as_integers: false,
39+
collapse_vars: true,
40+
comparisons: true,
41+
computed_props: true,
42+
conditionals: true,
43+
dead_code: true,
44+
directives: true,
45+
drop_console: false,
46+
drop_debugger: true,
47+
evaluate: true,
48+
expression: false,
49+
hoist_funs: false,
50+
hoist_props: true,
51+
hoist_vars: false,
52+
if_return: true,
53+
join_vars: true,
54+
keep_classnames: false,
55+
keep_fargs: true,
56+
keep_fnames: false,
57+
keep_infinity: false,
58+
loops: true,
59+
negate_iife: true,
60+
properties: true,
61+
reduce_funcs: false,
62+
reduce_vars: false,
63+
side_effects: true,
64+
switches: true,
65+
typeofs: true,
66+
unsafe: false,
67+
unsafe_arrows: false,
68+
unsafe_comps: false,
69+
unsafe_math: false,
70+
unsafe_symbols: false,
71+
unsafe_methods: false,
72+
unsafe_proto: false,
73+
unsafe_regexp: false,
74+
unsafe_undefined: false,
75+
unused: true,
76+
},
77+
mangle: {
78+
toplevel: false,
79+
keep_classnames: false,
80+
keep_fnames: false,
81+
keep_private_props: false,
82+
ie8: false,
83+
safari10: false,
84+
},
85+
},
86+
},
87+
module: {
88+
type: "es6",
89+
},
90+
minify: true,
91+
isModule: true,
92+
}
93+
94+
let swcInstance: typeof swc | null = null
95+
96+
export async function initSwc() {
97+
if (!swcInstance) {
98+
try {
99+
if (!swcInstance) {
100+
await swc.default()
101+
swcInstance = swc
102+
logger.log("SWC initialized successfully")
103+
}
104+
} catch (error) {
105+
logger.error("Failed to initialize SWC:", error)
106+
throw error
107+
}
108+
}
109+
return swcInstance
110+
}
111+
112+
export async function transformCode(
113+
code: string,
114+
): Promise<{ transformedCode: string; compiledCode: string }> {
115+
try {
116+
const instance = await initSwc()
117+
if (!instance) {
118+
throw new Error("SWC instance not initialized")
119+
}
120+
121+
const res = await Promise.all([
122+
instance.transform(code, SWC_PREVIEW_CONFIG),
123+
instance.transform(code, SWC_COMPILER_CONFIG),
124+
])
125+
126+
return {
127+
transformedCode: res[0].code,
128+
compiledCode: res[1].code,
129+
}
130+
} catch (error) {
131+
logger.error("Transform error:", error)
132+
throw error
133+
}
134+
}
135+
136+
export async function parse(code: string) {
137+
try {
138+
const instance = await initSwc()
139+
if (!instance) {
140+
throw new Error("SWC instance not initialized")
141+
}
142+
143+
const AST = instance.parseSync(code, {
144+
syntax: "ecmascript",
145+
jsx: true,
146+
target: "es2016",
147+
})
148+
149+
return AST
150+
} catch (error) {
151+
logger.error("Parse error:", error)
152+
throw error
153+
}
154+
}

src/template/hello.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import React from "react"
1+
import React, { useState } from "react"
22
import { createRoot } from "react-dom/client";
33

44
export default function App() {
5+
const [count, setCount] = useState(0)
6+
7+
const onClick = () => {
8+
setCount(count + 1)
9+
}
510
return (
611
<div>
712
<h1>Hello World</h1>
13+
<div style={{display: 'flex', gap: '10px'}}>
14+
<span>Count: {count}</span>
15+
<button type="button" onClick={onClick}> + 1</button>
16+
</div>
817
</div>
918
)
1019
}

0 commit comments

Comments
 (0)