v1.0.0

HTML components.
Zero runtime.

What if you could have components that were just HTML files without any special syntax? Now you can. Bascik is a build tool for HTML components with automatically scoped CSS and JS. Zero runtime. The code that ships is the code you wrote.

npm create bascik@latest
  • 0 kB framework JS added
  • Zero prod deps (single dev dep)
  • 78 pages transpiled in ~1.4s
  • Lighthouse 100s out of the box
  • Use what you know: vanilla HTML, CSS, JS

Write your code. Bascik does the rest.

Create a file with your component name. Add your HTML, CSS, and JavaScript. Reference that tag in any page or component with no imports or configuration needed.

my-card
html
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<body>
  <my-card>
    <h3>My Card</h3>
    <p>Any HTML goes inside as slot content.</p>
  </my-card>
</body>
</html>
html
<!-- src/components/my-card.html -->
<style>
  .card {
    padding: 24px 28px;
    border: 1px solid #3a3d40;
    border-top: 3px solid #d3ff8d;
    border-radius: 10px;
  }
</style>
<article class="card">
  <div data-bascik-slot></div>
</article>
Output is shown unminified (HTML, CSS, JS, and identifier names) for readability.
html
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .bascik__my-card__card {
      padding: 24px 28px;
      border: 1px solid #3a3d40;
      border-top: 3px solid #d3ff8d;
      border-radius: 10px;
    }
  </style>
</head>
<body>
  <article class="bascik__my-card__card">
    <h3>My Card</h3>
    <p>Any HTML goes inside as slot content.</p>
  </article>
</body>
</html>

Live Example

My Card

Any HTML goes inside as slot content.

Sub-second build speeds. Industry-leading performance.

Bascik transpiles and scopes entire static websites in milliseconds. Pure component scoping executes in under 0.2ms per page. This documentation website is a prime real-world example: across 78 pages executing over 500 custom Node.js build scripts to render Markdown and extract code blocks, the entire site transpiles for dev startup in about 1.4 seconds with a warm cache and workers enabled on multi-core hardware.

yarn docs:dev
text
$ bascik
transpiled: pages/404.html in 0.4ms
transpiled: pages/index.html in 0.8ms
transpiled: pages/cli.html in 0.5ms
transpiled: pages/license.html in 0.3ms
transpiled: pages/getting-started.html in 0.6ms
...
✓ 78 pages transpiled in 1.39s
Server running at http://localhost:8080
1.39s
78 full pages transpiled with 500+ build scripts, Markdown rendering, and component scoping (warm cache, workers enabled on multi-core hardware)
< 0.2ms
Pure component and CSS scoping time per page
0 kB
Framework runtime overhead

Warm vs. Cold Startup Note: The ~1.4s dev startup transpile time above reflects a warm start with workers enabled on multi-core hardware and SHA-256 build script output caching enabled. On a fresh cold start (or after clearing node_modules/.cache/bascik), Bascik runs each uncached build script in its own isolated child process bounded by a concurrency-aware semaphore, compiling all 78 pages and 500+ Markdown scripts in ~3.8s on an M1 MacBook Air. Once cached, subsequent restarts and live-reload edits compile in milliseconds.

One component. Two isolated instances.

Bascik scopes CSS class names and DOM selectors at build time. Each instance gets its own namespace with no runtime JS, no Shadow DOM, and no virtual DOM.

demo-counter
html
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
<body>
  <demo-counter data-bascik-prop-label="Instance A" />
  <demo-counter data-bascik-prop-label="Instance B" />
</body>
</html>
html
<!-- src/components/demo-counter/demo-counter.html -->
<div class="ctr">
  <span class="ctr-label" data-bascik-prop-label>
    Counter
  </span>
  <span class="ctr-count" id="count">0</span>
  <div class="ctr-btns">
    <button class="ctr-dec" id="dec">−</button>
    <button class="ctr-inc" id="inc">+</button>
  </div>
</div>
<script src="demo-counter.ts"></script>
css
/* src/components/demo-counter/demo-counter.css */
.ctr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.ctr-count {
  font-size: 2.4rem;
  font-weight: 700;
  color: #d3ff8d;
}
.ctr-dec, .ctr-inc {
  width: 40px;
  height: 40px;
  border-radius: 6px;
  cursor: pointer;
}
ts
// src/components/demo-counter/demo-counter.ts
const count = document.getElementById('count') as HTMLElement;
const dec   = document.getElementById('dec') as HTMLButtonElement;
const inc   = document.getElementById('inc') as HTMLButtonElement;
let n: number = 0;

dec.addEventListener('click', () => {
  n--;
  count.textContent = String(n);
});
inc.addEventListener('click', () => {
  n++;
  count.textContent = String(n);
});
Output is shown unminified (HTML, CSS, JS, and identifier names) for readability.
html
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    .bascik__demo-counter__ctr {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
    }
    .bascik__demo-counter__ctr-count {
      font-size: 2.4rem;
      font-weight: 700;
      color: #d3ff8d;
    }
    .bascik__demo-counter__ctr-dec,
    .bascik__demo-counter__ctr-inc {
      width: 40px;
      height: 40px;
      border-radius: 6px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="bascik__demo-counter__ctr">
    <span class="bascik__demo-counter__ctr-label">Instance A</span>
    <span class="bascik__demo-counter__ctr-count"
          id="bascik__demo-counter__a1b2__count">0</span>
    <div class="bascik__demo-counter__ctr-btns">
      <button class="bascik__demo-counter__ctr-dec"
              id="bascik__demo-counter__a1b2__dec">−</button>
      <button class="bascik__demo-counter__ctr-inc"
              id="bascik__demo-counter__a1b2__inc">+</button>
    </div>
  </div>
  <script>
    (function() {
      const count = document.getElementById("bascik__demo-counter__a1b2__count");
      const dec   = document.getElementById("bascik__demo-counter__a1b2__dec");
      const inc   = document.getElementById("bascik__demo-counter__a1b2__inc");
      let n = 0;
      dec.addEventListener("click", () => { n--; count.textContent = String(n); });
      inc.addEventListener("click", () => { n++; count.textContent = String(n); });
    })();
  </script>
</body>
</html>
Multiple instances of a component on the same page
Instance A 0
Instance B 0

Built for developer joy.

Component modularity and automatic scoping without the framework friction, complex setups, or runtime overhead.

Zero Configuration

Works instantly

No bundler setups, complex configs, or transpiler chains. Run bascik and start building instantly with standard Node.js.

Zero Dependencies

Lean in production

Zero production dependencies at build and server runtime. A single optional dependency (chokidar) powers local dev file watching.

Zero Learning Curve

Standard HTML & CSS

No JSX, custom template syntax, or framework hooks. Write pure HTML and CSS with standard DOM APIs—everything you know just works.

Zero Runtime Overhead

Automatic isolation

Component styles and DOM queries are scoped automatically at build time. Enjoy total component isolation with zero added JavaScript.

Up and running in under a minute.

One command scaffolds your project and starts the dev server. No config required to get going.

$ npm create bascik@latest
Read the Getting Started Guide →