Skip to content

A Vite plugin that enables the use of TC39 Import Attributes proposal for CSS files. Import CSS files with with { type: 'css' } syntax and get them as CSSStyleSheet objects, perfect for Web Components and Shadow DOM.

Notifications You must be signed in to change notification settings

arcmantle/vite-plugin-import-css-sheet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@arcmantle/vite-plugin-import-css-sheet

A Vite plugin that enables the use of TC39 Import Attributes proposal for CSS files. Import CSS files with with { type: 'css' } syntax and get them as CSSStyleSheet objects, perfect for Web Components and Shadow DOM.

Features

  • 🚀 Import CSS as CSSStyleSheet: Transform CSS imports into constructable stylesheets
  • 🎯 TC39 Standard Syntax: Uses the official with { type: 'css' } syntax
  • Vite Integration: Seamless integration with Vite's build process
  • 🤖 Auto-Import: Automatically inject CSS imports for co-located stylesheets
  • 🗜️ CSS Minification: Built-in CSS minification using Lightning CSS
  • 🔧 Customizable: Support for custom transformers and additional code injection
  • 📦 TypeScript Support: Full TypeScript definitions included
  • 🔍 Development Friendly: Watch mode support for CSS file changes

Installation

npm install @arcmantle/vite-plugin-import-css-sheet
# or
pnpm add @arcmantle/vite-plugin-import-css-sheet
# or
yarn add @arcmantle/vite-plugin-import-css-sheet

Usage

Basic Setup

Add the plugin to your vite.config.ts:

import { defineConfig } from 'vite';
import { importCSSSheet } from '@arcmantle/vite-plugin-import-css-sheet';

export default defineConfig({
  plugins: [
    importCSSSheet()
  ]
});

Import CSS as CSSStyleSheet

Use the TC39 import attributes syntax to import CSS files as CSSStyleSheet objects:

// Import CSS as CSSStyleSheet
import styles from './component.css' with { type: 'css' };

// Use with Web Components
class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });

    // Apply the stylesheet to shadow DOM
    shadow.adoptedStyleSheets = [styles];

    shadow.innerHTML = `
      <div class="container">
        <h1>Hello World</h1>
      </div>
    `;
  }
}

customElements.define('my-component', MyComponent);

Lit Integration

Perfect for Lit components:

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import componentStyles from './component.css' with { type: 'css' };

@customElement('my-lit-component')
export class MyLitComponent extends LitElement {
  static styles = [componentStyles];

  render() {
    return html`
      <div class="container">
        <p>Styled with imported CSS sheet!</p>
      </div>
    `;
  }
}

TypeScript Support

Include the provided type definitions in your project:

// In your vite-env.d.ts or types file
/// <reference types="@arcmantle/vite-plugin-import-css-sheet/client" />

This provides proper TypeScript support for CSS imports:

// TypeScript will know this is a CSSStyleSheet
import styles from './styles.css' with { type: 'css' };

Configuration

The plugin accepts several configuration options:

import { importCSSSheet } from '@arcmantle/vite-plugin-import-css-sheet';

export default defineConfig({
  plugins: [
    importCSSSheet({
      // Custom CSS transformers
      transformers: [
        (code, id) => {
          // Custom transformation logic
          return code.replace(/\$primary-color/g, '#007bff');
        }
      ],

      // Additional code to inject
      additionalCode: [
        'console.log("CSS sheet loaded:", sheet);'
      ],

      // Disable minification (enabled by default)
      minify: false
    })
  ]
});

Configuration Options

Option Type Default Description
transformers ((code: string, id: string) => string)[] [] Array of functions to transform CSS before converting to stylesheet
additionalCode string[] [] Additional JavaScript code to inject into the generated module
minify boolean true Whether to minify CSS using Lightning CSS
autoImport object undefined Configuration for automatic CSS import injection

Auto-Import Feature

The auto-import feature automatically detects when a .css file exists alongside your component file and automatically injects the import statement and adds it to your component's static styles property. This is perfect for component-based frameworks like Lit.

How It Works

When you have co-located CSS files:

src/
  ├── my-component.ts
  └── my-component.css

The plugin can automatically transform your component to include the CSS import, eliminating boilerplate.

Auto-Import Configuration

Enable auto-import by providing the autoImport configuration:

import { importCSSSheet } from '@arcmantle/vite-plugin-import-css-sheet';

export default defineConfig({
  plugins: [
    importCSSSheet({
      autoImport: {
        identifier: [
          {
            className: 'LitElement',
            styleName: 'styles',
            position: 'prepend' // or 'append'
          }
        ]
      }
    })
  ]
});

Auto-Import Options

Property Type Required Description
className string Yes The base class name to detect (e.g., 'LitElement', 'HTMLElement')
styleName string Yes The static property name to inject the stylesheet into (e.g., 'styles')
position 'prepend' | 'append' No Where to add the stylesheet in the array. Default: 'prepend'

Example: Without Auto-Import

// my-component.ts
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import componentStyles from './my-component.css' with { type: 'css' };

@customElement('my-component')
export class MyComponent extends LitElement {
  static styles = [componentStyles]; // Manually added

  render() {
    return html`<div>Hello World</div>`;
  }
}

Example: With Auto-Import

