Native Shell
Native Shell은 모든 플랫폼의 앱 안에서 만들어지는 AI UI입니다
단일 파일 .html 컴포넌트와 Svelte식 설정 스크립트, 세밀한 업데이트 지원하는 작은 반응형 라이브러리, 컴파일러·번들러·의존성 없음
A tiny reactive component library: single-file .html components, Svelte-style setup scripts, fine-grained updates. No compiler, no bundler, no dependencies.
개발 도구TypeScript · JavaScriptGitHub @jgermade온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A independent reactive component library that ships as a single file. Svelte-style reactive scripts, fine-grained DOM updates via proxy-based dependency tracking.
No compiler, no bundler, no dependencies. A component is a .html file — the browser already knows how to fetch one. Drop the library from a CDN, serve your components from any static host, and the page works. No npm install, no build step, no config.
Take the tutorial → — a handful of exercises you edit in the browser, with a live preview. Because there's no compiler, the tutorial runs the real library: the component you write is the component that mounts.
A component is a .html file. The browser already knows how to fetch one — so
nothing has to happen to your components between writing them and serving them:
<!doctype html>
<div id="app"></div>
<script type="module">
import { C79 } from "https://esm.sh/jq79"
C79.fetch("./app.html").mount("#app", { title: "Today" })
</script>
That is the whole deployment. The library from a CDN, the component from your
own host, no build step in between. C79 is Component79 under a shorter name
— the same class, the same API.
While you're writing them, npx jq79 dev serves that folder and hot-reloads the
components you edit, keeping their state — no build step there either. See the
dev server.
npm install jq79
import { Component79, C79, $, $$, $reactive, $computed, $toRaw, parseComponent } from "jq79"
With the bundled plugin, .html component files import as modules — no
runtime fetch, with HMR in dev:
// vite.config.js
import { jq79 } from "jq79/vite"
export default { plugins: [jq79()] }
import UserCard from "./UserCard.html"
UserCard.mount("#app")
The plugin is a pure loader (nothing inside the component is transformed), so
the same file keeps working from public/ via Component79.fetch — see
the Vite plugin docs.
Once published to npm, the package is automatically served by every major CDN — no separate publishing step:
<!-- as an ES module -->
<script type="module">
import { Component79 } from "https://esm.sh/jq79"
// or: https://cdn.jsdelivr.net/npm/jq79/+esm
// or: https://unpkg.com/jq79?module
// or, straight from this repo's GitHub Pages (latest release):
// https://jgermade.github.io/jq79/jq79.js
</script>
<!-- or as a classic script exposing window.jq79 -->
<script src="https://cdn.jsdelivr.net/npm/jq79/dist/jq79.global.js"></script>
<!-- or: <script src="https://jgermade.github.io/jq79/jq79.global.js"></script> -->
<script>
const { Component79 } = jq79
</script>
Pin a version in production: https://cdn.jsdelivr.net/npm/[email protected]/... (the GitHub Pages copy always tracks the latest release).
Which is enough for a whole page: the library from a CDN, the component from your
own host, no build step in between. C79 (short for Component79, the same class)
hands back a pending component you can mount right away:
<main id="app"></main>
<script type="module">
import { C79 } from "https://jgermade.github.io/jq79/jq79.js"
C79
.fetch("./app.html")
.mount("#app")
</script>
fetchAll([...]) fetches several at once instead. See
loading remote components.
The library also exports parseComponent(source) as a shorthand for
new Component79(source), and enableHotReload() / hotUpdate(filename, src)
for custom dev setups — see the dev server.
The source is small enough to read in a sitting: the core (parsing, rendering, components) lives in src/jq79.ts, with three leaf helpers — dom.ts, reactive.ts and transform.ts. The published build is a single dependency-free file.
<!doctype html>
<div id="app"></div>
<script type="module">
import { C79 } from "https://esm.sh/jq79"
C79.fetch("./app.html").mount("#app", { title: "Today" })
</script>
The component file is served as-is from your host — no bundler, no config.
import { Component79 } from "jq79"
const jq79 = new Component79(html`
<script :setup>
let firstName = null
let lastName = null
$: fullName = firstName && lastName ? \`\${firstName} \${lastName}\` : ""
API.fetch("/me").then(user => {
firstName = user.firstName
lastName = user.lastName
})
</script>
<div :if="fullName" class="user-info">
<span>{{ fullName }}</span>
</div>
<style>
.user-info { color: rebeccapurple; }
</style>
`)
jq79.mount("#app")
When the fetch resolves, the assignments to firstName/lastName re-run the $: declaration, which flips the :if and renders the span — no manual wiring.
mount, mountShadow, detach, destroy), instance events (on/off), <style scoped>, several components in one file with <template name>, loading remote components with Component79.fetch (chainable — fetch and mount in one expression) and fetchAll, and Component79.version.{{ }} interpolation, :name attribute bindings, :text/:html, :if/:elseif/:else, :each/:key, :with, @event listeners and modifiers, nested components.<script :setup> reactive scripts, $: declarations, $emit, await $mounted(), $self/$$self, and export default factory scripts (plain-JS alternative).$reactive store: $on, $onAny, $effect, and $computed for derived values.$, $$ and $create..html components as bundled modules, HMR, options.ISC
safeEval'unsafe-eval'npx jq79 dev: serve and hot-reload components with no build step.