Skip to content

Conversation

@danielbodart
Copy link

This is my first pass at adding support for custom code extensions, these will allow you to add support for your favourite language or tool in side the markdown.

Inside the observablehq.config.ts file you can add handlers like so:

export default {
  codeExtensions: {
    'uppercase' : (content: string, attributes: Record<string, string>) => {
      return  `"${content.trim().toUpperCase()}"`;
    }
  }
}

Then in a markdown file you can simply say:

``` uppercase
hello world
```

And it will result in

HELLO WORLD

The example is pretty stupid, but this could easily allow clojure compilation to javascript or SQL for BigQuery etc.

The result of the function call returns valid JavaScript, that will be parsed by the parseJavaScript. This change simply inserts itself in the getLiveSource. To be honest the logic could easily be pulled one level up to makeFenceRenderer however this allows one to clearly see how this supports overriding the built in handlers if desired.

A later refactor could be to convert the built in code handlers into a default CodeExtensions object. Again I was trying to keep the changes to an absolute minimum to increase chance of acceptance (but totally open to feedback).

Probably the most important thing in this PR is the name of the config option codeExtensions, other options I thought about might be code, codeBlock, fenceCodeBlocks, languageBlocks, languageHandlers, tag etc. Please give feedback and I will update all references to the same name.

I also tried to follow the same code style of each file, again to maximise chance of acceptance.

Last I added a very simple test for the above uppercase examples, and ran all the tests, linter and typechecker before commiting.

@danielbodart
Copy link
Author

I hope this is not bad etiquette, but would love to hear from @Fil or @mbostock on what I could do to make the PR better or if it's not the right direction (or will never be accepted). Happy to do whatever work needed.

@Fil
Copy link
Contributor

Fil commented Oct 27, 2025

Sorry I don't have much bandwidth for now.

The concept sounds interesting, and it would be nice to have richer motivating examples.

Note: I couldn't help but notice that the example is insecure as it passes " directly into the compiled JS expression. Maybe change it to something like this?

return JSON.stringify(content.trim().toUpperCase());

@danielbodart
Copy link
Author

danielbodart commented Nov 7, 2025

Well examples are so so many:

Any language that can be compiled to Javascript and supports the browser can now be supported directly in the markdown

  • Elm
  • Dart
  • Haxe
  • Scala
  • Kotlin
  • Coffescript
  • ClosureScript
  • AssemblyScript (probably anything that can target WASM)

Supporting server side rendered results, leverage HTTP caching

  • BigQuery -> converted in secure HTTP endpoints that deliver parquet files on demand while hiding the SQL (my use case), build time is fine if you don't have billions of variations or the need for real time data.
  • Every other database query language could be supported if they can output a DuckDB supported file type

Any unix command could have it's output embedded (this is like data loaders but inline instead of a separate file). Just like style sheets can be inline or separate URLs

It's also more general, the contract is code in, Javascript out.

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.

2 participants