Web Standards & Scoping Compatibility

This page documents Bascik's support matrix against authoritative W3C Recommendations, WHATWG Living Standards, IETF RFCs, and ECMA-262 specifications, cross-referenced with MDN Web Docs documentation and Baseline browser compatibility tiers.

Legend

  • Supported and tested
  • Partially supported (see notes)
  • Intentionally unsupported (see notes)
  • Not yet supported

Web Standards Authority Matrix

Bascik operates as a zero-runtime build-time compiler and HTTP delivery server. All transformations, protocols, and APIs are designed to comply with official web specifications:

Domain / SubsystemAuthoritative StandardMDN / Baseline StatusSummary
HTML Elements & CompositionWHATWG HTML §3 / §4Baseline: Widely AvailableCustom element naming, valid data-* attributes, slot fallback, raw-text masking
HTML MinificationCSS Text 3 / WHATWG HTMLBaseline: Widely AvailableSafe phrasing content space preservation (INLINE_TAGS) and block collapsing
CSS Selectors & NestingW3C Selectors 4 / CSS NestingBaseline: Widely AvailableClass scoping, 2023 relaxed nesting, pseudo-classes, combinators
Modern CSS At-RulesW3C CSS Module Level 3 / 5Baseline: Newly / Widely Available@layer, @container, @keyframes, @property, @counter-style, @starting-style, @position-try
JavaScript DOM ScopingWHATWG DOM §4 / WebIDLBaseline: Widely AvailableCompile-time query rewriting for standard DOM element methods
Script Execution IsolationECMA-262 / WHATWG HTML §7.1Baseline: Widely AvailableIIFE encapsulation for classic scripts, native module isolation for type="module"
HTTP/1.1 & HTTP/2 ProtocolsIETF RFC 9112 / RFC 9113Baseline: Widely AvailableALPN negotiation, HTTP/2 multiplexing, pseudo-headers, stream lifecycle
HTTP Semantics & CachingIETF RFC 9110 / RFC 6797Baseline: Widely AvailableStrong/weak ETags, conditional 304 responses, Vary, Brotli, HSTS header
MIME TypesIETF RFC 9239 / IANABaseline: Widely AvailableCurrent standard text/javascript; charset=utf-8 media types
Development Static AssetsNode.js filesystemN/AOptional relative symlinks for unchanged page assets; builds always emit regular files
Live Reload & EventsWHATWG EventSourceBaseline: Widely AvailableServer-Sent Events (SSE) /bascik-live-reload endpoint
Sitemaps & RobotsSitemaps 0.9 / IETF RFC 9309Standard ProtocolsCanonical XML sitemap and robots exclusion directives
Content Security PolicyW3C CSP Level 3Baseline: Widely AvailableSHA-256 hash manifests (dist/.bascik/csp-hashes.json) for inline scripts and styles; per-request nonces intentionally unsupported for static caching

Component Template Structure & HTML Standards

Bascik supports flexible HTML, CSS, and JavaScript structures inside .html component files without requiring runtime frameworks.

