@sdeverywhere/plugin-check 0.3.22 → 0.3.23

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.
@@ -6,6 +6,8 @@ import type { BundleLocation, BundleSpec } from '@sdeverywhere/check-ui-shell'
6
6
  import { initAppShell } from '@sdeverywhere/check-ui-shell'
7
7
  import '@sdeverywhere/check-ui-shell/dist/style.css'
8
8
 
9
+ import type { BundleMetadata, BundleResult } from './load-bundle'
10
+ import { loadLocalBundle, loadRemoteBundle } from './load-bundle'
9
11
  import { initOverlay } from './overlay'
10
12
 
11
13
  import './global.css'
@@ -15,11 +17,6 @@ import { createBundle as createBaselineBundle } from '@_baseline_bundle_'
15
17
  import { createBundle as createCurrentBundle } from '@_current_bundle_'
16
18
  import { getConfigOptions } from '@_test_config_'
17
19
 
18
- interface BundleMetadata {
19
- name: string
20
- url: string
21
- }
22
-
23
20
  function loadBundleMetadata(side: 'left' | 'right'): BundleMetadata | undefined {
24
21
  if (import.meta.hot) {
25
22
  const metadataJson = localStorage.getItem(`sde-check-selected-bundle-${side}`)
@@ -43,7 +40,7 @@ function saveBundleMetadata(side: 'left' | 'right', metadata: BundleMetadata): v
43
40
  let savedBundleMetadataL: BundleMetadata | undefined
44
41
  let savedBundleMetadataR: BundleMetadata | undefined
45
42
  // The following value will be injected by `vite-config-for-report.ts`
46
- const bundlesPath = './bundles/*.txt'
43
+ const bundlesPath = './bundles/**/*.txt'
47
44
  if (import.meta.hot && bundlesPath) {
48
45
  // Restore the previously selected bundles (from before the page was reloaded)
49
46
  savedBundleMetadataL = loadBundleMetadata('left')
@@ -90,12 +87,6 @@ async function initForProduction(): Promise<void> {
90
87
  }
91
88
 
92
89
  async function initForLocal(): Promise<void> {
93
- interface BundleResult {
94
- bundle: Bundle
95
- bundleName: string
96
- bundleUrl: string
97
- }
98
-
99
90
  async function createBundle(bundleMetadata: BundleMetadata | undefined): Promise<BundleResult> {
100
91
  if (bundleMetadata === undefined) {
101
92
  bundleMetadata = {
@@ -107,16 +98,7 @@ async function initForLocal(): Promise<void> {
107
98
  if (bundleMetadata.url.startsWith('http')) {
108
99
  // Load remote bundles using dynamic import
109
100
  try {
110
- // Add cache busting parameter
111
- const cacheBuster = `?cb=${Date.now()}`
112
- const fullUrl = `${bundleMetadata.url}${cacheBuster}`
113
- const module = await import(/* @vite-ignore */ fullUrl)
114
- const bundle = module.createBundle() as Bundle
115
- return {
116
- bundle,
117
- bundleName: bundleMetadata.name,
118
- bundleUrl: bundleMetadata.url
119
- }
101
+ return loadRemoteBundle(bundleMetadata)
120
102
  } catch (e) {
121
103
  console.error(
122
104
  `ERROR: Failed to load remote bundle from ${bundleMetadata.url}; will use "current" bundle instead. Cause:`,
@@ -124,35 +106,15 @@ async function initForLocal(): Promise<void> {
124
106
  )
125
107
  }
126
108
  } else if (bundleMetadata.url.startsWith('file://')) {
127
- // Load local bundles using `import.meta.glob` (since dynamic import isn't
128
- // available for file URLs due to security restrictions). The glob pattern
129
- // part will be replaced by Vite (see `vite-config-for-report.ts`). Note
130
- // that we provide a placeholder here that looks like a valid glob pattern,
131
- // since Vite's dependency resolver will report errors if it is invalid
132
- // (not a literal).
109
+ // Load local bundles using `import.meta.glob`
133
110
  try {
134
- const bundlesGlob = import.meta.glob('./bundles/*.txt', {
135
- eager: false
136
- })
137
- const remoteBundleUrlParts = bundleMetadata.url.split('/')
138
- const remoteBundleFileName = remoteBundleUrlParts[remoteBundleUrlParts.length - 1]
139
- const bundleKey = Object.keys(bundlesGlob).find(key => {
140
- const bundlePathParts = key.split('/')
141
- const bundleFileName = bundlePathParts[bundlePathParts.length - 1]
142
- return bundleFileName === remoteBundleFileName
143
- })
144
- if (bundleKey) {
145
- type BundleModule = {
146
- createBundle(): Bundle
147
- }
148
- const loadBundle = bundlesGlob[bundleKey]
149
- const module = (await loadBundle()) as BundleModule
150
- const bundle = module.createBundle() as Bundle
151
- return {
152
- bundle,
153
- bundleName: bundleMetadata.name,
154
- bundleUrl: bundleMetadata.url
155
- }
111
+ const result = await loadLocalBundle(bundleMetadata)
112
+ if (result) {
113
+ return result
114
+ } else {
115
+ console.error(
116
+ `ERROR: Bundle key not found in glob for ${bundleMetadata.name}; will use "current" bundle instead`
117
+ )
156
118
  }
157
119
  } catch (e) {
158
120
  console.error(
@@ -269,21 +231,14 @@ async function getLocalBundles(): Promise<BundleLocation[]> {
269
231
  const handleSuccess = (data: { bundles: Array<{ name: string; url: string; lastModified: string }> }) => {
270
232
  cleanup()
271
233
 
272
- // Add the bundles that were found in the Node process
234
+ // Add the bundles that were found in the Node process. The bundles returned from the Node process
235
+ // include both local bundles and the special "current" bundle with its up-to-date last modified time.
273
236
  const bundles: BundleLocation[] = data.bundles.map(b => ({
274
237
  name: b.name,
275
238
  url: b.url,
276
239
  lastModified: b.lastModified
277
240
  }))
278
241
 
279
- // Add the special "current" bundle that is generated by the builder
280
- const currentBundleLastModified = __CURRENT_BUNDLE_LAST_MODIFIED__
281
- bundles.push({
282
- name: 'current',
283
- url: 'current',
284
- lastModified: currentBundleLastModified
285
- })
286
-
287
242
  resolve(bundles)
288
243
  }
289
244
 
@@ -0,0 +1,95 @@
1
+ // Copyright (c) 2025 Climate Interactive / New Venture Fund
2
+
3
+ import type { Bundle } from '@sdeverywhere/check-core'
4
+
5
+ export interface BundleMetadata {
6
+ name: string
7
+ url: string
8
+ }
9
+
10
+ export interface BundleResult {
11
+ bundle: Bundle
12
+ bundleName: string
13
+ bundleUrl: string
14
+ }
15
+
16
+ /**
17
+ * Load a remote bundle from a given URL.
18
+ */
19
+ export async function loadRemoteBundle(bundleMetadata: BundleMetadata): Promise<BundleResult> {
20
+ // Add cache busting parameter
21
+ const cacheBuster = `?cb=${Date.now()}`
22
+ const fullUrl = `${bundleMetadata.url}${cacheBuster}`
23
+ const module = await import(/* @vite-ignore */ fullUrl)
24
+ const bundle = module.createBundle() as Bundle
25
+ return {
26
+ bundle,
27
+ bundleName: bundleMetadata.name,
28
+ bundleUrl: bundleMetadata.url
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Load a local bundle from the file system using dynamic import (only available in development mode with HMR).
34
+ */
35
+ export async function loadLocalBundle(bundleMetadata: BundleMetadata): Promise<BundleResult | undefined> {
36
+ // Only available in development mode with HMR
37
+ if (!import.meta.hot) {
38
+ return undefined
39
+ }
40
+
41
+ return new Promise((resolve, reject) => {
42
+ // Set up listeners
43
+ const handleSuccess = async (data: { name: string; url: string; sourceCode: string }) => {
44
+ cleanup()
45
+ if (data.url === bundleMetadata.url) {
46
+ try {
47
+ // Create a blob URL from the source code
48
+ const blob = new Blob([data.sourceCode], { type: 'application/javascript' })
49
+ const blobUrl = URL.createObjectURL(blob)
50
+
51
+ // Import the bundle from the blob URL
52
+ const module = await import(/* @vite-ignore */ blobUrl)
53
+ const bundle = module.createBundle() as Bundle
54
+
55
+ // Clean up the blob URL
56
+ URL.revokeObjectURL(blobUrl)
57
+
58
+ resolve({
59
+ bundle,
60
+ bundleName: bundleMetadata.name,
61
+ bundleUrl: bundleMetadata.url
62
+ })
63
+ } catch (error) {
64
+ console.error(`Failed to execute bundle ${bundleMetadata.name}:`, error)
65
+ resolve(undefined)
66
+ }
67
+ }
68
+ }
69
+
70
+ const handleError = (data: { name: string; url: string; error: string }) => {
71
+ cleanup()
72
+ if (data.url === bundleMetadata.url) {
73
+ console.error(`Failed to load bundle ${bundleMetadata.name}:`, data.error)
74
+ resolve(undefined)
75
+ }
76
+ }
77
+
78
+ const cleanup = () => {
79
+ import.meta.hot.off('load-bundle-success', handleSuccess)
80
+ import.meta.hot.off('load-bundle-error', handleError)
81
+ }
82
+
83
+ import.meta.hot.on('load-bundle-success', handleSuccess)
84
+ import.meta.hot.on('load-bundle-error', handleError)
85
+
86
+ // Send request to load bundle
87
+ import.meta.hot.send('load-bundle', { url: bundleMetadata.url, name: bundleMetadata.name })
88
+
89
+ // Timeout after 10 seconds
90
+ setTimeout(() => {
91
+ cleanup()
92
+ reject(new Error(`Timeout waiting for bundle: ${bundleMetadata.name}`))
93
+ }, 10000)
94
+ })
95
+ }