npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

myavka

v1.2.2

Published

CSS preprocessor

Readme

Myavka CSS Preprocessor

What is this

myavka is a small, lightweight CSS preprocessor with a module system built around a real dependency graph instead of textual substitution — modules are resolved and deduplicated properly, not copy-pasted wherever they're referenced.

Advantages

  • Modularity. Styles are organized as small, self-contained, reusable modules instead of one flat cascade you have to reason about as a whole.
  • Encapsulation. Modules don't leak into a shared global namespace — applying a module can't accidentally collide with or override something unrelated elsewhere in the project.
  • A real import/export system. Modules are explicitly imported and exported between files, closer to how JS modules work, instead of Sass-style implicit, order-dependent inclusion.
  • Built-in file watching. Tracks source file changes out of the box — no extra tooling needed to rebuild on edit.
  • Simple, focused set of directives. A small, deliberate instruction set rather than a sprawling language with loops, conditionals, and imperative-feeling variables.
  • Friendly to any valid CSS. Plain CSS files pass through untouched — you can drop myavka into an existing codebase without rewriting anything.

Why this exists

Why not Sass. Sass placeholders live in one shared, global namespace — there's no real encapsulation, just naming discipline you have to maintain by hand. @extend and nested @use/@import behave in ways that are hard to predict from reading the source alone, and the language itself is imperative in spirit (loops, conditionals, mutable-feeling variables) grafted onto a fundamentally declarative format. It works, but it doesn't feel like it was designed for the problem it's solving.

Why not PostCSS. PostCSS itself is just a transform pipeline, not a language — and the plugin ecosystem built on top of it is wildly uneven. Import/export isn't a coherent, built-in concept; you assemble it from third-party plugins, many of which are half-finished, disagree with each other on edge cases, and don't handle even obvious cases like deduplicating repeated output correctly. You end up integrating a pile of plugins instead of using a language.

Installation

npm install myavka
import { compile, compileString } from 'myavka';

const result = await compile('styles/main.css');

result.css; // compiled CSS as a string
await result.save('dist/main.css');

compileString works the same way, but takes source text directly instead of a file path:

const result = await compileString('%button { padding: 8px; } .btn { @apply %button; }');
result.css;

watch compiles a file and keeps recompiling it whenever it (or any of its dependencies) changes:

import { watch } from 'myavka';

const watcher = await watch('styles/main.css');

watcher.on('ok', (result) => {
  result.save('dist/main.css');
});

watcher.on('fail', (error) => {
  console.error(error);
});

// later
watcher.unwatch();

API

Global methods

  • async compile(url, options) — compiles a file from the given URL/path.
  • async compileString(source, options) — compiles a source string directly.
  • async compileFile(url, options) — alias for compile().
  • async watch(url, options) — compiles a file and watches it and all of its dependencies, recompiling as needed. Returns a Watcher.

url is a URL (or path) to the file being compiled — supports file:, http:, https:, data:, blob:, and relative paths (resolved relative to engine.url).

options.url — for compileString(), an additional base URL used to resolve any imports inside the given source.

options.style — CSS output formatting style. Default: "pretty".

  • "minify"
  • "pretty"
  • "colors" — same as "pretty", plus syntax highlighting in the console.
  • an object for fine-grained control:
    • options.style.tab — indentation characters
    • options.style.newline — line break characters
    • options.style.colorstrue/false, syntax highlighting

Engine (exported as Engine)

A separate, self-contained compiler instance — an alternative to the default global engine.

  • new Engine(options)
    • options.url — root URL for resolving all relative paths in compile/watch. Defaults to process.cwd().
    • options.gcDelay — delay (ms) after compiling all files before the file cache is garbage-collected.
  • engine.url — the engine's root URL.
  • engine.resolve(url) — resolves a URL/path to its full, absolute URL.
  • engine.compile(url, options) — same as the global method.
  • engine.compileString(source, options) — same as the global method.
  • engine.compileFile(url, options) — same as the global method.
  • engine.watch(url, options) — same as the global method.
  • engine.stats() — returns a stats object describing the engine's current state.
  • on('link', listener(url)) — fired once a module's dependencies have been resolved and its structure is fully built.
  • on('load', listener(url, source)) — fired once a source has been fetched, before parsing — source is the raw, unparsed text.
  • on('parse', listener(url, snapshot)) — fired once a source has been parsed into its internal representation.
  • on('watch', listener(url)) — fired when watching resumes for the given file.
  • on('unwatch', listener(url)) — fired when watching stops for the given file.
  • on('ok', listener(result)) — fired whenever a compilation finishes successfully, anywhere in the engine.
  • on('fail', listener(error, url)) — fired whenever a compilation fails, with the URL that caused it.
  • on('gc', listener(stats)) — fired after the engine's file cache is garbage-collected

Result

  • result.css — the compiled output as a string.
  • result.url — the root file's URL, or null for strings compiled via compileString.
  • result.imports — full URLs of every file used during compilation.
  • async result.save(filename) — convenience method to write the result to a file.

Watcher

Obtained from watch() / engine.watch().

  • Standard emitter methods: on/off/once/emit.
  • watcher.watchingtrue if file watching is currently active.
  • watch() — resumes file watching if it was previously turned off.
  • unwatch() — turns file watching off.
  • close() — alias for unwatch().
  • on('ok', listener(result)) — fired every time the file is recompiled successfully.
  • on('fail', listener(error)) — fired on a compilation error.
  • on('watch', listener()) — fired when watching resumes for the given file.
  • on('unwatch', listener()) — fired when watching stops for the given file.

