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

@web-features/tech

v0.0.1

Published

Detect the general technology a web-feature belongs to

Readme

@web-features/tech

Detect the general technology a web-feature belongs to.

Install

npm install @web-features/tech

Usage

import tech from "@web-features/tech";

tech("grid"); // Set { "css" }
tech("array-at"); // Set { "javascript" }
tech("import-maps"); // Set { "html", "javascript" }
tech("ad-selection"); // Set {} (real feature, no tech detected)
tech("not-a-feature"); // null (no such feature)

tech(webFeatureId) takes a web-features ID and returns a Set of technology identifiers (spread it into an array with [...tech(id)] if you need one). The vocabulary is open: an identifier defaults to the web-features prefix or group name it came from — "css", "html", "javascript", plus area names like "webgl" or "webxr" — and rules only step in to fix names that are wrong or too coarse. Coarsen the output as you see fit with a remap map. It returns an empty Set for a real feature with no detected technology, and null only when the ID is unknown. For a shipped feature, any non-empty result includes at least one core tech (css, html, or javascript), with area identifiers like images alongside. Unshipped or retracted proposals often have no BCD data yet, so they may resolve to just a group area (installSet { "progressive-web-app" }) or an empty Set — the library doesn't fabricate a classification for them. A moved/split redirect resolves to the union of its target features' technologies.

Technologies come from each feature's BCD compat_features, a few manual overrides, and its group ancestry — each feature inherits the technology of its group's root (e.g. a feature under the css group → "css", one under scrolling"scrolling"), which is how features with no compat_features of their own (e.g. color-contrastSet { "css" }) get classified. Group roots often name an area rather than a core tech, though, so whenever the signals so far leave a feature without a core tech, its spec hostname supplies one: image-function sits in the images area but its drafts.csswg.org spec makes it Set { "css", "images" }, and text-fit — with no other signal at all — becomes Set { "css" }. Only the feature itself is consulted, never its siblings, so results stay specific. The default rule sets live in src/defaults.js.

Output vocabulary

With the current web-features data and default rules, tech() resolves to one or more of 53 technology identifiers. The vocabulary is open (see Usage), so this list tracks the data — new features or groups can extend it.

Three core identifiers anchor the vocabulary — every shipped feature that resolves to anything includes at least one of them:

  • css, html, javascript

The rest name a specific technology area. Most are inherited from a feature's web-features group root (a feature under the scrolling group → "scrolling"); a handful — http, manifests, mathml, mediatypes, svg — come instead (or additionally) from a BCD prefix:

animation, canvas, clipboard, clipping-shapes-masking, compute-pressure, cookies, credential-management, dom, fetch, file-system, gamepad, geolocation, http, images, integrity, manifests, mathml, mediatypes, messaging, parsing-and-serialization, payments, performance, print, progressive-web-app, reading-order, reporting, resource-hints, ruby, scrolling, security, selection, sensors, speech, storage, streams, svg, text-fragments, view-transitions, web-audio, web-components, webassembly, webdriver, webgl, webgpu, webmcp, webrtc, webxr, workers, worklets, xml

webassembly and webdriver always appear alongside javascript, since both are surfaced to the web through it; a consumer who'd rather see a single coarse tech can fold them back with remap. The spec-hostname fallback only ever draws from techs already in this list (core techs, plus svg), so it adds nothing new.

Custom rules

Both tech() and inferFromBCD() accept custom rule sets, defaulting to the bundled ones — exported as defaults so you can build on them. Each option replaces its default wholesale (not merged), so spread the matching defaults.* in to extend rather than start from scratch — otherwise you'd drop rules like apijavascript that classify most JS features:

import tech, {
	inferFromBCD, // (compatFeatures, { add, replace }?) => Set<Tech>
	inferFromSpec, // (specs, { specHosts }?) => Set<Tech>
	defaults, // the bundled rule sets: { add, replace, remap, overrides, specHosts }
} from "@web-features/tech";

// tech(webFeatureId, { add?, replace?, remap?, overrides?, specHosts? })
tech("some-feature", { overrides: { ...defaults.overrides, "some-feature": ["javascript"] } });

