Skip to content

Commit 8ee034c

Browse files
Move the OJS runtime and its Observable forks from quarto-dev/quarto (#15046)
The OJS runtime and its five vendored Observable forks now live in packages/ojs, next to packages/quarto-types, instead of in quarto-dev/quarto. An npm workspace there builds the runtime bundles into src/resources/formats/html/ojs, and a CI check fails when the committed bundles drift from a fresh build. The bundles now include everything since the last rebuild in 2024-02, including acorn 8.17, inspector 5.0.1, Inputs 0.10.6 and mime 4. - Copy packages/ojs from quarto-dev/quarto, without its quarto.css asset (this repo's quarto-ojs.css is current) - Build with an npm workspace; drop the forks' devDependencies and yarn.lock files - Bundle the compiler fork from its ES module source, so no UMD build runs first - Drop the build year from the banner so rebuilds are reproducible - Bump quarto-ojs-runtime to 0.0.19 - Add a workflow that fails when the committed bundles are stale quarto-dev/quarto#1180 removes the original copy once this lands. Fixes #15043
1 parent 746cb0d commit 8ee034c

508 files changed

Lines changed: 39362 additions & 9681 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Rebuilds the OJS runtime bundles from packages/ojs and fails if they differ
2+
# from the bundles committed in src/resources/formats/html/ojs.
3+
name: OJS Runtime Bundle
4+
on:
5+
workflow_dispatch:
6+
pull_request:
7+
branches: [main]
8+
paths:
9+
- "packages/ojs/**"
10+
- "src/resources/formats/html/ojs/quarto-ojs-runtime*.js"
11+
- ".github/workflows/test-ojs-runtime.yml"
12+
push:
13+
# only trigger on branches, not on tags
14+
branches: [main]
15+
paths:
16+
- "packages/ojs/**"
17+
- "src/resources/formats/html/ojs/quarto-ojs-runtime*.js"
18+
- ".github/workflows/test-ojs-runtime.yml"
19+
20+
permissions:
21+
contents: read
22+
23+
jobs:
24+
check-bundle:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout Repo
28+
uses: actions/checkout@v6
29+
30+
- uses: actions/setup-node@v6
31+
with:
32+
node-version: 24
33+
34+
- name: Build OJS runtime
35+
working-directory: packages/ojs
36+
run: |
37+
npm ci
38+
npm run build
39+
40+
- name: Check committed bundles match the sources
41+
run: |
42+
if ! git diff --exit-code --stat -- src/resources/formats/html/ojs; then
43+
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."
44+
exit 1
45+
fi

‎news/changelog-1.11.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ All changes included in 1.11:
99
- ([#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.
1010
- ([#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.
1111

12+
## Dependencies
13+
14+
- ([#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.
15+
1216
## Accessibility
1317

1418
- ([#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.

‎packages/ojs/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

‎packages/ojs/README.md‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# OJS runtime
2+
3+
Sources for `quarto-ojs-runtime.js` and `quarto-ojs-runtime.min.js`, the bundles that
4+
Quarto serves for `{ojs}` cells. The built bundles are committed in
5+
`src/resources/formats/html/ojs/`.
6+
7+
- `quarto-ojs-runtime/`: Quarto's runtime: code transforms, the Observable connector,
8+
the inspector, and Shiny integration.
9+
- `external-*/`: Quarto forks of Observable's
10+
[runtime](https://github.com/observablehq/runtime),
11+
[stdlib](https://github.com/observablehq/stdlib) and
12+
[parser](https://github.com/observablehq/parser), of
13+
[d3-require](https://github.com/d3/d3-require), and of
14+
[unofficial-observablehq-compiler](https://github.com/asg017/unofficial-observablehq-compiler).
15+
16+
All of these packages are under the ISC license, as given in each `package.json`, except
17+
two files vendored in `quarto-ojs-runtime/src/`: `escodegen.js` is a fork of
18+
[escodegen](https://github.com/estools/escodegen) (BSD-2-Clause) and `escape.js` comes from
19+
[escape-html](https://github.com/component/escape-html) (MIT). Both keep their license
20+
headers, which are preserved in `quarto-ojs-runtime.js`.
21+
22+
These moved from `packages/ojs/` in [quarto-dev/quarto](https://github.com/quarto-dev/quarto)
23+
([#15043](https://github.com/quarto-dev/quarto-cli/issues/15043)), where the forks were
24+
`git subtree`s. They were copied from
25+
[quarto-dev/quarto@42177e13](https://github.com/quarto-dev/quarto/tree/42177e13074cf905619bbb52b1dba313fb5b4bb1/packages/ojs),
26+
the head of [quarto-dev/quarto#1180](https://github.com/quarto-dev/quarto/pull/1180) before
27+
it deleted them. That commit is not on quarto-dev/quarto's `main` branch.
28+
29+
## Building
30+
31+
With Node.js 24 or later:
32+
33+
```bash
34+
cd packages/ojs
35+
npm ci
36+
npm run build
37+
```
38+
39+
This writes both bundles directly into `src/resources/formats/html/ojs/`. The build is
40+
reproducible: CI rebuilds the bundles and fails if they differ from the committed
41+
ones, so commit the rebuilt bundles together with any change here.
42+
43+
The npm workspaces link the forks to each other and to the runtime. Rollup bundles
44+
everything from source. The compiler fork's `module` field points to its ES module
45+
source, so its `dist/` build isn't needed.
46+
47+
## Maintaining the forks
48+
49+
The forks are plain copies here, so `git subtree pull` doesn't work. To take an
50+
upstream change, apply it by hand and rebuild. Upstream development of these
51+
packages has largely stopped.
52+
53+
Changes from the copies in quarto-dev/quarto:
54+
55+
- The forks' `devDependencies` are removed. They were for each fork's own build,
56+
tests and linting, which don't run here, and their old rollup plugins conflict
57+
with npm's peer dependency checks. The forks' `scripts` won't work without them.
58+
The forks' `yarn.lock` files are removed too; `package-lock.json` here covers
59+
every package.
60+
- The forks' `.github/` and `.circleci/` directories are removed. GitHub only runs
61+
workflows from the repository root, and the forks' scripts can't run here anyway.
62+
- The compiler fork's `package.json` gains `"module": "src/index.js"`.
63+
64+
## Notes
65+
66+
- Keep `@observablehq/inputs` on 0.10. Version 0.11 renames the form class
67+
(`__ns__` to `inputs-3a86ea`), which breaks Shiny's button styling.
68+
- `src/resources/formats/html/ojs/quarto-ojs.css` is maintained directly in that
69+
directory. quarto-dev/quarto had an older copy of it, which wasn't brought over.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# TypeScript v1 declaration files
45+
typings/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Optional REPL history
57+
.node_repl_history
58+
59+
# Output of 'npm pack'
60+
*.tgz
61+
62+
# Yarn Integrity file
63+
.yarn-integrity
64+
65+
# dotenv environment variables file
66+
.env
67+
.env.test
68+
69+
# parcel-bundler cache (https://parceljs.org/)
70+
.cache
71+
72+
# next.js build output
73+
.next
74+
75+
# nuxt.js build output
76+
.nuxt
77+
78+
# vuepress build output
79+
.vuepress/dist
80+
81+
# Serverless directories
82+
.serverless/
83+
84+
# FuseBox cache
85+
.fusebox/
86+
87+
# DynamoDB Local files
88+
.dynamodb/
89+
90+
dist/
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{
2+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
src/
2+
node_modules/
3+
yarn.lock
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# unofficial-observablehq-compiler
2+
3+
An unoffical compiler _and_ interpreter for Observable notebooks (the glue between the Observable parser and runtime)
4+
5+
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:
6+
7+
```javascript
8+
import { Intepreter } from "@alex.garcia/unofficial-observablehq-compiler";
9+
import { Inspector, Runtime } from "@observablehq/runtime";
10+
11+
async function main() {
12+
const runtime = new Runtime();
13+
const main = runtime.module();
14+
const observer = Inspector.into(document.body);
15+
const interpret = new Intepreter({ module: main, observer });
16+
17+
await interpret.module(`
18+
import {text} from '@jashkenas/inputs'
19+
20+
viewof name = text({
21+
title: "what's your name?",
22+
value: ''
23+
})
24+
25+
md\`Hello **\${name}**, it's nice to meet you!\`
26+
27+
`);
28+
}
29+
main();
30+
```
31+
32+
The Compiler will compile "Observable syntax" into javascript source code (as an ES module).
33+
34+
For more live examples and functionality, take a look at the [announcement notebook](https://observablehq.com/d/74f872c4fde62e35)
35+
and this [test page](https://github.com/asg017/unofficial-observablehq-compiler/blob/master/test/test.html).
36+
37+
## API Reference
38+
39+
### new Interpreter(_params_)
40+
41+
`Interpreter` is a class that encompasses all logic to interpret Observable js code. _params_ is an optional object with the following allowed configuration:
42+
43+
`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`
44+
`resolveFileAttachments`: A function that, given the name of a FileAttachment, returns the URL that the FileAttachment will be fetched from. Defaults to `name => name`.
45+
`defineImportMarkdown`: A boolean, whether to define a markdown description cell for imports in the notebook. Defaults true.
46+
`observeViewofValues`: A boolean, whether to pass in an _observer_ for the value of viewof cells.
47+
`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.
48+
`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.
49+
50+
Keep in mind, there is no sandboxing done, so it has the same security implications as `eval()`.
51+
52+
interpret.**cell**(_source_ [, *module*, *observer*])
53+
54+
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.
55+
56+
interpret.**module**(_source_ [, *module*, *observer*])
57+
58+
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.
59+
60+
interpret.**notebook**(_source_ [, *module*, *observer*])
61+
62+
TODO
63+
64+
new **Compiler**(_params_)
65+
66+
`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.
67+
68+
`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`
69+
70+
`resolveFileAttachments`: A function that, given the name of a FileAttachment, returns the URL that the FileAttachment will be fetched from. Defaults to `name => name`.
71+
72+
`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.
73+
74+
```javascript
75+
76+
// This can be unsafe since FileAttachment names can include quotes.
77+
// Instead, map file attachments names to something deterministic and escape-safe,
78+
// like SHA hashes.
79+
const resolveFileAttachments = name => `new URL("./files/${name}", import.meta.url)`
80+
81+
Compiled output when:
82+
83+
// UNSAFE_allowJavascriptFileAttachments == false
84+
const fileAttachments = new Map([["a", "new URL(\"./files/a\", import.meta.url)"]]);
85+
86+
// UNSAFE_allowJavascriptFileAttachments == true
87+
const fileAttachments = new Map([["a", new URL("./files/a", import.meta.url)]]);
88+
89+
90+
```
91+
92+
`defineImportMarkdown` - A boolean, whether to define a markdown description cell for imports in the notebook. Defaults true.
93+
94+
`observeViewofValues` - A boolean, whether or not to pass in the `observer` function for viewof value cells. Defaults true.
95+
96+
`observeMutableValues` - A boolean, whether or not to pass in the `observer` function for mutable value cells. Defaults true.
97+
98+
compile.**module**(_source_)
99+
100+
TODO
101+
102+
compile.**notebook**(_source_)
103+
104+
TODO
105+
106+
## License
107+
108+
This library is MIT, but it relies and gets heavy inspiration from the following
109+
libraries licensed under ISC:
110+
111+
- [@observablehq/runtime](https://github.com/observablehq/runtime)
112+
- [@observablehq/stdlib](https://github.com/observablehq/stdlib)
113+
- [@observablehq/inspector](https://github.com/observablehq/inspector)
114+
- [@observablehq/parser](https://github.com/observablehq/parser)
115+
116+
## Contributing
117+
118+
Feel free to send in PR's as you wish! Take a look at the [issues](https://github.com/asg017/unofficial-observablehq-compiler/issues)
119+
to find something to work on. Just please follow the [Contributor Covenant](https://www.contributor-covenant.org/)
120+
in all your interactions :smile:

0 commit comments

Comments
 (0)