Skip to content

feat: Proof-of-concept for loading config as ES modules#318

Draft
ian-h-chamberlain wants to merge 1 commit into
glide-browser:mainfrom
ian-h-chamberlain:feature/esm-config-handler
Draft

feat: Proof-of-concept for loading config as ES modules#318
ian-h-chamberlain wants to merge 1 commit into
glide-browser:mainfrom
ian-h-chamberlain:feature/esm-config-handler

Conversation

@ian-h-chamberlain

@ian-h-chamberlain ian-h-chamberlain commented May 30, 2026

Copy link
Copy Markdown

Note: this is absolutely a proof-of-concept, there may be security issues and nice error handling is definitely lacking, but I wanted to be able to use modules for config so I spent some time trying to figure out how to implement it and this is the result.

I'm leaving this as a draft to see if this direction seems worth pursuing; I am not a browser expert so I'm not sure if there might be security implications (scary!) of doing something like this or any other issues, before I spend more time cleaning up the implementation.

The basic idea how this works:

  • Register a glide:// protocol handler, which serves TSBlank'd files relative to the resolved top-level glide.ts config
  • In the config sandbox evaluation, run import("glide://config/glide.ts") instead of evaluating the file contents directly
  • Now the top-level config is evaluated in module mode, which allows for static import via relative paths and such

A nice side effect is that you can look at the "effective configuration" by loading up glide://config/glide.js (or .js) in the browser, and load errors in :repl can be clicked to lead directly to the source of the error (even for imported modules).

To build this, I am utilizing the existing gemini:// protocol handler since it already implemented a lot of boilerplate, but things seems to "basically work" using these changes. Things I'd want to do to properly upstream:

  • Separate glide:// protocol handler, obviously don't want to actually replace the gemini:// protocol with this
  • Better error handling / reporting; if the glide:// handler can't resolve a file or something that should be surfaced to the user instead of just serving an error message
  • Module resolution: it would be nice to import from './test' instead of requiring .js. Maybe it's possible to make the view-source:glide://config/... lead to the original TypeScript source, while glide://config/... contains JS?
  • Double-check that the sandbox changes I made are both necessary for this to work, and don't regress any other functionality. As far as I can tell the browser APIs and everything are still functional, but the sandbox is used in a handful of places so it's possible something breaks with this.
  • Put this functionality behind an about:config flag maybe? Similar to glide.gemini.enabled this seems like it could break people's config so it might be best to make it experimental

Example file setup to test it out:

$ cat engine/glide.ts engine/test.ts engine/test2.ts
───────────────────────────────────────────────────────────────────────────────────────────
File: engine/glide.ts
───────────────────────────────────────────────────────────────────────────────────────────
/// <reference path="./glide.d.ts" />

import { get_it, HI } from './test.js';
const foo: string = '123';
get_it();
console.log(HI, foo);
───────────────────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────────────────────────
File: engine/test.ts
───────────────────────────────────────────────────────────────────────────────────────────
import { print } from './test2.js';

export const HI: string = 'hello world'

export function get_it() {
  print(HI);
  return HI
}
───────────────────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────────────────────────
File: engine/test2.ts
───────────────────────────────────────────────────────────────────────────────────────────
export function print(...args: any[]) {
  console.log(...args)
}
───────────────────────────────────────────────────────────────────────────────────────────

The basic idea:
- Register a `glide://` protocol handler, which serves TSBlank'd files
  relative to the resolved `glide.ts`
- In the top-level config sandbox evaluation, run
  `import("glide://config/glide.ts")`
- Config and subsequent imports are run in module mode
// document: props.document, // hmm needed?
glide: props.glide,

sandboxName: props.name,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, this was supposed to go in the sandbox props, not proto.

Comment on lines +112 to +113
// console: props.console,
// document: props.document, // hmm needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are read-only props on window, so if they need to be overridden then some alternative (proxying or something) might be necessary.

The actual props.window has to be used for the sandbox prototype in order to make a script loader available; creating a new proto object here (prior to my changes) fails to import anything with something like

JavaScript error: glide://config/glide.ts, line 2: Error: No ScriptLoader found for the current context

This is the biggest security concern I have with these changes; The sandbox implementation does a lot to make sure these imports are safe but I don't know enough about it to tell whether using the window directly like this is actually likely to be dangerous in any way.

// For nice module resolution we could probably check existence of files, like
// fname + `.js`, etc.
const relpath = uri.pathname.replace(/([.]m?)js$/, '$1ts');
const filepath = PathUtils.joinRelative('/Users/ianchamberlain/Documents/Development/glide/engine', relpath.replace(/^\//, ''));

@ian-h-chamberlain ian-h-chamberlain May 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, forgot I had hardcoded this path for prototyping; in principle I think it should be straightforward to just share logic here to get the same result as load_config_at_path would

"scheme": "glide",
"flags": [
"URI_STD",
"URI_LOADABLE_BY_ANYONE",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these flags probably should be adjusted, e.g. maybe URI_IS_LOCAL_FILE, URI_IS_UI_RESOURCE, URI_IS_LOCAL_RESOURCE? Not sure exactly what the right combination would be but this is probably important to get right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant