devjar 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1
- package/lib/core.mjs +191 -35
- package/lib/index.d.ts +1 -1
- package/lib/index.mjs +2 -180
- package/lib/module.mjs +46 -0
- package/lib/render.mjs +21 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -4,15 +4,68 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
### Introduction
|
|
8
|
+
|
|
9
|
+
devjar is a library that enables you to live test and share your code snippets and examples with others. devjar will generate a live code editor where you can run your code snippets and view the results in real-time based on the provided code content of your React app.
|
|
10
|
+
|
|
11
|
+
Notice: devjar only works for browser runtime at the moment. It will always render the default export component in `index.js` as the app entry.
|
|
12
|
+
|
|
7
13
|
### Install
|
|
8
14
|
|
|
9
15
|
```sh
|
|
10
16
|
yarn add devjar
|
|
11
17
|
```
|
|
12
18
|
|
|
19
|
+
|
|
13
20
|
### Usage
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
#### `<DevJar>`
|
|
23
|
+
|
|
24
|
+
`DevJar` is a react component that allows you to develop and test your code directly in the browser, using a CDN to load your dependencies.
|
|
25
|
+
|
|
26
|
+
**Props**
|
|
27
|
+
|
|
28
|
+
* `files`: An object that specifies the files you want to include in your development environment.
|
|
29
|
+
* `getModuleUrl`: A function that maps module names to CDN URLs.
|
|
30
|
+
* `onError`: Callback function of error event from the iframe sandbox. By default `console.log`.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
```jsx
|
|
34
|
+
import { DevJar } from 'devjar'
|
|
35
|
+
|
|
36
|
+
const CDN_HOST = 'https://esm.sh'
|
|
37
|
+
|
|
38
|
+
const files = {
|
|
39
|
+
'index.js': `export default function App() { return 'hello world' }`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function App() {
|
|
43
|
+
return (
|
|
44
|
+
<DevJar
|
|
45
|
+
files={files}
|
|
46
|
+
getModuleUrl={(m) => {
|
|
47
|
+
return `${CDN_HOST}/${m}`
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### `useLiveCode(options)`
|
|
55
|
+
|
|
56
|
+
**Parameters**
|
|
57
|
+
|
|
58
|
+
* `options`
|
|
59
|
+
* `getModulePath(module)`: A function that receives the module name and returns the CDN url of each imported module path. For example, import React from 'react' will load React from skypack.dev/react.
|
|
60
|
+
|
|
61
|
+
**Returns**
|
|
62
|
+
|
|
63
|
+
* `state`
|
|
64
|
+
* `ref`: A reference to the iframe element where the live coding will be executed.
|
|
65
|
+
* `error`: An error message in case the live coding encounters an issue.
|
|
66
|
+
* `load(codeFiles)`: void: Loads code files and executes them as live code.
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
16
69
|
import { useLiveCode } from 'devjar'
|
|
17
70
|
|
|
18
71
|
function Playground() {
|
package/lib/core.mjs
CHANGED
|
@@ -1,46 +1,202 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
1
|
+
import { useEffect, useCallback, useState, useId, useRef } from 'react'
|
|
2
|
+
import { createModule } from './module.mjs'
|
|
3
|
+
import { transform } from 'sucrase'
|
|
4
|
+
import { init, parse } from 'es-module-lexer'
|
|
5
|
+
|
|
6
|
+
let esModuleLexerInit
|
|
7
|
+
|
|
8
|
+
const isRelative = s => s.startsWith('./')
|
|
9
|
+
|
|
10
|
+
function transformCode(_code, getModuleUrl, externals) {
|
|
11
|
+
const code = transform(_code, {
|
|
12
|
+
transforms: ['jsx', 'typescript'],
|
|
13
|
+
}).code
|
|
14
|
+
|
|
15
|
+
return replaceImports(code, getModuleUrl, externals)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function replaceImports(_code, getModuleUrl, externals) {
|
|
19
|
+
let code = ''
|
|
20
|
+
let lastIndex = 0
|
|
21
|
+
let hasReactImports = false
|
|
22
|
+
const [imports] = parse(_code)
|
|
23
|
+
imports.forEach(({ s, e, ss, se, n }) => {
|
|
24
|
+
code += _code.slice(lastIndex, ss)
|
|
25
|
+
code += _code.substring(ss, s)
|
|
26
|
+
code += isRelative(n)
|
|
27
|
+
? ('@' + n.slice(2))
|
|
28
|
+
: externals.has(n) ? n : getModuleUrl(n)
|
|
29
|
+
code += _code.substring(e, se)
|
|
30
|
+
lastIndex = se
|
|
14
31
|
|
|
15
|
-
function updateImportMap(imports) {
|
|
16
|
-
imports['react'] = getModulePath('react')
|
|
17
|
-
imports['react-dom'] = getModulePath('react-dom')
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
currentImportMap.parentNode.removeChild(currentImportMap)
|
|
33
|
+
if (n === 'react') {
|
|
34
|
+
const statement = _code.slice(ss, se)
|
|
35
|
+
if (statement.includes('React')) {
|
|
36
|
+
hasReactImports = true
|
|
37
|
+
}
|
|
25
38
|
}
|
|
26
|
-
|
|
39
|
+
})
|
|
40
|
+
code += _code.substring(lastIndex)
|
|
41
|
+
|
|
42
|
+
if (!hasReactImports) {
|
|
43
|
+
code = `import React from 'react';\n${code}`
|
|
27
44
|
}
|
|
45
|
+
return code
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createRenderer(createModule_, getModuleUrl) {
|
|
49
|
+
let reactRoot
|
|
28
50
|
|
|
51
|
+
async function render(files) {
|
|
52
|
+
const mod = await createModule_(files, { getModuleUrl })
|
|
53
|
+
const React_ = await self.importShim('react')
|
|
54
|
+
const ReactDOM_ = await self.importShim('react-dom')
|
|
29
55
|
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
const _jsx = React_.createElement
|
|
57
|
+
const root = document.getElementById('root')
|
|
58
|
+
class ErrorBoundary extends React_.Component {
|
|
59
|
+
constructor(props) {
|
|
60
|
+
super(props)
|
|
61
|
+
this.state = { error: null }
|
|
62
|
+
}
|
|
63
|
+
componentDidCatch(error) {
|
|
64
|
+
this.setState({ error })
|
|
65
|
+
}
|
|
66
|
+
render() {
|
|
67
|
+
if (this.state.error) {
|
|
68
|
+
return _jsx('div', null, this.state.error.message)
|
|
69
|
+
}
|
|
70
|
+
return this.props.children
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isReact18 = !!ReactDOM_.createRoot
|
|
75
|
+
if (isReact18 && !reactRoot) {
|
|
76
|
+
reactRoot = ReactDOM_.createRoot(root)
|
|
77
|
+
}
|
|
78
|
+
const Component = mod.default
|
|
79
|
+
const element = _jsx(ErrorBoundary, null, _jsx(Component))
|
|
80
|
+
if (isReact18) {
|
|
81
|
+
reactRoot.render(element)
|
|
82
|
+
} else {
|
|
83
|
+
ReactDOM_.render(element, root)
|
|
84
|
+
}
|
|
32
85
|
}
|
|
33
86
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
87
|
+
return render
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createMainScript({ uid }) {
|
|
91
|
+
const code = (
|
|
92
|
+
`'use strict';
|
|
93
|
+
const _createModule = ${createModule.toString()};
|
|
94
|
+
const _createRenderer = ${createRenderer.toString()};
|
|
95
|
+
|
|
96
|
+
const getModuleUrl = (m) => window.parent.__devjar__[globalThis.uid].getModuleUrl(m)
|
|
41
97
|
|
|
42
|
-
|
|
43
|
-
|
|
98
|
+
globalThis.uid = ${JSON.stringify(uid)};
|
|
99
|
+
globalThis.__render__ = _createRenderer(_createModule, getModuleUrl);
|
|
100
|
+
`)
|
|
101
|
+
return code
|
|
44
102
|
}
|
|
45
103
|
|
|
46
|
-
|
|
104
|
+
function useLiveCode({ getModuleUrl }) {
|
|
105
|
+
const iframeRef = useRef()
|
|
106
|
+
const [error, setError] = useState()
|
|
107
|
+
const rerender = useState({})[1]
|
|
108
|
+
const scriptRef = useRef(typeof window !== 'undefined' ? document.createElement('script') : null)
|
|
109
|
+
const uid = useId()
|
|
110
|
+
|
|
111
|
+
// Let getModuleUrl executed on parent window side since it might involve
|
|
112
|
+
// variables that iframe cannot access.
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!globalThis.__devjar__) {
|
|
115
|
+
globalThis.__devjar__ = {};
|
|
116
|
+
}
|
|
117
|
+
globalThis.__devjar__[uid] = {
|
|
118
|
+
getModuleUrl,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
if (globalThis.__devjar__) {
|
|
123
|
+
delete globalThis.__devjar__[uid]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, [])
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const iframe = iframeRef.current
|
|
130
|
+
const doc = iframe && iframe.contentDocument
|
|
131
|
+
|
|
132
|
+
if (iframe) {
|
|
133
|
+
const doc = iframe.contentDocument
|
|
134
|
+
const div = document.createElement('div')
|
|
135
|
+
const script = scriptRef.current
|
|
136
|
+
const scriptContent = createMainScript({ uid })
|
|
137
|
+
|
|
138
|
+
div.id = 'root'
|
|
139
|
+
script.type = 'module'
|
|
140
|
+
script.id = 'main'
|
|
141
|
+
script.src = `data:text/javascript;utf-8,${encodeURIComponent(scriptContent)}`
|
|
142
|
+
|
|
143
|
+
doc.body.appendChild(div)
|
|
144
|
+
doc.body.appendChild(script)
|
|
145
|
+
}
|
|
146
|
+
return () => {
|
|
147
|
+
if (iframe) {
|
|
148
|
+
doc.body.removeChild(doc.getElementById('root'))
|
|
149
|
+
doc.body.removeChild(doc.getElementById('main'))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
const load = useCallback(async (files) => {
|
|
155
|
+
if (!esModuleLexerInit) {
|
|
156
|
+
await init
|
|
157
|
+
esModuleLexerInit = true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (files) {
|
|
161
|
+
const overrideExternals =
|
|
162
|
+
new Set(Object.keys(files).filter(name => !isRelative(name) && name !== 'index.js'))
|
|
163
|
+
|
|
164
|
+
// Always share react as externals
|
|
165
|
+
overrideExternals.add('react')
|
|
166
|
+
overrideExternals.add('react-dom')
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const transformedFiles = Object.keys(files).reduce((res, filename) => {
|
|
170
|
+
const key = isRelative(filename) ? ('@' + filename.slice(2)) : filename
|
|
171
|
+
res[key] = transformCode(files[filename], getModuleUrl, overrideExternals)
|
|
172
|
+
return res
|
|
173
|
+
}, {})
|
|
174
|
+
|
|
175
|
+
const iframe = iframeRef.current
|
|
176
|
+
const script = scriptRef.current
|
|
177
|
+
if (iframe) {
|
|
178
|
+
const render = iframe.contentWindow.__render__
|
|
179
|
+
if (render) {
|
|
180
|
+
render(transformedFiles)
|
|
181
|
+
} else {
|
|
182
|
+
// if render is not loaded yet, wait until it's loaded
|
|
183
|
+
script.onload = () => {
|
|
184
|
+
iframe.contentWindow.__render__(transformedFiles)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
setError()
|
|
189
|
+
} catch (e) {
|
|
190
|
+
setError(e)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
rerender({})
|
|
194
|
+
}, [])
|
|
195
|
+
|
|
196
|
+
return { ref: iframeRef, error, load }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
createModule,
|
|
201
|
+
useLiveCode,
|
|
202
|
+
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.mjs
CHANGED
|
@@ -1,180 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { transform } from 'sucrase'
|
|
4
|
-
import { init, parse } from 'es-module-lexer'
|
|
5
|
-
|
|
6
|
-
let esModuleLexerInit
|
|
7
|
-
|
|
8
|
-
const isRelative = s => s.startsWith('./')
|
|
9
|
-
|
|
10
|
-
function transformCode(_code, getModulePath, externals) {
|
|
11
|
-
const code = transform(_code, {
|
|
12
|
-
transforms: ['jsx', 'typescript'],
|
|
13
|
-
}).code
|
|
14
|
-
|
|
15
|
-
return replaceImports(code, getModulePath, externals)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function replaceImports(_code, getModulePath, externals) {
|
|
19
|
-
let code = ''
|
|
20
|
-
let lastIndex = 0
|
|
21
|
-
let hasReactImports = false
|
|
22
|
-
const [imports] = parse(_code)
|
|
23
|
-
imports.forEach(({ s, e, ss, se, n }) => {
|
|
24
|
-
code += _code.slice(lastIndex, ss)
|
|
25
|
-
code += _code.substring(ss, s)
|
|
26
|
-
code += isRelative(n)
|
|
27
|
-
? ('@' + n.slice(2))
|
|
28
|
-
: externals.has(n) ? n : getModulePath(n)
|
|
29
|
-
code += _code.substring(e, se)
|
|
30
|
-
lastIndex = se
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (n === 'react') {
|
|
34
|
-
const statement = _code.slice(ss, se)
|
|
35
|
-
if (statement.includes('React')) {
|
|
36
|
-
hasReactImports = true
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
code += _code.substring(lastIndex)
|
|
41
|
-
|
|
42
|
-
if (!hasReactImports) {
|
|
43
|
-
code = `import React from 'react';\n${code}`
|
|
44
|
-
}
|
|
45
|
-
return code
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function createRenderer(createModule_, getModulePath) {
|
|
49
|
-
let reactRoot
|
|
50
|
-
|
|
51
|
-
async function render(files) {
|
|
52
|
-
const mod = await createModule_(files, { getModulePath })
|
|
53
|
-
const React_ = await self.importShim('react')
|
|
54
|
-
const ReactDOM_ = await self.importShim('react-dom')
|
|
55
|
-
|
|
56
|
-
const _jsx = React_.createElement
|
|
57
|
-
const root = document.getElementById('root')
|
|
58
|
-
class ErrorBoundary extends React_.Component {
|
|
59
|
-
constructor(props) {
|
|
60
|
-
super(props)
|
|
61
|
-
this.state = { error: null }
|
|
62
|
-
}
|
|
63
|
-
componentDidCatch(error) {
|
|
64
|
-
this.setState({ error })
|
|
65
|
-
}
|
|
66
|
-
render() {
|
|
67
|
-
if (this.state.error) {
|
|
68
|
-
return _jsx('div', null, this.state.error.message)
|
|
69
|
-
}
|
|
70
|
-
return this.props.children
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const isReact18 = !!ReactDOM_.createRoot
|
|
75
|
-
if (isReact18 && !reactRoot) {
|
|
76
|
-
reactRoot = ReactDOM_.createRoot(root)
|
|
77
|
-
}
|
|
78
|
-
const Component = mod.default
|
|
79
|
-
const element = _jsx(ErrorBoundary, null, _jsx(Component))
|
|
80
|
-
if (isReact18) {
|
|
81
|
-
reactRoot.render(element)
|
|
82
|
-
} else {
|
|
83
|
-
ReactDOM_.render(element, root)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return render
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function createMainScript({ getModulePath }) {
|
|
91
|
-
const code = (
|
|
92
|
-
`'use strict';
|
|
93
|
-
const _createModule = ${createModule.toString()};
|
|
94
|
-
const _createRenderer = ${createRenderer.toString()};
|
|
95
|
-
const _getModulePath = ${getModulePath.toString()};
|
|
96
|
-
|
|
97
|
-
globalThis.__render__ = _createRenderer(_createModule, _getModulePath);
|
|
98
|
-
`)
|
|
99
|
-
return code
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
export function useLiveCode({ getModulePath }) {
|
|
104
|
-
const iframeRef = useRef()
|
|
105
|
-
const [error, setError] = useState()
|
|
106
|
-
const rerender = useState({})[1]
|
|
107
|
-
const scriptRef = useRef(typeof window !== 'undefined' ? document.createElement('script') : null)
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const iframe = iframeRef.current
|
|
111
|
-
const doc = iframe && iframe.contentDocument
|
|
112
|
-
|
|
113
|
-
if (iframe) {
|
|
114
|
-
const doc = iframe.contentDocument
|
|
115
|
-
const div = document.createElement('div')
|
|
116
|
-
const script = scriptRef.current
|
|
117
|
-
const scriptContent = createMainScript({ getModulePath })
|
|
118
|
-
|
|
119
|
-
div.id = 'root'
|
|
120
|
-
script.type = 'module'
|
|
121
|
-
script.id = 'main'
|
|
122
|
-
script.src = `data:text/javascript;utf-8,${encodeURIComponent(scriptContent)}`
|
|
123
|
-
|
|
124
|
-
doc.body.appendChild(div)
|
|
125
|
-
doc.body.appendChild(script)
|
|
126
|
-
}
|
|
127
|
-
return () => {
|
|
128
|
-
if (iframe) {
|
|
129
|
-
doc.body.removeChild(doc.getElementById('root'))
|
|
130
|
-
doc.body.removeChild(doc.getElementById('main'))
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}, [])
|
|
134
|
-
|
|
135
|
-
const load = useCallback(async (files) => {
|
|
136
|
-
if (!esModuleLexerInit) {
|
|
137
|
-
await init
|
|
138
|
-
esModuleLexerInit = true
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (files) {
|
|
142
|
-
const overrideExternals =
|
|
143
|
-
new Set(Object.keys(files).filter(name => !isRelative(name) && name !== 'index.js'))
|
|
144
|
-
|
|
145
|
-
// Always share react as externals
|
|
146
|
-
overrideExternals.add('react')
|
|
147
|
-
overrideExternals.add('react-dom')
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const transformedFiles = Object.keys(files).reduce((res, filename) => {
|
|
151
|
-
const key = isRelative(filename) ? ('@' + filename.slice(2)) : filename
|
|
152
|
-
res[key] = transformCode(files[filename], getModulePath, overrideExternals)
|
|
153
|
-
return res
|
|
154
|
-
}, {})
|
|
155
|
-
|
|
156
|
-
const iframe = iframeRef.current
|
|
157
|
-
const script = scriptRef.current
|
|
158
|
-
if (iframe) {
|
|
159
|
-
const render = iframe.contentWindow.__render__
|
|
160
|
-
if (render) {
|
|
161
|
-
render(transformedFiles)
|
|
162
|
-
} else {
|
|
163
|
-
// if render is not loaded yet, wait until it's loaded
|
|
164
|
-
script.onload = () => {
|
|
165
|
-
iframe.contentWindow.__render__(transformedFiles)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
setError()
|
|
170
|
-
} catch (e) {
|
|
171
|
-
setError(e)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
rerender({})
|
|
175
|
-
}, [])
|
|
176
|
-
|
|
177
|
-
return { ref: iframeRef, error, load }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export { createModule }
|
|
1
|
+
export { DevJar } from './render.mjs'
|
|
2
|
+
export { useLiveCode } from './core.mjs'
|
package/lib/module.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
async function createModule(files, { getModuleUrl }) {
|
|
2
|
+
let currentImportMap
|
|
3
|
+
let shim
|
|
4
|
+
|
|
5
|
+
async function setupImportMap() {
|
|
6
|
+
if (shim) return shim
|
|
7
|
+
window.esmsInitOptions = {
|
|
8
|
+
shimMode: true,
|
|
9
|
+
mapOverrides: true,
|
|
10
|
+
}
|
|
11
|
+
shim = import(/* webpackIgnore: true */ getModuleUrl('es-module-shims'))
|
|
12
|
+
await shim
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function updateImportMap(imports) {
|
|
16
|
+
imports['react'] = getModuleUrl('react')
|
|
17
|
+
imports['react-dom'] = getModuleUrl('react-dom')
|
|
18
|
+
|
|
19
|
+
const script = document.createElement('script')
|
|
20
|
+
script.type = 'importmap-shim'
|
|
21
|
+
script.innerHTML = JSON.stringify({ imports })
|
|
22
|
+
document.body.appendChild(script)
|
|
23
|
+
if (currentImportMap) {
|
|
24
|
+
currentImportMap.parentNode.removeChild(currentImportMap)
|
|
25
|
+
}
|
|
26
|
+
currentImportMap = script
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
function createInlinedModule(code) {
|
|
31
|
+
return `data:text/javascript;utf-8,${encodeURIComponent(code)}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await setupImportMap()
|
|
35
|
+
const imports = Object.fromEntries(
|
|
36
|
+
Object.entries(files).map(([key, code]) => [
|
|
37
|
+
key,
|
|
38
|
+
createInlinedModule(code),
|
|
39
|
+
])
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
updateImportMap(imports)
|
|
43
|
+
return self.importShim('index.js')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { createModule }
|
package/lib/render.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import { useLiveCode } from './core.mjs'
|
|
3
|
+
|
|
4
|
+
const defaultOnError = typeof window !== 'undefined' ? console.error : (() => {})
|
|
5
|
+
|
|
6
|
+
export function DevJar({ files, getModuleUrl, onError = defaultOnError, ...props }) {
|
|
7
|
+
const onErrorRef = useRef(onError)
|
|
8
|
+
const { ref, error, load } = useLiveCode({ getModuleUrl })
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
onErrorRef.current(error)
|
|
12
|
+
}, [error])
|
|
13
|
+
|
|
14
|
+
// load code files and execute them as live code
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
load(files)
|
|
17
|
+
}, [files])
|
|
18
|
+
|
|
19
|
+
// Attach the ref to an iframe element for runtime of code execution
|
|
20
|
+
return React.createElement('iframe', { ...props, ref })
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devjar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./lib/index.mjs",
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
"dev": "next dev ./docs"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"react": "^
|
|
20
|
+
"react": "^18.2.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"es-module-lexer": "
|
|
24
|
-
"es-module-shims": "
|
|
25
|
-
"sucrase": "3.
|
|
23
|
+
"es-module-lexer": "0.10.5",
|
|
24
|
+
"es-module-shims": "1.5.9",
|
|
25
|
+
"sucrase": "3.23.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"codice": "latest",
|
|
29
29
|
"devjar": "link:./",
|
|
30
30
|
"lodash-es": "^4.17.21",
|
|
31
|
-
"next": "
|
|
32
|
-
"react": "^18.
|
|
33
|
-
"react-dom": "^18.
|
|
34
|
-
"sugar-high": "^0.4.
|
|
31
|
+
"next": "^13.1.6",
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"react-dom": "^18.2.0",
|
|
34
|
+
"sugar-high": "^0.4.5"
|
|
35
35
|
}
|
|
36
36
|
}
|