परिचयअवलोकन
NUCLO / 12

भाषा

दस्तावेज़

Nuclo दस्तावेज़

Nuclo इंस्टॉल करने, स्पष्ट अपडेट के साथ UI बनाने, टाइपयुक्त CSS से स्टाइल करने और सर्वर पर HTML रेंडर करने के लिए व्यावहारिक संदर्भ।

त्वरित शुरुआतnpm create nuclo@latest
त्वरित शुरुआत
v0.3.29
मौजूदा रिलीज़
175
टैग बिल्डर
~18.4 KB
कोर, gzip संपीड़ित
स्पष्ट
रनटाइम मॉडल

अवलोकन

#

Nuclo TypeScript के लिए एक छोटी DOM लाइब्रेरी है। आप पृष्ठ को div() और button() जैसे साधारण फ़ंक्शन के साथ बनाते हैं, स्थिति को सामान्य चर में रखते हैं, और जब पृष्ठ बदलना चाहिए तो update() पर कॉल करते हैं।

Nuclo में कोई वर्चुअल DOM, कोई कंपाइलर, कोई प्रॉक्सी और कोई सिग्नल नहीं है।

main.ts
import 'nuclo'

let count = 0

const App = () =>
  div(
    h1(() => `Count: ${count}`),
    button('Add one', { onClick: () => { count++; update() } }),
  )

render(App, document.getElementById('app')!)

मानसिक मॉडल

  1. टैग बिल्डरों के साथ UI बनाएं।
  2. स्टेट पर निर्भर प्रत्येक मान के लिए एक फ़ंक्शन पास करें।
  3. साधारण JavaScript के साथ स्थिति बदलें।
  4. update() पर कॉल करें। Nuclo फिर से फ़ंक्शन चलाता है और DOM को पैच करता है।

किसी वेरिएबल को बदलने से DOM पर असर नहीं पड़ता है। पेज तभी बदलता है जब आप update() पर कॉल करते हैं।

त्वरित शुरुआत

#

create-nuclo एक Vite और TypeScript प्रोजेक्ट तैयार करता है जो Nuclo के लिए तैयार है:

$npm create nuclo@latest
$bun create nuclo
$pnpm create nuclo

यह प्रोजेक्ट का नाम पूछता है। प्रॉम्प्ट को छोड़ने के लिए, नाम और फ़्लैग पास करें। npm के साथ, फ़्लैग से पहले -- लगाएं:

terminal
$ npm create nuclo@latest my-app -- --template basic --yes

फिर डेव सर्वर स्थापित करें और प्रारंभ करें:

terminal
$ cd my-app
$ npm install
$ npm run dev

विकल्प

  • [project-name]: लक्ष्य फ़ोल्डर. डिफ़ॉल्ट nuclo-app है.
  • -t, --template <name>: कॉपी करने के लिए टेम्पलेट। आज केवल basic मौजूद है।
  • -y, --yes: कभी संकेत न दें, डिफ़ॉल्ट का उपयोग करें।
  • -f, --force: ऐसे फ़ोल्डर में लिखें जो खाली न हो।
  • -h, --help: सहायता प्रिंट करें।

इंस्टॉलेशन और इम्पोर्ट

#

किसी मौजूदा प्रोजेक्ट में Nuclo जोड़ने के लिए:

terminal
$ npm install nuclo

या pnpm add nuclo, yarn add nuclo या bun add nuclo का उपयोग करें। Nuclo की कोई निर्भरता नहीं है।

रूटिंग कोर पैकेज का हिस्सा नहीं है। इसे अलग से nuclo-router के रूप में भेजा जाता है, जिसे आप जरूरत पड़ने पर nuclo के बगल में स्थापित करते हैं।

ग्लोबल्स

import 'nuclo' इन्हें globalThis पर रखता है:

  • 175 टैग बिल्डर्स: HTML के लिए 112 (div, span, button, ...) और SVG के लिए 63, Svg के साथ प्रत्यय (svgSvg, pathSvg,…)। टैग बिल्डर्स देखें।
  • 16 सहायक: update, scope, when, list, region, into, on, render, hydrate, forceUpdate, css, cx, createCss, variants, keyframes और globalStyle।

आयात किसी भी कोड से पहले चलना चाहिए जो बिल्डर को कॉल करता है। ईएस मॉड्यूल आयात क्रम में चलते हैं, इसलिए यह विफल रहता है:

main.ts
import { App } from './app' // app.ts calls div() → ReferenceError: div is not defined
import 'nuclo'

अपनी प्रविष्टि फ़ाइल में import 'nuclo' को पहले रखें, या इसे ग्लोबल्स का उपयोग करने वाली प्रत्येक फ़ाइल में आयात करें।

नामित आयात

सहायकों को निर्यात नाम भी दिया गया है। वे ग्लोबल्स के समान ही कार्य हैं। getCssText() और resetStyles() को केवल निर्यात नाम दिया गया है। टैग बिल्डर्स केवल ग्लोबल हैं, इसलिए import { div } from 'nuclo' काम नहीं करता है।

main.ts
import { render, update, getCssText } from 'nuclo'

प्रवेश बिंदु

  • nuclo: ग्लोबल्स को पंजीकृत करता है और 16 हेल्पर्स प्लस getCssText और resetStyles को निर्यात करता है।
  • nuclo/ssr: renderToString, renderManyToString, renderToStringWithContainer और getCssText। सर्वर रेंडरिंग देखें।
  • nuclo/polyfill: नोड और बन के लिए एक छोटा DOM। nuclo/polyfill देखें।
  • nuclo/types: केवल प्रकार। TypeScript देखें।

TypeScript

#

Nuclo अपने स्वयं के प्रकार शिप करता है। आपको दो चीज़ें चाहिए:

  • "moduleResolution": "bundler". node16 और nodenext अभी समर्थित नहीं हैं।
  • DOM lib. जब आप lib सेट नहीं करते हैं तो TypeScript इसे डिफ़ॉल्ट रूप से शामिल करता है।

यदि प्रोजेक्ट में कोई फ़ाइल 'nuclo' आयात करती है, तो प्रत्येक फ़ाइल वैश्विक प्रकार देखती है। तुम्हें और कुछ नहीं चाहिए. यदि कोई फ़ाइल इसे आयात नहीं करती है, उदाहरण के लिए परीक्षणों के लिए एक अलग tsconfig में, प्रकार स्वयं जोड़ें:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "types": ["nuclo/types"]
  }
}

या .d.ts फ़ाइल में /// <reference types="nuclo/types" /> जोड़ें।

उपयोगी प्रकार

ये प्रकार वैश्विक हैं, इसलिए आप इन्हें आयात के बिना उपयोग करते हैं:

  • NodeModFn<Tag>: टैग बिल्डर क्या रिटर्न देता है। इसे एक घटक के रिटर्न प्रकार के रूप में उपयोग करें।
  • NodeModLike<Tag>: टैग बिल्डर किसी भी चीज़ को तर्क के रूप में स्वीकार करता है।
  • ExpandedElement<Tag>: render() और hydrate() क्या लौटाते हैं।
  • ElementTagName: कोई भी HTML टैग नाम।
  • ExpandedElementAttributes<Tag>: वह विशेषता ऑब्जेक्ट जिसे टैग बिल्डर स्वीकार करता है, जिसमें on* हैंडलर, onMount और onDestroy शामिल हैं। इसका उपयोग उन घटकों के लिए करें जो विशेषताओं को पार करते हैं।
  • MountCallback और DestroyCallback: onMount और onDestroy हैंडलर।
  • WhenBuilder, ListModifier: when() और list() क्या रिटर्न देते हैं।
  • RegionOptions, RegionType: विकल्प region() लेता है।
  • StyleResult, Style, ThemeConfig, CssInstance और अन्य स्टाइल प्रकार। आप इन्हें भी आयात कर सकते हैं: import type { StyleResult } from 'nuclo'.
badge.ts
function Badge(text: string): NodeModFn<'span'> {
  return span(text)
}

ExpandedElement<Tag> प्रत्येक DOM सदस्य को वैकल्पिक के रूप में चिह्नित करता है। जब आपको पूर्ण तत्व प्रकार की आवश्यकता हो तो इसे कास्ट करें: render(App, root) as HTMLDivElement।

प्रत्येक HTML टैग एक ही नाम के साथ एक वैश्विक फ़ंक्शन है: div, span, input, ... एक अपवाद <var> है, जो var_ है क्योंकि var एक आरक्षित शब्द है। प्रत्येक SVG टैग में एक Svg प्रत्यय होता है: svgSvg, pathSvg, circleSvg, ...

टैग बिल्डर को कॉल करने से अभी तक कोई तत्व नहीं बना है। div('Hi') एक builder लौटाता है: एक फ़ंक्शन जो बाद में तत्व बनाता है। जब आप इसे render() के साथ माउंट करते हैं या किसी अन्य बिल्डर, when() या list() के अंदर रखते हैं तो Nuclo इसे कॉल करता है। प्रत्येक प्रयोग एक नया तत्व बनाता है।

card.ts
let name = 'Ada'

const Card = () =>
  div(
    { id: 'card', className: 'card' },
    h2('Profile'),
    p(() => `Hello, ${name}`),
    button('Rename', { onClick: () => { name = 'Grace'; update() } }),
  )

render(Card, document.body)
लाइवबिल्डर एट्रिब्यूट, स्टैटिक चाइल्ड और डायनेमिक टेक्स्ट लेता है। Rename स्टेट बदलकर update() कॉल करता है।खोलें ↗

Profile

Hello, Ada

तर्क

एक बिल्डर किसी भी संख्या में तर्क लेता है और उन्हें बाएं से दाएं लागू करता है:

  • string, संख्या, bigint: एक टेक्स्ट नोड।
  • () => value: टेक्स्ट जो update() पर बदलता है। डायनामिक मान देखें।
  • null, undefined: छोड़ दिया गया।
  • true, false: पाठ को "सही" या "false" के रूप में प्रस्तुत किया गया। शर्तों के लिए when() का उपयोग करें।
  • A साधारण ऑब्जेक्ट: विशेषताएँ। Attributes देखें।
  • A css(), cx() या variants() परिणाम: अपनी कक्षाएं जोड़ता है।
  • on(...): एक इवेंट श्रोता या जीवनचक्र हुक।
  • एक अन्य बिल्डर, when() या list(): एक बच्चा।
  • A DOM Node: जैसा है वैसा ही जोड़ा गया।
  • (el, index) => …: एक कस्टम संशोधक। नीचे देखें।

गोचास

  • div(isAdmin && span('Admin')) "false" टेक्स्ट प्रस्तुत करता है। div(when(() => isAdmin, span('Admin'))) लिखें.
  • ऐरे चाइल्ड नोड नहीं हैं। उन्हें फैलाएं: ul(...names.map((n) => li(n))). बदलने वाली सूचियों के लिए, list() का उपयोग करें।
  • टैग नाम वाला एक स्थानीय वैरिएबल बिल्डर को छुपाता है। function Row(label: string) के अंदर, label(...) कॉल करने योग्य नहीं है। label, title, data, p, a और i जैसे स्थानीय नाम बदलें।
  • ब्राउज़र हर उस तत्व को उजागर करते हैं जिसमें id एक वैश्विक चर के रूप में होता है। यदि Nuclo लोड होने से पहले पृष्ठ में <main id="main"> शामिल है, तो Nuclo उस वैश्विक स्थान को छोड़ देता है, इसलिए main(...) एक TypeError फेंकता है। अन्य आईडी का प्रयोग करें.

कस्टम संशोधक

एक फ़ंक्शन जो कम से कम एक पैरामीटर घोषित करता है, तर्क क्रम में एक बार चलता है, जबकि तत्व बनाया जा रहा है। इसका पहला तर्क तत्व है। दूसरा एक आंतरिक सूचकांक है, इसलिए इस पर भरोसा न करें। यदि यह एक स्ट्रिंग या संख्या लौटाता है, तो वह टेक्स्ट बन जाता है। एक Node जोड़ा गया है। किसी ऑब्जेक्ट को विशेषताओं के रूप में लागू किया जाता है।

example.ts
const field = input((el) => {
  el.autocomplete = 'off'
})

तत्व प्राप्त करना

render() मूल तत्व लौटाता है। किसी ट्री के अंदर, on("mount", (el) => …) या एक कस्टम संशोधक का उपयोग करें। अलग तत्व बनाने के लिए आप बिल्डर फ़ंक्शन को स्वयं भी कॉल कर सकते हैं:

example.ts
const el = div('Hello')() as HTMLDivElement
document.body.append(el)

इसे अगले update() से पहले संलग्न करें। एक नोड जो दस्तावेज़ में नहीं है जब update() चलता है तो हमेशा के लिए अपडेट होना बंद हो जाता है।

SVG

SVG बिल्डर्स भी यही तर्क देते हैं। Nuclo प्रत्येक SVG विशेषता को setAttribute के साथ सेट करता है, इसलिए "stroke-width" जैसे नाम लिखित रूप में काम करते हैं।

icon.ts
const Icon = () =>
  svgSvg(
    { viewBox: '0 0 24 24', width: 24, height: 24, className: 'icon' },
    circleSvg({ cx: 12, cy: 12, r: 10, fill: 'currentColor' }),
  )

सभी बिल्डर्स

HTML: a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd label legend li link main map mark menu meta meter nav noscript object ol optgroup option output p picture pre progress q rp rt ruby s samp script search section select slot small source span strong style sub summary sup table tbody td template textarea tfoot th thead time title tr track u ul var_ video wbr

SVG: aSvg animateSvg animateMotionSvg animateTransformSvg circleSvg clipPathSvg defsSvg descSvg ellipseSvg feBlendSvg feColorMatrixSvg feComponentTransferSvg feCompositeSvg feConvolveMatrixSvg feDiffuseLightingSvg feDisplacementMapSvg feDistantLightSvg feDropShadowSvg feFloodSvg feFuncASvg feFuncBSvg feFuncGSvg feFuncRSvg feGaussianBlurSvg feImageSvg feMergeSvg feMergeNodeSvg feMorphologySvg feOffsetSvg fePointLightSvg feSpecularLightingSvg feSpotLightSvg feTileSvg feTurbulenceSvg filterSvg foreignObjectSvg gSvg imageSvg lineSvg linearGradientSvg markerSvg maskSvg metadataSvg mpathSvg pathSvg patternSvg polygonSvg polylineSvg radialGradientSvg rectSvg scriptSvg setSvg stopSvg styleSvg svgSvg switchSvg symbolSvg textSvg textPathSvg titleSvg tspanSvg useSvg viewSvg

एक घटक एक साधारण फ़ंक्शन है जो एक बिल्डर लौटाता है, जैसे button(...)। कोई घटक API नहीं है। जब तक आप इसे प्रस्तुत नहीं करते या इसे किसी ट्री पर नहीं रखते तब तक कुछ भी नहीं बनता है।

counter.ts
function Counter(label: string) {
  let count = 0 // one per call
  return button(() => `${label}: ${count}`, {
    onClick: () => { count++; update() },
  })
}

render(() => div(Counter('A'), Counter('B')))
लाइवकंपोनेंट एक साधारण फ़ंक्शन है। हर Counter() कॉल अपना अलग काउंटर रखती है।खोलें ↗

स्थिति उस उदाहरण से संबंधित है। बाहर घोषित स्टेट हर उदाहरण द्वारा साझा किया जाता है। forceUpdate() घटकों को फिर से कॉल करता है, जो उनके अंदर घोषित स्थिति को रीसेट करता है।

चाइल्ड नोड सिर्फ तर्क हैं:

card.ts
function Card(heading: string, ...children: NodeModLike<'div'>[]) {
  return div({ className: 'card' }, h2(heading), ...children)
}

render(() => Card('Hi', p('one'), p('two')))

एट्रिब्यूट

#

विशेषताएँ सेट करने के लिए एक साधारण ऑब्जेक्ट पास करें। आप कई ऑब्जेक्ट पास कर सकते हैं. समान कुंजी के लिए, className और style को छोड़कर, बाद वाला मान जीतता है, जो विलीन हो जाते हैं। एक मान स्थिर हो सकता है, या बिना किसी पैरामीटर वाला एक फ़ंक्शन हो सकता है जो प्रत्येक update() पर फिर से चलता है।

example.ts
let busy = false

const save = button(
  { type: 'submit', disabled: () => busy, 'aria-busy': () => String(busy), 'data-id': 42 },
  'Save',
)
लाइवएट्रिब्यूट के मान फ़ंक्शन हो सकते हैं। दूसरा बटन busy बदलकर update() कॉल करता है; Save के disabled और aria-busy उसी के अनुसार बदलते हैं।खोलें ↗

कोई मान कैसे सेट किया जाता है

  • यदि HTML तत्व में उस नाम (value, checked, disabled, htmlFor, tabIndex,…) के साथ एक संपत्ति है, तो Nuclo संपत्ति सेट करता है। अन्यथा यह setAttribute(key, String(value)) को कॉल करता है, उदाहरण के लिए "aria-*" और "data-*" के लिए।
  • SVG तत्वों पर, Nuclo हमेशा setAttribute को कॉल करता है।
  • null और undefined को छोड़ दिया गया है। एक गतिशील मान जो null या undefined लौटाता है, पुराना मान रखता है। मान साफ़ करने के लिए, बूलियन प्रॉपर्टी के लिए "", या false लौटाएँ।
  • बूलियन्स DOM बूलियन गुणों के लिए काम करते हैं, जैसे एक बटन पर disabled, checked, hidden और required। कहीं और, true और false को स्ट्रिंग "true" और "false" के रूप में लिखा गया है।
  • "data-*" कुंजियों का उपयोग करें। dataset समर्थित नहीं है.
  • innerHTML बच नहीं पाया है, और सर्वर रेंडरिंग इसे आउटपुट नहीं करता है। उससे बचिए।

कक्षाएं

className का उपयोग करें। स्टेटिक क्लास स्ट्रिंग्स, css() परिणाम और एक गतिशील className सभी विलीन हो जाते हैं। एक गतिशील className स्थिर कक्षाएं रखता है, और जब यह "" लौटाता है तो केवल स्थिर कक्षाएं ही रहती हैं।

example.ts
let active = false
const base = css({ p: 8 })

const item = li(base, { className: 'item' }, { className: () => (active ? 'active' : '') }, 'Home')
// class="<base> item", then "<base> item active" after active = true; update()

