Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
45 changes: 45 additions & 0 deletions .github/workflows/test-ojs-runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Rebuilds the OJS runtime bundles from packages/ojs and fails if they differ
# from the bundles committed in src/resources/formats/html/ojs.
name: OJS Runtime Bundle
on:
workflow_dispatch:
pull_request:
branches: [main]
paths:
- "packages/ojs/**"
- "src/resources/formats/html/ojs/quarto-ojs-runtime*.js"
- ".github/workflows/test-ojs-runtime.yml"
push:
# only trigger on branches, not on tags
branches: [main]
paths:
- "packages/ojs/**"
- "src/resources/formats/html/ojs/quarto-ojs-runtime*.js"
- ".github/workflows/test-ojs-runtime.yml"

permissions:
contents: read

jobs:
check-bundle:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v6
Comment thread
cderv marked this conversation as resolved.

- uses: actions/setup-node@v6
with:
node-version: 24

- name: Build OJS runtime
working-directory: packages/ojs
run: |
npm ci
npm run build

- name: Check committed bundles match the sources
run: |
if ! git diff --exit-code --stat -- src/resources/formats/html/ojs; then
echo "::error::The committed OJS runtime bundles don't match packages/ojs. Run 'npm ci && npm run build' in packages/ojs and commit the result."
exit 1
fi
4 changes: 4 additions & 0 deletions news/changelog-1.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ All changes included in 1.11:
- ([#14946](https://github.com/quarto-dev/quarto-cli/issues/14946)): Fix `revealjs` and `dashboard` logos resolving to broken paths in documents in a project subdirectory when the project has a brand (regression introduced in v1.8.22). Logo paths are now always relative to the document; a project-relative logo path in a website with a brand still works but prints a deprecation warning.
- ([#14993](https://github.com/quarto-dev/quarto-cli/issues/14993)): Fix Typst output dropping a brand font from a `font-family` list when another document in the same directory or project was rendered first with different brand fonts.

## Dependencies

- ([#15043](https://github.com/quarto-dev/quarto-cli/issues/15043)): Update the OJS runtime to acorn 8.17, Observable Inspector 5.0.1, and Observable Inputs 0.10.6. `FileAttachment(...).mimeType` now reports `text/javascript` instead of `application/javascript` for `.js` and `.mjs` files.

## Accessibility

- ([#730](https://github.com/quarto-dev/quarto-cli/issues/730)): Fix keyboard navigation of tabsets in HTML documents without Bootstrap (`theme: none`): Tab now moves from the selected tab into its panel instead of selecting the next tab, and the arrow keys switch tabs.
Expand Down
1 change: 1 addition & 0 deletions packages/ojs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
69 changes: 69 additions & 0 deletions packages/ojs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# OJS runtime

Sources for `quarto-ojs-runtime.js` and `quarto-ojs-runtime.min.js`, the bundles that
Quarto serves for `{ojs}` cells. The built bundles are committed in
`src/resources/formats/html/ojs/`.

- `quarto-ojs-runtime/`: Quarto's runtime: code transforms, the Observable connector,
the inspector, and Shiny integration.
- `external-*/`: Quarto forks of Observable's
[runtime](https://github.com/observablehq/runtime),
[stdlib](https://github.com/observablehq/stdlib) and
[parser](https://github.com/observablehq/parser), of
[d3-require](https://github.com/d3/d3-require), and of
[unofficial-observablehq-compiler](https://github.com/asg017/unofficial-observablehq-compiler).

All of these packages are under the ISC license, as given in each `package.json`, except
two files vendored in `quarto-ojs-runtime/src/`: `escodegen.js` is a fork of
[escodegen](https://github.com/estools/escodegen) (BSD-2-Clause) and `escape.js` comes from
[escape-html](https://github.com/component/escape-html) (MIT). Both keep their license
headers, which are preserved in `quarto-ojs-runtime.js`.

These moved from `packages/ojs/` in [quarto-dev/quarto](https://github.com/quarto-dev/quarto)
([#15043](https://github.com/quarto-dev/quarto-cli/issues/15043)), where the forks were
`git subtree`s. They were copied from
[quarto-dev/quarto@42177e13](https://github.com/quarto-dev/quarto/tree/42177e13074cf905619bbb52b1dba313fb5b4bb1/packages/ojs),
the head of [quarto-dev/quarto#1180](https://github.com/quarto-dev/quarto/pull/1180) before
it deleted them. That commit is not on quarto-dev/quarto's `main` branch.

## Building

With Node.js 24 or later:

```bash
cd packages/ojs
npm ci
npm run build
```

This writes both bundles directly into `src/resources/formats/html/ojs/`. The build is
reproducible: CI rebuilds the bundles and fails if they differ from the committed
ones, so commit the rebuilt bundles together with any change here.

The npm workspaces link the forks to each other and to the runtime. Rollup bundles
everything from source. The compiler fork's `module` field points to its ES module
source, so its `dist/` build isn't needed.

## Maintaining the forks

The forks are plain copies here, so `git subtree pull` doesn't work. To take an
upstream change, apply it by hand and rebuild. Upstream development of these
packages has largely stopped.

Changes from the copies in quarto-dev/quarto:

- The forks' `devDependencies` are removed. They were for each fork's own build,
tests and linting, which don't run here, and their old rollup plugins conflict
with npm's peer dependency checks. The forks' `scripts` won't work without them.
The forks' `yarn.lock` files are removed too; `package-lock.json` here covers
every package.
- The forks' `.github/` and `.circleci/` directories are removed. GitHub only runs
workflows from the repository root, and the forks' scripts can't run here anyway.
- The compiler fork's `package.json` gains `"module": "src/index.js"`.

## Notes

- Keep `@observablehq/inputs` on 0.10. Version 0.11 renames the form class
(`__ns__` to `inputs-3a86ea`), which breaks Shiny's button styling.
- `src/resources/formats/html/ojs/quarto-ojs.css` is maintained directly in that
directory. quarto-dev/quarto had an older copy of it, which wasn't brought over.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

dist/
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/
node_modules/
yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# unofficial-observablehq-compiler

An unoffical compiler _and_ interpreter for Observable notebooks (the glue between the Observable parser and runtime)

This library has two parts: The Interpreter and the Compiler. The Interpreter will interpret "Observable syntax" into "javascript syntax" live in a javascript environment. For example:

```javascript
import { Intepreter } from "@alex.garcia/unofficial-observablehq-compiler";
import { Inspector, Runtime } from "@observablehq/runtime";

async function main() {
const runtime = new Runtime();
const main = runtime.module();
const observer = Inspector.into(document.body);
const interpret = new Intepreter({ module: main, observer });

await interpret.module(`
import {text} from '@jashkenas/inputs'

viewof name = text({
title: "what's your name?",
value: ''
})

md\`Hello **\${name}**, it's nice to meet you!\`

`);
}
main();
```

The Compiler will compile "Observable syntax" into javascript source code (as an ES module).

For more live examples and functionality, take a look at the [announcement notebook](https://observablehq.com/d/74f872c4fde62e35)
and this [test page](https://github.com/asg017/unofficial-observablehq-compiler/blob/master/test/test.html).

## API Reference

### new Interpreter(_params_)

`Interpreter` is a class that encompasses all logic to interpret Observable js code. _params_ is an optional object with the following allowed configuration:

`resolveImportPath(path, specifers)`: An async function that resolves to the `define` definition for a notebook. _path_ is the string provided in the import statement, and _specifiers_ is a array of string if the imported cell name specifiers in the statement (cells inside `import {...}`). _specifiers_ is useful when implementing tree-shaking. For example, `import {chart as CHART} from "@d3/bar-chart"` would supply `path="@d3/bar-chart"` and `specifiers=["chart"]`. Default imports from observablehq.com, eg `https://api.observablehq.com/@d3/bar-chart.js?v=3`
`resolveFileAttachments`: A function that, given the name of a FileAttachment, returns the URL that the FileAttachment will be fetched from. Defaults to `name => name`.
`defineImportMarkdown`: A boolean, whether to define a markdown description cell for imports in the notebook. Defaults true.
`observeViewofValues`: A boolean, whether to pass in an _observer_ for the value of viewof cells.
`module`: A default Observable runtime [module](https://github.com/observablehq/runtime#modules) that will be used in operations such as `.cell` and `.module`, if no other module is passed in.
`observer`: A default Observable runtime [observer](https://github.com/observablehq/runtime#observer) that will be used in operations such as `.cell` and `.module`, if no other observer is passed in.

Keep in mind, there is no sandboxing done, so it has the same security implications as `eval()`.

interpret.**cell**(_source_ [, *module*, *observer*])

Parses the given string _source_ with the Observable parser [`.parseCell()`](https://github.com/observablehq/parser#parseCell) and interprets the source, passing it and the _observer_ along to the Observable _module_. Returns a Promise that resolves to an array of runtime [variables](https://github.com/observablehq/runtime#variables) that were defined when interpreting the source. More than one variable can be defined with a single cell, like with `viewof`, `mutable`, and `import` cells. _source_ can also be a pre-parsed [cell](https://github.com/observablehq/parser#cell) instead of source code.

interpret.**module**(_source_ [, *module*, *observer*])

Parses the given string _source_ with the Observable parser [`.parseModule()`](https://github.com/observablehq/parser#parseModule) and interprets the source, passing it and the _observer_ along to the Observable _module_. Returns a Promise that resolves to an array of an array of runtime [variables](https://github.com/observablehq/runtime#variables) that were defined when interpreting the source. _source_ can also be a pre-parsed [program](https://github.com/observablehq/parser#program) instead of source code.

interpret.**notebook**(_source_ [, *module*, *observer*])

TODO

new **Compiler**(_params_)

`Compiler` is a class that encompasses all logic to compile Observable javascript code into vanilla Javascript code, as an ES module. _params_ is an optional object with the following allowed configuration.

`resolveImportPath(path, specifers)`: A function that returns a URL to where the notebook is defined. _path_ is the string provided in the import statement, and _specifiers_ is a array of string if the imported cell name specifiers in the statement (cells inside `import {...}`). _specifiers_ is useful when implementing tree-shaking. For example, `import {chart as CHART} from "@d3/bar-chart"` would supply `path="@d3/bar-chart"` and `specifiers=["chart"]`. Default imports from observablehq.com, eg `https://api.observablehq.com/@d3/bar-chart.js?v=3`

`resolveFileAttachments`: A function that, given the name of a FileAttachment, returns the URL that the FileAttachment will be fetched from. Defaults to `name => name`.

`UNSAFE_allowJavascriptFileAttachments` A boolean. When true, the `resolveFileAttachments` function will resolve to raw JavaScript when calculating the value of a FileAttachment reference. This is useful if you need to use `new URL` or `import.meta.url` when determining where a FileAttachment url should resolve too. This is unsafe because the Compiler will not escape any quotes when including it in the compiled output, so do use with extreme caution when dealing with user input.

```javascript

// This can be unsafe since FileAttachment names can include quotes.
// Instead, map file attachments names to something deterministic and escape-safe,
// like SHA hashes.
const resolveFileAttachments = name => `new URL("./files/${name}", import.meta.url)`

Compiled output when:

// UNSAFE_allowJavascriptFileAttachments == false
const fileAttachments = new Map([["a", "new URL(\"./files/a\", import.meta.url)"]]);

// UNSAFE_allowJavascriptFileAttachments == true
const fileAttachments = new Map([["a", new URL("./files/a", import.meta.url)]]);


```

`defineImportMarkdown` - A boolean, whether to define a markdown description cell for imports in the notebook. Defaults true.

`observeViewofValues` - A boolean, whether or not to pass in the `observer` function for viewof value cells. Defaults true.

`observeMutableValues` - A boolean, whether or not to pass in the `observer` function for mutable value cells. Defaults true.

compile.**module**(_source_)

TODO

compile.**notebook**(_source_)

TODO

## License

This library is MIT, but it relies and gets heavy inspiration from the following
libraries licensed under ISC:

- [@observablehq/runtime](https://github.com/observablehq/runtime)
- [@observablehq/stdlib](https://github.com/observablehq/stdlib)
- [@observablehq/inspector](https://github.com/observablehq/inspector)
- [@observablehq/parser](https://github.com/observablehq/parser)

## Contributing

Feel free to send in PR's as you wish! Take a look at the [issues](https://github.com/asg017/unofficial-observablehq-compiler/issues)
to find something to work on. Just please follow the [Contributor Covenant](https://www.contributor-covenant.org/)
in all your interactions :smile:
Loading
Loading