commit 3c05b4cc72d443fe22f94f22f4c9f85d41a0e04f Author: Hans Fast Date: Wed Mar 11 14:58:31 2026 +0100 initial: working demo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ae5bb2 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Find linked Files in HTML and Include Them in Build Output + +`getLinkedFilesFromHtml.js` parses html input to an [abstract syntax tree](https://github.com/syntax-tree/hast) and extracts the `href`/`src` attributes from `link` and `img` nodes. + +We now have a list of slash-separated paths as strings. + +``` +[ + 'css/style.css', + 'about/bird.webp', + 'about/monkey.webp' +] + +``` + +But Origami doesn't work with paths, it works with trees. So to get Origami to copy the files' contents for us, we need a nested tree that Origami can traverse. + + +``` +{ + images: { + 'bird.webp': (binary image data), + 'monkey.webp': (binary image data), + }, + css: { + 'style.css': 'body { font-family: ...}' + } +} + +``` + +So `pathsToObjs.js` creates such an object from the slash-separated paths. + +The resulting object can be applied as a [mask](https://weborigami.org/builtins/tree/mask) to the tree of all files in the source directory. The result is a tree with only the files which the html files link to. We include the contents of this tree in the final top-level tree. + +## Demo +This file and the following two linked pages all reference `css/style.css`. The two pages also each reference an image from the `image/` directory. + +* [linked-image](linked-image.html) +* [linked-stylesheet](linked-stylesheet.html) + +Here's the full `site.ori` defining the files to copy to the output. + + +``` +${site.ori} +``` diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..704d0b0 --- /dev/null +++ b/css/style.css @@ -0,0 +1,5 @@ +body { + font-family: "Jost*", sans-serif; + max-width: 60ch; + margin: 0 auto; +} diff --git a/getLinkedFilesFromHtml.js b/getLinkedFilesFromHtml.js new file mode 100644 index 0000000..97ae245 --- /dev/null +++ b/getLinkedFilesFromHtml.js @@ -0,0 +1,19 @@ +import {fromHtml} from 'hast-util-from-html'; +import {visit} from 'unist-util-visit'; + +export default async function(value) { + const tree = fromHtml(value); + const links = []; + + //take tree; + //visit nodes matching one of the test patterns; + //apply function to those nodes. + visit(tree, [ + {tagName: 'link'}, + {tagName: 'img'} + ], function(node) { + links.push(node); + }) + + return links.map(link => link.properties.href || link.properties.src) +} diff --git a/images/bird.webp b/images/bird.webp new file mode 100644 index 0000000..6db86aa Binary files /dev/null and b/images/bird.webp differ diff --git a/images/cat.webp b/images/cat.webp new file mode 100644 index 0000000..1060f43 Binary files /dev/null and b/images/cat.webp differ diff --git a/images/monkey.webp b/images/monkey.webp new file mode 100644 index 0000000..b69b32f Binary files /dev/null and b/images/monkey.webp differ diff --git a/linked-image.html b/linked-image.html new file mode 100644 index 0000000..a1a96aa --- /dev/null +++ b/linked-image.html @@ -0,0 +1,18 @@ + + + + + + Copying Linked Image + + + +

Copying Linked Stylesheet

+

Find the files.

+

The image below appears if the mechanism we're testing found the img tag and managed to resolve it's src attribute.

+ origami bird + +

But if you look in the images/ directory, you won't see cat.webp even though it's in the source tree, because that file isn't linked to from any of the html pages.

+ + + diff --git a/linked-stylesheet.html b/linked-stylesheet.html new file mode 100644 index 0000000..ec202a5 --- /dev/null +++ b/linked-stylesheet.html @@ -0,0 +1,19 @@ + + + + + + Copying Linked Stylesheet + + + +

Copying Linked Stylesheet

+

Find the files.

+

This link to the Stylesheet will only work if the mechanism we're testing found the link in this page's head. + +

Just for fun, this file also links to an image:

+ a friendly origami monkey + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f55195d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,283 @@ +{ + "name": "ori-include-linked-paths", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ori-include-linked-paths", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "hast-util-from-html": "^2.0.3", + "unist-util-visit": "^5.1.0" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b329fa0 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "ori-include-linked-paths", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "hast-util-from-html": "^2.0.3", + "unist-util-visit": "^5.1.0" + } +} diff --git a/page.ori.html b/page.ori.html new file mode 100644 index 0000000..9fb9dc6 --- /dev/null +++ b/page.ori.html @@ -0,0 +1,17 @@ +--- +(body) => _template() +--- + + + + + + + README + + + + ${body} + + + diff --git a/pathsToObjs.js b/pathsToObjs.js new file mode 100644 index 0000000..c48d471 --- /dev/null +++ b/pathsToObjs.js @@ -0,0 +1,36 @@ +// Original source - https://stackoverflow.com/a/44681235 +// Posted by le_m +// Retrieved 2026-03-11, License - CC BY-SA 3.0 +// Insert path into directory tree structure: +// +//modified to construct an object instead of an array. + +function insert(tree = {}, [head, ...tail]) { + + if (tail.length > 0) { + tree[head] = insert(tree[head], tail) + } else { + tree[head] = true + } + + return tree; +} + +// Example: +let examplepaths = [ + 'css/style.css', + 'about/bird.svg', +]; + +//naive! assumes relative path like in example. +//A more robust implementation would need to resolve paths first, but to what. +export default function process(paths) { + return paths + .map(path => path.split('/') + //.slice(1) //if path starts with / or ./ + ) + .reduce((tree, path) => insert(tree, path), {}); +} + +//test: +// console.log(process(examplepaths)); diff --git a/site.ori b/site.ori new file mode 100644 index 0000000..6797bbf --- /dev/null +++ b/site.ori @@ -0,0 +1,26 @@ +{ + + (pages): { + ./linked-image.html + ./linked-stylesheet.html + } + ...pages/ + + /* + spread the final result of this closure into the parent tree. + */ + ...{ + hast = Tree.map(pages, getLinkedFilesFromHtml.js) + //the [...new Set()] construct filters out duplicates + links: Tree.flat(hast, 2) → (a) => [...new Set(a)] + + //linksAsTree is a nested object representation of the paths. + //can use that object as a mask on all to get a 'filtered' tree. + linksAsTree: pathsToObjs.js(links) + onlyLinkedFiles: Tree.mask(<.>, linksAsTree) + }.onlyLinkedFiles + + index.html: page.ori.html(Origami.mdHtml(Origami.inline(README.md))) + +} +