HTML तत्वों पर class कुंजी का उपयोग न करें। यह कच्ची विशेषता सेट करता है और css() क्लास सहित, इससे पहले लागू प्रत्येक वर्ग को प्रतिस्थापित करता है।

इनलाइन शैलियाँ

style camelCase कुंजियों के साथ एक ऑब्जेक्ट लेता है, या एक फ़ंक्शन जो एक लौटाता है।

example.ts
let progress = 30

const bar = div({ style: () => ({ width: `${progress}%`, backgroundColor: 'teal' }) })
  • Nuclo कोई इकाई नहीं जोड़ता। "12px" लिखें, 12 नहीं। इकाई रहित गुण जैसे opacity और zIndex संख्याएँ स्वीकार करते हैं।
  • "", null और undefined एक संपत्ति साफ़ करें।
  • एक गतिशील शैली केवल वही कुंजी सेट करती है जो वह लौटाती है। जिस कुंजी को वह लौटाना बंद कर देता है, उसका पुराना मान बना रहता है, इसलिए उसे साफ़ करने के लिए "" लौटाएँ।
  • स्टाइल स्ट्रिंग्स ("color: red") और कस्टम गुण ("--gap") समर्थित नहीं हैं। इसके बजाय css() का उपयोग करें। यह raw के माध्यम से कस्टम गुण लेता है।
fnसार्वजनिक API
// DOM events such as "click". El and Ev are inferred from the tag and the event name. function on(type: DOMEventName, listener: (this: El, e: Ev) => unknown, options?: boolean | AddEventListenerOptions): NodeModFn // Custom events function on<K extends string, E extends Event = Event>(type: K, listener: (this: El, e: E) => unknown, options?: boolean | AddEventListenerOptions): NodeModFn // Lifecycle, see Lifecycle function on(type: "mount", listener: MountCallback): NodeModFn function on(type: "destroy", listener: DestroyCallback): NodeModFn

घटनाओं को सुनने के दो तरीके हैं: on* विशेषताएँ और on() संशोधक।

on* विशेषताएँ

एक विशेषता ऑब्जेक्ट में, onClick (on जिसके बाद एक बड़ा अक्षर होता है) जैसी कुंजी के तहत एक फ़ंक्शन एक ईवेंट हैंडलर है, गतिशील मान नहीं।

search.ts
let query = ''

const search = input({
  placeholder: 'Search',
  value: () => query,
  onInput: (e) => { query = e.currentTarget.value; update() },
  onKeyDown: (e) => { if (e.key === 'Escape') { query = ''; update() } },
})
  • TypeScript कुंजी और तत्व से ईवेंट प्रकार का अनुमान लगाता है। e.currentTarget और this तत्व हैं।
  • Nuclo मूल संपत्ति सेट करता है, इसलिए onClick el.onclick बन जाता है। प्रति इवेंट एक हैंडलर होता है: बाद वाला onClick पहले वाले की जगह लेता है।
  • onDoubleClick और onDblClick दोनों का मतलब dblclick है। onclick जैसी लोअरकेस कुंजियाँ प्रकार की त्रुटियाँ हैं।
  • एक नाम जो ज्ञात घटना नहीं है, उसके लोअरकेस रूप को सुनता है: onMyEvent "myevent" को सुनता है।
  • हैंडलर द्वारा फेंकी गई त्रुटियाँ पकड़ में नहीं आतीं।
लाइवonInput और onKeyDown सामान्य एट्रिब्यूट हैं। टेक्स्ट लिखें और साफ़ करने के लिए Escape दबाएँ।खोलें ↗

Type something, then press Escape.

on()

on() एक संशोधक लौटाता है जो तत्व पर addEventListener को कॉल करता है।

panel.ts
const panel = div(
  on('scroll', onScroll, { passive: true }),
  on('click', logClick),
  on('click', closeMenu),
)
  • प्रत्येक on() एक श्रोता जोड़ता है। वे सभी क्रम से चलते हैं।
  • options सीधे addEventListener पर जाता है: capture, once, passive, signal।
  • सीधे बिल्डर के पास on() पास करें और तत्व प्रकार का अनुमान लगाया जाता है: input(on('input', (e) => e.currentTarget.value))।
  • यदि कोई श्रोता फेंकता है, तो Nuclo console.error के साथ त्रुटि लॉग करता है, और अन्य श्रोता अभी भी चलते हैं।
  • Nuclo तत्व को हटाते समय श्रोता को हटा देता है। सर्वर पर, on() कुछ नहीं करता है।
  • आप कई तत्वों पर एक on() मान का पुन: उपयोग कर सकते हैं।

एक कस्टम इवेंट के लिए, इवेंट प्रकार को सामान्य के रूप में पास करें:

picker.ts
type PickEvent = CustomEvent<{ id: number }>

const picker = ul(on<'pick', PickEvent>('pick', (e) => choose(e.detail.id)))

कौन सा उपयोग करना है

सामान्य स्थिति के लिए on* विशेषताओं का उपयोग करें। जब आपको किसी इवेंट के लिए एक से अधिक श्रोता, श्रोता विकल्प, या "my-event" जैसे कस्टम इवेंट नाम की आवश्यकता हो तो on() का उपयोग करें।

fnसार्वजनिक API
// As attributes { onMount?: (el: El) => void | (() => void), onDestroy?: (el: El) => void } // Or with on() function on(type: "mount", listener: (el: El) => void | (() => void)): NodeModFn function on(type: "destroy", listener: (el: El) => void): NodeModFn

onMount एक बार चलता है। onDestroy एक बार चलता है, जब Nuclo तत्व हटा देता है। उन्हें विशेषताओं के रूप में या on() के साथ लिखें। दोनों फॉर्म समान व्यवहार करते हैं, और तत्व उसके टैग से टाइप किया जाता है।

timer.ts
let seconds = 0

const Timer = () =>
  span(
    {
      onMount: () => {
        const id = setInterval(() => { seconds++; update() }, 1000)
        return () => clearInterval(id) // runs when the span is removed
      },
    },
    () => `${seconds}s`,
  )

const search = input(on('mount', (el) => el.focus()))
लाइवonMount इंटरवल शुरू करता है और उसका क्लीनअप फ़ंक्शन लौटाता है। अनमाउंट होने पर क्लीनअप चलता है, इसलिए टाइमर दोबारा माउंट होने तक गिनती रुक जाती है।खोलें ↗

जब वे चलते हैं

  • onMount उस render(), hydrate(), update() या forceUpdate() कॉल के अंत में चलता है जिसने तत्व डाला था। ट्री के निर्माण के दौरान यह कभी नहीं चलता।
  • यदि onMount एक फ़ंक्शन लौटाता है, तो तत्व के onDestroy कॉलबैक के बाद, वह फ़ंक्शन नष्ट हो जाता है।
  • onDestroy तब चलता है जब Nuclo तत्व को हटा देता है: एक list() पंक्ति हटा दी जाती है, एक when() शाखा स्विच हो जाती है, एक into() अपना क्षेत्र छोड़ देता है, या forceUpdate() इसे छोड़ देता है। यह हटाने से ठीक पहले चलता है, इसलिए तत्व अभी भी दस्तावेज़ में है।

रॉ DOM हटाना

यदि आप node.remove() या innerHTML = '' के साथ किसी तत्व को स्वयं हटाते हैं, तो इसका onDestroy और क्लीनअप कभी नहीं चलता है, और टाइमर चलते रहते हैं। Nuclo में कोई unmount() नहीं है। किसी ऐप को क्लीनअप से हटाने के लिए, उसे when() में लपेटें:

main.ts
let mounted = true
render(() => div(when(() => mounted, App())), root)

mounted = false
update() // App's onDestroy hooks and cleanups run

आदेश और त्रुटियाँ

  • आप किसी भी रूप में जितने चाहें उतने हुक पंजीकृत कर सकते हैं। वे पंजीकरण क्रम में चलते हैं।
  • onDestroy हमेशा पहले चाइल्ड नोड को चलाता है, फिर माता-पिता को।
  • तत्व अपने पहले onMount हुक के क्रम में माउंट होते हैं, जो तर्क क्रम का पालन करता है, और प्रत्येक तत्व अपने सभी हुक को एक साथ चलाता है। यदि माता-पिता उन पर निर्भर हैं तो अपने चाइल्ड नोड से पहले माता-पिता की जिम्मेदारी छोड़ें।
  • एक हुक जो फेंकता है उसे console.error के साथ लॉग किया जाता है। अन्य हुक अभी भी चल रहे हैं.
  • कॉलबैक को कुछ भी नहीं या क्लीनअप फ़ंक्शन लौटाना चाहिए, इसलिए इसे async न बनाएं। लौटाए गए वादे को कभी भी सफाई के रूप में उपयोग नहीं किया जाता है। इसके अंदर async कार्य प्रारंभ करें: { onMount: (el) => { void load(el) } }।
  • हुक कभी भी सर्वर पर नहीं चलते। क्लाइंट पर, वे hydrate() के बाद चलते हैं।
नोट: हुक प्रभाव नहीं हैं। स्टेट बदलने पर वे फिर कभी नहीं भागते। प्रत्येक तत्व प्रति तत्व अधिकतम एक बार चलता है।

Nuclo कभी भी पेज को अपने आप अपडेट नहीं करता है। अपना स्टेट बदलें, फिर update() पर कॉल करें।

counter.ts
let count = 0

const Counter = () =>
  button(() => `Clicked ${count} times`, {
    onClick: () => { count++; update() },
  })

render(Counter)
लाइवcount++ केवल वेरिएबल बदलता है। इस बटन या पेज की किसी दूसरी गतिविधि से अगला update() चलने पर टेक्स्ट अपडेट होता है।खोलें ↗

Clicked 0 times

जितना चाहें उतना स्टेट बदलें, फिर एक बार update() पर कॉल करें:

example.ts
let first = 'Ada'
let last = 'Lovelace'

render(() => p(() => `${first} ${last}`))

first = 'Grace'
last = 'Hopper'
update() // one pass, both changes

इवेंट हैंडलर, टाइमर, फ़ेच कॉलबैक या onMount से update() पर कॉल करें। इसे डायनामिक मान के अंदर से न बुलाएं।

डायनेमिक मान

#

नो पैरामीटर वाला एक फ़ंक्शन एक डायनामिक मान है। Nuclo तत्व बनाते समय इसे एक बार चलाता है, और फिर प्रत्येक update() पर। टेक्स्ट चिल्ड्रेन, विशेषता मान, style और className के लिए गतिशील मानों का उपयोग करें। दिखाई देने वाले, गायब होने वाले या दोहराए जाने वाले चाइल्ड नोड के लिए, when() और list() का उपयोग करें।

example.ts
let name = 'Alice'
let busy = false

const Profile = () =>
  div(
    h1('Welcome'),              // static
    p(() => `Hello, ${name}`),  // dynamic text
    button({ disabled: () => busy }, 'Save'),
  )
लाइवनाम बदलें। स्टैटिक टेक्स्ट एलिमेंट बनते समय तय हो गया था; डायनेमिक टेक्स्ट हर update() पर दोबारा चलता है। forceUpdate() ट्री दोबारा बनाता है, इसलिए स्टैटिक टेक्स्ट भी अपडेट होता है।खोलें ↗

static: Hello, Alice

dynamic: Hello, Alice

Nuclo पैरामीटर गिनती (fn.length) की जाँच करता है। एक फ़ंक्शन जो पैरामीटर घोषित करता है वह एक कस्टम संशोधक है और केवल एक बार चलता है। (x = 0) => … और (...args) => … की लंबाई 0 है, इसलिए वे गतिशील हैं।

गतिशील पाठ

  • एक स्ट्रिंग, संख्या, bigint या बूलियन String(value) के रूप में प्रस्तुत होता है। () => ok && 'Saved' "false" दिखाता है, इसलिए () => (ok ? 'Saved' : '') लिखें।
  • null और undefined खाली टेक्स्ट के रूप में प्रस्तुत होते हैं।
  • A css() या cx() परिणाम पहले रन पर फ़ंक्शन को एक गतिशील वर्ग बनाता है, टेक्स्ट नहीं। cx() देखें।
  • एक नोड, बिल्डर, ऐरे या साधारण ऑब्जेक्ट कुछ भी प्रस्तुत नहीं करता है, और कभी अपडेट नहीं होता है।

गतिशील विशेषताएँ

  • Nuclo कोई मान तभी लिखता है जब वह Nuclo द्वारा लिखे गए अंतिम मान से भिन्न हो। इसकी तुलना लाइव DOM से नहीं की जाती है, इसलिए यदि कोई उपयोगकर्ता इनपुट टाइप करता है और आपका फ़ंक्शन पहले जैसा ही मान लौटाता है, तो उनके द्वारा टाइप किया गया टेक्स्ट बना रहता है।
  • null या undefined का परिणाम कुछ भी नहीं लिखता है, इसलिए पुराना मान बना रहता है। className अपवाद है: कोई भी गलत परिणाम गतिशील वर्गों को हटा देता है और स्थिर वर्गों को रखता है।
  • एक style फ़ंक्शन प्रत्येक अपडेट पर लौटाए गए ऑब्जेक्ट को लागू करता है।

त्रुटियाँ

यदि कोई गतिशील मान फेंकता है, तो Nuclo त्रुटि पकड़ता है और शेष अद्यतन अभी भी चलता है। टेक्स्ट खाली हो जाता है या अपना पुराना मान रखता है, और एक विशेषता अपना पुराना मान रखता है। दोनों अगले update() पर ठीक हो जाते हैं जो सफल होता है। अपवाद: एक टेक्स्ट फ़ंक्शन जो अपने first रन पर फेंकता है वह हमेशा के लिए खाली रहता है।

fnसार्वजनिक API
function update(...scopeIds: string[]): void

पृष्ठ पर प्रत्येक गतिशील मान पर एक सिंक्रोनस पास चलाता है। आईडी के साथ, यह केवल उन scope() आईडी के साथ चिह्नित तत्वों के अंदर क्या अपडेट करता है। प्रत्येक पास इन चरणों को क्रम में चलाता है:

  1. list(): आइटम को दोबारा पढ़ता है और पंक्तियों को जोड़ता, स्थानांतरित या हटाता है।
  2. when(): शर्तों की दोबारा जाँच करता है। एक शाखा का पुनर्निर्माण तभी होता है जब कोई दूसरी शाखा सक्रिय हो जाती है।
  3. गतिशील विशेषताएँ, जिनमें style और className शामिल हैं।
  4. गतिशील पाठ.
  5. onMount।
  • यह सब कुछ चलाता है। प्रत्येक डायनामिक मान प्रत्येक कॉल पर चलता है, भले ही उसका डेटा नहीं बदला हो। केवल DOM लिखना छोड़ दिया गया है। गतिशील मूल्यों को तेज़ और दुष्प्रभावों से मुक्त रखें।
  • दस्तावेज़ में केवल नोड्स. यदि update() चलने पर दस्तावेज़ में कोई नोड नहीं है, तो Nuclo इसे ट्रैक करना बंद कर देता है। आपके द्वारा इसे संलग्न करने के बाद भी यह कभी भी दोबारा अपडेट नहीं होता है। यह list() और when() पर भी लागू होता है। दस्तावेज़ में मौजूद कंटेनर में प्रस्तुत करें।
  • स्थैतिक मान कभी नहीं बदलते। तत्व निर्मित होने पर साधारण स्ट्रिंग और विशेषता मान तय हो जाते हैं। उन्हें फिर से बनाने के लिए, forceUpdate() का उपयोग करें।
  • Errors. टेक्स्ट और विशेषता त्रुटियाँ पकड़ी जाती हैं, और पास जारी रहता है। यदि कोई when() स्थिति खराब हो जाती है, तो त्रुटि लॉग हो जाती है और when() अपडेट होना बंद हो जाता है। यदि कोई list() items या renderItem फ़ंक्शन फेंकता है, तो त्रुटि update() से बच जाती है और शेष चरण नहीं चलते हैं।
  • Server. update() सर्वर पर कुछ नहीं करता है।
लाइवहर update() सभी डायनेमिक मान चलाता है, भले उनका डेटा न बदला हो। इस पेज के दूसरे डेमो भी काउंटर बढ़ाते हैं।खोलें ↗

This dynamic value has run 1 times.

fnसार्वजनिक API
function scope(...ids: string[]): NodeModFn

तत्व देता है इसे एक या अधिक स्कोप आईडी में पास किया जाता है। update('id') तब केवल वही अपडेट करता है जो उस तत्व के अंदर है, जिसमें तत्व की अपनी विशेषताएं भी शामिल हैं।

example.ts
let cartItems: string[] = []
let user = 'Ada'

const App = () =>
  div(
    header(() => `Hi, ${user}`),
    aside(scope('cart'), span(() => `Items: ${cartItems.length}`)),
  )

render(App)

cartItems.push('apple')
user = 'Grace'
update('cart') // "Items: 1", the header still says "Hi, Ada"
update()       // everything, the header now says "Hi, Grace"
लाइवदोनों बटन एक आइटम जोड़ते हैं और उपयोगकर्ता का नाम बदलते हैं। update('cart') केवल उस स्कोप का पैनल अपडेट करता है।खोलें ↗
Hi, Ada
Items: 0
  • एक तत्व में कई आईडी हो सकती हैं: scope('cart', 'sidebar')।
  • update('a', 'b') a या b के अंदर सब कुछ अपडेट करता है। यदि कई तत्व एक ही आईडी का उपयोग करते हैं, तो वे सभी अपडेट हो जाते हैं।
  • नेस्टेड स्कोप के साथ, update('outer') में आंतरिक स्कोप शामिल है। update('inner') बाहरी हिस्से को नहीं छूता है।
  • एक आईडी जिसमें दस्तावेज़ के किसी भी तत्व को nothing अपडेट किया गया है और कोई त्रुटि नहीं है, इसलिए वर्तनी की जांच करें।
  • scope() सर्वर पर कुछ नहीं करता है।
fnसार्वजनिक API
function when(condition: boolean | (() => boolean), ...content: NodeModLike[]): WhenBuilder interface WhenBuilder { when(condition: boolean | (() => boolean), ...content: NodeModLike[]): WhenBuilder else(...content: NodeModLike[]): WhenBuilder }

एक शर्त के आधार पर सामग्री प्रस्तुत करता है। Nuclo प्रत्येक update() पर स्थितियों की दोबारा जाँच करता है। पहली सच्ची स्थिति जीतती है। यदि कोई भी सत्य नहीं है, तो .else() सामग्री प्रस्तुत होती है। .else() के बिना, कुछ भी प्रस्तुत नहीं होता।

