diff --git a/service_worker.js b/service_worker.js index 704f51c..6867862 100644 --- a/service_worker.js +++ b/service_worker.js @@ -13,18 +13,51 @@ self.addEventListener('activate', function(event) { }); */ -let data +let dir_handle self.addEventListener('message', async function(e) { const msg = e.data let reply if(msg.type == 'SET') { - data = msg.data + dir_handle = msg.data reply = null } else if(msg.type == 'GET') { - reply = data + reply = dir_handle } else { throw new Error('unknown message type: ' + msg.type) } e.ports[0].postMessage(reply) }) + +// Fake URL base prepended by code responsible for module loading +const FAKE_URL_BASE = 'https://leporello.import/' + +self.addEventListener("fetch", event => { + if(event.request.url.startsWith(FAKE_URL_BASE)) { + if(dir_handle != null) { + const headers = new Headers([ + ['Content-Type', 'text/javascript'] + ]) + const path = event.request.url.replace(FAKE_URL_BASE, '') + const response = read_file(dir_handle, path).then(file => + new Response(file, {headers}) + ) + event.respondWith(response) + } + } +}) + +const read_file = async (dir_handle, filename) => { + if(typeof(filename) == 'string') { + filename = filename.split('/') + } + const [first, ...rest] = filename + if(rest.length == 0) { + const fhandle = await dir_handle.getFileHandle(first) + const file_data = await fhandle.getFile() + return await file_data.text() + } else { + const nested_dir_handle = await dir_handle.getDirectoryHandle(first) + return read_file(nested_dir_handle, rest) + } +} diff --git a/src/effects.js b/src/effects.js index 58b10d0..82fd12f 100644 --- a/src/effects.js +++ b/src/effects.js @@ -10,7 +10,14 @@ const load_external_imports = async state => { } const urls = state.loading_external_imports_state.external_imports const results = await Promise.allSettled( - urls.map(u => import(u)) + urls.map(u => import( + /^\w+:\/\//.test(u) + ? // starts with protocol, import as is + u + : //local path, load using File System Access API, see service_worker.js + // Append fake host that will be intercepted in service worker + 'https://leporello.import/' + u + )) ) const modules = Object.fromEntries( results.map((r, i) => ( diff --git a/src/find_definitions.js b/src/find_definitions.js index 06f7135..86f9521 100644 --- a/src/find_definitions.js +++ b/src/find_definitions.js @@ -154,39 +154,6 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam } } -// see https://stackoverflow.com/a/29855511 - -// Joins path segments. Preserves initial "/" and resolves ".." and "." -// Does not support using ".." to go above/outside the root. -// This means that join("foo", "../../bar") will not resolve to "../bar" - -const join = new Function(` - // Split the inputs into a list of path commands. - var parts = []; - for (var i = 0, l = arguments.length; i < l; i++) { - parts = parts.concat(arguments[i].split("/")); - } - // Interpret the path commands to get the new resolved path. - var newParts = []; - for (i = 0, l = parts.length; i < l; i++) { - var part = parts[i]; - // Remove leading and trailing slashes - // Also remove "." segments - if (!part || part === ".") continue; - // Interpret ".." to pop the last segment - if (part === "..") newParts.pop(); - // Push new path segments. - else newParts.push(part); - } - // Preserve the initial slash if there was one. - if (parts[0] === "") newParts.unshift(""); - // Turn back into a single string path. - return newParts.join("/") || (newParts.length ? "/" : "."); -`) - -const concat_path = (base, imported) => - base == '' ? join(imported) : join(base, '..', imported) - export const find_export = (name, module) => { return map_find(module.stmts, n => { if(n.type != 'export') { @@ -197,6 +164,16 @@ export const find_export = (name, module) => { }) } +const BASE = 'dummy://dummy/' +const concat_path = (base, i) => { + const result = new URL(i, BASE + base).toString() + if(result.lastIndexOf(BASE) == 0) { + return result.replace(BASE, '') + } else { + return result + } + return result +} export const topsort_modules = (modules) => { const sort_module_deps = (module) => { diff --git a/src/globals.js b/src/globals.js index 19e36df..a2fea60 100644 --- a/src/globals.js +++ b/src/globals.js @@ -2,6 +2,7 @@ export const globals = new Set([ 'globalThis', // TODO Promise, // TODO Symbol + 'URL', 'Set', 'Map', "Infinity", diff --git a/test/test.js b/test/test.js index ef67200..0ee167d 100644 --- a/test/test.js +++ b/test/test.js @@ -2310,7 +2310,7 @@ const y = x()` on_async_call: (calls) => {console.log('test on async call', calls)} }) globalThis.__run_async_call() - delete globalThis.__run_async_call + //delete globalThis.__run_async_call }), ]