@lvce-editor/shared-process 0.125.14 → 0.125.16

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/index.js CHANGED
@@ -22,10 +22,10 @@ const toAbsoluteExtensionPath = (root, extensionPath) => {
22
22
  }
23
23
 
24
24
  /**
25
- * @param {{extensionPath?: string, extensionPaths?: string[] | string, onLoadCommands?: readonly unknown[], cspOptions?: {allowExternalConnections?: boolean}, testPath?: string, root?: string}} [options]
25
+ * @param {{bundleMode?: boolean, extensionPath?: string, extensionPaths?: string[] | string, onLoadCommands?: readonly unknown[], cspOptions?: {allowExternalConnections?: boolean}, testPath?: string, root?: string}} [options]
26
26
  */
27
27
  export const exportStatic = async (options = {}) => {
28
- const { cspOptions, extensionPath, extensionPaths = [], onLoadCommands = [], root = '', testPath = '' } = options
28
+ const { bundleMode = false, cspOptions, extensionPath, extensionPaths = [], onLoadCommands = [], root = '', testPath = '' } = options
29
29
  if (!root) {
30
30
  throw new Error(`root argument is required`)
31
31
  }
@@ -36,6 +36,7 @@ export const exportStatic = async (options = {}) => {
36
36
  const absoluteExtensionPaths = extensionPathList.map((extensionPath) => toAbsoluteExtensionPath(root, extensionPath))
37
37
  const pathPrefix = process.env.PATH_PREFIX || ''
38
38
  return fn.exportStatic({
39
+ bundleMode,
39
40
  cspOptions,
40
41
  extensionPath: absoluteExtensionPath,
41
42
  extensionPaths: absoluteExtensionPaths,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/shared-process",
3
- "version": "0.125.14",
3
+ "version": "0.125.16",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -27,6 +27,7 @@
27
27
  "@lvce-editor/rpc-registry": "9.66.2",
28
28
  "@lvce-editor/verror": "1.7.0",
29
29
  "is-object": "^1.0.2",
30
+ "rollup": "^4.63.6",
30
31
  "xdg-basedir": "^5.1.0"
31
32
  },
32
33
  "optionalDependencies": {
@@ -3,6 +3,6 @@ import { fileURLToPath } from 'url'
3
3
 
4
4
  export const getBuiltinExtensionsPath = () => {
5
5
  const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
6
- const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', '3f6689e', 'extensions')
6
+ const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', 'b9e03b1', 'extensions')
7
7
  return builtinExtensionsPath
8
8
  }
@@ -0,0 +1,88 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { rollup } from 'rollup'
4
+ import { createBundledWorkerConstructor } from '../BundledWorkerRuntime/BundledWorkerRuntime.js'
5
+
6
+ const mainCall = /^(?:await\s+)?main\(\);?$/m
7
+ const awaitedListen = /^await listen\(\);?$/m
8
+
9
+ const workerIds = ['editor', 'extensionManagementWorker', 'iconThemeWorker', 'cacheWorker', 'explorer']
10
+ const bindings = [
11
+ 'globalThis',
12
+ 'self',
13
+ 'Worker',
14
+ 'WorkerGlobalScope',
15
+ 'MessageChannel',
16
+ 'location',
17
+ 'name',
18
+ 'postMessage',
19
+ 'addEventListener',
20
+ 'removeEventListener',
21
+ 'close',
22
+ 'setTimeout',
23
+ 'clearTimeout',
24
+ 'setInterval',
25
+ 'clearInterval',
26
+ 'requestAnimationFrame',
27
+ 'cancelAnimationFrame',
28
+ 'fetch',
29
+ 'window',
30
+ 'document',
31
+ ]
32
+
33
+ export const createWorkerFactory = async (input, publicPath) => {
34
+ const bundle = await rollup({
35
+ input,
36
+ plugins: [
37
+ {
38
+ name: 'bundled-worker-scope',
39
+ resolveImportMeta(property) {
40
+ if (property === 'url') return 'globalThis.location.href'
41
+ throw new Error(`Unsupported worker import.meta property: ${property}`)
42
+ },
43
+ transform(code, id) {
44
+ if (id.replaceAll('\\', '/') === input.replaceAll('\\', '/')) {
45
+ // Capture rejected startup rather than leaving the parent's ready wait hanging.
46
+ if (awaitedListen.test(code)) return null
47
+ if (!mainCall.test(code)) throw new Error(`Missing worker startup call: ${input}`)
48
+ return code.replace(mainCall, 'const bundledStartupPromise = main();') + '\nawait bundledStartupPromise;\n'
49
+ }
50
+ return null
51
+ },
52
+ },
53
+ ],
54
+ preserveEntrySignatures: false,
55
+ })
56
+ try {
57
+ const { output } = await bundle.generate({ format: 'es', inlineDynamicImports: true })
58
+ const chunk = output[0]
59
+ if (chunk.type !== 'chunk' || chunk.imports.length || chunk.exports.length) throw new Error(`Worker must be self-contained: ${input}`)
60
+ return `[new URL(${JSON.stringify(publicPath)}, location.origin).href]: async (scope) => {\nconst { ${bindings.join(', ')} } = scope;\nawait (async () => {\n${chunk.code}\n})();\n}`
61
+ } finally {
62
+ await bundle.close()
63
+ }
64
+ }
65
+
66
+ export const bundleStaticWorkers = async ({ commitHash, pathPrefix, root, workers } ) => {
67
+ const paths = [
68
+ '/packages/renderer-worker/dist/rendererWorkerMain.js',
69
+ ...workerIds.map((id) => {
70
+ const worker = workers.find((item) => item.id === id)
71
+ if (!worker) throw new Error(`Missing bundled worker: ${id}`)
72
+ return worker.productionPath
73
+ }),
74
+ ]
75
+ const factories = []
76
+ for (const path of paths) {
77
+ const publicPath = `${pathPrefix}/${commitHash}${path}`
78
+ factories.push(await createWorkerFactory(join(root, 'dist', commitHash, path), publicPath))
79
+ }
80
+ const rendererDirectory = join(root, 'dist', commitHash, 'packages/renderer-process/dist')
81
+ const renderer = await readFile(join(rendererDirectory, 'rendererProcessMain.js'), 'utf8')
82
+ const runtime = `const Worker = (${createBundledWorkerConstructor.toString()})({${factories.join(',\n')}}, globalThis);\n`
83
+ await writeFile(join(rendererDirectory, 'renderer-process.bundled.js'), runtime + renderer)
84
+ const htmlPath = join(root, 'dist', 'index.html')
85
+ const html = await readFile(htmlPath, 'utf8')
86
+ if (!html.includes('/rendererProcessMain.js')) throw new Error('Missing renderer script in static export')
87
+ await writeFile(htmlPath, html.replaceAll('/rendererProcessMain.js', '/renderer-process.bundled.js'))
88
+ }
@@ -0,0 +1,183 @@
1
+ // This function is serialized into the static export. Keep it self-contained.
2
+ export const createBundledWorkerConstructor = (factories, host) => {
3
+ const NativeWorker = host.Worker
4
+ class BundledWorker extends host.EventTarget {
5
+ static [Symbol.hasInstance](value) {
6
+ return value instanceof NativeWorker || Object.prototype.isPrototypeOf.call(this.prototype, value)
7
+ }
8
+ onmessage = null
9
+ onmessageerror = null
10
+ onerror = null
11
+ stopped = false
12
+ ports = new Set ()
13
+ timers = new Map ()
14
+ frames = new Map ()
15
+ children = new Set ()
16
+ abortController = new host.AbortController()
17
+ endpoint
18
+
19
+ constructor(url, options = {}) {
20
+ super()
21
+ const resolved = new URL(url, host.location.href)
22
+ const factoryUrl = new URL(resolved)
23
+ factoryUrl.search = ''
24
+ factoryUrl.hash = ''
25
+ const factory = factories[resolved.href] || factories[factoryUrl.href]
26
+ if (!factory || options.type !== 'module') {
27
+ return new NativeWorker(url, options)
28
+ }
29
+ const channel = new host.MessageChannel()
30
+ this.endpoint = channel.port1
31
+ this.ports.add(channel.port1)
32
+ this.ports.add(channel.port2)
33
+ const dispatch = (event) => {
34
+ if (this.stopped) return
35
+ const copy = new host.MessageEvent(event.type, { data: event.data, ports: event.ports })
36
+ this.dispatchEvent(copy)
37
+ const listener = event.type === 'message' ? this.onmessage : this.onmessageerror
38
+ listener?.call(this, copy)
39
+ }
40
+ channel.port1.addEventListener('message', dispatch)
41
+ channel.port1.addEventListener('messageerror', dispatch)
42
+ channel.port1.start()
43
+ // The nested Worker/MessageChannel constructors have their own this.
44
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
45
+ const owner = this
46
+ const schedule =
47
+ (start, stop, repeat, registry = owner.timers) =>
48
+ (callback, delay, ...args) => {
49
+ if (owner.stopped) return 0
50
+ const id = start((...callbackArgs) => {
51
+ if (!repeat) registry.delete(id)
52
+ if (!owner.stopped) callback(...callbackArgs, ...args)
53
+ }, delay)
54
+ registry.set(id, stop)
55
+ return id
56
+ }
57
+ const clear = (id) => {
58
+ owner.timers.get(id)?.(id)
59
+ owner.timers.delete(id)
60
+ }
61
+ const trackedPorts = new WeakSet ()
62
+ const trackPort = (port) => {
63
+ if (owner.stopped) {
64
+ port.close()
65
+ return
66
+ }
67
+ if (trackedPorts.has(port)) return
68
+ trackedPorts.add(port)
69
+ owner.ports.add(port)
70
+ const close = port.close.bind(port)
71
+ const postMessage = port.postMessage.bind(port)
72
+ port.close = () => {
73
+ owner.ports.delete(port)
74
+ close()
75
+ }
76
+ port.postMessage = (message, options) => {
77
+ postMessage(message, options)
78
+ const transfer = Array.isArray(options) ? options : options?.transfer || []
79
+ for (const item of transfer) owner.ports.delete(item)
80
+ }
81
+ port.addEventListener('message', (event) => {
82
+ for (const transferred of event.ports) trackPort(transferred)
83
+ })
84
+ }
85
+ trackPort(channel.port2)
86
+ const scope = {
87
+ addEventListener: channel.port2.addEventListener.bind(channel.port2),
88
+ cancelAnimationFrame: (id) => {
89
+ owner.frames.get(id)?.(id)
90
+ owner.frames.delete(id)
91
+ },
92
+ clearInterval: clear,
93
+ clearTimeout: clear,
94
+ close: () => owner.terminate(),
95
+ document: undefined,
96
+ fetch: (input, init) => {
97
+ const signal = init?.signal || input?.signal
98
+ return host.fetch(typeof input === 'string' ? new URL(input, resolved) : input, {
99
+ ...init,
100
+ signal: signal ? host.AbortSignal.any([signal, owner.abortController.signal]) : owner.abortController.signal,
101
+ })
102
+ },
103
+ location: resolved,
104
+ MessageChannel: class extends host.MessageChannel {
105
+ constructor() {
106
+ super()
107
+ trackPort(this.port1)
108
+ trackPort(this.port2)
109
+ }
110
+ },
111
+ name: options.name || '',
112
+ postMessage: channel.port2.postMessage.bind(channel.port2),
113
+ removeEventListener: channel.port2.removeEventListener.bind(channel.port2),
114
+ requestAnimationFrame: schedule(host.requestAnimationFrame.bind(host), host.cancelAnimationFrame.bind(host), false, owner.frames),
115
+ setInterval: schedule(host.setInterval.bind(host), host.clearInterval.bind(host), true),
116
+ setTimeout: schedule(host.setTimeout.bind(host), host.clearTimeout.bind(host), false),
117
+ window: undefined,
118
+ Worker: class extends BundledWorker {
119
+ constructor(childUrl, childOptions) {
120
+ super(new URL(childUrl, resolved), childOptions)
121
+ if (owner.stopped) this.terminate()
122
+ else owner.children.add(this)
123
+ }
124
+ },
125
+ WorkerGlobalScope: class WorkerGlobalScope {},
126
+ }
127
+ Object.defineProperty(scope, 'onmessage', {
128
+ get: () => channel.port2.onmessage,
129
+ set: (listener) => {
130
+ channel.port2.onmessage = listener
131
+ },
132
+ })
133
+ const global = new Proxy(scope, {
134
+ get(target, key) {
135
+ if (key in target) return target[key]
136
+ return host[key]
137
+ },
138
+ has: (target, key) => key in target || key in host,
139
+ })
140
+ scope.globalThis = global
141
+ scope.self = global
142
+ channel.port2.start()
143
+ // Start after callers have installed readiness/error listeners.
144
+ queueMicrotask(() => {
145
+ if (owner.stopped) return
146
+ Promise.resolve()
147
+ .then(() => factory(global))
148
+ .catch((error) => {
149
+ if (owner.stopped) return
150
+ const event = new host.ErrorEvent('error', { error, filename: resolved.href, message: String(error) })
151
+ try {
152
+ owner.dispatchEvent(event)
153
+ owner.onerror?.call(owner, event)
154
+ } finally {
155
+ owner.terminate()
156
+ }
157
+ })
158
+ })
159
+ }
160
+
161
+ postMessage(message, transfer) {
162
+ if (!this.stopped) this.endpoint.postMessage(message, transfer)
163
+ }
164
+
165
+ terminate() {
166
+ if (this.stopped) return
167
+ this.stopped = true
168
+ this.abortController.abort()
169
+ for (const child of this.children) child.terminate()
170
+ for (const [id, cancel] of this.timers) cancel(id)
171
+ for (const [id, cancel] of this.frames) cancel(id)
172
+ for (const port of this.ports) port.close()
173
+ this.children.clear()
174
+ this.timers.clear()
175
+ this.frames.clear()
176
+ this.ports.clear()
177
+ this.onmessage = null
178
+ this.onmessageerror = null
179
+ this.onerror = null
180
+ }
181
+ }
182
+ return BundledWorker
183
+ }
@@ -756,9 +756,10 @@ const getExtensionPaths = (extensionPath, extensionPaths) => {
756
756
 
757
757
  /**
758
758
  *
759
- * @param {{root:string, pathPrefix:string, extensionPath:string, extensionPaths?:string[], onLoadCommands?:readonly unknown[], cspOptions?: {allowExternalConnections?: boolean}, testPath:string, useSimpleWebExtensionFile?:boolean, serverStaticPath?:string }} param0
759
+ * @param {{root:string, pathPrefix:string, extensionPath:string, extensionPaths?:string[], onLoadCommands?:readonly unknown[], cspOptions?: {allowExternalConnections?: boolean}, testPath:string, useSimpleWebExtensionFile?:boolean, serverStaticPath?:string, bundleMode?:boolean }} param0
760
760
  */
761
761
  export const exportStatic = async ({
762
+ bundleMode = false,
762
763
  cspOptions = {},
763
764
  extensionPath,
764
765
  extensionPaths,
@@ -814,6 +815,11 @@ export const exportStatic = async ({
814
815
  console.timeEnd('applyOverrides')
815
816
 
816
817
  await copyBuiltinWebExtensions({ commitHash, pathPrefix, root })
818
+
819
+ if (bundleMode) {
820
+ const BundleStaticWorkers = await import('../BundleStaticWorkers/BundleStaticWorkers.js')
821
+ await BundleStaticWorkers.bundleStaticWorkers({ commitHash, pathPrefix, root, workers })
822
+ }
817
823
  await updateExtensionsJson({ commitHash, extraExtensions: [], pathPrefix, root })
818
824
  await JsonFile.writeJson(Path.join(root, 'dist', commitHash, 'config', 'onLoadCommands.json'), onLoadCommands)
819
825
 
@@ -56,11 +56,11 @@ export const getSetupName = () => {
56
56
  return 'Lvce-Setup'
57
57
  }
58
58
 
59
- export const version = '0.125.14'
59
+ export const version = '0.125.16'
60
60
 
61
- export const commit = '3f6689e'
61
+ export const commit = 'b9e03b1'
62
62
 
63
- export const date = '2026-10-23T21:25:45.000Z'
63
+ export const date = '2026-10-24T21:14:34.000Z'
64
64
 
65
65
  export const getVersion = () => {
66
66
  return version
@@ -2,5 +2,5 @@ import { join } from 'node:path'
2
2
  import * as Root from '../Root/Root.js'
3
3
 
4
4
  export const getPreloadUrl = () => {
5
- return join(Root.root, 'static', '3f6689e', 'packages', 'preload', 'dist', 'index.js')
5
+ return join(Root.root, 'static', 'b9e03b1', 'packages', 'preload', 'dist', 'index.js')
6
6
  }