status.ts
let status: 'loading' | 'error' | 'ready' = 'loading'

const Status = () =>
  div(
    when(() => status === 'loading', p('Loading…'))
      .when(() => status === 'error', p('Something went wrong'))
      .else(p('Ready')),
  )
लाइवपहली सही शर्त चुनी जाती है; कोई शर्त सही न हो तो .else() रेंडर होता है। नई शाखा शुरू से बनती है।खोलें ↗

Loading…

शर्तें

  • स्टेट के लिए, एक फ़ंक्शन पास करें। एक साधारण बूलियन एक बार पढ़ा जाता है: when(open, …) कभी नहीं बदलता।
  • TypeScript में फ़ंक्शन को एक बूलियन लौटाना होगा। () => items.length > 0 या () => user != null लिखें.

क्या अपडेट

  • जबकि एक ही शाखा सक्रिय रहती है, इसके तत्व बने रहते हैं। इसके अंदर गतिशील मान, सूचियाँ और when() ब्लॉक अपडेट होते रहते हैं।
  • जब एक अलग शाखा सक्रिय हो जाती है, तो पुरानी सामग्री हटा दी जाती है और उसके onDestroy हुक चलते हैं। नई शाखा एकदम नए सिरे से बनाई गई है और इसके onMount हुक चलते हैं। पुराने तत्वों की स्थिति, जैसे इनपुट टेक्स्ट या फ़ोकस, खो जाती है।

शाखा सामग्री

चाइल्ड नोड को एक शाखा में रखें: टेक्स्ट, डायनेमिक टेक्स्ट, बिल्डर्स, list() और नेस्टेड when()। p('Hi') जैसे बिल्डरों को पास करें, DOM नोड्स को नहीं: एक DOM नोड अपनी शाखा के एक बार छिपने के बाद अपडेट होना बंद कर देता है।

विशेषता ऑब्जेक्ट और on() मूल तत्व पर लागू होते हैं, और वे शाखा के छिपने के बाद भी बने रहते हैं। एक सशर्त विशेषता के लिए, एक गतिशील विशेषता का उपयोग करें: { className: () => (active ? 'active' : '') }।

चेनिंग

.when() और .else() एक नया बिल्डर लौटाते हैं और मूल को नहीं बदलते हैं। यदि आप .else() को दो बार कॉल करते हैं, तो अंतिम कॉल जीत जाती है।

fnसार्वजनिक API
function list<T>( items: () => readonly T[] | Iterable<T>, renderItem: (item: T, index: number) => ListRenderResult, ): ListModifier

प्रति आइटम एक पंक्ति प्रस्तुत करता है। प्रत्येक update() पर, Nuclo फिर से items() को कॉल करता है और पहचान के आधार पर नए आइटम को मौजूदा पंक्तियों से मिलाता है (===)।

fruits.ts
let fruits = ['Apple', 'Banana']

const Fruits = () => ul(list(() => fruits, (fruit) => li(fruit)))

render(Fruits)

fruits.push('Cherry')
update()

पंक्तियों का मिलान कैसे किया जाता है

  • एक आइटम जो अभी भी वहां है, अपनी पंक्ति बनाए रखता है। यदि आवश्यक हो तो Nuclo पंक्ति को स्थानांतरित करता है, और renderItem को दोबारा कॉल नहीं करता है।
  • एक नए आइटम को एक नई पंक्ति मिलती है। हटाया गया आइटम अपनी पंक्ति खो देता है, और उसके onDestroy हुक चलते हैं।
  • डुप्लिकेट आइटमों में से प्रत्येक को अपनी पंक्ति मिलती है।
  • आप सरणी को उसके स्थान पर बदल सकते हैं (push, splice, sort) या एक नई सरणी लौटा सकते हैं।

एक रखी गई पंक्ति को दोबारा कभी प्रस्तुत नहीं किया जाता है, इसलिए बदलने वाले फ़ील्ड के लिए गतिशील मानों का उपयोग करें:

todos.ts
const todos = [{ text: 'Write docs', done: false }]

const Todos = () =>
  ul(
    list(() => todos, (todo) =>
      li({ className: () => (todo.done ? 'done' : '') }, () => todo.text),
    ),
  )

render(Todos)

todos[0].done = true
update() // same <li>, its class is now "done"

प्रतियां नए आइटम हैं, इसलिए सरणी को todos.map((t) => ({ ...t })) से बदलने से प्रत्येक पंक्ति का पुनर्निर्माण होता है।

लाइवपंक्तियाँ ऑब्जेक्ट की पहचान से मिलाई जाती हैं। पंक्ति पर चेक लगाएँ, फिर Reverse दबाएँ: चेकबॉक्स सहित वही <li> खिसकता है।खोलें ↗

renderItem क्या लौटाता है

  • एक तत्व लौटाएं, जैसे li(...), tr(...) या SVG बिल्डर। किसी आइटम को छोड़ने के लिए null या undefined लौटें। स्ट्रिंग्स, टेक्स्ट नोड्स और टुकड़े कुछ भी प्रस्तुत नहीं करते हैं।
  • सीधे when() या list() वापस न करें। उन्हें पंक्ति तत्व के अंदर रखें: li(todo.text, when(() => todo.done, ' ✓'))।
  • आइटम छिपाने के लिए, items() में फ़िल्टर करें, renderItem में नहीं।
  • renderItem को दुष्प्रभावों से मुक्त रखें। Nuclo एक ही आइटम के लिए इसे एक से अधिक बार कॉल कर सकता है।

सूचकांक

index आइटम की वह स्थिति है जब उसकी पंक्ति बनाई गई थी। पंक्तियों के हिलने पर यह अद्यतन नहीं होता है। वर्तमान स्थिति के लिए, एक गतिशील मान का उपयोग करें: li(() => `${names.indexOf(name) + 1}. ${name}`)।

पुनरावर्तनीय

कोई भी पुनरावर्तनीय कार्य: सरणियाँ, Set, जनरेटर और map.values()। हर बार एक ही सरणी या Set लौटाना ठीक है। एक पुनरावर्तक, जैसे जनरेटर या map.values(), का उपयोग एक पास के बाद किया जाता है, इसलिए इसे प्रत्येक कॉल पर items() के अंदर बनाएं। Map को पुनरावृत्त करने से हर बार सीधे नए [key, value] सरणियाँ बनती हैं, इसलिए प्रत्येक पंक्ति का पुनर्निर्माण किया जाता है। इसके बजाय map.values() का उपयोग करें।

प्लेसमेंट

पंक्तियाँ वहाँ दिखाई देती हैं जहाँ list() दो टिप्पणी मार्करों के बीच अपने भाई-बहनों के बीच बैठता है। माता-पिता के अन्य चाइल्ड नोड को छुआ नहीं जाता है। list() SVG बिल्डर्स के अंदर भी काम करता है।

