├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitattributes ├── .github ├── CLA.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ └── spec.yml ├── labeler.yml ├── pull_request_template.md ├── tools │ ├── browser_path.ts │ ├── bump_versions.ts │ ├── expand_glob.ts │ ├── fill_github_scopes.ts │ ├── generate_mermaid.ts │ ├── github_actions_deno_deploy.ts │ ├── mod_html_to_readme_md.ts │ └── vendor_imports.ts └── workflows │ ├── ci.yml │ ├── issue.yml │ ├── pr.yml │ └── stale.yml ├── .gitignore ├── .npmrc ├── @mizu ├── bind │ ├── README.md │ ├── boolean.ts │ ├── boolean_test.ts │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── clean │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── code │ ├── README.md │ ├── build.ts │ ├── deno.jsonc │ ├── mapping.json │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── custom-element │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── eval │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── event │ ├── README.md │ ├── deno.jsonc │ ├── keyboard.ts │ ├── keyboard_test.ts │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── for │ ├── README.md │ ├── deno.jsonc │ ├── empty │ │ ├── README.md │ │ ├── mod.html │ │ ├── mod.ts │ │ ├── mod_test.html │ │ └── mod_test.ts │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ ├── mod_test.ts │ ├── parse.ts │ └── parse_test.ts ├── html │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── http │ ├── README.md │ ├── deno.jsonc │ ├── event │ │ ├── README.md │ │ ├── mod.html │ │ ├── mod.ts │ │ ├── mod_test.html │ │ └── mod_test.ts │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── if │ ├── README.md │ ├── deno.jsonc │ ├── else │ │ ├── README.md │ │ ├── mod.html │ │ ├── mod.ts │ │ ├── mod_test.html │ │ └── mod_test.ts │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── internal │ ├── README.md │ ├── deno.jsonc │ ├── engine │ │ ├── directive.ts │ │ ├── directive_test.ts │ │ ├── mod.ts │ │ ├── mod_test.ts │ │ ├── phase.ts │ │ ├── phase_test.ts │ │ ├── renderer.ts │ │ └── renderer_test.ts │ ├── extras │ │ ├── evaluate.ts │ │ └── evaluate_test.ts │ ├── testing │ │ ├── filter.ts │ │ ├── filter_test.ts │ │ ├── fixtures │ │ │ ├── markup_fmt-v0.13.1.wasm │ │ │ └── mod_test.html │ │ ├── format.ts │ │ ├── format_test.ts │ │ ├── mod.ts │ │ ├── mod_test.ts │ │ ├── test.ts │ │ └── test_test.ts │ └── vdom │ │ ├── jsdom.ts │ │ ├── jsdom_test.ts │ │ ├── mod.ts │ │ └── mod_test.ts ├── is │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── markdown │ ├── README.md │ ├── build.ts │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── mizu │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── model │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── mustache │ ├── README.md │ ├── capture.ts │ ├── capture_test.ts │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── once │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── ref │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── refresh │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── render │ ├── README.md │ ├── client │ │ ├── client.ts │ │ ├── client_test.ts │ │ ├── defaults.ts │ │ ├── defaults_test.ts │ │ ├── mod.ts │ │ └── mod_test.ts │ ├── deno.jsonc │ └── server │ │ ├── defaults.ts │ │ ├── defaults_test.ts │ │ ├── generate.ts │ │ ├── generate_test.ts │ │ ├── mod.ts │ │ ├── mod_test.ts │ │ ├── server.ts │ │ └── server_test.ts ├── set │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── show │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── skip │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── test │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── text │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── toc │ ├── README.md │ ├── deno.jsonc │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts └── unstable │ ├── README.md │ ├── deno.jsonc │ └── noop │ ├── README.md │ ├── mod.html │ ├── mod.ts │ ├── mod_test.html │ └── mod_test.ts ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── deno.jsonc ├── deno.lock ├── package-lock.json ├── package.json ├── tsconfig.json └── www ├── api └── build.ts ├── build.ts ├── html ├── _ │ ├── footer.html │ ├── header.html │ ├── matcha-banner.html │ └── mizu-banner.html ├── build.html ├── community.html ├── index.html ├── mizu │ ├── api.html │ ├── concepts.html │ ├── demo.html │ ├── directives.html │ ├── examples │ │ ├── browsers │ │ │ ├── esm.html │ │ │ └── iife.html │ │ ├── bun │ │ │ ├── ssg.ts │ │ │ └── ssr.ts │ │ ├── deno │ │ │ ├── ssg.ts │ │ │ └── ssr.ts │ │ ├── node │ │ │ ├── ssg.mjs │ │ │ └── ssr.mjs │ │ └── playground │ │ │ └── example.html │ ├── faq.html │ ├── features.html │ ├── logo.html │ ├── tbd.html │ └── usage.html └── playground.html ├── misc └── rendering.mermaid ├── serve.ts ├── static ├── 180x180.png ├── 32x32.png ├── components │ ├── mizu-directive.html │ ├── mizu-example.html │ ├── mizu-import.html │ ├── mizu-modifier.html │ ├── mizu-note.html │ ├── mizu-restriction.html │ ├── mizu-tbd.html │ ├── mizu-variable.html │ └── mizu-warn.html ├── demo_browser.png ├── logo.png ├── minu.png ├── og.png ├── rendering.svg ├── styles.css └── waves.svg └── tools.ts /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:debian 2 | 3 | # Install deno 4 | ENV DENO_INSTALL=/deno 5 | RUN mkdir -p /deno \ 6 | && curl -fsSL https://deno.land/install.sh | sh \ 7 | && chown -R vscode /deno 8 | 9 | ENV PATH=${DENO_INSTALL}/bin:${PATH} \ 10 | DENO_DIR=${DENO_INSTALL}/.cache/deno 11 | 12 | RUN deno upgrade 13 | 14 | # Install Node.js 15 | RUN apt-get update \ 16 | && apt-get install -y curl \ 17 | && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ 18 | && apt-get install -y nodejs 19 | 20 | # Install Bun 21 | ENV BUN_INSTALL=/bun 22 | RUN curl -fsSL https://bun.sh/install | bash \ 23 | && chown -R vscode /bun 24 | ENV PATH=${BUN_INSTALL}/bin:${PATH} 25 | 26 | # Setup Chrome 27 | RUN apt-get update \ 28 | && apt-get install -y wget gnupg \ 29 | && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ 30 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ 31 | && apt-get update \ 32 | && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends \ 33 | && rm -rf /var/lib/apt/lists/* 34 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Deno", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | "customizations": { 7 | "vscode": { 8 | "settings": { 9 | "editor.defaultFormatter": "denoland.vscode-deno", 10 | "deno.lint": true, 11 | "deno.enable": true 12 | }, 13 | "extensions": [ 14 | "ms-azuretools.vscode-docker", 15 | "denoland.vscode-deno", 16 | "GitHub.vscode-github-actions", 17 | "GitHub.copilot", 18 | "GitHub.copilot-chat" 19 | ] 20 | }, 21 | "remoteUser": "vscode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | .devcontainer/* -linguist-detectable 5 | .github/* -linguist-detectable 6 | *.html linguist-documentation -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lowlighter 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | description: File a bug report. 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thank you for taking the time to report a bug! 9 | - type: dropdown 10 | id: scope 11 | attributes: 12 | label: Scope 13 | description: Select the scope(s) of the issue. 14 | multiple: true 15 | options: 16 | - bind 17 | - clean 18 | - code 19 | - custom-element 20 | - eval 21 | - event 22 | - for 23 | - html 24 | - http 25 | - if 26 | - internal 27 | - is 28 | - markdown 29 | - mizu 30 | - model 31 | - mustache 32 | - once 33 | - ref 34 | - refresh 35 | - render 36 | - repo 37 | - set 38 | - show 39 | - skip 40 | - test 41 | - text 42 | - toc 43 | - unstable 44 | - www 45 | validations: 46 | required: true 47 | - type: dropdown 48 | id: environment 49 | attributes: 50 | label: Environment 51 | description: Select the environment(s) where you encountered the issue. 52 | multiple: true 53 | options: 54 | - chromium 55 | - firefox 56 | - safari 57 | - node 58 | - deno 59 | - bun 60 | - linux 61 | - macos 62 | - windows 63 | - other 64 | validations: 65 | required: true 66 | - type: textarea 67 | id: content 68 | attributes: 69 | label: What is happening? 70 | description: Describe the issue and what you expected to happen. 71 | placeholder: | 72 | Provide a detailed description of the bug. 73 | Include screenshots, logs, or any other relevant information. 74 | validations: 75 | required: true 76 | - type: textarea 77 | id: reproduction 78 | attributes: 79 | label: Minimal reproduction example 80 | description: Describe how to reproduce the issue. 81 | placeholder: > 82 | Provide a minimal example to reproduce the issue. 83 | 84 | Failure to do so may result in the issue being closed without 85 | investigation. 86 | render: html 87 | validations: 88 | required: true 89 | - type: dropdown 90 | id: i-did-my-homework 91 | attributes: 92 | label: I have searched for existing issues 93 | description: You confirm that you took the time to search for existing issues. 94 | options: 95 | - "" 96 | - "Yes" 97 | validations: 98 | required: true 99 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/spec.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F4DC Spec definition" 2 | description: Propose a new specification. 3 | labels: 4 | - spec 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thank you for taking the time to propose a new specification! 9 | - type: dropdown 10 | id: scope 11 | attributes: 12 | label: Scope 13 | description: >- 14 | Select the scope(s) of the issue. To propose a new directive, use the 15 | `unstable` scope. 16 | multiple: true 17 | options: 18 | - bind 19 | - clean 20 | - code 21 | - custom-element 22 | - eval 23 | - event 24 | - for 25 | - html 26 | - http 27 | - if 28 | - internal 29 | - is 30 | - markdown 31 | - mizu 32 | - model 33 | - mustache 34 | - once 35 | - ref 36 | - refresh 37 | - render 38 | - repo 39 | - set 40 | - show 41 | - skip 42 | - test 43 | - text 44 | - toc 45 | - unstable 46 | - www 47 | validations: 48 | required: true 49 | - type: textarea 50 | id: content 51 | attributes: 52 | label: What do you want to define? 53 | description: Describe the specification you would like to see added. 54 | placeholder: | 55 | Provide a detailed description of the specification. 56 | Include diagrams, examples, or any other relevant information. 57 | validations: 58 | required: true 59 | - type: dropdown 60 | id: i-did-my-homework 61 | attributes: 62 | label: I have searched for existing issues 63 | description: You confirm that you took the time to search for existing issues. 64 | options: 65 | - "" 66 | - "Yes" 67 | validations: 68 | required: true 69 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /.github/tools/browser_path.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-console 2 | // Imports 3 | import { getBinary } from "@astral/astral" 4 | 5 | console.log(await getBinary("chrome", { cache: ".cache" })) 6 | -------------------------------------------------------------------------------- /.github/tools/bump_versions.ts: -------------------------------------------------------------------------------- 1 | // Imports 2 | import type { Directive } from "@mizu/internal/engine" 3 | import { Logger } from "@libs/logger" 4 | import { parseArgs } from "@std/cli" 5 | import * as git from "@libs/git" 6 | import * as semver from "@std/semver" 7 | const log = new Logger() 8 | 9 | /** Bump packages version and print changelogs. */ 10 | if (import.meta.main) { 11 | const { "dry-run": dryrun } = parseArgs(Deno.args, { boolean: ["dry-run"] }) 12 | 13 | // Compute scopes 14 | const scopes = [...await Array.fromAsync<{ name: string }>(Deno.readDir("@mizu"))] 15 | .filter(({ name }) => !["internal", "render", "coverage"].includes(name)) 16 | .map(({ name }) => name) 17 | 18 | // Compute render scopes 19 | const render = { scopes: ["internal", "render"] } 20 | const mapping = new Map>() 21 | for (const scope of scopes) { 22 | if (scope === "unstable") { 23 | continue 24 | } 25 | const { default: exported } = await import(`@mizu/${scope}`) 26 | for (const directive of [exported].flat()) { 27 | if (!mapping.has(directive)) { 28 | mapping.set(directive, new Set()) 29 | } 30 | mapping.get(directive)!.add(scope) 31 | } 32 | } 33 | for (const [directive, scopes] of mapping) { 34 | log.with({ directive: `${directive.name}`, scopes: [...scopes].join(",") }).debug() 35 | } 36 | for (const section of ["client", "server"] as const) { 37 | const { default: directives } = await import(`@mizu/render/${section}/defaults`) 38 | for (const directive of [directives].flat()) { 39 | render.scopes.push(...(mapping.get(directive) ?? [])) 40 | } 41 | } 42 | render.scopes = [...new Set(render.scopes)] 43 | 44 | // Bump package versions 45 | for (const scope of [...scopes, "internal", "render"]) { 46 | log.with({ directive: scope }).debug("checking") 47 | const { version, changelog } = git.changelog(`@mizu/${scope}/deno.jsonc`, { write: !dryrun, scopes: [scope, "internal", ...(scope === "render" ? render.scopes : [])] }) 48 | if (version.bump) { 49 | changelog.split("\n").map((line) => log.with({ directive: scope }).info(line)) 50 | log.with({ directive: scope }).ok(`${semver.format(version.current)} → ${semver.format(version.next)}`) 51 | } else { 52 | log.with({ directive: scope }).debug("no changes") 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/tools/expand_glob.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-console 2 | // Imports 3 | import { expandGlob } from "@std/fs" 4 | import { parseArgs } from "@std/cli" 5 | 6 | /*** Resolve globs. */ 7 | if (import.meta.main) { 8 | const { _: globs, ...flags } = parseArgs(Deno.args, { boolean: ["break"], string: ["separator"] }) 9 | const paths = [] 10 | for (const glob of globs) { 11 | for await (const { path } of expandGlob(`${glob}`)) { 12 | paths.push(path) 13 | } 14 | if ((paths.length) && (flags.break)) { 15 | break 16 | } 17 | } 18 | console.log(paths.join(flags.separator ?? ",")) 19 | } 20 | -------------------------------------------------------------------------------- /.github/tools/fill_github_scopes.ts: -------------------------------------------------------------------------------- 1 | // Imports 2 | import type { rw } from "@libs/typing/types" 3 | import { expandGlob } from "@std/fs" 4 | import { Logger } from "@libs/logger" 5 | import { command } from "@libs/run/command" 6 | import * as YAML from "@std/yaml" 7 | const log = new Logger() 8 | 9 | /*** Fill GitHub files with current existing scopes. */ 10 | if (import.meta.main) { 11 | // List scopes 12 | const scopes = [...await Array.fromAsync<{ name: string }>(Deno.readDir("@mizu"))] 13 | .concat([{ name: "www" }, { name: "repo" }]) 14 | .filter(({ name }) => !["coverage"].includes(name)) 15 | .map(({ name }) => name) 16 | .sort() 17 | // Patch issues templates 18 | log.info("patching issue templates") 19 | for await (const { path, name } of expandGlob(".github/ISSUE_TEMPLATE/*.yml")) { 20 | if (name === "config.yml") { 21 | continue 22 | } 23 | const parsed = YAML.parse(await Deno.readTextFile(path)) as rw 24 | if (parsed.body.some((field: rw) => field.id === "scope")) { 25 | parsed.body.find((filed: rw) => filed.id === "scope").attributes.options = scopes 26 | await Deno.writeTextFile(path, YAML.stringify(parsed)) 27 | await command("deno", ["fmt", path], { stdout: null, stderr: null }) 28 | log.with({ path }).ok("done") 29 | } 30 | } 31 | // Patch pull request labeler 32 | { 33 | log.info("patching pull request labeler") 34 | const path = ".github/labeler.yml" 35 | const generated = YAML.stringify(Object.fromEntries(scopes.map((scope) => [`scope: ${scope}`, [ 36 | { "changed-files": [{ "any-glob-to-any-file": `@mizu/${scope}/**` }] }, 37 | ]]))) 38 | await Deno.writeTextFile(path, (await Deno.readTextFile(path)).replace(/(## == Autogenerated configuration starts below == ##)[\s\S]*/g, `$1\n\n${generated}`)) 39 | await command("deno", ["fmt", path], { stdout: null, stderr: null }) 40 | log.with({ path }).ok("done") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/tools/github_actions_deno_deploy.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | // Imports 3 | import * as JSONC from "@std/jsonc" 4 | import * as core from "@actions/core" 5 | 6 | // Parse deno.jsonc and print deno deploy flags 7 | const config = JSONC.parse(await Deno.readTextFile("./deno.jsonc")) as any 8 | await Deno.writeTextFile(".imports.json", JSON.stringify({ imports: config.imports }, null, 2)) 9 | core.setOutput("project", config.deploy.project) 10 | core.setOutput("entrypoint", config.deploy.entrypoint) 11 | core.setOutput("include", config.deploy.include.join(",")) 12 | core.setOutput("exclude", config.deploy.exclude.join(",")) 13 | core.setOutput("import_map", ".imports.json") 14 | -------------------------------------------------------------------------------- /.github/tools/vendor_imports.ts: -------------------------------------------------------------------------------- 1 | // Imports 2 | import type { Promisable } from "@libs/typing" 3 | import { fromFileUrl, globToRegExp, join, parse } from "@std/path" 4 | import { emptyDir, exists } from "@std/fs" 5 | import { Logger } from "@libs/logger" 6 | 7 | /** Vendor imports. */ 8 | export class Vendor { 9 | /** Constructor. */ 10 | constructor({ directive, meta, name }: { directive: string | RegExp; meta: ImportMeta; name: string }) { 11 | this.#directory = join(fromFileUrl(meta.resolve(`./import/${name}`))) 12 | this.log = new Logger().with({ directive }) 13 | } 14 | 15 | /** Exposed import directory. */ 16 | readonly #directory 17 | 18 | /** Linked {@linkcode Logger}. */ 19 | readonly log 20 | 21 | /** List files from github repository. */ 22 | async #tree(repository: string, { branch, path }: { branch: string; path: string }) { 23 | const segments = [...path.split("/"), ""] 24 | let subtree = { url: `https://api.github.com/repos/${repository}/git/trees/${branch}`, tree: [] as Array<{ path: string; type: string }> } 25 | while (segments.length) { 26 | const segment = segments.shift() 27 | this.log.debug(`fetching ${subtree.url}`) 28 | const { tree } = await fetch(subtree.url).then((response) => response.json()) 29 | subtree = segment ? tree.find(({ path }: { path: string }) => path === segment) : { url: subtree.url, tree } 30 | } 31 | return subtree.tree.filter(({ type }: { type: string }) => type === "blob").map(({ path }: { path: string }) => path) 32 | } 33 | 34 | /** Vendor files from github repository. */ 35 | async github( 36 | { repository, branch, path, globs, destination = "", export: exporter, callback }: { 37 | repository: string 38 | branch: string 39 | path: string 40 | globs?: string[] 41 | destination?: string 42 | export?: (name: string) => string 43 | callback?: (name: string, _: { log: Logger }) => Promisable 44 | }, 45 | ) { 46 | // Clean directory 47 | if (exporter) { 48 | destination = join(this.#directory, destination) 49 | await emptyDir(destination) 50 | this.log.with({ destination }).ok("cleaned") 51 | } 52 | // Search files from repository 53 | let files = await this.#tree(repository, { branch, path }) 54 | if (globs) { 55 | files = files.filter((file) => globs.every((glob) => globToRegExp(glob, { extended: true }).test(file))) 56 | } 57 | this.log.with({ repository, branch, path }).info(`found ${files.length} matching files`) 58 | // Process files 59 | for (const filename of files) { 60 | const { name } = parse(filename) 61 | const log = this.log.with({ name }).debug("processing") 62 | // Re-export file 63 | if (exporter) { 64 | const exports = join(destination, `${name}.ts`) 65 | if (!await exists(exports)) { 66 | await Deno.writeTextFile(exports, exporter(name)) 67 | log.with({ exports }).ok() 68 | } 69 | } 70 | // Apply callback 71 | await callback?.(name, { log }) 72 | } 73 | return this 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/issue.yml: -------------------------------------------------------------------------------- 1 | name: Issue 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | - edited 7 | 8 | jobs: 9 | labeler: 10 | name: Labeler 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | issues: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/github-script@v7 18 | with: 19 | script: | 20 | for (const { section, prefix } of [ 21 | { section: "Scope", prefix: "scope: " }, 22 | { section: "Environment", prefix: "env: " }, 23 | ]) { 24 | const captured = (context.payload.issue.body.match(new RegExp(`### ${section}\\s*(?.*?)\\s*###`))?.groups?.labels ?? "").trim() 25 | console.log(captured, captured.split(",").map(name => `${prefix}${name.trim()}`)) 26 | if (!captured) { 27 | continue 28 | } 29 | 30 | // Validate labels 31 | const { data: allowed } = await github.rest.issues.listLabelsForRepo({ ...context.repo, per_page: 100 }).catch(() => ({ data: [] })) 32 | console.log(allowed.map(label => label.name)) 33 | const labels = captured.split(",").map(name => `${prefix}${name.trim()}`).filter(name => allowed.some(label => label.name === name)) 34 | console.log(`Issue #${context.issue.number} ${prefix}${JSON.stringify(labels)}`) 35 | 36 | // Add new labels 37 | await github.rest.issues.addLabels({ ...context.repo, issue_number: context.issue.number, labels }).catch(() => null) 38 | 39 | // Remove previous labels 40 | const { data: previous } = await github.rest.issues.listLabelsOnIssue({ ...context.repo, issue_number: context.issue.number, per_page: 100 }).catch(() => ({ data: [] })) 41 | for (const { name } of previous.filter(label => label.name.startsWith(prefix) && !labels.includes(label.name))) { 42 | console.log(`Removing previous label: ${name}`) 43 | await github.rest.issues.removeLabel({ ...context.repo, issue_number: context.issue.number, name }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull 2 | on: 3 | pull_request_target: 4 | 5 | jobs: 6 | labeler: 7 | name: Labeler 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | pull-requests: write 12 | steps: 13 | - uses: actions/labeler@v5 14 | with: 15 | sync-labels: true 16 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | on: 3 | schedule: 4 | - cron: "0 2 * * *" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v9 15 | with: 16 | stale-issue-label: stale 17 | stale-pr-label: stale 18 | exempt-all-milestones: true 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vercel 3 | .imports.json 4 | coverage 5 | node_modules 6 | www/.pages -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @jsr:registry=https://npm.jsr.io 2 | engine-strict=true -------------------------------------------------------------------------------- /@mizu/bind/boolean.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks whether an HTML attribute is a boolean attribute for the specified tag name. 3 | * 4 | * See {@link https://html.spec.whatwg.org/#attributes-3 | HTML attributes reference}. 5 | */ 6 | export function boolean(tagname: string, attribute: string): boolean { 7 | if (/^autofocus|inert|itemscope$/.test(attribute)) { 8 | return true 9 | } 10 | if (attribute === "disabled") { 11 | return /^(?:BUTTON|INPUT|OPTGROUP|OPTION|SELECT|TEXTAREA|FIELDSET|LINK)$/.test(tagname) 12 | } 13 | switch (tagname) { 14 | // Form elements 15 | case "FORM": 16 | return attribute === "novalidate" 17 | case "TEXTAREA": 18 | return /^(?:readonly|required)$/.test(attribute) 19 | case "INPUT": 20 | if (/^(?:checked|formnovalidate|readonly)$/.test(attribute)) { 21 | return true 22 | } 23 | // fallthrough 24 | case "SELECT": 25 | return /^(?:multiple|required)$/.test(attribute) 26 | case "OPTION": 27 | return attribute === "selected" 28 | case "BUTTON": 29 | return attribute === "formnovalidate" 30 | 31 | // Media elements 32 | case "IMG": 33 | return attribute === "ismap" 34 | case "VIDEO": 35 | if (attribute === "playsinline") { 36 | return true 37 | } 38 | // fallthrough 39 | case "AUDIO": 40 | return /^(?:autoplay|controls|loop|muted)$/.test(attribute) 41 | case "TRACK": 42 | return attribute === "default" 43 | 44 | // Other elements 45 | case "TEMPLATE": 46 | return /^shadowroot(?:delegatesfocus|clonable|serializable)$/.test(attribute) 47 | case "SCRIPT": 48 | return /^(?:async|defer|nomodule)$/.test(attribute) 49 | case "IFRAME": 50 | return attribute === "allowfullscreen" 51 | case "OL": 52 | return attribute === "reversed" 53 | case "DETAILS": 54 | case "DIALOG": 55 | return attribute === "open" 56 | } 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /@mizu/bind/boolean_test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "@libs/testing" 2 | import { boolean } from "./boolean.ts" 3 | 4 | for ( 5 | const [attribute, tags] of [ 6 | ["autofocus", { p: true }], 7 | ["disabled", { button: true, input: true, optgroup: true, option: true, select: true, textarea: true, fieldset: true, link: true, p: false }], 8 | ["novalidate", { form: true, p: false }], 9 | ["readonly", { input: true, textarea: true, p: false }], 10 | ["required", { textarea: true, input: true, select: true, p: false }], 11 | ["checked", { input: true, p: false }], 12 | ["formnovalidate", { input: true, button: true, p: false }], 13 | ["multiple", { select: true, p: false }], 14 | ["selected", { option: true, p: false }], 15 | ["ismap", { img: true, p: false }], 16 | ["playsinline", { video: true, p: false }], 17 | ["autoplay", { video: true, audio: true, p: false }], 18 | ["controls", { video: true, audio: true, p: false }], 19 | ["loop", { video: true, audio: true, p: false }], 20 | ["muted", { video: true, audio: true, p: false }], 21 | ["default", { track: true, p: false }], 22 | ["shadowrootdelegatesfocus", { template: true, p: false }], 23 | ["shadowrootclonable", { template: true, p: false }], 24 | ["shadowrootserializable", { template: true, p: false }], 25 | ["async", { script: true, p: false }], 26 | ["defer", { script: true, p: false }], 27 | ["nomodule", { script: true, p: false }], 28 | ["allowfullscreen", { iframe: true, p: false }], 29 | ["reversed", { ol: true, p: false }], 30 | ["open", { details: true, dialog: true, p: false }], 31 | ["other", { p: false }], 32 | ] as const 33 | ) { 34 | test(`{:attribute} boolean() treats accordingly {${attribute}}`, () => { 35 | for (const [tagname, expected] of Object.entries(tags)) { 36 | expect(boolean(tagname.toUpperCase(), attribute)).toBe(expected) 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /@mizu/bind/deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mizu/bind", 3 | "version": "0.8.2", 4 | "exports": { 5 | ".": "./mod.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /@mizu/bind/mod_test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "@mizu/internal/testing" 2 | test(import.meta) 3 | -------------------------------------------------------------------------------- /@mizu/clean/README.md: -------------------------------------------------------------------------------- 1 | # `*clean` 2 | 3 | | Version | Phase | 4 | | -------------------------------------- | ----------------------- | 5 | | ![](https://jsr.io/badges/@mizu/clean) | 49 — `CONTENT_CLEANING` | 6 | 7 | Clean up the element and its children from specified content. 8 | 9 | ```html 10 |
11 | 12 |
13 | ``` 14 | 15 | ## Modifiers 16 | 17 | ### `.comments[boolean]` 18 | 19 | Remove all [`Comment`](https://developer.mozilla.org/docs/Web/API/Comment) nodes within the subtree. 20 | 21 | ### `.spaces[boolean]` 22 | 23 | Remove all spaces (except non-breaking spaces [` `](https://developer.mozilla.org/docs/Glossary/Character_reference)) within the subtree. 24 | 25 | ### `.templates[boolean]` 26 | 27 | Clear all [`