diff --git a/babel.config.js b/babel.config.js index 8d4d5bb7b..00fe78963 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,6 +9,7 @@ module.exports = { presets: [ ['@babel/preset-env', {targets: {node: 'current'}}], ], + plugins: ['babel-plugin-transform-import-meta'], }, }, } diff --git a/package.json b/package.json index fee4b26e5..e8641e6ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bldrs", - "version": "1.0.1805", + "version": "1.0.1809", "main": "src/index.jsx", "license": "AGPL-3.0", "homepage": "https://github.com/bldrs-ai/Share", @@ -14,7 +14,7 @@ "build-conway-debug": "yarn clean && yarn build-share-conway && yarn build-share-copy-wasm-conway-profile", "build-webifc": "yarn clean && yarn build-share-webifc", "build-cosmos": "shx mkdir -p docs && shx rm -rf docs/cosmos && cosmos-export --config .cosmos.config.json && shx mv cosmos-export docs/cosmos", - "build-share": "yarn update-version && node tools/esbuild/build.js && shx mkdir -p docs/static/js && shx cp src/OPFS/OPFS.worker.js docs/ && shx cp src/net/github/Cache.js docs/ && yarn build-share-copy-wasm-conway-MT", + "build-share": "yarn update-version && node tools/esbuild/build.js && shx mkdir -p docs/static/js && yarn build-share-copy-wasm-conway-MT", "build-share-conway": "run-script-os", "build-share-conway:win32": "set USE_WEBIFC_SHIM=true && yarn build-share", "build-share-conway:linux:darwin": "USE_WEBIFC_SHIM=true yarn build-share", @@ -141,6 +141,7 @@ "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", "babel-jest": "28.1.3", + "babel-plugin-transform-import-meta": "^2.3.3", "eslint": "8.22.0", "eslint-config-google": "0.14.0", "eslint-import-resolver-typescript": "3.5.2", diff --git a/src/OPFS/OPFS.worker.js b/src/OPFS/OPFS.worker.js index 52def6b23..31777f035 100644 --- a/src/OPFS/OPFS.worker.js +++ b/src/OPFS/OPFS.worker.js @@ -1,5 +1,5 @@ -let GITHUB_BASE_URL_AUTHENTICATED = null -let GITHUB_BASE_URL_UNAUTHENTICATED = null +import * as CacheModule from '../net/github/Cache.js' + /** * @global @@ -9,8 +9,9 @@ let GITHUB_BASE_URL_UNAUTHENTICATED = null /* global FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemSyncAccessHandle */ -/* global importScripts, CacheModule */ -importScripts('./Cache.js') +let GITHUB_BASE_URL_AUTHENTICATED = null +let GITHUB_BASE_URL_UNAUTHENTICATED = null + self.addEventListener('message', async (event) => { try { diff --git a/src/OPFS/OPFSService.js b/src/OPFS/OPFSService.js index 23103e913..6aa11c0c0 100644 --- a/src/OPFS/OPFSService.js +++ b/src/OPFS/OPFSService.js @@ -9,14 +9,15 @@ let workerRef = null * Initializes and returns a reference to a web worker. * * Checks if a web worker reference already exists; if not, it creates a new web worker - * instance using the specified script path ('/OPFS.Worker.js'). This ensures that only one - * instance of the worker is created and reused across the application, optimizing resource usage. + * instance using the specified script path. This ensures that only one instance of the + * worker is created and reused across the application, optimizing resource usage. + * Uses ESM module worker with {type: 'module'}. * * @return {Worker} The reference to the initialized web worker. */ export function initializeWorker() { if (workerRef === null) { - workerRef = new Worker('/OPFS.Worker.js') + workerRef = new Worker(new URL('/OPFS.Worker.js', import.meta.url), {type: 'module'}) workerRef.postMessage({ command: 'initializeWorker', diff --git a/src/net/github/Cache.js b/src/net/github/Cache.js index 97905dee5..2e633fa84 100644 --- a/src/net/github/Cache.js +++ b/src/net/github/Cache.js @@ -1,220 +1,205 @@ /** * This module implements an etag caching system for network requests * using the browser Cache API. - * This module was rewritten to support ES6 + CommonJS for Web-Workers - * using the UMD (Universal Module Definition) pattern. * * Usage: - * 1. ES6 Main Thread: - * import {checkCache} from './Cache' - * ... - * checkCache("test") - * 2. CommonJS (Web Worker): - * importScripts('./Cache.js'); - * ... - * CacheModule.checkCache("test") + * import {checkCache} from './Cache' + * ... + * checkCache("test") */ -(function(root, factory) { - // eslint-disable-next-line no-undef - if (typeof define === 'function' && define.amd) { - // AMD - // eslint-disable-next-line no-undef - define([], factory) - } else if (typeof module === 'object' && module.exports) { - // CommonJS - module.exports = factory() - } else { - // Browser globals - root.CacheModule = factory() - } - // eslint-disable-next-line no-invalid-this -}(typeof self !== 'undefined' ? self : this, function() { - // http request etag cache - let httpCache = null - - const httpCacheApiAvailable = (typeof caches !== 'undefined') - - /** - * Retrieves the HTTP cache, opening it if it doesn't already exist. - * - * @return {Promise} The HTTP cache object. - */ - async function getCache() { - if (!httpCache) { - httpCache = await openCache() - } - return httpCache - } +// http request etag cache +let httpCache = null - /** - * Opens the HTTP cache if the Cache API is available. - * - * @return {Promise} A Cache object if the Cache API is available, otherwise an empty object. - */ - async function openCache() { - if (httpCacheApiAvailable) { - return await caches.open('bldrs-github-api-cache') - } - return {} - } - - /** - * Converts a cached response to an Octokit response format. - * - * @param {Response|null} cachedResponse The cached response to convert. - * @return {Promise} A structured object mimicking an Octokit response, or null if the response is invalid. - */ - async function convertToOctokitResponse(cachedResponse) { - if (!cachedResponse) { - return null - } +const httpCacheApiAvailable = (typeof caches !== 'undefined') - const data = await cachedResponse.json() - const headers = cachedResponse.headers - const status = cachedResponse.status - const octokitResponse = { - data: data, - status: status, - headers: {}, - url: cachedResponse.url, - } +/** + * Retrieves the HTTP cache, opening it if it doesn't already exist. + * + * @return {Promise} The HTTP cache object. + */ +async function getCache() { + if (!httpCache) { + httpCache = await openCache() + } + return httpCache +} - headers.forEach((value, key) => { - octokitResponse.headers[key] = value - }) - return octokitResponse +/** + * Opens the HTTP cache if the Cache API is available. + * + * @return {Promise} A Cache object if the Cache API is available, otherwise an empty object. + */ +async function openCache() { + if (httpCacheApiAvailable) { + return await caches.open('bldrs-github-api-cache') } + return {} +} - /** - * Checks the cache for a specific key and converts the response to an Octokit response format. - * - * @param {string} key The key to search for in the cache. - * @return {Promise} The cached response in Octokit format, or null if not found or an error occurs. - */ - async function checkCache(key) { - try { - if (httpCacheApiAvailable) { - const _httpCache = await getCache() - const response = await _httpCache.match(key) - return await convertToOctokitResponse(response) - } else { - return httpCache[key] - } - } catch (error) { - return null - } + +/** + * Converts a cached response to an Octokit response format. + * + * @param {Response|null} cachedResponse The cached response to convert. + * @return {Promise} A structured object mimicking an Octokit response, or null if the response is invalid. + */ +async function convertToOctokitResponse(cachedResponse) { + if (!cachedResponse) { + return null } - /** - * Checks the cache for a specific key and converts the response to an Octokit response format. - * - * @param {string} key The key to search for in the cache. - * @return {Promise} The cached response, or null if not found or an error occurs. - */ - async function checkCacheRaw(key) { - try { - if (httpCacheApiAvailable) { - const _httpCache = await getCache() - const response = await _httpCache.match(key) - return response - } else { - return httpCache[key] - } - } catch (error) { - return null - } + const data = await cachedResponse.json() + const headers = cachedResponse.headers + const status = cachedResponse.status + + const octokitResponse = { + data: data, + status: status, + headers: {}, + url: cachedResponse.url, } - /** - * Updates the cache entry for a given key with the response received. - * The cache will only be updated if the response headers contain an ETag. - * - * @param {string} key The cache key associated with the request. - * @param {object} response The HTTP response object from Octokit which includes headers and data. - */ - async function updateCache(key, response) { - if (response.headers.etag) { + headers.forEach((value, key) => { + octokitResponse.headers[key] = value + }) + + return octokitResponse +} + + +/** + * Checks the cache for a specific key and converts the response to an Octokit response format. + * + * @param {string} key The key to search for in the cache. + * @return {Promise} The cached response in Octokit format, or null if not found or an error occurs. + */ +async function checkCache(key) { + try { + if (httpCacheApiAvailable) { const _httpCache = await getCache() - if (httpCacheApiAvailable) { - const body = JSON.stringify(response.data) - const wrappedResponse = new Response(body) - wrappedResponse.headers.set('etag', response.headers.etag) - _httpCache.put(key, wrappedResponse) - } else { - _httpCache[key] = { - response: response, - } - } + const response = await _httpCache.match(key) + return await convertToOctokitResponse(response) + } else { + return httpCache[key] } + } catch (error) { + return null } +} - /** - * @param {string} key - Cache key - */ - async function deleteCache(key) { - const _httpCache = await getCache() - const success = await _httpCache.delete(key) - - if (success) { - // eslint-disable-next-line no-console - console.log(`Deleted ${key} from cache`) +/** + * Checks the cache for a specific key and converts the response to an Octokit response format. + * + * @param {string} key The key to search for in the cache. + * @return {Promise} The cached response, or null if not found or an error occurs. + */ +async function checkCacheRaw(key) { + try { + if (httpCacheApiAvailable) { + const _httpCache = await getCache() + const response = await _httpCache.match(key) + return response } else { - // eslint-disable-next-line no-console - console.log(`Failed to delete ${key} from cache`) + return httpCache[key] } + } catch (error) { + return null } +} - /** - * Updates the cache entry for a given key with the response received. - * The cache will only be updated if the response headers contain an ETag. - * - * @param {string} key The cache key associated with the request. - * @param {object} response The HTTP raw response object which includes headers and data. - * @param {string} commitHash The commit hash - */ - async function updateCacheRaw(key, response, commitHash) { + +/** + * Updates the cache entry for a given key with the response received. + * The cache will only be updated if the response headers contain an ETag. + * + * @param {string} key The cache key associated with the request. + * @param {object} response The HTTP response object from Octokit which includes headers and data. + */ +async function updateCache(key, response) { + if (response.headers.etag) { const _httpCache = await getCache() if (httpCacheApiAvailable) { - // Create a new Response with the body and headers from the original response - const headers = new Headers(response.headers) // Clone existing headers - if (commitHash !== null) { - headers.set('CommitHash', commitHash) // Set the new header - } - const wrappedResponse = new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: headers, // Use the updated headers - }) - await _httpCache.put(key, wrappedResponse) + const body = JSON.stringify(response.data) + const wrappedResponse = new Response(body) + wrappedResponse.headers.set('etag', response.headers.etag) + _httpCache.put(key, wrappedResponse) } else { - // Create a new Response with the body and headers from the original response - const headers = new Headers(response.headers) // Clone existing headers - if (commitHash !== null) { - headers.set('CommitHash', commitHash) // Set the new header - } - const wrappedResponse = new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: headers, // Use the updated headers - }) _httpCache[key] = { - response: wrappedResponse, + response: response, } } } +} - // Export the functions - return { - getCache, - convertToOctokitResponse, - checkCache, - checkCacheRaw, - updateCache, - updateCacheRaw, - deleteCache, + +/** + * @param {string} key - Cache key + */ +async function deleteCache(key) { + const _httpCache = await getCache() + + const success = await _httpCache.delete(key) + + if (success) { + // eslint-disable-next-line no-console + console.log(`Deleted ${key} from cache`) + } else { + // eslint-disable-next-line no-console + console.log(`Failed to delete ${key} from cache`) + } +} + + +/** + * Updates the cache entry for a given key with the response received. + * The cache will only be updated if the response headers contain an ETag. + * + * @param {string} key The cache key associated with the request. + * @param {object} response The HTTP raw response object which includes headers and data. + * @param {string} commitHash The commit hash + */ +async function updateCacheRaw(key, response, commitHash) { + const _httpCache = await getCache() + if (httpCacheApiAvailable) { + // Create a new Response with the body and headers from the original response + const headers = new Headers(response.headers) // Clone existing headers + if (commitHash !== null) { + headers.set('CommitHash', commitHash) // Set the new header + } + const wrappedResponse = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: headers, // Use the updated headers + }) + await _httpCache.put(key, wrappedResponse) + } else { + // Create a new Response with the body and headers from the original response + const headers = new Headers(response.headers) // Clone existing headers + if (commitHash !== null) { + headers.set('CommitHash', commitHash) // Set the new header + } + const wrappedResponse = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: headers, // Use the updated headers + }) + _httpCache[key] = { + response: wrappedResponse, + } } -})) +} + +// Export the functions +export { + getCache, + convertToOctokitResponse, + checkCache, + checkCacheRaw, + updateCache, + updateCacheRaw, + deleteCache, +} diff --git a/tools/esbuild/build.js b/tools/esbuild/build.js index 4e3b4c791..8a4c3ef52 100644 --- a/tools/esbuild/build.js +++ b/tools/esbuild/build.js @@ -1,17 +1,34 @@ import esbuild from 'esbuild' import fs from 'node:fs' -import {join} from 'node:path' +import * as path from 'node:path' +import {fileURLToPath} from 'url' import config from './common.js' -esbuild - .build(config) - .then((result) => { +const mainBuild = esbuild.build(config) + +// Worker +const repoRoot = path.resolve(fileURLToPath(import.meta.url), '../../../') +const workerFile = path.resolve(repoRoot, 'src', 'OPFS', 'OPFS.worker.js') +const buildDir = path.resolve(repoRoot, 'docs') +const workerBuild = esbuild.build({ + ...config, + entryPoints: [workerFile], + outfile: path.join(buildDir, 'OPFS.Worker.js'), + outdir: undefined, + // Build worker as ESM bundle - requires {type: 'module'} when loading + format: 'esm', +}) + + +// Wait for both builds to complete +Promise.all([mainBuild, workerBuild]) + .then(([result]) => { // Remove development resources from non-development builds if (config.define['process.env.MSW_IS_ENABLED'] !== 'true') { // eslint-disable-next-line no-console console.log('Removing MSW from build') - fs.unlink(join(config.outdir, 'mockServiceWorker.js'), (err) => { + fs.unlink(path.join(config.outdir, 'mockServiceWorker.js'), (err) => { if (err !== null) { console.warn('Unknown return from MSW unlink. Expected null on success, got:', err) } @@ -24,9 +41,9 @@ esbuild console.log(`Bundle analysis at: ${metaFilename}`) } // eslint-disable-next-line no-console - console.log(`Build succeeded.`) + console.log('Build succeeded.') }) .catch((err) => { - console.error(`Build failed:`, err) + console.error('Build failed:', err) process.exit(1) }) diff --git a/yarn.lock b/yarn.lock index 0e6e14dd0..7166cf1b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5322,6 +5322,14 @@ babel-plugin-polyfill-regenerator@^0.5.3: dependencies: "@babel/helper-define-polyfill-provider" "^0.4.3" +babel-plugin-transform-import-meta@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.3.tgz#863de841f7df37e2bf39a057572a24e4f65f3c51" + integrity sha512-bbh30qz1m6ZU1ybJoNOhA2zaDvmeXMnGNBMVMDOJ1Fni4+wMBoy/j7MTRVmqAUCIcy54/rEnr9VEBsfcgbpm3Q== + dependencies: + "@babel/template" "^7.25.9" + tslib "^2.8.1" + babel-preset-current-node-syntax@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" @@ -12104,7 +12112,7 @@ tslib@^2.0.3: resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tslib@^2.6.2: +tslib@^2.6.2, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==