// Collapse the granular webassembly/webdriver techs into javascript:
tech("wasm-garbage-collection", {
	remap: { webassembly: "javascript", webdriver: "javascript" },
});

// By default webgl.* → ["webgl"]; add a replace rule to reclassify it — spread
// `...defaults.replace` so the bundled rules (e.g. `api` → javascript) still apply:
inferFromBCD(["webgl.foo"], { replace: { ...defaults.replace, webgl: "javascript" } }); // Set { "javascript" }

// defaults.specHosts is a plain object keyed by hostname — extend it in place, or pass your own.
defaults.specHosts["tabatkins.github.io"] = ["css"];
inferFromSpec("https://tabatkins.github.io/specs/css-toggle-states/"); // Set { "css" }

Custom add/replace passed to tech() govern both BCD inference and group-ancestry resolution — a group inherits its root's technology through the same rules (the css group → "css"). remap then rewrites the final output (e.g. webassembly"javascript"), so you can coarsen granularity without touching inference. The specHosts map only applies as a fallback, when nothing else classifies a feature — see below.

An overrides entry is terminal: when a feature's exact ID is in the map, that value wins outright and BCD/group/spec inference is skipped entirely (remap still applies). This holds for moved/split redirect IDs too — an override on the alias replaces, rather than merges with, its target's techs.

Rule format

Two rule sets shape BCD inference, both keyed by prefix (the segment before the first .):

  • replace sets a prefix's base technology — only for prefixes whose own name isn't a tech ({ api: "javascript" }). A prefix with no replace rule is its own technology: css.*"css" needs no rule.
  • add layers extra techs on top of that base, for matching suffixes. Most refinements are additive: { html: { "elements.script.type.": "javascript" } } keeps the html base and adds javascript.

Both share the same rule grammar for the part of the key after the prefix. A rule is one of:

  • "css" — a tech, assigned wherever the rule is reached
  • ["css", "javascript"] — a list of rules
  • { test: /…/, tech } — a regex tested against the remaining suffix
  • { CSS: …, "*": … } — a nested map: each key is a literal prefix matched with startsWith and extended as you nest deeper; "*" (the wildcard) matches at the current level

So replace: { api: "javascript" } with add: { api: { CSS: "css" } } makes every api.* key javascript (since api is not itself a tech), and any api.CSS… key (e.g. CSSStyleSheet) additionally css.

The "*" prefix is itself a wildcard: rules under it apply to every prefix, matched against the suffix. That's how markup segments are tagged in one place — add: { "*": { "elements.": "html", "global_attributes.": "html" } } makes html.elements.*, svg.elements.*, and mathml.elements.* all html, so each vocabulary only needs to name what's specific to it (e.g. svg/mathml presentation attributes → css).

Coarsening the output with remap

remap is not an inference rule like add/replace: it's a flat Tech → Tech map applied once to the final output, letting consumers fold fine-grained techs into coarser ones without touching how features are classified. Each returned tech is swapped for its mapping (if any); since the output is a Set, techs that collapse onto the same target automatically dedupe. Mapping a tech to a falsy value (e.g. null) removes it from the output entirely.

It's empty by default — the bundled rules keep every tech distinct, so webassembly, webdriver, svg, and the rest each come through on their own. Supply a map to collapse the granularity you don't need:

tech("wasm"); // Set { "webassembly", "javascript" }
tech("wasm", { remap: { webassembly: "javascript" } }); // Set { "javascript" }

Only the keys you name are affected; every other tech passes through unchanged. Targets are just identifiers, so you can also route several techs into a coarse bucket of your own — { webgl: "graphics", webgpu: "graphics", canvas: "graphics" }.

Notes

Unshipped and unclassifiable features

A shipped feature always resolves to at least one core tech (see Usage). Unshipped or retracted proposals often have no BCD yet, so they fall back to whatever their group and spec provide — sometimes a bare area (installSet { "progressive-web-app" }), sometimes nothing.

Features with no compat_features, no override, no group, and no recognized spec host carry no signal at all and return an empty Set. With the current web-features data these are:

  • ad-selection
  • portal
  • usermedia

Shipped features that inference misses can be classified by adding an overrides entry; unshipped proposals are intentionally left alone.