or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-postcss-modules-extract-imports

A CSS Modules transform to extract local aliases for inline imports

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/postcss-modules-extract-imports@3.1.x

To install, run

npx @tessl/cli install tessl/npm-postcss-modules-extract-imports@3.1.0

index.mddocs/

PostCSS Modules Extract Imports

PostCSS Modules Extract Imports is a CSS Modules PostCSS plugin that transforms composes declarations by extracting import statements and converting them into :import pseudo-selectors. It processes CSS Modules compose declarations that reference external CSS files and transforms them into standardized import statements with temporary class name aliases, enabling CSS Modules bundlers to properly handle cross-file composition dependencies.

Package Information

  • Package Name: postcss-modules-extract-imports
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install postcss-modules-extract-imports

Core Imports

const extractImports = require("postcss-modules-extract-imports");

For PostCSS integration:

const postcss = require("postcss");
const extractImports = require("postcss-modules-extract-imports");

For standalone topological sort utility:

const topologicalSort = require("postcss-modules-extract-imports/src/topologicalSort");

Basic Usage

const postcss = require("postcss");
const extractImports = require("postcss-modules-extract-imports");

// Basic plugin usage
const processor = postcss([extractImports()]);

// Process CSS with compose declarations
const input = `
:local(.continueButton) {
  composes: button from "library/button.css";
  color: green;
}
`;

const result = processor.process(input);
console.log(result.css);
// Output:
// :import("library/button.css") {
//   button: i__imported_button_0;
// }
// :local(.continueButton) {
//   composes: i__imported_button_0;
//   color: green;
// }

Complete Processing Example:

const postcss = require("postcss");
const extractImports = require("postcss-modules-extract-imports");

// Configure plugin with custom options
const processor = postcss([
  extractImports({
    createImportedName: (name, path) => `imported_${name}_${Math.random().toString(36).substr(2, 5)}`,
    failOnWrongOrder: false  // Allow flexible import ordering
  })
]);

// Complex CSS with multiple imports and compositions
const complexInput = `
.header {
  composes: container from "./layout.css";
  composes: primary-theme from "./themes.css";
}

.button {
  composes: btn base-button from "./components.css";
  composes: shadow from "./effects.css";
}

.navigation {
  composes: flex-row from "./layout.css";
  composes: navbar from global;
}
`;

const result = processor.process(complexInput);
console.log(result.css);

Capabilities

Plugin Factory Function

Creates a PostCSS plugin instance that transforms CSS Modules compose declarations.

/**
 * Creates a PostCSS plugin that extracts and transforms CSS Modules compose declarations
 * @param options - Configuration options for the plugin
 * @returns PostCSS plugin object
 */
function extractImports(options = {}): PostCSSPlugin;

Usage Example:

const plugin = extractImports({
  failOnWrongOrder: true,
  createImportedName: (importName, path) => `custom_${importName}_from_${path.replace(/[^a-zA-Z0-9]/g, '_')}`
});

const processor = postcss([plugin]);

Configuration Options

The plugin accepts an options object with the following properties:

interface ExtractImportsOptions {
  /** Custom function to generate imported symbol names */
  createImportedName?: (importName: string, path: string) => string;
  /** When true, throws exception for unpredictable import order dependencies */
  failOnWrongOrder?: boolean;
}

Custom Import Name Generation

Override the default import name generation strategy.

/**
 * Custom function to generate imported symbol names
 * @param importName - The original class name being imported
 * @param path - The file path being imported from
 * @returns Custom generated name for the imported symbol
 */
type CreateImportedNameFunction = (importName: string, path: string) => string;

Default behavior: Generates names like i__imported_button_0, i__imported_card_1, etc. Non-word characters in import names are replaced with underscores.

Usage Example:

const plugin = extractImports({
  createImportedName: (importName, path) => {
    const cleanPath = path.replace(/[^a-zA-Z0-9]/g, '_');
    return `${importName}_from_${cleanPath}`;
  }
});

// Input: composes: button from "ui/button.css"
// Output: button_from_ui_button_css instead of i__imported_button_0

Advanced Usage Examples:

// Multiple classes from same file
const input = `
.navigation {
  composes: button primary large from "./ui/buttons.css";
  margin: 10px;
}
`;

// Global composition
const globalInput = `
.localButton {
  composes: btn-primary btn-large from global;
  color: blue;
}
`;

// Mixed local and external composition
const mixedInput = `
.complexButton {
  composes: button from "./base.css", primary-style;
  composes: hover-effect from "./animations.css";
}
`;

Import Order Validation

Enable strict validation of import dependency order.

/**
 * When true, throws exception for unpredictable import order dependencies
 * @default undefined (falsy)
 */
failOnWrongOrder?: boolean;

Usage Example:

// This will throw an error due to inconsistent import order
const plugin = extractImports({ failOnWrongOrder: true });

const problematicCSS = `
.aa {
  composes: b from "./b.css";
  composes: c from "./c.css";
}

.bb {
  /* c.css should come before b.css based on .aa rule */
  composes: c from "./c.css";
  composes: b from "./b.css";
}
`;

// Throws: "Failed to resolve order of composed modules `./b.css`, `./c.css`"

PostCSS Plugin Object

The plugin factory function returns a standard PostCSS plugin object.

interface PostCSSPlugin {
  /** Plugin identifier for PostCSS */
  postcssPlugin: "postcss-modules-extract-imports";
  /** Plugin preparation function */
  prepare(): PluginMethods;
}

interface PluginMethods {
  /** Main processing function called once per CSS root */
  Once(root: PostCSSRoot, postcss: PostCSSAPI): void;
}

Transformation Behavior

The plugin processes CSS according to these rules:

Supported Compose Syntax

/* Single class from external file */
composes: button from "library/button.css";

/* Multiple classes from external file */
composes: button primary from "library/button.css";

/* Mixed composition (external and local) */
composes: button from "library/button.css", local-class;

/* Global composition */
composes: button from global;

/* Multiple files in one rule */
composes: button from "ui/button.css", card from "ui/card.css";

Output Format

Input:

:local(.continueButton) {
  composes: button primary from "library/button.css";
  color: green;
}

Output:

:import("library/button.css") {
  button: i__imported_button_0;
  primary: i__imported_primary_1;
}
:local(.continueButton) {
  composes: i__imported_button_0 i__imported_primary_1;
  color: green;
}

Global Compose Handling

/* Input */
.button {
  composes: btn-primary from global;
}

/* Output */
.button {
  composes: global(btn-primary);
}

Error Handling

The plugin throws PostCSS CssSyntaxError instances for import order conflicts when failOnWrongOrder is enabled.

interface ImportOrderError extends CssSyntaxError {
  /** Plugin identifier */
  plugin: "postcss-modules-extract-imports";
  /** The CSS property that caused the error */
  word: "composes";
  /** Error message format */
  message: string; // "Failed to resolve order of composed modules `path1`, `path2`."
}

Error Examples:

const postcss = require("postcss");
const extractImports = require("postcss-modules-extract-imports");

try {
  const processor = postcss([extractImports({ failOnWrongOrder: true })]);
  
  // This CSS creates conflicting import order
  const result = processor.process(`
    .classA {
      composes: btn from "./buttons.css";
      composes: card from "./cards.css";
    }
    
    .classB {
      composes: card from "./cards.css";
      composes: btn from "./buttons.css";  /* Different order - creates conflict */
    }
  `);
  
} catch (error) {
  console.log(error.plugin); // "postcss-modules-extract-imports"
  console.log(error.word);   // "composes"
  console.log(error.message); // "Failed to resolve order of composed modules `./buttons.css`, `./cards.css`."
}

Topological Sort Utility

The package includes a standalone topological sort utility that can be used independently for dependency graph resolution.

/**
 * Performs topological sort on a dependency graph
 * @param graph - Object where keys are nodes and values are arrays of dependencies
 * @param strict - When true, throws error for circular dependencies; when false, resolves best possible order
 * @returns Array of nodes in topological order, or Error object if circular dependency detected in strict mode
 */
function topologicalSort(graph: DependencyGraph, strict?: boolean): string[] | TopologicalSortError;