engine — the default global engine instance backing the global methods above.

Language syntax

myavka's syntax is fully compatible with plain CSS. There are only three additional directives — @import, @export, @apply — plus module blocks, written as %name { ... } (a placeholder-like construct).

Modules

Modules (placeholders) are named, reusable blocks that can be applied in different contexts. They can contain any CSS content, and can apply other modules themselves.

%x { color: red; }

Declares a module — or appends to an existing one in the same scope under that name.

%x div { color: red; }

Shorthand for:

%x {
  div { color: red; }
}
%x, %y div { color: red; }

Adds the same block to both modules at once.

Creating and using a module

%button {
  padding: 8px 16px;
  border-radius: 4px;
}

.submit {
  @apply %button;
  background: blue;
}

%button never shows up in the output on its own — its content only appears wherever it's applied, here as part of .submit.

Scope

Module names follow the same lexical scoping as everything else in myavka: a %name declared inside a block is only visible within that block, and shadows any module with the same name from an outer scope.

%x { ... }
%x div { }

div {
  %x { }
  %x div { }
  @apply %x;
}

@apply %x;
  • Inside the div { ... } block, %x { } declares a new, block-scoped %x — distinct from the one at the root. %x div { } right after it appends to this same, inner %x.
  • @apply %x; inside the div block resolves to this inner %x — the nearest one in scope.
  • @apply %x; after the div block closes is back at the root scope, where the inner %x no longer exists — it resolves to the original, root-level %x.

Nested module declarations

A module can declare other modules inside its own body:

%x {
  %y { }
  @apply %y;
}

%y here is scoped to %x's body — it isn't visible outside of it. Applying it from within the same body is fine, since it's already in scope by the time @apply %y; is reached.

Cyclic usage

A module applying itself through a nested rule is a genuine cycle, and a compile-time error:

%x {
  div {
    @apply %x;
  }
}

A module applying itself directly, at the same level, with no additional selector nesting, is allowed but pointless — there's nothing new for the self-application to add, so it's simply ignored:

%x {
  @apply %x;
}

This is not a cycle either, even though it looks similar — the inner %x is a different, shadowed module (see Scope above), not a self-reference:

%x {
  %x { }
  div {
    @apply %x;
  }
}

@import

@import <url>;

Left untouched — passed straight through to the output as-is.

@import * from 'url';

Imports every module from the given file into the current namespace.

@import %x, %y from 'url';

Imports the named modules into the current namespace.

@import %x as %y from 'url';

Imports the named module under a new name.

@import % as %x from 'url';

Imports the file's root (if it exports one) into the current namespace. Renaming is mandatory here — you can't import the root without giving it a name.

@import %a as %b, %c, % as %e, * from 'url';

All of the above can be combined. Note that when renaming (%a as %b), the original name (%a) is not also imported — not even when combined with *.

Imports can be nested, and follow ordinary lexical scoping:

div {
  @import %x from 'url';
  /* %x is visible here */
  @apply %x;
}

/* error — %x isn't visible outside the block it was imported in */
@apply %x;

@export

Declares which modules from the current file are available for import elsewhere.

@export can't be nested — it's only valid at the file's root scope.

If a file contains no @export rules at all, the file's own root is exported implicitly, and can be imported as @import % as %x from 'url';. This is what keeps plain CSS files compatible with myavka's import system without any changes.

@export *;

Exports every module declared in the root scope except the root itself.

@export %;

Exports only the file's root (the whole file). This is the default behavior when there are no @export rules.

@export % as %x;

Exports the root under a new name.

@export %x, %y;

Exports the named modules from the root scope.

@export %x as %y;

Exports the named modules under new names.

@export *, %, %x as %y;

All of the above can be combined. As with imports, when renaming (%x as %y), the original name (%x) is not also exported.

Re-exporting

@export ... from 'url';

Exports modules sourced from another file, using the same set of variants as a regular export:

@export * from 'url';
@export % from 'url';
@export % as %x from 'url';
@export %x, %y from 'url';
@export %x as %y from 'url';
@export *, %, %x as %y from 'url';

@apply

Applies one or more modules — effectively, appends the current selector to the module's content.

@apply %x, %y;

Applies the named modules to the current scope.

@apply *;

Applies every module currently visible in scope.

@apply %x, %y from 'url';

Applies modules taken from another file — effectively @import + @apply combined. Unlike a regular import, the names aren't added to the current namespace; the modules are applied directly and nothing else.

@apply * from 'url';

Applies every module in the given file's export list (except the root).

@apply % from 'url';

Applies the given file's root, if it exports one.

@apply *, %;

Combines the root and every visible module.

Philosophy

  1. File-level modularity and encapsulation. Each file only sees what's declared in that file, or explicitly imported into it.
  2. Scope-level modularity and encapsulation. Each scope — any block — only sees what's declared or imported above it, or directly within it.
  3. Nothing is global. Nothing passes transitively between files unless you explicitly import it.
  4. Consistency and predictability. Every construct follows the same shape as much as possible — the syntax stays uniform across the language rather than growing special cases.
  5. Simplicity and minimalism. A minimal, sufficient set of directives, each as flexible as possible. The smallest possible footprint on plain CSS — every integration happens at the rule level.
  6. Unobtrusive by default. Any rule the preprocessor doesn't recognize as its own is passed through to the output as-is, following standard CSS rules.
  7. Valid CSS in, correctly handled. Any valid CSS file is processed correctly by the preprocessor.
  8. The reverse holds too. In the common case, valid myavka output is also valid CSS.
  9. Compact output. The compiled result avoids duplication wherever possible.