CapabilityStandard / SpecStatusNotes
Hyphenated custom element namesWHATWG HTML §4.13.1.2Component tags with hyphens (e.g. <my-button>, <site-nav>) follow the WHATWG custom element standard and prevent collisions with native tags.
Unique component filenamesWHATWG HTML §4.13.1.2Component names are derived strictly from filenames. Subfolders are supported for organization, but duplicate filenames producing the same tag name error at build time.
Single-word component filenamesWHATWG HTML §4.13.1.2Single-word component names (e.g. card.html -> <card></card>) compile for backward compatibility, but Bascik's CLI compiler and VS Code extension issue warnings recommending a hyphenated name (e.g. my-card.html).
Native element shadowing guardWHATWG HTML §4Bascik maintains a set of 115 native HTML elements and issues a build-time warning if a component filename shadows a native tag (e.g. header.html or dialog.html).
Exact component tag matchingWHATWG HTML §13.2.5A component name matches only the complete tag name. A card component never claims a longer hyphenated tag such as <card-header>.
Custom data-* attributesWHATWG HTML §3.2.6.6Internal directives (data-bascik-prop-*, data-bascik-attr-*, data-bascik-preserve, data-bascik-slot, data-bascik-build, data-bascik-routes, data-bascik-server, data-bascik-stream) strictly conform to XML NCName lower-case naming syntax. Directives match only as whole attribute names, so an unrelated attribute such as data-bascik-server-foo or data-bascik-build-x is left alone and the tag is treated as an ordinary client script.
URL-safe dynamic route parametersWHATWG URLDynamic route parameters reject illegal filename and URL characters (#, %, &, ', +, spaces, leading dots, Windows device names) to ensure predictable static serving.
Self-closing custom tagsWHATWG HTML §13.1.2In HTML source code, custom tags can use self-closing syntax with or without a space (<my-comp /> or <my-comp/>). Both forms balance correctly when nested inside a paired instance and expand at build time into standard HTML.
Multiple top-level HTML elementsWHATWG HTMLSupported naturally without requiring single wrapper elements or fragment tags. All root elements are inserted in document order. Inherited usage attributes merge onto the first content element after leading text, <link>, or <meta> nodes. Root opening tags are parsed quote-aware, so > inside an attribute value remains intact.
Multiple <style> blocksWHATWG HTML §4.2.6Extracted and combined with any companion .css file before scoping and deduplication. Note: Using multiple <style> tags in a single component file is supported, but using a single stylesheet pattern per component is recommended for maintainability.
Deterministic instance IDsWHATWG HTMLInstance IDs for scoped id and name attributes are derived deterministically from (page path, component name, ordinal index), ensuring byte-identical builds across repeated runs.
Multiple <script> blocksWHATWG HTML §4.12Client <script> blocks are each wrapped in an independent IIFE. Recommended for clean, maintainable code when separating unrelated logic within a component. Build (data-bascik-build), server (data-bascik-server), stream (data-bascik-stream), routes (data-bascik-routes), and data scripts (e.g. type="application/ld+json") are processed according to their script type.
Raw-text comments and nested scriptsWHATWG HTML §4.12Comments and comment-like text inside <pre>, <textarea>, scripts, and <style> are shielded before HTML comments are stripped. CSS CDO/CDC tokens (<!-- ... -->) inside a <style> element are preserved so authored styles survive HTML minification. Scripts nested inside containers remain in place.
Raw-text document closing tagsWHATWG HTML §13.2.5Literal </body> text inside <textarea> and </head> text inside <script> do not terminate document extraction or duplicate the remaining page during reassembly.
Global class passthroughWHATWG HTMLClass names in component HTML that are not declared in the component's stylesheet (such as global utility classes like skip-link, flex, hidden) pass through as unscoped global classes so global stylesheets continue to match them. A component with no stylesheet, an empty one, or one containing only comments declares no classes, so none of its classes are scoped.
Class attribute whitespaceWHATWG HTML §2.3.7Class tokens are normalized across spaces, tabs, and newlines. Leading, trailing, and repeated whitespace never creates an empty scoped token.
Quoted, unquoted, and whitespace-spaced attributesWHATWG HTML §13.1.2.3Double-quoted (class="card"), single-quoted (class='card'), whitespace-spaced (class = "card"), and unquoted (class=card, id=hero, for=email) attributes are supported across HTML attribute scoping, element/ID class injection, and ID reference rewriting.
Slot fallback semanticsWHATWG DOM §4Default and named slots preserve their inner placeholder markup when no replacement content is passed from the parent template.
Prop value escapingWHATWG HTML §13.2.5Prop values are HTML-escaped on injection. Entity-encoded quotes round-trip as text; slots are the raw-markup path.
Prop-to-attribute bindingWHATWG HTML §3.2.6.6data-bascik-attr-{attribute}="{propName}" binds a supplied prop to plain and hyphenated attributes, then removes the directive. Missing props add nothing; existing targets warn and are replaced. Bound id, name, and class values use normal scoping rules.
Default slot forwardingWHATWG HTML §4.12.4A valueless data-bascik-slot marker written between a nested component's usage tags receives the outer component's default slot content, or its own fallback when the outer tag is empty. Named wrappers stay with the nested component.
Nested prop boundaryWHATWG HTML §3.2.6.6Props are extracted only from a component's opening usage tag, so declarations on nested components inside slot content never leak into the parent.
Tag-level preserveWHATWG HTML §13.1.2scoping.preserve keeps each configured tag's id, name, and class attributes, contents, and descendants unscoped through shared restorable shielding. Entries accept * wildcard patterns (for example vendor-*) with the same semantics as exact names.
External custom elementsWHATWG HTML §4.13components.external lists hyphenated tags owned by a custom element or library (exact names or * wildcards, case-insensitive). Matching tags are omitted from the Unresolved component tag build warning and the --check unmatched-tag findings. A real component of the same name still expands, and attribute scoping is unaffected.
Element-level preserveWHATWG HTML §3.2.6.6data-bascik-preserve applies to one subtree. A bare directive, or an empty value (data-bascik-preserve=""), preserves id, name, and class; a space-separated value preserves only listed attributes. The directive is removed from output.
Internal raw-text maskWHATWG HTML §13.1.2Internal scans use a hardcoded same-length discard mask for scripts, styles, textareas, and comments. It is not configurable and is distinct from author-facing preservation.
Inline phrasing whitespace preservationCSS Text Level 3HTML minification preserves single spaces between inline phrasing elements (INLINE_TAGS: span, a, strong, em, code, etc.) while safely collapsing block-level whitespace. Only spaces, tabs, and line breaks collapse: U+00A0 and other Unicode spaces, attribute values, and <pre>, <textarea>, <script>, and <style> content are kept. CSS is not read, so white-space: pre on another element is not detected.
<meta> tag preservationWHATWG HTML §4.2.5Standard metadata attributes on <meta> tags (e.g. name="viewport", name="description") are shielded from attribute scoping.
Exclusive <details name> groupsWHATWG HTML §4.11.1name is scoped per instance like any other name, so the <details> elements of one component instance form one group and other instances stay independent. Add data-bascik-preserve="name" to group across instances or with page markup.
Declarative shadow DOMWHATWG HTML §4.12.3<template shadowrootmode="open"> in a component passes through as markup. Bascik does not scope inside a shadow root, which isolates its own styles.
Custom elements defined in scriptWHATWG HTML §4.13Real custom elements from a library or your own customElements.define work alongside Bascik components. List their tags in components.external so they are not reported as unresolved components. Their shadow roots are not scoped by Bascik.

ID References

When an id declaration is scoped, Bascik rewrites references that resolve to that declaration in the same component. Unresolved references remain byte-identical so components can still target literal page-level IDs.

ReferenceStatusNotes
for on <label>Rewritten as one ID so label activation continues to focus the scoped control.
form, list, popovertarget, commandforEach single-ID reference is rewritten when its target is declared locally.
aria-activedescendant, aria-errormessageSingle-ID ARIA references resolve locally.
aria-labelledby, aria-describedby, aria-controls, aria-owns, aria-flowto, aria-detailsSpace-separated tokens resolve independently; nonlocal tokens remain unchanged.
itemrefSpace-separated microdata item IDs resolve independently.
headers on <td> and <th>Space-separated header IDs resolve independently.
for on <output>Treated as a space-separated ID list, unlike the single-ID <label for>.
Fragment links on <a> and <area>Fragment-only values such as href="#section" resolve locally. Bare hashes and other-document URLs remain unchanged.
SVG <use href> and xlink:hrefFragment-only references to local SVG IDs are rewritten.
SVG presentation attributesfill, stroke, mask, clip-path, filter, marker-start, marker-mid, and marker-end rewrite local url(#id) fragments.
Inline style attributesLocal url(#id) fragments are rewritten. Component <style> blocks and stylesheets are covered separately by CSS scoping.
CSS url(#id) fragmentsLocal fragments in component stylesheets and <style> blocks are rewritten for properties including fill, stroke, clip-path, mask, filter, and marker properties. Components with resolvable fragments automatically emit per-instance CSS. New in 1.0 hardening: url-like text inside string literals (e.g. content: "url(#local)") is preserved byte-for-byte, and only genuine url(...) fragments are rewritten.
CSS cross-document fragmentsValues such as url(other.svg#icon) are deliberately untouched because the fragment belongs to another document. Real, remote, and data URLs also remain unchanged.
usemap on <img>Resolves against a local <map name>, not an ID, and follows scoping.attributes.name.
Cross-component ID references✗IDs are scoped per instance, so references cannot resolve safely across component boundaries at build time. They remain unchanged.
Preserved subtreesReferences and declarations inside scoping.preserve tags or data-bascik-preserve subtrees remain literal. A link inside a preserved subtree is never rewritten, even to a scoped target elsewhere in the component, so a link and its target must be both preserved or both scoped. A scoped link to a preserved target keeps working.

Server & API Features

CapabilityStatusNotes
WHATWG Request / Response handlersStandard handler signature (request: Request, context) => Response | Promise<Response> in src/api/*.ts.
Supported HTTP methodsGET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.
Automatic Allow headers405 responses automatically list exported methods in Allow.
Derived HEADHEAD derives automatically from GET when unexported, returning headers with empty body.
Auto OPTIONSAuto-responds 204 with Allow header.
Multiple Set-Cookie headersPreserved via Headers.getSetCookie() without comma-flattening.
Streaming responsesWHATWG ReadableStream bodies stream directly to clients.
Dynamic API route parametersExtracted from [param] path segments into context.params.
Catch-all API routes ([...path].ts)Required catch-all: one or more remaining segments as a decoded string[]. Static beats [param] beats catch-all. Unsafe captures return 400; single-dot and hidden segments are blocked before matching with 404. API routes only; same behavior on --server, HTTP/2, and the Cloudflare adapter.
Client IP resolutionForwarded client IP provided via context.remoteIp, respecting http.trustProxy.
Request body streamingStandard WHATWG request.body stream with duplex: 'half' support.
Streaming body size limithttp.maxBodySize counts bytes on the fly (defaults to 1 MB) and aborts with 413 without buffering.
Handler execution timeouthttp.apiTimeout triggers 504 and cooperative AbortSignal for async work.
Error information protectionThrown handler errors return generic 500 without leaking stack traces or source paths.
Header injection protectionCR and LF in handler-supplied header values are rejected.
Directive scripts printed by a build scriptUnsupportedA data-bascik-build, data-bascik-server, data-bascik-stream, or data-bascik-routes script in build script output is removed with a warning, so HTML from a CMS or an API cannot run code at build or request time. Ordinary scripts and handlers in printed HTML are not removed; sanitize it.
Large dynamic route dataAny size the routes script prints. Over 32 KB, BASCIK_ROUTE is delivered through a temporary file, avoiding the OS environment limit (spawn E2BIG).
Middleware chains (_middleware.ts)Not supported by design. Handlers compose plain functions directly.
Built-in schema validationNot supported by design. Validate payloads using standard libraries (e.g. Zod, Valibot).
Automatic response compression on API routesDeliberately omitted to prevent BREACH attacks on sensitive dynamic data. Handlers set Content-Encoding manually if needed.
Automatic CORS headersNot injected by default. Handlers return explicit Access-Control-* headers when required.

CSS Scoping

CSS scoping applies to .css files paired with a component's HTML file. Place the .css file in the same directory as the component and give it the same base name.

Selectors

PatternExampleStatusNotes
Class selector.foo {}Scoped with unique instance prefix
Descendant with class.foo .bar {}All class names in selector scoped
Multi-class.foo.bar {}Both class names scoped
Standalone element selectorp {}Converted to a generated class and injected only on elements with the exact tag name in component HTML, including indented selectors inside at-rules such as @media and inline <style> blocks. A selector for header does not match a custom element such as <header-link>, and hyphenated type selectors such as code-block are left unchanged rather than split at the hyphen. Note on specificity: converting element selectors (0, 0, 1) to class selectors (0, 1, 0) is an intentional zero-runtime build-time transformation that guarantees robust class isolation across components without requiring runtime shadow DOM.
Element pseudo-classp:hover {}Element converted to class; pseudo-class preserved: .bascik__...__el__p:hover {}
Element pseudo-elementp::before {}Element converted to class; pseudo-element preserved: .bascik__...__el__p::before {}
@keyframes name@keyframes spin {}Name scoped; animation: and animation-name: references updated to match. Only whole names match, so a never rewrites part of a-b or of another scoped name.
@keyframes stops40% {}, 0%, 100% {}, from {}Stop selectors are left as written, including percentage lists split across lines, so the browser keeps every stop.
@media query@media (max-width: 600px) {}Media condition untouched; class names inside scoped normally
@supports@supports (display: grid) { .foo {} }Class names inside @supports blocks are scoped normally.
@layer@layer base { .foo {} }Layer names are scoped in declaration blocks and single-name or comma-list ordering statements, including leading-hyphen names such as --utils. Only whole names match, so a never rewrites part of a-b or of another scoped name.
@container@container sidebar (min-width: …) {}Container names declared via container-name: or the container: shorthand are scoped; @container name (…) queries updated to match. Only whole names match, so a never rewrites part of a-b or of another scoped name. Unnamed queries untouched.
CSS custom properties--brand: #d3ff8d / var(--brand)Declarations and all var() references in the same file scoped together. var(--prop, fallback) is fully supported, the fallback value is preserved and the property name is scoped. Only whole property names match, so --a never rewrites part of --a-b or of another scoped name.
Multiple animation: valuesanimation: a 1s, b 2sBoth keyframe name references are scoped when an animation: shorthand lists more than one animation.
Child / sibling combinators.a > .b, .a + .b, .a ~ .bAll class names on both sides of >, +, and ~ are scoped. An element after a combinator (.a > p, .a>p) is scoped with or without surrounding spaces.
:is() / :where() / :has() with class args:is(.foo, .bar) {}Class names inside :is(), :where(), and :has() are scoped normally. Element names inside these functions are not converted (see below).
Inline <style> in component HTML<style>.foo {}</style>Full CSS scoping pipeline applied to inline <style> blocks. Extracted from component HTML into component CSS, deduplicated across component instances, and injected into page <head>.
CSS #id selector#btn {}Converted to a component-scoped class selector (.bascik__comp__id__btn {}) using a context-aware lookahead that correctly distinguishes selector position from hex color values. The generated class is injected onto the HTML element across development and production builds with identifier minification, inside @media and paired stylesheets. Specificity drops from (0,1,0,0) to (0,0,1,0). String literals, comments, and attribute-selector values (a[href="#tab"]) are never treated as ID selectors; their contents are preserved byte-for-byte.
[id] / [id="…"] attribute selector[id] {}Stripped at compile time, including inside @media and @supports blocks or when paired in a comma-separated selector list (preserving sibling selectors). Attribute-selector forms cannot be scoped without DOM wrapping.
Attribute selector[data-foo="bar"] {}Passed through untouched, not scoped. Will apply globally. Avoid in component CSS or use a class-based selector alongside it. Attribute-selector values containing # (e.g. a[href="#tab"]) or . in quoted and unquoted forms (a[href$=.pdf]) remain literal and are not rewritten as selector syntax.
Compound / descendant element selectorsdiv p {}, .card p {}, #panel p {}, .list > li {}Every element name in the chain is converted to a component-scoped class and injected onto the matching elements in the component's own template, whether anchored by an element, a class, :is(.a, .b) / :where(.a, .b), or a #id selector. Descendant, child (>), adjacent (+), and general sibling (~) combinators work, as do longer chains with pseudo-classes and the universal selector (nav a, #panel > h3 + p, p + p, ul li *, ul li:first-child img, ul a:hover img). Scoped in dev and production, including with identifier minification. The rules never match page markup or a child component's root element; see Styling a Child Component from Its Parent. Element names inside :is(), :where(), and :has() are a separate case, listed below.
Parent element selector vs. child component rootnav a {} with <nav-link> inside <nav>A parent's element selector matches only elements written in the parent's own template, never a child component's root element. Components are isolated by design. To style a child's root from the parent, put a class that the parent's CSS defines on the child's usage tag (<nav-link class="item">); Bascik scopes it to the parent and merges it onto the child's root. See Attribute Inheritance.
Element selector vs. elements a script createstd {} with document.createElement('td')An element selector is scoped by adding a generated class to the elements written in the component's template, including the content of a <template>. An element a script creates has no such class, so the rule never matches it, with no warning. Clone a <template> that the component contains, or give the element a class the stylesheet defines and set it with classList.add or className. See From Vue.
Comma-separated element selector listh1, h2 {}All elements in a comma list are converted, both multi-line (each at column 0) and same-line (h1, h2 {}). A ) stop in the lookahead prevents false positives inside :is(), :where(), :has().
Cross-boundary root element selectorshtml[data-theme="dark"] .foo {}html, body, and head are excluded from element-to-class conversion so cross-boundary selectors compile with the root element name intact. html[data-theme="light"] .component-class {} becomes html[data-theme="light"] .bascik__comp__class {} and correctly matches the component element when the document root carries a theme or state attribute.
:is() / :where() / :has() with element names:is(p, h2) {}Element names inside these functions are not converted. Class equivalents work fine: :is(.foo, .bar) {}. Anchored on a scoped class and a descendant combinator (.markdown :is(p, ul, ol)), the unconverted names match every descendant of that element, slotted content included, which is the usual intent for rendered Markdown, so css-is-element-names does not flag that form.
Element selector vs. slotted content.prose h2 {} with <h2> passed through a slot.prose h2 becomes .prose .<scoped h2>, and the scoped class is added only to h2 elements in the component's own template. Content passed through a slot (including rendered Markdown a build script prints inside the usage tag) keeps plain tags, so the rule does not match it. Write .prose :is(h2) {}: Bascik leaves element names inside :is() as written, the wrapper class stays scoped, and specificity is the same as .prose h2. See Markdown.
CSS nesting, class selectors& .child {}Class selectors inside nesting are scoped normally.
CSS nesting, element selectors& p {}, & > h2 {}, > h2 {}, p {}Fully supported for explicit nesting (& p {}, & > h2 {}, &>h2 {}, & + li {}, & ~ span {}) and 2023 relaxed direct nesting (> h2 {}, + li {}, ~ span {}, direct nested element selectors without &).
@scope (native)@scope (.foo) { .bar {} }Class names in both the @scope (.selector) argument and the optional to (.selector) clause are scoped normally (handled by the global class-scoping pass). Class names inside the @scope block are also scoped. Element names in @scope arguments and indented element selectors inside the block follow the same rules as other at-rules.
:nth-child(An+B of .selector):nth-child(2n+1 of .item) {}Class names in the of <selector> argument are scoped by the global class-scoping pass (the same (?<=\.) regex that handles :is(), :where(), and :has() class arguments). Works for :nth-child and :nth-last-child.

Other CSS Features

FeatureStatusNotes
CSS deduplicationComponent CSS is normally injected once per type. Components containing resolvable url(#id) references emit per-instance CSS so each stylesheet targets that instance's scoped ID.
minify.identifiersIn production builds, verbose names like bascik__site-nav__a1b2c3__logo are hashed to short strings (e.g. ba1b2c3d) for name compression.
minify.cssWhitespace in the compiled <style> block is collapsed. A space before : that is a descendant combinator (.a :hover, .a :is(h2)) is kept; only declaration colons are tightened.
minify.htmlMinifies HTML output. Style (and pre/textarea/script) raw text is shielded first, so CSS CDO/CDC tokens and comment-like strings inside those elements survive while ordinary document comments are stripped.
CommentsOrdinary HTML comments (<!-- ... -->) are stripped from the page head and body. Comment-like content inside <pre>, <textarea>, <style>, and scripts is preserved. The development server and minify.html: false keep comments; a component tag named inside a comment, in a page or a component template, stays text and is never expanded.
SVG elements in component HTMLclass attributes on SVG elements (<svg>, <circle>, <path>, <rect>, etc.) are scoped with the same pipeline as HTML elements. JS classList and querySelector calls targeting SVG children are rewritten.
@font-facePassed through untouched, the font-family name is not scoped. Both the declaration and all usage sites remain unmodified, so the font resolves correctly within the page. Declare @font-face in a shared global stylesheet rather than a component .css file to avoid duplicate declarations when a component is used multiple times.
@importLocal file imports (@import "./file.css") are inlined recursively at build time and scoped to the component. Remote URLs (@import "https://...") are preserved and hoisted to the top of the compiled stylesheet per W3C CSS spec requirements. A missing file becomes a not found comment and the rules after it are kept. @import text inside comments and strings is ignored. In dev, editing an imported file rebuilds the pages of every component that imports it.
@property@property --name { } declaration names are scoped. Any matching --name: element declarations and var(--name) references in the same component file are scoped to match.
@starting-styleClass names and element selectors inside @starting-style blocks are scoped by the same passes that handle other at-rules. Both standalone @starting-style { .foo { } } and nested .foo { @starting-style { } } forms are handled.
@counter-style@counter-style name { } declaration names are scoped. References in list-style, list-style-type, counter(counter, name), and counters(counter, sep, name) in the same component file are updated to match. Only whole names match, so a never rewrites part of a-b or of another scoped name.
view-transition-nameview-transition-name: name values are scoped to the component. Matching ::view-transition-old(name), ::view-transition-new(name), ::view-transition-group(name), and ::view-transition-image-pair(name) pseudo-element references in the same file are updated to match. Only whole names match, and the keywords none and auto are not scoped.
anchor-name / @position-tryanchor-name: --name declarations are scoped per component. Matching position-anchor: --name references and @position-try --name { } at-rules in the same CSS file are updated to match. Only whole anchor names declared in the component's own CSS are scoped, and external anchor references are left untouched.

Names Shared by Every Instance

Container names, layer names, anchor names, view transition names, keyframes, and custom properties declared in component CSS are scoped per component type: every instance of a component shares one name, and no other component can see it. That keeps components apart, but it changes how these features combine.

PatternExampleStatusNotes
Child component querying a parent's named container@container sidebar (…) in a child of side-barThe parent's sidebar is renamed to the parent, so the child's query never matches. Use an unnamed query, @container (min-width: 30rem), which targets the nearest container, or declare the named container in a global stylesheet.
Global layer order including component layers@layer reset, components; in a page with @layer components {} in a componentThe component's layer becomes its own private layer and is not placed by the global statement. Put base styles in layers in the global stylesheet and leave component CSS unlayered; unlayered rules win over layered ones.
anchor-name with several instances on a page.trigger { anchor-name: --menu; }Every instance declares the same scoped name, so each positioned element anchors to the last one. Use the implicit anchor of popover and popovertarget, or set the name per instance from the script: el.style.setProperty('anchor-name', `--${el.id}`).
view-transition-name with several instances on a page.card { view-transition-name: card; }Two elements with one name abort the transition. Use it on components that appear once per page, or name instances from the script: el.style.setProperty('view-transition-name', el.id). The same component on two pages shares its name, so it morphs across a navigation.
Site-wide tokens declared in a component:root { --brand: … } in component CSSThe property is renamed to the component, so no other component sees it. Declare tokens in a global stylesheet and only read them in components.

JavaScript Scoping

Bascik rewrites DOM selector references inside component <script> tags to match scoped attribute values. All rewrites happen at build time with no runtime is added.

IIFE Isolation

PatternStatusNotes
<script> (no type)Wrapped in an IIFE to prevent variable leakage between components.
<script type="text/javascript">Wrapped in an IIFE.
<script type="module">Not wrapped in an IIFE (modules are already isolated by spec). DOM selector references still rewritten.
JavaScript literal preservation in minify.js (built-in)Strings, nested template literals (including ${} expressions that contain templates, strings, regexes, and comments), and regex literals are kept byte-for-byte. A / after ), ], a quote, or a backtick is division, except after an if, while, for, or with header, where it starts a regex statement. A / after } is treated as a possible regex only when it closes on the same line.
JavaScript MIME type minificationInline scripts with no type or text/javascript, module, application/javascript, text/ecmascript, and application/ecmascript are minified when minify.js is enabled. External scripts are left empty and unchanged.
<script type="application/json"> (and any non-JS type)Left completely untouched.
Referenced .ts / .mts companion scripts (<script src="counter.ts">)Erasable TypeScript is stripped automatically when the companion is inlined, with no configuration and before any minify.js step. Line structure is preserved so //# sourceURL line numbers stay accurate. Non-erasable syntax (enum, parameter properties, runtime namespaces) fails the build with the file path under the default compiler.
scripts.typescript compiler selectiontrue (default) is Node strip-only mode; a function is a bring-your-own compiler (esbuild, swc, tsc) receiving (code, { sourcePath, kind }) and returning JavaScript, which unlocks enum, decorators, and downleveling; false disables the transform so pre-compiled output passes through untouched. Applies to both browser paths, in dev and build, on the main thread and in workers. A compiler that throws or returns non-JavaScript fails the build with the source path.
<script type="text/typescript"> (inline, page or component)Stripped to JavaScript and emitted as an ordinary <script> (the type attribute is removed), then scoped and optionally minified like any other inline script. Never applied to data-bascik-* directive scripts.
TypeScript syntax in an unmarked <script>UnsupportedLeft byte-for-byte on the JavaScript path because browsers do not execute TypeScript there. When Bascik can positively identify erasable TypeScript in such a block it logs a warning naming the file and the fix (type="text/typescript" or a companion .ts). Valid JavaScript is never rewritten or warned about.
Module-shaped .ts companion or type="text/typescript" block targeting a classic scriptUnsupportedStripping types never removes import/export. A companion .ts inlined into a <script src="..."> with no type="module", or any type="text/typescript" block (always classic), fails the build with an actionable error naming the file if the stripped code still contains a static import/export declaration, since a classic script's IIFE wrapper would throw a SyntaxError in the browser. Fix by adding type="module" to the referencing tag, or by bundling the file first so no import/export remains. Dynamic import() and import.meta are unaffected.
<script data-bascik-build>Executed at transpile time as a Node.js ESM module. The script exports a default callable function returning an HTML string, which replaces the tag. Runs in both dev and build modes. Console methods log to the terminal for debugging. Top-level import and await are supported.
<script data-bascik-routes>Executed at build time inside bracket templates (e.g. [slug].html). The script exports a default callable function returning an array of { params, data? } objects to expand the template into concrete static HTML pages.
<script data-bascik-server>Executed on the server at request time in-process as a Node.js ESM module via ScriptRegistry. Returned markup replaces the tag dynamically. Receives a WHATWG Request, a { remoteIp } context, and { signal }, the same signature as API routes. Top-level import and await are supported. No helpers are injected or exported. Buffered: all server scripts resolve before the response is sent.
<script data-bascik-stream>Same authoring model as data-bascik-server. Receives a WHATWG Request, a { remoteIp } context, and { signal }, the same signature as API routes. Top-level import and await are supported. No helpers are injected or exported. The server commits headers and every static byte before the tag without waiting for it, then writes its output when it resolves, in document order, as a chunked response. All server scripts on the page resolve before the first byte. A stream failure can never be a 500; the slot is emitted empty. Mutually exclusive with server, build, and routes.
Class names inside data-bascik-server and data-bascik-stream script source within a componentScoped at build time like any other string in the component, so a class in the returned markup matches the component's scoped CSS in both readable and minified-identifier modes. Verified: class="result" becomes the same hashed name used in the emitted :has(> .result) selector. Classes built dynamically at request time (string concatenation, values from a database) are not visible to the build and are not scoped.
Markup printed by a component's data-bascik-build scriptScoped like the component's template, in dev and production and with identifier minification. Page-aware scripts (data-bascik-build="page") run at page time, after the template is scoped, so Bascik applies the component's class and element mapping to their output. That includes markup returned by an imported helper and class names computed while the script runs. Classes the component's stylesheet does not define stay global. Markup inside comments, <script>, <style>, <textarea>, and attribute values is left as written.
DevTools //# sourceURL directivesAutomatically appended to every client, build, and server <script> block. Client scripts are padded so DevTools line numbers match the component file, including when markup or <style> blocks sit above the script (unless minify.js joins the lines). Companion scripts are numbered from line 1 of their own file. The second and later instances of a component on a page get ?instance=N, so each instance is its own DevTools file. The directive is held back from minify.js (built-in or custom) and re-attached on its own line afterwards, so a minifier that drops comments or collapses the IIFE to one line can neither lose it nor glue it onto })();.
Stack trace remapping (stack-trace.ts)Build, routes, server, and stream script errors name the file the script was written in and the failing line and column in it, including in components whose markup and <style> blocks are moved before the script runs, and in columns after an import Bascik rewrote on the same line. Build and routes syntax errors show the line and a caret; server and stream syntax errors, which Node reports with no line, name the file and the line the script starts on. src= scripts and imported helpers keep their own paths and lines. Each error prints once, without Node.js internals or Bascik's own runner and request-handling frames.
Literal component tags inside <script>, <style>, or <textarea>Treated as text, never resolved into components. Safe to mention tags like <my-card> in JSON-LD strings, inline scripts, or code examples.
HTML comments containing component tagsHTML comments (<!-- <my-card> -->) are stripped during HTML minification, so commented custom tags are never expanded into components.

DOM Selector Rewriting

MethodExampleAttribute ScopedStatusNotes
document.getElementByIdgetElementById("my-id")id
document.querySelector with #idquerySelector("#my-id")id
document.querySelectorAll with #idquerySelectorAll("#my-id")id
document.getElementsByClassNamegetElementsByClassName("my-cls")classSingle and space-separated multi-class string arguments are rewritten. Scoped to the component type (document-wide across instances unless scoping.deduplicateCss is false); use getElementById or root.querySelector for per-instance lookup. getElementsByClassName("my-cls")[0] and .item(0) raise the js-document-class-query warning.
document.querySelector with .classquerySelector(".my-cls")classSingle-token class selector only. Scoped to the component type (returns the first instance's element when scoping.deduplicateCss is true); use getElementById or root.querySelector for per-instance lookup. Any class selector without an #id (".card .title", "button.btn", backtick strings) raises the js-document-class-query warning; a selector that includes a scoped #id is per-instance and is not flagged.
document.querySelectorAll with .classquerySelectorAll(".my-cls")classSingle-token class selector only. Scoped to the component type (matches all instances on the page when scoping.deduplicateCss is true); use root.querySelectorAll rooted on a per-instance id to query within one instance.
document.getElementsByNamegetElementsByName("my-name")name
element.closest with #idel.closest("#my-id")id
element.closest with .classel.closest(".my-cls")classSingle-token class selector only.
element.matches with #idel.matches("#my-id")id
element.matches with .classel.matches(".my-cls")classSingle-token class selector only. Works for event delegation: e.target.matches(".my-cls").
element.classList.addel.classList.add("my-cls")classSingle and multi-argument forms: classList.add("a", "b") rewrites all class names.
element.classList.removeel.classList.remove("my-cls")classSingle and multi-argument forms.
element.classList.toggleel.classList.toggle("my-cls")classThe optional boolean second argument is passed through unchanged.
element.classList.containsel.classList.contains("my-cls")class
element.classList.replaceel.classList.replace("old", "new")classBoth old and new class name arguments are rewritten.
Compound querySelector / querySelectorAllquerySelector(".foo .bar")class / idSpace and combinator tokens (>, +, ~) rewritten. Adjacent .foo.bar rewrites leading token.
element.className setterel.className = "my-cls"classSingle and multi-class string assignments rewritten (= and +=). Reading className is unchanged.
element.setAttribute("class", …)el.setAttribute("class", "my-cls")classSingle and space-separated multi-class string literal values are rewritten.
element.setAttribute("id", …)el.setAttribute("id", "my-id")idString literal values are rewritten.
element.setAttribute("name", …)el.setAttribute("name", "my-name")nameString literal values for known name attributes are rewritten.
innerHTML / insertAdjacentHTML stringsel.innerHTML = '<div class="box">'classKnown class names in static HTML string literals are rewritten.
element.removeAttributeel.removeAttribute("class")Attribute names (not values) passed with no rewriting needed.
element.hasAttributeel.hasAttribute("id")Same as removeAttribute: attribute name, not value.
element.toggleAttributeel.toggleAttribute("hidden")Boolean attribute name only with no value to rewrite.
element.style.setProperty for CSS varsel.style.setProperty("--accent", v)Runtime CSS custom property names are not rewritten. Use scoped property name explicitly.
Template literal in className / selectorsel.className = `box ${state}`Template literals with expressions are not rewritten. Use classList.add/remove.
element.id setterel.id = "my-id"idNot rewritten. Use getElementById to retrieve and operate on the reference.
querySelector attribute selectorquerySelector("[id='my-id']")idUse getElementById instead.

Notes on Gaps

The unsupported JS patterns above all involve dynamic attribute manipulation where static analysis cannot safely identify which component's attribute is being referenced from a string literal.

JS-only class discovery

Class names that only appear in JavaScript (never in a class="…" HTML attribute) are automatically discovered and added to the scope map before the JS rewrite runs. This covers all class-referencing patterns: classList.* arguments, .className tokens in querySelector / querySelectorAll / closest / matches selector strings, el.className = "…" assignments, and el.setAttribute("class", "…") values. CSS-only classes (only in the .css file, never in HTML or JS) are scoped in CSS only, which is fine since nothing in JS needs to reference them.

The exception is innerHTML / insertAdjacentHTML string scanning, which only recognizes classes that appear in the HTML template.

The recommended pattern is to query scoped elements by a single id or single-class selector first, store the reference, then use the reference for all further DOM operations:

html
<!-- source - works correctly -->
<div id="panel" class="card"></div>
<script>
  const panel = document.getElementById("panel"); // ← rewritten by Bascik
  panel.style.display = "none"; // ← operate on the reference
  panel.dataset.state = "closed"; // ← data attributes for state
</script>

Class queries are document-wide (not per-instance)

Class names are scoped to the component type, not to individual instances. This means querySelectorAll(".my-class") inside a component script, which Bascik rewrites to querySelectorAll(".bascik__comp__my-class"): will find matching elements across all instances of that component on the page, not just the current instance.

To operate only on the current instance's elements, query by id (which includes a per-instance hash) and traverse from the returned element:

javascript
// In component - gets only THIS instance's panel:
const panel = document.getElementById("panel"); // rewritten with instance hash

FormData with scoped name attributes

When a component uses <input name="username">, Bascik scopes the name attribute to a per-instance value like bascik__comp__a1b2c3__username. As a result, new FormData(form) entries use the scoped name as the key. If your server-side code expects the unscoped field name, you will need to adapt it, or extract values using formData.get with the scoped name, or via form.elements iteration.


HTTP Protocols, Caching & Security Standards

When running Bascik's built-in HTTP/1.1 and HTTP/2 production server (bascik --server) or dev server (bascik), responses strictly adhere to modern IETF network and caching standards:

Feature / StandardProtocol AuthorityStatusImplementation Details
HTTP/2 & ALPN NegotiationIETF RFC 9113 / RFC 9112Uses http2.createSecureServer({ allowHTTP1: true }) for ALPN negotiation with automatic HTTP/1.1 fallback. Handles HTTP/2 pseudo-headers (:status, :path, :method, :scheme).
Strong & Content-Hash ETagsIETF RFC 9110 §8.8.3Generates strong SHA-256 content-hash ETags for both HTML pages and static assets, with distinct encoding-specific ETags ("hash-br").
Conditional GET & 304 ResponsesIETF RFC 9110 §13.1.1Evaluates incoming If-None-Match request headers against generated ETags and returns 304 Not Modified with zero response body.
Content Negotiation & VaryIETF RFC 9110 §12.5.5Sends Vary: Accept-Encoding and negotiates Brotli or gzip for compressible assets. Precompressed .br/.gz sidecars are served only when stamped with matching .bmeta provenance, which bascik --build emits under http.precompress: true; otherwise compression runs on demand. Assets above 2 MiB are streamed as identity with a weak validator.
Method GuardingIETF RFC 9110 §9.1Enforces GET and HEAD requests only. Rejects other methods with 405 Method Not Allowed and sends Allow: GET, HEAD.
Strict-Transport-Security (HSTS)IETF RFC 6797Automatically sends Strict-Transport-Security: max-age=31536000; includeSubDomains when serving HTTPS or when behind an SSL reverse proxy (x-forwarded-proto: https).
Standard Security HeadersOWASP / IETF GuidelinesEvery response sends X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, and Permissions-Policy: interest-cohort=().
Modern JavaScript MIME TypesIETF RFC 9239Serves JavaScript files (.js, .mjs, .cjs) as text/javascript; charset=utf-8 (application/javascript is legacy and deprecated per RFC 9239).
Text MIME CharsetsIANA Media TypesAll text and data MIME types (text/html, text/css, application/json, application/geo+json) include explicit charset=utf-8.
Server-Sent Events (SSE)WHATWG EventSourceEndpoint /bascik-live-reload sends Content-Type: text/event-stream; charset=utf-8, Cache-Control: no-cache, and standard double-newline frames (data: ...\n\n). Injected client includes automatic reconnect and focus re-sync.
Path Traversal SecurityWHATWG URL StandardDecodes request paths and strictly enforces boundary containment within dist/. Rejects /../ path segments with 400 Bad Request.
Sitemaps ProtocolSitemaps.org / W3CGenerates valid XML sitemaps with namespace http://www.sitemaps.org/schemas/sitemap/0.9, escaping XML metacharacters and normalizing canonical URLs.
Robots Exclusion ProtocolIETF RFC 9309Generates RFC 9309 compliant robots.txt pointing crawlers at the XML sitemap.