With auto-import enabled, you can omit the import and static property:

// my-component.ts
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
// Import is automatically injected!

@customElement('my-component')
export class MyComponent extends LitElement {
  // static styles is automatically created/updated!

  render() {
    return html`<div>Hello World</div>`;
  }
}

The plugin automatically transforms this to:

import my_component_styles from './my-component.css' with { type: 'css' };
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('my-component')
export class MyComponent extends LitElement {
  static styles = [my_component_styles]; // Auto-injected!

  render() {
    return html`<div>Hello World</div>`;
  }
}

Auto-Import with Existing Styles

If you already have a static styles property, the auto-import will add to it:

// Before transformation
import { LitElement } from 'lit';
import sharedStyles from './shared.css' with { type: 'css' };

export class MyComponent extends LitElement {
  static styles = [sharedStyles];
}

// After transformation (with position: 'prepend')
import my_component_styles from './my-component.css' with { type: 'css' };
import { LitElement } from 'lit';
import sharedStyles from './shared.css' with { type: 'css' };

export class MyComponent extends LitElement {
  static styles = [my_component_styles, sharedStyles]; // Prepended
}

// After transformation (with position: 'append')
export class MyComponent extends LitElement {
  static styles = [sharedStyles, my_component_styles]; // Appended
}

Multiple Class Support

You can configure auto-import for multiple base classes:

importCSSSheet({
  autoImport: {
    identifier: [
      {
        className: 'LitElement',
        styleName: 'styles',
        position: 'prepend'
      },
      {
        className: 'CustomBaseElement',
        styleName: 'styleSheets',
        position: 'append'
      },
      {
        className: 'MyFrameworkComponent',
        styleName: 'componentStyles'
      }
    ]
  }
})

When Auto-Import Triggers

The auto-import feature only activates when:

  1. A .css file exists with the same name as your component file
  2. Your class extends one of the configured base classes
  3. The file is a supported type (.ts, .tsx, .js, .jsx, .mts, .mjs)

Position: Prepend vs Append

The position option controls CSS cascade order:

  • prepend (default): Component styles come first, can be overridden by later styles
  • append: Component styles come last, override earlier styles
// position: 'prepend'
static styles = [componentStyles, baseStyles, themeStyles];
//                ^^^^^^^^^^^^^^ auto-imported (lowest specificity)

// position: 'append'
static styles = [baseStyles, themeStyles, componentStyles];
//                                        ^^^^^^^^^^^^^^ auto-imported (highest specificity)

Benefits of Auto-Import

Less Boilerplate: No need to manually import and assign stylesheets ✅ Co-location: Encourages keeping styles next to components ✅ Consistency: Automatic naming conventions ✅ Flexibility: Works with existing manual imports ✅ Type-Safe: Full TypeScript support with source maps

Plugin Architecture

  1. Detection: The plugin scans your source files for CSS imports using the with { type: 'css' } syntax
  2. Virtual Modules: Creates virtual modules for CSS files that need to be converted
  3. Transformation: Processes CSS through any custom transformers and minification
  4. Code Generation: Generates JavaScript code that creates a CSSStyleSheet object
  5. Export: Exports the stylesheet as the default export

Browser Support

This plugin generates code that uses the Constructable Stylesheets API:

  • Chrome/Edge 73+
  • Firefox 101+
  • Safari 16.4+

For older browsers, consider using a polyfill.

Why Use This Plugin?

Traditional CSS Imports

// Traditional Vite CSS import (injects into document)
import './styles.css';

With This Plugin

// Get CSS as CSSStyleSheet object
import styles from './styles.css' with { type: 'css' };

// Perfect for Shadow DOM
shadowRoot.adoptedStyleSheets = [styles];

Benefits

  • Encapsulation: Styles don't leak into global scope
  • Performance: No style injection/removal overhead
  • Standards Compliant: Uses official TC39 syntax
  • Shadow DOM Ready: Perfect for Web Components
  • Tree Shakable: Only load styles when needed

Examples

Multiple Stylesheets

import baseStyles from './base.css' with { type: 'css' };
import themeStyles from './theme.css' with { type: 'css' };
import componentStyles from './component.css' with { type: 'css' };

// Combine multiple stylesheets
element.shadowRoot.adoptedStyleSheets = [
  baseStyles,
  themeStyles,
  componentStyles
];

Dynamic Style Loading

// Conditional style loading
const isDark = document.body.classList.contains('dark-theme');
const themeStyles = isDark
  ? await import('./dark-theme.css' with { type: 'css' })
  : await import('./light-theme.css' with { type: 'css' });

shadowRoot.adoptedStyleSheets = [baseStyles, themeStyles.default];

Development

# Install dependencies
pnpm install

# Run demo in development mode
pnpm dev

# Build the plugin
pnpm build

Requirements

  • Node.js >= 22
  • Vite >= 7.0.0

Dependencies

  • lightningcss: Fast CSS transformer and minifier for CSS processing

License

Apache-2.0

Contributing

This package is part of the Arcmantle Weave monorepo. Contributions are welcome! .......

About

A Vite plugin that enables the use of TC39 Import Attributes proposal for CSS files. Import CSS files with with { type: 'css' } syntax and get them as CSSStyleSheet objects, perfect for Web Components and Shadow DOM.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published