devjar 0.0.2 → 0.1.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.
Files changed (3) hide show
  1. package/lib/index.mjs +38 -31
  2. package/lib/react.mjs +154 -19
  3. package/package.json +12 -3
package/lib/index.mjs CHANGED
@@ -1,39 +1,46 @@
1
- import { transform } from 'sucrase'
1
+ async function createModule(files, { getModulePath }) {
2
+ let currentImportMap
3
+ let shim
2
4
 
3
- const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
4
-
5
- async function createModuleImporter(code) {
6
- const buildModule = new AsyncFunction(undefined, `return await import('data:text/javascript;base64,${btoa(code)}')`)
7
- let mod
8
- try {
9
- mod = buildModule()
10
- } catch (e) {
11
- return {}
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
12
13
  }
13
- return mod
14
- }
15
14
 
16
- function transformCode(code) {
17
- return transform(code, {
18
- transforms: ['jsx', 'typescript'],
19
- }).code
20
- }
15
+ function updateImportMap(imports) {
16
+ imports['react'] = getModulePath('react')
17
+ imports['react-dom'] = getModulePath('react-dom')
21
18
 
22
- async function compileModule(code) {
23
- const transformed = transformCode(code)
24
- return createModuleImporter(transformed)
25
- }
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
+ }
26
28
 
27
- async function createModule(code) {
28
- let mod = {}, error
29
- try {
30
- mod = await compileModule(code)
31
- } catch (e) {
32
- error = e
29
+
30
+ function createInlinedModule(code) {
31
+ return `data:text/javascript;utf-8,${encodeURIComponent(code)}`
33
32
  }
34
- return { mod, error }
35
- }
36
33
 
37
- export {
38
- createModule,
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')
39
44
  }
45
+
46
+ export { createModule }
package/lib/react.mjs CHANGED
@@ -1,29 +1,164 @@
1
- import { useCallback, useEffect, useState, useRef } from 'react'
1
+ import { useEffect, useCallback, useState, useRef } from 'react'
2
2
  import { createModule } from './index.mjs'
3
+ import { transform } from 'sucrase'
4
+ import { init, parse } from 'es-module-lexer'
3
5
 
4
- export function useDynamicModule(code) {
5
- const [{ mod, error }, setMod] = useState({})
6
- const [shouldRender, rerender] = useState({})
7
- const prevShouldRenderRef = useRef({})
6
+ let esModuleLexerInit
8
7
 
9
- const load = () => {
10
- rerender({})
11
- }
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
12
37
 
13
- const loadMod = useCallback(() => {
14
- if (code) {
15
- createModule(code).then(_mod => {
16
- setMod(_mod)
17
- })
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)
18
70
  }
19
- }, [code])
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)
20
94
 
21
95
  useEffect(() => {
22
- if (prevShouldRenderRef.current !== shouldRender) {
23
- loadMod()
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
+ }
24
118
  }
25
- prevShouldRenderRef.current = shouldRender
26
- }, [shouldRender, loadMod])
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
+ }, [])
27
162
 
28
- return { mod, error, load }
163
+ return { ref: iframeRef, error, load }
29
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devjar",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./lib/index.mjs",
@@ -11,16 +11,25 @@
11
11
  "lib"
12
12
  ],
13
13
  "scripts": {
14
+ "build": "next build ./docs",
15
+ "start": "next start ./docs",
14
16
  "dev": "next dev ./docs"
15
17
  },
18
+ "peerDependencies": {
19
+ "react": "^17.0.0 || ^18.0.0"
20
+ },
16
21
  "dependencies": {
22
+ "es-module-lexer": "^0.10.5",
23
+ "es-module-shims": "^1.5.4",
17
24
  "sucrase": "3.21.0"
18
25
  },
19
26
  "devDependencies": {
27
+ "codice": "^0.0.4",
20
28
  "devjar": "link:./",
21
- "next": "^12.1.6-canary.4",
29
+ "lodash-es": "^4.17.21",
30
+ "next": "^12.1.7-canary.0",
22
31
  "react": "^18.0.0",
23
32
  "react-dom": "^18.0.0",
24
- "sugar-high": "^0.3.1"
33
+ "sugar-high": "^0.4.0"
25
34
  }
26
35
  }