├── docs ├── .gitignore ├── articles │ ├── index.qmd │ ├── default-prop-values │ │ └── index.qmd │ └── migrating-from-sverto-v1 │ │ └── index.qmd ├── README.md ├── package.json ├── copy-extension.sh ├── Circles.svelte ├── _quarto.yml ├── style.scss ├── news.qmd ├── examples │ ├── barchart │ │ ├── BarChart.svelte │ │ └── index.qmd │ └── time-series │ │ ├── TimeSeriesChart.svelte │ │ └── index.qmd ├── index.qmd └── package-lock.json ├── .quartoignore ├── _extensions └── quarto-svelte │ ├── _extension.yml │ ├── rollup.config.js │ ├── quarto-svelte-prerender.lua │ ├── quarto-svelte.lua │ ├── util.lua │ └── quarto-svelte-logo.svg ├── package.json ├── .gitignore ├── LICENSE ├── Circles.svelte ├── example.qmd ├── .github └── workflows │ ├── check.yml │ └── publish.yml ├── NEWS.md └── README.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | 3 | /node_modules/ 4 | /.quarto-svelte/ -------------------------------------------------------------------------------- /.quartoignore: -------------------------------------------------------------------------------- 1 | README.md 2 | NEWS.md 3 | LICENSE 4 | .luarc.json 5 | package-lock.json 6 | docs/ 7 | .quarto/ 8 | .github/ 9 | !.gitignore -------------------------------------------------------------------------------- /docs/articles/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Articles" 3 | description: In-depth use of quarto-svelte. 4 | listing: 5 | contents: 6 | - "*/index.qmd" 7 | --- -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # quarto-svelte documentation website 2 | 3 | Render (getting the latest copy of quarto-svelte) and preview using: 4 | 5 | ```bash 6 | ./copy-extension.sh && quarto render 7 | 8 | quarto preview 9 | ``` 10 | -------------------------------------------------------------------------------- /_extensions/quarto-svelte/_extension.yml: -------------------------------------------------------------------------------- 1 | title: quarto-svelte 2 | author: James Goldie 3 | version: 2.0.1 4 | quarto-version: ">=1.5.46" 5 | contributes: 6 | filters: 7 | - quarto-svelte.lua 8 | metadata: 9 | project: 10 | pre-render: quarto-svelte-prerender.lua 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "2.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "build": "rollup -c", 8 | "dev": "rollup -w -c" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^28.0.0", 12 | "@rollup/plugin-terser": "^0.4.4", 13 | "@rollup/plugin-node-resolve": "^16.0.0", 14 | "rollup": "^4.22.0", 15 | "rollup-plugin-svelte": "^7.2.2", 16 | "svelte": "^5.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "2.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "build": "rollup -c", 8 | "dev": "rollup -w -c" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^28.0.0", 12 | "@rollup/plugin-node-resolve": "^16.0.0", 13 | "@rollup/plugin-terser": "^0.4.4", 14 | "rollup": "^4.22.0", 15 | "rollup-plugin-svelte": "^7.2.2", 16 | "svelte": "^5.0.0" 17 | }, 18 | "dependencies": { 19 | "d3": "^7.9.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/copy-extension.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # from github.com/qmd-lab/closeread 4 | # (but this can't be run as a project pre-render because it's a custom project 5 | # type... so the extension needs to already be present!) 6 | 7 | mkdir -p _extensions/ 8 | cp -Rf ../_extensions/quarto-svelte _extensions/ 9 | echo "> quarto-svelte extension retrieved" 10 | 11 | if [ ! -f ./package.json ]; then 12 | echo "> quarto-svelte package.json not found; also copying" 13 | cp ../package.json package.json 14 | fi 15 | 16 | npm install 17 | echo "> NPM packes installed" 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # quarto backend folder 2 | /.quarto/ 3 | 4 | # .quarto-svelte directory (except rollup.config.js) 5 | /.quarto-svelte/import 6 | /.quarto-svelte/.quarto-svelte-* 7 | !/.quarto-svelte/rollup.config.js 8 | 9 | # installed npm packages 10 | /node_modules/ 11 | 12 | # system files 13 | .DS_Store 14 | 15 | # lua 16 | /.luarc.json 17 | 18 | # rendered documentation site 19 | /docs/_site/ 20 | /docs/.quarto/ 21 | /docs/.quarto-svelte/ 22 | 23 | # quarto-svelte extension + files in docs site (we get it from project root on render) 24 | /docs/_extensions/quarto-svelte 25 | /docs/package.json 26 | /docs/package-lock.json 27 | /example_files/ 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 360info 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Circles.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 23 | {@debug data} 24 | 25 | 28 | 29 | {#each data as d, i (i)} 30 | 36 | {/each} 37 | 38 | -------------------------------------------------------------------------------- /docs/Circles.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 25 | 26 | 27 | {@debug data} 28 | 29 | 32 | 33 | {#each data as d, i (i)} 34 | 40 | {/each} 41 | 42 | -------------------------------------------------------------------------------- /example.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: quarto-svelte example 3 | filters: 4 | - quarto-svelte 5 | quarto-svelte: 6 | use: 7 | - Circles.svelte 8 | --- 9 | 10 | This document shows you how to use a basic example Svelte component called `Circles.svelte`. 11 | 12 | Create one using an HTML template: 13 | 14 | ```{ojs} 15 | myCircles = html`` 16 | ``` 17 | 18 | Now the fun part - updating our visual when our document changes. It's as simple as changing the prop using `componentName.propName =`. 19 | 20 | For example, here are three datasets and an [Observable Inputs](https://observablehq.com/documentation/inputs/overview) dropdown menu that lets you select one of three datasets: 21 | 22 | ```{ojs} 23 | // here are some datasets... 24 | allDatasets = new Map([ 25 | ["Dataset A", [5, 15, 25, 17, 8]], 26 | ["Dataset B", [25, 5, 5]], 27 | ["Dataset C", [12, 5, 8, 21, 5]] 28 | ]); 29 | ``` 30 | 31 | ```{ojs} 32 | viewof selectedDataset = 33 | Inputs.select(allDatasets, { label: "Selected dataset" }); 34 | ``` 35 | 36 | Now we can update the prop `data` of the visual `myCircles` using: 37 | 38 | ```{ojs} 39 | //| output: false 40 | myCircles.data = selectedDataset 41 | ``` 42 | 43 | And there you go! 🚀 44 | 45 | For more help writing Svelte components, check out the [Svelte tutorial](https://svelte.dev/tutorial/basics) or take look at some of the examples in the [quarto-svelte documentation](https://quarto-svelte.jamesgoldie.dev). 46 | 47 | -------------------------------------------------------------------------------- /docs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | execute: 5 | freeze: auto 6 | 7 | website: 8 | announcement: 9 | icon: stars 10 | dismissable: true 11 | type: warning 12 | position: above-navbar 13 | content: "Sverto is now quarto-svelte — and it supports Svelte 5! [**Learn how to migrate to the new version.**](/articles/migrating-from-sverto-v1){style=\"color: var(--bs-text-warning)\"}" 14 | navbar: 15 | background: "#ff3e00" 16 | foreground: "white" 17 | search: true 18 | left: 19 | - text: "quarto-svelte" 20 | file: index.qmd 21 | - text: Examples 22 | menu: 23 | - text: "Simple bar chart" 24 | file: examples/barchart/index.qmd 25 | - text: "Time series chart" 26 | file: examples/time-series/index.qmd 27 | - text: Articles 28 | menu: 29 | - text: Migrating from Sverto v1 and Svelte 3 30 | file: articles/migrating-from-sverto-v1 31 | - text: Default prop values 32 | file: articles/default-prop-values/index.qmd 33 | - text: News 34 | file: news.qmd 35 | right: 36 | - icon: github 37 | href: https://github.com/jimjam-slam/quarto-svelte 38 | page-footer: "The developers of Svelte are not affiliated with quarto-svelte." 39 | 40 | format: 41 | html: 42 | title-block-banner: "#222222" 43 | theme: [default, style.scss] 44 | linkcolor: "#ff3e00" 45 | callout-appearance: simple 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # workflow to test quarto-svelte 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main, dev] 7 | pull_request: 8 | branches: [main, dev] 9 | 10 | name: quarto-svelte-check 11 | 12 | jobs: 13 | quarto-svelte-check: 14 | 15 | strategy: 16 | matrix: 17 | os: ["ubuntu-latest", "windows-latest"] 18 | quarto: ["release", "pre-release"] 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | # check on both latest and prerelease 29 | - name: Install Quarto 30 | uses: quarto-dev/quarto-actions/setup@v2 31 | with: 32 | version: ${{ matrix.quarto }} 33 | 34 | - name: Install Node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | 39 | - name: Copy quarto-evelte extension; run npm install 40 | run: ./copy-extension.sh 41 | working-directory: ./docs 42 | shell: bash 43 | 44 | - name: Render quarto-svelte docs 45 | uses: quarto-dev/quarto-actions/render@v2 46 | with: 47 | path: docs 48 | 49 | - name: Verify that compiled Svelte .js bundles are present 50 | shell: bash 51 | run: | 52 | test -f docs/_site/Circles.js && 53 | test -f docs/_site/examples/barchart/BarChart.js && 54 | test -f docs/_site/examples/time-series/TimeSeriesChart.js 55 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # workflow to build docs site and publish to github pages 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: main 7 | 8 | name: quarto-svelte-publish 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | concurrency: 16 | group: "pages" 17 | cancel-in-progress: false 18 | 19 | jobs: 20 | build-docs: 21 | runs-on: "ubuntu-latest" 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | steps: 25 | 26 | - uses: actions/checkout@v4 27 | 28 | # use latest release version to publish docs 29 | - name: Install Quarto 30 | uses: quarto-dev/quarto-actions/setup@v2 31 | with: 32 | version: "release" 33 | 34 | - name: Install Node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | 39 | - name: Copy quarto-evelte extension; run npm install 40 | run: ./copy-extension.sh 41 | working-directory: ./docs 42 | shell: bash 43 | 44 | - name: Render quarto-svelte docs 45 | uses: quarto-dev/quarto-actions/render@v2 46 | with: 47 | path: docs 48 | 49 | - name: Verify that compiled Svelte .js bundles are present 50 | shell: bash 51 | run: | 52 | test -f docs/_site/Circles.js && 53 | test -f docs/_site/examples/barchart/BarChart.js && 54 | test -f docs/_site/examples/time-series/TimeSeriesChart.js 55 | 56 | - name: Setup Pages 57 | uses: actions/configure-pages@v5 58 | 59 | - name: Upload docs/ artifact 60 | uses: actions/upload-pages-artifact@v3 61 | with: 62 | path: "./docs/_site" 63 | 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | 68 | -------------------------------------------------------------------------------- /_extensions/quarto-svelte/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from "rollup-plugin-svelte" 2 | import commonjs from "@rollup/plugin-commonjs" 3 | import { nodeResolve } from "@rollup/plugin-node-resolve" 4 | import terser from "@rollup/plugin-terser" 5 | 6 | const path = require("node:path") 7 | 8 | // this is false when we run rollup with -w/--watch (never presently) 9 | const production = !process.env.ROLLUP_WATCH 10 | 11 | /* export an array of rollup configs - one for each input svelte file - using 12 | additional command line args supplied from lua */ 13 | export default cmd => { 14 | 15 | const svelteInputPaths = 16 | cmd["configSvelteInPaths"].split(":").filter(d => d != "") 17 | 18 | // if no svelte paths, bail out early 19 | if (svelteInputPaths == undefined || svelteInputPaths.length == 0) { 20 | console.log("No Svelte filtes found; skipping Svelte compilation") 21 | process.exit(0) 22 | } 23 | 24 | /* get quarto render dir from cmd line arg */ 25 | const quartoRenderPath = cmd["configQuartoOutPath"] 26 | if (quartoRenderPath == undefined) { 27 | console.error( 28 | "Error: supply a --configQuartoOutPath. Please report this to " + 29 | "the quarto-svelte developer.") 30 | process.exit(1) 31 | } 32 | 33 | // send a rollup config for each svelte file 34 | return svelteInputPaths.map( 35 | 36 | (svelteFile, index) => ({ 37 | input: svelteFile, 38 | output: { 39 | format: "es", 40 | dir: path.join(quartoRenderPath, path.dirname(svelteFile)), 41 | sourcemap: true 42 | }, 43 | plugins: [ 44 | svelte({ 45 | // css is added to the js bundle instead 46 | emitCss: false, 47 | compilerOptions: { 48 | // required for ojs reactivity 49 | accessors: true, 50 | customElement: true, 51 | dev: !production, 52 | } 53 | }), 54 | nodeResolve({ 55 | browser: true, 56 | dedupe: ["svelte"] 57 | }), 58 | commonjs(), 59 | production && terser() 60 | ] 61 | }) 62 | 63 | ) 64 | 65 | } 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/style.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | 3 | /*-- scss:rules --*/ 4 | 5 | .btn { 6 | box-shadow: 7 | 0 .2em .8em .2em rgba(0,0,0,.05), 8 | 0 .2em .6em .1em rgba(0,0,0,.025); 9 | } 10 | 11 | /* svelte code blocks (no syntax highlighting) */ 12 | 13 | .code-with-filename { 14 | margin: 1em 0; 15 | } 16 | 17 | .svelte.sourceCode { 18 | background-color: rgba(233,236,239,.65); 19 | border: 1px solid rgba(233,236,239,.65); 20 | border-radius: .25rem; 21 | color: #003b4f; 22 | } 23 | 24 | .code-with-filename .code-with-filename-file strong { 25 | color: var(--quarto-scss-export-text-muted, "slategrey"); 26 | } 27 | 28 | @include media-breakpoint-up(md) { 29 | body .quarto-title-block .quarto-title-banner { 30 | background: radial-gradient(circle at 20%, 31 | #222, 32 | #222 calc(520px), 33 | white calc(520px), 34 | white calc(520px + 5%), 35 | #ff3e00 calc(520px + 5%), 36 | #ff3e00 calc(520px + 10%), 37 | #75A9DB calc(520px + 10%), 38 | #75A9DB calc(520px + 15%), 39 | #222 calc(520px + 15%)); 40 | } 41 | } 42 | 43 | @include media-breakpoint-up(lg) { 44 | body .quarto-title-block .quarto-title-banner { 45 | background: radial-gradient(circle at 50%, 46 | #222, 47 | #222 calc(420px), 48 | white calc(420px), 49 | white calc(420px + 30px), 50 | #ff3e00 calc(420px + 30px), 51 | #ff3e00 calc(420px + 60px), 52 | #75A9DB calc(420px + 60px), 53 | #75A9DB calc(420px + 90px), 54 | #222 calc(420px + 90px)); 55 | } 56 | } 57 | @include media-breakpoint-up(lg) { 58 | body:has(main.page-full) .quarto-title-block .quarto-title-banner { 59 | background: radial-gradient(circle at 42.5%, 60 | #222, 61 | #222 calc(420px), 62 | white calc(420px), 63 | white calc(420px + 30px), 64 | #ff3e00 calc(420px + 30px), 65 | #ff3e00 calc(420px + 60px), 66 | #75A9DB calc(420px + 60px), 67 | #75A9DB calc(420px + 90px), 68 | #222 calc(420px + 90px)); 69 | } 70 | } -------------------------------------------------------------------------------- /_extensions/quarto-svelte/quarto-svelte-prerender.lua: -------------------------------------------------------------------------------- 1 | -- quarto-svelte-prerender.lua 2 | -- james goldie 3 | -- this pre-render script runs when quarto-svelte is installed on a quarto project. it: 4 | -- 1) looks in the doc meta for svelte paths 5 | -- 2) calls the svelte compiler with the svelte paths identified in (1) 6 | -- (injecting the bundled svelte files into the html is handled by quarto-svelte.lua) 7 | 8 | print(">>> quarto-svelte: project pre-render") 9 | 10 | -- try to load a module from multiple paths 11 | -- modified version of https://stackoverflow.com/a/17878208/3246758 12 | local function try_require(path_table) 13 | for _, v in pairs(path_table) do 14 | ok, err = pcall(require, v) 15 | if ok then 16 | return err 17 | end 18 | end 19 | error("Could not find a module to load from selected paths") 20 | end 21 | 22 | util = try_require({ 23 | "_extensions.jimjam-slam.quarto-svelte.util", 24 | "_extensions.quarto-svelte.util", 25 | }) 26 | 27 | input_paths = os.getenv("QUARTO_PROJECT_INPUT_FILES") 28 | 29 | -- walk input files, processing the meta to get the svelte files 30 | -- (make relative paths relative to the input file!) 31 | 32 | svelte_paths = {} 33 | 34 | -- for _, input_path in ipairs(input_paths) do 35 | for input_path in input_paths:gmatch("[^\n]+") do 36 | 37 | local doc = pandoc.read(util.read_file(input_path), "markdown") 38 | 39 | doc:walk { 40 | Meta = function(m) 41 | 42 | -- confirm quarto-svelte.use is a string or list 43 | if m["quarto-svelte"] == nil or m["quarto-svelte"].use == nil then 44 | return nil 45 | end 46 | 47 | local quarto_svelte_use = util.get_svelte_paths_from_meta(m) 48 | -- add each unique path, resolving relative project location 49 | for _, svelte_path in ipairs(quarto_svelte_use) do 50 | local offset_path = util.offset_svelte_path( 51 | pandoc.utils.stringify(svelte_path), 52 | input_path) 53 | svelte_paths[offset_path] = offset_path 54 | end 55 | 56 | end 57 | } 58 | 59 | end 60 | 61 | -- now concat .svelte paths 62 | local svelte_path_string = "" 63 | for _, svelte_path in pairs(svelte_paths) do 64 | svelte_path_string = svelte_path_string .. ":" .. svelte_path 65 | end 66 | 67 | util.compile_svelte_files( 68 | os.getenv("QUARTO_PROJECT_OUTPUT_DIR"), 69 | svelte_path_string) 70 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ## quarto-svelte 2.0.1 2 | 3 | - Improvement: extraneous OJS import code is removed from bootstrapping script 4 | - Fix errors in GitHub Actions workflows that prevented examples in the documentation site from appearing. GHA actions now run on latest Quarto release (check workflow also runs on latest prerelease). 5 | 6 | ## quarto-svelte 2.0.0 7 | 8 | - Rename Sverto to quarto-svelte 9 | - **Breaking:** `filters: ["sverto"]` is now `filters: ["quarto-svelte"]` 10 | - **Breaking:** `sverto.use` is now `quarto-svelte.use` 11 | - Repo name is now `jimjam-slam/quarto-svelte`, but `jimjam-slam/sverto` will continue to work 12 | - Upgrade from Svelte 3 to Svelte 5 13 | - For notes on rewriting existing Svelte components for Svelte 5, see the [Svelte 5 Migration Guide](https://svelte.dev/docs/svelte/v5-migration-guide) 14 | - Now uses web components 15 | - **Breaking:** in addition to general Svelte 5 migration, components for quarto-svelte need to have a [custom element option](https://svelte.dev/docs/svelte/custom-elements) 16 | - **Breaking:** old instantiation syntax no longer works in Svelte 5. Instead, use an HTML template or JavaScript to create an element with the custom element name 17 | 18 | ## Sverto 1.0.0 19 | 20 | - Significant refactor of Sverto makes it easier to use and more compatible with Quarto's other features 21 | - Use Sverto in a Quarto document by adding `sverto` to `filters` in the document frontmatter 22 | - Add Svelte files to a document using the frontmatter key `sverto.use` 23 | - No need for magic blocks anymore! 24 | - When working in a website project, optionally use the `sverto` project type to cut down on duplicate Svelte compilation Quarto documents 25 | - Works properly with Quarto includes 26 | - Requires Quarto pre-release 1.5.25 or higher on Windows, but should work fine on Quarto 1.4 on macOS and Linux. 27 | 28 | ## Sverto 0.0.3 29 | 30 | - Migrated from [`360-info/sverto`](https://github.comn/360-info/sverto) to [`jimjam-slam/sverto`](htps://github.com/jimjam-slam/sverto). Old GitHub links are maintained. 31 | 32 | ## Sverto 0.0.2 33 | 34 | - Bump minimum Quarto version to 1.3.0. 35 | - Fixes for compatibility with newer Quarto 1.3 pre-releases 36 | - Quarto's switch from Pandoc 2 to Pandoc 3 caused some issues with the way Sverto identifies Svelte import statements. This should no longer be a problem. 37 | - We now take advantage of the improved `.quartoignore` functionality in Quarto 1.3 to: 38 | 1. avoid copying the `docs` folder in with the project template; and 39 | 2. include the `.gitignore` with the template 40 | 41 | ## Sverto 0.0.1 42 | 43 | - Initial release 44 | -------------------------------------------------------------------------------- /docs/news.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: News 3 | date: last-modified 4 | --- 5 | 6 | ## quarto-svelte 2.0.1 7 | 8 | - Improvement: extraneous OJS import code is removed from bootstrapping script 9 | - Fix errors in GitHub Actions workflows that prevented examples in the documentation site from appearing. GHA actions now run on latest Quarto release (check workflow also runs on latest prerelease). 10 | 11 | ## quarto-svelte 2.0.0 12 | 13 | - Rename Sverto to quarto-svelte 14 | - **Breaking:** `filters: ["sverto"]` is now `filters: ["quarto-svelte"]` 15 | - **Breaking:** `sverto.use` is now `quarto-svelte.use` 16 | - Repo name is now `jimjam-slam/quarto-svelte`, but `jimjam-slam/sverto` will continue to work 17 | - Upgrade from Svelte 3 to Svelte 5 18 | - For notes on rewriting existing Svelte components for Svelte 5, see the [Svelte 5 Migration Guide](https://svelte.dev/docs/svelte/v5-migration-guide) 19 | - Now uses web components 20 | - **Breaking:** in addition to general Svelte 5 migration, components for quarto-svelte need to have a [custom element option](https://svelte.dev/docs/svelte/custom-elements) 21 | - **Breaking:** old instantiation syntax no longer works in Svelte 5. Instead, use an HTML template or JavaScript to create an element with the custom element name 22 | 23 | ## Sverto 1.0.0 24 | 25 | - Big refactor of sverto makes it easier to use and more compatible with Quarto's other features! 26 | - Use Sverto in a Quarto document by adding `sverto` to `filters` in the document frontmatter 27 | - Add Svelte files to a document using the frontmatter key `sverto.use` 28 | - No need for magic blocks anymore! 29 | - When working in a website project, optionally use the `sverto` project type to cut down on duplicate Svelte compilation Quarto documents 30 | - Works properly with Quarto includes 31 | - **Requires Quarto pre-release 1.5.25 or higher on Windows,** but should work fine on Quarto 1.4 on macOS and Linux. 32 | 33 | ## Sverto 0.0.3 34 | 35 | - Migrated from [`360-info/sverto`](https://github.comn/360-info/sverto) to [`jimjam-slam/sverto`](htps://github.com/jimjam-slam/sverto). Old GitHub links are maintained. 36 | 37 | ## Sverto 0.0.2 38 | 39 | - Bump minimum Quarto version to 1.3.0. 40 | - Fixes for compatibility with newer Quarto 1.3 pre-releases 41 | - Quarto's switch from Pandoc 2 to Pandoc 3 caused some issues with the way Sverto identifies Svelte import statements. This should no longer be a problem. 42 | - We now take advantage of the improved `.quartoignore` functionality in Quarto 1.3 to: 43 | 1. avoid copying the `docs` folder in with the project template; and 44 | 2. include the `.gitignore` with the template 45 | 46 | ## 0.0.1 47 | 48 | - Initial release 49 | -------------------------------------------------------------------------------- /docs/examples/barchart/BarChart.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 36 | 37 | 38 | 43 | 44 | 50 | 51 | 52 | 53 | {#each normalisedData as d, i (i)} 54 | 63 | 64 | 66 | {d.y} 76 | {/each} 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/examples/time-series/TimeSeriesChart.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 67 | 68 | 83 | 84 |
85 | 86 | 87 | 88 | {#each data as { year, value } (year) } 89 | 90 | 98 | 99 | {/each} 100 | 101 | 102 | 103 | 104 | 107 | 110 | 111 |
112 | 113 | -------------------------------------------------------------------------------- /_extensions/quarto-svelte/quarto-svelte.lua: -------------------------------------------------------------------------------- 1 | -- quarto-svelte.lua 2 | -- james goldie 3 | -- this filter runs on individual documents when it's specified. it handles 2–3 4 | -- things: 5 | -- 1) looks in the doc meta for svelte paths 6 | -- 2) adds a vanilla js block to the doc header to initialise the compiled svelte 7 | -- 3) IF the filter is NOT running in a project context (ie. a single doc), 8 | -- call the svelte compiler with the svelte paths identified in (1) 9 | -- (if it is a project, this step is handled by quarto-svelte-prerender.lua across 10 | -- the whole project) 11 | 12 | quarto.log.info(">>> quarto-svelte: document filter") 13 | 14 | util = require("./util") 15 | 16 | -- inject_svelte: a pandoc filter that extracts svelte files named in the doc 17 | -- frontmatter and adds javascript blocks to import them as es modules 18 | function inject_svelte_and_compile(m) 19 | 20 | if not quarto.doc.isFormat("html") then 21 | quarto.log.warning("quarto-svelte shortcode ignored for non-HTML output") 22 | return nil 23 | end 24 | 25 | -- no files to process? abort 26 | if m["quarto-svelte"] == nil or m["quarto-svelte"].use == nil then 27 | quarto.log.warning( 28 | "No Svelte files found under `quarto-svelte.use`. " .. 29 | "To use quarto-svelte with this document, add a list of .svelte files " .."to the document frontmatter under the `quarto-svelte.use` key.\n\n" .. 30 | "If you did not intend to use quarto-svelte on this document, " .. 31 | "consider removing the quarto-svelte filter (you can still use it on " .. 32 | "other pages of a Quarto website).") 33 | return nil 34 | end 35 | 36 | -- either add text to start of body (and return nil), or return a rawblock 37 | -- %s: compiled svelte js path 38 | -- %s: obj_name 39 | local svelte_js_import_template = '' 40 | 41 | -- abort if quarto-svelte.use is not a string or a list of MetaInlines 42 | local quarto_svelte_use = util.get_svelte_paths_from_meta(m) 43 | 44 | -- now inject the ojs init code for the user's svelte bundles while 45 | -- building a list of .svelte files to potentially compile 46 | local svelte_paths_string = "" 47 | for _, path in ipairs(quarto_svelte_use) do 48 | -- this is where we process the other .svelte paths 49 | local in_path = pandoc.utils.stringify(path) 50 | local in_dir = pandoc.path.directory(in_path) 51 | local in_name = pandoc.path.filename(in_path) 52 | local obj_name = pandoc.path.split_extension(in_name) 53 | -- bundle path should use *nix separators for web, not platform default 54 | local compiled_path = pandoc.path.join({ in_dir, obj_name .. ".js" }) 55 | local web_path = compiled_path:gsub("\\", "/") 56 | 57 | -- add .svelte path to svelte compiler path list... 58 | svelte_paths_string = svelte_paths_string .. in_path .. ":" 59 | 60 | -- ... and inject the ojs init code for it 61 | local svelte_insert = string.format(svelte_js_import_template, 62 | web_path) 63 | quarto.doc.include_text("before-body", svelte_insert) 64 | 65 | -- finally, if we're rendering a single doc (not in a project), 66 | -- compile the svelte file to a js bundle 67 | if quarto.project.directory ~= nil then 68 | quarto.log.debug("Project found; deferring Svelte compilation to pre-render script") 69 | else 70 | util.compile_svelte_files("./", svelte_paths_string) 71 | end 72 | end 73 | end 74 | 75 | return { 76 | Meta = inject_svelte_and_compile 77 | } 78 | -------------------------------------------------------------------------------- /_extensions/quarto-svelte/util.lua: -------------------------------------------------------------------------------- 1 | 2 | -- return contents of named file 3 | function read_file(name) 4 | local file = io.open(name, "r") 5 | if file == nil then 6 | return "" 7 | end 8 | local contents = file:read("a") 9 | file:close() 10 | return contents 11 | end 12 | 13 | -- get a prefix for calling npm run based on windows or *nix/macos 14 | -- using path separator: i don't know pandoc.system.os() values 15 | function get_cmd_prefix() 16 | if pandoc.path.separator == "\\" then 17 | return "cmd /c " 18 | else 19 | return "" 20 | end 21 | end 22 | 23 | -- file_exists: true if the file at `name` exists 24 | -- from https://pandoc.org/lua-filters.html#building-images-with-tikz 25 | function file_exists(name) 26 | local f = io.open(name, 'r') 27 | if f ~= nil then 28 | io.close(f) 29 | return true 30 | else 31 | return false 32 | end 33 | end 34 | 35 | -- offset a relative `svelte_path` to a .qmd `input_path`, or an absolute 36 | -- `svelte_path` to the project path. then normalize. 37 | function offset_svelte_path(svelte_path, input_path) 38 | 39 | -- offset from either input .qmd (if relative) or project dir (if absolute) 40 | -- local offset_from = pandoc.system.get_working_directory() 41 | local offset_from = "./" 42 | if pandoc.path.is_relative(svelte_path) then 43 | -- offset_from = pandoc.path.directory(input_path) 44 | offset_from = pandoc.path.join({ 45 | offset_from, 46 | pandoc.path.directory(input_path) 47 | }) 48 | end 49 | 50 | -- join offset and svelt paths 51 | local relative_path = pandoc.path.join({ 52 | offset_from, 53 | pandoc.utils.stringify(svelte_path) 54 | }) 55 | 56 | -- normalize and return 57 | local final_path = pandoc.path.normalize(relative_path) 58 | return final_path 59 | end 60 | 61 | function get_svelte_paths_from_meta(m) 62 | local quarto_svelte_use 63 | if pandoc.utils.type(m["quarto-svelte"].use) == "List" then 64 | quarto_svelte_use = m["quarto-svelte"].use 65 | elseif type(m["quarto-svelte"].use) == "string" then 66 | quarto_svelte_use = { m["quarto-svelte"].use } 67 | else 68 | print( 69 | "[\"quarto-svelte\"].use should be Inlines, not " .. 70 | pandoc.utils.type(m["quarto-svelte"].use)) 71 | end 72 | return quarto_svelte_use 73 | end 74 | 75 | function compile_svelte_files(quarto_out_path, svelte_paths_string) 76 | -- first, find the rollup config 77 | local rollup_config 78 | if file_exists("./_extensions/jimjam-slam/quarto-svelte/rollup.config.js") then 79 | rollup_config = "./_extensions/jimjam-slam/quarto-svelte/rollup.config.js" 80 | elseif file_exists("./_extensions/quarto-svelte/rollup.config.js") then 81 | rollup_config = "./_extensions/quarto-svelte/rollup.config.js" 82 | else 83 | print("Error: quarto-svelte extension files not found. " .. 84 | "Is quarto-svelte installed properly?") 85 | os.exit(1) 86 | end 87 | 88 | local svelte_command = 89 | "npm run build " .. rollup_config .. " -- " .. 90 | '--configQuartoOutPath="' .. quarto_out_path .. '" ' .. 91 | '--configSvelteInPaths="' .. svelte_paths_string .. '" ' .. 92 | '--bundleConfigAsCjs' 93 | 94 | local rollup_result = os.execute(svelte_command) 95 | if rollup_result == nil or rollup_result == true then 96 | print(">>> quarto-svelte: Svelte compiler finished!") 97 | else 98 | print(">>> quarto-svelte: Svelte compiler exited with code " .. tostring(rollup_result)) 99 | end 100 | end 101 | 102 | return { 103 | read_file = read_file, 104 | get_cmd_prefix = get_cmd_prefix, 105 | file_exists = file_exists, 106 | offset_svelte_path = offset_svelte_path, 107 | get_svelte_paths_from_meta = get_svelte_paths_from_meta, 108 | compile_svelte_files = compile_svelte_files } -------------------------------------------------------------------------------- /docs/articles/default-prop-values/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Default prop values 3 | description: More advanced ways to instantiate your Svelte components and pass props to them. 4 | --- 5 | 6 | When you place your Svelte component on the page, it will be instantiated (created) with initial values for its props — either the default prop values written into the component, or initial values that you give it. 7 | 8 | For example, the `Circles.svelte` component on this site's home page has a prop, `data`: 9 | 10 | ```{.svelte .sourceCode filename="Circles.svelte"} 11 | let { data = [5, 15, 10, 12, 14] } = $props(); 12 | ``` 13 | 14 | If you create `` without supplying `data`, that prop will first be `[5, 15, 10, 12, 14]`: 15 | 16 | ```{ojs} 17 | //| eval: false 18 | myCircles = html` 19 | 20 | ` 21 | ``` 22 | 23 | But you could also create it your own initial value for `data`: 24 | 25 | ```{ojs} 26 | //| eval: false 27 | myCircles = html` 28 | 29 | ` 30 | ``` 31 | 32 | If you are also supplying prop values to a Svelte component via OJS, the component will begin at the initial value above and then switch to the OJS-supplied value as soon as it becomes available. 33 | 34 | That might be nearly immediately, so you may not notice the differnce. But if your component is near the top of the page and transitions elements slowly, you may notice the change — a bar chart might immediate transition from one set of bar chart heights to another, for example. You may want to supply initial props that make sense for your component. 35 | 36 | # Passing complex data as default props 37 | 38 | If you're supplying props to your Svelte component when you place it, you may notice that they're in quotes. Like regular HTML attributes, those props are strings. 39 | 40 | If you want a Svelte component to convert those props from strings to something else, you can specify that at the top of the component using [component options](https://svelte.dev/docs/svelte/custom-elements#Component-options). 41 | 42 | For example, the component options for `Circles.svelte` looks like this: 43 | 44 | ```{.svelte .sourceCode filename="Circles.svelte"} 45 | 51 | ``` 52 | 53 | Instead of simply supplying a name, like `customElement="bar-chart"`, we provide an object with the configuration. The name of the element is the `tag` option, while prop configuration is under `props`. 54 | 55 | For each prop, you can provide a `type`—one of `'String'`, `'Boolean'`, `'Number'`, `'Array'` or `'Object'`. For our data prop, the type is set to `"Array"`, so the default value `"[1, 2, 3, 4, 5]"` is converted from a String to an array of numbers. 56 | 57 | ## Nested arrays and objects 58 | 59 | Nested arrays and objects are common in data science, where we might want to pass a data-frame like array through. 60 | 61 | For example, you might be tempted to try something like this: 62 | 63 | ```{ojs} 64 | //| eval: false 65 | myCircles = html` 66 | 68 | ` 69 | ``` 70 | 71 | This, unfortunately, won't work, even if you've set the prop type to `"Array"` or `"Object"`. 72 | 73 | But there is also an alternate way of placing your element with JavaScript that allows you to pass nested arrays and objects as default props: 74 | 75 | ```{ojs} 76 | //| eval: false 77 | anotherBarChart = { 78 | const chart = document.createElement("bar-chart") 79 | chart.data = [ 80 | { name: "James", score: 13 }, 81 | { name: "Andrew", score: 15 } ] 82 | return chart 83 | } 84 | ``` 85 | 86 | Of course, you can always make your default value an empty array (or make the default value inside the component an empty array) and then pass the nested one via OJS: 87 | 88 | ```{ojs} 89 | //| eval: false 90 | myCircles = html` 91 | 92 | ` 93 | ``` 94 | 95 | 96 | ```{ojs} 97 | //| eval: false 98 | myCircles.data = [ 99 | { name: "James", score: 13 }, 100 | { name: "Andrew", score: 15 } ] 101 | ``` -------------------------------------------------------------------------------- /docs/articles/migrating-from-sverto-v1/index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migrating from Sverto v1 and Svelte 3 3 | date: 2025-06-12 4 | --- 5 | 6 | If you've already been using Sverto v1, there are a few changes you'll need to make to update to quarto-svelte v2. 7 | 8 | # Update the extension 9 | 10 | Because the name of the extension has changed, I recommend deleting `_extensions/jimjam-slam/sverto` (or `_extensions/sverto`, if you installed from a ZIP file) from your project. 11 | 12 | Then, from the root of your project (or the same folder as your doc, if you aren't using a project) run: 13 | 14 | ```bash 15 | quarto use template jimjam-slam/quarto-svelte 16 | ``` 17 | 18 | # (For projects) Remove the custom project type 19 | 20 | Previously, we used `project.type: sverto` instead of `project.type: website` in `_quarto.yml`. This meant that you couldn't use Sverto with other custom project types. 21 | 22 | This is no longer the case: quarto-svelte v2 operates as a [metadata extension](https://quarto.org/docs/extensions/metadata.html), so its project prerender script will work automatically whenever it's installed in a Quarto project. You can revert the project type to `website`, or to another custom project type if you have other extensions installed. 23 | 24 | # Migrate components 25 | 26 | Version 2 of quarto-svelte moves from Svelte 3 to Svelte 5. 27 | 28 | This is a substantial change even to basic components, and your Svelte components will need to be updated for the change. Start with the [Svelte 5 migration guide](https://svelte.dev/docs/svelte/v5-migration-guide) to get a sense of the changes you need to make. 29 | 30 | Thankfully, Svelte also provides a [migration script](https://svelte.dev/docs/svelte/v5-migration-guide#Migration-script) that can automatically update old components. 31 | 32 | To run the migration tool, from your project or document folder run: 33 | 34 | ```bash 35 | npx sv migrate svelte-5 36 | ``` 37 | 38 | # Add custom element options to components 39 | 40 | As well as migrating components to Svelte 5, you will need to configure them as [custom elements](https://svelte.dev/docs/svelte/custom-elements). 41 | 42 | Using custom elements allows Svelte components to be placed on the page as if they were HTML elements. It also allows quarto-svelte to sidestep difficult changes to the way Svelte components are normally placed on pages. 43 | 44 | Unfortunately, this configuration isn't something that can be done automatically — you'll need to provide, at minimum, a name for your custom element by providing a block like the following at the top of your Svelte component (before the `