Skip to content

Latest commit

 

History

History
252 lines (175 loc) · 5.42 KB

File metadata and controls

252 lines (175 loc) · 5.42 KB

This project uses GitHub Actions for continuous integration.

type-css-modules

Generate declaration files for CSS modules

What is it?

The package provides accurate types for CSS module files. It's designed with Ember projects in mind, but can be used with any JavaScript framework and build tool.

Installation

pnpm add -D type-css-modules

Ensure that CSS declaration files exist before types are checked. For example, you can write a pre-script.

/* package.json */
{
  "scripts": {
    "prelint:types": "type-css-modules <arguments>",
    "lint:types": "ember-tsc --noEmit" // or "glint"
  },
  "devDependencies": {
    "type-css-modules": "...",
    "typescript": "..."
  }
}

Arguments

You must pass --src to indicate the location of your CSS module files.

type-css-modules --src app

You can pass multiple values or use glob patterns to specify multiple locations.

type-css-modules --src app/components app/templates
type-css-modules --src "app/{components,controllers,templates}"
Optional: Specify the project root

Pass --root to run the codemod somewhere else (i.e. not in the current directory).

type-css-modules --root <path/to/your/project>

Use Prettier?

You can run into a couple of issues when prettier and type-css-modules run side-by-side.

type-css-modules uses quotation marks in declaration files so that we can always use class selector names as object keys. On the other hand, prettier removes quotation marks when it deems unnecessary. To separate formatting concerns, set quoteProps: 'preserve' for *.css.d.ts files:

/* prettier.config.mjs */
export default {
  overrides: [
    {
      files: '*.css.d.ts',
      options: {
        quoteProps: 'preserve',
      },
    },
  ],
};

Furthermore, if you run prettier as a standalone tool (i.e. not as a linter plugin), you can run into a concurrency issue because type-css-modules deletes declaration files before creating new ones. Make the two independent by adding *.css.d.ts to .prettierignore.

Can I use the file extension *.module.css?

Yes! You may use *.module.css to indicate the stylesheets that are for CSS modules. type-css-modules will create declaration files with the extension *.module.css.d.ts.

The Prettier configuration (shown above) can remain as is.

Limitations

To reduce complexity, type-css-modules expects you to follow the conventions of embroider-css-modules:

  • Give the local scope to the styles that you own1
  • Use the default import to import styles

Here are some examples that meet the syntax requirements.

Ember: Glimmer component
/* app/components/ui/page.css */
.container {
  display: grid;
  grid-template-areas:
    "header"
    "body";
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  height: calc(100% - 3em);
  overflow-y: auto;
  padding: 1.5rem 1rem;
  scrollbar-gutter: stable;
}

.header {
  grid-area: header;
}

.body {
  grid-area: body;
}
{{! app/components/ui/page.hbs }}
<div class={{local this.styles "container"}}>
  <h1 class={{this.styles.header}}>
    {{@title}}
  </h1>

  <div class="{{this.styles.body}}">
    {{yield}}
  </div>
</div>
/* app/components/ui/page.ts */
import Component from '@glimmer/component';

import styles from './page.css';

export default class UiPageComponent extends Component {
  styles = styles;
}
Ember: <template> tag
/* app/components/ui/page.gts */
import { local } from 'embroider-css-modules';

import styles from './page.css';

<template>
  <div class={{local styles "container"}}>
    <h1 class={{styles.header}}>
      {{@title}}
    </h1>

    <div class="{{styles.body}}">
      {{yield}}
    </div>
  </div>
</template>

And some counterexamples (what not to do):

Don't use the :local() pseudo-class selector
/* app/components/ui/page.css */
:local(.container) {
  display: grid;
  grid-template-areas:
    "header"
    "body";
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  height: calc(100% - 3em);
  overflow-y: auto;
  padding: 1.5rem 1rem;
  scrollbar-gutter: stable;
}

:local(.header) {
  grid-area: header;
}

:local(.body) {
  grid-area: body;
}
Don't use named imports to import styles
/* app/components/ui/page.gts */
import { container, header, body } from './page.css';

<template>
  <div class={{container}}>
    <h1 class={{header}}>
      {{@title}}
    </h1>

    <div class="{{body}}">
      {{yield}}
    </div>
  </div>
</template>

1. With webpack, for example, you can configure mode to be a function that returns 'local' or 'global'. In stylesheets, you can use the :global() pseudo-class selector to refer to "things from outside."

Compatibility

  • Node.js v22 or above

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.