interface DependencyGraph {
  [node: string]: string[];
}

interface TopologicalSortError extends Error {
  /** Error message */
  message: "Nondeterministic import's order";
  /** Array of conflicting nodes that create circular dependency */
  nodes: [string, string];
}

Usage Example:

const topologicalSort = require("postcss-modules-extract-imports/src/topologicalSort");

// Define dependency graph
const graph = {
  "file-a.css": ["file-b.css", "file-c.css"],  // file-a depends on file-b and file-c
  "file-b.css": [],                              // file-b has no dependencies
  "file-c.css": ["file-b.css"],                // file-c depends on file-b
};

// Get topological order
const order = topologicalSort(graph, true);
console.log(order); // ["file-b.css", "file-c.css", "file-a.css"]

// Example with circular dependency
const cyclicGraph = {
  "file-a.css": ["file-b.css"],
  "file-b.css": ["file-a.css"],  // Creates cycle
};

const result = topologicalSort(cyclicGraph, true);
if (result instanceof Error) {
  console.log(result.message); // "Nondeterministic import's order"
  console.log(result.nodes);   // ["file-a.css", "file-b.css"]
}

PostCSS Plugin Identification

The plugin exports a PostCSS identification marker:

/**
 * PostCSS plugin identification marker
 * @type {boolean}
 */
extractImports.postcss = true;

This marker allows PostCSS to identify the function as a valid PostCSS plugin.

Types

/**
 * PostCSS plugin configuration options
 */
interface ExtractImportsOptions {
  createImportedName?: (importName: string, path: string) => string;
  failOnWrongOrder?: boolean;
}

/**
 * PostCSS plugin factory function type
 */
type ExtractImportsFactory = (options?: ExtractImportsOptions) => PostCSSPlugin;

/**
 * Topological sort function type
 */
type TopologicalSortFunction = (graph: DependencyGraph, strict?: boolean) => string[] | TopologicalSortError;

/**
 * Standard PostCSS plugin interface
 */
interface PostCSSPlugin {
  postcssPlugin: string;
  prepare(): {
    Once(root: PostCSSRoot, postcss: PostCSSAPI): void;
  };
}

/**
 * PostCSS AST Root node
 */
interface PostCSSRoot {
  /** Walk through all rules in the CSS */
  walkRules(callback: (rule: PostCSSRule) => void): void;
  /** Walk through all declarations in the CSS */
  walkDecls(pattern: RegExp | string, callback: (decl: PostCSSDeclaration) => void): void;
  /** Prepend a node to the beginning */
  prepend(node: PostCSSNode): void;
  /** Insert a node after another node */
  insertAfter(target: PostCSSNode, newNode: PostCSSNode): void;
  /** Get the index of a child node */
  index(child: PostCSSNode): number;
}

/**
 * PostCSS API object passed to plugin methods
 */
interface PostCSSAPI {
  /** Create a new CSS rule */
  rule(props: { selector: string; raws?: { after?: string } }): PostCSSRule;
  /** Create a new CSS declaration */
  decl(props: { prop: string; value: string; raws?: { before?: string } }): PostCSSDeclaration;
}

/**
 * PostCSS Rule node
 */
interface PostCSSRule {
  /** CSS selector */
  selector: string;
  /** Parent node */
  parent: PostCSSNode;
  /** Append a declaration to this rule */
  append(decl: PostCSSDeclaration): void;
}

/**
 * PostCSS Declaration node
 */
interface PostCSSDeclaration {
  /** CSS property name */
  prop: string;
  /** CSS property value */
  value: string;
  /** Parent rule */
  parent: PostCSSRule;
  /** Create an error associated with this declaration */
  error(message: string, options?: { plugin?: string; word?: string }): Error;
}

/**
 * Base PostCSS node
 */
interface PostCSSNode {
  /** Node type */
  type: string;
  /** Parent node */
  parent?: PostCSSNode;
}

/**
 * Topological sort utility types
 */
interface DependencyGraph {
  [node: string]: string[];
}

interface TopologicalSortError extends Error {
  message: "Nondeterministic import's order";
  nodes: [string, string];
}