fnसार्वजनिक API
function region(options: RegionOptions): NodeModFn function into(id: string, ...content: NodeModLike[]): NodeModFn function into(regions: Record<string, NodeModLike | NodeModLike[]>): NodeModFn interface RegionOptions { id: string type?: 'latest' | 'stack' // default: 'latest' empty?: NodeModLike }

region() ट्री में एक नामित स्थान को चिह्नित करता है। into() इसे कहीं और से, किसी भी गहराई से भर देता है। लेआउट सामग्री को प्रोप के रूप में प्राप्त नहीं करता है, और सामग्री बनाने वाले घटक को यह नहीं पता होता है कि वह कहां पहुंचती है।

app.ts
const Layout = () =>
  div(
    aside(nav(a({ href: '/' }, 'Home'))),
    main(region({ id: 'main', empty: p('Nothing open.') })),
  )

// Anywhere in the tree, before or after the region
const Article = () => into('main', article(h1('Hello')))

render(() => div(Layout(), Article()))

यह कैसे काम करता है

  • into() केवल एक टिप्पणी छोड़ता है जहां यह लिखा जाता है। इसकी सामग्री क्षेत्र के मार्करों के बीच प्रस्तुत होती है।
  • ऑर्डर कोई मायने नहीं रखता। अपने क्षेत्र से पहले निर्मित एक into() प्रतीक्षा करता है और उस क्षण को दिखाता है जब क्षेत्र का निर्माण होता है। दृश्य उसी क्रम में दिखते हैं जिस क्रम में वे लिखे गए थे।
  • सामग्री तब तक जीवित रहती है जब तक दृश्य रहता है। जब into() रखने वाला when() बंद हो जाता है, या इसकी list() पंक्ति हटा दी जाती है, तो सामग्री क्षेत्र छोड़ देती है। इसे दोबारा खोलने पर एक प्रति जुड़ती है, दो नहीं।
  • जब कोई क्षेत्र चला जाता है, उदाहरण के लिए उसका when() बंद हो जाता है, तो उसके दृश्य फिर से प्रतीक्षा करते हैं। बाद में उसी आईडी से बनाया गया क्षेत्र उन्हें दिखाता है।
  • एक into() जिसकी आईडी किसी भी क्षेत्र से मेल नहीं खाती है, कुछ भी प्रस्तुत नहीं करती है और कोई त्रुटि उत्पन्न नहीं करती है, इसलिए वर्तनी की जांच करें।
  • आईडी ट्री में अद्वितीय हैं। लाइव आईडी के अंतर्गत निर्मित दूसरा क्षेत्र एक चेतावनी लॉग करता है।
  • क्षेत्र सर्वर रेंडरिंग के साथ काम करते हैं। hydrate() पर, प्रत्येक दृश्य सर्वर द्वारा इसे दोबारा बनाने के बजाय प्रस्तुत किए गए HTML का दावा करता है।

विकल्प

  • id: क्षेत्र को खोजने के लिए into() नाम का उपयोग किया जाता है।
  • type: 'latest' (डिफ़ॉल्ट) केवल नवीनतम दृश्य दिखाता है। 'stack' प्रत्येक दृश्य को आगमन क्रम में दिखाता है।
  • empty: उस समय प्रस्तुत किया गया जब इस क्षेत्र में कोई दृश्य नहीं है।

स्टैक्ड क्षेत्र

type: 'stack' के साथ, एक दृश्य जो दूसरे के ऊपर खुलता है वह नीचे वाले का पुनर्निर्माण नहीं करता है। इसका DOM, फोकस और फॉर्म स्टेट स्टे है। किसी पृष्ठ पर संवाद जैसी परत के लिए इसका उपयोग करें।

layers.ts
let open = false

const App = () =>
  div(
    region({ id: 'main', type: 'stack' }),
    into('main', form(input({ placeholder: 'Keeps its text' }))),
    when(() => open, into('main', div('On top'))),
  )

एक साथ कई क्षेत्र

एक into() से एक से अधिक क्षेत्रों को भरने के लिए एक ऑब्जेक्ट पास करें। एक मान एक बच्चा या चाइल्ड नोड की एक श्रृंखला हो सकता है। कोई भी रूप घोंसला बनाता है: किसी अन्य दृश्य की सामग्री के अंदर एक into() अपने स्वयं के क्षेत्र को लक्षित करता है।

page.ts
const Page = () =>
  into({
    main: article(h1('Eager route')),
    sidebar: [h2('Related'), ul(li('One'), li('Two'))],
  })

ट्री की गहराई से एक संवाद खुला

ओवरले क्षेत्र मूल पर बैठता है. कोई भी घटक, किसी भी गहराई पर, into() को when() में लपेटकर एक संवाद खोलता है। when() को बंद करने से क्षेत्र से संवाद हट जाता है।

dialog.ts
let confirming = false

const App = () =>
  div(
    Page(),
    div({ className: 'overlay' }, region({ id: 'overlay' })),
  )

// Deep inside Page()
const DeleteButton = () =>
  div(
    button('Delete', { onClick: () => { confirming = true; update() } }),
    when(() => confirming,
      into('overlay',
        dialog({ open: true },
          p('Delete this item?'),
          button('Cancel', { onClick: () => { confirming = false; update() } }),
        ),
      ),
    ),
  )

टोस्ट

list() की प्रत्येक पंक्ति एक दृश्य है। एक स्टैक्ड क्षेत्र उन्हें क्रम में दिखाता है, और सरणी से किसी आइटम को हटाने से उसका टोस्ट हट जाता है।

toasts.ts
const toasts: { text: string }[] = []

const notify = (text: string) => {
  toasts.push({ text })
  update()
}

const dismiss = (toast: { text: string }) => {
  toasts.splice(toasts.indexOf(toast), 1)
  update()
}

const App = () =>
  div(
    button('Save', { onClick: () => notify('Saved.') }),
    div({ className: 'toasts' }, region({ id: 'toasts', type: 'stack' })),
    list(() => toasts, (toast) =>
      into('toasts', div(toast.text, button('×', { onClick: () => dismiss(toast) }))),
    ),
  )

पृष्ठ द्वारा निर्धारित एक पृष्ठ शीर्षक

डिफ़ॉल्ट type: 'latest' के साथ, नवीनतम दृश्य जीतता है। लेआउट शीर्षक का स्वामी होता है, और जो भी पृष्ठ खुला होता है उसे नाम देता है। बिना into('title', …) वाला पेज empty टेक्स्ट छोड़ता है।

title.ts
let page: 'home' | 'settings' = 'home'

const Layout = () =>
  div(
    h1(region({ id: 'title', empty: 'My app' })),
    when(() => page === 'home', Home()).else(Settings()),
  )

const Home = () => p('Welcome.')

const Settings = () =>
  div(
    into('title', 'Settings'),
    form(/* … */),
  )

गतिशील मान

into() की सामग्री सामान्य Nuclo सामग्री है। इसमें फ़ंक्शन स्टेट-निर्भर मान हैं और क्षेत्र जहां भी हो, update() पर ताज़ा होते हैं।

status.ts
const selected = new Set<string>()

const Layout = () =>
  div(
    header(region({ id: 'status' })),
    Table(),
  )

const Table = () =>
  div(
    into('status', () => `${selected.size} selected`),
    list(() => rows, (row) =>
      label(
        input({ type: 'checkbox', onChange: (e) => {
          e.currentTarget.checked ? selected.add(row.id) : selected.delete(row.id)
          update()
        } }),
        row.name,
      ),
    ),
  )

एक क्षेत्र जो आता और जाता है

डैशबोर्ड का into() एक बार लिखा है। जबकि उपयोगकर्ता साइन आउट है, कोई main क्षेत्र नहीं है, इसलिए दृश्य प्रतीक्षा करता है। साइन इन करने से क्षेत्र बनता है और दृश्य दिखता है। साइन आउट करने से क्षेत्र हट जाता है और दृश्य फिर से प्रतीक्षा करता है।

auth.ts
let user: { name: string } | null = null

const App = () =>
  div(
    when(() => user !== null,
      main(region({ id: 'main' })),
    ).else(
      LoginForm(),
    ),
    into('main', Dashboard()),
  )

अपने क्षेत्र के साथ एक दृश्य

एक दृश्य में region() हो सकता है, और अन्य दृश्य इसे लक्षित कर सकते हैं। यहां संपादक पृष्ठ अपने साथ एक tabs क्षेत्र लाता है, और प्रत्येक टैब इसे भरता है। जब संपादक बंद हो जाता है, तो उसका क्षेत्र उसके साथ चला जाता है।

editor.ts
let tab: 'code' | 'preview' = 'code'

const Editor = () =>
  into('main',
    div(
      nav(
        button('Code', { onClick: () => { tab = 'code'; update() } }),
        button('Preview', { onClick: () => { tab = 'preview'; update() } }),
      ),
      section(region({ id: 'tabs' })),
    ),
    when(() => tab === 'code', into('tabs', textarea()))
      .else(into('tabs', iframe({ src: '/preview' }))),
  )

लाइव उदाहरण

region-app.ts
import 'nuclo'

let page: 'home' | 'article' | null = 'home'
let dialog = false

const go = (next: typeof page) => () => {
  page = next
  dialog = false
  update()
}

const Layout = () =>
  div(
    nav(
      button('Home', { onClick: go('home') }),
      button('Article', { onClick: go('article') }),
      button('Close page', { onClick: go(null) }),
    ),
    main(
      region({ id: 'main', type: 'stack', empty: p('Nothing open.') }),
    ),
    aside(region({ id: 'side', empty: p('No related links.') })),
  )

// Pages fill the layout's regions from outside it.
const Home = () =>
  into('main',
    input({ placeholder: 'Type here, then open the dialog' }),
    button('Open dialog', {
      onClick: () => { dialog = true; update() },
    }),
  )

const Article = () =>
  into({
    main: article(h2('Article'), p('One into() fills both regions.')),
    side: [h2('Related'), ul(li('One'), li('Two'))],
  })

// Stacks over Home, so Home keeps its DOM and the text you typed.
const Dialog = () =>
  into('main',
    div(
      p('On top of Home.'),
      button('Close', { onClick: () => { dialog = false; update() } }),
    ),
  )

export const RegionApp = () =>
  div(
    Layout(),
    when(() => page === 'home', Home()),
    when(() => page === 'article', Article()),
    when(() => dialog, Dialog()),
  )
लाइवमुख्य और साइड रीजन वाला लेआउट बाहर से भरा जाता है। Home में लिखें, डायलॉग खोलकर बंद करें: टेक्स्ट बना रहता है। Article एक into() से दोनों रीजन भरता है।खोलें ↗

No related links.

fnसार्वजनिक API
function forceUpdate(): void function forceUpdate(nodeModFn: NodeModFn | (() => NodeModFn), parent?: Element): ExpandedElement

update() केवल गतिशील मान चलाता है। स्थैतिक मान, जैसे कि h1(labels[lang].title) में पाठ, ट्री के निर्माण के समय तय किए जाते हैं। forceUpdate() आपके घटकों को फिर से कॉल करता है और लाइव DOM को मिलान करने के लिए पैच करता है, स्थिर मान शामिल हैं। भाषा बदलने जैसे दुर्लभ, ऐप-व्यापी परिवर्तनों के लिए इसका उपयोग करें। बाकी सभी चीज़ों के लिए update() का उपयोग करें।

i18n.ts
const labels = { en: { title: 'Hello' }, pt: { title: 'Olá' } }
let lang: keyof typeof labels = 'en'

const App = () => div(h1(labels[lang].title))
render(App, root)

lang = 'pt'
forceUpdate() // same <h1> element, now "Olá"
लाइवupdate() केवल डायनेमिक lang पंक्ति अपडेट करता है। forceUpdate() App को दोबारा कॉल करके उसी <h1> को पैच करता है। स्पष्ट रूप केवल इसी ट्री को दोबारा बनाता है, पूरे पेज को नहीं।खोलें ↗

lang = 'en'

Hello

Static text, fixed on build.

बिना किसी तर्क के, forceUpdate() render(App) या hydrate(App) के साथ रेंडर किए गए प्रत्येक रूट का पुनर्निर्माण करता है। render(App()) के साथ रेंडर किए गए रूट छोड़ दिए गए हैं। यदि कोई घटक फेंकता है, तो त्रुटि लॉग हो जाती है और वह रूट अपना पुराना DOM रखता है। बाद में जब नई बिल्ड लागू की जाती है तो एक त्रुटि भी लॉग की जाती है, लेकिन पहले से पैच किए गए हिस्से पैच किए हुए रहते हैं।

क्या रहता है और क्या बदलता है

  • चाइल्ड नोड का मिलान क्रम से, टैग द्वारा किया जाता है। एक मिलान तत्व का पुन: उपयोग किया जाता है, इसलिए फोकस और टाइप किए गए इनपुट मान बने रहते हैं, और इसके onMount हुक दोबारा नहीं चलते हैं। एक बार जब एक चाइल्ड नोड का टैग अलग हो जाता है, तो उस चाइल्ड नोड और उसके बाद के प्रत्येक भाई-बहन का निर्माण नए सिरे से होता है।
  • टेक्स्ट, विशेषताएँ, वर्ग, शैलियाँ, श्रोता, when() शाखाएँ और list() पंक्तियाँ नए निर्माण का अनुसरण करती हैं।
  • नए तत्व बनाए जाते हैं, और उनके onMount हुक चलते हैं। जो तत्व नया निर्माण नहीं करता है उन्हें हटा दिया जाता है, और उनके onDestroy हुक चलते हैं।
  • list() पंक्तियों का मिलान स्थिति से भी किया जाता है, आइटम से नहीं, इसलिए पुन: उपयोग किया गया पंक्ति तत्व एक अलग आइटम दिखा सकता है।
  • नई बिल्ड विशेषताएँ अब सेट नहीं हटाई जाती हैं।
देखें: घटक फ़ंक्शन के अंदर घोषित स्थिति रीसेट हो जाती है, क्योंकि फ़ंक्शन फिर से चलता है। ऐसी स्थिति रखें जो घटक के बाहर जीवित रहनी चाहिए।

स्पष्ट फॉर्म. forceUpdate(App(), parent) parent में एक ट्री का पुनर्निर्माण करता है और उसकी जड़ लौटाता है। यह बाद की कॉल के लिए रूट को पंजीकृत नहीं करता है। hydrate() की तरह, यह उम्मीद करता है कि रूट parent में पहला नोड होगा। यदि पहले नोड में एक अलग टैग है, तो उसे बदल दिया जाता है।

सर्वर पर, forceUpdate() कुछ नहीं करता है।

render()

#
fnसार्वजनिक API
function render(nodeModFn: NodeModFn | (() => NodeModFn), parent?: Element, index?: number): ExpandedElement

आपका ट्री बनाता है और उसके मूल तत्व को parent में जोड़ता है। parent डिफ़ॉल्ट रूप से document.body है। यह मूल तत्व लौटाता है।

main.ts
import 'nuclo'
import { App } from './app.ts'

render(App, document.getElementById('app')!)
  • आप घटक (render(App)) या इसे लौटाने वाले बिल्डर (render(App())) को पास कर सकते हैं। DOM वही है. केवल render(App) forceUpdate() को बाद में रूट का पुनर्निर्माण करने देता है, इसलिए इसे प्राथमिकता दें।
  • यह जुड़ता है। यह parent को साफ़ नहीं करता है, इसलिए इसे दो बार कॉल करने से दो प्रतियां जुड़ जाती हैं।
  • render() के वापस आने पर ट्री में प्रत्येक onMount चल चुका होता है।
  • दस्तावेज़ में मौजूद तत्व में प्रस्तुत करें। update() देखें।
  • रूट एक तत्व होना चाहिए, जैसे div(...), when() या list() नहीं।
  • index आंतरिक है। यह यह नहीं चुनता कि तत्व कहाँ डाला गया है। उसे छोड़ दो।

render(App) के रूप में पारित एक घटक को कोई पैरामीटर नहीं लेना चाहिए। एक आवश्यक पैरामीटर एक प्रकार की त्रुटि है. एक वैकल्पिक टाइप-चेक करता है, लेकिन render() रनटाइम पर थ्रो करता है। एक घटक लपेटें जो प्रॉप्स लेता है:

main.ts
const Page = (props: { name: string }) => div(h1('Hi ', props.name))

render(() => Page({ name: 'Ana' }), root)

hydrate()

#
fnसार्वजनिक API
function hydrate(nodeModFn: NodeModFn | (() => NodeModFn), parent?: Element): ExpandedElement

ने renderToString() द्वारा निर्मित HTML का अधिग्रहण किया। यह मौजूदा तत्वों का पुन: उपयोग करता है, ईवेंट श्रोताओं को जोड़ता है, और गतिशील मान, when() और list() को जोड़ता है, इसलिए update() बाद में काम करता है। फिर यह onMount हुक चलाता है। parent डिफ़ॉल्ट रूप से document.body है। यह मूल तत्व लौटाता है।

client.ts
import 'nuclo'
import { App } from './app.ts'

hydrate(App, document.getElementById('root')!)
  • सर्वर के समान घटक और समान डेटा का उपयोग करें।
  • एक समर्पित कंटेनर में हाइड्रेट करें। ऐप का रूट उसका पहला नोड होना चाहिए, हालांकि व्हाइटस्पेस ठीक है। यदि कोई अन्य नोड पहले आता है, तो Nuclo उस नोड को बदल देता है और सर्वर कॉपी को पीछे छोड़ देता है।
  • render() की तरह, App पास करें, App() नहीं, इसलिए forceUpdate() बाद में रूट को फिर से बना सकता है। App को कोई पैरामीटर नहीं लेना चाहिए। एक वैकल्पिक के साथ, hydrate() चुपचाप कुछ भी नहीं जोड़ता है। एक घटक लपेटें जो प्रॉप्स लेता है: hydrate(() => Page(props), root)।
  • यदि कोई सर्वर HTML नहीं है, तो render() का उपयोग करें।

जब सर्वर और क्लाइंट भिन्न होते हैं

ग्राहक जीतता है:

  • एक अलग रूट टैग: उस नोड को ताज़ा DOM से बदल दिया गया है। एक खाली कंटेनर: ताज़ा DOM जोड़ा गया है।
  • अलग पाठ पैच किया गया है। विभिन्न list() आइटम और when() शाखाएँ ग्राहक का अनुसरण करती हैं।
  • अतिरिक्त सर्वर चिल्ड्रेन हटा दिए जाते हैं, और गायब चिल्ड्रन बनाए जाते हैं।
  • विशेषताएँ जो सर्वर ने लिखीं लेकिन क्लाइंट ने सेट नहीं कीं, उन्हें वैसे ही छोड़ दिया गया है।

renderToString() HTML टिप्पणियों को मार्कर के रूप में लिखता है, जैसे प्रत्येक टेक्स्ट नोड से पहले <!-- text-0 -->। hydrate() को उनकी आवश्यकता है, इसलिए सर्वर HTML से टिप्पणियाँ न हटाएँ। मार्करों के बिना, हाइड्रेशन list() पंक्तियों को हटा देता है, और list() और when() अपडेट करना बंद कर देते हैं।

सर्वर रेंडरिंग

#
fnसार्वजनिक API
// import { ... } from 'nuclo/ssr' function renderToString(input: RenderableInput): string function renderManyToString(inputs: RenderableInput[]): string[] function renderToStringWithContainer(input: RenderableInput, containerTag?: string, containerAttrs?: Record<string, string>): string function getCssText(): string type RenderableInput = NodeModFn | (() => NodeModFn) | Element | Node | null | undefined

सर्वर पर एक घटक को HTML स्ट्रिंग में प्रस्तुत करें। ब्राउज़र में, उस HTML को इंटरैक्टिव बनाने के लिए उसी घटक के साथ hydrate() पर कॉल करें। सर्वर रनटाइम में कोई document नहीं है, इसलिए पहले nuclo/polyfill आयात करें।

app.ts
let count = 0
const heading = css({ color: 'red' })

export const App = () =>
  main(
    h1(heading, () => `Count: ${count}`),
    button({ onClick: () => { count++; update() } }, '+1'),
  )
server.ts
import 'nuclo/polyfill'
import 'nuclo'
import { renderToString, getCssText } from 'nuclo/ssr'
import { App } from './app.ts'

const html = renderToString(App)
const page = `<!doctype html>
<html>
  <head><style id="nuclo-styles">${getCssText()}</style></head>
  <body>
    <div id="root">${html}</div>
    <script type="module" src="/client.js"></script>
  </body>
</html>`
client.ts
import 'nuclo'
import { App } from './app.ts'

hydrate(App, document.getElementById('root')!)

स्टाइल टैग पर id="nuclo-styles" रखें। क्लाइंट उस तत्व का पुन: उपयोग करता है और केवल नए नियम जोड़ता है। getCssText() देखें।

सर्वर पर क्या चलता है

  • प्रत्येक renderToString() कॉल ट्री बनाता है और प्रत्येक गतिशील मान को एक बार चलाता है।
  • इवेंट हैंडलर, onMount और onDestroy कभी नहीं चलते। update(), scope() और forceUpdate() कुछ नहीं करते।
  • रेंडरिंग सिंक्रोनस है। पहले अपना डेटा लोड करें, फिर रेंडर करें।
  • रेंडर के बाद कुछ भी पंजीकृत नहीं रहता है, इसलिए एक प्रक्रिया कई अनुरोधों को पूरा कर सकती है। अनुरोध डेटा को तर्क के रूप में पास करें, जैसे renderToString(Page(user))। मॉड्यूल-स्तरीय चर सभी अनुरोधों द्वारा साझा किए जाते हैं।
  • सर्वर पर render() को कॉल न करें। यह साझा document.body से जुड़ता है।

renderToString()

एक बिल्डर (App()), एक घटक (App) या एक DOM नोड लेता है। null और undefined "" लौटाते हैं। जड़ एक तत्व होना चाहिए. renderToString(Page) के रूप में पारित घटक को कोई पैरामीटर नहीं लेना चाहिए, इसलिए डेटा लेने वाले घटक के लिए Page(user) पास करें।

example.ts
renderToString(p('Hello'))
// '<p><!-- text-0 -->Hello</p>'
  • टेक्स्ट और विशेषता मान HTML-एस्केप्ड हैं। script() और style() के अंदर टेक्स्ट वैसे ही लिखा गया है, केवल क्लोजिंग टैग टूटे हुए हैं, इसलिए वहां कभी भी यूजर इनपुट न डालें।
  • यह फेंकता नहीं है। यदि घटक, एक list() फ़ंक्शन या एक when() स्थिति विफल हो जाती है, या पॉलीफ़िल गायब है, तो यह त्रुटि लॉग करता है और "" लौटाता है। यदि अनुरोध विफल हो जाए तो खाली परिणाम की जाँच करें। एक टेक्स्ट या विशेषता फ़ंक्शन जो फेंकता है वह केवल खाली प्रस्तुत करता है।

renderManyToString()

inputs.map(renderToString) के समान।

renderToStringWithContainer()

आउटपुट को एक तत्व में लपेटता है। containerTag डिफ़ॉल्ट रूप से "div" पर और containerAttrs पर डिफ़ॉल्ट रूप से {} पर सेट होता है। विशेषता मान बच गए हैं. गुण नाम दिए गए अनुसार लिखे गए हैं, इसलिए class जैसे HTML नामों का उपयोग करें। उपयोगकर्ता इनपुट को कभी भी टैग के रूप में पास न करें.

example.ts
renderToStringWithContainer(p('Hi'), 'section', { id: 'main', class: 'page' })
// '<section id="main" class="page"><p><!-- text-0 -->Hi</p></section>'

nuclo/polyfill

#
import 'nuclo/polyfill' // Named exports: // document, Event, CustomEvent, Node, Element, HTMLElement, // NucloDocument, NucloElement, NucloText, NucloNode

एक छोटा DOM ताकि Nuclo नोड और बन में ट्री बना सके। अपने पहले रेंडर से पहले इसे एक बार आयात करें। अधिकांश ऐप्स को केवल साधारण आयात की आवश्यकता होती है।

  • जब कोई window नहीं होता है, तो यह globalThis.document, Node, Element और HTMLElement सेट करता है, लेकिन केवल वे जो गायब हैं। यह window नहीं बनाता है.
  • किसी ब्राउज़र में, या jsdom के अंतर्गत, यह कुछ नहीं करता है।
  • Event और CustomEvent रनटाइम के स्वयं के कंस्ट्रक्टर हैं। Node, Element और HTMLElement पॉलीफ़िल क्लास के उपनाम हैं।

यह ट्री बनाने के लिए DOM है, ब्राउज़र नहीं। इसमें कोई innerHTML नहीं है और कोई getElementById नहीं है, querySelector() रिटर्न null है, और इवेंट श्रोताओं को नजरअंदाज कर दिया जाता है। HTML प्राप्त करने के लिए renderToString() का उपयोग करें।

css()

#
fnसार्वजनिक API
function css(style: Style): StyleResult function css(name: string, style: Style): StyleResult type StyleResult = { className: string }

एक टाइप की गई स्टाइल ऑब्जेक्ट को जेनरेट किए गए CSS क्लास में बदल देता है। परिणाम को किसी भी टैग बिल्डर को भेजें। ब्राउज़र में, नियम एक <style id="nuclo-styles"> तत्व में जाते हैं। सर्वर पर, getCssText() उन्हें लौटाता है।

card.ts
const card = css({
  p: 16,
  rounded: 8,
  bg: '#fff7ed',
  hover: { bg: '#ffedd5' },
})

const Card = () => div(card, 'Simple card')
  • css() आधार घोषणाओं के लिए एक वर्ग बनाता है, साथ ही प्रत्येक छद्म कुंजी, चयनकर्ता और मीडिया ब्लॉक के लिए एक वर्ग बनाता है। उपरोक्त कार्ड को दो वर्ग मिलते हैं।
  • क्लास नाम सामग्री के हैश हैं। समान शैलियाँ एक वर्ग साझा करती हैं, और सर्वर और क्लाइंट समान नाम उत्पन्न करते हैं।
  • मॉड्यूल स्तर पर एक बार शैलियों को परिभाषित करें। css() ऑब्जेक्ट द्वारा कैश होता है, इसलिए प्रत्येक कॉल पर एक नया ऑब्जेक्ट हर बार फिर से संसाधित होता है।
  • किसी स्टाइल ऑब्जेक्ट को css() में पास करने के बाद उसे बदलने से कुछ नहीं होता है।
  • प्रत्येक शैली पृष्ठ या सर्वर प्रक्रिया के जीवनकाल के लिए पंजीकृत रहती है। उन मानों के लिए जो प्रति आइटम बदलते हैं, जैसे डेटा से चौड़ाई, style विशेषता का उपयोग करें।

नामांकित शैलियाँ

पठनीय वर्ग नाम पाने के लिए पहले एक नाम पास करें:

title.ts
const pageTitle = css('page-title', { text: 24, hover: { color: 'red' } })

pageTitle.className // "page-title page-title-<hash>"
  • बेस क्लास बिल्कुल नाम है। अन्य ब्लॉकों को name-<hash> मिलता है।
  • नाम एक वैध वर्ग नाम होना चाहिए। अन्यथा Nuclo चेतावनी देता है और एक उत्पन्न करता है।
  • नाम वैश्विक हैं। यदि दो अलग-अलग शैलियाँ एक ही नाम का उपयोग करती हैं, तो Nuclo चेतावनी देता है।

परिणाम का उपयोग करना

example.ts
const card = css({ p: 8 })

div(card)                           // best
div(card, { className: 'featured' }) // className merges
div(card, { class: 'featured' })     // replaces: only "featured" is left

String(card) और card.className दोनों वर्ग नाम लौटाते हैं।

स्टाइल ऑब्जेक्ट

#

आप जिस ऑब्जेक्ट को css() पर पास करते हैं, वह camelCase में सामान्य CSS गुणों, शॉर्टहैंड का एक सेट और छद्म स्टेटों, चयनकर्ताओं और मीडिया प्रश्नों के लिए नेस्टेड कुंजियों का उपयोग करता है। TypeScript उन सभी को स्वत: पूर्ण करता है। टाइप किए गए सेट के बाहर की संपत्तियाँ, जैसे borderTopLeftRadius, fill या paddingInline, raw में जाती हैं (नीचे देखें)।

मान

  • संख्याएँ 0 और इकाई रहित संपत्तियों को छोड़कर px बन जाती हैं: zIndex, opacity, fontWeight, lineHeight, flex, flexGrow, flexShrink, order, aspectRatio, gridColumn और gridRow। raw में, zoom, scale, column-count, orphans, widows, tab-size और animation-iteration-count भी यूनिटलेस हैं।
  • स्ट्रिंग्स का उपयोग लिखित रूप में किया जाता है। null, undefined और false को छोड़ दिया गया है।
  • lineHeight (और leading) इकाई रहित है, इसलिए leading: 24 का अर्थ फ़ॉन्ट आकार का 24 गुना है। पिक्सेल के लिए "24px" लिखें.
example.ts
css({ p: 16, m: 0, opacity: 0.5, leading: 1.5, z: 10, w: '50%' })
// padding:16px; margin:0; opacity:0.5; line-height:1.5; z-index:10; width:50%

शॉर्टहैंड

के लिए भी यही बात लागू है
कुंजीCSS
p, pt, pr, pb, plपैडिंग, और प्रत्येक साइड
px / pyपैडिंग बाएँ और दाएँ / ऊपर और नीचे
m, mt, mr, mb, ml, mx, mythe मार्जिन
w, h, minW, maxW, minH, maxHचौड़ाई, ऊंचाई और उनकी न्यूनतम और अधिकतम
sizeचौड़ाई और ऊंचाई
bgपृष्ठभूमि
text / font / weightfont-size / font-family / font-weight
leading / trackingline-height / letter-spacing
align / items / justifytext-align / align-items / justify-content
z / rounded / shadow / selectz-index / border-radius / box-shadow / user-select

ये कुंजियाँ true लेती हैं:

कुंजीCSS
rowdisplay: flex; flex-direction: row
coldisplay: flex; flex-direction: column
centeralign-items: center; justify-content: center (डिस्प्ले सेट नहीं है)
truncateoverflow: hidden; text-overflow: ellipsis; white-space: nowrap

छद्म स्टेट

hover, focus, focusVisible, focusWithin, active, visited, disabled, enabled, checked, required, invalid, valid, readOnly, first (:first-child), last (:last-child), only (:only-child), odd, even, empty, placeholderShown, placeholder, before, after, selection, marker, firstLine और firstLetter। वे घोंसला बनाते हैं: hover: { focus: {…} } :hover:focus देता है।

चयनकर्ता और नियम

एक कुंजी जो & से शुरू होती है वह एक चयनकर्ता है, और प्रत्येक & वर्ग बन जाता है। एक कुंजी जो @ से शुरू होती है वह एक नियम है: @media, @container या @supports।

menu.ts
const menu = css({
  p: 8,
  '& > li': { py: 4 },
  '& > li:hover': { color: 'red' },
  '&:is(.dark *)': { color: 'white' },
  '@media (min-width: 768px)': { p: 16 },
})
  • कुंजी & से शुरू होनी चाहिए। ".dark &" जैसी कुंजी को नजरअंदाज कर दिया जाता है, इसलिए "&:is(.dark *)" लिखें।
  • दो नेस्टेड @media ब्लॉक and के साथ संयोजित होते हैं। मीडिया प्रकार (print, only screen) के नेतृत्व वाली क्वेरी को पहले रखा जाता है, क्योंकि व्याकरण केवल शुरुआत में ही प्रकार की अनुमति देता है। एक जोड़ी जिसे समतल नहीं किया जा सकता - दो मीडिया प्रकार, या एक अग्रणी not - केवल आंतरिक क्वेरी को चेतावनी देता है और उसका उपयोग करता है। कोई भी अन्य नेस्टेड एट-रूल बाहरी को प्रतिस्थापित कर देता है।

raw

किसी भी संपत्ति के लिए raw का उपयोग करें, नाम बिल्कुल CSS जैसा लिखा गया है। नंबर अभी भी px मिलते हैं, इसलिए स्ट्रिंग पास करें:

example.ts
css({ raw: { '--gap': '8px', '-webkit-line-clamp': '2' } })

किसी भी अन्य फ़्लैट कुंजी को कबाब-केस में बदल दिया जाता है और उसे वैसे ही लिखा जाता है, इसलिए colr: 'red' जैसी टाइपो एक घोषणा बन जाती है जिसे ब्राउज़र ड्रॉप कर देता है। किसी कुंजी के अंतर्गत एक नेस्टेड ऑब्जेक्ट जो एक छद्म स्थिति, एक स्क्रीन, या एक & या @ कुंजी नहीं है, को नजरअंदाज कर दिया जाता है। TypeScript दोनों की रिपोर्ट करता है।

fnसार्वजनिक API
function cx(...inputs: ClassInput[]): StyleResult type ClassInput = StyleResult | string | false | null | undefined | ClassInput[]

शैलियों को जोड़ती है। जब दो इनपुट समान गुण सेट करते हैं, तो बाद वाला इनपुट जीत जाता है।

example.ts
const base = css({ p: 8, color: 'red' })
const blue = css({ color: 'blue' })

cx(base, blue)                  // padding: 8px; color: blue
cx(base, false, null, 'extra')  // base's classes plus "extra"
div(cx(base, blue), 'Blue text')
  • गलत इनपुट को छोड़ दिया जाता है, एरे को चपटा कर दिया जाता है, और स्ट्रिंग्स को विभाजित और डी-डुप्लिकेट कर दिया जाता है।
  • एक ही ब्लॉक (आधार, एक ही छद्म, एक ही मीडिया क्वेरी) के लिए जेनरेट की गई कक्षाएं एक नई कक्षा में विलय हो जाती हैं। अन्य वर्ग के नाम अपरिवर्तित रहते हैं।
  • केवल cx() तर्क क्रम जानता है। div(base, blue) के साथ, दोनों वर्ग जोड़े जाते हैं, और जो नियम पिछली बार बनाया गया था वह जीत जाता है। एक cx() कॉल में एक-दूसरे को ओवरराइड करने वाली शैलियाँ डालें।
  • जब एक नामित शैली का ब्लॉक किसी अन्य इनपुट के ब्लॉक के साथ विलय हो जाता है, तो मर्ज किया गया वर्ग name-<hash> होता है, न कि मूल नाम। जो ब्लॉक मर्ज नहीं होते हैं वे अपनी कक्षा बनाए रखते हैं, इसलिए cx(pageTitle, 'extra') में अभी भी page-title है।

गतिशील कक्षाएं

एक फ़ंक्शन पास करें जो हमेशा cx(...) लौटाता है:

toggle.ts
const baseButton = css({ px: 12, py: 8, border: '1px solid #ccc' })
const activeButton = css({ bg: '#ff3f00', color: 'white' })

let active = false

const Toggle = () =>
  button(
    () => cx(baseButton, active && activeButton),
    () => (active ? 'Active' : 'Inactive'),
    { onClick: () => { active = !active; update() } },
  )

फ़ंक्शन को अपने पहले रन पर एक स्टाइल परिणाम लौटाना होगा। () => active && activeButton एक क्लास के बजाय "false" टेक्स्ट को प्रस्तुत करता है।

createCss()

#
fnसार्वजनिक API
function createCss<const T extends ThemeConfig>(theme?: T): CssInstance<T> // CssInstance<T> = { css, cx, variants, keyframes, globalStyle, theme }

एक थीम से बंधे अपने स्वयं के css, cx, variants, keyframes और globalStyle लौटाता है। एक थीम मानों को नाम देती है: नीचे दी गई थीम के साथ, borderColor: 'border' #e5e7eb लिखता है, और TypeScript नामों को स्वतः पूर्ण करता है। screens में प्रत्येक प्रविष्टि मीडिया क्वेरी के लिए एक शैली कुंजी बन जाती है, जैसे md: {…}।

theme.ts
export const { css, cx, variants } = createCss({
  colors: { primary: '#ff3f00', border: '#e5e7eb' },
  radii: { card: '12px' },
  screens: { md: '(min-width: 768px)' },
})

const panel = css({
  p: 12,
  rounded: 'card',
  border: '1px solid',
  borderColor: 'border',
  md: { p: 24 },
})
द्वारा उपयोग किया गया
थीम कुंजी
colorsbg, backgroundColor, color, borderColor और प्रत्येक पक्ष, outlineColor, caretColor, textDecorationColor
fontsfont, fontFamily
shadowsshadow, boxShadow
radiirounded, borderRadius
screensनई स्टाइल कुंजियाँ, जैसे md: {…}
  • एक थीम नाम तभी हल होता है जब वह संपूर्ण मान हो। border: "1px solid primary" का समाधान नहीं हुआ।
  • @ के बिना एक स्क्रीन मान @media <value> हो जाता है। @ से शुरू होने वाला मान लिखित रूप में उपयोग किया जाता है, उदाहरण के लिए "@container (min-width: 400px)"।
  • स्क्रीन केवल createCss() से बने इंस्टेंस पर मौजूद हैं। वैश्विक css() में कोई नहीं है, लेकिन यह @media कुंजी स्वीकार करता है।
  • सभी उदाहरण और वैश्विक सहायक एक स्टाइलशीट साझा करते हैं। स्क्रीन मीडिया नियम स्क्रीन घोषित होने के क्रम में आउटपुट होते हैं, न कि उस क्रम में जिस क्रम में आप उनका उपयोग करते हैं, इसलिए min-width स्क्रीन को छोटे से बड़े तक सूचीबद्ध करें। प्रति ऐप एक createCss() इंस्टेंस का उपयोग करें, ताकि वह ऑर्डर आपकी थीम में से एक हो।
  • theme वह विषय है जिसे आपने उत्तीर्ण किया है।

variants()

#
fnसार्वजनिक API
function variants(config: { base?: Style variants?: { [group: string]: { [value: string]: Style } } defaultVariants?: { [group: string]: value } compoundVariants?: ({ [group: string]: value } & { css: Style })[] }): (props?: { [group: string]: value }) => StyleResult

एक फ़ंक्शन बनाता है जो विकल्पों के एक सेट के लिए एक शैली लौटाता है, जैसे कि बटन का इरादा और आकार।

button.ts
const buttonClass = variants({
  base: { px: 12, py: 8, rounded: 6 },
  variants: {
    intent: { primary: { bg: '#2563eb' }, danger: { bg: '#dc2626' } },
    size: { sm: { text: 12 }, lg: { text: 18 } },
    block: { true: { w: '100%' } },
  },
  defaultVariants: { intent: 'primary', size: 'sm' },
  compoundVariants: [{ intent: 'danger', size: 'lg', css: { weight: 700 } }],
})

button(buttonClass(), 'Save')                                                // primary, sm
button(buttonClass({ intent: 'danger', size: 'lg', block: true }), 'Delete') // bold, full width
  • शैलियाँ इस क्रम में विलीन हो जाती हैं: base, प्रत्येक समूह का चुना हुआ मान, फिर प्रत्येक मिलान यौगिक। बाद की शैलियाँ प्रति संपत्ति जीतती हैं।
  • एक अनुपलब्ध, undefined या null विकल्प डिफ़ॉल्ट का उपयोग करता है। बिना किसी डिफ़ॉल्ट वाले समूह को छोड़ दिया जाता है।
  • केवल true और false कुंजियों वाला एक समूह एक बूलियन लेता है।
  • एक कंपाउंड तब लागू होता है जब इसमें सूचीबद्ध प्रत्येक समूह मेल खाता है, डिफ़ॉल्ट शामिल हैं।
  • प्रत्येक चयन एक बार बनाया जाता है और कैश किया जाता है।

keyframes() और globalStyle()

#
fnसार्वजनिक API
function keyframes(frames: KeyframeFrames): string function globalStyle(selector: string, style: FlatStyle): void

keyframes() एक एनीमेशन पंजीकृत करता है और उसका उत्पन्न नाम लौटाता है। फ़्रेम कुंजियाँ from, to और प्रतिशत हैं, जैसे "50%" या "0%, 100%"। फ़्रेम केवल समतल गुण लेते हैं. समान फ़्रेम समान नाम लौटाते हैं.

fade.ts
const fadeIn = keyframes({
  from: { opacity: 0 },
  to: { opacity: 1 },
})

const panel = css({ animation: `${fadeIn} 200ms ease-out` })

globalStyle() किसी भी चयनकर्ता के लिए एक नियम जोड़ता है:

reset.ts
globalStyle('*, *::before, *::after', { boxSizing: 'border-box' })
globalStyle('body', { m: 0, font: 'system-ui, sans-serif' })
  • चयनकर्ता का उपयोग बिल्कुल वैसे ही किया गया है जैसा लिखा गया है।
  • केवल फ्लैट संपत्तियां ही काम करती हैं। छद्म कुंजियाँ, स्क्रीन और @ कुंजियाँ नजरअंदाज कर दी जाती हैं, इसलिए globalStyle() मीडिया नियम नहीं लिख सकता।
  • एक ही नियम एक बार जोड़ा जाता है। एक ही चयनकर्ता के लिए एक अलग शैली दूसरा नियम जोड़ती है, और बाद वाला जीतता है।

getCssText() और resetStyles()

#
fnसार्वजनिक API
function getCssText(): string // from 'nuclo/ssr' or 'nuclo' function resetStyles(): void // from 'nuclo'

getCssText() हर नियम लौटाता है जो css(), cx(), variants(), keyframes() और globalStyle() ने इस प्रक्रिया में बनाया है: पहले आधार नियम, फिर @media, @container और @supports ब्लॉक। सर्वर रेंडरिंग के लिए इसका उपयोग करें।

  • इसे renderToString() के बाद कॉल करें। रेंडर के दौरान बनाए गए नियम शामिल हैं।
  • यह एक पेज या अनुरोध को नहीं बल्कि पूरी प्रक्रिया को कवर करता है, और यह केवल बढ़ता है। यह सुरक्षित है, क्योंकि वर्ग के नाम सामग्री हैश हैं।
  • इसे <style id="nuclo-styles"> में डालें। क्लाइंट उस तत्व का पुन: उपयोग करता है और उसमें पहले से मौजूद नियमों को छोड़ देता है। किसी अन्य आईडी के साथ, या किसी भी आईडी के साथ, क्लाइंट प्रत्येक नियम के साथ फिर से एक दूसरी स्टाइलशीट जोड़ता है।
  • टेक्स्ट बच नहीं पाया है, इसलिए शैली मानों में कभी भी अविश्वसनीय इनपुट न डालें।

resetStyles() एक परीक्षण सहायक है। यह प्रत्येक नियम और #nuclo-styles तत्व को हटा देता है। आपके द्वारा पहले बनाई गई शैलियाँ अपने वर्ग के नाम तो रखती हैं लेकिन अपना CSS खो देती हैं। इसे किसी ऐप या सर्वर पर कॉल न करें.

setup.test.ts
import { resetStyles } from 'nuclo'

beforeEach(() => resetStyles())
अलग पैकेज: nuclo-router Nuclo कोर पैकेज का हिस्सा नहीं है। इसे nuclo के बगल में स्थापित करें। यह एक पूर्वावलोकन रिलीज़ है, इसलिए इसका API अभी भी बदल सकता है।

Nuclo के लिए रूटिंग: सर्वर रेंडरिंग, हाइड्रेशन, कोड-स्प्लिट रूट और निष्क्रिय प्रीलोडिंग, list() और update() पर निर्मित। 4.9 KB के बारे में gzipped, Nuclo से परे कोई निर्भरता नहीं है और कोई साइड इफेक्ट नहीं है, इसलिए अप्रयुक्त होने पर यह ट्री से हिल जाता है।

terminal
$ npm install nuclo nuclo-router

या समान दो नामों के साथ pnpm add, yarn add या bun add का उपयोग करें। इसे सहकर्मी निर्भरता के रूप में nuclo 0.3 या बाद के संस्करण की आवश्यकता है।

राउटर what को लोड करने का निर्णय लेता है। ऐप कहता है where router.outlet() के साथ: परत स्टैक पर एक list(), प्रति परत एक पंक्ति, इसलिए एक साधारण पृष्ठ एक पंक्ति है। सर्वर और क्लाइंट एक ही ट्री बनाते हैं, इसलिए hydrate() सर्वर के नोड्स को बदलने के बजाय उन पर दावा करता है। बाकी सब कुछ history है, एक प्रत्यायोजित click श्रोता और requestIdleCallback है।

एक आयात

main.ts
export const router = createRouter(routes)   // define once, import anywhere
await router.start()                          // resolve the current URL
render(App, document.getElementById('app')!) // App places router.outlet() and reads router.path, router.go(), …

start() मिलान रूट के मॉड्यूल को वापस लौटने से पहले हल करता है, इसलिए पहला ट्री सर्वर और क्लाइंट दोनों पर पूरा हो जाता है। उसके बाद राउटर उस रूट को पढ़ता है जो उसने शुरू किया था: router.path, router.pending, router.go()।

एक संपूर्ण ऐप

routes.ts
// isomorphic: imported by the browser and the server
import { createRouter } from 'nuclo-router'

export const router = createRouter({
  '/':           () => import('./pages/Home.ts'),
  '/docs':       () => import('./pages/Docs.ts'),
  '/blog/:slug': () => import('./pages/Post.ts'),
  '*':           () => import('./pages/NotFound.ts'),
})

export const App = () =>
  div(
    header(a({ href: router.href('/') }, 'Home'), a({ href: router.href('/docs') }, 'Docs')),
    main(
      when(() => router.pending, Spinner()),
      when(() => router.error !== null, ErrorView()),
      router.outlet(),   // where the pages land
    ),
    footer('© nuclo'),
  )
pages/Post.ts
import type { RouteContext } from 'nuclo-router'
import { router } from '../routes.ts'

// A page is plain content
export default function Post(ctx: RouteContext) {
  // A query-only change keeps the page, so the query is read live.
  return article(h1(ctx.params.slug), p(() => (router.search.has('preview') ? 'draft' : '')))
}
main.ts
import 'nuclo'
import { router, App } from './routes.ts'

await router.start()
hydrate(App, document.getElementById('app')!)   // or render() without SSR
server.ts
import 'nuclo/polyfill'
import 'nuclo'
import { renderToString } from 'nuclo/ssr'
import { router, App } from './routes.ts'

// one Route per request
const route = await router.start(request.url)
const html = route.run(() => renderToString(App))

यह संपूर्ण एकीकरण है। लिंक काम करते हैं क्योंकि <a href> सिर्फ एक <a href> है। document पर एक प्रत्यायोजित श्रोता राउटर के स्वामित्व वाले लिंक को रोकता है और ब्राउज़र पर हर दूसरे लिंक को छोड़ देता है।

सर्वर पर, start() प्रति अनुरोध एक Route लौटाता है और राउटर को अकेला छोड़ देता है, इसलिए समवर्ती अनुरोध एक दूसरे का मार्ग नहीं देख सकते हैं। route.run() उस रूट को बनाता है जो राउटर, और इसलिए आउटलेट, रेंडर की लंबाई के लिए पढ़ता है। ब्राउज़र में प्रति पेज लोड एक रूट होता है, और start() इसे राउटर का अपना बनाता है। कोई run() की आवश्यकता नहीं है.

एक पृष्ठ को इसका RouteContext और इसका PageProps, (ctx, { layer, data, outlet, beforeLeave }) के रूप में प्राप्त होता है, और कम तर्क ले सकता है। यह list() द्वारा स्वीकार की गई कोई भी चीज़ लौटाता है। लेआउट के अन्य भाग को भी भरने के लिए, पृष्ठ के अंदर से एक into() लौटाएँ, जैसे div(article(…), into('sidebar', nav(…)))। वह सामग्री पृष्ठ के साथ चली जाती है। Pages & क्षेत्र देखें।

पैटर्न और नेस्टेड रूट

#
नमूनामाचिसparams
/, /docs/introबिल्कुल वही path{}
/blog/:slug/blog/hello{ slug: 'hello' }
/u/:id/edit/u/7/edit{ id: '7' }
/files/*rest/files/a/b.txt{ rest: 'a/b.txt' }
*Zकुछ भी बेजोड़, आपका 404{ '*': '…' }

प्राथमिकता स्थिर है, फिर घोषणा क्रम में गतिशील है, फिर पहले सबसे लंबे उपसर्ग के साथ सभी को पकड़ें। तो '*' केवल वही मेल खाता है जो कोई और नहीं करता है और तालिका में कहीं भी बैठ सकता है। ट्रेलिंग स्लैश, डुप्लिकेट स्लैश और प्रतिशत-एन्कोडिंग को सामान्यीकृत किया जाता है, और परम मान डिकोड किए जाते हैं। मिलान प्रति पैटर्न एक खंड तुलना है। कोई रेगेक्स नहीं बनाया या चलाया जाता है।

सापेक्ष मार्ग

./ के साथ लिखा गया एक पैटर्न इसे घोषित करने वाले माता-पिता के सापेक्ष है, और ./ के साथ लिखा गया href सक्रिय मार्ग के सापेक्ष है। वे मिलकर एक पुन: प्रयोज्य अनुभाग बनाते हैं जिसका अर्थ प्रत्येक माता-पिता के लिए समान होता है। किसी तालिका का मान या तो एक लोडर होता है या कोई अन्य तालिका, जिसकी कुंजियाँ उससे संबंधित होती हैं:

records/routes.ts
import type { RouteTable } from 'nuclo-router'

// a section, with no idea where it lives
export const recordSection: RouteTable = {
  '/':             () => import('./Record.ts'),   // the parent path itself
  './preview':     () => import('./Preview.ts'),
  './preview/raw': () => import('./Raw.ts'),
}
routes.ts
// declare it wherever it belongs
createRouter({
  '/': () => import('./pages/Home.ts'),
  '/invoices/:id':  recordSection,
  '/customers/:id': recordSection,
  '*': () => import('./pages/NotFound.ts'),
})

वे दो पंक्तियाँ सभी छह पैटर्न उत्पन्न करती हैं जिनमें कोई पथ दो बार नहीं लिखा होता है। माता-पिता दोनों को समान लोडर ऑब्जेक्ट मिलते हैं, इसलिए वे एक मॉड्यूल कैश और एक import() साझा करते हैं।

  • एक चाइल्ड टेबल के अंदर, './x', '/x' और 'x' सभी का मतलब एक ही है, '/' का मतलब मूल पथ ही है, और '*rest' अनुभाग को अपना स्वयं का कैच-ऑल देता है। घोंसला बनाना आप जितना चाहें उतना गहरा हो जाता है।
  • शीर्ष-स्तरीय कुंजियों का उपयोग लिखित रूप में किया जाता है, इसलिए '*' '*' रहता है।
  • *catch-all माता-पिता के अंतर्गत एक बच्चा एक createRouter() त्रुटि है, क्योंकि कैच-ऑल को अपना पैटर्न समाप्त करना होता है।

मूल पृष्ठ और outlet()

एक माता-पिता और उसके चाइल्ड नोड एक वास्तविक पदानुक्रम हैं। पेरेंट रेंडर करता है, और उसका बच्चा इसके अंदर रेंडर करता है, जहां भी वह outlet() कहता है:

records/Record.ts
import type { PageProps, RouteContext } from 'nuclo-router'

// a parent page takes its outlet from (ctx, { outlet })
export default function Record(ctx: RouteContext, { outlet }: PageProps) {
  return div(Header(ctx), input({ id: 'notes' }), main(outlet()))
}

चाइल्ड नोड के बीच नेविगेट करने से माता-पिता का पुनर्निर्माण नहीं होता है। इसका DOM नोड एक ही नोड है, इसलिए इसका फोकस, इसकी स्क्रॉल स्थिति, इसका आधा टाइप किया गया इनपुट और इसके बंद होने की कोई भी स्थिति सभी जीवित रहती है।

  • बिना किसी चाइल्ड रूट वाला पेज अपने आउटलेट से कुछ भी प्रस्तुत नहीं करता है, इसलिए एक लेआउट इसे बिना शर्त कॉल कर सकता है। एक माता-पिता जिसके पास एक बच्चा है और वह कभी भी outlet() को नहीं बुलाता है, वह उसे निगल जाता है।
  • कोई सूचकांक मार्ग नहीं हैं। '/' स्वयं लेआउट है, इसलिए मूल पथ लेआउट को उसके अंदर एक अलग इंडेक्स पेज के बजाय एक खाली आउटलेट के साथ प्रस्तुत करता है। लैंडिंग सामग्री को लेआउट में रखें, या इसे वास्तविक चाइल्ड पथ दें।

फिर वही कॉल माता-पिता में से किसी एक से और एक ऐसे पेज से काम करती है जो यह नहीं जानता कि यह किसमें है:

example.ts
router.href('./preview')      // '/invoices/42/preview' or '/customers/7/preview'
await router.go('./preview')
await layer.push('./preview') // the usual shape: a child layer

./x, ../x, . और .. वर्तमान रूट के पथ के विरुद्ध समाधान करते हैं, URL की "निर्देशिका" के विरुद्ध नहीं। /invoices/42 से, ./preview /invoices/42/preview है, जहां एक ब्राउज़र ने /invoices/preview कहा होगा, और URL पर एक पिछला स्लैश कुछ भी नहीं बदलता है। ../ एक खंड तक चलता है और मूल पर रुकता है। क्वेरी और हैश को आगे बढ़ाया जाता है, base को फिर से जोड़ा जाता है, और हल किया गया निरपेक्ष पथ इतिहास में चला जाता है। यह URL रिज़ॉल्यूशन से वही विचलन है जो रिएक्ट राउटर <Link to="./x"> के लिए करता है।

  • केवल ./ और ../ मार्ग-सापेक्ष हैं। एक href में एक नंगे preview को सामान्य ब्राउज़र नियमों के लिए छोड़ दिया गया है।
  • हाथ से href="./preview" लिखने के बजाय router.href('./preview') के साथ सापेक्ष लिंक बनाएं। href() रेंडर समय पर हल हो जाता है, इसलिए जो मार्कअप भेजा जाता है वह क्रॉलर के लिए, मध्य-क्लिक के लिए और JavaScript अक्षम होने पर भी पूर्ण और सही होता है।

क्या रखा है, और क्या बनाया गया है

को पुनः चलाएँ
नोडनेविगेशन पर
a पैरेंट जिसका पैटर्न और पथ अपरिवर्तित हैkept: वही नोड, और इसका load() नहीं है
मिलान किया गया पृष्ठkept जब इसका पैटर्न और पथ अपरिवर्तित होता है और इसमें कोई load() नहीं होता है, तो एक क्वेरी या हैश परिवर्तन इसे रखता है। प्रत्येक विज़िट पर लोडर वाला मार्ग ताज़ा हो जाता है।
एक अभिभावक जिसका मार्ग बदल गया, इसके तहत सब कुछ के साथ, फिर से बनाया गया
a पुश लेयर अकेले मिलान किए गए पृष्ठ को प्रस्तुत करता है। इसके माता-पिता पहले से ही इसके नीचे की पंक्ति में आरूढ़ हैं।

एक रखा हुआ पेज उस ctx को रखता है जिसके साथ इसे बनाया गया था। हर जगह ctx का यही अर्थ है: जीवंत मूल्यों के लिए router.* पढ़ें। क्वेरी-संचालित स्थिति, जैसे फ़िल्टर, पेजिनेशन और टैब, एक गतिशील मान के अंदर router.search को पढ़ता है और router.go('?page=2', { replace: true }) के साथ लिखता है। पेज, उसका फोकस और उसका आधा-टाइप इनपुट यथावत रहता है।

पेज और रीजन

#

router.outlet() पेज को एक स्थान पर रखता है। जिस पृष्ठ को लेआउट में कहीं और साइडबार, टूलबार या शीर्षक की भी आवश्यकता होती है, उसे into() से भर दिया जाता है। लेआउट region() के साथ स्थान को चिह्नित करता है, और पृष्ठ अपनी सामग्री के आगे एक into() लौटाता है। लेआउट कभी भी पृष्ठ सामग्री को प्रोप के रूप में प्राप्त नहीं करता है, और नेविगेशन कभी भी इसका पुनर्निर्माण नहीं करता है।

app.ts
export const App = () =>
  div(
    aside(
      input({ placeholder: 'Filter' }),   // keeps its text across navigations
      region({ id: 'sidebar', empty: p('Nothing to show.') }),
    ),
    main(router.outlet()),
  )
pages/Post.ts
import type { PageProps, RouteContext } from 'nuclo-router'

export default function Post(_ctx: RouteContext, { data: post }: PageProps<Post>) {
  return div(
    article(h1(post.title), p(post.body)),
    into('sidebar', nav(h2('In this post'), ul(list(() => post.headings, (h) => li(h))))),
  )
}

पृष्ठ की सामग्री आउटलेट के list() की एक पंक्ति है, और एक into() तब तक जीवित रहता है जब तक वह पंक्ति इसे धारण करती है। तो साइडबार सामग्री पृष्ठ के साथ आती है और उसके साथ चली जाती है: into('sidebar', …) के बिना किसी पृष्ठ पर नेविगेट करने से क्षेत्र की empty सामग्री फिर से दिखाई देती है। पेज दोनों भागों के लिए अपने ctx और data का उपयोग कर सकता है, क्योंकि दोनों एक ही फ़ंक्शन द्वारा बनाए गए हैं।

नेविगेशन पर क्या होता है

  • A रखा गया पेज अपने क्षेत्रों को रखता है। एक क्वेरी या हैश परिवर्तन जो पेज को रखता है क्षेत्र की सामग्री को भी नहीं छूता है। यदि साइडबार इस पर निर्भर करता है तो डायनामिक मान के अंदर router.search पढ़ें।
  • A पुनर्निर्मित पृष्ठ उन्हें पुनः भर देता है। जब मार्ग बदलता है, तो पुरानी पंक्ति हटा दी जाती है और नए पृष्ठ के into() दृश्य आ जाते हैं। कुछ भी पीछे नहीं छूटा.
  • A पुश की गई परत एक दृश्य जोड़ती है। परत आउटलेट की बाद की पंक्ति है, इसलिए इसका into() उसी क्षेत्र का एक नया दृश्य है। डिफ़ॉल्ट type: 'latest' के साथ यह परत बंद होने तक पृष्ठ के दृश्य को कवर करता है, और फिर पृष्ठ का दृश्य फिर से दिखाई देता है। type: 'stack' के साथ दोनों शो।
  • A मूल पृष्ठ क्षेत्रों को भी भर सकता है। इसके दृश्य तब भी बने रहते हैं जब इसके चाइल्ड नोड बदलते हैं, क्योंकि मूल पृष्ठ का पुनर्निर्माण नहीं किया जाता है।
  • सर्वर रेंडरिंग वैसे ही काम करता है जैसे सर्वर route.run() के अंदर पेज बनाता है, इसलिए इसके into() दृश्य HTML में हैं, और hydrate() उनका दावा करता है।

एक पृष्ठ से कई क्षेत्र

एक बार में एक से अधिक क्षेत्र भरने के लिए किसी ऑब्जेक्ट को पास करें। पृष्ठ की मुख्य सामग्री अभी भी आउटलेट के माध्यम से जाती है, इसलिए इसे into() के पास लौटा दें:

pages/Report.ts
export default function Report(ctx: RouteContext) {
  return div(
    table(…),
    into({
      toolbar: [button('Export'), button('Print')],
      sidebar: nav(a({ href: router.href('./summary') }, 'Summary')),
    }),
  )
}

किस काम के लिए कौन सा टूल

  • outlet() एक चाइल्ड रूट के लिए है: यूआरएल द्वारा तय की गई सामग्री, उसके मूल पृष्ठ के अंदर प्रस्तुत की गई है। नेस्टेड रूट देखें।
  • into() मिलान किए गए पेज के लिए है जो पूरे लेआउट में फैला हुआ है। सामग्री पृष्ठ से संबंधित है और उसके डेटा का उपयोग करती है।
  • matches() अकेले यूआरएल से लेआउट तय करने के लिए है, इसकी अपनी छोटी तालिका है और इसमें कोई पेज शामिल नहीं है। matches() देखें।

ये सभी एक सुमेलित मार्ग से संबंधित हैं। राउटर एक यूआरएल से दो स्वतंत्र क्षेत्रों को नहीं चलाता है, इसलिए अपने स्वयं के मार्गों के साथ एक साइडबार वह क्षेत्र है जिसे एक पृष्ठ भरता है, दूसरा आउटलेट नहीं। एक साइडबार जो वास्तव में अपना स्वयं का राउटर चाहता है, लोडिंग और फ़ॉलबैक को अनस्टार्ट रखता है और match() के साथ हल करता है - मल्टीपल राउटर्स & कंबाइंड लोडिंग देखें।

रूट लोडर और कोड विभाजन

#

एक पेज मॉड्यूल एक load फ़ंक्शन निर्यात कर सकता है। यह पेज बनने से पहले, उस रूट के हर नेविगेशन पर चलता है, और जो कुछ भी लौटाता है उसे data के रूप में पेज पर भेज दिया जाता है:

pages/Post.ts
import type { DataLoader, PageProps, RouteContext } from 'nuclo-router'

export const load: DataLoader<Post> = (ctx) => fetchPost(ctx.params.slug)

export default function Post(_ctx: RouteContext, { data: post }: PageProps<Post>) {
  return article(h1(post.title), p(post.body))
}

रूट तालिका अपरिवर्तित है। लोडर पेज के साथ यात्रा करता है। इसमें मिलान RouteContext मिलता है, इसलिए params, search और path सभी उपलब्ध हैं। यह सिंक या एसिंक हो सकता है, और यह सर्वर पर भी चलता है, इसलिए सर्वर-रेंडर पेज हाइड्रेशन के बाद लाने के बजाय पहले से ही HTML में अपना डेटा भेजता है।

मॉड्यूल कैश्ड है। डेटा नहीं है. एक रूट का हिस्सा एक बार लाया जाता है और पुन: उपयोग किया जाता है, और इसका लोडर हर नेविगेशन पर फिर से चलता है। यही कारण है कि /blog/a और /blog/b को भ्रमित नहीं किया जा सकता है, और एक असफल लोडर को आसानी से पुनः प्रयास किया जा सकता है।

स्थितिव्यवहार
चलता हैrouter.pending सच है और जिस पेज पर आप थे वह चालू रहता है
it को router.error पर फेंकता या अस्वीकार करता है। go() अभी भी कभी भी अस्वीकार नहीं करता है, और नेविगेशन का पुनः प्रयास किया जा सकता है।
start() के दौरान अस्वीकृति start() से आती है। इसे लगाने के लिए अभी तक कोई रूट नहीं है.
a पुश लेयर अपने स्वयं के डेटा के साथ अपना स्वयं का लोडर चलाता है
केवल निष्क्रिय प्रीलोडरवार्म्स मॉड्यूल। इसमें पारित करने के लिए कोई संदर्भ नहीं है, और यह उस मार्ग के लिए डेटा लाएगा जिसे आप कभी नहीं खोल सकते।
उसी URL पर पुनः नेविगेट करने पर लोडर फिर से चलता है और पंक्ति फिर से बन जाती है। लोडर के बिना एक मार्ग अपनी पंक्ति रखता है।

पुनः सत्यापित करने के लिए, इतिहास प्रविष्टि जोड़े बिना उस स्थान पर नेविगेट करें जहां आप पहले से हैं:

example.ts
await router.go(router.url, { replace: true })   // runs load() again

खंड के साथ डेटा प्रारंभ करना

पेज मॉड्यूल द्वारा निर्यात किया गया load केवल उस मॉड्यूल के आने के बाद ही चल सकता है, इसलिए एक ठंडा नेविगेशन खंड, फिर डेटा प्राप्त करता है। दोनों को एक साथ शुरू करने के लिए, लोडर को withLoad() के साथ तालिका में घोषित करें:

routes.ts
import { createRouter, withLoad } from 'nuclo-router'
import { loadPost } from './data.ts'   // small, in the main bundle

createRouter({
  '/blog/:slug': withLoad(() => import('./pages/Post.ts'), loadPost),
})

यह मॉड्यूल के अपने load को प्रतिस्थापित करता है और हर विजिट पर इसी तरह चलता है। एक अस्वीकृति जबकि हिस्सा अभी भी किसी अन्य की तरह router.error पर लैंड लोड कर रहा है।

डिज़ाइन के अनुसार, HTML में सर्वर डेटा का कोई क्रमांकन नहीं है। क्लाइंट का load फिर से हाइड्रेशन पर चलता है। यदि यह मायने रखता है, तो सर्वर को route.data उत्सर्जित करने दें, उदाहरण के लिए window.__DATA__ के रूप में, और लोडर इसे पहले ले। route.data केवल मिलान किए गए पृष्ठ का डेटा है। माता-पिता का load() परिणाम इस पर नहीं है।

कोड विभाजन और प्रीलोडिंग

एक रूट का मान हमेशा एक फ़ंक्शन होता है जो पृष्ठ घटक लौटाता है, इसलिए घटक तक आलसी तरीके से पहुंचा जाता है:

routes.ts
'/a': () => import('./A.ts'),                      // default export (+ optional load)
'/b': () => import('./B.ts').then(m => m.BPage),   // named export
'/c': () => CPage,                                 // eager, not split
'/d': () => ({ default: DPage, load: loadD }),     // eager, module-shaped

केवल मॉड्यूल-आकार वाले फॉर्म ही load निर्यात कर सकते हैं। .then(m => m.BPage) के साथ नामित घटक को चुनने से राउटर को अकेले ही फ़ंक्शन सौंप दिया जाता है।

प्रत्येक मॉड्यूल को एक बार आयात किया जाता है और कैश किया जाता है। एक ही मार्ग पर समवर्ती नेविगेशन एक import() साझा करते हैं। एक बार जब उपयोगकर्ता ने पृष्ठ को idleTimeout (डिफ़ॉल्ट रूप से एक सेकंड; कोई टैप, कुंजी, पहिया या स्क्रॉल नहीं) के लिए अकेला छोड़ दिया है, तो शेष मार्ग पृष्ठभूमि में लोड हो जाते हैं, प्रति निष्क्रिय स्लाइस (requestIdleCallback)। उपयोगकर्ता जिस भी इनपुट, या नेविगेशन की प्रतीक्षा कर रहा है, उसे तब तक रोक देता है जब तक कि पृष्ठ फिर से निष्क्रिय न हो जाए।

एक प्रीलोडेड या उत्सुक मार्ग कभी भी router.pending को फ्लिप नहीं करता है और अगले पेंट से पहले लैंड करता है: कोई स्पिनर फ्रेम नहीं, कोई लेआउट शिफ्ट नहीं। router.pending केवल एक मॉड्यूल या लोडर के लिए सही है जिसके लिए इंतजार करना पड़ता है, और आउटगोइंग पेज तब भी स्क्रीन पर रहता है। एक टुकड़ा जो सतहों को router.error के रूप में लोड करने में विफल रहता है, कभी भी एक अनचाहे अस्वीकृति के रूप में नहीं, और बस पुनः प्रयास किया जा सकता है।

राउटर API

#
fnसार्वजनिक API
// import { ... } from 'nuclo-router' function createRouter(table: RouteTable, options?: RouterOptions): Router function withLoad(loader: RouteLoader, load: DataLoader): RouteLoader interface RouterOptions { base?: string; preload?: boolean; idleTimeout?: number; onNavigate?: (ctx: RouteContext, kind: NavigationKind) => void }

createRouter(table, options?)

table एक पैटर्न को लोडर, या नेस्टेड टेबल पर मैप करता है। विकल्प:

के दौरान कभी कॉल नहीं किया गया
विकल्पगलती करना
base'/'URL उपसर्ग के तहत ऐप परोसा जाता है, जैसे '/docs'। यह केवल खंड सीमा पर मेल खाता है: /docs/intro '/docs' के अंदर है, /docsearch नहीं है। इसके बाहर के यूआरएल किसी भी तरह से मेल नहीं खाते हैं, इसलिए start() अस्वीकार कर देता है (आपका सर्वर-साइड 404) और उनके लिंक ब्राउज़र पर छोड़ दिए जाते हैं।
preloadtrueपृष्ठ निष्क्रिय होने पर शेष मार्गों के मॉड्यूल को पृष्ठभूमि में लोड करें। false इसे बंद कर देता है.
idleTimeout1000मिलीसेकंड प्रीलोडिंग शुरू होने से पहले, या उपयोगकर्ता के शुरू होने के बाद फिर से शुरू होता है व्यस्त.
onNavigate(ctx, kind) => void, ब्राउज़र में प्रत्येक हल किए गए नेविगेशन के बाद कॉल किया जाता है, जिसमें शुरुआती नेविगेशन भी शामिल है, एक बार DOM पकड़ लिया है. kind 'start', 'push', 'replace', 'pop' (पीछे, आगे, close()) या 'layer' है। document.title, मेटा टैग, फोकस, स्क्रॉलिंग और एनालिटिक्स के लिए हुक। SSR.

राउटर लौटाता है: start(), match(), outlet(), matches(), और run() को छोड़कर प्रत्येक Route सदस्य, सक्रिय रूट को पढ़ता है। वह वह है जो ब्राउज़र में लौटाया गया अंतिम start() है, या वह जिसका run() सर्वर पर निष्पादित हो रहा है। किसी भी थ्रो के साथ एक सदस्य को पढ़ना। रूट तालिका, संकलित मिलानकर्ता और मॉड्यूल कैश राउटर पर रहते हैं, इसलिए उन्हें सर्वर पर अनुरोधों के बीच साझा किया जाता है।

router.start(url?)

अपने पेज मॉड्यूल में url (ब्राउज़र में डिफ़ॉल्ट: location.href) को हल करता है और एक Promise<Route> लौटाता है। जब कुछ भी मेल नहीं खाता और तालिका में कोई '*' रूट नहीं है तो अस्वीकार कर देता है। एक सर्वर पर, वह आपका 404 है। ब्राउज़र में यह रूटर को सक्रिय बनाता है, popstate और प्रत्यायोजित click श्रोताओं को जोड़ता है और प्रीलोडिंग शेड्यूल करता है। दूसरा start() आपके लिए पिछले रूट को समाप्त कर देता है। सर्वर पर यह साझा की गई किसी भी चीज़ को नहीं छूता है, इसलिए route.run() के अंदर प्रस्तुत करें।

router.match(url?)

कुछ भी लोड किए बिना तालिका के विरुद्ध एक यूआरएल का समाधान करता है: कोई आयात नहीं, कोई इतिहास नहीं, कोई श्रोता नहीं, कोई रूट नहीं। जब URL base के बाहर हो, किसी पैटर्न से मेल न खाता हो या ब्राउज़र में किसी अन्य मूल पर हो, तो RouteContext, या null लौटाता है। केवल पूर्ण URL, क्योंकि ./ रिज़ॉल्यूशन के लिए एक सक्रिय रूट की आवश्यकता होती है।

example.ts
// 404 before importing a single chunk
if (!router.match(request.url)) return new Response(null, { status: 404 })

// "is this link ours?" for a custom link component or a prefetch rule
const owned = router.match(href) !== null

router.outlet()

सक्रिय मार्ग के पृष्ठ list() के रूप में। जहाँ भी पन्ने जाते हैं, इसे एक बार रखें। एक धकेली गई परत इसकी एक बाद की पंक्ति है। यह start() के समाधान से पहले और stop() के बाद खाली है। सर्वर पर यह उस रूट को प्रस्तुत करता है जिसका run() निष्पादित हो रहा है।

router.matches(table)

एक दूसरी तालिका का दृश्य प्रस्तुत करता है जो सक्रिय मार्ग के पथ से मेल खाता है, list() के रूप में आप इसे कहीं भी रख सकते हैं। matches() देखें।

Route

run() को छोड़कर प्रत्येक सदस्य राउटर पर भी है, जहां एक ऐप इसे पढ़ता है।

पर लैंड करता है
सदस्य
path, pattern, params, search, hash, urlसक्रिय मैच.searchएक हैURLSearchParams. search, hashऔरurlलाइव हैं.
pendinga नेविगेशन एक मॉड्यूल या लोडर की प्रतीक्षा कर रहा है
errorअंतिम विफल लोड, अगले नेविगेशन द्वारा साफ़ किया गया
data मिलान किए गए पृष्ठ का load() क्या लौटा, सर्वर को क्रमबद्ध करने के लिए। इस पर माता-पिता का नाम नहीं है.
depth स्टैक पर कितनी परतें हैं (एक सामान्य पृष्ठ के लिए 1)
go(href, { replace })नेविगेट करें, यदि आवश्यक हो तो मॉड्यूल लोड करें। कभी अस्वीकार नहीं करता: एक मॉड्यूल जो विफल हो जाता है, अस्वीकार करके या फेंककर, error.
push(href) एक रूट को शीर्ष पर एक नई परत के रूप में खोलें, जिस पर यह बंद होता है उसके साथ हल करें। ./ स्वीकार करता है। कोई मिलान नहीं होने या विफल मॉड्यूल पर अस्वीकार कर दिया गया।
href(path) a({ href }) के लिए base के साथ एक पथ उपसर्ग करें। सक्रिय मार्ग के विरुद्ध ./ और ../ का समाधान करता है।
stop() प्रत्येक श्रोता को अलग करें, खुली परतों को खारिज करें, आउटलेट खाली करें। नपुंसक। रुका हुआ रूट अभी भी अपने अंतिम मैच के साथ उत्तर देता है, और go() कुछ नहीं करता है।
run(fn) को राउटर के सक्रिय रूट के रूप में इस रूट के साथ fn पर कॉल करें और इसका परिणाम लौटाएं: सर्वर पर route.run(() => renderToString(App))। केवल तुल्यकालिक. fn के वापस आने पर रूट सक्रिय होना बंद हो जाता है।

RouteContext

एक पृष्ठ का पहला तर्क: path, pattern, params, search, hash, url। यह वह संदर्भ है जिसके साथ पेज बनाया गया था। जीवंत मूल्यों के लिए router.* पढ़ें। वे केवल तभी भिन्न होते हैं जब क्वेरी या हैश पुनर्निर्माण के बिना बदल जाता है।

params डिफ़ॉल्ट रूप से { [name]: string } है। एक पेज इसे Params के साथ अपने पैटर्न से टाइप कर सकता है, जो '/blog/:slug' को { slug: string } में और '/files/*rest' को { rest: string } में बदल देता है:

pages/Post.ts
import type { Params, RouteContext } from 'nuclo-router'

export default function Post(ctx: RouteContext<Params<'/blog/:slug'>>) {
  return article(h1(ctx.params.slug))   // slug: string, nothing else exists
}

PageProps

एक पेज का दूसरा तर्क। पृष्ठ को जो चाहिए उसे नष्ट करें।

द्वारा टाइप किया गया
प्रॉप
layerजहां पेज स्टैक में बैठता है। नीचे Layer देखें।
dataरूट का load() क्या लौटा, या undefined बिना किसी के। PageProps<TData>.
outlet जहां एक मूल पृष्ठ अपने चाइल्ड रूट को प्रस्तुत करता है। नेस्टेड रूट देखें.
beforeLeave इस पृष्ठ को खारिज करने से पहले एक गार्ड से पूछा गया पंजीकरण करता है। देखें गार्ड छोड़ें.

Layer

जहां एक पेज स्टैक में बैठता है, और उसका रास्ता बाहर आता है।

के समान, एक पृष्ठ के अंदर से एक और परत खोलें
सदस्य
depth0, पुश लेयर्स के लिए 1 और ऊपर
push(href)Route.push()
close(result?)इस परत और इसके ऊपर की सभी चीज़ों को बंद करें, इसे खोलने वाले push() को हल करें। किसी भी तर्क का मतलब खारिज नहीं किया गया. गहराई पर एक नो-ऑप 0.

DataLoader और withLoad()

DataLoader (ctx: RouteContext) => TData | Promise<TData> है, जो एक पेज मॉड्यूल का वैकल्पिक load निर्यात है। withLoad(loader, load) एक मार्ग बनाता है जिसका load उसके हिस्से के आयात के बाद के बजाय उसके बगल से शुरू होता है। रूट लोडर देखें।

प्रकार

प्रत्येक प्रकार nuclo-router का नामित निर्यात है, इसलिए एक पेज, एक लोडर या एक गार्ड को पथ से आयात किए बिना टाइप किया जा सकता है।

का दूसरा तर्क का दूसरा तर्क
टाइपयह क्या है
Params<'/blog/:slug'> एक पैटर्न के लिए पैरामीटर ऑब्जेक्ट। RouteParams अलिखित आकार है, और Match मिलानकर्ता का परिणाम है।
PageComponenta पेज: (ctx, { layer, data, outlet, beforeLeave }) => …
RouteModulea पेज मॉड्यूल: घटक स्वयं, या import() उपज के रूप में { default, load? }
RouteLoader() => RouteModule | Promise<RouteModule>, वैकल्पिक रूप से एक ले जाना load से withLoad()
RouteTable तालिका createRouter() लेती है: लोडर के लिए पैटर्न, या एक नेस्टेड तालिका
RouterOptions{ base?, preload?, idleTimeout?, onNavigate? }
NavigateOptions{ replace? }, go()
NavigationKind'start' | 'push' | 'replace' | 'pop' | 'layer', onNavigate
Outletका प्रकार outlet() से PageProps
LeaveGuard(to) => boolean | void | Promise<…>, beforeLeave() क्या लेता है
MatchViewmatches() दृश्य क्या है: किसी पृष्ठ के पैरामीटर, सामग्री या import()

लेयर और लीव गार्ड

#

push() वर्तमान रूट को बदलने के बजाय उसके शीर्ष पर एक रूट खोलता है, और जो भी परत उसके साथ बंद होती है, उसके साथ उसका समाधान हो जाता है। क्योंकि पेज स्टैक के ऊपर एक एकल list() हैं, एक परत खोलना एक परिशिष्ट है। नीचे दिए गए पृष्ठ को दोबारा प्रस्तुत नहीं किया गया है, इसलिए परत बंद होने पर इसका DOM, इसका फोकस और इसका आधा-भरा फॉर्म सभी अभी भी वहीं हैं।

यह "मुझे एक ऐसे रिकॉर्ड की आवश्यकता है जो अभी तक मौजूद नहीं है" प्रवाह को उस फॉर्म को हटाए बिना काम करता है जिसे उपयोगकर्ता भर रहा था:

pages/Edit.ts
// the page underneath
const created = await layer.push<Option>('/options/new')
if (created) { options.push(created); selected = created.id; update() }
pages/NewOption.ts
// the pushed page: (ctx, { layer })
layer.close(created)   // resolves the push() above with the new option
layer.close()          // dismissed: resolves with undefined

परतें घोंसला। एक संवाद जिसे अपने स्वयं के संवाद की आवश्यकता होती है वह layer.push() को कॉल करता है और रूट के संदर्भ के बिना, इसकी प्रतीक्षा करता है।

  • URL शीर्ष परत है। push() एक इतिहास प्रविष्टि जोड़ता है, इसलिए एक परत लिंक करने योग्य है और वापस इसे बंद कर देता है, इसके push() को खारिज कर दिया गया है, बिल्कुल रद्द की तरह। पीछे और close() एक कोड पथ हैं: close() अपना परिणाम रिकॉर्ड करता है और इतिहास को पीछे ले जाता है, और history.state में गहराई प्राधिकरण है। ब्राउज़र लगभग 50 प्रविष्टियों को एक टैब में रखते हैं, इसलिए परतों के माध्यम से लंबे समय तक चलने के बाद नीचे की प्रविष्टि गायब हो सकती है: फिर close() परतों को जगह में बंद कर देता है और वर्तमान प्रविष्टि को दिखाए जाने वाले पृष्ठ पर फिर से लिखता है।
  • एक साधारण नेविगेशन स्टैक को साफ़ करता है। एक लिंक क्लिक या go() एक परत नहीं है। यह सब कुछ बदल देता है और हर खुली परत को खारिज कर देता है, इसलिए किसी भी कॉल करने वाले को इंतजार नहीं करना पड़ता है।
  • A कोल्ड लोड एक स्टैंडअलोन पेज है। URL शीर्ष परत का नाम देता है, लेकिन उसके नीचे के स्टैक का नहीं, इसलिए एक परत का URL खोलने पर यह सीधे layer.depth === 0 और एक निष्क्रिय close() के साथ गहराई 1 पर प्रस्तुत होता है। एक धकेले गए मार्ग को अपने आप में अर्थपूर्ण होना चाहिए। रद्द करें बटन के स्थान पर लिंक आउट की पेशकश करने के लिए layer.depth पर शाखा करें।
  • वादा पुनः लोड तक नहीं टिक सकता, क्योंकि यह एक समापन स्थिति में रहता है। परिणाम को प्रवाह के माध्यम से एक शॉर्टकट के रूप में मानें जो लंबे समय तक भी काम करता है।
  • प्रतीक्षा में कुछ भी नहीं बचा है। stop() प्रत्येक खुली परत को खारिज कर देता है, और नेविगेशन द्वारा छोड़ी गई परत undefined के साथ हल हो जाती है।

गार्ड छोड़ें

एक पृष्ठ जिसमें रखने योग्य कुछ चीज़ होती है, जैसे कि आधा भरा हुआ फॉर्म, एक गार्ड को पंजीकृत करता है। पृष्ठ को त्यागने से पहले यह पूछा जाता है, और false (या इसका वादा) लौटाने से पृष्ठ बना रहता है:

pages/Edit.ts
import type { PageProps, RouteContext } from 'nuclo-router'

export default function Edit(_ctx: RouteContext, { beforeLeave }: PageProps) {
  let dirty = false
  beforeLeave(() => !dirty || confirm('Discard your changes?'))
  return form(input({ onInput: () => (dirty = true) }), …)
}
  • केवल जब पेज जाता है। लेआउट के तहत एक चाइल्ड नेविगेशन, या एक क्वेरी या हैश परिवर्तन जो पेज रखता है, नहीं पूछता है। एक गार्ड अपने पेज के साथ जीता और मरता है, इसलिए अपंजीकृत करने जैसा कुछ भी नहीं है।
  • हर तरह से बाहर। एक लिंक, go(), पीछे और आगे, एक परत का close(), और go() एक यूआरएल पर जो राउटर के पास नहीं है (ऐप को छोड़कर, जहां to null है)। अस्वीकृत बैक या फ़ॉरवर्ड को वापस चला दिया जाता है, इसलिए पता बार पृष्ठ पर बना रहता है। टैब बंद करना या पुनः लोड करना beforeunload का काम है।
  • to वह RouteContext है जिस पर नेविगेशन जा रहा है। गार्ड सबसे पहले अंतरतम दौड़ते हैं और पहले इनकार पर रुक जाते हैं। जो फेंकता है वह पृष्ठ रखता है। close(result) गार्ड से भी गुजरता है, इसलिए परिणाम के साथ बंद करने से पहले अपना ध्वज साफ़ करें।
  • राउटर केवल अपने द्वारा लिखी गई इतिहास प्रविष्टियों को वापस चला सकता है। जिस पर बैक या फॉरवर्ड नहीं किया गया, जैसे कि मूल #fragment लिंक या किसी अन्य स्क्रिप्ट का pushState, नहीं पूछा जाता है।

matches() और सब-राउटर

#

router.outlet() पृष्ठ प्रस्तुत करता है। router.matches(table) कुछ और प्रस्तुत करता है जो इस पर निर्भर करता है कि आप कहां हैं, जैसे साइडबार, ब्रेडक्रंब या टूलबार, अपनी छोटी तालिका से, बिना रूट लोड किए या पेज जोड़े:

app.ts
div(
  aside(
    router.matches({
      '/':           () => nav('Home links'),
      '/blog/new':   () => nav('Drafting tips'),
      '/blog/:slug': ({ slug }) => nav(`More like ${slug}`),
      '*':           () => import('./Sidebar.ts'),   // code-split, rendered once loaded
    }),
  ),
  main(router.outlet()),
)
  • रूट तालिका के समान मिलान। पैटर्न, प्राथमिकता, कच्चे-पथ मिलान और base स्ट्रिपिंग राउटर के स्वयं के हैं, इसलिए एक दृश्य और एक पृष्ठ हमेशा यूआरएल के बारे में सहमत होते हैं। यह router.path की तरह शीर्ष परत का पथ पढ़ता है।
  • A view (params, ctx) => content है, जहां params इसके पैटर्न से टाइप किया गया है और ctx व्यू के pattern और params के साथ RouteContext है। इसके बजाय यह पेज मॉड्यूल का import() लौटा सकता है, जिसे लैंड करने के बाद ctx के साथ कॉल किया जाता है।
  • कुछ भी मेल नहीं खाता, कुछ भी प्रस्तुत नहीं करता। फ़ॉलबैक के लिए एक '*' दृश्य जोड़ें। यह आउटलेट की तरह start() से पहले और stop() के बाद खाली है।
  • यह केवल तभी पुन: प्रस्तुत होता है जब मार्ग बदलता है, इसलिए एक दृश्य पृष्ठ को रखने वाली किसी भी चीज़ पर अपना DOM रखता है।
  • A आलसी दृश्य अपने मॉड्यूल आने तक कुछ भी प्रस्तुत नहीं करता है। एक विफल import() को console.error के साथ लॉग किया गया है, router.error सेट नहीं करता है, और अगले नेविगेशन पर पुनः प्रयास किया जाता है। एक साधारण update() पुनः प्रयास नहीं करता है।
  • सर्वर पर एक आलसी दृश्य कुछ भी नहीं भेजता है, क्योंकि इसका मॉड्यूल क्लाइंट पर लैंड करता है। पहले HTML में शामिल होने वाली किसी भी चीज़ के लिए एक समकालिक दृश्य का उपयोग करें।

सब-राउटर

रूट्स नेस्ट, इसलिए एक सब-राउटर आमतौर पर सिर्फ एक नेस्टेड टेबल होता है। एक राउटर: सेक्शन का अपना '/' पेज इसका क्रोम है, और इसके चाइल्ड नोड उस पेज के outlet() में रेंडर करते हैं, इसलिए क्रोम सेक्शन के भीतर नेविगेशन से बच जाता है:

routes.ts
createRouter({ '/': …, '/docs': docsRoutes, '*': … })

या फीचर को अपना स्वयं का राउटर दें, बनाया गया लेकिन कभी शुरू नहीं हुआ। माता-पिता के पास एकल कैच-ऑल प्रविष्टि होती है और बच्चा अपने स्वयं के पैटर्न, पैरामीटर और फ़ॉलबैक के अनुसार पूंछ का समाधान करता है:

routes.ts
// parent table
'/docs/*rest': () => import('./docs/Shell.ts'),
docs/Shell.ts
const docs = createRouter(docsRoutes, { base: '/docs' })
const hit = docs.match()                       // { pattern: '/:topic', … }
const page = await docsRoutes[hit.pattern]()   // the app's own lookup

इसे कभी शुरू न करना ही मुद्दा है। start() वह है जो श्रोताओं को जोड़ता है, और दो शुरू किए गए राउटर संघर्ष करते हैं। व्यापार यह है कि चाइल्ड नोड के पास रिज़ॉल्यूशन होता है, इसलिए रूट लोडर, जो एक स्टार्टर राउटर चलाता है, आपको कॉल करना है। यह आकार पूंछ बदलने पर शेल का पुनर्निर्माण भी करता है, क्योंकि राउटर एक मार्ग देखता है और शेल बाकी को स्वयं ही हल कर लेता है। नेस्टेड टेबल वह है जो आपको माता-पिता बनाती है, राउटर माउंटेड रहता है, इसलिए इसे तब तक प्राथमिकता दें जब तक कि फीचर को वास्तव में अपने राउटर, बेस और फ़ॉलबैक की आवश्यकता न हो।

कई राउटर और संयुक्त लोडिंग

#

एक ऐप में उतने ही राउटर हो सकते हैं जितने उसके क्षेत्र हैं। नियम जो इसे सरल रखता है: प्रति दस्तावेज़ बिल्कुल एक शुरू करें, और match() के साथ यूआरएल को हल करते हुए, हर दूसरे राउटर को अनस्टार्ट रहने दें। केवल प्रारंभ किया गया राउटर ही यूआरएल, इतिहास और क्लिक श्रोता का स्वामी होता है। बाकी टेबलें हैं जिनके बारे में आप प्रश्न पूछते हैं - और प्रत्येक क्षेत्र अपने स्वयं के स्पिनर के साथ अपनी घड़ी पर लोड कर सकता है।

संयुक्त लोडिंग: लोडर की एक श्रृंखला

मिलान श्रृंखला का प्रत्येक पृष्ठ निर्यात किया जा सकता है load(). चाइल्ड रूट के ठंडे नेविगेशन पर, लेआउट का लोडर और पेज का लोडर चलता है समानांतर में, और एक router.pending पूरी श्रृंखला को कवर करता है: पिछला पृष्ठ जोड़ी के सुलझने तक स्क्रीन पर रहता है, इसलिए आधा भरा हुआ ट्री कभी पेंट नहीं करता। प्रत्येक घटक को अपना स्वयं का प्राप्त होता है data:

routes.ts
'/shop': {
  '/':          () => import('./pages/Shop.ts'),    // layout, has a load()
  './:section': () => import('./pages/Section.ts'), // page, has a load()
},
pages/Shop.ts
import type { DataLoader, PageProps, RouteContext } from 'nuclo-router'

export const load: DataLoader<ShopData> = async () => {
  await wait(500)                              // a slow API
  return { sections: ['toys', 'books', 'music'] }
}

// the layout gets its own data, and its outlet for the child page
export default function Shop(_ctx: RouteContext, { data, outlet }: PageProps<ShopData>) {
  return div(
    h1('Shop'),
    nav(...data.sections.map((s) => a({ href: router.href(`/shop/${s}`) }, s))),
    main(outlet()),
  )
}
pages/Section.ts
export const load: DataLoader<SectionData> = async (ctx) => {
  await wait(900)                              // slower than the layout's
  return { section: ctx.params.section, items: catalog[ctx.params.section] }
}

// the page gets its own data: the two loaders ran beside each other
export default function Section(_ctx: RouteContext, { data }: PageProps<SectionData>) {
  return article(h2(data.section), ul(...data.items.map((i) => li(i))))
}

दोनों लोडर एक के बाद एक नहीं बल्कि एक साथ शुरू होते हैं। router.pending तब सत्य है जब इनमें से किसी एक का इंतजार किया जा रहा है, और false शेष समय में - एक प्रीलोडेड या उत्सुक मार्ग इसे कभी फ़्लिप नहीं करता है। अनुभागों के बीच जाने से लेआउट बना रहता है, इसलिए केवल अनुभाग का लोडर ही दोबारा चलता है; लेआउट का डेटा, इसका DOM और आधा टाइप किया गया इनपुट सभी जीवित हैं। उसी यूआरएल पर दोबारा नेविगेट करने से लीफ का लोडर फिर से चलता है और उसकी पंक्ति फिर से बन जाती है, बिल्कुल एक पेज की तरह।

एकाधिक लोडिंग: अपने स्वयं के स्पिनर के साथ एक दूसरा राउटर

एक स्वतंत्र क्षेत्र - एक साइडबार, एक ट्रे, एक लाइव क्षेत्र - को अपना स्वयं का राउटर मिलता है, बनाया गया लेकिन कभी शुरू नहीं हुआ, और इसका अपना लोडिंग फ़्लैग होता है। router.pending प्रारंभ किए गए राउटर से संबंधित है और इस क्षेत्र के बारे में कुछ नहीं कहता है, इसलिए दोनों साथ-साथ लोड हो सकते हैं और कोई भी पहले समाप्त कर सकता है:

sidebar.ts
import { createRouter } from 'nuclo-router'

// Created but never started: start() is what attaches the history and click
// listeners, and only the main router is started. match() attaches nothing.
const standIn = () => ({ default: () => div() })   // never rendered

const sidebarRouter = createRouter({
  '/shop':          standIn,
  '/shop/:section': standIn,
  '*':              standIn,
}, { base: '/docs' })              // the same base as the main router

export const sidebar = { loading: false, info: null as SidebarInfo | null }

export async function refreshSidebar(at: string) {
  const hit = sidebarRouter.match(at)          // the second router resolves
  if (!hit || (hit.pattern !== '/shop' && hit.pattern !== '/shop/:section')) {
    sidebar.info = null; update(); return
  }
  sidebar.loading = true                       // its own flag, its own spinner
  sidebar.info = null; update()
  const data = await fetchSidebar(hit.params.section)
  sidebar.loading = false; sidebar.info = data
  update()
}
routes.ts
createRouter(routes, {
  onNavigate: (ctx, kind) => {
    announce(kind)
    refreshSidebar(ctx.url)   // every navigation re-resolves the sidebar
  },
})

दूसरे राउटर के बारे में पूछा गया, लेकिन कभी नहीं बताया गया: match() यूआरएल पढ़ता है और एक संदर्भ देता है, और साइडबार के बारे में कुछ भी मुख्य रूट टेबल में प्रवेश नहीं करता है। onNavigate प्रत्येक हल किए गए नेविगेशन के बाद चलता है, जिसमें प्रारंभिक भी शामिल है, इसलिए यह एक ऐसा स्थान है जो यूआरएल के साथ एक स्वतंत्र क्षेत्र रखता है।

  • pending प्रारंभ किए गए राउटर से संबंधित है। एक कभी प्रारंभ न होने वाले राउटर का कोई रूट नहीं है, कोई pending नहीं है और कोई error नहीं है। क्षेत्र अपने स्वयं के फ़्लैग ट्रैक करता है, और दूसरा स्पिनर पहले के बगल में बैठ सकता है। डेमो में साइडबार 800 एमएस के लिए लोड होता है जबकि रूट के लोडर 900 एमएस के लिए चलते हैं, इसलिए पेज साइडबार से पहले या दूसरे तरीके से खत्म हो सकता है।
  • match() कुछ भी नहीं जोड़ता है। किसी भी संख्या में अनस्टार्ट राउटर दस्तावेज़ साझा कर सकते हैं; प्रत्येक की अपनी तालिका, आधार और मॉड्यूल कैश है, और उनमें से कोई भी इतिहास को नहीं छूता है। उनकी कीमत एक टेबल और एक मैचर है।
  • इसके लोडर कभी नहीं चलते हैं। प्रारंभ किया गया राउटर उससे मेल खाने वाले पृष्ठों के load() को चलाता है। कभी भी प्रारंभ न किया गया राउटर ही समस्या का समाधान करता है, इसलिए यदि इसके दृश्यों को डेटा की आवश्यकता है, तो इसे स्वयं प्राप्त करें - डेमो का refreshSidebar()। यदि आप लोडर चाहते हैं, तो एक नेस्टेड टेबल या एक region एक पेज भरता है उस काम को स्टार्ट किए गए राउटर के साथ रखता है।
  • Guard बासी उत्तर. धीमी साइडबार फ़ेच को उस अनुभाग पर नहीं उतरना चाहिए जहां से आपने नेविगेट किया था। प्रति रिफ्रेश एक जनरेशन काउंटर को टक्कर देता है और उन उत्तरों को छोड़ देता है जो अब मेल नहीं खाते हैं, जिस तरह राउटर खुद ही ओवरटेक किए गए नेविगेशन को रिटायर कर देता है।
  • A आलसी matches() दृश्य में कोई pending भी नहीं है। यह तब तक कुछ भी प्रस्तुत नहीं करता जब तक इसका मॉड्यूल लैंड नहीं हो जाता। तीन लोडिंग सिस्टम, तीन स्वतंत्र घड़ियाँ: मार्ग का pending, आपके अपने फ़्लैग, और एक आलसी दृश्य जो अभी तक वहाँ नहीं है।

गणना किए गए मान

#

Nucloकोई गणना नहीं हैAPI. एक साधारण फ़ंक्शन का उपयोग करें, और इसे गतिशील मानों से कॉल करें:

example.ts
let items = ['apple', 'banana', 'cherry']
let filter = ''

const visible = () => items.filter((item) => item.includes(filter))

const Fruits = () =>
  div(
    p(() => `Showing ${visible().length} of ${items.length}`),
    ul(list(visible, (item) => li(item))),
  )

filter = 'an'
update()

एसिंक और लोडिंग

#

साधारण async/await का उपयोग करें, और हर बार स्थिति बदलने पर update() पर कॉल करें। एक फ़ेच के लिए, इसका मतलब है एक बार await से पहले और एक बार बाद में:

example.ts
let status: 'idle' | 'loading' | 'done' | 'error' = 'idle'
let result: unknown = null

async function loadData() {
  status = 'loading'
  update()
  try {
    result = await fetch('/api/data').then((r) => r.json())
    status = 'done'
  } catch {
    status = 'error'
  }
  update()
}

सुझाए गए तरीके

#
  • बैच परिवर्तन। आपको आवश्यक सभी स्थिति बदलें, फिर एक बार update() पर कॉल करें।
  • गतिशील मूल्यों को तेज और शुद्ध रखें। ये सभी प्रत्येक update() पर चलते हैं।
  • छोटे, लगातार अपडेट, जैसे टाइपिंग या टाइमर के लिए scope() का उपयोग करें।
  • दस्तावेज़ में प्रस्तुत करें। नोड्स जो दस्तावेज़ में नहीं हैं जब update() चलता है तो अपडेट करना बंद कर देता है।
  • उन चाइल्ड नोड के लिए when() और list() का उपयोग करें, न कि कोई फ़ंक्शन जो नोड्स लौटाता है।
  • className का उपयोग करें, class का नहीं, और गतिशील क्लास के लिए () => cx(...) या { className: () => … } का उपयोग करें।
  • मॉड्यूल स्तर पर शैलियों को एक बार परिभाषित करें।
  • सर्वर रेंडरिंग: पहले रेंडर करें, फिर <style id="nuclo-styles"> में getCssText() डालें। अनुरोध डेटा को तर्क के रूप में पास करें, और आउटपुट में HTML टिप्पणियाँ रखें।

API सूचकांक

#

प्रत्येक सार्वजनिक समारोह, और यह कहाँ से आता है।

nuclo (वैश्विक और नामित निर्यात)

nuclo (केवल निर्यात नाम से)

nuclo/ssr

  • renderToString(input), renderManyToString(inputs), renderToStringWithContainer(input, tag?, attrs?) और getCssText()।

nuclo/polyfill

nuclo-router (अलग पैकेज)

  • createRouter(table, options?): राउटर, start(), match(), outlet(), matches(), go(), push() के साथ, href() और stop()।
  • withLoad(loader, load): एक मार्ग जिसका डेटा लोडर उसके हिस्से के बगल से शुरू होता है।
  • Types: RouteContext, PageProps, Params, DataLoader, RouteTable और अधिक।

ग्लोबल्स

  • 175 टैग बिल्डर्स: div, span, ..., var_, और svgSvg, pathSvg, ....
  • Types: NodeModFn, NodeModLike, ExpandedElement और अधिक। StyleResult जैसे स्टाइलिंग प्रकार को 'nuclo' से भी आयात किया जा सकता है।

विशेष कुंजी

अक्सर पूछे जाने वाले प्रश्न

#

क्या स्थिति बदलने से UI अपडेट हो जाता है?

नंबर। DOM तब तक वैसा ही रहता है जब तक आप update() पर कॉल नहीं करते।

क्या Nuclo सिग्नल, प्रॉक्सी या वर्चुअल DOM का उपयोग करता है?

नंबर स्टेट साधारण JavaScript रहता है। जब एक ट्री प्रस्तुत किया जाता है, तो Nuclo वास्तविक तत्व और टेक्स्ट नोड बनाता है। update() प्रत्येक पंजीकृत गतिशील मान, या दिए गए दायरे के अंदर चलाता है, और उन्हीं नोड्स को पैच करता है जहां मान बदल गया है। कोई निर्भरता ग्राफ़ और कोई वर्चुअल ट्री नहीं है।

मेरा पेज "false" क्यों दिखाता है?

बूलियन्स पाठ के रूप में प्रस्तुत करते हैं। div(ok && span('Done')) "false" दिखाता है। चाइल्ड नोड के लिए when(() => ok, span('Done')) और टेक्स्ट के लिए ok ? 'Done' : '' का उपयोग करें।

जब मैं आइटम संपादित करता हूं तो मेरी सूची पंक्ति क्यों नहीं बदलती?

उन आइटमों की पंक्तियाँ जो अभी भी सूची में हैं, रखी जाती हैं, दोबारा प्रस्तुत नहीं की जाती हैं। बदलने वाले फ़ील्ड के लिए () => todo.text जैसे गतिशील मानों का उपयोग करें। list() देखें।

मेरे UI का हिस्सा अपडेट होना क्यों बंद हो जाता है?

आमतौर पर जब update() चलता था तो इसके नोड्स दस्तावेज़ में नहीं होते थे। Nuclo उन नोड्स को ट्रैक करना बंद कर देता है, भले ही आप उन्हें संलग्न कर लें, इसलिए दस्तावेज़ में मौजूद कंटेनर में प्रस्तुत करें। दो अन्य कारण: एक when() जिसकी स्थिति थ्रो अपडेट करना बंद कर देती है, और एक टेक्स्ट फ़ंक्शन जो अपने पहले रन पर थ्रो करता है वह खाली रहता है। update() देखें।

मुझे forceUpdate() का उपयोग कब करना चाहिए?

केवल स्थिर मानों में दुर्लभ परिवर्तनों के लिए, जैसे भाषा बदलना। बाकी सभी चीज़ों के लिए, update() का उपयोग करें। forceUpdate() देखें।

मैं किसी पेज को प्रोप के रूप में पास किए बिना लेआउट में कैसे डालूं?

region({ id: 'main' }) के साथ स्थान को चिह्नित करें और पृष्ठ को into('main', …) पर लौटाएं। region() & into() देखें।

मैं किसी ऐप को कैसे अनमाउंट करूं?

इसे when() में लपेटें और स्थिति को बंद कर दें। DOM को स्वयं हटाने से onDestroy नहीं चलता है। Lifecycle देखें।

क्या Nuclo में राउटर शामिल है?

कोर पैकेज में नहीं है। सर्वर रेंडरिंग, हाइड्रेशन, कोड-स्प्लिट रूट और निष्क्रिय प्रीलोडिंग के साथ जहाजों को nuclo-router के रूप में अलग से रूट करना। इसे npm install nuclo nuclo-router के साथ इंस्टॉल करें। nuclo-router देखें।

क्या Nuclo का प्रभाव है?

नंबर onMount और onDestroy प्रति तत्व एक बार चलते हैं, सम्मिलन और निष्कासन से बंधे होते हैं। कुछ भी दोबारा नहीं चलता क्योंकि कुछ स्टेट बदल गए।