devjar 0.5.0 → 0.7.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/dist/index.d.ts +28 -0
- package/dist/index.js +325 -0
- package/package.json +20 -14
- package/lib/core.js +0 -278
- package/lib/index.d.ts +0 -13
- package/lib/index.js +0 -2
- package/lib/module.js +0 -48
- package/lib/render.js +0 -21
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
esmsInitOptions: {
|
|
7
|
+
shimMode: boolean;
|
|
8
|
+
mapOverrides: boolean;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function importShim(url: string): Promise<any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare function useLiveCode({ getModuleUrl }: {
|
|
15
|
+
getModuleUrl?: (name: string) => string;
|
|
16
|
+
}): {
|
|
17
|
+
ref: react.RefObject<any>;
|
|
18
|
+
error: undefined;
|
|
19
|
+
load: (files: any) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
declare function DevJar({ files, getModuleUrl, onError, ...props }: {
|
|
23
|
+
files: Record<string, string>;
|
|
24
|
+
getModuleUrl?: (name: string) => string;
|
|
25
|
+
onError?: (...data: any[]) => void;
|
|
26
|
+
} & React.IframeHTMLAttributes<HTMLIFrameElement>): react_jsx_runtime.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { DevJar, useLiveCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { useRef, useState, useId, useEffect, useCallback } from 'react';
|
|
2
|
+
import { transform } from 'sucrase';
|
|
3
|
+
import { init, parse } from 'es-module-lexer';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// declare esmsInitOptions on global window
|
|
7
|
+
async function createModule(files, { getModuleUrl }) {
|
|
8
|
+
let currentImportMap;
|
|
9
|
+
let shim;
|
|
10
|
+
async function setupImportMap() {
|
|
11
|
+
if (shim) return shim;
|
|
12
|
+
window.esmsInitOptions = {
|
|
13
|
+
shimMode: true,
|
|
14
|
+
mapOverrides: true
|
|
15
|
+
};
|
|
16
|
+
shim = import(/* webpackIgnore: true */ getModuleUrl('es-module-shims'));
|
|
17
|
+
await shim;
|
|
18
|
+
}
|
|
19
|
+
function updateImportMap(imports) {
|
|
20
|
+
imports['react'] = getModuleUrl('react');
|
|
21
|
+
imports['react-dom'] = getModuleUrl('react-dom');
|
|
22
|
+
imports['react-dom/client'] = getModuleUrl('react-dom/client');
|
|
23
|
+
const script = document.createElement('script');
|
|
24
|
+
script.type = 'importmap-shim';
|
|
25
|
+
script.innerHTML = JSON.stringify({
|
|
26
|
+
imports
|
|
27
|
+
});
|
|
28
|
+
document.body.appendChild(script);
|
|
29
|
+
if (currentImportMap) {
|
|
30
|
+
currentImportMap.parentNode.removeChild(currentImportMap);
|
|
31
|
+
}
|
|
32
|
+
currentImportMap = script;
|
|
33
|
+
}
|
|
34
|
+
function createInlinedModule(code, type) {
|
|
35
|
+
if (type === 'css') return `data:text/css;utf-8,${encodeURIComponent(code)}`;
|
|
36
|
+
return `data:text/javascript;utf-8,${encodeURIComponent(code)}`;
|
|
37
|
+
}
|
|
38
|
+
await setupImportMap();
|
|
39
|
+
const imports = Object.fromEntries(Object.entries(files).map(([fileName, code])=>[
|
|
40
|
+
fileName,
|
|
41
|
+
createInlinedModule(code, fileName.endsWith('.css') ? 'css' : 'js')
|
|
42
|
+
]));
|
|
43
|
+
updateImportMap(imports);
|
|
44
|
+
return self.importShim('index');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let esModuleLexerInit;
|
|
48
|
+
const isRelative = (s)=>s.startsWith('./');
|
|
49
|
+
const removeExtension = (str)=>str.replace(/\.[^/.]+$/, '');
|
|
50
|
+
function transformCode(_code, getModuleUrl, externals) {
|
|
51
|
+
const code = transform(_code, {
|
|
52
|
+
transforms: [
|
|
53
|
+
'jsx',
|
|
54
|
+
'typescript'
|
|
55
|
+
]
|
|
56
|
+
}).code;
|
|
57
|
+
return replaceImports(code, getModuleUrl, externals);
|
|
58
|
+
}
|
|
59
|
+
function replaceImports(source, getModuleUrl, externals) {
|
|
60
|
+
let code = '';
|
|
61
|
+
let lastIndex = 0;
|
|
62
|
+
let hasReactImports = false;
|
|
63
|
+
const [imports] = parse(source);
|
|
64
|
+
const cssImports = [];
|
|
65
|
+
let cssImportIndex = 0;
|
|
66
|
+
// start, end, statementStart, statementEnd, assertion, name
|
|
67
|
+
imports.forEach(({ s, e, ss, se, a, n })=>{
|
|
68
|
+
code += source.slice(lastIndex, ss) // content from last import to beginning of this line
|
|
69
|
+
;
|
|
70
|
+
// handle imports
|
|
71
|
+
if (n.endsWith('.css')) {
|
|
72
|
+
// Map './styles.css' -> '@styles.css', and collect it
|
|
73
|
+
const cssPath = `${'@' + n.slice(2)}`;
|
|
74
|
+
cssImports.push(cssPath);
|
|
75
|
+
} else {
|
|
76
|
+
code += source.substring(ss, s);
|
|
77
|
+
code += isRelative(n) ? '@' + n.slice(2) : externals.has(n) ? n : getModuleUrl(n);
|
|
78
|
+
code += source.substring(e, se);
|
|
79
|
+
}
|
|
80
|
+
lastIndex = se;
|
|
81
|
+
if (n === 'react') {
|
|
82
|
+
const statement = source.slice(ss, se);
|
|
83
|
+
if (statement.includes('React')) {
|
|
84
|
+
hasReactImports = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
cssImports.forEach((cssPath)=>{
|
|
88
|
+
code += `\nimport sheet${cssImportIndex} from "${cssPath}" assert { type: "css" };\n`;
|
|
89
|
+
cssImportIndex++;
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
if (cssImports.length) {
|
|
93
|
+
code += `const __customStyleSheets = [`;
|
|
94
|
+
for(let i = 0; i < cssImports.length; i++){
|
|
95
|
+
code += `sheet${i}`;
|
|
96
|
+
if (i < cssImports.length - 1) {
|
|
97
|
+
code += `, `;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
code += `];\n`;
|
|
101
|
+
code += `document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...__customStyleSheets];\n`;
|
|
102
|
+
}
|
|
103
|
+
code += source.substring(lastIndex);
|
|
104
|
+
if (!hasReactImports) {
|
|
105
|
+
code = `import React from 'react';\n${code}`;
|
|
106
|
+
}
|
|
107
|
+
return code;
|
|
108
|
+
}
|
|
109
|
+
// createRenderer is going to be stringified and executed in the iframe
|
|
110
|
+
function createRenderer(createModule_, getModuleUrl) {
|
|
111
|
+
let reactRoot;
|
|
112
|
+
async function render(files) {
|
|
113
|
+
const mod = await createModule_(files, {
|
|
114
|
+
getModuleUrl
|
|
115
|
+
});
|
|
116
|
+
const ReactMod = await self.importShim('react');
|
|
117
|
+
const ReactDOMMod = await self.importShim('react-dom/client');
|
|
118
|
+
const _jsx = ReactMod.createElement;
|
|
119
|
+
const root = document.getElementById('__reactRoot');
|
|
120
|
+
class ErrorBoundary extends ReactMod.Component {
|
|
121
|
+
componentDidCatch(error) {
|
|
122
|
+
this.setState({
|
|
123
|
+
error
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
render() {
|
|
127
|
+
if (this.state.error) {
|
|
128
|
+
return _jsx('div', null, this.state.error?.message);
|
|
129
|
+
}
|
|
130
|
+
return this.props.children;
|
|
131
|
+
}
|
|
132
|
+
constructor(props){
|
|
133
|
+
super(props);
|
|
134
|
+
this.state = {
|
|
135
|
+
error: null
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!reactRoot) {
|
|
140
|
+
reactRoot = ReactDOMMod.createRoot(root);
|
|
141
|
+
}
|
|
142
|
+
const Component = mod.default;
|
|
143
|
+
const element = _jsx(ErrorBoundary, null, _jsx(Component));
|
|
144
|
+
reactRoot.render(element);
|
|
145
|
+
}
|
|
146
|
+
return render;
|
|
147
|
+
}
|
|
148
|
+
function createMainScript({ uid }) {
|
|
149
|
+
const code = `\
|
|
150
|
+
'use strict';
|
|
151
|
+
const _createModule = ${createModule.toString()};
|
|
152
|
+
const _createRenderer = ${createRenderer.toString()};
|
|
153
|
+
|
|
154
|
+
const getModuleUrl = (m) => window.parent.__devjar__[globalThis.uid].getModuleUrl(m)
|
|
155
|
+
|
|
156
|
+
globalThis.uid = ${JSON.stringify(uid)};
|
|
157
|
+
globalThis.__render__ = _createRenderer(_createModule, getModuleUrl);
|
|
158
|
+
`;
|
|
159
|
+
return code;
|
|
160
|
+
}
|
|
161
|
+
function createEsShimOptionsScript() {
|
|
162
|
+
return `\
|
|
163
|
+
window.esmsInitOptions = {
|
|
164
|
+
polyfillEnable: ['css-modules', 'json-modules'],
|
|
165
|
+
onerror: console.error,
|
|
166
|
+
}`;
|
|
167
|
+
}
|
|
168
|
+
function useScript() {
|
|
169
|
+
return useRef(typeof window !== 'undefined' ? document.createElement('script') : null);
|
|
170
|
+
}
|
|
171
|
+
function createScript(scriptRef, { content, src, type } = {}) {
|
|
172
|
+
const script = scriptRef.current;
|
|
173
|
+
if (type) script.type = type;
|
|
174
|
+
if (content) {
|
|
175
|
+
script.src = `data:text/javascript;utf-8,${encodeURIComponent(content)}`;
|
|
176
|
+
}
|
|
177
|
+
if (src) {
|
|
178
|
+
script.src = src;
|
|
179
|
+
}
|
|
180
|
+
return script;
|
|
181
|
+
}
|
|
182
|
+
function useLiveCode({ getModuleUrl }) {
|
|
183
|
+
const iframeRef = useRef(null);
|
|
184
|
+
const [error, setError] = useState();
|
|
185
|
+
const rerender = useState({})[1];
|
|
186
|
+
const appScriptRef = useScript();
|
|
187
|
+
const esShimOptionsScriptRef = useScript();
|
|
188
|
+
const tailwindcssScriptRef = useScript();
|
|
189
|
+
const uid = useId();
|
|
190
|
+
// Let getModuleUrl executed on parent window side since it might involve
|
|
191
|
+
// variables that iframe cannot access.
|
|
192
|
+
useEffect(()=>{
|
|
193
|
+
if (!globalThis.__devjar__) {
|
|
194
|
+
globalThis.__devjar__ = {};
|
|
195
|
+
}
|
|
196
|
+
globalThis.__devjar__[uid] = {
|
|
197
|
+
getModuleUrl
|
|
198
|
+
};
|
|
199
|
+
return ()=>{
|
|
200
|
+
if (globalThis.__devjar__) {
|
|
201
|
+
delete globalThis.__devjar__[uid];
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}, []);
|
|
205
|
+
useEffect(()=>{
|
|
206
|
+
const iframe = iframeRef.current;
|
|
207
|
+
if (!iframe || !iframe.contentDocument) return;
|
|
208
|
+
const doc = iframe.contentDocument;
|
|
209
|
+
const body = doc.body;
|
|
210
|
+
const div = document.createElement('div');
|
|
211
|
+
div.id = '__reactRoot';
|
|
212
|
+
const appScriptContent = createMainScript({
|
|
213
|
+
uid
|
|
214
|
+
});
|
|
215
|
+
const scriptOptionsContent = createEsShimOptionsScript();
|
|
216
|
+
const esmShimOptionsScript = createScript(esShimOptionsScriptRef, {
|
|
217
|
+
content: scriptOptionsContent
|
|
218
|
+
});
|
|
219
|
+
const appScript = createScript(appScriptRef, {
|
|
220
|
+
content: appScriptContent,
|
|
221
|
+
type: 'module'
|
|
222
|
+
});
|
|
223
|
+
const tailwindScript = createScript(tailwindcssScriptRef, {
|
|
224
|
+
src: 'https://unpkg.com/@tailwindcss/browser@4'
|
|
225
|
+
});
|
|
226
|
+
body.appendChild(div);
|
|
227
|
+
body.appendChild(esmShimOptionsScript);
|
|
228
|
+
body.appendChild(appScript);
|
|
229
|
+
body.appendChild(tailwindScript);
|
|
230
|
+
return ()=>{
|
|
231
|
+
if (!iframe || !iframe.contentDocument) return;
|
|
232
|
+
body.removeChild(div);
|
|
233
|
+
body.removeChild(esmShimOptionsScript);
|
|
234
|
+
body.removeChild(appScript);
|
|
235
|
+
body.removeChild(tailwindScript);
|
|
236
|
+
};
|
|
237
|
+
}, []);
|
|
238
|
+
const load = useCallback(async (files)=>{
|
|
239
|
+
if (!esModuleLexerInit) {
|
|
240
|
+
await init;
|
|
241
|
+
esModuleLexerInit = true;
|
|
242
|
+
}
|
|
243
|
+
if (files) {
|
|
244
|
+
// { 'react', 'react-dom' }
|
|
245
|
+
const overrideExternals = new Set(Object.keys(files).filter((name)=>!isRelative(name) && name !== 'index.js'));
|
|
246
|
+
// Always share react as externals
|
|
247
|
+
overrideExternals.add('react');
|
|
248
|
+
overrideExternals.add('react-dom');
|
|
249
|
+
try {
|
|
250
|
+
/**
|
|
251
|
+
* transformedFiles
|
|
252
|
+
* {
|
|
253
|
+
* 'index.js': '...',
|
|
254
|
+
* '@mod1': '...',
|
|
255
|
+
* '@mod2': '...',
|
|
256
|
+
*/ const transformedFiles = Object.keys(files).reduce((res, filename)=>{
|
|
257
|
+
// 1. Remove ./
|
|
258
|
+
// 2. For non css files, remove extension
|
|
259
|
+
// e.g. './styles.css' -> '@styles.css'
|
|
260
|
+
// e.g. './foo.js' -> '@foo'
|
|
261
|
+
const moduleKey = isRelative(filename) ? '@' + filename.slice(2) : filename;
|
|
262
|
+
if (filename.endsWith('.css')) {
|
|
263
|
+
res[moduleKey] = files[filename];
|
|
264
|
+
} else {
|
|
265
|
+
// JS or TS files
|
|
266
|
+
const normalizedModuleKey = removeExtension(moduleKey);
|
|
267
|
+
res[normalizedModuleKey] = transformCode(files[filename], getModuleUrl, overrideExternals);
|
|
268
|
+
}
|
|
269
|
+
return res;
|
|
270
|
+
}, {});
|
|
271
|
+
const iframe = iframeRef.current;
|
|
272
|
+
const script = appScriptRef.current;
|
|
273
|
+
if (iframe) {
|
|
274
|
+
const render = iframe.contentWindow.__render__;
|
|
275
|
+
if (render) {
|
|
276
|
+
render(transformedFiles);
|
|
277
|
+
} else {
|
|
278
|
+
// if render is not loaded yet, wait until it's loaded
|
|
279
|
+
script.onload = ()=>{
|
|
280
|
+
iframe.contentWindow.__render__(transformedFiles).catch((err)=>{
|
|
281
|
+
setError(err);
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
setError(undefined);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
console.warn(e);
|
|
289
|
+
setError(e);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
rerender({});
|
|
293
|
+
}, []);
|
|
294
|
+
return {
|
|
295
|
+
ref: iframeRef,
|
|
296
|
+
error,
|
|
297
|
+
load
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const defaultOnError = typeof window !== 'undefined' ? console.error : ()=>{};
|
|
302
|
+
function DevJar({ files, getModuleUrl, onError = defaultOnError, ...props }) {
|
|
303
|
+
const onErrorRef = useRef(onError);
|
|
304
|
+
const { ref, error, load } = useLiveCode({
|
|
305
|
+
getModuleUrl
|
|
306
|
+
});
|
|
307
|
+
useEffect(()=>{
|
|
308
|
+
onErrorRef.current(error);
|
|
309
|
+
}, [
|
|
310
|
+
error
|
|
311
|
+
]);
|
|
312
|
+
// load code files and execute them as live code
|
|
313
|
+
useEffect(()=>{
|
|
314
|
+
load(files);
|
|
315
|
+
}, [
|
|
316
|
+
files
|
|
317
|
+
]);
|
|
318
|
+
// Attach the ref to an iframe element for runtime of code execution
|
|
319
|
+
return /*#__PURE__*/ jsx("iframe", {
|
|
320
|
+
...props,
|
|
321
|
+
ref: ref
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export { DevJar, useLiveCode };
|
package/package.json
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devjar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".": "./
|
|
6
|
+
".": "./dist/index.js",
|
|
7
7
|
"./package.json": "./package.json"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"files": [
|
|
11
|
-
"
|
|
11
|
+
"dist"
|
|
12
12
|
],
|
|
13
|
-
"types": "./
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build
|
|
15
|
+
"build": "bunchee",
|
|
16
|
+
"prepublishOnly": "pnpm run build",
|
|
17
|
+
"build:site": "pnpm run build && next build ./site",
|
|
16
18
|
"start": "next start ./site",
|
|
17
19
|
"dev": "next dev ./site"
|
|
18
20
|
},
|
|
19
21
|
"peerDependencies": {
|
|
20
|
-
"react": "^18.2.0"
|
|
22
|
+
"react": "^18.2.0 || ^19.0.0"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"es-module-lexer": "1.
|
|
24
|
-
"es-module-shims": "
|
|
25
|
+
"es-module-lexer": "1.6.0",
|
|
26
|
+
"es-module-shims": "2.0.3",
|
|
25
27
|
"sucrase": "3.35.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
|
-
"
|
|
30
|
+
"@types/node": "^22.10.7",
|
|
31
|
+
"@types/react": "^19.0.7",
|
|
32
|
+
"@types/react-dom": "^19.0.3",
|
|
33
|
+
"bunchee": "^6.3.2",
|
|
34
|
+
"codice": "1.0.0",
|
|
29
35
|
"devjar": "link:./",
|
|
30
|
-
"next": "^
|
|
31
|
-
"react": "^
|
|
32
|
-
"react-dom": "^
|
|
33
|
-
"
|
|
36
|
+
"next": "^15.1.5",
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-dom": "^19.0.0",
|
|
39
|
+
"typescript": "^5.7.3"
|
|
34
40
|
},
|
|
35
|
-
"packageManager": "pnpm@
|
|
41
|
+
"packageManager": "pnpm@9.15.4"
|
|
36
42
|
}
|
package/lib/core.js
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { useEffect, useCallback, useState, useId, useRef } from 'react'
|
|
2
|
-
import { createModule } from './module.js'
|
|
3
|
-
import { transform } from 'sucrase'
|
|
4
|
-
import { init, parse } from 'es-module-lexer'
|
|
5
|
-
|
|
6
|
-
let esModuleLexerInit
|
|
7
|
-
const isRelative = s => s.startsWith('./')
|
|
8
|
-
|
|
9
|
-
function transformCode(_code, getModuleUrl, externals) {
|
|
10
|
-
const code = transform(_code, {
|
|
11
|
-
transforms: ['jsx', 'typescript'],
|
|
12
|
-
}).code
|
|
13
|
-
|
|
14
|
-
return replaceImports(code, getModuleUrl, externals)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function replaceImports(source, getModuleUrl, externals) {
|
|
18
|
-
let code = ''
|
|
19
|
-
let lastIndex = 0
|
|
20
|
-
let hasReactImports = false
|
|
21
|
-
const [imports] = parse(source)
|
|
22
|
-
const cssImports = []
|
|
23
|
-
let cssImportIndex = 0
|
|
24
|
-
|
|
25
|
-
// start, end, statementStart, statementEnd, assertion, name
|
|
26
|
-
imports.forEach(({ s, e, ss, se, a, n }) => {
|
|
27
|
-
code += source.slice(lastIndex, ss) // content from last import to beginning of this line
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// handle imports
|
|
31
|
-
if (n.endsWith('.css')) {
|
|
32
|
-
// Map './styles.css' -> '@styles.css', and collect it
|
|
33
|
-
const cssPath = `${'@' + n.slice(2)}`
|
|
34
|
-
cssImports.push(cssPath)
|
|
35
|
-
|
|
36
|
-
} else {
|
|
37
|
-
code += source.substring(ss, s)
|
|
38
|
-
code += isRelative(n)
|
|
39
|
-
? ('@' + n.slice(2))
|
|
40
|
-
: externals.has(n) ? n : getModuleUrl(n)
|
|
41
|
-
code += source.substring(e, se)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
lastIndex = se
|
|
45
|
-
|
|
46
|
-
if (n === 'react') {
|
|
47
|
-
const statement = source.slice(ss, se)
|
|
48
|
-
if (statement.includes('React')) {
|
|
49
|
-
hasReactImports = true
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
cssImports.forEach(cssPath => {
|
|
54
|
-
code += `\nimport sheet${cssImportIndex} from "${cssPath}" assert { type: "css" };\n`
|
|
55
|
-
cssImportIndex++
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
if (cssImports.length) {
|
|
60
|
-
code += `const __customStyleSheets = [`
|
|
61
|
-
for (let i = 0; i < cssImports.length; i++) {
|
|
62
|
-
code += `sheet${i}`
|
|
63
|
-
if (i < cssImports.length - 1) {
|
|
64
|
-
code += `, `
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
code += `];\n`
|
|
68
|
-
code += `document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...__customStyleSheets];\n`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
code += source.substring(lastIndex)
|
|
72
|
-
|
|
73
|
-
if (!hasReactImports) {
|
|
74
|
-
code = `import React from 'react';\n${code}`
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return code
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createRenderer(createModule_, getModuleUrl) {
|
|
81
|
-
let reactRoot
|
|
82
|
-
|
|
83
|
-
async function render(files) {
|
|
84
|
-
const mod = await createModule_(files, { getModuleUrl })
|
|
85
|
-
const ReactMod = await self.importShim('react')
|
|
86
|
-
const ReactDOMMod = await self.importShim('react-dom')
|
|
87
|
-
|
|
88
|
-
const _jsx = ReactMod.createElement
|
|
89
|
-
const root = document.getElementById('__reactRoot')
|
|
90
|
-
class ErrorBoundary extends ReactMod.Component {
|
|
91
|
-
constructor(props) {
|
|
92
|
-
super(props)
|
|
93
|
-
this.state = { error: null }
|
|
94
|
-
}
|
|
95
|
-
componentDidCatch(error) {
|
|
96
|
-
this.setState({ error })
|
|
97
|
-
}
|
|
98
|
-
render() {
|
|
99
|
-
if (this.state.error) {
|
|
100
|
-
return _jsx('div', null, this.state.error.message)
|
|
101
|
-
}
|
|
102
|
-
return this.props.children
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const isReact18 = !!ReactDOMMod.createRoot
|
|
107
|
-
if (isReact18 && !reactRoot) {
|
|
108
|
-
reactRoot = ReactDOMMod.createRoot(root)
|
|
109
|
-
}
|
|
110
|
-
const Component = mod.default
|
|
111
|
-
const element = _jsx(ErrorBoundary, null, _jsx(Component))
|
|
112
|
-
if (isReact18) {
|
|
113
|
-
reactRoot.render(element)
|
|
114
|
-
} else {
|
|
115
|
-
ReactDOMMod.render(element, root)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return render
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function createMainScript({ uid }) {
|
|
123
|
-
const code = (`\
|
|
124
|
-
'use strict';
|
|
125
|
-
const _createModule = ${createModule.toString()};
|
|
126
|
-
const _createRenderer = ${createRenderer.toString()};
|
|
127
|
-
|
|
128
|
-
const getModuleUrl = (m) => window.parent.__devjar__[globalThis.uid].getModuleUrl(m)
|
|
129
|
-
|
|
130
|
-
globalThis.uid = ${JSON.stringify(uid)};
|
|
131
|
-
globalThis.__render__ = _createRenderer(_createModule, getModuleUrl);
|
|
132
|
-
`)
|
|
133
|
-
return code
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function createEsShimOptionsScript() {
|
|
137
|
-
return `\
|
|
138
|
-
window.esmsInitOptions = {
|
|
139
|
-
polyfillEnable: ['css-modules', 'json-modules'],
|
|
140
|
-
onerror: error => console.log(error),
|
|
141
|
-
}`
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function useScript() {
|
|
145
|
-
return useRef(typeof window !== 'undefined' ? document.createElement('script') : null)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function createScript(scriptRef, { content, src, type } = {}) {
|
|
149
|
-
const script = scriptRef.current
|
|
150
|
-
if (type) script.type = type
|
|
151
|
-
|
|
152
|
-
if (content) {
|
|
153
|
-
script.src = `data:text/javascript;utf-8,${encodeURIComponent(content)}`
|
|
154
|
-
}
|
|
155
|
-
if (src) {
|
|
156
|
-
script.src = src
|
|
157
|
-
}
|
|
158
|
-
return script
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function useLiveCode({ getModuleUrl }) {
|
|
162
|
-
const iframeRef = useRef()
|
|
163
|
-
const [error, setError] = useState()
|
|
164
|
-
const rerender = useState({})[1]
|
|
165
|
-
const appScriptRef = useScript()
|
|
166
|
-
const esShimOptionsScriptRef = useScript()
|
|
167
|
-
const tailwindcssScriptRef = useScript()
|
|
168
|
-
const uid = useId()
|
|
169
|
-
|
|
170
|
-
// Let getModuleUrl executed on parent window side since it might involve
|
|
171
|
-
// variables that iframe cannot access.
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (!globalThis.__devjar__) {
|
|
174
|
-
globalThis.__devjar__ = {};
|
|
175
|
-
}
|
|
176
|
-
globalThis.__devjar__[uid] = {
|
|
177
|
-
getModuleUrl,
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return () => {
|
|
181
|
-
if (globalThis.__devjar__) {
|
|
182
|
-
delete globalThis.__devjar__[uid]
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}, [])
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
const iframe = iframeRef.current
|
|
189
|
-
if (!iframe || !iframe.contentDocument) return
|
|
190
|
-
|
|
191
|
-
const doc = iframe.contentDocument
|
|
192
|
-
const body = doc.body
|
|
193
|
-
const div = document.createElement('div')
|
|
194
|
-
div.id = '__reactRoot'
|
|
195
|
-
|
|
196
|
-
const appScriptContent = createMainScript({ uid })
|
|
197
|
-
const scriptOptionsContent = createEsShimOptionsScript()
|
|
198
|
-
|
|
199
|
-
const esmShimOptionsScript = createScript(esShimOptionsScriptRef, { content: scriptOptionsContent })
|
|
200
|
-
const appScript = createScript(appScriptRef, { content: appScriptContent, type: 'module' })
|
|
201
|
-
const tailwindScript = createScript(tailwindcssScriptRef, { src: 'https://cdn.tailwindcss.com' })
|
|
202
|
-
|
|
203
|
-
body.appendChild(div)
|
|
204
|
-
body.appendChild(esmShimOptionsScript)
|
|
205
|
-
body.appendChild(appScript)
|
|
206
|
-
body.appendChild(tailwindScript)
|
|
207
|
-
|
|
208
|
-
return () => {
|
|
209
|
-
if (!iframe || !iframe.contentDocument) return
|
|
210
|
-
body.removeChild(div)
|
|
211
|
-
body.removeChild(esmShimOptionsScript)
|
|
212
|
-
body.removeChild(appScript)
|
|
213
|
-
body.removeChild(tailwindScript)
|
|
214
|
-
}
|
|
215
|
-
}, [])
|
|
216
|
-
|
|
217
|
-
const load = useCallback(async (files) => {
|
|
218
|
-
if (!esModuleLexerInit) {
|
|
219
|
-
await init
|
|
220
|
-
esModuleLexerInit = true
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (files) {
|
|
224
|
-
// { 'react', 'react-dom' }
|
|
225
|
-
const overrideExternals =
|
|
226
|
-
new Set(Object.keys(files).filter(name => !isRelative(name) && name !== 'index.js'))
|
|
227
|
-
|
|
228
|
-
// Always share react as externals
|
|
229
|
-
overrideExternals.add('react')
|
|
230
|
-
overrideExternals.add('react-dom')
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
/**
|
|
234
|
-
* transformedFiles
|
|
235
|
-
* {
|
|
236
|
-
* 'index.js': '...',
|
|
237
|
-
* '@mod1': '...',
|
|
238
|
-
* '@mod2': '...',
|
|
239
|
-
*/
|
|
240
|
-
const transformedFiles = Object.keys(files).reduce((res, filename) => {
|
|
241
|
-
const key = isRelative(filename) ? ('@' + filename.slice(2)) : filename
|
|
242
|
-
if (filename.endsWith('.css')) {
|
|
243
|
-
res[key] = files[filename]
|
|
244
|
-
} else {
|
|
245
|
-
res[key] = transformCode(files[filename], getModuleUrl, overrideExternals)
|
|
246
|
-
}
|
|
247
|
-
return res
|
|
248
|
-
}, {})
|
|
249
|
-
|
|
250
|
-
const iframe = iframeRef.current
|
|
251
|
-
const script = appScriptRef.current
|
|
252
|
-
if (iframe) {
|
|
253
|
-
const render = iframe.contentWindow.__render__
|
|
254
|
-
if (render) {
|
|
255
|
-
render(transformedFiles)
|
|
256
|
-
} else {
|
|
257
|
-
// if render is not loaded yet, wait until it's loaded
|
|
258
|
-
script.onload = () => {
|
|
259
|
-
iframe.contentWindow.__render__(transformedFiles)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
setError()
|
|
264
|
-
} catch (e) {
|
|
265
|
-
console.error(e)
|
|
266
|
-
setError(e)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
rerender({})
|
|
270
|
-
}, [])
|
|
271
|
-
|
|
272
|
-
return { ref: iframeRef, error, load }
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export {
|
|
276
|
-
createModule,
|
|
277
|
-
useLiveCode,
|
|
278
|
-
}
|
package/lib/index.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
type LiveCodeHandles = {
|
|
4
|
-
load(files: Record<string, string>): void
|
|
5
|
-
ref: React.Ref
|
|
6
|
-
error?: unknown
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type Options = {
|
|
10
|
-
getModuleUrl(modulePath: string): string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function useLiveCode(options: Options): LiveCodeHandles
|
package/lib/index.js
DELETED
package/lib/module.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
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, type) {
|
|
31
|
-
if (type === 'css') return `data:text/css;utf-8,${encodeURIComponent(code)}`
|
|
32
|
-
|
|
33
|
-
return `data:text/javascript;utf-8,${encodeURIComponent(code)}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
await setupImportMap()
|
|
37
|
-
const imports = Object.fromEntries(
|
|
38
|
-
Object.entries(files).map(([fileName, code]) => [
|
|
39
|
-
fileName,
|
|
40
|
-
createInlinedModule(code, fileName.endsWith('.css') ? 'css' : 'js'),
|
|
41
|
-
])
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
updateImportMap(imports)
|
|
45
|
-
return self.importShim('index.js')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export { createModule }
|
package/lib/render.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react'
|
|
2
|
-
import { useLiveCode } from './core.js'
|
|
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
|
-
}
|