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.2✓Component 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.2✓Component 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.2△Single-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 §4✓Bascik 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.5✓A 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.6✓Internal 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 URL✓Dynamic 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.2✓In 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 HTML✓Supported 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.6✓Extracted 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 HTML✓Instance 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.12✓Client <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.12✓Comments 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.5✓Literal </body> text inside <textarea> and </head> text inside <script> do not terminate document extraction or duplicate the remaining page during reassembly.
Global class passthroughWHATWG HTML✓Class 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.
Class attribute whitespaceWHATWG HTML §2.3.7✓Class tokens are normalized across spaces, tabs, and newlines. Leading, trailing, and repeated whitespace never creates an empty scoped token.
Slot fallback semanticsWHATWG DOM §4✓Default and named slots preserve their inner placeholder markup when no replacement content is passed from the parent template.
Prop value escapingWHATWG HTML §13.2.5✓Prop 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.6✓data-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.
Nested prop boundaryWHATWG HTML §3.2.6.6✓Props 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.2✓scoping.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.
Element-level preserveWHATWG HTML §3.2.6.6✓data-bascik-preserve applies to one subtree. A bare directive 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.2✓Internal 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 3✓HTML minification preserves single spaces between inline phrasing elements (INLINE_TAGS: span, a, strong, em, code, etc.) while safely collapsing block-level whitespace.
<meta> tag preservationWHATWG HTML §4.2.5✓Standard metadata attributes on <meta> tags (e.g. name="viewport", name="description") are shielded from attribute scoping.

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, commandfor✓Each single-ID reference is rewritten when its target is declared locally.
aria-activedescendant, aria-errormessage✓Single-ID ARIA references resolve locally.
aria-labelledby, aria-describedby, aria-controls, aria-owns, aria-flowto, aria-details✓Space-separated tokens resolve independently; nonlocal tokens remain unchanged.
itemref✓Space-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:href✓Fragment-only references to local SVG IDs are rewritten.
SVG presentation attributes✓fill, stroke, mask, clip-path, filter, marker-start, marker-mid, and marker-end rewrite local url(#id) fragments.
Inline style attributes✓Local url(#id) fragments are rewritten. Component <style> blocks and stylesheets are covered separately by CSS scoping.
CSS url(#id) fragments✓Local 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 fragments✓Values 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 subtrees✓References and declarations inside scoping.preserve tags or data-bascik-preserve subtrees remain literal.

Server & API Features

CapabilityStatusNotes
WHATWG Request / Response handlers✓Standard handler signature (request: Request, context) => Response | Promise<Response> in src/api/*.ts.
Supported HTTP methods✓GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.
Automatic Allow headers✓405 responses automatically list exported methods in Allow.
Derived HEAD✓HEAD derives automatically from GET when unexported, returning headers with empty body.
Auto OPTIONS✓Auto-responds 204 with Allow header.
Multiple Set-Cookie headers✓Preserved via Headers.getSetCookie() without comma-flattening.
Streaming responses✓WHATWG ReadableStream bodies stream directly to clients.
Dynamic API route parameters✓Extracted from [param] path segments into context.params.
Client IP resolution✓Forwarded client IP provided via context.remoteIp, respecting http.trustProxy.
Request body streaming✓Standard WHATWG request.body stream with duplex: 'half' support.
Streaming body size limit✓http.maxBodySize counts bytes on the fly (defaults to 1 MB) and aborts with 413 without buffering.
Handler execution timeout✓http.apiTimeout triggers 504 and cooperative AbortSignal for async work.
Error information protection✓Thrown handler errors return generic 500 without leaking stack traces or source paths.
Header injection protection✓CR and LF in handler-supplied header values are rejected.
Middleware chains (_middleware.ts)✕Not supported by design. Handlers compose plain functions directly.
Built-in schema validation✕Not supported by design. Validate payloads using standard libraries (e.g. Zod, Valibot).
Automatic response compression on API routes✕Deliberately omitted to prevent BREACH attacks on sensitive dynamic data. Handlers set Content-Encoding manually if needed.
Automatic CORS headers✕Not 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 on matching elements in component HTML, including indented selectors inside at-rules such as @media and inline <style> blocks. 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
@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.
@container@container sidebar (min-width: …) {}✓Container names declared via container-name: or the container: shorthand are scoped; @container name (…) queries updated to match. 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.
Multiple animation: valuesanimation: a 1s, b 2s✓Both keyframe name references are scoped when an animation: shorthand lists more than one animation.
Child / sibling combinators.a > .b, .a + .b, .a ~ .b✓All class names on both sides of >, +, and ~ are scoped.
: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. 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. 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"]) remain literal and are not rewritten as selector syntax.
Compound / descendant element selectorsdiv p {}, .card p {}, .list > li {}△.class element {} and .class > element {} (class followed by descendant/child element) are now scoped: the element name is converted to a class and injected onto matching HTML elements. Patterns with two bare element types (div p {}, p + p {}) still require a class anchor on the left side of the combinator.
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) {}.
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 deduplication✓Component 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.identifiers✓In production builds, verbose names like bascik__site-nav__a1b2c3__logo are hashed to short strings (e.g. ba1b2c3d) for name compression.
minify.css✓Whitespace in the compiled <style> block is collapsed.
minify.html✓Minifies 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.
Comments✓Ordinary HTML comments (<!-- ... -->) are stripped. Comment-like content inside <pre>, <textarea>, <style>, and scripts is preserved.
SVG elements in component HTML✓class 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-face△Passed 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.
@import✓Local 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.
@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-style✓Class 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.
view-transition-name✓view-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. The keywords none and auto are not scoped.
anchor-name / @position-try✓anchor-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 anchors declared in the component's own CSS are scoped, external anchor references are left untouched.

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 MIME type minification✓Inline 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 selection✓true (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>🚫Left 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 script🚫Stripping 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's stdout is injected in place of the tag. Runs in both dev and build modes. Use console.log() / process.stdout.write() to output HTML. Top-level import and await are supported.
<script data-bascik-routes>✓Executed at build time inside bracket templates (e.g. [slug].html). Standard output is parsed as a JSON 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 component✓Scoped 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.
DevTools //# sourceURL directives✓Automatically appended to every client, build, and server <script> block with 1:1 newline padding to preserve source file paths and line numbers in browser DevTools and Node.js debuggers. 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)✓Ephemeral build-time and server-side script errors are remapped back to source HTML files and exact line numbers while stripping noisy internal Node.js runtime 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 tags✓HTML 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")class✓
document.querySelector with .classquerySelector(".my-cls")class✓Single-token class selector only.
document.querySelectorAll with .classquerySelectorAll(".my-cls")class✓Single-token class selector only.
document.getElementsByNamegetElementsByName("my-name")name✓
element.closest with #idel.closest("#my-id")id✓
element.closest with .classel.closest(".my-cls")class✓Single-token class selector only.
element.matches with #idel.matches("#my-id")id✓
element.matches with .classel.matches(".my-cls")class✓Single-token class selector only. Works for event delegation: e.target.matches(".my-cls").
element.classList.addel.classList.add("my-cls")class✓Single and multi-argument forms: classList.add("a", "b") rewrites all class names.
element.classList.removeel.classList.remove("my-cls")class✓Single and multi-argument forms.
element.classList.toggleel.classList.toggle("my-cls")class✓The optional boolean second argument is passed through unchanged.
element.classList.containsel.classList.contains("my-cls")class✓
element.classList.replaceel.classList.replace("old", "new")class✓Both old and new class name arguments are rewritten.
Compound querySelector / querySelectorAllquerySelector(".foo .bar")class / id✓Space and combinator tokens (>, +, ~) rewritten. Adjacent .foo.bar rewrites leading token.
element.className setterel.className = "my-cls"class✓Single and multi-class string assignments rewritten (= and +=). Reading className is unchanged.
element.setAttribute("class", …)el.setAttribute("class", "my-cls")class✓String literal values are rewritten.
element.setAttribute("id", …)el.setAttribute("id", "my-id")id✓String literal values are rewritten.
element.setAttribute("name", …)el.setAttribute("name", "my-name")name✓String literal values for known name attributes are rewritten.
innerHTML / insertAdjacentHTML stringsel.innerHTML = '<div class="box">'class✓Known 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"id✕Not rewritten. Use getElementById to retrieve and operate on the reference.
querySelector attribute selectorquerySelector("[id='my-id']")id✕Use 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 9112✓Uses 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.3✓Generates 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.1✓Evaluates 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.5✓Sends 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.1✓Enforces GET and HEAD requests only. Rejects other methods with 405 Method Not Allowed and sends Allow: GET, HEAD.
Strict-Transport-Security (HSTS)IETF RFC 6797✓Automatically 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 Guidelines✓Every 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 9239✓Serves JavaScript files (.js, .mjs, .cjs) as text/javascript; charset=utf-8 (application/javascript is legacy and deprecated per RFC 9239).
Text MIME CharsetsIANA Media Types✓All text and data MIME types (text/html, text/css, application/json, application/geo+json) include explicit charset=utf-8.
Server-Sent Events (SSE)WHATWG EventSource✓Endpoint /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 Standard✓Decodes request paths and strictly enforces boundary containment within dist/. Rejects /../ path segments with 400 Bad Request.
Sitemaps ProtocolSitemaps.org / W3C✓Generates valid XML sitemaps with namespace http://www.sitemaps.org/schemas/sitemap/0.9, escaping XML metacharacters and normalizing canonical URLs.
Robots Exclusion ProtocolIETF RFC 9309✓Generates RFC 9309 compliant robots.txt pointing crawlers at the XML sitemap.