Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-runner-with-swc (Need to create a new branch) #166

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"prettier": "prettier --write '**/src/**/*.{ts,tsx}'"
},
"dependencies": {
"@swc/wasm-web": "^1.3.66",
"sucrase": "^3.21.0"
},
"peerDependencies": {
Expand Down
27 changes: 24 additions & 3 deletions packages/react-runner/src/Runner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, ReactElement } from 'react'

import init from '@swc/wasm-web/wasm-web'
import { generateElement } from './utils'
import { RunnerOptions, Scope } from './types'

Expand All @@ -13,6 +13,13 @@ type RunnerState = {
error: Error | null
prevCode: string | null
prevScope: Scope | undefined
readied: boolean
}
let drive: ReturnType<typeof init>

export function setDrive(swc?: Parameters<typeof init> | string) {
drive = drive || init(swc)
return drive
}

export class Runner extends Component<RunnerProps, RunnerState> {
Expand All @@ -21,13 +28,17 @@ export class Runner extends Component<RunnerProps, RunnerState> {
error: null,
prevCode: null,
prevScope: undefined,
readied: false,
}

static getDerivedStateFromProps(
props: RunnerProps,
state: RunnerState
): Partial<RunnerState> | null {
// only regenerate on code/scope change
if (!state.readied) {
return null
}
if (state.prevCode === props.code && state.prevScope === props.scope) {
return null
}
Expand All @@ -54,14 +65,24 @@ export class Runner extends Component<RunnerProps, RunnerState> {
}

componentDidMount() {
this.props.onRendered?.(this.state.error || undefined)
if (this.state.readied) {
this.props.onRendered?.(this.state.error || undefined)
} else {
setDrive()
drive.then(() => {
this.setState({
readied: true,
})
})
}
}

shouldComponentUpdate(nextProps: RunnerProps, nextState: RunnerState) {
return (
nextProps.code !== this.props.code ||
nextProps.scope !== this.props.scope ||
nextState.error !== this.state.error
nextState.error !== this.state.error ||
nextState.readied !== this.state.readied
)
}

Expand Down
28 changes: 23 additions & 5 deletions packages/react-runner/src/transform.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { transform as _transform } from 'sucrase'
import { transformSync as _transform } from '@swc/wasm-web/wasm-web'
// import { transform as _transform1 } from 'sucrase'

const config: any = {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
target: 'es5',
loose: false,
minify: {
compress: false,
mangle: false,
},
},
module: {
type: 'commonjs',
},
minify: false,
isModule: true,
}

export const transform = (code: string) => {
return _transform(code, {
transforms: ['jsx', 'typescript', 'imports'],
production: true,
}).code.substring(13) // remove leading `"use strict";`
return _transform(code, config).code.substring(13) // remove leading `"use strict";`
}

const firstStatementRegexp =
Expand Down
22 changes: 1 addition & 21 deletions packages/react-runner/src/useRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,13 @@ export const useRunner = ({
scope,
disableCache,
}: UseRunnerProps): UseRunnerReturn => {
const isMountRef = useRef(true)
const elementRef = useRef<ReactElement | null>(null)

const [state, setState] = useState<UseRunnerReturn>(() => {
const element = createElement(Runner, {
code,
scope,
onRendered: (error) => {
if (error) {
setState({
element: disableCache ? null : elementRef.current,
error: error.toString(),
})
} else {
elementRef.current = element
}
},
})
return { element, error: null }
return { element: null, error: null }
})

useEffect(() => {
if (isMountRef.current) {
isMountRef.current = false
return
}

const element = createElement(Runner, {
code,
scope,
Expand Down
1 change: 1 addition & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"@radix-ui/react-icons": "^1.1.0",
"@swc/wasm-web": "^1.3.102",
"clsx": "^1.1.1",
"construct-style-sheets-polyfill": "^3.1.0",
"lz-string": "^1.4.4",
Expand Down
13 changes: 11 additions & 2 deletions playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import React, { useEffect, useState, useReducer, lazy } from 'react'

import { Header } from './components/Header'
import { TabBar } from './components/TabBar'
import { Preview } from './components/Preview'
import { ResizePane } from './components/ResizePane'
import { SafeSuspense } from './components/SafeSuspense'
import { getHash, getHashFiles, updateHash } from './utils/urlHash'
import { defaultHash } from './utils/defaultHash'
import styles from './App.module.css'

const Editor = lazy(() => import('./components/Editor'))
const Preview = lazy(
() =>
new Promise((res) => {
import('./components/Preview').then(({ Preview }) => {
res({ default: Preview })
})
})
)

const supportedExts = ['js', 'jsx', 'ts', 'tsx', 'css']
const supportedExtsText = supportedExts.map((x) => `.${x}`).join(' ,')
Expand Down Expand Up @@ -102,7 +109,9 @@ function App() {
</SafeSuspense>
</ResizePane>
<div className={styles.PreviewPane}>
<Preview files={files} />
<SafeSuspense fallback={null}>
<Preview files={files} />
</SafeSuspense>
</div>
</div>
</>
Expand Down
167 changes: 92 additions & 75 deletions playground/src/hooks/useAsyncRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import React, {
ReactElement,
} from 'react'
import 'construct-style-sheets-polyfill'
import { Runner, UseRunnerProps, UseRunnerReturn } from 'react-runner'
import { Runner, UseRunnerProps, UseRunnerReturn, setDrive } from 'react-runner'
import wasmUrl from '@swc/wasm-web/wasm-web_bg.wasm?url'

import { withFiles } from '../utils/withFiles'
import { useUncaughtError } from './useUncaughtError'
Expand All @@ -15,6 +16,8 @@ const esmCDN = import.meta.env.VITE_ESM_CDN
const esmCDNQuery = import.meta.env.VITE_ESM_CDN_QUERY
const cssCDN = import.meta.env.VITE_CSS_CDN

const Swc = setDrive(wasmUrl)

const importModuleRegexp = /^import [^'"]* from ['"]([^\.'"\n ][^'"\n ]*)['"]/gm
const importCssRegexp = /^import +['"]([^\.'"\n ][^'"\n ]*\.css)['"]/gm
const remoteRegexp = /^https?:\/\//
Expand Down Expand Up @@ -118,6 +121,7 @@ export const useAsyncRunner = ({
const elementRef = useRef<ReactElement | null>(null)
const styleSheetsRef = useRef<CSSStyleSheet[]>([])
const scopeRef = useRef(scope)
const [loadSwc, setLoadSwc] = useState(true)
scopeRef.current = scope

const [state, setState] = useState<UseAsyncRunnerReturn>({
Expand All @@ -128,87 +132,100 @@ export const useAsyncRunner = ({
})

useEffect(() => {
const controller = new AbortController()
const code = Object.values(files).join('\n\n')
const trimmedCode = code.trim()
const extractedImports = extractImports(
trimmedCode,
Object.keys(scopeRef.current?.import || {})
)
if (extractedImports[0].length || extractedImports[1].length) {
setState({
isLoading: true,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: null,
})
}
resolveImports(...extractedImports)
.then(([importsMap, styleSheets]) => {
if (controller.signal.aborted) return

const code = normalizeJs(files['App.tsx'])
const jsFiles: Record<string, string> = {}
const cssFiles: Record<string, string> = {}
Object.keys(files).forEach((name) => {
if (name.endsWith('.css')) cssFiles[name] = normalizeCss(files[name])
else if (name !== 'App.tsx') jsFiles[name] = normalizeJs(files[name])
})
Object.values(cssFiles).forEach((css) => {
try {
const style = new CSSStyleSheet()
style.replaceSync(css)
Swc.then(() => {
setLoadSwc(false)
})
}, [])

styleSheets.push(style)
} catch {}
useEffect(() => {
if (!loadSwc) {
const controller = new AbortController()
const code = Object.values(files).join('\n\n')
const trimmedCode = code.trim()
const extractedImports = extractImports(
trimmedCode,
Object.keys(scopeRef.current?.import || {})
)
if (extractedImports[0].length || extractedImports[1].length) {
setState({
isLoading: true,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: null,
})
if (controller.signal.aborted) return
const element = createElement(Runner, {
code,
scope: withFiles(
{
...scopeRef.current,
import: {
react: React,
...Object.keys(cssFiles).reduce((acc, name) => {
acc[`./${name}`] = undefined
return acc
}, {} as Record<string, undefined>),
...scopeRef.current?.import,
...importsMap,
}
resolveImports(...extractedImports)
.then(([importsMap, styleSheets]) => {
if (controller.signal.aborted) return

const code = normalizeJs(files['App.tsx'])
const jsFiles: Record<string, string> = {}
const cssFiles: Record<string, string> = {}
Object.keys(files).forEach((name) => {
if (name.endsWith('.css'))
cssFiles[name] = normalizeCss(files[name])
else if (name !== 'App.tsx')
jsFiles[name] = normalizeJs(files[name])
})
Object.values(cssFiles).forEach((css) => {
try {
const style = new CSSStyleSheet()
style.replaceSync(css)

styleSheets.push(style)
} catch {}
})
if (controller.signal.aborted) return
const element = createElement(Runner, {
code,
scope: withFiles(
{
...scopeRef.current,
import: {
react: React,
...Object.keys(cssFiles).reduce((acc, name) => {
acc[`./${name}`] = undefined
return acc
}, {} as Record<string, undefined>),
...scopeRef.current?.import,
...importsMap,
},
},
jsFiles
),
onRendered: (error) => {
if (controller.signal.aborted) return
if (error) {
setState({
isLoading: false,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: error.toString(),
})
} else {
elementRef.current = element
styleSheetsRef.current = styleSheets
}
},
jsFiles
),
onRendered: (error) => {
if (controller.signal.aborted) return
if (error) {
setState({
isLoading: false,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: error.toString(),
})
} else {
elementRef.current = element
styleSheetsRef.current = styleSheets
}
},
})
if (controller.signal.aborted) return
setState({ isLoading: false, element, styleSheets, error: null })
})
if (controller.signal.aborted) return
setState({ isLoading: false, element, styleSheets, error: null })
})
.catch((error: Error) => {
setState({
isLoading: false,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: error.toString().replace(esmCDN, '').replace(esmCDNQuery, ''),
.catch((error: Error) => {
setState({
isLoading: false,
element: disableCache ? null : elementRef.current,
styleSheets: disableCache ? [] : styleSheetsRef.current,
error: error
.toString()
.replace(esmCDN, '')
.replace(esmCDNQuery, ''),
})
})
})

return () => controller.abort()
}, [files, disableCache])
return () => controller.abort()
}
}, [files, disableCache, loadSwc])

useUncaughtError((error) => {
setState({
Expand Down
5 changes: 5 additions & 0 deletions playground/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@
estree-walker "^2.0.1"
picomatch "^2.2.2"

"@swc/wasm-web@^1.3.102":
version "1.3.102"
resolved "https://registry.npmmirror.com/@swc/wasm-web/-/wasm-web-1.3.102.tgz#001be902c65f329de06369c09155d17bc4862a68"
integrity sha512-av/VdyTZ9jcawcwJGfgfaBEC9mKXfgYRQGMjSgcD8vz/gLQrhPSgce+yHRCqvsEUGaciKsKuyC63I2VgLccDqw==

"@types/body-parser@*":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
Expand Down
Loading