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

@flarian/language-plugin-css-modules

v1.0.0

Published

Vue language tools plugin: types for external CSS modules straight from PostCSS, without .d.ts files

Readme

@flarian/language-plugin-css-modules

Types for external CSS modules in Vue single-file components, computed by the Vue language tools themselves. No .d.ts files on disk, no build step, no watcher.

<style module="styles" src="./styles.module.pcss" lang="pcss" />

With the plugin, styles.hint is checked against the real class names of styles.module.pcss — in the editor and in vue-tsc alike. A typo is an error, and go-to-definition jumps into the stylesheet.


Why

Volar reads class names only from the inline content of a <style module> block. For src="…" it emits typeof import('./styles.module.pcss').default and leaves the rest to TypeScript module resolution.

Vite declares module '*.module.pcss' with an index signature. So that import resolves to "any string key", and every typo passes silently.

The common workaround writes a .d.ts next to each stylesheet. This plugin instead turns every *.module.pcss into a virtual TypeScript module inside the language service.

Class names are taken after PostCSS runs, so classes born from transformations are typed too:

.hint-test {
	&-deep {
		color: blue;
	}
}
styles.hintTest; // ok
styles.hintTestDeep; // ok

Install

pnpm add -D @flarian/language-plugin-css-modules

postcss is a peer dependency and is loaded from your project.


Setup

tsconfig.json:

{
	"vueCompilerOptions": {
		"extensions": [".vue", ".pcss"],
		"strictCssModules": true,
		"resolveStyleImports": true,
		"plugins": [
			{
				"name": "@flarian/language-plugin-css-modules",
				"localsConvention": "camelCaseOnly",
			},
		],
	},
}

postcss.config.cjs, shared with Vite:

module.exports = {
	plugins: {
		'postcss-nested': {},
	},
};

Plugins are loaded by name from your project, so the config needs no require, and @typescript-eslint/no-require-imports has nothing to complain about. An array of plugin instances works as well.

Vite picks this file up on its own, and the plugin reads the same file. Types and the build use one list of PostCSS plugins, so they cannot drift apart.


Options

| Option | Default | Meaning | | ------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | localsConvention | names as they are | Same values as css.modules.localsConvention in Vite: camelCase, camelCaseOnly, dashes, dashesOnly. Must match the build, or types and runtime disagree. | | moduleSuffix | .module.pcss | Which stylesheets are CSS modules. | | extensions | [".pcss"] | Stylesheet extensions the plugin owns. Every entry must also be listed in vueCompilerOptions.extensions. | | postcssConfig | postcss.config.cjs | Config file name, searched upward from the stylesheet. |

Name conversion mirrors postcss-modules exactly, including lodash.camelcase for the camelCase modes.


Limitations

  • Only synchronous PostCSS plugins affect types, because the language service is synchronous. With an asynchronous plugin the stylesheet is parsed without plugins.
  • A function as localsConvention cannot be written in tsconfig.json.
  • :global(...) classes are not module keys, same as in postcss-modules.

How it works

The plugin registers two instances.

The first one runs before the built-in ones (order: -1) and claims every stylesheet extension. Without it the built-in .vue handler takes the file, parses CSS as an SFC, and the import types as an empty component.

The second one runs after the built-in code generation (order: 1) and replaces the generated script with a module that declares the class names. Each key is mapped back to its class in the stylesheet, so navigation lands on the right line.