Showing preview only (644K chars total). Download the full file or copy to clipboard to get everything.
Repository: ivijs/ivi
Branch: master
Commit: 305896b59e30
Files: 257
Total size: 576.1 KB
Directory structure:
gitextract_8ywn65uv/
├── .clippy.toml
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── renovate.json
│ └── workflows/
│ ├── ci.yml
│ ├── napi-libraries.yml
│ └── napi-release.yml
├── .gitignore
├── .rustfmt.toml
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── crates/
│ └── ivi_compiler/
│ ├── Cargo.toml
│ └── src/
│ ├── chunk/
│ │ └── mod.rs
│ ├── context.rs
│ ├── import.rs
│ ├── lib.rs
│ ├── module/
│ │ └── mod.rs
│ ├── oveo.rs
│ └── tpl/
│ ├── emit.rs
│ ├── html.rs
│ ├── mod.rs
│ ├── opcodes.rs
│ └── parser.rs
├── docs/
│ ├── internals/
│ │ ├── dynamic-lists.md
│ │ ├── misc.md
│ │ ├── perf.md
│ │ └── template-compiler.md
│ └── misc/
│ └── migrating-from-react.md
├── justfile
├── napi.just
├── package.json
├── packages/
│ ├── @ivi/
│ │ ├── compiler/
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── build.rs
│ │ │ ├── index.d.ts
│ │ │ ├── index.js
│ │ │ ├── package.json
│ │ │ ├── packages/
│ │ │ │ ├── darwin-arm64/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── package.json
│ │ │ │ ├── darwin-x64/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── package.json
│ │ │ │ ├── linux-arm64-gnu/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── package.json
│ │ │ │ ├── linux-x64-gnu/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── package.json
│ │ │ │ ├── win32-arm64-msvc/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── package.json
│ │ │ │ └── win32-x64-msvc/
│ │ │ │ ├── README.md
│ │ │ │ └── package.json
│ │ │ ├── src/
│ │ │ │ └── lib.rs
│ │ │ └── tsconfig.json
│ │ ├── identity/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── mock-dom/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── global.ts
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── portal/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── rolldown/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── rollup-plugin/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ └── vite-plugin/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ └── ivi/
│ ├── LICENSE
│ ├── README.md
│ ├── oveo.json
│ ├── package.json
│ ├── src/
│ │ ├── html/
│ │ │ ├── index.ts
│ │ │ └── parser.ts
│ │ ├── index.ts
│ │ ├── lib/
│ │ │ ├── core.ts
│ │ │ ├── equal.ts
│ │ │ ├── state.ts
│ │ │ ├── template.ts
│ │ │ └── utils.ts
│ │ ├── template/
│ │ │ ├── compiler.ts
│ │ │ ├── ir.ts
│ │ │ ├── parser.ts
│ │ │ └── shared.ts
│ │ └── test-utils/
│ │ └── index.ts
│ └── tsconfig.json
├── rust-toolchain.toml
├── tests/
│ ├── compiler/
│ │ ├── chunk/
│ │ │ └── strings/
│ │ │ ├── data/
│ │ │ │ ├── 01-basic/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ └── 02-multiple/
│ │ │ │ ├── input.js
│ │ │ │ └── output.js
│ │ │ └── strings.test.ts
│ │ ├── module/
│ │ │ ├── data/
│ │ │ │ ├── 01-text/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 02-element/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 03-element/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 04-nested-text/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 05-nested-expr/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 06-text-before-expr/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 07-text-after-expr/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 09-text-before-and-after-expr/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 103-hoist-return-fn/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 11-multiple-roots/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 12-multiple-roots/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 13-multiple-nested-expr/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 14-svg-element/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 15-svg-template/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 16-nested-mix/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 17-void-element/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 18-expr-before-element/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 19-comment/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 20-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 21-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 22-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 23-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 24-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 25-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 26-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 27-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 28-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 29-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 30-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 31-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 32-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 33-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 34-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 35-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 36-whitespace/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 40-attribute/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 41-attribute/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 42-attribute/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 43-attributes/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 44-attributes/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 50-prop/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 51-prop/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 60-style/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 61-style/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 62-style/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 63-style-mix/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 70-event/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 80-directive/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── 90-hoist-class-identifier/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ └── 91-hoist-class-static-member/
│ │ │ │ ├── input.js
│ │ │ │ └── output.js
│ │ │ └── module.test.ts
│ │ ├── module-all-disabled/
│ │ │ ├── data/
│ │ │ │ ├── 01-event/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ └── 02-hoist-return-fn/
│ │ │ │ ├── input.js
│ │ │ │ └── output.js
│ │ │ └── module.test.ts
│ │ ├── module-oveo-disabled/
│ │ │ ├── data/
│ │ │ │ ├── 01-event/
│ │ │ │ │ ├── input.js
│ │ │ │ │ └── output.js
│ │ │ │ └── 02-hoist-return-fn/
│ │ │ │ ├── input.js
│ │ │ │ └── output.js
│ │ │ └── module.test.ts
│ │ └── normalize.ts
│ ├── package.json
│ └── runtime/
│ ├── array.test.ts
│ ├── component.test.ts
│ ├── containsDOMElement.test.ts
│ ├── context.test.ts
│ ├── equal.test.ts
│ ├── findDOMNode.test.ts
│ ├── hasDOMElement.test.ts
│ ├── hole.test.ts
│ ├── html/
│ │ └── parser.test.ts
│ ├── list.test.ts
│ ├── mock-dom/
│ │ ├── document.test.ts
│ │ ├── element.test.ts
│ │ ├── innerHTML.test.ts
│ │ ├── node.test.ts
│ │ └── template.test.ts
│ ├── template/
│ │ ├── attribute.test.ts
│ │ ├── className.test.ts
│ │ ├── directive.test.ts
│ │ ├── event.test.ts
│ │ ├── htm.test.ts
│ │ ├── innerHTML.test.ts
│ │ ├── property.test.ts
│ │ ├── propertyDiffDOM.test.ts
│ │ ├── style.test.ts
│ │ ├── svg.test.ts
│ │ └── textContent.test.ts
│ ├── text.test.ts
│ ├── useAnimationFrameEffect.test.ts
│ ├── useEffect.test.ts
│ ├── useIdleEffect.test.ts
│ ├── useMemo.test.ts
│ ├── useReducer.test.ts
│ └── useState.test.ts
├── tsconfig.composite.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .clippy.toml
================================================
disallowed-methods = [
{ path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." },
{ path = "str::to_ascii_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_uppercase` instead." },
{ path = "str::to_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_lowercase` instead." },
{ path = "str::to_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_uppercase` instead." },
{ path = "str::replace", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replace` instead." },
{ path = "str::replacen", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replacen` instead." },
]
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.{mts,ts,js,mjs}]
indent_style = space
indent_size = 2
[*.{css}]
indent_style = space
indent_size = 2
[*.{json,yml,yaml}]
indent_style = space
indent_size = 2
[*.{md}]
indent_style = space
indent_size = 2
================================================
FILE: .gitattributes
================================================
* text=auto
*.sh text eol=lf merge=union
*.rs text eol=lf merge=union
*.js text eol=lf merge=union
*.mjs text eol=lf merge=union
*.ts text eof=lf merge=union
*.mts text eof=lf merge=union
*.toml text eol=lf merge=union
*.json text eol=lf merge=union
*.yaml text eol=lf merge=union
*.yml text eol=lf merge=union
*.md text eol=lf merge=union
*.css text eol=lf merge=union
*.html text eol=lf merge=union
*.csv text eol=lf merge=union
================================================
FILE: .github/renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"npm:unpublishSafe",
":semanticCommits"
],
"timezone": "Etc/UTC",
"schedule": [
"* 0-4 * * 1-3"
],
"labels": [
"dependencies"
],
"commitMessagePrefix": "chore: ",
"commitMessageAction": "bump up",
"commitMessageTopic": "{{depName}} version",
"ignoreDeps": [],
"packageRules": [
{
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch",
"matchPackageNames": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
]
},
{
"groupName": "all devDependencies",
"groupSlug": "all-dev-dependencies",
"matchDepTypes": [
"devDependencies"
],
"rangeStrategy": "bump"
}
]
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
env:
ACTION_CACHE_PATH: |
~/.bun/install/cache
node_modules/
permissions:
contents: write
id-token: write
on:
workflow_dispatch:
inputs:
publish:
required: false
type: boolean
push:
paths:
- packages/**
- bun.lock
- "!crates/**"
- "!packages/@ivi/compiler/**"
- "!tests/compiler/**"
branches:
- master
pull_request:
paths:
- packages/**
- bun.lock
- "!crates/**"
- "!packages/@ivi/compiler/**"
- "!tests/compiler/**"
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
publish:
name: Publish
if: "${{ github.event_name != 'pull_request' && (inputs.publish || startsWith(github.event.head_commit.message, 'publish:')) }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: oven-sh/setup-bun@v2
- uses: extractions/setup-just@v4
- uses: actions/cache@v5
with:
path: ${{ env.ACTION_CACHE_PATH }}
key: CI
- run: just init
- run: just tsc
- name: Publish to NPM
run: |
npm config set provenance true
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
just publish --provenance --access public
================================================
FILE: .github/workflows/napi-libraries.yml
================================================
name: NAPI Libraries
env:
DEBUG: napi:*
MACOSX_DEPLOYMENT_TARGET: "10.13"
CARGO_INCREMENTAL: "1"
ACTION_CACHE_PATH: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
~/.bun/install/cache
node_modules/
permissions:
contents: write
id-token: write
on:
workflow_dispatch:
inputs:
publish:
required: false
type: boolean
push:
paths:
- crates/**
- packages/@ivi/compiler/**
- tests/compiler/**
- Cargo.lock
branches:
- master
pull_request:
paths:
- crates/**
- packages/@ivi/compiler/**
- tests/compiler/**
- Cargo.lock
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-napi:
strategy:
fail-fast: false
matrix:
settings:
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
build: just napi build --release --target x86_64-unknown-linux-gnu --use-napi-cross
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
build: just napi build --release --target aarch64-unknown-linux-gnu --use-napi-cross
- host: macos-latest
target: x86_64-apple-darwin
build: just napi build --release --target x86_64-apple-darwin
- host: macos-latest
target: aarch64-apple-darwin
build: just napi build --release --target aarch64-apple-darwin
- host: windows-latest
target: x86_64-pc-windows-msvc
build: just napi build --release --target x86_64-pc-windows-msvc
- host: windows-latest
target: aarch64-pc-windows-msvc
build: just napi build --release --target aarch64-pc-windows-msvc
name: stable - ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: oven-sh/setup-bun@v2
- uses: extractions/setup-just@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.settings.target }}
- uses: actions/cache@v5
with:
path: ${{ env.ACTION_CACHE_PATH }}
key: NAPI-${{ matrix.settings.target }}-${{ matrix.settings.host }}
- run: just init
- run: ${{ matrix.settings.build }}
- uses: actions/upload-artifact@v7
with:
name: NAPI-${{ matrix.settings.target }}
path: ./packages/@ivi/compiler/ivi-compiler.*.node
if-no-files-found: error
test:
name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}
needs:
- build-napi
strategy:
fail-fast: false
matrix:
settings:
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
architecture: x64
- host: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
architecture: arm64
- host: windows-latest
target: x86_64-pc-windows-msvc
architecture: x64
# Bun doesn't have windows arm64 binaries
# - host: windows-11-arm
# target: aarch64-pc-windows-msvc
# architecture: arm64
- host: macos-latest
target: aarch64-apple-darwin
architecture: arm64
# Bun is broken on x86_64 macos
# - host: macos-latest
# target: x86_64-apple-darwin
# architecture: x64
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
architecture: ${{ matrix.settings.architecture }}
- uses: oven-sh/setup-bun@v2
- uses: extractions/setup-just@v4
- uses: actions/cache@v5
with:
path: ${{ env.ACTION_CACHE_PATH }}
key: ${{ matrix.settings.target }}-${{ matrix.settings.host }}
- run: just init
if: steps.cache.outputs.cache-hit != 'true'
- uses: actions/download-artifact@v8
with:
name: NAPI-${{ matrix.settings.target }}
path: ./packages/@ivi/compiler/
- run: just napi test
publish:
name: Publish
if: "${{ github.event_name != 'pull_request' && (inputs.publish || startsWith(github.event.head_commit.message, 'publish:')) }}"
runs-on: ubuntu-latest
needs:
- test
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: oven-sh/setup-bun@v2
- uses: extractions/setup-just@v4
- uses: actions/cache@v5
with:
path: ${{ env.ACTION_CACHE_PATH }}
key: NAPI-x86_64-unknown-linux-gnu-ubuntu-latest
- run: just init
if: steps.cache.outputs.cache-hit != 'true'
- uses: actions/download-artifact@v8
with:
pattern: NAPI-*
path: napi-artifacts
- run: just napi artifacts
- name: Publish to NPM
run: |
npm config set provenance true
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
just napi publish --provenance --access public
================================================
FILE: .github/workflows/napi-release.yml
================================================
name: NAPI Release
permissions:
contents: write
id-token: write
on:
workflow_dispatch:
inputs:
increment:
type: choice
required: true
description: Increment version
options:
- patch
- minor
- major
jobs:
publish:
name: Release new @ivi/compiler version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: oven-sh/setup-bun@v2
- uses: extractions/setup-just@v4
- name: Increment versions and push changes
run: |
just napi increment-versions ${{ github.event.inputs.increment }}
git config --global user.name "GitHub Action"
git config --global user.email "username@users.noreply.github.com"
git commit -a -m "publish: @ivi/compiler $(jq -r .version ./packages/@ivi/compiler/package.json)"
git push
================================================
FILE: .gitignore
================================================
/examples/**/dist
/packages/**/dist
/tests/dist
.rsync-filter
.DS_Store
# Rust
/target/
# NAPI-rs
/napi-artifacts/
# JS
node_modules
*.tsbuildinfo
npm-debug.log
# Editors
/.idea/
/.vscode/
!/.vscode/settings.json
================================================
FILE: .rustfmt.toml
================================================
unstable_features = true
version = "Two"
style_edition = "2024"
edition = "2024"
format_strings = true
format_code_in_doc_comments = true
hex_literal_case = "Lower"
wrap_comments = true
reorder_modules = true
reorder_impl_items = true
use_field_init_shorthand = true
use_small_heuristics = "Max"
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Requirements
- Bun
- Just
## Getting Started
1. Clone the git repository: `git clone git@github.com:localvoid/ivi.git`
2. Go into the cloned folder: `cd ivi/`
3. Install all dependencies: `just init`
## Tasks
- `just init` - initializes development environment.
- `just napi build` - builds NAPI bindings.
- `just tsc` - builds typescript packages.
- `just napi test` - runs NAPI tests.
- `just test` - runs tests.
================================================
FILE: Cargo.toml
================================================
[workspace]
members = ["crates/*", "packages/@ivi/compiler"]
resolver = "3"
[workspace.dependencies]
thiserror = "2"
rustc-hash = "2"
indexmap = "2.10"
oxc_allocator = "0.121"
oxc_ast = "0.121"
oxc_codegen = "0.121"
oxc_data_structures = "0.121"
oxc_diagnostics = "0.121"
oxc_ecmascript = "0.121"
oxc_parser = "0.121"
oxc_semantic = "0.121"
oxc_span = "0.121"
oxc_syntax = "0.121"
oxc_traverse = "0.121"
napi = "3"
napi-derive = "3"
napi-build = "2"
ivi_compiler = { path = "./crates/ivi_compiler" }
[workspace.lints.clippy]
dbg_macro = "warn"
todo = "warn"
unimplemented = "warn"
print_stdout = "warn"
print_stderr = "warn"
allow_attributes = "warn"
clone_on_ref_ptr = "warn"
self_named_module_files = "warn"
empty_drop = "warn"
empty_structs_with_brackets = "warn"
exit = "warn"
get_unwrap = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
rest_pat_in_fully_bound_structs = "warn"
unnecessary_safety_comment = "warn"
undocumented_unsafe_blocks = "warn"
infinite_loop = "warn"
map_with_unused_argument_over_ranges = "warn"
unused_result_ok = "warn"
pathbuf_init_then_push = "warn"
collapsible_if = "allow"
collapsible_else_if = "allow"
collapsible_match = "allow"
[profile.release]
opt-level = 3
codegen-units = 1
lto = "fat"
panic = "abort"
strip = true
split-debuginfo = "packed"
[profile.dev]
debug = false
[profile.release-with-debug]
inherits = "release"
strip = false
debug = true
[profile.coverage]
inherits = "release"
opt-level = 2
codegen-units = 256
lto = "thin"
debug-assertions = true
overflow-checks = true
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: crates/ivi_compiler/Cargo.toml
================================================
[package]
name = "ivi_compiler"
version = "0.1.0"
edition = "2024"
authors = ["Boris Kaul <localvoid@gmail.com>"]
license = "MIT"
homepage = "https://github.com/localvoid/ivi"
repository = "https://github.com/localvoid/ivi"
description = "ivi template compiler"
[dependencies]
thiserror.workspace = true
rustc-hash.workspace = true
indexmap.workspace = true
oxc_allocator.workspace = true
oxc_ast.workspace = true
oxc_codegen.workspace = true
oxc_data_structures.workspace = true
oxc_diagnostics.workspace = true
oxc_ecmascript.workspace = true
oxc_parser.workspace = true
oxc_semantic.workspace = true
oxc_span.workspace = true
oxc_syntax.workspace = true
oxc_traverse.workspace = true
[lints]
workspace = true
================================================
FILE: crates/ivi_compiler/src/chunk/mod.rs
================================================
use oxc_allocator::{Allocator, Vec as ArenaVec};
use oxc_ast::ast::*;
use oxc_semantic::Scoping;
use oxc_span::SPAN;
use oxc_traverse::{Traverse, traverse_mut};
use rustc_hash::FxHashMap;
use crate::{
context::{TraverseCtx, TraverseCtxState},
tpl::opcodes::prop_op,
};
pub fn compile_chunk<'a>(
program: &mut Program<'a>,
allocator: &'a Allocator,
scoping: Scoping,
strings: &FxHashMap<String, u8>,
) {
let mut t = ChunkCompiler::new(strings);
traverse_mut(&mut t, allocator, program, scoping, TraverseCtxState::default());
}
struct ChunkCompiler<'ctx> {
strings: &'ctx FxHashMap<String, u8>,
}
impl<'ctx> ChunkCompiler<'ctx> {
pub fn new(strings: &'ctx FxHashMap<String, u8>) -> Self {
Self { strings }
}
}
impl<'a> Traverse<'a, TraverseCtxState<'a>> for ChunkCompiler<'_> {
fn enter_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match node {
Expression::ArrayExpression(expr) => {
if expr.elements.len() == 1 {
if let Some(Expression::StringLiteral(s)) = expr.elements[0].as_expression() {
if s.value == STRINGS_UUID {
let mut indexed: Vec<_> = self.strings.iter().collect();
indexed.sort_by_key(|e| e.1);
let mut strings = ctx.ast.vec_with_capacity(self.strings.len());
for (s, _) in indexed {
strings.push(ArrayExpressionElement::StringLiteral(
ctx.ast.alloc_string_literal(SPAN, ctx.ast.atom(s), None),
));
}
*node = ctx.ast.expression_array(SPAN, strings);
}
}
}
}
Expression::CallExpression(expr) => {
if let Some("__IVI_TPL__") = expr.callee_name() {
if let Some(mut arg0) = expr.arguments.pop() {
if let Some(Expression::CallExpression(call)) = arg0.as_expression_mut() {
if call.arguments.len() > 5 {
if let Some(Argument::ArrayExpression(tpl_strings)) =
call.arguments.pop()
{
let prop_op_codes = &mut call.arguments[2];
update_prop_op_codes(
prop_op_codes.as_expression_mut().unwrap(),
&tpl_strings.elements,
self.strings,
);
}
}
*node = arg0.into_expression();
}
}
}
}
_ => {}
}
}
}
fn update_prop_op_codes<'a>(
expr: &mut Expression,
tpl_strings: &ArenaVec<'a, ArrayExpressionElement<'a>>,
strings: &FxHashMap<String, u8>,
) {
match expr {
// dedupe(op_codes)
Expression::CallExpression(c) => {
if let Some(e) = c.arguments.get_mut(0).and_then(|a| a.as_expression_mut()) {
update_prop_op_codes(e, tpl_strings, strings);
}
}
Expression::ArrayExpression(a) => {
for el in &mut a.elements {
if let Some(Expression::NumericLiteral(op)) = el.as_expression_mut() {
let v = op.value as u32;
let ty = v & prop_op::TYPE_MASK;
if ty != prop_op::SET_NODE && ty != prop_op::COMMON && ty != prop_op::DIRECTIVE
{
let i = v >> prop_op::DATA_SHIFT;
let s = &tpl_strings[i as usize];
if let ArrayExpressionElement::StringLiteral(s) = s {
if let Some(new_index) = strings.get(s.value.as_str()) {
op.value = ((v & ((1 << prop_op::DATA_SHIFT) - 1))
| ((*new_index as u32) << prop_op::DATA_SHIFT))
as f64;
}
}
}
}
}
}
_ => {}
}
}
const STRINGS_UUID: &str = "IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc";
================================================
FILE: crates/ivi_compiler/src/context.rs
================================================
use std::marker::PhantomData;
pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, TraverseCtxState<'a>>;
#[derive(Default)]
pub struct TraverseCtxState<'a> {
data: PhantomData<&'a ()>,
}
================================================
FILE: crates/ivi_compiler/src/import.rs
================================================
use oxc_ast::{
NONE,
ast::{
Expression, ImportDeclarationSpecifier, ImportOrExportKind, ModuleExportName, Statement,
},
};
use oxc_semantic::SymbolFlags;
use oxc_span::SPAN;
use oxc_traverse::BoundIdentifier;
use crate::context::TraverseCtx;
#[derive(Default)]
pub struct ImportSymbols<'a> {
descriptor_id: Option<BoundIdentifier<'a>>, // _T
html_id: Option<BoundIdentifier<'a>>, // _hN
html_el_id: Option<BoundIdentifier<'a>>, // _hE
svg_id: Option<BoundIdentifier<'a>>, // _sN
svg_el_id: Option<BoundIdentifier<'a>>, // _sE
tpl_id: Option<BoundIdentifier<'a>>, // _t
empty_array_id: Option<BoundIdentifier<'a>>, // _t
hoist_id: Option<BoundIdentifier<'a>>,
dedupe_id: Option<BoundIdentifier<'a>>,
}
impl<'a> ImportSymbols<'a> {
pub fn template_descriptor(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.descriptor_id, "_T", ctx)
}
pub fn html_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.html_id, "_hN", ctx)
}
pub fn html_element(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.html_el_id, "_hE", ctx)
}
pub fn svg_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.svg_id, "_sN", ctx)
}
pub fn svg_element(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.svg_el_id, "_sE", ctx)
}
pub fn create_from_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.tpl_id, "_t", ctx)
}
pub fn empty_array(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.empty_array_id, "EMPTY_ARRAY", ctx)
}
pub fn hoist(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.hoist_id, "hoist", ctx)
}
pub fn dedupe(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
get(&mut self.dedupe_id, "dedupe", ctx)
}
pub fn create_import_statements(&self, ctx: &mut TraverseCtx<'a>) -> Vec<Statement<'a>> {
let mut imports = Vec::new();
let mut specifiers = ctx.ast.vec();
if let Some(id) = &self.descriptor_id {
specifiers.push(spec("_T", id, ctx));
}
if let Some(id) = &self.html_id {
specifiers.push(spec("_hN", id, ctx));
}
if let Some(id) = &self.html_el_id {
specifiers.push(spec("_hE", id, ctx));
}
if let Some(id) = &self.svg_id {
specifiers.push(spec("_sN", id, ctx));
}
if let Some(id) = &self.svg_el_id {
specifiers.push(spec("_sE", id, ctx));
}
if let Some(id) = &self.tpl_id {
specifiers.push(spec("_t", id, ctx));
}
if let Some(id) = &self.empty_array_id {
specifiers.push(spec("EMPTY_ARRAY", id, ctx));
}
if !specifiers.is_empty() {
imports.push(Statement::ImportDeclaration(ctx.ast.alloc_import_declaration(
SPAN,
Some(specifiers),
ctx.ast.string_literal(SPAN, ctx.ast.atom("ivi"), None),
None,
NONE,
ImportOrExportKind::Value,
)));
}
let mut specifiers = ctx.ast.vec();
if let Some(id) = &self.hoist_id {
specifiers.push(spec("hoist", id, ctx));
}
if let Some(id) = &self.dedupe_id {
specifiers.push(spec("dedupe", id, ctx));
}
if !specifiers.is_empty() {
imports.push(Statement::ImportDeclaration(ctx.ast.alloc_import_declaration(
SPAN,
Some(specifiers),
ctx.ast.string_literal(SPAN, ctx.ast.atom("oveo"), None),
None,
NONE,
ImportOrExportKind::Value,
)));
}
imports
}
}
fn get<'a>(
cell: &mut Option<BoundIdentifier<'a>>,
name: &'static str,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if let Some(id) = cell {
id.create_read_expression(ctx)
} else {
let uid = ctx.generate_uid_in_root_scope(name, SymbolFlags::ConstVariable);
let read = uid.create_read_expression(ctx);
*cell = Some(uid);
read
}
}
fn spec<'a>(
name: &'static str,
id: &BoundIdentifier<'a>,
ctx: &mut TraverseCtx<'a>,
) -> ImportDeclarationSpecifier<'a> {
ImportDeclarationSpecifier::ImportSpecifier(ctx.ast.alloc_import_specifier(
SPAN,
ModuleExportName::IdentifierName(ctx.ast.identifier_name(SPAN, ctx.ast.atom(name))),
id.create_binding_identifier(ctx),
ImportOrExportKind::Value,
))
}
================================================
FILE: crates/ivi_compiler/src/lib.rs
================================================
use std::path::PathBuf;
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_diagnostics::GraphicalReportHandler;
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use rustc_hash::{FxHashMap, FxHashSet};
mod chunk;
mod context;
mod import;
mod module;
mod oveo;
mod tpl;
#[derive(Default, Debug)]
pub struct CompilerOptions {
pub dedupe_strings: bool,
pub oveo: bool,
}
pub struct CompilerOutput {
pub code: String,
pub map: String,
}
#[derive(Debug, thiserror::Error)]
pub enum CompilerError {
#[error("Invalid module type: {0}")]
ModuleType(String),
#[error("Unable to parse javascript file: {0}")]
SyntaxError(String),
#[error("Unable to parse javascript file: {0}")]
SemanticError(String),
#[error("Invalid template: {0}")]
InvalidTemplate(String),
}
pub fn compile_module(
source_text: &str,
module_type: &str,
options: &CompilerOptions,
strings: &mut FxHashSet<String>,
) -> Result<CompilerOutput, CompilerError> {
let allocator = Allocator::default();
let source_type = match module_type {
"js" => SourceType::mjs(),
"jsx" => SourceType::jsx(),
"ts" => SourceType::ts(),
"tsx" => SourceType::tsx(),
_ => return Err(CompilerError::ModuleType(module_type.to_string())),
};
let ret = Parser::new(&allocator, source_text, source_type).parse();
if let Some(err) = ret.errors.first() {
return Err(CompilerError::SyntaxError(err.to_string()));
}
let mut program = ret.program;
let ret = SemanticBuilder::new().with_excess_capacity(1.0).build(&program);
if let Some(err) = ret.errors.first() {
return Err(CompilerError::SemanticError(err.to_string()));
}
let scoping = ret.semantic.into_scoping();
let mut errors = module::compile_module(&mut program, &allocator, scoping, options, strings);
if let Some(err) = errors.drain(..).next() {
let report_handler = GraphicalReportHandler::new();
let mut s = String::new();
let _ = report_handler
.render_report(&mut s, err.with_source_code(source_text.to_string()).as_ref());
return Err(CompilerError::InvalidTemplate(s));
}
let result = Codegen::new()
.with_options(CodegenOptions {
source_map_path: Some(PathBuf::new()),
..Default::default()
})
.build(&program);
Ok(CompilerOutput {
code: result.code,
map: result.map.map_or_else(String::default, |v| v.to_json_string()),
})
}
pub fn compile_chunk(
source_text: &str,
strings: &FxHashMap<String, u8>,
) -> Result<CompilerOutput, CompilerError> {
let allocator = Allocator::default();
let source_type = SourceType::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
if let Some(err) = ret.errors.first() {
return Err(CompilerError::SyntaxError(err.to_string()));
}
let mut program = ret.program;
let ret = SemanticBuilder::new().with_excess_capacity(0.1).build(&program);
if let Some(err) = ret.errors.first() {
return Err(CompilerError::SemanticError(err.to_string()));
}
let scoping = ret.semantic.into_scoping();
chunk::compile_chunk(&mut program, &allocator, scoping, strings);
let result = Codegen::new()
.with_options(CodegenOptions {
source_map_path: Some(PathBuf::new()),
..Default::default()
})
.build(&program);
Ok(CompilerOutput {
code: result.code,
map: result.map.map_or_else(String::default, |v| v.to_json_string()),
})
}
================================================
FILE: crates/ivi_compiler/src/module/mod.rs
================================================
use std::collections::hash_map;
use oxc_allocator::{Address, Allocator, GetAddress, TakeIn};
use oxc_ast::ast::*;
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::{Scoping, SymbolId};
use oxc_traverse::{Traverse, traverse_mut};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
CompilerOptions,
context::{TraverseCtx, TraverseCtxState},
import::ImportSymbols,
oveo::oveo_intrinsic,
tpl::{TemplateKind, compile_template},
};
pub fn compile_module<'a>(
program: &mut Program<'a>,
allocator: &'a Allocator,
scoping: Scoping,
options: &CompilerOptions,
strings: &mut FxHashSet<String>,
) -> Vec<OxcDiagnostic> {
let mut t = ModuleCompiler::new(options, strings);
traverse_mut(&mut t, allocator, program, scoping, TraverseCtxState::default());
t.errors
}
struct ModuleCompiler<'a, 'ctx> {
options: &'ctx CompilerOptions,
strings: &'ctx mut FxHashSet<String>,
ivi_module: FxHashMap<SymbolId, IviSymbol>,
imports: ImportSymbols<'a>,
statements: Vec<Address>,
templates: FxHashMap<Address, Vec<Statement<'a>>>,
errors: Vec<OxcDiagnostic>,
}
impl<'a, 'ctx> ModuleCompiler<'a, 'ctx> {
pub fn new(options: &'ctx CompilerOptions, strings: &'ctx mut FxHashSet<String>) -> Self {
Self {
options,
strings,
ivi_module: FxHashMap::default(),
imports: ImportSymbols::default(),
statements: Vec::new(),
templates: FxHashMap::default(),
errors: Vec::new(),
}
}
fn resolve(&self, expr: &Expression<'a>, scoping: &Scoping) -> Option<IviSymbol> {
match expr {
Expression::Identifier(id) => {
let r = scoping.get_reference(id.reference_id());
if let Some(symbol_id) = r.symbol_id() {
self.ivi_module.get(&symbol_id).copied()
} else {
None
}
}
Expression::StaticMemberExpression(expr) => {
if let Some(IviSymbol::Module) = self.resolve(&expr.object, scoping) {
match expr.property.name.as_str() {
"component" => Some(IviSymbol::Component),
"html" => Some(IviSymbol::Html),
"svg" => Some(IviSymbol::Svg),
_ => None,
}
} else {
None
}
}
_ => None,
}
}
fn add_template_decl(&mut self, address: Address, decl: Statement<'a>) {
match self.templates.entry(address) {
hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().push(decl);
}
hash_map::Entry::Vacant(entry) => {
entry.insert(vec![decl]);
}
}
}
}
impl<'a> Traverse<'a, TraverseCtxState<'a>> for ModuleCompiler<'a, '_> {
fn enter_statement(&mut self, node: &mut Statement<'a>, _ctx: &mut TraverseCtx<'a>) {
self.statements.push(node.address());
}
fn exit_statement(&mut self, _node: &mut Statement<'a>, _ctx: &mut TraverseCtx<'a>) {
self.statements.pop();
}
fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match node {
Expression::CallExpression(expr) if self.options.oveo => {
// hoist render functions
// component(() => hoist(() => { .. }));
if let Some(IviSymbol::Component) = self.resolve(&expr.callee, ctx.scoping()) {
if let Some(Argument::ArrowFunctionExpression(expr)) = expr.arguments.get_mut(0)
{
if expr.expression {
if let Some(Statement::ExpressionStatement(expr_stmt)) =
&mut expr.body.statements.get_mut(0)
{
expr_stmt.expression = oveo_intrinsic(
expr_stmt.expression.take_in(ctx.ast.allocator),
self.imports.hoist(ctx),
ctx,
);
}
}
}
}
}
Expression::TaggedTemplateExpression(expr) => {
if let Some(ivi) = self.resolve(&expr.tag, ctx.scoping()) {
let kind = match ivi {
IviSymbol::Html => TemplateKind::Html,
IviSymbol::Svg => TemplateKind::Svg,
_ => {
return;
}
};
match compile_template(
&mut expr.quasi,
ctx,
kind,
&mut self.imports,
self.options.oveo,
self.options.dedupe_strings,
) {
Ok(result) => {
for s in result.strings {
self.strings.insert(s);
}
let address = self.statements[0];
for decl in result.decl {
self.add_template_decl(address, decl);
}
*node = result.expr;
}
Err(error) => {
self.errors.push(error);
}
}
}
}
_ => {}
}
}
fn exit_import_declaration(
&mut self,
node: &mut ImportDeclaration<'a>,
_ctx: &mut TraverseCtx<'a>,
) {
// Resolve ivi module
if let Some(specifiers) = &node.specifiers {
let source = &node.source;
if source.value != "ivi" {
return;
}
for spec in specifiers {
match spec {
// import { imported } from "source"
// import { imported as local } from "source"
ImportDeclarationSpecifier::ImportSpecifier(spec) => {
let s = match spec.imported.name().as_str() {
"component" => IviSymbol::Component,
"html" => IviSymbol::Html,
"svg" => IviSymbol::Svg,
_ => {
continue;
}
};
self.ivi_module.insert(spec.local.symbol_id(), s);
}
// import * as local from "source"
ImportDeclarationSpecifier::ImportNamespaceSpecifier(spec) => {
self.ivi_module.insert(spec.local.symbol_id(), IviSymbol::Module);
}
_ => {}
}
}
}
}
fn exit_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
let imports = self.imports.create_import_statements(ctx);
if !imports.is_empty() {
let index = node
.body
.iter()
.position(|stmt| !matches!(stmt, Statement::ImportDeclaration(_)))
.unwrap_or(node.body.len());
node.body.splice(index..index, imports);
}
if !self.templates.is_empty() {
let statements = &mut node.body;
let mut new_statements =
ctx.ast.vec_with_capacity(statements.len() + self.templates.len());
for stmt in statements.drain(..) {
if let Some(s) = self.templates.remove(&stmt.address()) {
new_statements.extend(s.into_iter());
}
new_statements.push(stmt);
}
*statements = new_statements;
}
}
}
#[derive(Clone, Copy)]
enum IviSymbol {
Module,
Component,
Html,
Svg,
}
================================================
FILE: crates/ivi_compiler/src/oveo.rs
================================================
use oxc_ast::{NONE, ast::Expression};
use oxc_span::SPAN;
use crate::context::TraverseCtx;
// annotation(expr)
pub fn oveo_intrinsic<'a>(
expr: Expression<'a>,
callee: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec_from_array([expr.into()]), false)
}
================================================
FILE: crates/ivi_compiler/src/tpl/emit.rs
================================================
use indexmap::IndexSet;
use oxc_allocator::{TakeIn, Vec as ArenaVec};
use oxc_ast::{
AstBuilder,
ast::{Expression, TemplateElement, TemplateElementValue},
};
use oxc_span::SPAN;
use crate::{
context::TraverseCtx,
import::ImportSymbols,
oveo::oveo_intrinsic,
tpl::{
TemplateKind,
opcodes::{child_op, common_prop_type, prop_op, state_op, template_flags},
parser::{
TElement, TNode, TNodeKind, TProperty, TPropertyAttributeValue, TPropertyStyleValue,
},
},
};
pub enum TemplateNode<'a> {
Block(TemplateBlock<'a>),
Text(String),
Expr(usize),
}
pub struct TemplateBlock<'a> {
pub statics: Expression<'a>,
pub flags: u32,
pub props_op_codes: Vec<u32>,
pub child_op_codes: Vec<u32>,
pub state_op_codes: Vec<u32>,
pub strings: IndexSet<String>,
pub expressions: Vec<usize>,
}
pub fn emit_root_element<'a>(
node: &TNode,
kind: TemplateKind,
expressions: &mut ArenaVec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
imports: &mut ImportSymbols<'a>,
oveo: bool,
) -> TemplateNode<'a> {
match &node.kind {
TNodeKind::Element(e) => {
let statics = emit_static_template(e, expressions, &mut ctx.ast);
let expr_map = create_expr_map(e, ctx, expressions, imports, oveo);
let state_op_codes = emit_state_op_codes(e);
let (props_op_codes, strings) = emit_props_op_codes(node, &expr_map);
let child_op_codes = emit_child_op_codes(node, &expr_map);
let state_slots = count_state_slots(&state_op_codes);
let child_slots = count_child_slots(&child_op_codes);
let mut flags = state_slots | (child_slots << template_flags::CHILDREN_SIZE_SHIFT);
if let TemplateKind::Svg = kind {
flags |= template_flags::SVG;
}
TemplateNode::Block(TemplateBlock {
statics,
flags,
props_op_codes,
child_op_codes,
state_op_codes,
strings,
expressions: expr_map.iter().copied().collect(),
})
}
TNodeKind::Text(t) => TemplateNode::Text(t.value.clone()),
TNodeKind::Expr(e) => TemplateNode::Expr(e.index.inner()),
}
}
fn count_state_slots(op_codes: &[u32]) -> u32 {
let mut count = 1;
for op in op_codes {
if *op & state_op::SAVE != 0
|| (*op & state_op::ENTER_OR_REMOVE != 0 && (op >> state_op::OFFSET_SHIFT) == 0)
{
count += 1
}
}
count
}
fn count_child_slots(op_codes: &[u32]) -> u32 {
let mut count = 0;
for op in op_codes {
if op & child_op::TYPE == child_op::CHILD {
count += 1;
}
}
count
}
fn create_expr_map<'a>(
root: &TElement,
ctx: &mut TraverseCtx<'a>,
expressions: &mut ArenaVec<'a, Expression<'a>>,
imports: &mut ImportSymbols<'a>,
oveo: bool,
) -> IndexSet<usize> {
let mut map = IndexSet::default();
_create_expr_map(&mut map, root, ctx, expressions, imports, oveo);
map
}
fn _create_expr_map<'a>(
map: &mut IndexSet<usize>,
node: &TElement,
ctx: &mut TraverseCtx<'a>,
expressions: &mut ArenaVec<'a, Expression<'a>>,
imports: &mut ImportSymbols<'a>,
oveo: bool,
) {
for p in &node.properties {
match p {
TProperty::Attribute(p) => {
if let TPropertyAttributeValue::Expr(v) = &p.value
&& !v.hoist
{
map.insert(v.index.inner());
}
}
TProperty::Value(p) => {
map.insert(p.value.inner());
}
TProperty::DOMValue(p) => {
map.insert(p.value.inner());
}
TProperty::Style(p) => {
if let TPropertyStyleValue::Expr(v) = &p.value {
map.insert(v.inner());
}
}
TProperty::Event(p) => {
let i = p.value.inner();
if oveo {
expressions[i] = oveo_intrinsic(
expressions[i].take_in(ctx.ast.allocator),
imports.hoist(ctx),
ctx,
);
}
map.insert(i);
}
TProperty::Directive(p) => {
map.insert(p.inner());
}
}
}
for c in &node.children {
match &c.kind {
TNodeKind::Element(e) => {
_create_expr_map(map, e, ctx, expressions, imports, oveo);
}
TNodeKind::Expr(e) => {
map.insert(e.index.inner());
}
_ => {}
}
}
}
fn emit_static_template<'a>(
node: &TElement,
template_expressions: &mut ArenaVec<'a, Expression<'a>>,
ast: &mut AstBuilder<'a>,
) -> Expression<'a> {
let mut static_part = String::new();
let mut quasis = ast.vec();
let mut expressions = ast.vec();
// Node doesn't have any children elements/texts or static properties
let mut is_simple_node = true;
_emit_static_template(
&mut is_simple_node,
node,
&mut static_part,
&mut quasis,
&mut expressions,
template_expressions,
ast,
);
if is_simple_node {
ast.expression_string_literal(SPAN, ast.atom(&node.tag), None)
} else {
quasis.push(ast.template_element(
SPAN,
TemplateElementValue { raw: ast.atom(&static_part), cooked: None },
true,
false,
));
ast.expression_template_literal(SPAN, quasis, expressions)
}
}
fn _emit_static_template<'a>(
is_simple_node: &mut bool,
node: &TElement,
static_part: &mut String,
quasis: &mut ArenaVec<'a, TemplateElement<'a>>,
expressions: &mut ArenaVec<'a, Expression<'a>>,
template_expressions: &mut ArenaVec<'a, Expression<'a>>,
ast: &mut AstBuilder<'a>,
) {
static_part.push('<');
static_part.push_str(&node.tag);
let mut style = String::new();
for p in &node.properties {
match p {
TProperty::Attribute(p) => match &p.value {
TPropertyAttributeValue::String(v) => {
if p.key == "style" {
if style.is_empty() {
style = v.clone();
} else {
style.push(';');
style.push_str(v);
}
} else {
*is_simple_node = false;
static_part.push(' ');
static_part.push_str(&p.key);
static_part.push_str("=\"");
static_part.push_str(v);
static_part.push('"');
}
}
TPropertyAttributeValue::Bool => {
*is_simple_node = false;
static_part.push(' ');
static_part.push_str(&p.key);
}
TPropertyAttributeValue::Expr(v) => {
if v.hoist {
*is_simple_node = false;
static_part.push(' ');
static_part.push_str(&p.key);
static_part.push_str("=\"");
quasis.push(ast.template_element(
SPAN,
TemplateElementValue { raw: ast.atom(static_part), cooked: None },
false,
false,
));
expressions
.push(template_expressions[v.index.inner()].take_in(ast.allocator));
static_part.clear();
static_part.push('"');
}
}
},
TProperty::Style(p) => {
if let TPropertyStyleValue::String(v) = &p.value {
if !style.is_empty() {
style.push(';');
}
style.push_str(&p.key);
style.push(':');
style.push_str(v);
}
}
_ => {}
}
}
if !style.is_empty() {
*is_simple_node = false;
static_part.push_str(" style=\"");
static_part.push_str(&style);
static_part.push('"');
}
static_part.push('>');
if node.void {
return;
}
let mut siblings_state = 0;
for c in &node.children {
match &c.kind {
TNodeKind::Element(c) => {
*is_simple_node = false;
_emit_static_template(
is_simple_node,
c,
static_part,
quasis,
expressions,
template_expressions,
ast,
);
siblings_state = 0;
}
TNodeKind::Text(n) => {
*is_simple_node = false;
if (siblings_state & 3) == 3 {
static_part.push_str("<!>");
}
siblings_state = 1;
static_part.push_str(&n.value);
}
TNodeKind::Expr(_) => {
siblings_state |= 2;
}
}
}
static_part.push_str("</");
static_part.push_str(&node.tag);
static_part.push('>');
}
fn emit_props_op_codes(node: &TNode, expr_map: &IndexSet<usize>) -> (Vec<u32>, IndexSet<String>) {
let mut op_codes = Vec::new();
let mut strings = IndexSet::new();
_emit_props_op_codes(&mut op_codes, node, true, &mut strings, expr_map);
(op_codes, strings)
}
fn _emit_props_op_codes(
op_codes: &mut Vec<u32>,
node: &TNode,
is_root: bool,
strings: &mut IndexSet<String>,
expr_map: &IndexSet<usize>,
) {
fn string_index(strings: &mut IndexSet<String>, key: &str) -> u32 {
if let Some(i) = strings.get_index_of(key) {
i as u32
} else {
let (i, _) = strings.insert_full(key.to_string());
i as u32
}
}
if let TNodeKind::Element(e) = &node.kind {
if node.props_exprs > 0 {
if !is_root {
op_codes
.push(prop_op::SET_NODE | ((node.state_index as u32) << prop_op::DATA_SHIFT));
}
for p in &e.properties {
match p {
TProperty::Attribute(p) => {
if let TPropertyAttributeValue::Expr(expr) = &p.value {
if let Some(i) = expr_map.get_index_of(&expr.index.inner()) {
if p.key == "class" {
op_codes.push(
prop_op::COMMON
| (common_prop_type::CLASS_NAME << prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
} else {
op_codes.push(
prop_op::ATTRIBUTE
| (string_index(strings, &p.key)
<< prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
}
}
}
}
TProperty::Value(p) => {
match p.key.as_str() {
"textContent" => {
op_codes.push(
prop_op::COMMON
| (common_prop_type::TEXT_CONTENT << prop_op::DATA_SHIFT)
| ((p.value.inner() as u32) << prop_op::INPUT_SHIFT),
);
}
"innerHTML" => {
op_codes.push(
prop_op::COMMON
| (common_prop_type::INNER_HTML << prop_op::DATA_SHIFT)
| ((p.value.inner() as u32) << prop_op::INPUT_SHIFT),
);
}
_ => {
if let Some(i) = expr_map.get_index_of(&p.value.inner()) {
op_codes.push(
prop_op::PROPERTY
| (string_index(strings, &p.key)
<< prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
}
}
};
}
TProperty::DOMValue(p) => {
match p.key.as_str() {
"textContent" => {
op_codes.push(
prop_op::COMMON
| (common_prop_type::TEXT_CONTENT << prop_op::DATA_SHIFT)
| ((p.value.inner() as u32) << prop_op::INPUT_SHIFT),
);
}
"innerHTML" => {
op_codes.push(
prop_op::COMMON
| (common_prop_type::INNER_HTML << prop_op::DATA_SHIFT)
| ((p.value.inner() as u32) << prop_op::INPUT_SHIFT),
);
}
_ => {
if let Some(i) = expr_map.get_index_of(&p.value.inner()) {
op_codes.push(
prop_op::DIFF_DOM_PROPERTY
| (string_index(strings, &p.key)
<< prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
}
}
};
}
TProperty::Style(p) => {
if let TPropertyStyleValue::Expr(expr_index) = &p.value {
if let Some(i) = expr_map.get_index_of(&expr_index.inner()) {
op_codes.push(
prop_op::STYLE
| (string_index(strings, &p.key) << prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
}
}
}
TProperty::Event(p) => {
if let Some(i) = expr_map.get_index_of(&p.value.inner()) {
op_codes.push(
prop_op::EVENT
| (string_index(strings, &p.key) << prop_op::DATA_SHIFT)
| ((i as u32) << prop_op::INPUT_SHIFT),
);
}
}
TProperty::Directive(p) => {
if let Some(i) = expr_map.get_index_of(&p.inner()) {
op_codes
.push(prop_op::DIRECTIVE | ((i as u32) << prop_op::INPUT_SHIFT));
}
}
}
}
}
for c in &e.children {
_emit_props_op_codes(op_codes, c, false, strings, expr_map);
}
}
}
fn emit_state_op_codes(node: &TElement) -> Vec<u32> {
let mut op_codes = Vec::new();
_emit_state_op_codes(&mut op_codes, node);
op_codes
}
fn _emit_state_op_codes(op_codes: &mut Vec<u32>, node: &TElement) {
mod state_flags {
pub const PREV_TEXT: u32 = 1;
pub const PREV_EXPR: u32 = 1 << 1;
}
let mut state = 0;
'outer: for c in &node.children {
match &c.kind {
TNodeKind::Element(e) => {
let mut op = 0;
if state & state_flags::PREV_EXPR != 0 || c.children_exprs > 0 || c.props_exprs > 0
{
op = state_op::SAVE;
}
let current_op_index = op_codes.len();
op_codes.push(op);
if c.flags & TNode::HAS_EXPRESSIONS != 0 {
_emit_state_op_codes(op_codes, e);
let children_offset = op_codes.len() - (current_op_index + 1);
if children_offset > 0 {
op |= state_op::ENTER_OR_REMOVE
| ((children_offset as u32) << state_op::OFFSET_SHIFT);
op_codes[current_op_index] = op;
}
}
if c.flags & (TNode::HAS_NEXT_EXPRESSION | TNode::HAS_NEXT_DOM_NODE)
!= (TNode::HAS_NEXT_EXPRESSION | TNode::HAS_NEXT_DOM_NODE)
{
if op == 0 {
op_codes.pop();
}
break 'outer;
}
state = 0;
}
TNodeKind::Text(_) => {
if state & (state_flags::PREV_TEXT | state_flags::PREV_EXPR)
== (state_flags::PREV_TEXT | state_flags::PREV_EXPR)
{
op_codes.push(state_op::ENTER_OR_REMOVE);
} else if state & state_flags::PREV_EXPR != 0 {
op_codes.push(state_op::SAVE);
} else if c.flags & (TNode::HAS_NEXT_EXPRESSION | TNode::HAS_NEXT_DOM_NODE)
!= (TNode::HAS_NEXT_EXPRESSION | TNode::HAS_NEXT_DOM_NODE)
{
break 'outer;
} else {
op_codes.push(0);
}
state = state_flags::PREV_TEXT;
}
TNodeKind::Expr(_) => {
state |= state_flags::PREV_EXPR;
}
}
}
}
fn emit_child_op_codes(node: &TNode, expr_map: &IndexSet<usize>) -> Vec<u32> {
let mut op_codes = Vec::new();
_emit_child_op_codes(&mut op_codes, node, true, expr_map);
op_codes
}
fn _emit_child_op_codes(
op_codes: &mut Vec<u32>,
node: &TNode,
is_root: bool,
expr_map: &IndexSet<usize>,
) {
if let TNodeKind::Element(e) = &node.kind {
if node.children_exprs > 0 {
if !is_root {
op_codes.push(
child_op::SET_PARENT | ((node.state_index as u32) << child_op::VALUE_SHIFT),
);
}
let mut prev_state_index = None;
let mut prev_expr = false;
for c in e.children.iter().rev() {
if let TNodeKind::Expr(expr_index) = &c.kind {
if let Some(prev_state_index) = prev_state_index
&& !prev_expr
{
op_codes.push(
child_op::SET_NEXT
| ((prev_state_index as u32) << child_op::VALUE_SHIFT),
);
}
op_codes.push(
child_op::CHILD
| ((expr_map.get_index_of(&expr_index.index.inner()).unwrap() as u32)
<< child_op::VALUE_SHIFT),
);
prev_expr = true;
} else {
prev_expr = false;
prev_state_index = Some(c.state_index);
}
}
}
for c in e.children.iter().rev() {
_emit_child_op_codes(op_codes, c, false, expr_map);
}
}
}
================================================
FILE: crates/ivi_compiler/src/tpl/html.rs
================================================
pub fn is_html_void_element(tag: &str) -> bool {
matches!(
tag,
"audio"
| "video"
| "embed"
| "input"
| "param"
| "source"
| "track"
| "area"
| "base"
| "link"
| "meta"
| "br"
| "col"
| "hr"
| "img"
| "wbr"
)
}
================================================
FILE: crates/ivi_compiler/src/tpl/mod.rs
================================================
use indexmap::IndexSet;
use oxc_allocator::TakeIn;
use oxc_ast::{NONE, ast::*};
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::SymbolFlags;
use oxc_span::SPAN;
use crate::{
context::TraverseCtx, import::ImportSymbols, oveo::oveo_intrinsic, tpl::emit::TemplateNode,
};
mod emit;
mod html;
pub mod opcodes;
mod parser;
pub struct CompiledTemplate<'a> {
pub decl: Vec<Statement<'a>>,
pub expr: Expression<'a>,
pub strings: Vec<String>,
}
#[derive(Clone, Copy)]
pub enum TemplateKind {
Html,
Svg,
}
pub fn compile_template<'a>(
tpl: &mut TemplateLiteral<'a>,
ctx: &mut TraverseCtx<'a>,
kind: TemplateKind,
imports: &mut ImportSymbols<'a>,
oveo: bool,
dedupe_strings: bool,
) -> Result<CompiledTemplate<'a>, OxcDiagnostic> {
let mut decl = Vec::new();
let mut exprs = Vec::new();
let mut strings = Vec::new();
let nodes = parser::parse_template(tpl, ctx.scoping())?;
for n in &nodes {
let e = emit::emit_root_element(n, kind, &mut tpl.expressions, ctx, imports, oveo);
match e {
TemplateNode::Block(t) => {
let uid = ctx.generate_uid_in_root_scope("_TPL_", SymbolFlags::ConstVariable);
// const _TPL_ = __IVI_TPL__(_T(statics, ..opcodes));
let statics = if let Expression::StringLiteral(_) = t.statics {
ctx.ast.expression_call(
SPAN,
match kind {
TemplateKind::Html => imports.html_element(ctx),
TemplateKind::Svg => imports.svg_element(ctx),
},
NONE,
ctx.ast.vec1(t.statics.into()),
false,
)
} else {
ctx.ast.expression_call(
SPAN,
match kind {
TemplateKind::Html => imports.html_template(ctx),
TemplateKind::Svg => imports.svg_template(ctx),
},
NONE,
ctx.ast.vec1(t.statics.into()),
false,
)
};
let statics =
if oveo { oveo_intrinsic(statics, imports.dedupe(ctx), ctx) } else { statics };
let mut arguments = ctx.ast.vec_with_capacity(6);
arguments.push(statics.into());
arguments.push(
ctx.ast
.expression_numeric_literal(SPAN, t.flags as f64, None, NumberBase::Decimal)
.into(),
);
arguments
.push(op_codes_into_expression(&t.props_op_codes, ctx, imports, oveo).into());
arguments
.push(op_codes_into_expression(&t.child_op_codes, ctx, imports, oveo).into());
arguments
.push(op_codes_into_expression(&t.state_op_codes, ctx, imports, oveo).into());
if !t.strings.is_empty() {
arguments.push(strings_into_expression(&t.strings, ctx).into());
strings.extend(t.strings.into_iter());
}
let template_descriptor = ctx.ast.expression_call(
SPAN,
imports.template_descriptor(ctx),
NONE,
arguments,
false,
);
let v = ctx.ast.declaration_variable(
SPAN,
VariableDeclarationKind::Const,
ctx.ast.vec1(ctx.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Const,
ctx.ast.binding_pattern_binding_identifier(SPAN, uid.name),
NONE,
Some(if dedupe_strings {
ctx.ast.expression_call(
SPAN,
ctx.ast.expression_identifier(SPAN, ctx.ast.atom("__IVI_TPL__")),
NONE,
ctx.ast.vec_from_array([template_descriptor.into()]),
false,
)
} else {
template_descriptor
}),
false,
)),
false,
);
decl.push(v.into());
// _t(_TPL_, [expressions])
let call_expressions = if t.expressions.is_empty() {
ctx.ast.vec_from_array([uid.create_read_expression(ctx).into()])
} else {
ctx.ast.vec_from_array([
uid.create_read_expression(ctx).into(),
ctx.ast
.expression_array(
SPAN,
ctx.ast.vec_from_iter(t.expressions.iter().map(|i| {
tpl.expressions[*i].take_in(ctx.ast.allocator).into()
})),
)
.into(),
])
};
let call = ctx.ast.expression_call(
SPAN,
imports.create_from_template(ctx),
NONE,
call_expressions,
false,
);
exprs.push(call);
}
TemplateNode::Text(text) => {
exprs.push(ctx.ast.expression_string_literal(SPAN, ctx.ast.atom(&text), None));
}
TemplateNode::Expr(i) => {
exprs.push(tpl.expressions[i].take_in(ctx.ast.allocator));
}
}
}
let expr = if exprs.len() > 1 {
ctx.ast.expression_array(SPAN, ctx.ast.vec_from_iter(exprs.into_iter().map(|e| e.into())))
} else {
exprs.pop().unwrap()
};
Ok(CompiledTemplate { decl, expr, strings })
}
fn op_codes_into_expression<'a>(
op_codes: &[u32],
ctx: &mut TraverseCtx<'a>,
imports: &mut ImportSymbols<'a>,
oveo: bool,
) -> Expression<'a> {
if op_codes.is_empty() {
imports.empty_array(ctx)
} else {
let expr = ctx.ast.expression_array(
SPAN,
ctx.ast.vec_from_iter(op_codes.iter().map(|o| {
ctx.ast
.expression_numeric_literal(SPAN, *o as f64, None, NumberBase::Decimal)
.into()
})),
);
if oveo { oveo_intrinsic(expr, imports.dedupe(ctx), ctx) } else { expr }
}
}
fn strings_into_expression<'a>(
strings: &IndexSet<String>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
ctx.ast.expression_array(
SPAN,
ctx.ast.vec_from_iter(
strings
.iter()
.map(|s| ctx.ast.expression_string_literal(SPAN, ctx.ast.atom(s), None).into()),
),
)
}
================================================
FILE: crates/ivi_compiler/src/tpl/opcodes.rs
================================================
pub mod template_flags {
pub const CHILDREN_SIZE_SHIFT: u32 = 6;
pub const SVG: u32 = 1 << 12;
}
pub mod state_op {
pub const SAVE: u32 = 0b01;
pub const ENTER_OR_REMOVE: u32 = 0b10;
pub const OFFSET_SHIFT: u32 = 2;
}
pub mod common_prop_type {
pub const CLASS_NAME: u32 = 0;
pub const TEXT_CONTENT: u32 = 1;
pub const INNER_HTML: u32 = 2;
}
pub mod prop_op {
pub const SET_NODE: u32 = 0;
pub const COMMON: u32 = 1;
pub const ATTRIBUTE: u32 = 2;
pub const PROPERTY: u32 = 3;
pub const DIFF_DOM_PROPERTY: u32 = 4;
pub const STYLE: u32 = 5;
pub const EVENT: u32 = 6;
pub const DIRECTIVE: u32 = 7;
pub const TYPE_MASK: u32 = 0b111;
pub const INPUT_SHIFT: u32 = 3;
pub const DATA_SHIFT: u32 = 9;
}
pub mod child_op {
pub const CHILD: u32 = 0b00;
pub const SET_NEXT: u32 = 0b01;
pub const SET_PARENT: u32 = 0b11;
pub const TYPE: u32 = 0b11;
pub const VALUE_SHIFT: u32 = 2;
}
================================================
FILE: crates/ivi_compiler/src/tpl/parser.rs
================================================
use oxc_ast::ast::{Expression, TemplateElement, TemplateLiteral};
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::Scoping;
use crate::tpl::html::is_html_void_element;
#[derive(Clone, Copy)]
pub struct ExprIndex(usize);
impl ExprIndex {
pub fn inner(self) -> usize {
self.0
}
}
pub struct THoistableExpr {
pub index: ExprIndex,
pub hoist: bool,
}
pub struct TNode {
pub kind: TNodeKind,
pub flags: u8,
pub state_index: u16,
pub children_exprs: u16,
pub props_exprs: u16,
}
pub enum TNodeKind {
Element(TElement),
Text(TText),
Expr(TExpr),
}
impl TNode {
pub const HAS_EXPRESSIONS: u8 = 1;
pub const HAS_NEXT_EXPRESSION: u8 = 1 << 1;
pub const HAS_NEXT_DOM_NODE: u8 = 1 << 2;
fn new(kind: TNodeKind) -> Self {
Self { kind, flags: 0, state_index: 0, children_exprs: 0, props_exprs: 0 }
}
fn text(text: &str) -> Self {
Self {
kind: TNodeKind::Text(TText { value: text.to_string() }),
flags: 0,
state_index: 0,
children_exprs: 0,
props_exprs: 0,
}
}
fn space_text() -> Self {
Self::text(" ")
}
}
pub struct TElement {
pub tag: String,
pub properties: Vec<TProperty>,
pub children: Vec<TNode>,
pub void: bool,
}
pub struct TText {
pub value: String,
}
pub struct TExpr {
pub index: ExprIndex,
}
pub enum TProperty {
Attribute(TPropertyAttribute),
Value(TPropertyValue),
DOMValue(TPropertyDOMValue),
Style(TPropertyStyle),
Event(TPropertyEvent),
Directive(ExprIndex),
}
pub struct TPropertyAttribute {
pub key: String,
pub value: TPropertyAttributeValue,
}
pub enum TPropertyAttributeValue {
String(String),
Bool,
Expr(THoistableExpr),
}
pub struct TPropertyValue {
pub key: String,
pub value: ExprIndex,
}
pub struct TPropertyDOMValue {
pub key: String,
pub value: ExprIndex,
}
pub struct TPropertyStyle {
pub key: String,
pub value: TPropertyStyleValue,
}
pub enum TPropertyStyleValue {
String(String),
Expr(ExprIndex),
}
pub struct TPropertyEvent {
pub key: String,
pub value: ExprIndex,
}
pub fn parse_template<'a>(
tpl: &'a TemplateLiteral,
scoping: &'a Scoping,
) -> Result<Vec<TNode>, OxcDiagnostic> {
let mut parser = Parser::new(scoping, &tpl.quasis, &tpl.expressions);
let mut nodes = parser.parse_children_list()?;
for n in &mut nodes {
update_flags(n);
assign_state_slots(n);
}
Ok(nodes)
}
#[derive(Debug, Clone)]
struct Parser<'a> {
scoping: &'a Scoping,
quasis: &'a [TemplateElement<'a>],
expressions: &'a [Expression<'a>],
text: &'a str,
expr_cursor: usize,
}
impl<'a> Parser<'a> {
fn new(
scoping: &'a Scoping,
quasis: &'a [TemplateElement<'a>],
expressions: &'a [Expression<'a>],
) -> Self {
Self {
scoping,
quasis,
expressions,
text: quasis[0].value.cooked.unwrap().as_str(),
expr_cursor: 0,
}
}
fn current_element(&self) -> &TemplateElement<'a> {
&self.quasis[self.expr_cursor]
}
fn is_end(&self) -> bool {
self.text.is_empty() && self.expr_cursor == self.expressions.len()
}
fn peek_char(&self) -> Option<char> {
self.text.chars().next()
}
fn peek_nth_char(&self, n: usize) -> Option<char> {
self.text.chars().nth(n)
}
fn try_consume_char(&mut self, expected: char) -> Option<char> {
if let Some(c) = self.text.chars().next()
&& c == expected
{
self.text = &self.text[expected.len_utf8()..];
Some(expected)
} else {
None
}
}
fn consume_char(&mut self, expected: char) -> Result<(), OxcDiagnostic> {
if let Some(c) = self.text.chars().next()
&& c == expected
{
self.text = &self.text[expected.len_utf8()..];
Ok(())
} else {
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Expected a '{expected}' char: {}", parts.0))
.with_label(self.current_element().span))
}
}
fn advance(&mut self, i: usize) {
self.text = &self.text[i..];
}
fn consume_expr(&mut self) -> Result<usize, OxcDiagnostic> {
if self.text.is_empty() && (self.expr_cursor) < self.expressions.len() {
let i = self.expr_cursor;
self.expr_cursor += 1;
self.text = self.current_element().value.cooked.unwrap().as_str();
Ok(i)
} else {
Err(OxcDiagnostic::error("Expected an expression")
.with_label(self.current_element().span))
}
}
fn consume_whitespace(&mut self) -> WhitespaceState {
let mut state = 0;
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
match c {
' ' | '\t' => {
state |= WhitespaceState::WHITESPACE;
}
'\n' | '\r' => {
state |= WhitespaceState::WHITESPACE | WhitespaceState::CONTAINS_NEWLINE;
}
'\x0b' => {
state |= WhitespaceState::WHITESPACE | WhitespaceState::CONTAINS_VERTICAL_TAB;
}
_ => {
len = i;
break;
}
}
}
if len == 0 {
WhitespaceState(0)
} else {
self.advance(len);
WhitespaceState(state)
}
}
fn parse_tag_name(&mut self) -> Result<String, OxcDiagnostic> {
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
match c {
'0'..='9' | 'a'..='z' | 'A'..='Z' | '_' => {}
'-' => {
if i == 0 {
len = 0;
break;
}
}
_ => {
len = i;
break;
}
}
}
if len > 0 {
let id = self.text[..len].to_string();
self.advance(len);
Ok(id)
} else {
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Invalid tag name: {}", parts.0))
.with_label(self.current_element().span))
}
}
fn parse_attribute_name(&mut self) -> Result<String, OxcDiagnostic> {
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
match c {
'0'..='9' | 'a'..='z' | 'A'..='Z' | '_' => {}
'-' => {
if i == 0 {
len = 0;
break;
}
}
_ => {
len = i;
break;
}
}
}
if len > 0 {
let id = self.text[..len].to_string();
self.advance(len);
Ok(id)
} else {
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Invalid attribute name: {}", parts.0))
.with_label(self.current_element().span))
}
}
fn parse_js_property(&mut self) -> Result<String, OxcDiagnostic> {
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
match c {
'0'..='9' | 'a'..='z' | 'A'..='Z' | '_' | '$' => {}
_ => {
len = i;
break;
}
}
}
if len > 0 {
let id = self.text[..len].to_string();
self.advance(len);
Ok(id)
} else {
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Invalid property name: {}", parts.0))
.with_label(self.current_element().span))
}
}
fn parse_style_name(&mut self) -> Result<String, OxcDiagnostic> {
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
match c {
'0'..='9' | 'a'..='z' | 'A'..='Z' | '-' | '_' => {}
_ => {
len = i;
break;
}
}
}
if len > 0 {
let id = self.text[..len].to_string();
self.advance(len);
Ok(id)
} else {
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Invalid style name: {}", parts.0))
.with_label(self.current_element().span))
}
}
fn parse_children_list(&mut self) -> Result<Vec<TNode>, OxcDiagnostic> {
let mut children = Vec::new();
let mut whitespace_state = self.consume_whitespace();
while !self.is_end() {
if let Some(c) = self.peek_char() {
if c == '<' {
if whitespace_state.should_insert_whitespace() {
children.push(TNode::space_text());
}
match self.peek_nth_char(1) {
Some('/') => {
break;
}
Some('!') => {
self.parse_comment();
}
_ => {
children.push(TNode::new(TNodeKind::Element(self.parse_element()?)));
}
}
} else {
children.push(TNode::new(TNodeKind::Text(self.parse_text(whitespace_state)?)));
}
} else {
let index = self.consume_expr()?;
if whitespace_state.should_insert_whitespace() {
children.push(TNode::space_text());
}
children.push(TNode::new(TNodeKind::Expr(TExpr { index: ExprIndex(index) })));
}
whitespace_state = self.consume_whitespace();
}
Ok(children)
}
fn parse_comment(&mut self) {
let mut len = self.text.len();
for (i, c) in self.text.char_indices() {
if c == '>' {
len = i + 1;
break;
}
}
if len > 0 {
self.advance(len);
}
}
fn parse_element(&mut self) -> Result<TElement, OxcDiagnostic> {
self.advance(1);
let tag = self.parse_tag_name()?;
self.consume_whitespace();
let properties = self.parse_attributes()?;
let mut children = Vec::new();
let mut void = false;
if self.try_consume_char('/').is_some() {
self.consume_char('>')?;
} else {
self.consume_char('>')?;
if is_html_void_element(&tag) {
void = true;
} else {
children = self.parse_children_list()?;
self.consume_char('<')?;
self.consume_char('/')?;
self.advance(tag.len());
self.consume_whitespace();
self.consume_char('>')?;
}
}
Ok(TElement { tag, properties, children, void })
}
fn parse_text(&mut self, whitespace_state: WhitespaceState) -> Result<TText, OxcDiagnostic> {
let mut text = String::new();
let mut len = self.text.len();
let mut whitespace_state = whitespace_state;
for (i, c) in self.text.char_indices() {
match c {
'<' => {
len = i;
break;
}
' ' | '\t' => {
whitespace_state.0 |= WhitespaceState::WHITESPACE;
continue;
}
'\n' | '\r' => {
whitespace_state.0 |= WhitespaceState::CONTAINS_NEWLINE;
continue;
}
'\x0b' => {
whitespace_state.0 |= WhitespaceState::CONTAINS_VERTICAL_TAB;
continue;
}
_ => {}
}
if whitespace_state.0 & WhitespaceState::WHITESPACE != 0
&& (whitespace_state.0
& (WhitespaceState::TEXT_CONTENT | WhitespaceState::CONTAINS_VERTICAL_TAB)
!= 0
|| whitespace_state.0 & WhitespaceState::CONTAINS_NEWLINE == 0)
{
text.push(' ');
}
whitespace_state.0 = WhitespaceState::TEXT_CONTENT;
text.push(c);
}
if whitespace_state.should_insert_whitespace() {
text.push(' ');
}
self.advance(len);
if text.len() <= (1 << 16) {
Ok(TText { value: text })
} else {
// Text nodes are splitted into two nodes when they exceed their length limit (64k).
// https://github.com/chromium/chromium/blob/91159249db3086f17b28b7a060f55ec0345c24c7/third_party/blink/renderer/core/dom/text.h#L42
Err(OxcDiagnostic::error("Text is too long (>64Kb)")
.with_label(self.current_element().span))
}
}
fn parse_attributes(&mut self) -> Result<Vec<TProperty>, OxcDiagnostic> {
let mut properties = Vec::new();
while !self.is_end() {
if let Some(c) = self.peek_char() {
match c {
'/' | '>' => {
return Ok(properties);
}
'.' => {
self.advance(1);
let key = self.parse_js_property()?;
self.consume_char('=')?;
let expr_index = self.consume_expr()?;
properties.push(TProperty::Value(TPropertyValue {
key,
value: ExprIndex(expr_index),
}));
}
'*' => {
self.advance(1);
let key = self.parse_js_property()?;
self.consume_char('=')?;
let expr_index = self.consume_expr()?;
properties.push(TProperty::DOMValue(TPropertyDOMValue {
key,
value: ExprIndex(expr_index),
}));
}
'@' => {
self.advance(1);
let key = self.parse_js_property()?;
self.consume_char('=')?;
let expr_index = self.consume_expr()?;
properties.push(TProperty::Event(TPropertyEvent {
key,
value: ExprIndex(expr_index),
}));
}
'~' => {
self.advance(1);
let key = self.parse_style_name()?;
self.consume_char('=')?;
let value = if self.peek_char().is_some() {
TPropertyStyleValue::String(self.parse_attribute_string()?)
} else {
TPropertyStyleValue::Expr(ExprIndex(self.consume_expr()?))
};
properties.push(TProperty::Style(TPropertyStyle { key, value }));
}
_ => {
let key = self.parse_attribute_name()?;
let mut hoist = false;
let value;
if self.try_consume_char('=').is_some() {
if let Some('"') = self.peek_char() {
value =
TPropertyAttributeValue::String(self.parse_attribute_string()?);
} else {
let expr_index = self.consume_expr()?;
if key == "class" {
let expr = &self.expressions[expr_index];
// Hoist symbols from the root scope
if is_hoistable_expr(expr, self.scoping) {
hoist = true;
}
}
value = TPropertyAttributeValue::Expr(THoistableExpr {
index: ExprIndex(expr_index),
hoist,
});
}
} else {
value = TPropertyAttributeValue::Bool;
}
properties.push(TProperty::Attribute(TPropertyAttribute { key, value }));
}
}
} else {
properties.push(TProperty::Directive(ExprIndex(self.consume_expr()?)))
}
self.consume_whitespace();
}
let parts = self.text.split_at(self.text.len().min(10));
Err(OxcDiagnostic::error(format!("Expected a '>' char: {}", parts.0))
.with_label(self.current_element().span))
}
fn parse_attribute_string(&mut self) -> Result<String, OxcDiagnostic> {
let delim;
let mut chars = self.text.char_indices();
if let Some((_, c)) = chars.next() {
match c {
'\'' | '"' => {
delim = c;
}
_ => {
return Err(OxcDiagnostic::error(
"Invalid string value, it should start with '\"' char.",
)
.with_label(self.current_element().span));
}
}
for (i, c) in chars {
if c == delim {
let v = self.text[1..i].to_string();
self.advance(i + 1);
return Ok(v);
}
}
Err(OxcDiagnostic::error("Invalid string value, it should end with '\"' char.")
.with_label(self.current_element().span))
} else {
Err(OxcDiagnostic::error("Invalid string value")
.with_label(self.current_element().span))
}
}
}
#[derive(Clone, Copy)]
struct WhitespaceState(u8);
impl WhitespaceState {
const WHITESPACE: u8 = 1;
const CONTAINS_NEWLINE: u8 = 1 << 1;
const CONTAINS_VERTICAL_TAB: u8 = 1 << 2;
const TEXT_CONTENT: u8 = 1 << 3;
fn should_insert_whitespace(self) -> bool {
self.0 & WhitespaceState::WHITESPACE != 0
&& (self.0 & WhitespaceState::CONTAINS_NEWLINE == 0
|| self.0 & WhitespaceState::CONTAINS_VERTICAL_TAB != 0)
}
}
// RTL pass
fn update_flags(node: &mut TNode) {
_update_flags(node, 0);
}
fn _update_flags(node: &mut TNode, flags: u8) -> u8 {
let mut flags = flags;
if let TNodeKind::Element(e) = &mut node.kind {
let mut props_exprs = 0;
for p in &e.properties {
match p {
TProperty::Attribute(p) => {
if let TPropertyAttributeValue::Expr(e) = &p.value
&& !e.hoist
{
props_exprs += 1;
break;
}
}
TProperty::Style(p) => {
if let TPropertyStyleValue::Expr(_) = &p.value {
props_exprs += 1;
break;
}
}
TProperty::Value(_)
| TProperty::DOMValue(_)
| TProperty::Event(_)
| TProperty::Directive(_) => {
props_exprs += 1;
break;
}
}
}
let mut siblings_flags = 0;
let mut children_exprs = 0;
for c in e.children.iter_mut().rev() {
match &mut c.kind {
TNodeKind::Element(_) => {
let f = _update_flags(c, siblings_flags);
if f & TNode::HAS_EXPRESSIONS != 0 {
flags |= TNode::HAS_EXPRESSIONS;
siblings_flags |= TNode::HAS_NEXT_EXPRESSION | TNode::HAS_NEXT_DOM_NODE;
} else {
siblings_flags |= TNode::HAS_NEXT_DOM_NODE;
}
}
TNodeKind::Text(_) => {
c.flags = siblings_flags;
siblings_flags |= TNode::HAS_NEXT_DOM_NODE;
}
TNodeKind::Expr(_) => {
siblings_flags |= TNode::HAS_NEXT_EXPRESSION;
c.flags = siblings_flags;
children_exprs += 1;
}
}
}
if props_exprs > 0 || children_exprs > 0 {
flags |= TNode::HAS_EXPRESSIONS;
}
node.flags = flags;
node.props_exprs = props_exprs;
node.children_exprs = children_exprs;
}
flags
}
fn assign_state_slots(node: &mut TNode) {
_assign_state_slots(node, 1);
}
fn _assign_state_slots(node: &mut TNode, mut state_index: u16) -> u16 {
if let TNodeKind::Element(e) = &mut node.kind {
let mut prev_expr = false;
for c in &mut e.children {
match c.kind {
TNodeKind::Element(_) => {
if prev_expr {
prev_expr = false;
c.state_index = state_index;
state_index += 1;
} else if c.props_exprs > 0 || c.children_exprs > 0 {
c.state_index = state_index;
state_index += 1;
}
state_index = _assign_state_slots(c, state_index);
}
TNodeKind::Text(_) => {
if prev_expr {
prev_expr = false;
c.state_index = state_index;
state_index += 1;
}
}
TNodeKind::Expr(_) => {
prev_expr = true;
}
}
}
}
state_index
}
fn is_hoistable_expr<'a>(expr: &Expression<'a>, scoping: &Scoping) -> bool {
match expr {
Expression::Identifier(id) => {
let r = scoping.get_reference(id.reference_id());
if let Some(symbol_id) = r.symbol_id()
&& scoping.symbol_scope_id(symbol_id) == scoping.root_scope_id()
{
return true;
}
}
Expression::StaticMemberExpression(expr) => {
return is_hoistable_expr(&expr.object, scoping);
}
_ => {}
}
false
}
================================================
FILE: docs/internals/dynamic-lists.md
================================================
# Dynamic Lists
Just some reminders:
## Lazy rendering
It may seem like a good idea to render dynamic lists lazily and even avoid
storing keys in memory, but it may lead to some subtle bugs with mutable
entries.
## Dynamic Lists with Immutable Entries
It is possible to create stateless node for immutable lists by storing
entries, key function and render function in a stateless node. On initial
mount we will need to invoke render function for each entry and keys can be
completely ignored. And when dynamic list is updated, we can lazily recover
old keys from previous entries.
Don't think that it is worth it in most real-world scenarios, and we can
always just memoize stateless node with dynamic list in a component state.
================================================
FILE: docs/internals/misc.md
================================================
# Misc
## Type Casting
The current code base contains a lot of type casting. In idiomatic typescript
it should be implemented with type guards, and if javascript toolchains
supported inlining in some reliable way instead of relying on their
heuristics, the code could be way much cleaner with type guard functions.
## Root Entry Functions
Entry functions (update, dirtyCheck, unmount, etc) should save and restore
render context to avoid edge cases when they invoked synchronously in a
different root context.
================================================
FILE: docs/internals/perf.md
================================================
# Perf
## `var` vs `let`
In some places variables are declared with `var` instead of `let`, it is a
micro optimization that propably won't have any significant impact on
performance, especially when the JIT kicks in.
```js
function __var(i) {
var j = i;
return function _var() {
return j;
}
}
function __let(i) {
let j = i;
return function _let() {
return j;
};
}
```
In the example above, `_var` function will have the following bytecode in V8:
```txt
LdaImmutableCurrentContextSlot [2]
Return
```
And `_let` function:
```txt
LdaImmutableCurrentContextSlot [2]
ThrowReferenceErrorIfHole[0];
Return
```
## `if (a === true) {}` vs `if (a) {}`
In a lot of places there are explicit strict equality checks to avoid
`toBool()` coercion. Sometimes we can avoid explicit checks when JIT compiler
is going to inline functions and will be able to eliminate `toBool()`
coercion. For example, `_isArray()` calls doesn't use strict equality checks.
`(a === true)`
```txt
0x738b024 24 488b5518 REX.W movq rdx, [rbp + 0x18];
0x738b028 28 493995b0000000 REX.W cmpq[r13 + 0xb0](root(true_value)), rdx;
0x738b02f 2f 0f8424000000 jz 0x738b059 < +0x59 >
0x738b035 35 48b80000000014000000 REX.W movq rax, 0x1400000000;
0x738b03f 3f 488b4de8 REX.W movq rcx, [rbp - 0x18];
0x738b043 43 488be5 REX.W movq rsp, rbp;
0x738b046 46 5d pop rbp;
0x738b047 47 4883f902 REX.W cmpq rcx, 0x2;
0x738b04b 4b 7f03 jg 0x738b050 < +0x50 >
0x738b04d 4d c21000 ret 0x10;
`(a)`, inlines `toBool()`
0x618b024 24 488b5518 REX.W movq rdx, [rbp + 0x18];
0x618b028 28 f6c201 testb rdx, 0x1;
0x618b02b 2b 0f84a7000000 jz 0x618b0d8 < +0xd8 >
0x618b031 31 493995b8000000 REX.W cmpq[r13 + 0xb8](root(false_value)), rdx;
0x618b038 38 0f8459000000 jz 0x618b097 < +0x97 >
0x618b03e 3e 493995c0000000 REX.W cmpq[r13 + 0xc0](root(empty_string)), rdx;
0x618b045 45 0f844c000000 jz 0x618b097 < +0x97 >
0x618b04b 4b 488b4aff REX.W movq rcx, [rdx - 0x1];
0x618b04f 4f f6410d10 testb[rcx + 0xd], 0x10;
0x618b053 53 0f853e000000 jnz 0x618b097 < +0x97 >
0x618b059 59 49398d38010000 REX.W cmpq[r13 + 0x138](root(heap_number_map)), rcx;
0x618b060 60 0f8484000000 jz 0x618b0ea < +0xea >
0x618b066 66 49398db8010000 REX.W cmpq[r13 + 0x1b8](root(bigint_map)), rcx;
0x618b06d 6d 0f846c000000 jz 0x618b0df < +0xdf >
0x618b073 73 48b8000000000a000000 REX.W movq rax, 0xa00000000;
0x618b07d 7d 488b4de8 REX.W movq rcx, [rbp - 0x18];
0x618b081 81 488be5 REX.W movq rsp, rbp;
0x618b084 84 5d pop rbp;
0x618b085 85 4883f902 REX.W cmpq rcx, 0x2;
0x618b089 89 7f03 jg 0x618b08e < +0x8e >
0x618b08b 8b c21000 ret 0x10;
```
## Polymorphic Call-Sites
It is not always a good idea to optimize for monomorphic call-sites. If there
is a low degree polymorphism, it can be better to use different shapes.
In some cases compiler can optimize several polymorphic call-sites and
perform just one shape check. To understand how to reorganize code, so that
compiler could better optimize it, it is necessary to understand aliasing:
https://en.wikipedia.org/wiki/Aliasing_(computing)
## Additional Resources
- https://v8.dev/docs/turbofan
- https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html
- https://benediktmeurer.de/2017/06/29/javascript-optimization-patterns-part2/
- https://github.com/thlorenz/v8-perf/blob/master/language-features.md
================================================
FILE: docs/internals/template-compiler.md
================================================
# Template Compiler
## Markers
Comment markers `<!>` should be inserted into template to delineate a slot
position for an expression when it is inserted between two text nodes. E.g.
<div>prefix${expr}suffix</div>
Should produce the following HTML template:
<div>prefix<!>suffix</div>
## OpCodes
When properties `PropOpCode` are starting to update:
- currentNode is assigned to template root node
- properties are updated starting from the root node
Because of this invariants we can avoid adding SetNode opCode for root nodes.
When children `ChildOpCode` are starting to update:
- parentNode is assigned to root node
- nextNode is assigned to null
Because of this invariants we can avoid generating state and children opcodes
in cases like:
```html
<div>
<span>${expr}</span>
</div>
```
Here we don't need to traverse DOM tree when mounting, because we already know
its root node and dynamic child is positioned in the end. So, `stateOpCodes`
should be empty, and `childOpCodes` should have only `UpdateChild` opCode.
## Ideas
### SMI Arrays vs Strings for OpCodes
It is possible to encode OpCodes as strings and get deduplication via string
interning. OpCodes encoded as strings should also have smaller size and faster
to parse. But overall it is probably not worth it.
### Optimize PropOpCode encoding to reduce code size
In majority of templates, the size of expressions array, state array, etc
is lower than 16. So instead of storing indexes in a contiguous set of bits,
we can store lowest bits in the first 4 bits and the rest in the highest
bits, e.g.:
```txt
PropOpCode {
type:3,
expr_lo:4,
data_lo:4,
expr_hi:6,
data_hi:..,
}
expr = ((op >> 3) & Mask4) | ((op >> 11) & Mask6);
data = ((op >> 7) & Mask4) | (op >> 17);
```
And since we are deduplicating all data and storing it in a shared array,
we can sort it by the number of occurences in templates, so that indices for
the 16 most common keys will be able to fit into 4 bits.
### Store string length in StateOpCodes to separate static text nodes
It is possible to avoid injecting `<!>` comment nodes to separate static
strings by replacing remove opCode with split opCode that is going to store
string length.
================================================
FILE: docs/misc/migrating-from-react.md
================================================
# Migrating From React
This document shows how to rewrite examples from the https://react.dev/learn/
documentation with ivi API.
## Table of Contents
- [Describing the UI](#describing-the-ui)
- [Your first component](#your-first-component)
- [Importing and exporting component](#importing-and-exporting-components)
- [Writing markup with JSX](#writing-markup-with-jsx)
- [Javascript in JSX with curly braces](#javascript-in-jsx-with-curly-braces)
- [Passing props to components](#passing-props-to-a-component)
- [Conditional rendering](#conditional-rendering)
- [Rendering lists](#rendering-lists)
- [Keeping components pure](#keeping-components-pure)
- [Adding interactivity](#adding-interactivity)
- [Responding to events](#responding-to-events)
- [State: a component's memory](#state-a-components-memory)
- [Updating objects in state](#updating-objects-in-state)
- [Managing state](#managing-state)
- [Reacting to input with state](#reacting-to-input-with-state)
- [Choosing the state structure](#choosing-the-state-structure)
- [Preserving and resetting state](#preserving-and-resetting-state)
- [Extracting state logic into a reducer](#extracting-state-logic-into-a-reducer)
- [Passing data deeply with context](#passing-data-deeply-with-context)
- [Escape hatches](#escape-hatches)
- [Referencing values with refs](#referencing-values-with-refs)
- [Manipulating the DOM with refs](#manipulating-the-dom-with-refs)
- [Synchronizing with Effects](#synchronizing-with-effects)
- [Removing Effect dependencies](#removing-effect-dependencies)
- [Reusing logic with custom hooks](#reusing-logic-with-custom-hooks)
## [Describing the UI](https://react.dev/learn/describing-the-ui)
### [Your first component](https://react.dev/learn/describing-the-ui#your-first-component)
React:
```js
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
```
ivi:
```js
import { html } from 'ivi';
const Profile = () => html`
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
`;
const Gallery = () => html`
<section>
<h1>Amazing scientists</h1>
${Profile()}
${Profile()}
${Profile()}
</section>
`;
export default Gallery;
```
### [Importing and exporting components](https://react.dev/learn/describing-the-ui#importing-and-exporting-components)
React:
```js
import Profile from './Profile.js';
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
```
ivi:
```js
import { html } from 'ivi';
import Profile from './Profile.js';
const Gallery = () => html`
section
h1 'Amazing scientists'
${Profile()}
${Profile()}
${Profile()}
`;
export default Gallery;
```
### [Writing markup with JSX](https://react.dev/learn/describing-the-ui#writing-markup-with-jsx)
React:
```js
export default function TodoList() {
return (
<>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve spectrum technology</li>
</ul>
</>
);
}
```
ivi:
```js
import { html } from 'ivi';
const TodoList = () => html`
<h1>Hedy Lamarr's Todos</h1>
<img
class"photo"
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve spectrum technology</li>
</ul>
`;
export default TodoList;
```
### [JavaScript in JSX with curly braces](https://react.dev/learn/describing-the-ui#javascript-in-jsx-with-curly-braces)
React:
```js
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};
export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}
```
ivi:
```js
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};
const TodoList = () => html`
<div
~background-color=${person.theme.backgroundColor}
~color=${person.theme.color}
>
<h1>${person.name}'s Todos</h1>
<img
class="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
`;
export default TodoList;
```
### [Passing props to a component](https://react.dev/learn/describing-the-ui#passing-props-to-a-component)
React:
```jsx
import { getImageUrl } from './utils.js'
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
```
ivi:
```js
import { getImageUrl } from './utils.js'
const Profile = () => (
Card(
Avatar({
size: 100,
person: {
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
},
}),
)
);
export default Profile;
```
### [Conditional Rendering](https://react.dev/learn/describing-the-ui#conditional-rendering)
React:
```js
function Item({ name, isPacked }) {
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride's Packing List</h1>
<ul>
<Item
isPacked={true}
name="Space suit"
/>
<Item
isPacked={true}
name="Helmet with a golden leaf"
/>
<Item
isPacked={false}
name="Photo of Tam"
/>
</ul>
</section>
);
}
```
ivi:
```js
import { html } from "ivi";
const Item = ({ name, isPacked }) => html`
<li class="item">
${name} ${isPacked && '✔'}
</li>
`;
const PackingList = () => html`
<section>
<h1>Sally Ride's Packing List</h1>
<ul>
${Item({ isPacked: true, name: 'Space suit' })}
${Item({ isPacked: true, name: 'Helmet with a golden leaf' })}
${Item({ isPacked: false, name: 'Photo of Tam' })}
</ul>
</section>
`;
export default PackingList;
```
### [Rendering lists](https://react.dev/learn/describing-the-ui#rendering-lists)
React:
```js
import { people } from './data.js';
import { getImageUrl } from './utils.js';
export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
return (
<article>
<h1>Scientists</h1>
<ul>{listItems}</ul>
</article>
);
}
```
ivi:
```js
import { List } from 'ivi';
import { html } from 'ivi';
import { people } from './data.js';
import { getImageUrl } from './utils.js';
const ScientistsList = () => html`
<article>
<h1>Scientists</h1>
<ul>
${List(people, (person) => person.id, (person) => html`
<li>
<img
src=${getImageUrl(person)}
alt=${person.name}
/>
<p>
<b>${person.name}:</b>
\v ${person.profession}
\v known for ${person.accomplishment}
</p>
</li>
`)}
</ul>
</article>
`;
export default ScientistsList;
```
### [Keeping components pure](https://react.dev/learn/describing-the-ui#keeping-components-pure)
React:
```js
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}
export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
```
ivi:
```js
import { html } from 'ivi';
const Cup = ({ guest }) => html`
<h2>Tea cup for guest #${guest}</h2>
`;
const TeaSet = () => [
Cup({ guest: 1 }),
Cup({ guest: 2 }),
Cup({ guest: 3 }),
];
export default TeaSet;
```
## [Adding Interactivity](https://react.dev/learn/adding-interactivity)
### [Responding to events](https://react.dev/learn/adding-interactivity#responding-to-events)
React:
```js
export default function App() {
return (
<Toolbar
onPlayMovie={() => alert('Playing!')}
onUploadImage={() => alert('Uploading!')}
/>
);
}
function Toolbar({ onPlayMovie, onUploadImage }) {
return (
<div>
<Button onClick={onPlayMovie}>
Play Movie
</Button>
<Button onClick={onUploadImage}>
Upload Image
</Button>
</div>
);
}
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
const App = component((c) => {
const onPlayMovie = () => alert('Playing!');
const onUploadImage = () => alert('Uploading!');
return () => ToolBar({ onPlayMovie, onUploadImage });
});
export default App;
const Toolbar = ({ onPlayMovie, onUploadImage }) => html`
<div>
${Button({ onClick: onPlayMovie, children: 'Play Movie' })}
${Button({ onClick: onUploadImage, children: 'Upload Image' })}
</div>
`;
const Button = ({ onClick, children }) => html`
<button @click=${onClick}>
${children}
</button>
`;
```
### [State: a component’s memory](https://react.dev/learn/adding-interactivity#state-a-components-memory)
React:
```js
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>{sculpture.description}</p>}
<img
src={sculpture.url}
alt={sculpture.alt}
/>
</>
);
}
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
import { sculptureList } from './data.js';
const Gallery = () => {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
return () => {
const index = index();
const showMore = showMore();
const sculpture = sculptureList[index];
return html`
<button @click=${handleNextClick}>Next</button>
<h2>
<i>${sculpture.name}</i>
\v by ${sculpture.artist}
</h2>
<h3>(${index + 1} of ${sculptureList.length}</h3>
<button @click=${handleMoreClick}>
${showMore ? 'Hide' : 'Show'} details
</button>
${showMore && html`<p>${sculpture.description}</p>`}
<img
src=${sculpture.url}
alt=${sculpture.alt}
/>
`;
};
}
export default Gallery;
```
### [State as a snapshot](https://react.dev/learn/adding-interactivity#state-as-a-snapshot)
React:
```js
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
```
ivi:
Like regular javascript variables, ivi state is updated immediately.
```js
console.log(count()); // 0
setCount(count() + 1); // Request a re-render with 1
console.log(count()); // 1
```
### [Updating objects in state](https://react.dev/learn/adding-interactivity#updating-objects-in-state)
React:
```js
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: e.target.value
}
});
}
function handleImageChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
image: e.target.value
}
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
const Form = component((c) => {
const [person, setPerson] = useState(c, {
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
setPerson({
...person(),
name: e.target.value
});
}
function handleTitleChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
city: e.target.value
}
});
}
function handleImageChange(e) {
const p = person();
setPerson({
...p,
artwork: {
...p.artwork,
image: e.target.value
}
});
}
return () => {
const p = person();
return html`
<label>
Name:
<input
*value=${p.name}
@input=${handleNameChange}
/>
</label>
<label>
Title:
<input
*value=${p.artwork.title}
@input=${handleTitleChange}
/>
</label>
<label>
City:
<input
*value=${p.artwork.city}
@input=${handleCityChange}
/>
</label>
<label>
Image:
<input
*value=${p.artwork.image}
@input=${handleImageChange}
/>
</label>
<p>
<i>${p.artwork.title}</i>
\v by ${p.name}
<br>
(located in ${p.artwork.city} )
</p>
<img
src=${p.artwork.image}
alt=${p.artwork.title}
/>
`;
};
});
export default Form;
```
## [Managing State](https://react.dev/learn/managing-state)
### [Reacting to input with state](https://react.dev/learn/managing-state#reacting-to-input-with-state)
React:
```js
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if (status === 'success') {
return <h1>That's right!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
Submit
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
</>
);
}
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
const Form = component((c) => {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
async function handleSubmit(e) {
e.preventDefault();
const answer = answer();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return () => {
if (status() === 'success') {
return html`h1 'That's right!`;
}
return html`
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form @submit=${handleSubmit}>
<textarea
*value=${answer()}
@input=${handleTextareaChange}
disabled=${status() === 'submitting'}
/>
<br>
<button
disabled=${
answer().length === 0 ||
status() === 'submitting'
}
Submit
/>
${error() && html`<p class="Error">${error().message}</p>`}
</form>
`;
};
});
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}
```
### [Choosing the state structure](https://react.dev/learn/managing-state#choosing-the-state-structure)
React:
```js
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
const Form = component((c) => {
const [firstName, setFirstName] = useState(c, '');
const [lastName, setLastName] = useState(c, '');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return () => {
const fullName = firstName + ' ' + lastName;
return html`
<h2>Let's check you in</h2>
<label>
First Name: \v
<input *value=${firstName()} @input=${handleFirstNameChange} />
</label>
<label>
Last name: \v
<input *value=${lastName()} @input=${handleLastNameChange} />
<p>
Your ticket will be issued to: \v
<b>${fullName}</b>
</p>
`;
};
});
```
### [Preserving and resetting state](https://react.dev/learn/managing-state#preserving-and-resetting-state)
React:
```js
import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
export default function Messenger() {
const [to, setTo] = useState(contacts[0]);
return (
<div>
<ContactList
contacts={contacts}
selectedContact={to}
onSelect={contact => setTo(contact)}
/>
<Chat key={to.email} contact={to} />
</div>
)
}
const contacts = [
{ name: 'Taylor', email: 'taylor@mail.com' },
{ name: 'Alice', email: 'alice@mail.com' },
{ name: 'Bob', email: 'bob@mail.com' }
];
```
ivi:
```js
import { component, useState } from 'ivi';
import { html } from 'ivi';
import { Identity } from '@ivi/identity';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
const Messenger = component((c) => {
const [_to, setTo] = useState(contacts[0]);
const onSelect = (contact) => { setTo(contact); };
return () => {
const to = _to();
return html`
<div>
${ContactList({
contacts,
selectedContact: to,
onSelect,
})}
${Identity({ key: to.email, contact: to })}
</div>
`;
}
});
export default Messenger;
const contacts = [
{ name: 'Taylor', email: 'taylor@mail.com' },
{ name: 'Alice', email: 'alice@mail.com' },
{ name: 'Bob', email: 'bob@mail.com' }
];
```
### [Extracting state logic into a reducer](https://react.dev/learn/managing-state#extracting-state-logic-into-a-reducer)
React:
```js
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return (
<>
<h1>Prague itinerary</h1>
<AddTask
onAddTask={handleAddTask}
/>
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false }
];
```
ivi:
```js
import { component, useReducer } from 'ivi';
import { html } from 'ivi';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
const TaskApp = component((c) => {
const [tasks, dispatch] = useReducer(c, initialTasks, tasksReducer);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return () => html`
<h1>Prague itinerary</h1>
${AddTask({ onAddTask })}
${TaskList({
tasks,
onChangeTask: handleChangeTask,
onDeleteTask: handleDeleteTask,
})}
`;
});
export default TaskApp;
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
let nextId = 3;
const initialTasks = [
{ id: 0, text: 'Visit Kafka Museum', done: true },
{ id: 1, text: 'Watch a puppet show', done: false },
{ id: 2, text: 'Lennon Wall pic', done: false }
];
```
### [Passing data deeply with context](https://react.dev/learn/managing-state#passing-data-deeply-with-context)
React:
```js
import { createContext, useContext } from 'react';
const LevelContext = createContext(0);
function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}
function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
function Page() {
return (
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
```
ivi:
```js
import { component, context } from 'ivi';
import { html } from 'ivi';
const [getLevelContext, LevelContext] = context();
const Heading = (c) => {
const level = getLevelContext(c);
return (children) => {
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return html`<h1>${children}</h1>`;
case 2:
return html`<h2>${children}</h2>`;
case 3:
return html`<h3>${children}</h3>`;
case 4:
return html`<h4>${children}</h4>`;
case 5:
return html`<h5>${children}</h5>`;
case 6:
return html`<h6>${children}</h6>`;
default:
throw Error('Unknown level: ' + level);
}
}
}
const Section = component(c) => {
const level = useContext(LevelContext);
return (children) => html`
<section class="section">
${LevelContext(level + 1, children)}
</section>
`;
}
const Page = () => (
Section([
Heading("Title"),
Section([
Heading("Heading"),
Heading("Heading"),
Heading("Heading"),
]),
Section([
Heading("Sub-heading"),
Heading("Sub-heading"),
Heading("Sub-heading"),
Section([
Heading("Sub-sub-heading"),
Heading("Sub-sub-heading"),
Heading("Sub-sub-heading"),
]),
]),
])
);
```
## [Escape Hatches](https://react.dev/learn/escape-hatches)
### [Referencing values with refs](https://react.dev/learn/escape-hatches#referencing-values-with-refs)
React:
```js
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
```
ivi:
```js
import { component } from 'ivi';
import { html } from 'ivi';
const Counter = component((c) => {
let _ref = 0;
function handleClick() {
_ref += 1;
alert('You clicked ' + _ref + ' times!');
}
return html`<button @click=${handleClick}>Click me!</button>`;
});
export default Counter;
```
### [Manipulating the DOM with refs](https://react.dev/learn/escape-hatches#manipulating-the-dom-with-refs)
React:
```js
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
```
ivi:
```js
import { component } from 'ivi';
import { html } from 'ivi';
const Form = component((c) => {
let _inputRef = null;
const onInputRef = (e) => _inputRef = e;
function handleClick() {
inputRef.current.focus();
}
return () => html`
<input ${onInputRef}/>
<button @click=${handleClick}>Focus the input</button>
`;
});
```
### [Synchronizing with Effects](https://react.dev/learn/escape-hatches#synchronizing-with-effects)
React:
```js
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}
```
ivi:
```js
import { component, useEffect } from 'ivi';
import { html } from 'ivi';
const VideoPlayer = component((c) => {
let _ref = null;
const getRef = (e) => _ref = e;
const update = useEffect(c, (isPlaying) => {
if (isPlaying) {
_ref.play();
} else {
_ref.pause();
}
}, strictEq);
return ({ src, isPlaying }) => (
update(isPlaying),
html`<video ${getRef} src=${src} loop playsInline />`);
});
const App = component((c) => {
const [isPlaying, setIsPlaying] = useState(c, false);
return html`
<button @click=${onClick}>${isPlaying() ? 'Pause' : 'Play'}</button>
${VideoPlayer({
isPlaying: isPlaying(),
src: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
})}
`;
});
export default App;
```
### [Removing Effect dependencies](https://react.dev/learn/escape-hatches#removing-effect-dependencies)
React:
```js
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return (
<>
<h1>Welcome to the {roomId} room!</h1>
<input value={message} onChange={e => setMessage(e.target.value)} />
</>
);
}
export default function App() {
const [roomId, setRoomId] = useState('general');
return (
<>
<label>
Choose the chat room:{' '}
<select
value={roomId}
onChange={e => setRoomId(e.target.value)}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
</label>
<hr />
<ChatRoom roomId={roomId} />
</>
);
}
```
ivi:
```js
import { component, useState, useEffect } from 'ivi';
import { html } from 'ivi';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
const ChatRoom = component((c) => {
const [message, setMessage] = useState(c, '');
const updateRoomConnection = useEffect(c, (roomId) => {
const options = {
serverUrl,
roomId,
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, strictEq);
const onInput = (ev) => { setMessage(ev.target.value); };
return ({ roomId }) => (
updateRoomConnection(roomId),
html`
<h1>Welcome to the ${roomId} room!</h1>
<input *value=${message()} @input=${onInput} />
`,
);
});
const App = component((c) => {
const [roomId, setRoomId] = useState('general');
const onChange = (ev) => setRoomId(ev.target.value);
return html`
<label>
Choose the chat room: \v
<select
*value=${roomId}
@change=${onChange}
>
<option value="general">general</option>
<option value="travel">travel</option>
<option value="music">music</option>
</select>
<hr>
${ChatRoom({ roomId: roomId() })}
`;
});
export default App;
```
### [Reusing logic with custom Hooks](https://react.dev/learn/escape-hatches#reusing-logic-with-custom-hooks)
React:
```jsx
import { useState, useEffect } from 'react';
function useDelayedValue(value, delay) {
const [delayedValue, setDelayedValue] = useState(value);
useEffect(() => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, [value, delay]);
return delayedValue;
}
function usePointerPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('pointermove', handleMove);
return () => window.removeEventListener('pointermove', handleMove);
}, []);
return position;
}
export default function Canvas() {
const pos1 = usePointerPosition();
const pos2 = useDelayedValue(pos1, 100);
const pos3 = useDelayedValue(pos2, 200);
const pos4 = useDelayedValue(pos3, 100);
const pos5 = useDelayedValue(pos3, 50);
return (
<>
<Dot position={pos1} opacity={1} />
<Dot position={pos2} opacity={0.8} />
<Dot position={pos3} opacity={0.6} />
<Dot position={pos4} opacity={0.4} />
<Dot position={pos5} opacity={0.2} />
</>
);
}
function Dot({ position, opacity }) {
const style = {
position: 'absolute',
backgroundColor: 'pink',
borderRadius: '50%',
opacity,
transform: `translate(${position.x}px, ${position.y}px)`,
pointerEvents: 'none',
left: -20,
top: -20,
width: 40,
height: 40,
};
return (
<div style={style} />
);
}
```
ivi:
```js
import {
createRoot, update, component, useState, useEffect, shallowEqArray, html,
} from "ivi";
const ZERO: Point = { x: 0, y: 0 };
function useDelayedValue(c) {
const [delayedValue, setDelayedValue] = useState(c, ZERO);
return [
delayedValue,
useEffect(c, ([value, delay]) => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, shallowEqArray)
];
}
function usePointerPosition(c) {
const [position, setPosition] = useState(c, ZERO);
useEffect(c, () => {
const onMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('pointermove', onMove);
return () => window.removeEventListener('pointermove', onMove);
})();
return position;
}
const Canvas = component((c) => {
const _pos1 = usePointerPosition(c);
const [_pos2, updatePos2] = useDelayedValue(c);
const [_pos3, updatePos3] = useDelayedValue(c);
const [_pos4, updatePos4] = useDelayedValue(c);
const [_pos5, updatePos5] = useDelayedValue(c);
return () => {
const pos1 = _pos1();
const pos2 = _pos2();
const pos3 = _pos3();
const pos4 = _pos4();
const pos5 = _pos5();
updatePos2([pos1, 100]);
updatePos3([pos2, 200]);
updatePos4([pos3, 100]);
updatePos5([pos3, 50]);
return [
Dot({ position: pos1, opacity: 1 }),
Dot({ position: pos2, opacity: 0.8 }),
Dot({ position: pos3, opacity: 0.6 }),
Dot({ position: pos4, opacity: 0.4 }),
Dot({ position: pos5, opacity: 0.2 }),
];
};
});
const Dot = ({ position, opacity }) => html`
<div
~position="absolute"
~background-color="pink"
~border-radius="50%"
~pointer-events="none"
~left="-20px"
~top="-20px"
~width="40px"
~height="40px"
~opacity=${opacity}
~transform=${`translate(${position.x}px,${position.y}px)`}
/>
`;
update(
createRoot(document.getElementById("app")!),
Canvas(),
);
```
================================================
FILE: justfile
================================================
#!/usr/bin/env -S just --justfile
export PATH := justfile_directory() + "/node_modules/.bin/:" + env_var('PATH')
set shell := ["bash", "-cu"]
_default:
@just --list -u
mod napi
init:
bun install
test:
bun test tests/runtime/
tsc *FLAGS:
tsc -b {{FLAGS}}
publish *NPM_FLAGS:
just _pkg-publish ./packages/@ivi/rolldown/ {{NPM_FLAGS}}
just _pkg-publish ./packages/@ivi/rollup-plugin/ {{NPM_FLAGS}}
just _pkg-publish ./packages/@ivi/vite-plugin/ {{NPM_FLAGS}}
just _pkg-publish ./packages/ivi/ {{NPM_FLAGS}}
_pkg-set-version dir ver:
echo "$(jq --arg v "{{ver}}" '.version = $v' {{dir}}/package.json)" > {{dir}}/package.json
_pkg-publish dir *NPM_FLAGS:
#!/usr/bin/env bash
set -euo pipefail;
cd {{dir}}
if [ "$(npm --no-workspaces view $(jq -r .name package.json) version)" != "$(jq -r .version package.json)" ]; then
filename="${PWD}/archive.tgz"
bun pm pack --filename ${filename}
npm --no-workspaces publish ${filename} {{NPM_FLAGS}}
rm -rf ${filename}
fi
================================================
FILE: napi.just
================================================
PKG_DIR := "./packages/@ivi/compiler"
build *FLAGS:
napi build --platform --esm --manifest-path {{PKG_DIR}}/Cargo.toml --package-json-path {{PKG_DIR}}/package.json --output-dir {{PKG_DIR}} {{FLAGS}}
test:
bun test ./tests/compiler/
create-npm-dirs:
napi create-npm-dirs --npm-dir {{PKG_DIR}}/packages --package-json-path {{PKG_DIR}}/package.json
artifacts:
napi artifacts --npm-dir {{PKG_DIR}}/packages --package-json-path {{PKG_DIR}}/package.json --output-dir ./napi-artifacts
update-versions version:
just _pkg-set-version {{PKG_DIR}}/packages/darwin-arm64 {{version}}
just _pkg-set-version {{PKG_DIR}}/packages/darwin-x64 {{version}}
just _pkg-set-version {{PKG_DIR}}/packages/linux-arm64-gnu {{version}}
just _pkg-set-version {{PKG_DIR}}/packages/linux-x64-gnu {{version}}
just _pkg-set-version {{PKG_DIR}}/packages/win32-arm64-msvc {{version}}
just _pkg-set-version {{PKG_DIR}}/packages/win32-x64-msvc {{version}}
increment-versions increment:
cd {{PKG_DIR}} && bun pm version {{increment}} --no-git-tag-version
just napi update-versions $(jq -r .version {{PKG_DIR}}/package.json)
publish *NPM_FLAGS:
just _pkg-publish {{PKG_DIR}}/packages/darwin-arm64 {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/packages/darwin-x64 {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/packages/linux-arm64-gnu {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/packages/linux-x64-gnu {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/packages/win32-arm64-msvc {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/packages/win32-x64-msvc {{NPM_FLAGS}}
just _pkg-publish {{PKG_DIR}}/ {{NPM_FLAGS}}
================================================
FILE: package.json
================================================
{
"private": true,
"workspaces": {
"packages": [
"packages/@ivi/*",
"packages/@ivi/compiler/packages/*",
"packages/ivi",
"tests"
],
"catalog": {
"rollup": "^4.52.5",
"rolldown": "^1.0.0-beta.43"
}
},
"scripts": {
"clean": "rm -rf ./node_modules ./tsconfig.tsbuildinfo",
"build": "tsc -b",
"build:watch": "tsc -b -w --pretty",
"build:force": "tsc -b --force --pretty"
},
"devDependencies": {
"@types/node": "^25.6.0",
"@types/bun": "^1.3.12",
"@napi-rs/cli": "^3.6.1",
"typescript": "^6.0.2"
}
}
================================================
FILE: packages/@ivi/compiler/.gitignore
================================================
ivi-compiler.*.node
================================================
FILE: packages/@ivi/compiler/Cargo.toml
================================================
[package]
publish = false
name = "ivi-compiler-napi"
version = "0.0.1"
authors = ["Boris Kaul <localvoid@gmail.com>"]
edition = "2024"
license = "MIT"
homepage = "https://github.com/localvoid/ivi"
repository = "https://github.com/localvoid/ivi"
description = "ivi compiler NAPI bindings"
[lib]
crate-type = ["cdylib"]
[dependencies]
rustc-hash.workspace = true
napi.workspace = true
napi-derive.workspace = true
ivi_compiler.workspace = true
[build-dependencies]
napi-build.workspace = true
[lints]
workspace = true
================================================
FILE: packages/@ivi/compiler/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2025 Boris Kaul <localvoid@gmail.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: packages/@ivi/compiler/README.md
================================================
NAPI bindings for the [ivi](https://github.com/localvoid/ivi) template compiler.
================================================
FILE: packages/@ivi/compiler/build.rs
================================================
fn main() {
napi_build::setup();
}
================================================
FILE: packages/@ivi/compiler/index.d.ts
================================================
/* auto-generated by NAPI-RS */
/* eslint-disable */
export declare class CompilerOutput {
code: string
map: string
}
export declare class TemplateCompiler {
constructor(options?: CompilerOptions | undefined | null)
transform(sourceText: string, moduleType: string): Promise<CompilerOutput>
renderStart(): void
renderChunk(sourceText: string): Promise<CompilerOutput>
}
export interface CompilerOptions {
dedupeStrings?: boolean
oveo?: boolean
}
================================================
FILE: packages/@ivi/compiler/index.js
================================================
// prettier-ignore
/* eslint-disable */
// @ts-nocheck
/* auto-generated by NAPI-RS */
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const __dirname = new URL('.', import.meta.url).pathname
const { readFileSync } = require('node:fs')
let nativeBinding = null
const loadErrors = []
const isMusl = () => {
let musl = false
if (process.platform === 'linux') {
musl = isMuslFromFilesystem()
if (musl === null) {
musl = isMuslFromReport()
}
if (musl === null) {
musl = isMuslFromChildProcess()
}
}
return musl
}
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
const isMuslFromFilesystem = () => {
try {
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
} catch {
return null
}
}
const isMuslFromReport = () => {
let report = null
if (typeof process.report?.getReport === 'function') {
process.report.excludeNetwork = true
report = process.report.getReport()
}
if (!report) {
return null
}
if (report.header && report.header.glibcVersionRuntime) {
return false
}
if (Array.isArray(report.sharedObjects)) {
if (report.sharedObjects.some(isFileMusl)) {
return true
}
}
return false
}
const isMuslFromChildProcess = () => {
try {
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
} catch (e) {
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
return false
}
}
function requireNative() {
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
try {
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
} catch (err) {
loadErrors.push(err)
}
} else if (process.platform === 'android') {
if (process.arch === 'arm64') {
try {
return require('./ivi-compiler.android-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-android-arm64')
const bindingPackageVersion = require('@ivi/compiler-android-arm64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./ivi-compiler.android-arm-eabi.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-android-arm-eabi')
const bindingPackageVersion = require('@ivi/compiler-android-arm-eabi/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
}
} else if (process.platform === 'win32') {
if (process.arch === 'x64') {
if (process.report?.getReport?.()?.header?.osName?.startsWith?.('MINGW')) {
try {
return require('./ivi-compiler.win32-x64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-win32-x64-gnu')
const bindingPackageVersion = require('@ivi/compiler-win32-x64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.win32-x64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-win32-x64-msvc')
const bindingPackageVersion = require('@ivi/compiler-win32-x64-msvc/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'ia32') {
try {
return require('./ivi-compiler.win32-ia32-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-win32-ia32-msvc')
const bindingPackageVersion = require('@ivi/compiler-win32-ia32-msvc/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./ivi-compiler.win32-arm64-msvc.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-win32-arm64-msvc')
const bindingPackageVersion = require('@ivi/compiler-win32-arm64-msvc/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
}
} else if (process.platform === 'darwin') {
try {
return require('./ivi-compiler.darwin-universal.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-darwin-universal')
const bindingPackageVersion = require('@ivi/compiler-darwin-universal/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
if (process.arch === 'x64') {
try {
return require('./ivi-compiler.darwin-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-darwin-x64')
const bindingPackageVersion = require('@ivi/compiler-darwin-x64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./ivi-compiler.darwin-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-darwin-arm64')
const bindingPackageVersion = require('@ivi/compiler-darwin-arm64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
}
} else if (process.platform === 'freebsd') {
if (process.arch === 'x64') {
try {
return require('./ivi-compiler.freebsd-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-freebsd-x64')
const bindingPackageVersion = require('@ivi/compiler-freebsd-x64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm64') {
try {
return require('./ivi-compiler.freebsd-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-freebsd-arm64')
const bindingPackageVersion = require('@ivi/compiler-freebsd-arm64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
}
} else if (process.platform === 'linux') {
if (process.arch === 'x64') {
if (isMusl()) {
try {
return require('./ivi-compiler.linux-x64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-x64-musl')
const bindingPackageVersion = require('@ivi/compiler-linux-x64-musl/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.linux-x64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-x64-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-x64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm64') {
if (isMusl()) {
try {
return require('./ivi-compiler.linux-arm64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-arm64-musl')
const bindingPackageVersion = require('@ivi/compiler-linux-arm64-musl/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.linux-arm64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-arm64-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-arm64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'arm') {
if (isMusl()) {
try {
return require('./ivi-compiler.linux-arm-musleabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-arm-musleabihf')
const bindingPackageVersion = require('@ivi/compiler-linux-arm-musleabihf/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.linux-arm-gnueabihf.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-arm-gnueabihf')
const bindingPackageVersion = require('@ivi/compiler-linux-arm-gnueabihf/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'loong64') {
if (isMusl()) {
try {
return require('./ivi-compiler.linux-loong64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-loong64-musl')
const bindingPackageVersion = require('@ivi/compiler-linux-loong64-musl/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.linux-loong64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-loong64-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-loong64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'riscv64') {
if (isMusl()) {
try {
return require('./ivi-compiler.linux-riscv64-musl.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-riscv64-musl')
const bindingPackageVersion = require('@ivi/compiler-linux-riscv64-musl/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
try {
return require('./ivi-compiler.linux-riscv64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-riscv64-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-riscv64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
}
} else if (process.arch === 'ppc64') {
try {
return require('./ivi-compiler.linux-ppc64-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-ppc64-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-ppc64-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 's390x') {
try {
return require('./ivi-compiler.linux-s390x-gnu.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-linux-s390x-gnu')
const bindingPackageVersion = require('@ivi/compiler-linux-s390x-gnu/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
}
} else if (process.platform === 'openharmony') {
if (process.arch === 'arm64') {
try {
return require('./ivi-compiler.openharmony-arm64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-openharmony-arm64')
const bindingPackageVersion = require('@ivi/compiler-openharmony-arm64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'x64') {
try {
return require('./ivi-compiler.openharmony-x64.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-openharmony-x64')
const bindingPackageVersion = require('@ivi/compiler-openharmony-x64/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else if (process.arch === 'arm') {
try {
return require('./ivi-compiler.openharmony-arm.node')
} catch (e) {
loadErrors.push(e)
}
try {
const binding = require('@ivi/compiler-openharmony-arm')
const bindingPackageVersion = require('@ivi/compiler-openharmony-arm/package.json').version
if (bindingPackageVersion !== '0.1.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.1.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}
return binding
} catch (e) {
loadErrors.push(e)
}
} else {
loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
}
} else {
loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
}
}
nativeBinding = requireNative()
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
let wasiBinding = null
let wasiBindingError = null
try {
wasiBinding = require('./ivi-compiler.wasi.cjs')
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
wasiBindingError = err
}
}
if (!nativeBinding) {
try {
wasiBinding = require('@ivi/compiler-wasm32-wasi')
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
wasiBindingError.cause = err
loadErrors.push(err)
}
}
}
if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
error.cause = wasiBindingError
throw error
}
}
if (!nativeBinding) {
if (loadErrors.length > 0) {
throw new Error(
`Cannot find native binding. ` +
`npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
{
cause: loadErrors.reduce((err, cur) => {
cur.cause = err
return cur
}),
},
)
}
throw new Error(`Failed to load native binding`)
}
const { CompilerOutput, TemplateCompiler } = nativeBinding
export { CompilerOutput }
export { TemplateCompiler }
================================================
FILE: packages/@ivi/compiler/package.json
================================================
{
"name": "@ivi/compiler",
"version": "0.1.14",
"type": "module",
"sideEffects": false,
"exports": {
".": "./index.js"
},
"files": [
"index.d.ts",
"index.js"
],
"optionalDependencies": {
"@ivi/compiler-darwin-arm64": "workspace:*",
"@ivi/compiler-darwin-x64": "workspace:*",
"@ivi/compiler-linux-arm64-gnu": "workspace:*",
"@ivi/compiler-linux-x64-gnu": "workspace:*",
"@ivi/compiler-win32-arm64-msvc": "workspace:*",
"@ivi/compiler-win32-x64-msvc": "workspace:*"
},
"napi": {
"binaryName": "ivi-compiler",
"targets": [
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"aarch64-apple-darwin",
"aarch64-pc-windows-msvc"
]
},
"engines": {
"node": ">= 20.0.0"
},
"scripts": {
"clean": "rm -rf ivi-compiler-*.node"
},
"description": "ivi compiler NAPI",
"license": "MIT",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"bugs": "https://github.com/localvoid/ivi/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
}
}
================================================
FILE: packages/@ivi/compiler/packages/darwin-arm64/README.md
================================================
# `@ivi/compiler-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/darwin-arm64/package.json
================================================
{
"name": "@ivi/compiler-darwin-arm64",
"version": "0.1.14",
"cpu": [
"arm64"
],
"main": "ivi-compiler.darwin-arm64.node",
"files": [
"ivi-compiler.darwin-arm64.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"darwin"
]
}
================================================
FILE: packages/@ivi/compiler/packages/darwin-x64/README.md
================================================
# `@ivi/compiler-darwin-x64`
This is the **x86_64-apple-darwin** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/darwin-x64/package.json
================================================
{
"name": "@ivi/compiler-darwin-x64",
"version": "0.1.14",
"cpu": [
"x64"
],
"main": "ivi-compiler.darwin-x64.node",
"files": [
"ivi-compiler.darwin-x64.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"darwin"
]
}
================================================
FILE: packages/@ivi/compiler/packages/linux-arm64-gnu/README.md
================================================
# `@ivi/compiler-linux-arm64-gnu`
This is the **aarch64-unknown-linux-gnu** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/linux-arm64-gnu/package.json
================================================
{
"name": "@ivi/compiler-linux-arm64-gnu",
"version": "0.1.14",
"cpu": [
"arm64"
],
"main": "ivi-compiler.linux-arm64-gnu.node",
"files": [
"ivi-compiler.linux-arm64-gnu.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"linux"
],
"libc": [
"glibc"
]
}
================================================
FILE: packages/@ivi/compiler/packages/linux-x64-gnu/README.md
================================================
# `@ivi/compiler-linux-x64-gnu`
This is the **x86_64-unknown-linux-gnu** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/linux-x64-gnu/package.json
================================================
{
"name": "@ivi/compiler-linux-x64-gnu",
"version": "0.1.14",
"cpu": [
"x64"
],
"main": "ivi-compiler.linux-x64-gnu.node",
"files": [
"ivi-compiler.linux-x64-gnu.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"linux"
],
"libc": [
"glibc"
]
}
================================================
FILE: packages/@ivi/compiler/packages/win32-arm64-msvc/README.md
================================================
# `@ivi/compiler-win32-arm64-msvc`
This is the **aarch64-pc-windows-msvc** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/win32-arm64-msvc/package.json
================================================
{
"name": "@ivi/compiler-win32-arm64-msvc",
"version": "0.1.14",
"cpu": [
"arm64"
],
"main": "ivi-compiler.win32-arm64-msvc.node",
"files": [
"ivi-compiler.win32-arm64-msvc.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"win32"
]
}
================================================
FILE: packages/@ivi/compiler/packages/win32-x64-msvc/README.md
================================================
# `@ivi/compiler-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `@ivi/compiler`
================================================
FILE: packages/@ivi/compiler/packages/win32-x64-msvc/package.json
================================================
{
"name": "@ivi/compiler-win32-x64-msvc",
"version": "0.1.14",
"cpu": [
"x64"
],
"main": "ivi-compiler.win32-x64-msvc.node",
"files": [
"ivi-compiler.win32-x64-msvc.node"
],
"description": "ivi compiler NAPI",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"license": "MIT",
"engines": {
"node": ">= 20.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/localvoid/ivi.git"
},
"bugs": "https://github.com/localvoid/ivi/issues",
"os": [
"win32"
]
}
================================================
FILE: packages/@ivi/compiler/src/lib.rs
================================================
use ivi_compiler::{compile_chunk, compile_module};
use napi::{Env, bindgen_prelude::*};
use napi_derive::napi;
use rustc_hash::{FxHashMap, FxHashSet};
use std::sync::{Arc, Mutex, RwLock};
#[napi]
pub struct CompilerOutput {
pub code: String,
pub map: String,
}
#[napi(object)]
pub struct CompilerOptions {
pub dedupe_strings: Option<bool>,
pub oveo: Option<bool>,
}
#[napi]
pub struct TemplateCompiler {
inner: Arc<CompilerState>,
}
struct CompilerState {
options: ivi_compiler::CompilerOptions,
unique_strings: Mutex<FxHashSet<String>>,
indexed_strings: RwLock<FxHashMap<String, u8>>,
}
#[napi]
impl TemplateCompiler {
#[napi(constructor)]
pub fn new(options: Option<CompilerOptions>) -> Result<Self> {
let options = if let Some(options) = options {
ivi_compiler::CompilerOptions {
oveo: options.oveo.unwrap_or(false),
dedupe_strings: options.dedupe_strings.unwrap_or(false),
}
} else {
ivi_compiler::CompilerOptions::default()
};
Ok(Self {
inner: Arc::new(CompilerState {
options,
unique_strings: Mutex::default(),
indexed_strings: RwLock::default(),
}),
})
}
#[napi(ts_return_type = "Promise<CompilerOutput>")]
pub fn transform(&self, source_text: String, module_type: String) -> AsyncTask<TransformTask> {
AsyncTask::new(TransformTask {
compiler: Arc::clone(&self.inner),
source_text,
module_type,
dedupe_strings: self.inner.options.dedupe_strings,
})
}
#[napi]
pub fn render_start(&self) {
let unique_lock = self.inner.unique_strings.lock().unwrap();
let mut unique: Vec<_> = unique_lock.iter().collect();
unique.sort();
let mut strings = self.inner.indexed_strings.write().unwrap();
strings.clear();
for (i, s) in unique.iter().enumerate() {
strings.insert(s.to_string(), i as u8);
}
unique.clear();
}
#[napi(ts_return_type = "Promise<CompilerOutput>")]
pub fn render_chunk(&self, source_text: String) -> AsyncTask<RenderChunkTask> {
AsyncTask::new(RenderChunkTask { compiler: Arc::clone(&self.inner), source_text })
}
}
pub struct TransformTask {
compiler: Arc<CompilerState>,
source_text: String,
module_type: String,
dedupe_strings: bool,
}
impl Task for TransformTask {
type Output = CompilerOutput;
type JsValue = CompilerOutput;
fn compute(&mut self) -> Result<Self::Output> {
let mut strings = FxHashSet::default();
let result = compile_module(
&self.source_text,
&self.module_type,
&self.compiler.options,
&mut strings,
)
.map(|v| CompilerOutput { code: v.code, map: v.map })
.map_err(|err| Error::from_reason(err.to_string()))?;
if self.dedupe_strings && !strings.is_empty() {
let mut unique = self.compiler.unique_strings.lock().unwrap();
unique.extend(strings.drain());
}
Ok(result)
}
fn resolve(&mut self, _env: Env, output: CompilerOutput) -> Result<Self::JsValue> {
Ok(output)
}
}
pub struct RenderChunkTask {
compiler: Arc<CompilerState>,
source_text: String,
}
impl Task for RenderChunkTask {
type Output = CompilerOutput;
type JsValue = CompilerOutput;
fn compute(&mut self) -> Result<Self::Output> {
let strings = self.compiler.indexed_strings.read().unwrap();
compile_chunk(&self.source_text, &strings)
.map(|v| CompilerOutput { code: v.code, map: v.map })
.map_err(|err| Error::from_reason(err.to_string()))
}
fn resolve(&mut self, _env: Env, output: CompilerOutput) -> Result<Self::JsValue> {
Ok(output)
}
}
================================================
FILE: packages/@ivi/compiler/tsconfig.json
================================================
{
"extends": "../../../tsconfig.composite.json",
"files": ["./index.d.ts"]
}
================================================
FILE: packages/@ivi/identity/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: packages/@ivi/identity/README.md
================================================
# [ivi](https://github.com/localvoid/ivi) Identity
Identity component is used to reset internal state.
## Example
```ts
import { createRoot, update, component, useState, useEffect, html } from "ivi";
import { Identity } from "@ivi/identity";
const Timer = component((c) => {
const [time, setTime] = useState(c, 0);
useEffect(c, () => {
const t = setInterval(() => {
setTime(time() + 100);
}, 100);
return () => { clearInterval(t); };
})();
return () => time();
});
const App = component((c) => {
const [key, setKey] = useState(c, 0);
const onClick = () => { setKey(key() + 1); };
return () => (
html`
<div class="App">
<button @click=${onClick}>Next</button>
${Identity({ key: key(), children: Timer() })}
</div>
`
);
});
update(
createRoot(document.body),
App(),
);
```
================================================
FILE: packages/@ivi/identity/package.json
================================================
{
"name": "@ivi/identity",
"version": "0.4.0",
"type": "module",
"exports": {
".": "./dist/index.js"
},
"sideEffects": false,
"files": [
"dist",
"src",
"!dist/**/*.tsbuildinfo",
"README.md",
"LICENSE"
],
"peerDependencies": {
"ivi": "workspace:^"
},
"devDependencies": {
"ivi": "workspace:*"
},
"scripts": {
"clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo"
},
"description": "ivi identity component.",
"license": "MIT",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"bugs": "https://github.com/localvoid/ivi/issues",
"repository": "github:localvoid/ivi"
}
================================================
FILE: packages/@ivi/identity/src/index.ts
================================================
import { type VAny, type VComponent, component } from "ivi";
export interface PortalEntry {
v: VAny;
}
const _renderChildren = (children: VAny) => children;
const _identityComponent = () => _renderChildren;
const A = component<VAny>(_identityComponent);
const B = component<VAny>(_identityComponent);
export interface IdentityProps {
readonly key: any;
readonly children: VAny;
}
export const Identity: (props: IdentityProps) => VComponent<IdentityProps> = component<IdentityProps>(() => {
var _prevKey: any;
var _prevType = A;
return ({ key, children }) => {
if (_prevKey !== key) {
_prevKey = key;
_prevType = (_prevType === A) ? B : A;
}
return _prevType(children);
};
});
================================================
FILE: packages/@ivi/identity/tsconfig.json
================================================
{
"extends": "../../../tsconfig.composite.json",
"references": [
{ "path": "../../ivi" },
],
}
================================================
FILE: packages/@ivi/mock-dom/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: packages/@ivi/mock-dom/README.md
================================================
# [ivi](https://github.com/localvoid/ivi) DOM Mocking Tools
This package is designed for internal tests of the ivi library.
================================================
FILE: packages/@ivi/mock-dom/package.json
================================================
{
"private": true,
"name": "@ivi/mock-dom",
"version": "0.2.0",
"type": "module",
"exports": {
".": "./dist/index.js",
"./global": "./dist/global.js"
},
"sideEffects": false,
"files": [
"dist",
"src",
"!dist/**/*.tsbuildinfo",
"README.md",
"LICENSE"
],
"scripts": {
"clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo"
},
"description": "ivi DOM mocking tools.",
"license": "MIT",
"keywords": [
"ivi"
],
"author": "Boris Kaul <localvoid@gmail.com> (https://github.com/localvoid)",
"homepage": "https://github.com/localvoid/ivi",
"bugs": "https://github.com/localvoid/ivi/issues",
"repository": "github:localvoid/ivi"
}
================================================
FILE: packages/@ivi/mock-dom/src/global.ts
================================================
export {
toSnapshot,
} from "./index.js";
import {
// DOMException as _DOMException,
Node as _Node,
Element as _Element,
HTMLElement as _HTMLElement,
SVGElement as _SVGElement,
Template as _Template,
DocumentFragment as _DocumentFragment,
Document as _Document,
CSSStyleDeclaration as _CSSStyleDeclaration
} from "./index.js";
declare global {
let requestAnimationFrame: (cb: (t: number) => void) => void;
let requestIdleCallback: (cb: () => void) => void;
// let DOMException: typeof _DOMException;
let Node: typeof _Node;
let Element: typeof _Element;
let HTMLElement: typeof _HTMLElement;
let SVGElement: typeof _SVGElement;
let Template: typeof _Template;
let DocumentFragment: typeof _DocumentFragment;
let Document: typeof _Document;
let CSSStyleDeclaration: typeof _CSSStyleDeclaration;
let document: _Document;
}
let _animationFrameQueue: ((t: number) => void)[] = [];
let _idleCallbackQueue: (() => void)[] = [];
const requestAnimationFrame = (cb: (t: number) => void) => {
_animationFrameQueue.push(cb);
};
const requestIdleCallback = (cb: () => void) => {
_idleCallbackQueue.push(cb);
};
export const flushAnimationFrames = (t: number) => {
const queue = _animationFrameQueue;
if (queue.length > 0) {
_animationFrameQueue = [];
for (let i = 0; i < queue.length; i++) {
queue[i](t);
}
}
};
export const flushIdleCallbacks = () => {
const queue = _idleCallbackQueue;
if (queue.length > 0) {
_idleCallbackQueue = [];
for (let i = 0; i < queue.length; i++) {
queue[i]();
}
}
};
// TODO: How to avoid (global as any). It seems that extending NodeJ.Global
// doesn't work.
(global as any).requestAnimationFrame = requestAnimationFrame;
(global as any).requestIdleCallback = requestIdleCallback;
// (global as any).DOMException = _DOMException;
(global as any).Node = _Node;
(global as any).Element = _Element;
(global as any).HTMLElement = _HTMLElement;
(global as any).SVGElement = _SVGElement;
(global as any).Template = _Template;
(global as any).DocumentFragment = _DocumentFragment;
(global as any).Document = _Document;
(global as any).CSSStyleDeclaration = _CSSStyleDeclaration;
(global as any).document = new _Document();
export const trace = (fn: () => void): string[] => {
let log;
try {
document._startTracing();
fn();
} finally {
log = document._log;
document._stopTracing();
}
return log!;
};
export const reset = () => {
document._reset();
_animationFrameQueue = [];
_idleCallbackQueue = [];
};
export const emit = (node: any, type: string) => {
let target: _Node | null = node;
while (target !== null) {
if (target._eventHandlers !== null) {
const handlers = target._eventHandlers.get(type);
if (handlers !== void 0) {
for (const h of handlers) {
h();
}
}
}
target = target._parentNode;
}
};
================================================
FILE: packages/@ivi/mock-dom/src/index.ts
================================================
export enum NodeType {
Element = 1,
Attribute = 2,
Text = 3,
CData = 4,
EntityReference = 5,
ProcessingInstruction = 7,
Comment = 8,
Document = 9,
DocumentFragment = 11,
}
// export class DOMException extends Error {
// constructor(msg: string) {
// super(msg);
// }
// }
export abstract class Node {
readonly uid: number;
_document: Document;
_nodeType: NodeType;
_nodeName: string;
_nodeValue: any;
_parentNode: Node | null;
_previousSibling: Node | null;
_nextSibling: Node | null;
_firstChild: Node | null;
_lastChild: Node | null;
_eventHandlers: Map<string, EventHandler[]> | null;
constructor(
doc: Document,
uid: number,
nodeType: NodeType,
nodeName: string,
eventHandlers: Map<string, EventHandler[]> | null = null,
) {
this.uid = uid;
this._document = doc;
this._nodeType = nodeType;
this._nodeName = nodeName;
this._nodeValue = null;
this._parentNode = null;
this._previousSibling = null;
this._nextSibling = null;
this._firstChild = null;
this._lastChild = null;
this._eventHandlers = eventHandlers;
}
get nodeType(): number {
this._trace(`Node.nodeType => ${this._nodeType}`);
return this._nodeType;
}
get nodeName(): string {
this._trace(`Node.nodeName => "${this._nodeName}"`);
return this._nodeName;
}
get nodeValue(): any {
this._trace(`Node.nodeValue => "${this._nodeValue}"`);
return this._nodeValue;
}
set nodeValue(s: any) {
this._trace(`Node.nodeValue = ${JSON.stringify(s)}`);
this._nodeValue = s.toString();
}
get parentNode(): Node | null {
this._trace(`Node.parentNode => ${this._parentNode === null ? "null" : this._parentNode.uid}`);
return this._parentNode;
}
get previousSibling(): Node | null {
this._trace(`Node.previousSibling => ${this._previousSibling === null ? "null" : this._previousSibling.uid}`);
return this._previousSibling;
}
get nextSibling(): Node | null {
this._trace(`Node.nextSibling => ${this._nextSibling === null ? "null" : this._nextSibling.uid}`);
return this._nextSibling;
}
get firstChild(): Node | null {
this._trace(`Node.firstChild => ${this._firstChild === null ? "null" : this._firstChild.uid}`);
return this._firstChild;
}
get lastChild(): Node | null {
this._trace(`Node.lastChild => ${this._lastChild === null ? "null" : this._lastChild.uid}`);
return this._lastChild;
}
set textContent(s: string) {
this._trace(`Node.textContent = ${JSON.stringify(s)}`);
this._setTextContent(s);
}
_setTextContent(s: string) {
let node = this._firstChild;
while (node !== null) {
const next = node._nextSibling;
this._removeChild(node);
node = next;
}
if (s !== "") {
this._appendChild(document._createTextNode(s));
}
}
appendChild(child: Node) {
this._trace(`Node.appendChild(${child.uid})`);
return this._appendChild(child);
}
_appendChild(child: Node) {
this._insertBefore(child, null);
return child;
}
insertBefore(child: Node, ref: Node | null) {
this._trace(
`Node.insertBefore(${child.uid}, ${ref === null ? "null" : ref.uid})`
);
this._insertBefore(child, ref);
}
_insertBefore(child: Node, ref: Node | null = null) {
child._remove();
child._parentNode = this;
if (ref === null) {
const last = this._lastChild;
this._lastChild = child;
child._previousSibling = last;
if (last !== null) {
last._nextSibling = child;
} else {
this._firstChild = child;
}
} else {
const prev = ref._previousSibling;
if (prev !== null) {
prev._nextSibling = child;
child._previousSibling = prev;
} else {
this._firstChild = child;
}
ref._previousSibling = child;
child._nextSibling = ref;
}
return child;
}
removeChild(child: Node) {
this._trace(`Node.removeChild(${child.uid})`);
return this._removeChild(child);
}
_removeChild(child: Node) {
if (child._parentNode?.uid !== this.uid) {
throw new Error("Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.");
}
const prev = child._previousSibling;
const next = child._nextSibling;
if (prev !== null) {
prev._nextSibling = next;
} else {
this._firstChild = next;
}
if (next !== null) {
next._previousSibling = prev;
} else {
this._lastChild = prev;
}
child._parentNode = null;
child._previousSibling = null;
child._nextSibling = null;
return child;
}
remove() {
this._trace("Node.remove()");
this._remove();
}
_remove() {
if (this._parentNode !== null) {
this._parentNode._removeChild(this);
}
}
_trace(s: string) {
this._document._trace(`[${this.uid}] ${s}`);
}
cloneNode(deep: boolean): Node {
const n = this._cloneNode(deep);
this._trace(`Node.cloneNode(${deep}) => ${n.uid}`);
return n;
}
_cloneNode(deep: boolean): Node {
return cloneNode(this, deep);
}
contains(node: Node | null): boolean {
const r = this._contains(node);
this._trace(`Node.contains(${node?.uid ?? "null"}) => ${r}`);
return r;
}
_contains(node: Node | null): boolean {
while (node !== null) {
if (this.uid === node.uid) {
return true;
}
node = node._parentNode;
}
return false;
}
}
export class Text extends Node {
constructor(document: Document, uid: number, text: string = "") {
super(document, uid, NodeType.Text, "");
this._nodeValue = text;
}
}
export class Comment extends Node {
constructor(document: Document, uid: number) {
super(document, uid, NodeType.Comment, "");
}
}
export class Element extends Node {
_namespaceURI: string;
_attributes: Map<string, string>;
_properties: Map<string | symbol, any>;
_styles: Map<string, string>;
constructor(
document: Document,
uid: number,
nodeType: number,
tagName: string,
namespaceURI: string = "http://www.w3.org/1999/xhtml",
attributes: Map<string, string> = new Map(),
properties: Map<string | symbol, any> = new Map(),
styles: Map<string, string> = new Map(),
eventHandlers: Map<string, EventHandler[]> | null = null,
) {
super(document, uid, nodeType, tagName, eventHandlers);
this._namespaceURI = namespaceURI;
this._attributes = attributes;
this._properties = properties;
this._styles = styles;
this._eventHandlers = eventHandlers;
}
get namespaceURI() {
this._trace(`Element.namespaceURI => "${this._namespaceURI}"`);
return this._namespaceURI;
}
get className(): string {
const r = this._getAttribute("class") ?? "";
this._trace(`Element.className => "${r}"`);
return r;
}
set className(value: string) {
this._trace(`Element.className = ${JSON.stringify(value)}`);
this._setAttribute("class", value);
}
setAttribute(key: string, value: string) {
this._trace(`Element.setAttribute("${key}", ${JSON.stringify(value)})`);
this._setAttribute(key, "" + value);
}
_setAttribute(key: string, value: string) {
this._attributes.set(key, value);
}
getAttribute(key: string): string | null {
const v = this._getAttribute(key);
this._trace(`Element.getAttribute("${key}") => ${JSON.stringify(v)}`);
return v;
}
_getAttribute(key: string): string | null {
return this._attributes.get(key) ?? null;
}
setProperty(key: string | symbol, value: any) {
this._trace(`Element.setProperty("${key.toString()}", ${JSON.stringify(value)})`);
this._setProperty(key, value);
}
_setProperty(key: string | symbol, value: any) {
this._properties.set(key, value);
}
getProperty(key: string) {
const v = this._getProperty(key);
this._trace(`Element.getProperty("${key.toString()}") => ${JSON.stringify(v)}`);
return v;
}
_getProperty(key: string): any {
const prop = this._properties.get(key);
if (prop !== void 0) {
return prop;
}
return this._attributes.get(key);
}
removeAttribute(key: string) {
this._trace(`Element.removeAttribute("${key}")`);
this._removeAttribute(key);
}
_removeAttribute(key: string) {
this._attributes.delete(key);
}
addEventListener(type: string, handler: EventHandler) {
this._trace(`Element.addEventListener("${type}", ${handler.name})`);
this._addEventListener(type, handler);
}
_addEventListener(type: string, handler: EventHandler) {
if (this._eventHandlers === null) {
this._eventHandlers = new Map();
this._eventHandlers.set(type, [handler]);
} else {
let handlers = this._eventHandlers.get(type);
if (handlers === void 0) {
this._eventHandlers.set(type, [handler]);
} else {
handlers.push(handler);
}
}
}
removeEventListener(type: string, handler: EventHandler) {
this._trace(`Element.removeEventListener("${type}", ${handler.name})`);
this._remo
gitextract_8ywn65uv/ ├── .clippy.toml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── renovate.json │ └── workflows/ │ ├── ci.yml │ ├── napi-libraries.yml │ └── napi-release.yml ├── .gitignore ├── .rustfmt.toml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── crates/ │ └── ivi_compiler/ │ ├── Cargo.toml │ └── src/ │ ├── chunk/ │ │ └── mod.rs │ ├── context.rs │ ├── import.rs │ ├── lib.rs │ ├── module/ │ │ └── mod.rs │ ├── oveo.rs │ └── tpl/ │ ├── emit.rs │ ├── html.rs │ ├── mod.rs │ ├── opcodes.rs │ └── parser.rs ├── docs/ │ ├── internals/ │ │ ├── dynamic-lists.md │ │ ├── misc.md │ │ ├── perf.md │ │ └── template-compiler.md │ └── misc/ │ └── migrating-from-react.md ├── justfile ├── napi.just ├── package.json ├── packages/ │ ├── @ivi/ │ │ ├── compiler/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── darwin-arm64/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── package.json │ │ │ │ ├── darwin-x64/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── package.json │ │ │ │ ├── linux-arm64-gnu/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── package.json │ │ │ │ ├── linux-x64-gnu/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── package.json │ │ │ │ ├── win32-arm64-msvc/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── package.json │ │ │ │ └── win32-x64-msvc/ │ │ │ │ ├── README.md │ │ │ │ └── package.json │ │ │ ├── src/ │ │ │ │ └── lib.rs │ │ │ └── tsconfig.json │ │ ├── identity/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── mock-dom/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── global.ts │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── portal/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── rolldown/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── rollup-plugin/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── vite-plugin/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── ivi/ │ ├── LICENSE │ ├── README.md │ ├── oveo.json │ ├── package.json │ ├── src/ │ │ ├── html/ │ │ │ ├── index.ts │ │ │ └── parser.ts │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── core.ts │ │ │ ├── equal.ts │ │ │ ├── state.ts │ │ │ ├── template.ts │ │ │ └── utils.ts │ │ ├── template/ │ │ │ ├── compiler.ts │ │ │ ├── ir.ts │ │ │ ├── parser.ts │ │ │ └── shared.ts │ │ └── test-utils/ │ │ └── index.ts │ └── tsconfig.json ├── rust-toolchain.toml ├── tests/ │ ├── compiler/ │ │ ├── chunk/ │ │ │ └── strings/ │ │ │ ├── data/ │ │ │ │ ├── 01-basic/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ └── 02-multiple/ │ │ │ │ ├── input.js │ │ │ │ └── output.js │ │ │ └── strings.test.ts │ │ ├── module/ │ │ │ ├── data/ │ │ │ │ ├── 01-text/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 02-element/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 03-element/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 04-nested-text/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 05-nested-expr/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 06-text-before-expr/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 07-text-after-expr/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 09-text-before-and-after-expr/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 103-hoist-return-fn/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 11-multiple-roots/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 12-multiple-roots/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 13-multiple-nested-expr/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 14-svg-element/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 15-svg-template/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 16-nested-mix/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 17-void-element/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 18-expr-before-element/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 19-comment/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 20-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 21-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 22-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 23-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 24-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 25-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 26-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 27-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 28-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 29-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 30-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 31-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 32-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 33-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 34-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 35-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 36-whitespace/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 40-attribute/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 41-attribute/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 42-attribute/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 43-attributes/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 44-attributes/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 50-prop/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 51-prop/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 60-style/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 61-style/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 62-style/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 63-style-mix/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 70-event/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 80-directive/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ ├── 90-hoist-class-identifier/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ └── 91-hoist-class-static-member/ │ │ │ │ ├── input.js │ │ │ │ └── output.js │ │ │ └── module.test.ts │ │ ├── module-all-disabled/ │ │ │ ├── data/ │ │ │ │ ├── 01-event/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ └── 02-hoist-return-fn/ │ │ │ │ ├── input.js │ │ │ │ └── output.js │ │ │ └── module.test.ts │ │ ├── module-oveo-disabled/ │ │ │ ├── data/ │ │ │ │ ├── 01-event/ │ │ │ │ │ ├── input.js │ │ │ │ │ └── output.js │ │ │ │ └── 02-hoist-return-fn/ │ │ │ │ ├── input.js │ │ │ │ └── output.js │ │ │ └── module.test.ts │ │ └── normalize.ts │ ├── package.json │ └── runtime/ │ ├── array.test.ts │ ├── component.test.ts │ ├── containsDOMElement.test.ts │ ├── context.test.ts │ ├── equal.test.ts │ ├── findDOMNode.test.ts │ ├── hasDOMElement.test.ts │ ├── hole.test.ts │ ├── html/ │ │ └── parser.test.ts │ ├── list.test.ts │ ├── mock-dom/ │ │ ├── document.test.ts │ │ ├── element.test.ts │ │ ├── innerHTML.test.ts │ │ ├── node.test.ts │ │ └── template.test.ts │ ├── template/ │ │ ├── attribute.test.ts │ │ ├── className.test.ts │ │ ├── directive.test.ts │ │ ├── event.test.ts │ │ ├── htm.test.ts │ │ ├── innerHTML.test.ts │ │ ├── property.test.ts │ │ ├── propertyDiffDOM.test.ts │ │ ├── style.test.ts │ │ ├── svg.test.ts │ │ └── textContent.test.ts │ ├── text.test.ts │ ├── useAnimationFrameEffect.test.ts │ ├── useEffect.test.ts │ ├── useIdleEffect.test.ts │ ├── useMemo.test.ts │ ├── useReducer.test.ts │ └── useState.test.ts ├── tsconfig.composite.json └── tsconfig.json
SYMBOL INDEX (419 symbols across 37 files)
FILE: crates/ivi_compiler/src/chunk/mod.rs
function compile_chunk (line 13) | pub fn compile_chunk<'a>(
type ChunkCompiler (line 23) | struct ChunkCompiler<'ctx> {
function new (line 28) | pub fn new(strings: &'ctx FxHashMap<String, u8>) -> Self {
function enter_expression (line 34) | fn enter_expression(&mut self, node: &mut Expression<'a>, ctx: &mut Trav...
function update_prop_op_codes (line 80) | fn update_prop_op_codes<'a>(
constant STRINGS_UUID (line 116) | const STRINGS_UUID: &str = "IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc";
FILE: crates/ivi_compiler/src/context.rs
type TraverseCtx (line 3) | pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, TraverseCtxStat...
type TraverseCtxState (line 6) | pub struct TraverseCtxState<'a> {
FILE: crates/ivi_compiler/src/import.rs
type ImportSymbols (line 14) | pub struct ImportSymbols<'a> {
function template_descriptor (line 28) | pub fn template_descriptor(&mut self, ctx: &mut TraverseCtx<'a>) -> Expr...
function html_template (line 32) | pub fn html_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression...
function html_element (line 36) | pub fn html_element(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<...
function svg_template (line 40) | pub fn svg_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<...
function svg_element (line 44) | pub fn svg_element(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'...
function create_from_template (line 48) | pub fn create_from_template(&mut self, ctx: &mut TraverseCtx<'a>) -> Exp...
function empty_array (line 52) | pub fn empty_array(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'...
function hoist (line 56) | pub fn hoist(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
function dedupe (line 60) | pub fn dedupe(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
function create_import_statements (line 64) | pub fn create_import_statements(&self, ctx: &mut TraverseCtx<'a>) -> Vec...
function get (line 121) | fn get<'a>(
function spec (line 136) | fn spec<'a>(
FILE: crates/ivi_compiler/src/lib.rs
type CompilerOptions (line 19) | pub struct CompilerOptions {
type CompilerOutput (line 24) | pub struct CompilerOutput {
type CompilerError (line 30) | pub enum CompilerError {
function compile_module (line 41) | pub fn compile_module(
function compile_chunk (line 90) | pub fn compile_chunk(
FILE: crates/ivi_compiler/src/module/mod.rs
function compile_module (line 18) | pub fn compile_module<'a>(
type ModuleCompiler (line 30) | struct ModuleCompiler<'a, 'ctx> {
function new (line 41) | pub fn new(options: &'ctx CompilerOptions, strings: &'ctx mut FxHashSet<...
function resolve (line 53) | fn resolve(&self, expr: &Expression<'a>, scoping: &Scoping) -> Option<Iv...
function add_template_decl (line 79) | fn add_template_decl(&mut self, address: Address, decl: Statement<'a>) {
function enter_statement (line 92) | fn enter_statement(&mut self, node: &mut Statement<'a>, _ctx: &mut Trave...
function exit_statement (line 96) | fn exit_statement(&mut self, _node: &mut Statement<'a>, _ctx: &mut Trave...
function exit_expression (line 100) | fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut Trave...
function exit_import_declaration (line 159) | fn exit_import_declaration(
function exit_program (line 196) | fn exit_program(&mut self, node: &mut Program<'a>, ctx: &mut TraverseCtx...
type IviSymbol (line 226) | enum IviSymbol {
FILE: crates/ivi_compiler/src/oveo.rs
function oveo_intrinsic (line 7) | pub fn oveo_intrinsic<'a>(
FILE: crates/ivi_compiler/src/tpl/emit.rs
type TemplateNode (line 22) | pub enum TemplateNode<'a> {
type TemplateBlock (line 28) | pub struct TemplateBlock<'a> {
function emit_root_element (line 38) | pub fn emit_root_element<'a>(
function count_state_slots (line 78) | fn count_state_slots(op_codes: &[u32]) -> u32 {
function count_child_slots (line 90) | fn count_child_slots(op_codes: &[u32]) -> u32 {
function create_expr_map (line 100) | fn create_expr_map<'a>(
function _create_expr_map (line 112) | fn _create_expr_map<'a>(
function emit_static_template (line 170) | fn emit_static_template<'a>(
function _emit_static_template (line 204) | fn _emit_static_template<'a>(
function emit_props_op_codes (line 320) | fn emit_props_op_codes(node: &TNode, expr_map: &IndexSet<usize>) -> (Vec...
function _emit_props_op_codes (line 326) | fn _emit_props_op_codes(
function emit_state_op_codes (line 461) | fn emit_state_op_codes(node: &TElement) -> Vec<u32> {
function _emit_state_op_codes (line 467) | fn _emit_state_op_codes(op_codes: &mut Vec<u32>, node: &TElement) {
function emit_child_op_codes (line 527) | fn emit_child_op_codes(node: &TNode, expr_map: &IndexSet<usize>) -> Vec<...
function _emit_child_op_codes (line 533) | fn _emit_child_op_codes(
FILE: crates/ivi_compiler/src/tpl/html.rs
function is_html_void_element (line 1) | pub fn is_html_void_element(tag: &str) -> bool {
FILE: crates/ivi_compiler/src/tpl/mod.rs
type CompiledTemplate (line 17) | pub struct CompiledTemplate<'a> {
type TemplateKind (line 24) | pub enum TemplateKind {
function compile_template (line 29) | pub fn compile_template<'a>(
function op_codes_into_expression (line 166) | fn op_codes_into_expression<'a>(
function strings_into_expression (line 187) | fn strings_into_expression<'a>(
FILE: crates/ivi_compiler/src/tpl/opcodes.rs
constant CHILDREN_SIZE_SHIFT (line 2) | pub const CHILDREN_SIZE_SHIFT: u32 = 6;
constant SVG (line 3) | pub const SVG: u32 = 1 << 12;
constant SAVE (line 7) | pub const SAVE: u32 = 0b01;
constant ENTER_OR_REMOVE (line 8) | pub const ENTER_OR_REMOVE: u32 = 0b10;
constant OFFSET_SHIFT (line 9) | pub const OFFSET_SHIFT: u32 = 2;
constant CLASS_NAME (line 13) | pub const CLASS_NAME: u32 = 0;
constant TEXT_CONTENT (line 14) | pub const TEXT_CONTENT: u32 = 1;
constant INNER_HTML (line 15) | pub const INNER_HTML: u32 = 2;
constant SET_NODE (line 19) | pub const SET_NODE: u32 = 0;
constant COMMON (line 20) | pub const COMMON: u32 = 1;
constant ATTRIBUTE (line 21) | pub const ATTRIBUTE: u32 = 2;
constant PROPERTY (line 22) | pub const PROPERTY: u32 = 3;
constant DIFF_DOM_PROPERTY (line 23) | pub const DIFF_DOM_PROPERTY: u32 = 4;
constant STYLE (line 24) | pub const STYLE: u32 = 5;
constant EVENT (line 25) | pub const EVENT: u32 = 6;
constant DIRECTIVE (line 26) | pub const DIRECTIVE: u32 = 7;
constant TYPE_MASK (line 27) | pub const TYPE_MASK: u32 = 0b111;
constant INPUT_SHIFT (line 28) | pub const INPUT_SHIFT: u32 = 3;
constant DATA_SHIFT (line 29) | pub const DATA_SHIFT: u32 = 9;
constant CHILD (line 33) | pub const CHILD: u32 = 0b00;
constant SET_NEXT (line 34) | pub const SET_NEXT: u32 = 0b01;
constant SET_PARENT (line 35) | pub const SET_PARENT: u32 = 0b11;
constant TYPE (line 36) | pub const TYPE: u32 = 0b11;
constant VALUE_SHIFT (line 37) | pub const VALUE_SHIFT: u32 = 2;
FILE: crates/ivi_compiler/src/tpl/parser.rs
type ExprIndex (line 8) | pub struct ExprIndex(usize);
method inner (line 11) | pub fn inner(self) -> usize {
type THoistableExpr (line 16) | pub struct THoistableExpr {
type TNode (line 21) | pub struct TNode {
constant HAS_EXPRESSIONS (line 36) | pub const HAS_EXPRESSIONS: u8 = 1;
constant HAS_NEXT_EXPRESSION (line 37) | pub const HAS_NEXT_EXPRESSION: u8 = 1 << 1;
constant HAS_NEXT_DOM_NODE (line 38) | pub const HAS_NEXT_DOM_NODE: u8 = 1 << 2;
method new (line 40) | fn new(kind: TNodeKind) -> Self {
method text (line 44) | fn text(text: &str) -> Self {
method space_text (line 54) | fn space_text() -> Self {
type TNodeKind (line 29) | pub enum TNodeKind {
type TElement (line 59) | pub struct TElement {
type TText (line 66) | pub struct TText {
type TExpr (line 70) | pub struct TExpr {
type TProperty (line 74) | pub enum TProperty {
type TPropertyAttribute (line 83) | pub struct TPropertyAttribute {
type TPropertyAttributeValue (line 88) | pub enum TPropertyAttributeValue {
type TPropertyValue (line 94) | pub struct TPropertyValue {
type TPropertyDOMValue (line 99) | pub struct TPropertyDOMValue {
type TPropertyStyle (line 104) | pub struct TPropertyStyle {
type TPropertyStyleValue (line 109) | pub enum TPropertyStyleValue {
type TPropertyEvent (line 114) | pub struct TPropertyEvent {
function parse_template (line 119) | pub fn parse_template<'a>(
type Parser (line 133) | struct Parser<'a> {
function new (line 142) | fn new(
function current_element (line 156) | fn current_element(&self) -> &TemplateElement<'a> {
function is_end (line 160) | fn is_end(&self) -> bool {
function peek_char (line 164) | fn peek_char(&self) -> Option<char> {
function peek_nth_char (line 168) | fn peek_nth_char(&self, n: usize) -> Option<char> {
function try_consume_char (line 172) | fn try_consume_char(&mut self, expected: char) -> Option<char> {
function consume_char (line 183) | fn consume_char(&mut self, expected: char) -> Result<(), OxcDiagnostic> {
function advance (line 196) | fn advance(&mut self, i: usize) {
function consume_expr (line 200) | fn consume_expr(&mut self) -> Result<usize, OxcDiagnostic> {
function consume_whitespace (line 212) | fn consume_whitespace(&mut self) -> WhitespaceState {
function parse_tag_name (line 240) | fn parse_tag_name(&mut self) -> Result<String, OxcDiagnostic> {
function parse_attribute_name (line 268) | fn parse_attribute_name(&mut self) -> Result<String, OxcDiagnostic> {
function parse_js_property (line 296) | fn parse_js_property(&mut self) -> Result<String, OxcDiagnostic> {
function parse_style_name (line 318) | fn parse_style_name(&mut self) -> Result<String, OxcDiagnostic> {
function parse_children_list (line 340) | fn parse_children_list(&mut self) -> Result<Vec<TNode>, OxcDiagnostic> {
function parse_comment (line 376) | fn parse_comment(&mut self) {
function parse_element (line 389) | fn parse_element(&mut self) -> Result<TElement, OxcDiagnostic> {
function parse_text (line 416) | fn parse_text(&mut self, whitespace_state: WhitespaceState) -> Result<TT...
function parse_attributes (line 465) | fn parse_attributes(&mut self) -> Result<Vec<TProperty>, OxcDiagnostic> {
function parse_attribute_string (line 554) | fn parse_attribute_string(&mut self) -> Result<String, OxcDiagnostic> {
type WhitespaceState (line 586) | struct WhitespaceState(u8);
constant WHITESPACE (line 589) | const WHITESPACE: u8 = 1;
constant CONTAINS_NEWLINE (line 590) | const CONTAINS_NEWLINE: u8 = 1 << 1;
constant CONTAINS_VERTICAL_TAB (line 591) | const CONTAINS_VERTICAL_TAB: u8 = 1 << 2;
constant TEXT_CONTENT (line 592) | const TEXT_CONTENT: u8 = 1 << 3;
method should_insert_whitespace (line 594) | fn should_insert_whitespace(self) -> bool {
function update_flags (line 602) | fn update_flags(node: &mut TNode) {
function _update_flags (line 605) | fn _update_flags(node: &mut TNode, flags: u8) -> u8 {
function assign_state_slots (line 670) | fn assign_state_slots(node: &mut TNode) {
function _assign_state_slots (line 674) | fn _assign_state_slots(node: &mut TNode, mut state_index: u16) -> u16 {
function is_hoistable_expr (line 707) | fn is_hoistable_expr<'a>(expr: &Expression<'a>, scoping: &Scoping) -> bo...
FILE: packages/@ivi/compiler/build.rs
function main (line 1) | fn main() {
FILE: packages/@ivi/compiler/index.d.ts
class CompilerOutput (line 3) | class CompilerOutput {
class TemplateCompiler (line 8) | class TemplateCompiler {
type CompilerOptions (line 15) | interface CompilerOptions {
FILE: packages/@ivi/compiler/index.js
function requireNative (line 67) | function requireNative() {
FILE: packages/@ivi/compiler/src/lib.rs
type CompilerOutput (line 9) | pub struct CompilerOutput {
type CompilerOptions (line 15) | pub struct CompilerOptions {
type TemplateCompiler (line 21) | pub struct TemplateCompiler {
method new (line 34) | pub fn new(options: Option<CompilerOptions>) -> Result<Self> {
method transform (line 54) | pub fn transform(&self, source_text: String, module_type: String) -> A...
method render_start (line 64) | pub fn render_start(&self) {
method render_chunk (line 77) | pub fn render_chunk(&self, source_text: String) -> AsyncTask<RenderChu...
type CompilerState (line 25) | struct CompilerState {
type TransformTask (line 82) | pub struct TransformTask {
type Output (line 90) | type Output = CompilerOutput;
type JsValue (line 91) | type JsValue = CompilerOutput;
method compute (line 93) | fn compute(&mut self) -> Result<Self::Output> {
method resolve (line 112) | fn resolve(&mut self, _env: Env, output: CompilerOutput) -> Result<Self:...
type RenderChunkTask (line 117) | pub struct RenderChunkTask {
type Output (line 123) | type Output = CompilerOutput;
type JsValue (line 124) | type JsValue = CompilerOutput;
method compute (line 126) | fn compute(&mut self) -> Result<Self::Output> {
method resolve (line 134) | fn resolve(&mut self, _env: Env, output: CompilerOutput) -> Result<Self:...
FILE: packages/@ivi/identity/src/index.ts
type PortalEntry (line 3) | interface PortalEntry {
type IdentityProps (line 12) | interface IdentityProps {
FILE: packages/@ivi/mock-dom/src/index.ts
type NodeType (line 1) | enum NodeType {
method constructor (line 32) | constructor(
method nodeType (line 52) | get nodeType(): number {
method nodeName (line 57) | get nodeName(): string {
method nodeValue (line 62) | get nodeValue(): any {
method nodeValue (line 67) | set nodeValue(s: any) {
method parentNode (line 72) | get parentNode(): Node | null {
method previousSibling (line 77) | get previousSibling(): Node | null {
method nextSibling (line 82) | get nextSibling(): Node | null {
method firstChild (line 87) | get firstChild(): Node | null {
method lastChild (line 92) | get lastChild(): Node | null {
method textContent (line 97) | set textContent(s: string) {
method _setTextContent (line 102) | _setTextContent(s: string) {
method appendChild (line 114) | appendChild(child: Node) {
method _appendChild (line 119) | _appendChild(child: Node) {
method insertBefore (line 124) | insertBefore(child: Node, ref: Node | null) {
method _insertBefore (line 131) | _insertBefore(child: Node, ref: Node | null = null) {
method removeChild (line 157) | removeChild(child: Node) {
method _removeChild (line 162) | _removeChild(child: Node) {
method remove (line 184) | remove() {
method _remove (line 189) | _remove() {
method _trace (line 195) | _trace(s: string) {
method cloneNode (line 199) | cloneNode(deep: boolean): Node {
method _cloneNode (line 205) | _cloneNode(deep: boolean): Node {
method contains (line 209) | contains(node: Node | null): boolean {
method _contains (line 215) | _contains(node: Node | null): boolean {
class Text (line 226) | class Text extends Node {
method constructor (line 227) | constructor(document: Document, uid: number, text: string = "") {
class Comment (line 233) | class Comment extends Node {
method constructor (line 234) | constructor(document: Document, uid: number) {
class Element (line 239) | class Element extends Node {
method constructor (line 245) | constructor(
method namespaceURI (line 264) | get namespaceURI() {
method className (line 269) | get className(): string {
method className (line 275) | set className(value: string) {
method setAttribute (line 280) | setAttribute(key: string, value: string) {
method _setAttribute (line 285) | _setAttribute(key: string, value: string) {
method getAttribute (line 289) | getAttribute(key: string): string | null {
method _getAttribute (line 295) | _getAttribute(key: string): string | null {
method setProperty (line 299) | setProperty(key: string | symbol, value: any) {
method _setProperty (line 304) | _setProperty(key: string | symbol, value: any) {
method getProperty (line 308) | getProperty(key: string) {
method _getProperty (line 314) | _getProperty(key: string): any {
method removeAttribute (line 322) | removeAttribute(key: string) {
method _removeAttribute (line 327) | _removeAttribute(key: string) {
method addEventListener (line 331) | addEventListener(type: string, handler: EventHandler) {
method _addEventListener (line 336) | _addEventListener(type: string, handler: EventHandler) {
method removeEventListener (line 350) | removeEventListener(type: string, handler: EventHandler) {
method _removeEventListener (line 355) | _removeEventListener(type: string, handler: EventHandler) {
method innerHTML (line 367) | set innerHTML(html: string) {
method _setInnerHTML (line 372) | _setInnerHTML(html: string) {
constant ELEMENT_PROXY_HANDLER (line 384) | const ELEMENT_PROXY_HANDLER: ProxyHandler<Element> = {
method get (line 385) | get(target, p) {
method set (line 391) | set(target, p, newValue) {
class HTMLElement (line 401) | class HTMLElement extends Element {
method constructor (line 402) | constructor(
method style (line 424) | get style() {
method _getStyle (line 429) | _getStyle() {
class SVGElement (line 433) | class SVGElement extends Element {
method constructor (line 434) | constructor(
method style (line 456) | get style() {
method _getStyle (line 461) | _getStyle() {
class DocumentFragment (line 466) | class DocumentFragment extends Node {
method constructor (line 467) | constructor(document: Document, uid: number) {
class Template (line 472) | class Template extends Element {
method constructor (line 475) | constructor(document: Document, uid: number) {
method content (line 480) | get content() {
method innerHTML (line 485) | override set innerHTML(html: string) {
method _setInnerHTML (line 490) | override _setInnerHTML(html: string) {
class Document (line 506) | class Document extends Element {
method constructor (line 511) | constructor() {
method body (line 518) | get body() {
method createElement (line 522) | createElement(tagName: string) {
method _createElement (line 528) | _createElement(tagName: string) {
method createElementNS (line 538) | createElementNS(namespace: string, tagName: string) {
method _createElementNS (line 544) | _createElementNS(namespace: string, tagName: string) {
method createTextNode (line 563) | createTextNode(text: string) {
method _createTextNode (line 569) | _createTextNode(text: string) {
method createDocumentFragment (line 573) | createDocumentFragment() {
method _createDocumentFragment (line 579) | _createDocumentFragment() {
method createComment (line 583) | createComment() {
method _createComment (line 589) | _createComment() {
method _reset (line 593) | _reset() {
method _trace (line 599) | override _trace(s: string) {
method _startTracing (line 605) | _startTracing() {
method _stopTracing (line 609) | _stopTracing() {
type EventHandler (line 614) | type EventHandler = () => void;
class CSSStyleDeclaration (line 616) | class CSSStyleDeclaration {
method constructor (line 620) | constructor(element: Element, styles: Map<string, string>) {
method setProperty (line 625) | setProperty(key: string, value: string) {
method _setProperty (line 630) | _setProperty(key: string, value: string) {
method removeProperty (line 634) | removeProperty(key: string) {
method _removeProperty (line 639) | _removeProperty(key: string) {
method getPropertyValue (line 643) | getPropertyValue(key: string) {
method _getPropertyValue (line 649) | _getPropertyValue(key: string): string {
type HTMLParserContext (line 808) | interface HTMLParserContext {
function parseHTML (line 816) | function parseHTML(doc: Document, html: string, namespaceURI: string): D...
function parseChildren (line 827) | function parseChildren(
constant IDENTIFIER (line 853) | const IDENTIFIER = /[a-zA-Z_][\w-]*/y;
constant ATTR_IDENTIFIER (line 854) | const ATTR_IDENTIFIER = /[&a-zA-Z_][\w-]*/y;
function parseElement (line 856) | function parseElement(ctx: HTMLParserContext, namespaceURI: string): Ele...
function parseAttributes (line 905) | function parseAttributes(ctx: HTMLParserContext, element: Element) {
function parseAttributeString (line 928) | function parseAttributeString(ctx: HTMLParserContext): string {
function parseComment (line 945) | function parseComment(ctx: HTMLParserContext): Comment {
function parseText (line 958) | function parseText(ctx: HTMLParserContext): Text {
function parseSkipWhitespace (line 971) | function parseSkipWhitespace(ctx: HTMLParserContext): void {
function parseCharCode (line 990) | function parseCharCode(ctx: HTMLParserContext, charCode: number): boolean {
function parseRegExp (line 998) | function parseRegExp(ctx: HTMLParserContext, re: RegExp): string | undef...
constant VOID_ELEMENTS (line 1008) | const VOID_ELEMENTS = /^(audio|video|embed|input|param|source|textarea|t...
type CharCode (line 1012) | const enum CharCode {
FILE: packages/@ivi/portal/src/index.ts
type PortalEntry (line 6) | interface PortalEntry {
FILE: packages/@ivi/rolldown/src/index.ts
type IviOptions (line 4) | interface IviOptions extends CompilerOptions {
function ivi (line 8) | function ivi(options: IviOptions = {}): RolldownPlugin {
FILE: packages/@ivi/rollup-plugin/src/index.ts
type IviOptions (line 4) | interface IviOptions extends CompilerOptions {
function ivi (line 8) | function ivi(options: IviOptions = {}): Plugin {
FILE: packages/@ivi/vite-plugin/src/index.ts
type IviOptions (line 4) | interface IviOptions extends CompilerOptions {
function ivi (line 8) | function ivi(options: IviOptions = {}): Plugin & { config(options: any, ...
FILE: packages/ivi/src/html/index.ts
constant DESCRIPTORS (line 10) | const DESCRIPTORS = new WeakMap<TemplateStringsArray, (exprs: any[]) => ...
type RootNodeBlock (line 131) | interface RootNodeBlock {
type RootNode (line 135) | type RootNode = RootNodeBlock | string | number;
FILE: packages/ivi/src/html/parser.ts
class TemplateParser (line 30) | class TemplateParser extends TemplateScanner {
method constructor (line 31) | constructor(
method parse (line 37) | parse(): INode[] {
method parseChildrenList (line 41) | parseChildrenList(): INode[] {
method parseComment (line 95) | parseComment(): void {
method parseElement (line 112) | parseElement(): INodeElement {
method parseAttributes (line 163) | parseAttributes(): IProperty[] {
method dynamicProp (line 263) | dynamicProp(properties: IProperty[], type: IPropertyType, key: string) {
method parseAttributeString (line 279) | parseAttributeString(): string {
method parseText (line 312) | parseText(state: number): string {
method whitespace (line 370) | whitespace(): number {
type WhitespaceState (line 401) | const enum WhitespaceState {
constant IDENTIFIER (line 409) | const IDENTIFIER = /[a-zA-Z_][\w-]*/y;
constant JS_PROPERTY (line 410) | const JS_PROPERTY = /[a-zA-Z_$][\w]*/y;
constant SPACE_TEXT_NODE (line 412) | const SPACE_TEXT_NODE: INodeText = {
FILE: packages/ivi/src/lib/core.ts
constant EMPTY_ARRAY (line 6) | const EMPTY_ARRAY: any[] = [];
constant HTM_TEMPLATE (line 31) | const HTM_TEMPLATE = /**@__PURE__*/doc.createElement("template");
constant HTM_TEMPLATE_CONTENT (line 32) | const HTM_TEMPLATE_CONTENT = HTM_TEMPLATE.content;
constant SVG_TEMPLATE (line 34) | const SVG_TEMPLATE = /**@__PURE__*/doc.createElementNS("http://www.w3.or...
constant SVG_TEMPLATE_CONTENT (line 36) | const SVG_TEMPLATE_CONTENT = _SVG_TEMPLATE.content.firstChild as Element;
type Element (line 48) | interface Element {
type RenderContext (line 84) | interface RenderContext {
constant RENDER_CONTEXT (line 100) | const RENDER_CONTEXT: RenderContext = _Object.seal({
type Flags (line 112) | const enum Flags {
type SAny (line 135) | type SAny =
type SNode (line 148) | type SNode<V = VAny> = SNode1<V> | SNode2<V>;
type SNode1 (line 155) | interface SNode1<V = VAny, S1 = any> {
type SNode2 (line 174) | interface SNode2<V = VAny, S1 = any, S2 = any> extends SNode1<V, S1> {
type SRoot (line 180) | type SRoot<S = any> = SNode1<VRoot, S>;
type Root (line 182) | type Root<S = any> = SRoot<S>;
type SText (line 184) | type SText = SNode1<string | number, Text>;
type STemplate (line 186) | type STemplate = SNode1<VTemplate, Node[]>;
type SList (line 188) | type SList = SNode1<VList, null>;
type SComponent (line 190) | type SComponent<P = any> = SNode2<
type Component (line 198) | type Component<P = any> = SComponent<P>;
type ComponentRenderFn (line 200) | type ComponentRenderFn<P = any> = (props: P) => VAny;
type SContext (line 202) | type SContext = SNode1<VContext, null>;
type VAny (line 221) | type VAny =
type VDescriptor (line 238) | interface VDescriptor<P1 = any, P2 = any> {
type OnRootInvalidated (line 248) | type OnRootInvalidated<S> = (root: SRoot<S>, state: S) => void;
type RootDescriptor (line 250) | type RootDescriptor<S = any> = VDescriptor<OnRootInvalidated<S>, null>;
type TemplateDescriptor (line 253) | type TemplateDescriptor = VDescriptor<TemplateData, () => Element>;
type ComponentDescriptor (line 256) | type ComponentDescriptor<P = any> = VDescriptor<
type ComponentFactoryFn (line 263) | type ComponentFactoryFn<P = any> = (component: Component) => (props: P) ...
type ContextDescriptor (line 266) | type ContextDescriptor = VDescriptor<null, null>;
type ListDescriptor (line 269) | type ListDescriptor = VDescriptor<null, null>;
type VNode (line 277) | interface VNode<D extends VDescriptor<any, any> = VDescriptor<any, any>,...
type VRoot (line 285) | type VRoot = VNode<RootDescriptor, RootProps>;
type VTemplate (line 287) | type VTemplate<P = any> = VNode<TemplateDescriptor, P>;
type VComponent (line 289) | type VComponent<P = any> = VNode<ComponentDescriptor, P>;
type VContext (line 291) | type VContext<T = any> = VNode<ContextDescriptor, ContextProps<T>>;
type VList (line 293) | type VList<K = any> = VNode<ListDescriptor, ListProps<K>>;
type RootProps (line 300) | interface RootProps {
type ContextProps (line 310) | interface ContextProps<T = any> {
type ListProps (line 322) | interface ListProps<K = any> {
type ElementDirective (line 332) | type ElementDirective = <E extends Element>(element: E) => void;
type MagicValues (line 949) | const enum MagicValues {
type ComponentFactory (line 1485) | type ComponentFactory = {
type Effect (line 1548) | type Effect = {
constant LIST_DESCRIPTOR (line 1734) | const LIST_DESCRIPTOR: ListDescriptor = {
type RootFactory (line 1853) | type RootFactory = {
FILE: packages/ivi/src/lib/state.ts
type Dispatch (line 82) | type Dispatch<A> = (action: A) => void;
FILE: packages/ivi/src/lib/template.ts
type TemplateData (line 2) | interface TemplateData {
type TemplateFlags (line 50) | const enum TemplateFlags {
type StateOpCode (line 78) | const enum StateOpCode {
type CommonPropType (line 89) | const enum CommonPropType {
type PropOpCode (line 113) | const enum PropOpCode {
type ChildOpCode (line 148) | const enum ChildOpCode {
FILE: packages/ivi/src/lib/utils.ts
type DispatchEventOptions (line 9) | interface DispatchEventOptions {
type EventDispatcher (line 26) | type EventDispatcher = {
type VisitNodesDirective (line 99) | const enum VisitNodesDirective {
FILE: packages/ivi/src/template/compiler.ts
type TemplateCompilationArtifact (line 23) | interface TemplateCompilationArtifact {
type TemplateNodeType (line 28) | enum TemplateNodeType {
type TemplateNodeBlock (line 34) | interface TemplateNodeBlock {
type TemplateNodeText (line 53) | interface TemplateNodeText {
type TemplateNodeExpr (line 58) | interface TemplateNodeExpr {
type TemplateNode (line 63) | type TemplateNode =
class TemplateCompilerError (line 75) | class TemplateCompilerError extends Error {
method constructor (line 76) | constructor(msg: string) {
type VisitState (line 431) | const enum VisitState {
FILE: packages/ivi/src/template/ir.ts
type ITemplate (line 5) | interface ITemplate {
type ITemplateType (line 10) | type ITemplateType =
constant TEMPLATE_TYPE_HTM (line 15) | const TEMPLATE_TYPE_HTM = 0;
constant TEMPLATE_TYPE_SVG (line 16) | const TEMPLATE_TYPE_SVG = 1;
constant NODE_TYPE_ELEMENT (line 18) | const NODE_TYPE_ELEMENT = 0;
constant NODE_TYPE_TEXT (line 19) | const NODE_TYPE_TEXT = 1;
constant NODE_TYPE_EXPR (line 20) | const NODE_TYPE_EXPR = 2;
type IPropertyType (line 22) | type IPropertyType =
constant PROPERTY_TYPE_ATTRIBUTE (line 31) | const PROPERTY_TYPE_ATTRIBUTE = 0;
constant PROPERTY_TYPE_VALUE (line 32) | const PROPERTY_TYPE_VALUE = 1;
constant PROPERTY_TYPE_DOMVALUE (line 33) | const PROPERTY_TYPE_DOMVALUE = 2;
constant PROPERTY_TYPE_STYLE (line 34) | const PROPERTY_TYPE_STYLE = 3;
constant PROPERTY_TYPE_EVENT (line 35) | const PROPERTY_TYPE_EVENT = 4;
constant PROPERTY_TYPE_DIRECTIVE (line 36) | const PROPERTY_TYPE_DIRECTIVE = 5;
type IPropertyAttribute (line 38) | interface IPropertyAttribute {
type IPropertyValue (line 44) | interface IPropertyValue {
type IPropertyDOMValue (line 51) | interface IPropertyDOMValue {
type IPropertyStyle (line 58) | interface IPropertyStyle {
type IPropertyEvent (line 65) | interface IPropertyEvent {
type IPropertyDirective (line 72) | interface IPropertyDirective {
type IProperty (line 79) | type IProperty =
type INodeElement (line 88) | interface INodeElement {
type INodeText (line 95) | interface INodeText {
type INodeExpr (line 100) | interface INodeExpr {
type INode (line 105) | type INode = INodeElement | INodeText | INodeExpr;
FILE: packages/ivi/src/template/parser.ts
class TemplateParserError (line 1) | class TemplateParserError extends Error {
method constructor (line 5) | constructor(message: string, staticsOffset: number, textOffset: number) {
class TemplateScanner (line 12) | class TemplateScanner {
method constructor (line 19) | constructor(statics: string[] | TemplateStringsArray) {
method isEnd (line 31) | isEnd(): boolean {
method peekCharCode (line 38) | peekCharCode(i: number = 0): number {
method charCode (line 45) | charCode(c: number): boolean {
method peekExpr (line 53) | peekExpr(): number {
method expr (line 60) | expr(): number {
method peekString (line 70) | peekString(s: string): boolean {
method string (line 79) | string(s: string): boolean {
method peekRegExp (line 87) | peekRegExp(re: RegExp): string | undefined {
method regExp (line 99) | regExp(re: RegExp): string | undefined {
type CharCode (line 169) | const enum CharCode {
FILE: packages/ivi/src/template/shared.ts
type SNodeFlags (line 8) | const enum SNodeFlags {
type SNode (line 17) | interface SNode<T extends INode = INode> {
constant VOID_ELEMENTS (line 95) | const VOID_ELEMENTS = /^(audio|video|embed|input|param|source|textarea|t...
FILE: packages/ivi/src/test-utils/index.ts
class TestRoot (line 6) | class TestRoot {
method root (line 10) | get root() { return this._root; }
method isDirty (line 11) | get isDirty() { return this._isDirty; }
method constructor (line 13) | constructor() {
method findDOMNode (line 18) | findDOMNode<T extends Node>(): T | null {
method update (line 22) | update(v: VAny, forceUpdate?: boolean) {
method dirtyCheck (line 27) | dirtyCheck(forceUpdate?: boolean) {
method dispose (line 32) | dispose() {
method _invalidate (line 37) | _invalidate() {
FILE: tests/compiler/chunk/strings/data/01-basic/input.js
constant STRINGS (line 3) | const STRINGS = ["IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc"];
FILE: tests/compiler/chunk/strings/data/01-basic/output.js
constant STRINGS (line 4) | const STRINGS = ["a"];
FILE: tests/compiler/chunk/strings/data/02-multiple/input.js
constant STRINGS (line 3) | const STRINGS = ["IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc"];
FILE: tests/compiler/chunk/strings/data/02-multiple/output.js
constant STRINGS (line 4) | const STRINGS = ["a", "b"];
FILE: tests/runtime/mock-dom/innerHTML.test.ts
type Document (line 7) | interface Document {
Condensed preview — 257 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (637K chars).
[
{
"path": ".clippy.toml",
"chars": 793,
"preview": "disallowed-methods = [\n { path = \"str::to_ascii_lowercase\", reason = \"To avoid memory allocation, use `cow_utils::CowUt"
},
{
"path": ".editorconfig",
"chars": 321,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{mts,t"
},
{
"path": ".gitattributes",
"chars": 467,
"preview": "* text=auto\n\n*.sh text eol=lf merge=union\n*.rs text eol=lf merge=union\n*.js text eol=lf merge=union\n*.mjs"
},
{
"path": ".github/renovate.json",
"chars": 837,
"preview": "{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\n \"config:recommended\",\n \"npm:un"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1424,
"preview": "name: CI\nenv:\n ACTION_CACHE_PATH: |\n ~/.bun/install/cache\n node_modules/\npermissions:\n contents: write\n id-toke"
},
{
"path": ".github/workflows/napi-libraries.yml",
"chars": 5353,
"preview": "name: NAPI Libraries\nenv:\n DEBUG: napi:*\n MACOSX_DEPLOYMENT_TARGET: \"10.13\"\n CARGO_INCREMENTAL: \"1\"\n ACTION_CACHE_PA"
},
{
"path": ".github/workflows/napi-release.yml",
"chars": 957,
"preview": "name: NAPI Release\npermissions:\n contents: write\n id-token: write\non:\n workflow_dispatch:\n inputs:\n increment"
},
{
"path": ".gitignore",
"chars": 218,
"preview": "/examples/**/dist\n/packages/**/dist\n/tests/dist\n\n.rsync-filter\n.DS_Store\n\n# Rust\n/target/\n\n# NAPI-rs\n/napi-artifacts/\n\n#"
},
{
"path": ".rustfmt.toml",
"chars": 361,
"preview": "unstable_features = true\nversion = \"Two\"\nstyle_edition = \"2024\"\nedition = \"2024\"\nformat_strings = true\nformat_code_in_do"
},
{
"path": "CONTRIBUTING.md",
"chars": 439,
"preview": "# Contributing\n\n## Requirements\n\n- Bun\n- Just\n\n## Getting Started\n\n1. Clone the git repository: `git clone git@github.co"
},
{
"path": "Cargo.toml",
"chars": 1524,
"preview": "[workspace]\nmembers = [\"crates/*\", \"packages/@ivi/compiler\"]\nresolver = \"3\"\n\n[workspace.dependencies]\nthiserror = \"2\"\nru"
},
{
"path": "LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "crates/ivi_compiler/Cargo.toml",
"chars": 714,
"preview": "[package]\nname = \"ivi_compiler\"\nversion = \"0.1.0\"\nedition = \"2024\"\nauthors = [\"Boris Kaul <localvoid@gmail.com>\"]\nlicens"
},
{
"path": "crates/ivi_compiler/src/chunk/mod.rs",
"chars": 4542,
"preview": "use oxc_allocator::{Allocator, Vec as ArenaVec};\nuse oxc_ast::ast::*;\nuse oxc_semantic::Scoping;\nuse oxc_span::SPAN;\nuse"
},
{
"path": "crates/ivi_compiler/src/context.rs",
"chars": 198,
"preview": "use std::marker::PhantomData;\n\npub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, TraverseCtxState<'a>>;\n\n#[derive"
},
{
"path": "crates/ivi_compiler/src/import.rs",
"chars": 4790,
"preview": "use oxc_ast::{\n NONE,\n ast::{\n Expression, ImportDeclarationSpecifier, ImportOrExportKind, ModuleExportName"
},
{
"path": "crates/ivi_compiler/src/lib.rs",
"chars": 3680,
"preview": "use std::path::PathBuf;\n\nuse oxc_allocator::Allocator;\nuse oxc_codegen::{Codegen, CodegenOptions};\nuse oxc_diagnostics::"
},
{
"path": "crates/ivi_compiler/src/module/mod.rs",
"chars": 8228,
"preview": "use std::collections::hash_map;\n\nuse oxc_allocator::{Address, Allocator, GetAddress, TakeIn};\nuse oxc_ast::ast::*;\nuse o"
},
{
"path": "crates/ivi_compiler/src/oveo.rs",
"chars": 343,
"preview": "use oxc_ast::{NONE, ast::Expression};\nuse oxc_span::SPAN;\n\nuse crate::context::TraverseCtx;\n\n// annotation(expr)\npub fn "
},
{
"path": "crates/ivi_compiler/src/tpl/emit.rs",
"chars": 20627,
"preview": "use indexmap::IndexSet;\nuse oxc_allocator::{TakeIn, Vec as ArenaVec};\nuse oxc_ast::{\n AstBuilder,\n ast::{Expressio"
},
{
"path": "crates/ivi_compiler/src/tpl/html.rs",
"chars": 415,
"preview": "pub fn is_html_void_element(tag: &str) -> bool {\n matches!(\n tag,\n \"audio\"\n | \"video\"\n "
},
{
"path": "crates/ivi_compiler/src/tpl/mod.rs",
"chars": 7191,
"preview": "use indexmap::IndexSet;\nuse oxc_allocator::TakeIn;\nuse oxc_ast::{NONE, ast::*};\nuse oxc_diagnostics::OxcDiagnostic;\nuse "
},
{
"path": "crates/ivi_compiler/src/tpl/opcodes.rs",
"chars": 970,
"preview": "pub mod template_flags {\n pub const CHILDREN_SIZE_SHIFT: u32 = 6;\n pub const SVG: u32 = 1 << 12;\n}\n\npub mod state_"
},
{
"path": "crates/ivi_compiler/src/tpl/parser.rs",
"chars": 23364,
"preview": "use oxc_ast::ast::{Expression, TemplateElement, TemplateLiteral};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_semantic::"
},
{
"path": "docs/internals/dynamic-lists.md",
"chars": 735,
"preview": "# Dynamic Lists\n\nJust some reminders:\n\n## Lazy rendering\n\nIt may seem like a good idea to render dynamic lists lazily an"
},
{
"path": "docs/internals/misc.md",
"chars": 514,
"preview": "# Misc\n\n## Type Casting\n\nThe current code base contains a lot of type casting. In idiomatic typescript\nit should be impl"
},
{
"path": "docs/internals/perf.md",
"chars": 3747,
"preview": "# Perf\n\n## `var` vs `let`\n\nIn some places variables are declared with `var` instead of `let`, it is a\nmicro optimization"
},
{
"path": "docs/internals/template-compiler.md",
"chars": 2218,
"preview": "# Template Compiler\n\n## Markers\n\nComment markers `<!>` should be inserted into template to delineate a slot\nposition for"
},
{
"path": "docs/misc/migrating-from-react.md",
"chars": 38150,
"preview": "# Migrating From React\n\nThis document shows how to rewrite examples from the https://react.dev/learn/\ndocumentation with"
},
{
"path": "justfile",
"chars": 1009,
"preview": "#!/usr/bin/env -S just --justfile\n\nexport PATH := justfile_directory() + \"/node_modules/.bin/:\" + env_var('PATH')\nset sh"
},
{
"path": "napi.just",
"chars": 1600,
"preview": "PKG_DIR := \"./packages/@ivi/compiler\"\n\nbuild *FLAGS:\n napi build --platform --esm --manifest-path {{PKG_DIR}}/Cargo.tom"
},
{
"path": "package.json",
"chars": 593,
"preview": "{\n \"private\": true,\n \"workspaces\": {\n \"packages\": [\n \"packages/@ivi/*\",\n \"packages/@ivi/compiler/packages"
},
{
"path": "packages/@ivi/compiler/.gitignore",
"chars": 19,
"preview": "ivi-compiler.*.node"
},
{
"path": "packages/@ivi/compiler/Cargo.toml",
"chars": 520,
"preview": "\n[package]\npublish = false\nname = \"ivi-compiler-napi\"\nversion = \"0.0.1\"\nauthors = [\"Boris Kaul <localvoid@gmail.com>\"]\ne"
},
{
"path": "packages/@ivi/compiler/LICENSE",
"chars": 1099,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2025 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of charg"
},
{
"path": "packages/@ivi/compiler/README.md",
"chars": 81,
"preview": "NAPI bindings for the [ivi](https://github.com/localvoid/ivi) template compiler.\n"
},
{
"path": "packages/@ivi/compiler/build.rs",
"chars": 37,
"preview": "fn main() {\n napi_build::setup();\n}\n"
},
{
"path": "packages/@ivi/compiler/index.d.ts",
"chars": 464,
"preview": "/* auto-generated by NAPI-RS */\n/* eslint-disable */\nexport declare class CompilerOutput {\n code: string\n map: string\n"
},
{
"path": "packages/@ivi/compiler/index.js",
"chars": 24157,
"preview": "// prettier-ignore\n/* eslint-disable */\n// @ts-nocheck\n/* auto-generated by NAPI-RS */\n\nimport { createRequire } from 'n"
},
{
"path": "packages/@ivi/compiler/package.json",
"chars": 1263,
"preview": "{\n \"name\": \"@ivi/compiler\",\n \"version\": \"0.1.14\",\n \"type\": \"module\",\n \"sideEffects\": false,\n \"exports\": {\n \".\": "
},
{
"path": "packages/@ivi/compiler/packages/darwin-arm64/README.md",
"chars": 96,
"preview": "# `@ivi/compiler-darwin-arm64`\n\nThis is the **aarch64-apple-darwin** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/darwin-arm64/package.json",
"chars": 632,
"preview": "{\n \"name\": \"@ivi/compiler-darwin-arm64\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"arm64\"\n ],\n \"main\": \"ivi-compiler.dar"
},
{
"path": "packages/@ivi/compiler/packages/darwin-x64/README.md",
"chars": 93,
"preview": "# `@ivi/compiler-darwin-x64`\n\nThis is the **x86_64-apple-darwin** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/darwin-x64/package.json",
"chars": 624,
"preview": "{\n \"name\": \"@ivi/compiler-darwin-x64\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"x64\"\n ],\n \"main\": \"ivi-compiler.darwin-"
},
{
"path": "packages/@ivi/compiler/packages/linux-arm64-gnu/README.md",
"chars": 104,
"preview": "# `@ivi/compiler-linux-arm64-gnu`\n\nThis is the **aarch64-unknown-linux-gnu** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/linux-arm64-gnu/package.json",
"chars": 669,
"preview": "{\n \"name\": \"@ivi/compiler-linux-arm64-gnu\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"arm64\"\n ],\n \"main\": \"ivi-compiler."
},
{
"path": "packages/@ivi/compiler/packages/linux-x64-gnu/README.md",
"chars": 101,
"preview": "# `@ivi/compiler-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/linux-x64-gnu/package.json",
"chars": 661,
"preview": "{\n \"name\": \"@ivi/compiler-linux-x64-gnu\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"x64\"\n ],\n \"main\": \"ivi-compiler.linu"
},
{
"path": "packages/@ivi/compiler/packages/win32-arm64-msvc/README.md",
"chars": 103,
"preview": "# `@ivi/compiler-win32-arm64-msvc`\n\nThis is the **aarch64-pc-windows-msvc** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/win32-arm64-msvc/package.json",
"chars": 643,
"preview": "{\n \"name\": \"@ivi/compiler-win32-arm64-msvc\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"arm64\"\n ],\n \"main\": \"ivi-compiler"
},
{
"path": "packages/@ivi/compiler/packages/win32-x64-msvc/README.md",
"chars": 100,
"preview": "# `@ivi/compiler-win32-x64-msvc`\n\nThis is the **x86_64-pc-windows-msvc** binary for `@ivi/compiler`\n"
},
{
"path": "packages/@ivi/compiler/packages/win32-x64-msvc/package.json",
"chars": 635,
"preview": "{\n \"name\": \"@ivi/compiler-win32-x64-msvc\",\n \"version\": \"0.1.14\",\n \"cpu\": [\n \"x64\"\n ],\n \"main\": \"ivi-compiler.win"
},
{
"path": "packages/@ivi/compiler/src/lib.rs",
"chars": 3928,
"preview": "use ivi_compiler::{compile_chunk, compile_module};\nuse napi::{Env, bindgen_prelude::*};\nuse napi_derive::napi;\nuse rustc"
},
{
"path": "packages/@ivi/compiler/tsconfig.json",
"chars": 81,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"files\": [\"./index.d.ts\"]\n}\n"
},
{
"path": "packages/@ivi/identity/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/identity/README.md",
"chars": 847,
"preview": "# [ivi](https://github.com/localvoid/ivi) Identity\n\nIdentity component is used to reset internal state.\n\n## Example\n\n```"
},
{
"path": "packages/@ivi/identity/package.json",
"chars": 755,
"preview": "{\n \"name\": \"@ivi/identity\",\n \"version\": \"0.4.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n },\n "
},
{
"path": "packages/@ivi/identity/src/index.ts",
"chars": 718,
"preview": "import { type VAny, type VComponent, component } from \"ivi\";\n\nexport interface PortalEntry {\n v: VAny;\n}\n\nconst _render"
},
{
"path": "packages/@ivi/identity/tsconfig.json",
"chars": 105,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"references\": [\n { \"path\": \"../../ivi\" },\n ],\n}\n"
},
{
"path": "packages/@ivi/mock-dom/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/mock-dom/README.md",
"chars": 125,
"preview": "# [ivi](https://github.com/localvoid/ivi) DOM Mocking Tools\n\nThis package is designed for internal tests of the ivi libr"
},
{
"path": "packages/@ivi/mock-dom/package.json",
"chars": 702,
"preview": "{\n \"private\": true,\n \"name\": \"@ivi/mock-dom\",\n \"version\": \"0.2.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dis"
},
{
"path": "packages/@ivi/mock-dom/src/global.ts",
"chars": 2914,
"preview": "export {\n toSnapshot,\n} from \"./index.js\";\nimport {\n // DOMException as _DOMException,\n Node as _Node,\n Element as _"
},
{
"path": "packages/@ivi/mock-dom/src/index.ts",
"chars": 26250,
"preview": "export enum NodeType {\n Element = 1,\n Attribute = 2,\n Text = 3,\n CData = 4,\n EntityReference = 5,\n ProcessingInstr"
},
{
"path": "packages/@ivi/mock-dom/tsconfig.json",
"chars": 104,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"compilerOptions\": {\n \"lib\": [\"ESNext\"],\n },\n}\n"
},
{
"path": "packages/@ivi/portal/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/portal/README.md",
"chars": 882,
"preview": "# [ivi](https://github.com/localvoid/ivi) Portals\n\n**EXPERIMENTAL API**\n\n## Example\n\n```ts\nimport { createRoot, update, "
},
{
"path": "packages/@ivi/portal/package.json",
"chars": 741,
"preview": "{\n \"name\": \"@ivi/portal\",\n \"version\": \"0.4.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n },\n \"s"
},
{
"path": "packages/@ivi/portal/src/index.ts",
"chars": 2184,
"preview": "import {\n type Component, type VAny,\n component, useUnmount, invalidate, List\n} from \"ivi\";\n\nexport interface PortalEn"
},
{
"path": "packages/@ivi/portal/tsconfig.json",
"chars": 105,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"references\": [\n { \"path\": \"../../ivi\" },\n ],\n}\n"
},
{
"path": "packages/@ivi/rolldown/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/rolldown/README.md",
"chars": 286,
"preview": "# [ivi](https://github.com/localvoid/ivi) Rolldown Plugin\n\n## Example Configuration\n\n`rolldown.config.js`\n\n```js\nimport "
},
{
"path": "packages/@ivi/rolldown/package.json",
"chars": 796,
"preview": "{\n \"name\": \"@ivi/rolldown\",\n \"version\": \"5.0.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n },\n "
},
{
"path": "packages/@ivi/rolldown/src/index.ts",
"chars": 1407,
"preview": "import { TemplateCompiler, type CompilerOptions } from \"@ivi/compiler\";\nimport type { HookFilter, RolldownPlugin } from "
},
{
"path": "packages/@ivi/rolldown/tsconfig.json",
"chars": 105,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"references\": [\n { \"path\": \"../../ivi\" },\n ],\n}\n"
},
{
"path": "packages/@ivi/rollup-plugin/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/rollup-plugin/README.md",
"chars": 312,
"preview": "# [ivi](https://github.com/localvoid/ivi) Rollup Plugin\n\n## Example Configuration\n\n`rollup.config.mjs`\n\n```js\nimport { i"
},
{
"path": "packages/@ivi/rollup-plugin/package.json",
"chars": 768,
"preview": "{\n \"name\": \"@ivi/rollup-plugin\",\n \"version\": \"6.0.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n "
},
{
"path": "packages/@ivi/rollup-plugin/src/index.ts",
"chars": 1354,
"preview": "import { TemplateCompiler, type CompilerOptions } from \"@ivi/compiler\";\nimport type { HookFilter, Plugin } from \"rollup\""
},
{
"path": "packages/@ivi/rollup-plugin/tsconfig.json",
"chars": 105,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"references\": [\n { \"path\": \"../../ivi\" },\n ],\n}\n"
},
{
"path": "packages/@ivi/vite-plugin/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/@ivi/vite-plugin/README.md",
"chars": 291,
"preview": "# [ivi](https://github.com/localvoid/ivi) Vite Plugin\n\n`\"@ivi/vite-plugin\"` package provides [Vite](https://vitejs.dev/)"
},
{
"path": "packages/@ivi/vite-plugin/package.json",
"chars": 764,
"preview": "{\n \"name\": \"@ivi/vite-plugin\",\n \"version\": \"6.0.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n },"
},
{
"path": "packages/@ivi/vite-plugin/src/index.ts",
"chars": 1656,
"preview": "import { TemplateCompiler, type CompilerOptions } from \"@ivi/compiler\";\nimport type { HookFilter, Plugin } from \"rollup\""
},
{
"path": "packages/@ivi/vite-plugin/tsconfig.json",
"chars": 112,
"preview": "{\n \"extends\": \"../../../tsconfig.composite.json\",\n \"references\": [\n { \"path\": \"../rollup-plugin\" },\n ],\n}\n"
},
{
"path": "packages/ivi/LICENSE",
"chars": 1105,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2024 Boris Kaul <localvoid@gmail.com>.\n\nPermission is hereby granted, free of "
},
{
"path": "packages/ivi/README.md",
"chars": 49370,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/localvoid/ivi\" target=\"_blank\" rel=\"noopener noreferrer\">\n <picture>"
},
{
"path": "packages/ivi/oveo.json",
"chars": 181,
"preview": "{\n \"ivi\": {\n \"exports\": {\n \"component\": {\n \"type\": \"function\",\n \"arguments\": [\n {\n "
},
{
"path": "packages/ivi/package.json",
"chars": 1068,
"preview": "{\n \"name\": \"ivi\",\n \"version\": \"5.1.0\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\",\n \"./test\": \"."
},
{
"path": "packages/ivi/src/html/index.ts",
"chars": 5320,
"preview": "import {\n type TemplateDescriptor, type VAny,\n _hN, _hE, _sN, _sE, _T, _t\n} from \"../lib/core.js\";\nimport { type Templ"
},
{
"path": "packages/ivi/src/html/parser.ts",
"chars": 12241,
"preview": "import {\n type ITemplate, type IProperty, type INode, type INodeElement, type INodeText,\n type IPropertyType, type ITe"
},
{
"path": "packages/ivi/src/index.ts",
"chars": 934,
"preview": "export {\n EMPTY_ARRAY,\n // Stateful Nodes\n type SNode,\n type Root, type Component,\n // Stateless Nodes\n type VAny,"
},
{
"path": "packages/ivi/src/lib/core.ts",
"chars": 54964,
"preview": "import {\n type TemplateData,\n TemplateFlags, ChildOpCode, PropOpCode, StateOpCode, CommonPropType,\n} from \"./template."
},
{
"path": "packages/ivi/src/lib/equal.ts",
"chars": 1457,
"preview": "/**\n * Prevents triggering updates.\n */\nexport const preventUpdates = (a: any, b: any) => true;\n\n/**\n * Checks if values"
},
{
"path": "packages/ivi/src/lib/state.ts",
"chars": 3053,
"preview": "import type { Component } from \"./core.js\";\nimport { invalidate } from \"./core.js\";\n\n/**\n * Creates a memoized function."
},
{
"path": "packages/ivi/src/lib/template.ts",
"chars": 3841,
"preview": "/** Template Data. */\nexport interface TemplateData {\n /**\n * SMI (Small Integer) value that packs several values:\n "
},
{
"path": "packages/ivi/src/lib/utils.ts",
"chars": 5019,
"preview": "import {\n type SNode, type Component, type STemplate, type SText,\n Flags\n} from \"./core.js\";\n\n/**\n * Dispatch Event op"
},
{
"path": "packages/ivi/src/template/compiler.ts",
"chars": 16785,
"preview": "import {\n TemplateFlags, ChildOpCode, CommonPropType, PropOpCode, StateOpCode,\n} from \"../lib/template.js\";\nimport {\n "
},
{
"path": "packages/ivi/src/template/ir.ts",
"chars": 2432,
"preview": "/**\n * Template Intermediate Representation.\n */\n\nexport interface ITemplate {\n readonly type: ITemplateType;\n readonl"
},
{
"path": "packages/ivi/src/template/parser.ts",
"chars": 4646,
"preview": "export class TemplateParserError extends Error {\n staticsOffset: number;\n textOffset: number;\n\n constructor(message: "
},
{
"path": "packages/ivi/src/template/shared.ts",
"chars": 2451,
"preview": "import {\n NODE_TYPE_ELEMENT,\n NODE_TYPE_EXPR,\n NODE_TYPE_TEXT,\n type INode, type INodeElement,\n} from \"./ir.js\";\n\nex"
},
{
"path": "packages/ivi/src/test-utils/index.ts",
"chars": 1113,
"preview": "import {\n type Root, type VAny,\n defineRoot, dirtyCheck, unmount, findDOMNode, update,\n} from \"../index.js\";\n\nexport c"
},
{
"path": "packages/ivi/tsconfig.json",
"chars": 50,
"preview": "{\n \"extends\": \"../../tsconfig.composite.json\",\n}\n"
},
{
"path": "rust-toolchain.toml",
"chars": 51,
"preview": "[toolchain]\nchannel = \"stable\"\nprofile = \"default\"\n"
},
{
"path": "tests/compiler/chunk/strings/data/01-basic/input.js",
"chars": 170,
"preview": "import { component, html } from \"ivi\";\n\nconst STRINGS = [\"IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc\"];\n\nconst c = compone"
},
{
"path": "tests/compiler/chunk/strings/data/01-basic/output.js",
"chars": 315,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/chunk/strings/data/02-multiple/input.js",
"chars": 241,
"preview": "import { component, html } from \"ivi\";\n\nconst STRINGS = [\"IVI:fa7327d9-0034-492d-bfdf-576548b2d9cc\"];\n\nconst c1 = compon"
},
{
"path": "tests/compiler/chunk/strings/data/02-multiple/output.js",
"chars": 473,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/chunk/strings/strings.test.ts",
"chars": 973,
"preview": "import { beforeEach, expect, test } from \"bun:test\";\nimport { readdir } from \"node:fs/promises\";\nimport * as path from \""
},
{
"path": "tests/compiler/module/data/01-text/input.js",
"chars": 95,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`a`;\n});"
},
{
"path": "tests/compiler/module/data/01-text/output.js",
"chars": 91,
"preview": "import { component, html } from \"ivi\";\nconst c = component(() => {\n\treturn (v) => \"a\";\n});\n"
},
{
"path": "tests/compiler/module/data/02-element/input.js",
"chars": 98,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a/>`;\n});"
},
{
"path": "tests/compiler/module/data/02-element/output.js",
"chars": 298,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/03-element/input.js",
"chars": 101,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a></a>`;\n});"
},
{
"path": "tests/compiler/module/data/03-element/output.js",
"chars": 298,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/04-nested-text/input.js",
"chars": 102,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a>a</a>`;\n});"
},
{
"path": "tests/compiler/module/data/04-nested-text/output.js",
"chars": 305,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/05-nested-expr/input.js",
"chars": 105,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a>${v}</a>`;\n});"
},
{
"path": "tests/compiler/module/data/05-nested-expr/output.js",
"chars": 304,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/06-text-before-expr/input.js",
"chars": 106,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a>a${v}</a>`;\n});"
},
{
"path": "tests/compiler/module/data/06-text-before-expr/output.js",
"chars": 311,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/07-text-after-expr/input.js",
"chars": 106,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a>${v}b</a>`;\n});"
},
{
"path": "tests/compiler/module/data/07-text-after-expr/output.js",
"chars": 314,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/09-text-before-and-after-expr/input.js",
"chars": 107,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<a>a${v}b</a>`;\n});"
},
{
"path": "tests/compiler/module/data/09-text-before-and-after-expr/output.js",
"chars": 321,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/103-hoist-return-fn/input.js",
"chars": 86,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => (v) => html`<a/>`);\n"
},
{
"path": "tests/compiler/module/data/103-hoist-return-fn/output.js",
"chars": 310,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { hoist as"
},
{
"path": "tests/compiler/module/data/11-multiple-roots/input.js",
"chars": 100,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`a<a/>b`;\n});"
},
{
"path": "tests/compiler/module/data/11-multiple-roots/output.js",
"chars": 319,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/12-multiple-roots/input.js",
"chars": 100,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`a${v}b`;\n});"
},
{
"path": "tests/compiler/module/data/12-multiple-roots/output.js",
"chars": 110,
"preview": "import { component, html } from \"ivi\";\nconst c = component(() => {\n\treturn (v) => [\n\t\t\"a\",\n\t\tv,\n\t\t\"b\"\n\t];\n});\n"
},
{
"path": "tests/compiler/module/data/13-multiple-nested-expr/input.js",
"chars": 159,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div>\n\t\t <span>${v.a}</span"
},
{
"path": "tests/compiler/module/data/13-multiple-nested-expr/output.js",
"chars": 367,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/14-svg-element/input.js",
"chars": 99,
"preview": "import { component, svg } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => svg`<a></a>`;\n});"
},
{
"path": "tests/compiler/module/data/14-svg-element/output.js",
"chars": 300,
"preview": "import { component, svg } from \"ivi\";\nimport { _T, _sE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe as"
},
{
"path": "tests/compiler/module/data/15-svg-template/input.js",
"chars": 101,
"preview": "import { component, svg } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => svg`<a a></a>`;\n});"
},
{
"path": "tests/compiler/module/data/15-svg-template/output.js",
"chars": 308,
"preview": "import { component, svg } from \"ivi\";\nimport { _T, _sN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe as"
},
{
"path": "tests/compiler/module/data/16-nested-mix/input.js",
"chars": 432,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a>\n <b>\n <c>\n"
},
{
"path": "tests/compiler/module/data/16-nested-mix/output.js",
"chars": 540,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t } from \"ivi\";\nimport { dedupe as _dedupe } from \"oveo\";\ncons"
},
{
"path": "tests/compiler/module/data/17-void-element/input.js",
"chars": 270,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <a>\n\t\t\t<audio>\n\t\t\t<video>\n\t\t"
},
{
"path": "tests/compiler/module/data/17-void-element/output.js",
"chars": 401,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/18-expr-before-element/input.js",
"chars": 153,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a class=${v.a}>\n ${v"
},
{
"path": "tests/compiler/module/data/18-expr-before-element/output.js",
"chars": 298,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t } from \"ivi\";\nimport { dedupe as _dedupe } from \"oveo\";\ncons"
},
{
"path": "tests/compiler/module/data/19-comment/input.js",
"chars": 160,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n <!-- comment -"
},
{
"path": "tests/compiler/module/data/19-comment/output.js",
"chars": 309,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/20-whitespace/input.js",
"chars": 110,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <a>\n\t\t</a>\n\t`;\n});"
},
{
"path": "tests/compiler/module/data/20-whitespace/output.js",
"chars": 298,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/21-whitespace/input.js",
"chars": 113,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <a>\n\t\t\n\t\t</a>\n\t`;\n});"
},
{
"path": "tests/compiler/module/data/21-whitespace/output.js",
"chars": 298,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/22-whitespace/input.js",
"chars": 108,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <a> </a>\n\t`;\n});"
},
{
"path": "tests/compiler/module/data/22-whitespace/output.js",
"chars": 305,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/23-whitespace/input.js",
"chars": 146,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n <div>\n <a></a>\n <"
},
{
"path": "tests/compiler/module/data/23-whitespace/output.js",
"chars": 322,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/24-whitespace/input.js",
"chars": 127,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n a\n </div>\n\t"
},
{
"path": "tests/compiler/module/data/24-whitespace/output.js",
"chars": 309,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/25-whitespace/input.js",
"chars": 135,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n a\n b\n "
},
{
"path": "tests/compiler/module/data/25-whitespace/output.js",
"chars": 311,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/26-whitespace/input.js",
"chars": 130,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n a b\n </div"
},
{
"path": "tests/compiler/module/data/26-whitespace/output.js",
"chars": 311,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/27-whitespace/input.js",
"chars": 122,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div> a b </div>\n\t`;\n})"
},
{
"path": "tests/compiler/module/data/27-whitespace/output.js",
"chars": 313,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/28-whitespace/input.js",
"chars": 129,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n \\va\n </div>"
},
{
"path": "tests/compiler/module/data/28-whitespace/output.js",
"chars": 310,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/29-whitespace/input.js",
"chars": 129,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n a\\v\n </div>"
},
{
"path": "tests/compiler/module/data/29-whitespace/output.js",
"chars": 310,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/30-whitespace/input.js",
"chars": 158,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <div>\n a\n <a>\n "
},
{
"path": "tests/compiler/module/data/30-whitespace/output.js",
"chars": 317,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/31-whitespace/input.js",
"chars": 115,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a> ${v}</a>\n\t`;\n});\n"
},
{
"path": "tests/compiler/module/data/31-whitespace/output.js",
"chars": 311,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/32-whitespace/input.js",
"chars": 115,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a>${v} </a>\n\t`;\n});\n"
},
{
"path": "tests/compiler/module/data/32-whitespace/output.js",
"chars": 314,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/33-whitespace/input.js",
"chars": 116,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a> ${v} </a>\n\t`;\n});\n"
},
{
"path": "tests/compiler/module/data/33-whitespace/output.js",
"chars": 321,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/34-whitespace/input.js",
"chars": 121,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a> ${v} ${v} </a>\n\t`;\n});"
},
{
"path": "tests/compiler/module/data/34-whitespace/output.js",
"chars": 349,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/35-whitespace/input.js",
"chars": 124,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a>\n ${v}\n </a>\n\t`;\n"
},
{
"path": "tests/compiler/module/data/35-whitespace/output.js",
"chars": 304,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/36-whitespace/input.js",
"chars": 128,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n return (v) => html`\n <a>\n \\v${v}\\v\n </a>\n"
},
{
"path": "tests/compiler/module/data/36-whitespace/output.js",
"chars": 321,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/40-attribute/input.js",
"chars": 102,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<div a/>`;\n});"
},
{
"path": "tests/compiler/module/data/40-attribute/output.js",
"chars": 310,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/41-attribute/input.js",
"chars": 106,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<div a=\"1\"/>`;\n});"
},
{
"path": "tests/compiler/module/data/41-attribute/output.js",
"chars": 314,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/42-attribute/input.js",
"chars": 107,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`<div a=${v}/>`;\n});"
},
{
"path": "tests/compiler/module/data/42-attribute/output.js",
"chars": 312,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/43-attributes/input.js",
"chars": 127,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t a=\"1\"\n\t\t\tb=\"2\"\n\t\t/>"
},
{
"path": "tests/compiler/module/data/43-attributes/output.js",
"chars": 320,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/44-attributes/input.js",
"chars": 132,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t a=\"1\"\n\t\t\tb=\"2\"\n\t\t><"
},
{
"path": "tests/compiler/module/data/44-attributes/output.js",
"chars": 320,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/50-prop/input.js",
"chars": 120,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t .a=${v}\n\t\t/>`;\n});\n"
},
{
"path": "tests/compiler/module/data/50-prop/output.js",
"chars": 312,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/51-prop/input.js",
"chars": 120,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t *a=${v}\n\t\t/>`;\n});\n"
},
{
"path": "tests/compiler/module/data/51-prop/output.js",
"chars": 312,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/60-style/input.js",
"chars": 119,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t ~a=\"0\"\n\t\t/>`;\n});\n"
},
{
"path": "tests/compiler/module/data/60-style/output.js",
"chars": 320,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/61-style/input.js",
"chars": 120,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t ~a=${v}\n\t\t/>`;\n});\n"
},
{
"path": "tests/compiler/module/data/61-style/output.js",
"chars": 312,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hE, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/62-style/input.js",
"chars": 144,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t ~a=\"0\"\n\t\t\tstyle=\"b:"
},
{
"path": "tests/compiler/module/data/62-style/output.js",
"chars": 328,
"preview": "import { component, html } from \"ivi\";\nimport { _T, _hN, _t, EMPTY_ARRAY as _EMPTY_ARRAY } from \"ivi\";\nimport { dedupe a"
},
{
"path": "tests/compiler/module/data/63-style-mix/input.js",
"chars": 155,
"preview": "import { component, html } from \"ivi\";\n\nconst c = component(() => {\n\treturn (v) => html`\n\t <div\n\t\t ~a=\"0\"\n\t\t\t~a=${v}\n\t"
}
]
// ... and 57 more files (download for full content)
About this extraction
This page contains the full source code of the ivijs/ivi GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 257 files (576.1 KB), approximately 164.5k tokens, and a symbol index with 419 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.