devjar 0.1.0 → 0.2.0

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 CHANGED
@@ -1 +1,51 @@
1
1
  # devjar
2
+ > bundless runtime for your ESM JavaScript project in browser
3
+
4
+
5
+ ![image](https://repository-images.githubusercontent.com/483779830/23b4d7c8-dd8e-48b0-a3ea-c519e8236714)
6
+
7
+ ### Install
8
+
9
+ ```sh
10
+ yarn add devjar
11
+ ```
12
+
13
+ ### Usage
14
+
15
+ ```js
16
+ import { useLiveCode } from 'devjar'
17
+
18
+ function Playground() {
19
+ const { ref, error, load } = useLiveCode({
20
+ getModulePath(modPath) {
21
+ return `https://cdn.skypack.dev/${modPath}`
22
+ }
23
+ })
24
+
25
+ // logging failures
26
+ if (error) {
27
+ console.error(error)
28
+ }
29
+
30
+ // load code files and execute them as live code
31
+ function run() {
32
+ load({
33
+ 'index.js': `export default function Main() { return 'hello world' }`,
34
+ './mod': `...` // other relative modules
35
+ })
36
+ }
37
+
38
+ // Attach the ref to an iframe element for runtime of code execution
39
+ return (
40
+ <div>
41
+ <button onClick={run}>run</h3>
42
+ <iframe ref={ref} />
43
+ </div>
44
+ )
45
+ }
46
+ ```
47
+
48
+ ### License
49
+
50
+ The MIT License (MIT).
51
+
package/lib/core.mjs ADDED
@@ -0,0 +1,46 @@
1
+ async function createModule(files, { getModulePath }) {
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 */ getModulePath('es-module-shims'))
12
+ await shim
13
+ }
14
+
15
+ function updateImportMap(imports) {
16
+ imports['react'] = getModulePath('react')
17
+ imports['react-dom'] = getModulePath('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/index.mjs CHANGED
@@ -1,46 +1,166 @@
1
- async function createModule(files, { getModulePath }) {
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,
1
+ import { useEffect, useCallback, useState, useRef } from 'react'
2
+ import { createModule } from './core.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, 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
+ const [imports] = parse(_code)
20
+ let code = ''
21
+ let lastIndex = 0
22
+ imports.forEach(({ ss, s, e, se, n }) => {
23
+ code += _code.slice(lastIndex, ss)
24
+ code += _code.substring(ss, s)
25
+ code += isRelative(n)
26
+ ? ('@' + n.slice(2))
27
+ : externals.has(n) ? n : getModulePath(n)
28
+ code += _code.substring(e, se)
29
+ lastIndex = se
30
+ })
31
+ code += _code.substring(lastIndex)
32
+ return code
33
+ }
34
+
35
+ function createRenderer(_React0, _ReactDOM0, _createModule, _getModulePath) {
36
+ let reactRoot
37
+
38
+ async function render(files) {
39
+ const mod = await _createModule(files, { getModulePath: _getModulePath })
40
+ const _React = await self.importShim('react')
41
+ const _ReactDOM = await self.importShim('react-dom')
42
+
43
+ const _jsx = _React.createElement
44
+ const root = document.getElementById('root')
45
+ class ErrorBoundary extends _React.Component {
46
+ state = {
47
+ error: null,
48
+ }
49
+ componentDidCatch(error) {
50
+ this.setState({ error })
51
+ }
52
+ render() {
53
+ if (this.state.error) {
54
+ return _jsx('div', null, this.state.error.message)
55
+ }
56
+ return this.props.children
57
+ }
58
+ }
59
+
60
+ const isReact18 = !!_ReactDOM.createRoot
61
+ if (isReact18 && !reactRoot) {
62
+ reactRoot = _ReactDOM.createRoot(root)
63
+ }
64
+ const Component = mod.default
65
+ const element = _jsx(ErrorBoundary, null, _jsx(Component))
66
+ if (isReact18) {
67
+ reactRoot.render(element)
68
+ } else {
69
+ _ReactDOM.render(element, root)
10
70
  }
11
- shim = import(/* webpackIgnore: true */ getModulePath('es-module-shims'))
12
- await shim
13
71
  }
14
72
 
15
- function updateImportMap(imports) {
16
- imports['react'] = getModulePath('react')
17
- imports['react-dom'] = getModulePath('react-dom')
73
+ return render
74
+ }
75
+
76
+ function createMainScript({ getModulePath }) {
77
+ const code = (
78
+ `'use strict';
79
+ const createModule = ${createModule.toString()};
80
+ const createRenderer = ${createRenderer.toString()};
81
+ const getModulePath = ${getModulePath.toString()};
18
82
 
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)
83
+ globalThis.__render__ = createRenderer(0, 0, createModule, getModulePath);
84
+ `)
85
+ return code
86
+ }
87
+
88
+
89
+ export function useLiveCode({ getModulePath }) {
90
+ const iframeRef = useRef()
91
+ const [error, setError] = useState()
92
+ const rerender = useState({})[1]
93
+ const scriptRef = useRef(typeof window !== 'undefined' ? document.createElement('script') : null)
94
+
95
+ useEffect(() => {
96
+ const iframe = iframeRef.current
97
+ const doc = iframe && iframe.contentDocument
98
+
99
+ if (iframe) {
100
+ const doc = iframe.contentDocument
101
+ const div = document.createElement('div')
102
+ const script = scriptRef.current
103
+ const scriptContent = createMainScript({ getModulePath })
104
+
105
+ div.id = 'root'
106
+ script.type = 'module'
107
+ script.id = 'main'
108
+ script.src = `data:text/javascript;utf-8,${encodeURIComponent(scriptContent)}`
109
+
110
+ doc.body.appendChild(div)
111
+ doc.body.appendChild(script)
25
112
  }
26
- currentImportMap = script
27
- }
113
+ return () => {
114
+ if (iframe) {
115
+ doc.body.removeChild(doc.getElementById('root'))
116
+ doc.body.removeChild(doc.getElementById('main'))
117
+ }
118
+ }
119
+ }, [])
28
120
 
121
+ const load = useCallback(async (files) => {
122
+ if (!esModuleLexerInit) {
123
+ await init
124
+ esModuleLexerInit = true
125
+ }
29
126
 
30
- function createInlinedModule(code) {
31
- return `data:text/javascript;utf-8,${encodeURIComponent(code)}`
32
- }
127
+ if (files) {
128
+ const overrideExternals =
129
+ new Set(Object.keys(files).filter(name => !isRelative(name) && name !== 'index.js'))
33
130
 
34
- await setupImportMap()
35
- const imports = Object.fromEntries(
36
- Object.entries(files).map(([key, code]) => [
37
- key,
38
- createInlinedModule(code),
39
- ])
40
- )
131
+ // Always share react as externals
132
+ overrideExternals.add('react')
133
+ overrideExternals.add('react-dom')
134
+
135
+ try {
136
+ const transformedFiles = Object.keys(files).reduce((res, filename) => {
137
+ const key = isRelative(filename) ? ('@' + filename.slice(2)) : filename
138
+ res[key] = transformCode(files[filename], getModulePath, overrideExternals)
139
+ return res
140
+ }, {})
141
+
142
+ const iframe = iframeRef.current
143
+ const script = scriptRef.current
144
+ if (iframe) {
145
+ const render = iframe.contentWindow.__render__
146
+ if (render) {
147
+ render(transformedFiles)
148
+ } else {
149
+ // if render is not loaded yet, wait until it's loaded
150
+ script.onload = () => {
151
+ iframe.contentWindow.__render__(transformedFiles)
152
+ }
153
+ }
154
+ }
155
+ setError()
156
+ } catch (e) {
157
+ setError(e)
158
+ }
159
+ }
160
+ rerender({})
161
+ }, [])
41
162
 
42
- updateImportMap(imports)
43
- return self.importShim('index.js')
163
+ return { ref: iframeRef, error, load }
44
164
  }
45
165
 
46
166
  export { createModule }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "devjar",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./lib/index.mjs",
7
- "./react": "./lib/react.mjs"
7
+ "./package.json": "./package.json"
8
8
  },
9
9
  "license": "MIT",
10
10
  "files": [
@@ -24,12 +24,12 @@
24
24
  "sucrase": "3.21.0"
25
25
  },
26
26
  "devDependencies": {
27
- "codice": "^0.0.4",
27
+ "codice": "latest",
28
28
  "devjar": "link:./",
29
29
  "lodash-es": "^4.17.21",
30
- "next": "^12.1.7-canary.0",
30
+ "next": "canary",
31
31
  "react": "^18.0.0",
32
32
  "react-dom": "^18.0.0",
33
- "sugar-high": "^0.4.0"
33
+ "sugar-high": "^0.4.2"
34
34
  }
35
35
  }
package/lib/react.mjs DELETED
@@ -1,164 +0,0 @@
1
- import { useEffect, useCallback, useState, useRef } from 'react'
2
- import { createModule } from './index.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, 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
- const [imports] = parse(_code)
20
- let code = ''
21
- let lastIndex = 0
22
- imports.forEach(({ ss, s, e, se, n }) => {
23
- code += _code.slice(lastIndex, ss)
24
- code += _code.substring(ss, s)
25
- code += isRelative(n)
26
- ? ('@' + n.slice(2))
27
- : externals.has(n) ? n : getModulePath(n)
28
- code += _code.substring(e, se)
29
- lastIndex = se
30
- })
31
- code += _code.substring(lastIndex)
32
- return code
33
- }
34
-
35
- function createRenderer(_React0, _ReactDOM0, _createModule, _getModulePath) {
36
- let reactRoot
37
-
38
- async function render(files) {
39
- const mod = await _createModule(files, { getModulePath: _getModulePath })
40
- const _React = await self.importShim('react')
41
- const _ReactDOM = await self.importShim('react-dom')
42
-
43
- const _jsx = _React.createElement
44
- const root = document.getElementById('root')
45
- class ErrorBoundary extends _React.Component {
46
- state = {
47
- error: null,
48
- }
49
- componentDidCatch(error) {
50
- this.setState({ error })
51
- }
52
- render() {
53
- if (this.state.error) {
54
- return _jsx('div', null, this.state.error.message)
55
- }
56
- return this.props.children
57
- }
58
- }
59
-
60
- const isReact18 = !!_ReactDOM.createRoot
61
- if (isReact18 && !reactRoot) {
62
- reactRoot = _ReactDOM.createRoot(root)
63
- }
64
- const Component = mod.default
65
- const element = _jsx(ErrorBoundary, null, _jsx(Component))
66
- if (isReact18) {
67
- reactRoot.render(element)
68
- } else {
69
- _ReactDOM.render(element, root)
70
- }
71
- }
72
-
73
- return render
74
- }
75
-
76
- function createMainScript({ getModulePath }) {
77
- const code = (
78
- `'use strict';
79
- const createModule = ${createModule.toString()};
80
- const createRenderer = ${createRenderer.toString()};
81
- const getModulePath = ${getModulePath.toString()};
82
-
83
- globalThis.__render__ = createRenderer(0, 0, createModule, getModulePath);
84
- `)
85
- return code
86
- }
87
-
88
-
89
- export function useLiveCode({ getModulePath }) {
90
- const iframeRef = useRef()
91
- const [error, setError] = useState()
92
- const rerender = useState({})[1]
93
- const scriptRef = useRef(typeof window !== 'undefined' ? document.createElement('script') : null)
94
-
95
- useEffect(() => {
96
- const iframe = iframeRef.current
97
- const doc = iframe && iframe.contentDocument
98
-
99
- if (iframe) {
100
- const doc = iframe.contentDocument
101
- const div = document.createElement('div')
102
- const script = scriptRef.current
103
- const scriptContent = createMainScript({ getModulePath })
104
-
105
- div.id = 'root'
106
- script.type = 'module'
107
- script.id = 'main'
108
- script.src = `data:text/javascript;utf-8,${encodeURIComponent(scriptContent)}`
109
-
110
- doc.body.appendChild(div)
111
- doc.body.appendChild(script)
112
- }
113
- return () => {
114
- if (iframe) {
115
- doc.body.removeChild(doc.getElementById('root'))
116
- doc.body.removeChild(doc.getElementById('main'))
117
- }
118
- }
119
- }, [])
120
-
121
- const load = useCallback(async (files) => {
122
- if (!esModuleLexerInit) {
123
- await init
124
- esModuleLexerInit = true
125
- }
126
-
127
- if (files) {
128
- const overrideExternals =
129
- new Set(Object.keys(files).filter(name => !isRelative(name) && name !== 'index.js'))
130
-
131
- // Always share react as externals
132
- overrideExternals.add('react')
133
- overrideExternals.add('react-dom')
134
-
135
- try {
136
- const transformedFiles = Object.keys(files).reduce((res, filename) => {
137
- const key = isRelative(filename) ? ('@' + filename.slice(2)) : filename
138
- res[key] = transformCode(files[filename], getModulePath, overrideExternals)
139
- return res
140
- }, {})
141
-
142
- const iframe = iframeRef.current
143
- const script = scriptRef.current
144
- if (iframe) {
145
- const render = iframe.contentWindow.__render__
146
- if (render) {
147
- render(transformedFiles)
148
- } else {
149
- // if render is not loaded yet, wait until it's loaded
150
- script.onload = () => {
151
- iframe.contentWindow.__render__(transformedFiles)
152
- }
153
- }
154
- }
155
- setError()
156
- } catch (e) {
157
- setError(e)
158
- }
159
- }
160
- rerender({})
161
- }, [])
162
-
163
- return { ref: iframeRef, error, load }
164
- }