@jseeio/jsee 0.8.0 → 0.8.2
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/CHANGELOG.md +6 -0
- package/README.md +31 -11
- package/dist/jsee.core.js +1 -1
- package/dist/jsee.full.js +1 -1
- package/dist/jsee.runtime.js +1 -1
- package/package.json +10 -7
- package/src/browser-bundle-node.js +9 -0
- package/src/cli.js +134 -17
- package/src/main.js +12 -0
- package/templates/common-outputs.js +30 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jseeio/jsee",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "JavaScript Execution Environment",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "src/cli.js",
|
|
@@ -17,27 +17,27 @@
|
|
|
17
17
|
"default": "./src/cli.js"
|
|
18
18
|
},
|
|
19
19
|
"./core": {
|
|
20
|
-
"require": "./
|
|
20
|
+
"require": "./src/browser-bundle-node.js",
|
|
21
21
|
"default": "./dist/jsee.core.js"
|
|
22
22
|
},
|
|
23
23
|
"./full": {
|
|
24
|
-
"require": "./
|
|
24
|
+
"require": "./src/browser-bundle-node.js",
|
|
25
25
|
"default": "./dist/jsee.full.js"
|
|
26
26
|
},
|
|
27
27
|
"./runtime": {
|
|
28
|
-
"require": "./
|
|
28
|
+
"require": "./src/browser-bundle-node.js",
|
|
29
29
|
"default": "./dist/jsee.runtime.js"
|
|
30
30
|
},
|
|
31
31
|
"./dist/jsee.core.js": {
|
|
32
|
-
"require": "./
|
|
32
|
+
"require": "./src/browser-bundle-node.js",
|
|
33
33
|
"default": "./dist/jsee.core.js"
|
|
34
34
|
},
|
|
35
35
|
"./dist/jsee.full.js": {
|
|
36
|
-
"require": "./
|
|
36
|
+
"require": "./src/browser-bundle-node.js",
|
|
37
37
|
"default": "./dist/jsee.full.js"
|
|
38
38
|
},
|
|
39
39
|
"./dist/jsee.runtime.js": {
|
|
40
|
-
"require": "./
|
|
40
|
+
"require": "./src/browser-bundle-node.js",
|
|
41
41
|
"default": "./dist/jsee.runtime.js"
|
|
42
42
|
},
|
|
43
43
|
"./package.json": "./package.json"
|
|
@@ -120,6 +120,9 @@
|
|
|
120
120
|
"vue3-json-viewer": "^2.2.2",
|
|
121
121
|
"vuex": "^4.0.2"
|
|
122
122
|
},
|
|
123
|
+
"optionalDependencies": {
|
|
124
|
+
"esbuild": "^0.28.0"
|
|
125
|
+
},
|
|
123
126
|
"devDependencies": {
|
|
124
127
|
"@babel/core": "^7.21.4",
|
|
125
128
|
"@babel/plugin-transform-runtime": "^7.4.4",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function browserBundleOnly () {
|
|
4
|
+
throw new Error('@jseeio/jsee browser bundles require a browser DOM. Use require("@jseeio/jsee") for the Node CLI/API, or load dist/jsee.core.js / dist/jsee.full.js in a browser.')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
browserBundleOnly.browserOnly = true
|
|
8
|
+
|
|
9
|
+
module.exports = browserBundleOnly
|
package/src/cli.js
CHANGED
|
@@ -259,6 +259,114 @@ function resolveOutputPath (cwd, outputPath) {
|
|
|
259
259
|
return path.join(cwd, outputPath)
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
let optionalEsbuild
|
|
263
|
+
function getOptionalEsbuild () {
|
|
264
|
+
if (optionalEsbuild) return optionalEsbuild
|
|
265
|
+
try {
|
|
266
|
+
optionalEsbuild = require('esbuild')
|
|
267
|
+
return optionalEsbuild
|
|
268
|
+
} catch (error) {
|
|
269
|
+
throw new Error('Bundling model dependencies requires optional dependency esbuild. Run `npm install esbuild` or use schema imports/prebundled browser code.')
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isJsIdentifier (value) {
|
|
274
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function toJsIdentifier (value, fallback='model') {
|
|
278
|
+
const raw = value ? String(value) : fallback
|
|
279
|
+
const clean = raw.replace(/[^A-Za-z0-9_$]/g, '_')
|
|
280
|
+
if (!clean) return fallback
|
|
281
|
+
return /^[A-Za-z_$]/.test(clean) ? clean : '_' + clean
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getModelExportName (model) {
|
|
285
|
+
if (model.name) return model.name
|
|
286
|
+
if (model.url) return path.basename(model.url).replace(/\.[^.]+$/, '')
|
|
287
|
+
return 'model'
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function hasCommonJsExport (code) {
|
|
291
|
+
return /\bmodule\s*\.\s*exports\b|\bexports\s*\.\s*[A-Za-z_$]/.test(code)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function hasEsModuleExport (code) {
|
|
295
|
+
return /^\s*export\s+(?:default|function|class|const|let|var|\{)/m.test(code)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function hasStaticImport (code) {
|
|
299
|
+
return /^\s*import\s+(?!\()/m.test(code)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function hasRequireCall (code) {
|
|
303
|
+
return /\brequire\s*\(/.test(code)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function shouldBundleModelCode (code) {
|
|
307
|
+
return hasRequireCall(code) || hasStaticImport(code) || hasCommonJsExport(code) || hasEsModuleExport(code)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildBundledModelExposeCode (bundleGlobalName, modelName) {
|
|
311
|
+
return `
|
|
312
|
+
;(function () {
|
|
313
|
+
var mod = ${bundleGlobalName}
|
|
314
|
+
var target = mod
|
|
315
|
+
if (mod && (typeof mod === 'object')) {
|
|
316
|
+
if (typeof mod[${JSON.stringify(modelName)}] !== 'undefined') {
|
|
317
|
+
target = mod[${JSON.stringify(modelName)}]
|
|
318
|
+
} else if (typeof mod.default !== 'undefined') {
|
|
319
|
+
target = mod.default
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
globalThis[${JSON.stringify(modelName)}] = target
|
|
323
|
+
})()
|
|
324
|
+
`
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function bundleModelCode (model, modelPath, code) {
|
|
328
|
+
if (!shouldBundleModelCode(code)) return code
|
|
329
|
+
|
|
330
|
+
const esbuild = getOptionalEsbuild()
|
|
331
|
+
const modelName = getModelExportName(model)
|
|
332
|
+
const bundleGlobalName = `__jsee_bundle_${toJsIdentifier(modelName)}`
|
|
333
|
+
const needsExportShim = modelName &&
|
|
334
|
+
!hasCommonJsExport(code) &&
|
|
335
|
+
!hasEsModuleExport(code) &&
|
|
336
|
+
(hasRequireCall(code) || hasStaticImport(code))
|
|
337
|
+
|
|
338
|
+
if (needsExportShim && !isJsIdentifier(modelName)) {
|
|
339
|
+
throw new Error(`Bundling ${model.url || modelPath} requires a valid JavaScript model name or an explicit module export.`)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const options = {
|
|
343
|
+
bundle: true,
|
|
344
|
+
write: false,
|
|
345
|
+
platform: 'browser',
|
|
346
|
+
format: 'iife',
|
|
347
|
+
globalName: bundleGlobalName,
|
|
348
|
+
logLevel: 'silent'
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (needsExportShim) {
|
|
352
|
+
options.stdin = {
|
|
353
|
+
contents: `${code}\nexport default ${modelName}\n`,
|
|
354
|
+
resolveDir: path.dirname(modelPath),
|
|
355
|
+
sourcefile: path.basename(modelPath),
|
|
356
|
+
loader: 'js'
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
options.entryPoints = [modelPath]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const result = await esbuild.build(options)
|
|
364
|
+
return result.outputFiles[0].text + buildBundledModelExposeCode(bundleGlobalName, modelName)
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw new Error(`Failed to bundle ${model.url || modelPath}: ${error.message}`)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
262
370
|
const FULL_BUNDLE_TYPES = ['chart', '3d', 'map']
|
|
263
371
|
|
|
264
372
|
function needsFullBundle (schema) {
|
|
@@ -739,7 +847,9 @@ function template(schema, blocks) {
|
|
|
739
847
|
|
|
740
848
|
async function gen (pargv, returnHtml=false) {
|
|
741
849
|
// Determine if JSEE CLI is imported or run directly
|
|
742
|
-
const
|
|
850
|
+
const mainPath = require.main && require.main.path ? require.main.path : process.cwd()
|
|
851
|
+
const mainFilename = require.main && require.main.filename ? require.main.filename : ''
|
|
852
|
+
const imported = path.dirname(__dirname) !== path.dirname(mainPath)
|
|
743
853
|
|
|
744
854
|
// First pass over CLI arguments
|
|
745
855
|
// JSEE-level args
|
|
@@ -749,14 +859,14 @@ async function gen (pargv, returnHtml=false) {
|
|
|
749
859
|
description: 'd',
|
|
750
860
|
port: 'p',
|
|
751
861
|
version: 'v',
|
|
752
|
-
fetch: 'f',
|
|
862
|
+
fetch: ['f', 'bundle', 'b'],
|
|
753
863
|
execute: 'e',
|
|
754
864
|
cdn: 'c',
|
|
755
865
|
runtime: 'r',
|
|
756
866
|
}
|
|
757
867
|
const argvDefault = {
|
|
758
868
|
execute: 'auto', // execute the model code on the server (auto = server-side when serving local .js models)
|
|
759
|
-
fetch: false, //
|
|
869
|
+
fetch: false, // bundle runtime, local code and imports into generated output
|
|
760
870
|
inputs: 'schema.json', // default input is schema.json in the current working directory
|
|
761
871
|
port: 3000, // default port for the server
|
|
762
872
|
version: 'latest', // default version of JSEE runtime to use
|
|
@@ -798,7 +908,7 @@ async function gen (pargv, returnHtml=false) {
|
|
|
798
908
|
new JSEE({
|
|
799
909
|
container: document.getElementById('jsee-container'),
|
|
800
910
|
schema: {
|
|
801
|
-
model: { code: function chat(
|
|
911
|
+
model: { code: function chat(inputs) { return { chat: 'You said: ' + inputs.message } }, worker: false },
|
|
802
912
|
inputs: [{ name: 'message', type: 'string', enter: true }],
|
|
803
913
|
outputs: [{ name: 'chat', type: 'chat' }]
|
|
804
914
|
}
|
|
@@ -821,7 +931,7 @@ async function gen (pargv, returnHtml=false) {
|
|
|
821
931
|
new JSEE({
|
|
822
932
|
container: document.getElementById('jsee-container'),
|
|
823
933
|
schema: {
|
|
824
|
-
model: { code: function greet(
|
|
934
|
+
model: { code: function greet(inputs) { return { greeting: (inputs.name + '\\n').repeat(inputs.count) } }, worker: false },
|
|
825
935
|
inputs: [
|
|
826
936
|
{ name: 'name', type: 'string', default: 'World' },
|
|
827
937
|
{ name: 'count', type: 'slider', min: 1, max: 10, default: 3 }
|
|
@@ -846,8 +956,8 @@ async function gen (pargv, returnHtml=false) {
|
|
|
846
956
|
inputs: [{ name: 'message', type: 'string', enter: true }],
|
|
847
957
|
outputs: [{ name: 'chat', type: 'chat' }]
|
|
848
958
|
}, null, 2) + '\n'
|
|
849
|
-
files['model.js'] = `function chat(
|
|
850
|
-
return { chat: 'You said: ' + message }
|
|
959
|
+
files['model.js'] = `function chat(inputs) {
|
|
960
|
+
return { chat: 'You said: ' + inputs.message }
|
|
851
961
|
}
|
|
852
962
|
`
|
|
853
963
|
files['README.md'] = `# Chat
|
|
@@ -870,8 +980,8 @@ npx @jseeio/jsee schema.json
|
|
|
870
980
|
],
|
|
871
981
|
outputs: [{ name: 'greeting', type: 'code' }]
|
|
872
982
|
}, null, 2) + '\n'
|
|
873
|
-
files['model.js'] = `function greet(
|
|
874
|
-
return { greeting: (name + '\\n').repeat(count) }
|
|
983
|
+
files['model.js'] = `function greet(inputs) {
|
|
984
|
+
return { greeting: (inputs.name + '\\n').repeat(inputs.count) }
|
|
875
985
|
}
|
|
876
986
|
`
|
|
877
987
|
files['README.md'] = `# My App
|
|
@@ -918,7 +1028,8 @@ Options:
|
|
|
918
1028
|
-d, --description <file> Markdown description file to include
|
|
919
1029
|
-p, --port <number> Dev server port (default: 3000)
|
|
920
1030
|
-v, --version <version> JSEE runtime version (default: latest)
|
|
921
|
-
-
|
|
1031
|
+
-b, --bundle Bundle runtime + dependencies into output
|
|
1032
|
+
-f, --fetch Alias for --bundle
|
|
922
1033
|
-e, --execute Execute model server-side (auto-enabled when serving local .js models)
|
|
923
1034
|
--client Force client-side execution (disable auto server-side)
|
|
924
1035
|
-c, --cdn <url|bool> Rewrite model URLs for CDN deployment
|
|
@@ -940,7 +1051,7 @@ Examples:
|
|
|
940
1051
|
jsee schema.json --a=100 --b=200 Pass named data inputs
|
|
941
1052
|
jsee schema.json data.csv Pass a file path as input
|
|
942
1053
|
jsee schema.json -o app.html Generate static HTML file
|
|
943
|
-
jsee schema.json -o app.html
|
|
1054
|
+
jsee schema.json -o app.html --bundle Generate self-contained HTML with bundled runtime
|
|
944
1055
|
jsee -p 8080 Start dev server on port 8080
|
|
945
1056
|
jsee report.pdf Serve a PDF file (auto-detected viewer)
|
|
946
1057
|
jsee data/ Serve a folder (file browser with preview)
|
|
@@ -971,8 +1082,8 @@ Documentation: https://jsee.org
|
|
|
971
1082
|
log('Current working directory:', process.cwd())
|
|
972
1083
|
log('Script location:', __dirname)
|
|
973
1084
|
log('Script file:', __filename)
|
|
974
|
-
log('Require location:',
|
|
975
|
-
log('Require file:',
|
|
1085
|
+
log('Require location:', mainPath)
|
|
1086
|
+
log('Require file:', mainFilename)
|
|
976
1087
|
|
|
977
1088
|
let cwd = process.cwd()
|
|
978
1089
|
let inputs = argv.inputs
|
|
@@ -1081,7 +1192,7 @@ Documentation: https://jsee.org
|
|
|
1081
1192
|
}
|
|
1082
1193
|
log('Argv:', argv)
|
|
1083
1194
|
|
|
1084
|
-
//
|
|
1195
|
+
// Bundle/offline generation
|
|
1085
1196
|
// Check if schema has model, convert to array if needed
|
|
1086
1197
|
if (!schema.model) {
|
|
1087
1198
|
// console.error('No model found in schema')
|
|
@@ -1192,8 +1303,9 @@ Documentation: https://jsee.org
|
|
|
1192
1303
|
// Generate jsee code
|
|
1193
1304
|
let jseeHtml = ''
|
|
1194
1305
|
let hiddenElementHtml = ''
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1306
|
+
const shouldBundle = Boolean(argv.fetch)
|
|
1307
|
+
const runtimeMode = resolveRuntimeMode(argv.runtime, shouldBundle, hasOutputs)
|
|
1308
|
+
if (shouldBundle) {
|
|
1197
1309
|
// Fetch jsee code from the CDN or local server
|
|
1198
1310
|
const jseeCode = await loadRuntimeCode(argv.version, schema)
|
|
1199
1311
|
jseeHtml = `<script>${jseeCode}</script>`
|
|
@@ -1207,7 +1319,10 @@ Documentation: https://jsee.org
|
|
|
1207
1319
|
}
|
|
1208
1320
|
if (m.url) {
|
|
1209
1321
|
// Fetch model from the local file system (remote URLs not yet supported here)
|
|
1210
|
-
const
|
|
1322
|
+
const modelPath = path.join(cwd, m.url)
|
|
1323
|
+
if (!m.name) m.name = getModelExportName(m)
|
|
1324
|
+
const modelSource = fs.readFileSync(modelPath, 'utf8')
|
|
1325
|
+
const modelCode = await bundleModelCode(m, modelPath, modelSource)
|
|
1211
1326
|
hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${m.url}">${modelCode}</script>`
|
|
1212
1327
|
}
|
|
1213
1328
|
const imports = toArray(m.imports)
|
|
@@ -1499,3 +1614,5 @@ module.exports.resolveFetchImport = resolveFetchImport
|
|
|
1499
1614
|
module.exports.resolveRuntimeMode = resolveRuntimeMode
|
|
1500
1615
|
module.exports.resolveOutputPath = resolveOutputPath
|
|
1501
1616
|
module.exports.needsFullBundle = needsFullBundle
|
|
1617
|
+
module.exports.shouldBundleModelCode = shouldBundleModelCode
|
|
1618
|
+
module.exports.bundleModelCode = bundleModelCode
|
package/src/main.js
CHANGED
|
@@ -1010,6 +1010,18 @@ export default class JSEE {
|
|
|
1010
1010
|
|| (output.alias && res[output.alias])
|
|
1011
1011
|
if (typeof r !== 'undefined') {
|
|
1012
1012
|
log(`Updating output: ${output.name} with data: ${typeof r}`)
|
|
1013
|
+
if (output.type === 'file' && r && typeof r === 'object' && !Array.isArray(r)) {
|
|
1014
|
+
if (typeof r.filename === 'string') output.filename = r.filename
|
|
1015
|
+
if (typeof r.name === 'string' && !output.filename) output.filename = r.name
|
|
1016
|
+
if (typeof r.mime === 'string') output.mime = r.mime
|
|
1017
|
+
if (typeof r.contentType === 'string') output.mime = r.contentType
|
|
1018
|
+
if (Object.prototype.hasOwnProperty.call(r, 'content')) output.value = r.content
|
|
1019
|
+
else if (Object.prototype.hasOwnProperty.call(r, 'value')) output.value = r.value
|
|
1020
|
+
else if (Object.prototype.hasOwnProperty.call(r, 'data')) output.value = r.data
|
|
1021
|
+
else if (Object.prototype.hasOwnProperty.call(r, 'url')) output.value = r.url
|
|
1022
|
+
else output.value = r
|
|
1023
|
+
return
|
|
1024
|
+
}
|
|
1013
1025
|
// Convert large base64 image data URLs to blob URLs for efficiency
|
|
1014
1026
|
if (output.type === 'image' && typeof r === 'string'
|
|
1015
1027
|
&& r.startsWith('data:') && r.length > 50000) {
|
|
@@ -19,6 +19,27 @@ function stringify (v) {
|
|
|
19
19
|
: JSON.stringify(v)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function getFileDescriptor (output) {
|
|
23
|
+
const value = output.value
|
|
24
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
25
|
+
return {
|
|
26
|
+
filename: output.filename || output.name || 'output',
|
|
27
|
+
content: value,
|
|
28
|
+
mime: output.mime || output.contentType || 'application/octet-stream'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const has = (key) => Object.prototype.hasOwnProperty.call(value, key)
|
|
32
|
+
return {
|
|
33
|
+
filename: value.filename || value.name || output.filename || output.name || 'output',
|
|
34
|
+
content: has('content') ? value.content
|
|
35
|
+
: has('value') ? value.value
|
|
36
|
+
: has('data') ? value.data
|
|
37
|
+
: has('url') ? value.url
|
|
38
|
+
: value,
|
|
39
|
+
mime: value.mime || value.contentType || output.mime || output.contentType || 'application/octet-stream'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
const component = {
|
|
23
44
|
props: ['output'],
|
|
24
45
|
emits: ['notification'],
|
|
@@ -260,19 +281,24 @@ const component = {
|
|
|
260
281
|
}
|
|
261
282
|
},
|
|
262
283
|
downloadFile () {
|
|
263
|
-
|
|
264
|
-
let
|
|
284
|
+
const file = getFileDescriptor(this.output)
|
|
285
|
+
let filename = file.filename
|
|
286
|
+
let value = file.content
|
|
265
287
|
if (typeof value === 'string' && value.startsWith('data:')) {
|
|
266
288
|
fetch(value)
|
|
267
289
|
.then(r => r.blob())
|
|
268
290
|
.then(blob => saveAs(blob, filename))
|
|
269
291
|
.catch(() => {
|
|
270
|
-
let blob = new Blob([value], { type:
|
|
292
|
+
let blob = new Blob([value], { type: file.mime })
|
|
271
293
|
saveAs(blob, filename)
|
|
272
294
|
})
|
|
273
295
|
} else {
|
|
296
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
297
|
+
saveAs(value, filename)
|
|
298
|
+
return
|
|
299
|
+
}
|
|
274
300
|
let content = typeof value === 'string' ? value : JSON.stringify(value)
|
|
275
|
-
let blob = new Blob([content], { type:
|
|
301
|
+
let blob = new Blob([content], { type: file.mime })
|
|
276
302
|
saveAs(blob, filename)
|
|
277
303
|
}
|
|
278
304
|
},
|