├── docs ├── examples │ ├── cli │ │ ├── .gitignore │ │ ├── src │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── package.json │ │ └── README.md │ ├── cli-typescript │ │ ├── .gitignore │ │ ├── src │ │ │ ├── index.css │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── README.md │ │ └── package.json │ ├── react-ssr-webpack-typescript │ │ ├── src │ │ │ ├── app │ │ │ │ ├── components │ │ │ │ │ ├── a.component.css │ │ │ │ │ ├── b.component.css │ │ │ │ │ ├── a.component.tsx │ │ │ │ │ └── b.component.tsx │ │ │ │ └── app.component.tsx │ │ │ ├── index-client.tsx │ │ │ ├── global.d.ts │ │ │ └── index-server.tsx │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ ├── package.json │ │ ├── test │ │ │ └── index.ts │ │ └── README.md │ ├── lit-element-webpack-typescript │ │ ├── src │ │ │ ├── index.css │ │ │ ├── global.d.ts │ │ │ └── index.ts │ │ ├── index.html │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ ├── package.json │ │ ├── test │ │ │ └── index.ts │ │ └── README.md │ ├── sidebar.md │ └── README.md ├── CHANGELOG.md ├── css-es-modules │ ├── CHANGELOG.md │ ├── sidebar.md │ └── README.md ├── postcss-node │ ├── sidebar.md │ └── README.md ├── api │ ├── sidebar.md │ ├── index.md │ ├── postcss-node-loader.getformat.md │ ├── postcss-node-loader.transformsource.md │ ├── postcss-node.md │ ├── css-es-modules.css_global_key.md │ ├── css-es-modules.css_locals_key.md │ ├── postcss-node-loader.md │ ├── css-es-modules.stylescollector.raw.md │ ├── postcss-es-modules.options.modules.md │ ├── css-es-modules.defaultstylesinjectoptions.md │ ├── css-es-modules.stylescollector.ids.md │ ├── postcss-es-modules.options.inject.md │ ├── postcss-es-modules.postcssesmodules.md │ ├── css-es-modules.stylesinjectoptions.usenounce.md │ ├── css-es-modules.stylescollector.html.md │ ├── README.md │ ├── css-es-modules.collectstyles.md │ ├── css-es-modules.stylesinjectoptions.usenodeglobal.md │ ├── postcss-es-modules.modulesoptions.hashprefix.md │ ├── postcss-es-modules.modulesoptions.exportglobals.md │ ├── postcss-es-modules.modulesoptions.attachoriginalclassname.md │ ├── postcss-es-modules.modulesoptions.scopebehaviour.md │ ├── postcss-es-modules.extendedstylesinjectoptions.scripttype.md │ ├── postcss-es-modules.modulesoptions.globalmodulepaths.md │ ├── postcss-es-modules.extendedstylesinjectoptions.moduletype.md │ ├── postcss-es-modules.modulesoptions.localsconvention.md │ ├── postcss-es-modules.extendedstylesinjectoptions.scriptejectpath.md │ ├── postcss-es-modules.extendedstylesinjectoptions.custom.md │ ├── css-es-modules.stylesinjectoptions.useconstructablestylesheet.md │ ├── css-es-modules.stylesinjectoptions.usestyletag.md │ ├── postcss-es-modules.modulesoptions.generatescopedname.md │ ├── postcss-es-modules.md │ ├── css-es-modules.stylescollector.md │ ├── postcss-node.register.md │ ├── postcss-es-modules.options.md │ ├── postcss-es-modules.extendedstylesinjectoptions.script.md │ ├── postcss-es-modules.extendedstylesinjectoptions.injectmode.md │ ├── css-es-modules.injectstyles.md │ ├── css-es-modules.md │ ├── css-es-modules.stylesinjectoptions.md │ ├── postcss-es-modules.modulesoptions.md │ └── postcss-es-modules.extendedstylesinjectoptions.md ├── sidebar.md ├── sidebar_examples_internal.md └── index.html ├── packages ├── postcss-node │ ├── test │ │ ├── test3.css │ │ ├── test1.css │ │ ├── test4.bcss │ │ ├── test4.ccss │ │ ├── test2.css │ │ ├── test5.css │ │ ├── test6.css │ │ └── index.spec.ts │ ├── register.js │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── options.ts │ │ │ ├── worker-init.ts │ │ │ ├── types.ts │ │ │ ├── worker.ts │ │ │ ├── register.ts │ │ │ └── worker-sync-function.ts │ ├── index.js │ ├── postcss.config.js │ ├── api-extractor.json │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── postcss-node-loader │ ├── test │ │ ├── test1.css │ │ ├── test.js │ │ └── .postcssrc.json │ ├── index.js │ ├── api-extractor.json │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ └── package.json ├── css-es-modules │ ├── src │ │ ├── index.ts │ │ ├── collect-styles.ts │ │ ├── collect-styles.spec.ts │ │ ├── inject-styles.ts │ │ └── inject-styles.spec.ts │ ├── api-extractor.json │ ├── tsconfig.json │ ├── README.md │ └── package.json └── postcss-es-modules │ ├── index.js │ ├── src │ ├── index.ts │ └── lib │ │ ├── utils │ │ ├── read-code-for-embed.spec.ts │ │ ├── read-code-for-embed.ts │ │ ├── runtime-options.spec.ts │ │ └── runtime-options.ts │ │ ├── builders │ │ ├── import-statement.ts │ │ ├── import-statement.spec.ts │ │ ├── styles-statement.ts │ │ └── styles-statement.spec.ts │ │ ├── eject.ts │ │ ├── plugin.ts │ │ ├── eject.spec.ts │ │ ├── options.ts │ │ ├── stringify.ts │ │ └── plugin.spec.ts │ ├── api-extractor.json │ ├── tsconfig.json │ └── package.json ├── .gitignore ├── package.json ├── .github └── workflows │ └── build.yml └── README.md /docs/examples/cli/.gitignore: -------------------------------------------------------------------------------- 1 | *.css-module.js 2 | -------------------------------------------------------------------------------- /packages/postcss-node/test/test3.css: -------------------------------------------------------------------------------- 1 | error text file 2 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/.gitignore: -------------------------------------------------------------------------------- 1 | *.css-module.ts 2 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - initial implementation 4 | -------------------------------------------------------------------------------- /packages/postcss-node/test/test1.css: -------------------------------------------------------------------------------- 1 | .a { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /packages/postcss-node/test/test4.bcss: -------------------------------------------------------------------------------- 1 | .a { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /packages/postcss-node/test/test4.ccss: -------------------------------------------------------------------------------- 1 | .a { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /packages/postcss-node/register.js: -------------------------------------------------------------------------------- 1 | require('./index.js').register(); 2 | -------------------------------------------------------------------------------- /packages/postcss-node/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/register'; 2 | -------------------------------------------------------------------------------- /packages/postcss-node/test/test2.css: -------------------------------------------------------------------------------- 1 | .a { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /packages/postcss-node/test/test5.css: -------------------------------------------------------------------------------- 1 | .a { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/cli/src/index.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 24px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/test/test1.css: -------------------------------------------------------------------------------- 1 | .a { 2 | color: blue; 3 | } -------------------------------------------------------------------------------- /docs/css-es-modules/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - initial implementation 4 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/index.js: -------------------------------------------------------------------------------- 1 | export * from './dist/dist-esm/index.js'; 2 | -------------------------------------------------------------------------------- /packages/postcss-node/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/dist-cjs'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .nyc_output 3 | node_modules 4 | dist 5 | coverage 6 | temp 7 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/src/index.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 24px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/test/test.js: -------------------------------------------------------------------------------- 1 | import * as styles from './test1.css'; 2 | console.log(styles); 3 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/app/components/a.component.css: -------------------------------------------------------------------------------- 1 | .aComponent { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/app/components/b.component.css: -------------------------------------------------------------------------------- 1 | .bComponent { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/src/index.css: -------------------------------------------------------------------------------- 1 | /* simple example css file */ 2 | .app { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ES5", 5 | "skipLibCheck": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/css-es-modules/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collect-styles'; 2 | export * from './inject-styles' 3 | import { injectStyles } from './inject-styles'; 4 | export default injectStyles; 5 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/index.js: -------------------------------------------------------------------------------- 1 | const { postcssEsModules } = require('./dist/dist-cjs/index.js'); 2 | module.exports = postcssEsModules; 3 | module.exports.postcssEsModules = postcssEsModules; 4 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/options.ts: -------------------------------------------------------------------------------- 1 | export const defaultPostCssNodeExtensions = ['.css', '.scss', '.sass', '.less', '.stylus']; 2 | export const defaultResponseTimeout = 60000; 3 | export const defaultBufferSize = 1024; 4 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/test/.postcssrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "postcss-es-modules": { 4 | "inject": { 5 | "moduleType": "esm" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/postcss-node/sidebar.md: -------------------------------------------------------------------------------- 1 | * [Overview](../) 2 | * [Examples](../examples/) 3 | * Utility packages 4 | * [css-es-modules](../css-es-modules/) 5 | * [postcss-node](./) 6 | * [Changelog](../CHANGELOG.md) 7 | * [Api reference](../api/) 8 | -------------------------------------------------------------------------------- /docs/api/sidebar.md: -------------------------------------------------------------------------------- 1 | * [Overview](../) 2 | * [Examples](../examples/) 3 | * Utility packages 4 | * [css-es-modules](../css-es-modules/) 5 | * [postcss-node](../postcss-node/) 6 | * [Changelog](../CHANGELOG.md) 7 | * [Api reference](./) 8 | 9 | -------------------------------------------------------------------------------- /docs/css-es-modules/sidebar.md: -------------------------------------------------------------------------------- 1 | * [Overview](../) 2 | * [Examples](../examples/) 3 | * Utility packages 4 | * [css-es-modules](./) 5 | * [postcss-node](../postcss-node/) 6 | * [Changelog](../CHANGELOG.md) 7 | * [Api reference](../api/) 8 | 9 | -------------------------------------------------------------------------------- /docs/examples/sidebar.md: -------------------------------------------------------------------------------- 1 | * [Overview](../) 2 | * [Examples](.) 3 | * Utility packages 4 | * [css-es-modules](../css-es-modules/) 5 | * [postcss-node](../postcss-node/) 6 | * [Changelog](../CHANGELOG.md) 7 | * [Api reference](../api/) 8 | 9 | -------------------------------------------------------------------------------- /docs/sidebar.md: -------------------------------------------------------------------------------- 1 | * [Overview](.) 2 | * [Examples](./examples/) 3 | * Utility packages 4 | * [css-es-modules](./css-es-modules/) 5 | * [postcss-node](./postcss-node/) 6 | * [Changelog](./CHANGELOG.md) 7 | * [Api reference](./api/) 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/sidebar_examples_internal.md: -------------------------------------------------------------------------------- 1 | * [Overview](../../) 2 | * [Examples](../) 3 | * [example](./) 4 | * Utility packages 5 | * [css-es-modules](../../css-es-modules/) 6 | * [postcss-node](../../postcss-node/) 7 | * [Changelog](../../CHANGELOG.md) 8 | * [Api reference](../../api/) 9 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) 4 | 5 | ## API Reference 6 | 7 | ## Packages 8 | 9 | | Package | Description | 10 | | --- | --- | 11 | | [postcss-es-modules](./postcss-es-modules.md) | | 12 | 13 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/app/components/a.component.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'react'; 2 | import styles from './a.component.css'; 3 | 4 | export const AComponent = () => { 5 | return
6 | Component A. 7 |
8 | } 9 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/app/components/b.component.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'react'; 2 | import styles from './b.component.css'; 3 | 4 | export const BComponent = () => { 5 | return
6 | Component B. 7 |
8 | } 9 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/worker-init.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | import {Worker} from "worker_threads"; 3 | 4 | /** 5 | * Create worker. 6 | * @internal 7 | */ 8 | export function workerInit(filename: string, workerData: any): Worker { 9 | return new Worker(filename, { workerData, execArgv: [] }) 10 | } 11 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/index-client.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'react'; 2 | import { hydrate } from 'react-dom'; 3 | import { App } from './app/app.component'; 4 | 5 | // hydrate the prerendered application 6 | hydrate( 7 | , 8 | document.getElementById('app')); 9 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true 7 | }, 8 | "ts-node": { 9 | "compilerOptions": { 10 | "module": "CommonJS" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/api/postcss-node-loader.getformat.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-node-loader](./postcss-node-loader.md) > [getFormat](./postcss-node-loader.getformat.md) 4 | 5 | ## getFormat variable 6 | 7 | Signature: 8 | 9 | ```typescript 10 | getFormat: GetFormatFn 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/postcss-node/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = process.env.TEST_ERROR_CASE 2 | ? { 3 | "plugins": { 4 | "xyz": 1 5 | } 6 | } 7 | : { 8 | "plugins": { 9 | "postcss-es-modules": { 10 | "inject": { 11 | "moduleType": "cjs" 12 | } 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /docs/api/postcss-node-loader.transformsource.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-node-loader](./postcss-node-loader.md) > [transformSource](./postcss-node-loader.transformsource.md) 4 | 5 | ## transformSource variable 6 | 7 | Signature: 8 | 9 | ```typescript 10 | transformSource: TransformSourceFn 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/api/postcss-node.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-node](./postcss-node.md) 4 | 5 | ## postcss-node package 6 | 7 | ## Functions 8 | 9 | | Function | Description | 10 | | --- | --- | 11 | | [register(extensions, timeout, bufferSize)](./postcss-node.register.md) | Register the css files handle for node.js | 12 | 13 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // As we want to import the '*.css' files within the typescript code, we need to provide this 2 | // declaration to avoid compiler errors 3 | declare module '*.css' { 4 | type Styles = { [className: string]: string; } & { inject(): void; } 5 | export const styles: Styles; 6 | export const key: string; 7 | export const css: string; 8 | export default styles; 9 | } 10 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.css_global_key.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [CSS\_GLOBAL\_KEY](./css-es-modules.css_global_key.md) 4 | 5 | ## CSS\_GLOBAL\_KEY variable 6 | 7 | Global styles shared context property name. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | CSS_GLOBAL_KEY = "$CSS$IN$JS$GLOBALS$" 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.css_locals_key.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [CSS\_LOCALS\_KEY](./css-es-modules.css_locals_key.md) 4 | 5 | ## CSS\_LOCALS\_KEY variable 6 | 7 | Local styles shared context property name. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | CSS_LOCALS_KEY = "$CSS$IN$JS$LOCALS$" 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // As we want to import the '*.css' files within the typescript code, we need to provide this 2 | // declaration to avoid compiler errors 3 | declare module '*.css' { 4 | type Styles = { [className: string]: string; } & { inject(): void; } 5 | export const styles: Styles; 6 | export const key: string; 7 | export const css: string; 8 | export default styles; 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules-monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*", 7 | "docs/examples/*" 8 | ], 9 | "scripts": { 10 | "build": "yarn workspaces run build", 11 | "compile": "yarn workspaces run compile", 12 | "test": "yarn workspaces run test", 13 | "lint": "yarn workspaces run lint" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/api/postcss-node-loader.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-node-loader](./postcss-node-loader.md) 4 | 5 | ## postcss-node-loader package 6 | 7 | ## Variables 8 | 9 | | Variable | Description | 10 | | --- | --- | 11 | | [getFormat](./postcss-node-loader.getformat.md) | | 12 | | [transformSource](./postcss-node-loader.transformsource.md) | | 13 | 14 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'postcss' 2 | import { Options, ExtendedStylesInjectOptions, ModulesOptions } from './lib/options'; 3 | import { plugin } from './lib/plugin'; 4 | 5 | /** 6 | * Postcss plugin which converts the css code into the es module. 7 | * @public 8 | */ 9 | export const postcssEsModules: (opts: Options) => Plugin = plugin; 10 | export type { Options, ExtendedStylesInjectOptions, ModulesOptions } 11 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylescollector.raw.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesCollector](./css-es-modules.stylescollector.md) > [raw](./css-es-modules.stylescollector.raw.md) 4 | 5 | ## StylesCollector.raw property 6 | 7 | Raw css string. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | readonly raw: string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.options.modules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [Options](./postcss-es-modules.options.md) > [modules](./postcss-es-modules.options.modules.md) 4 | 5 | ## Options.modules property 6 | 7 | Css modules configuration. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | modules?: ModulesOptions; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/cli/src/index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | // simple import generated module 3 | const { styles, key, css } = require('./index.css'); 4 | 5 | //assertion 6 | assert.match(styles.title, /_title_/); 7 | assert.ok(key.length > 1); 8 | assert.match(css, /font-size: 24px;/); 9 | 10 | // and logging the generated values 11 | console.log('title class name: ', styles.title); 12 | console.log('stylesheet key: ', key); 13 | console.log('raw css: ', css) 14 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.defaultstylesinjectoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [defaultStylesInjectOptions](./css-es-modules.defaultstylesinjectoptions.md) 4 | 5 | ## defaultStylesInjectOptions variable 6 | 7 | Default options values. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | defaultStylesInjectOptions: StylesInjectOptions 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylescollector.ids.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesCollector](./css-es-modules.stylescollector.md) > [ids](./css-es-modules.stylescollector.ids.md) 4 | 5 | ## StylesCollector.ids property 6 | 7 | Stylesheets ids map. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | readonly ids: Record; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.options.inject.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [Options](./postcss-es-modules.options.md) > [inject](./postcss-es-modules.options.inject.md) 4 | 5 | ## Options.inject property 6 | 7 | Css injection configuration. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | inject?: ExtendedStylesInjectOptions; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | // simple import generated module 3 | import { styles, key, css } from './index.css-module'; 4 | 5 | //assertion 6 | assert.match(styles.title, /_title_/); 7 | assert.ok(key.length > 1); 8 | assert.match(css, /font-size: 24px;/); 9 | 10 | // and logging the generated values 11 | console.log('title class name: ', styles.title); 12 | console.log('stylesheet key: ', key); 13 | console.log('raw css: ', css) 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.postcssesmodules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [postcssEsModules](./postcss-es-modules.postcssesmodules.md) 4 | 5 | ## postcssEsModules variable 6 | 7 | Postcss plugin which converts the css code into the es module. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | postcssEsModules: (opts: Options) => Plugin 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylesinjectoptions.usenounce.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) > [useNounce](./css-es-modules.stylesinjectoptions.usenounce.md) 4 | 5 | ## StylesInjectOptions.useNounce property 6 | 7 | The style nounce key. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | useNounce?: string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src/index.ts', 3 | output: { 4 | filename: 'index.js', 5 | }, 6 | mode: 'development', 7 | devtool: 'source-map', 8 | resolve: { 9 | extensions: [ '.js', '.ts', '.tsx', '.css'] 10 | }, 11 | module: { 12 | rules: [ 13 | { test: /\.tsx?$/i, use: 'ts-loader' }, 14 | { test: /\.css$/i, use: 'postcss-loader' }, 15 | ], 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylescollector.html.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesCollector](./css-es-modules.stylescollector.md) > [html](./css-es-modules.stylescollector.html.md) 4 | 5 | ## StylesCollector.html property 6 | 7 | Raw html which can be injected on ssr into the markup on the header. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | readonly html: string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | [Home](./index.md) 2 | 3 | ## API Reference 4 | 5 | ## Packages 6 | 7 | | Package | Description | 8 | | --- | --- | 9 | | [css-es-modules](./css-es-modules.md) | Helper package which is responsible for isomorphic css styles injection | 10 | | [postcss-es-modules](./postcss-es-modules.md) | Postcss plugin which transform css files in to the .js/.ts css modules | 11 | | [postcss-node](./postcss-node.md) | Package which allows import of css files under the node.js eg `require('styles.css')` | 12 | 13 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.collectstyles.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [collectStyles](./css-es-modules.collectstyles.md) 4 | 5 | ## collectStyles() function 6 | 7 | Start collecting of styles. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export declare function collectStyles(): StylesCollector; 13 | ``` 14 | Returns: 15 | 16 | [StylesCollector](./css-es-modules.stylescollector.md) 17 | 18 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import * as Module from 'module'; 2 | 3 | /** 4 | * @internal 5 | */ 6 | export type CssRenderSyncFn = 7 | (data: { code: string, filename: string }) => { css: string, ex: any }; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export type CssRenderFn = 13 | (data: Parameters[0]) => Promise>; 14 | 15 | /** 16 | * @internal 17 | */ 18 | export type CompilableModule = 19 | Module & { _compile: (code: string, fileName: string) => string }; 20 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylesinjectoptions.usenodeglobal.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) > [useNodeGlobal](./css-es-modules.stylesinjectoptions.usenodeglobal.md) 4 | 5 | ## StylesInjectOptions.useNodeGlobal property 6 | 7 | Enable node.js global method of injection. Default true. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | useNodeGlobal?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "jsxFactory": "createElement", 9 | "skipLibCheck": true, 10 | "jsx": "react" 11 | }, 12 | "include": [ 13 | "src" 14 | ], 15 | "ts-node": { 16 | "compilerOptions": { 17 | "module": "CommonJS" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.hashprefix.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [hashPrefix](./postcss-es-modules.modulesoptions.hashprefix.md) 4 | 5 | ## ModulesOptions.hashPrefix property 6 | 7 | It's possible to add custom hash to generate more unique classes using the hashPrefix option. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | hashPrefix?: string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.exportglobals.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [exportGlobals](./postcss-es-modules.modulesoptions.exportglobals.md) 4 | 5 | ## ModulesOptions.exportGlobals property 6 | 7 | If you need to export global names via the module object along with the local ones, add the exportGlobals option. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | exportGlobals?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.attachoriginalclassname.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [attachOriginalClassName](./postcss-es-modules.modulesoptions.attachoriginalclassname.md) 4 | 5 | ## ModulesOptions.attachOriginalClassName property 6 | 7 | If you want to still use the original class name next to local one. Default false. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | attachOriginalClassName?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.scopebehaviour.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [scopeBehaviour](./postcss-es-modules.modulesoptions.scopebehaviour.md) 4 | 5 | ## ModulesOptions.scopeBehaviour property 6 | 7 | By default, the plugin assumes that all the classes are local. You can change this behaviour using the scopeBehaviour option. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | scopeBehaviour?: 'global' | 'local'; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.scripttype.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [scriptType](./postcss-es-modules.extendedstylesinjectoptions.scripttype.md) 4 | 5 | ## ExtendedStylesInjectOptions.scriptType property 6 | 7 | The script type. Options: - `ts`: typescript - `js`: javascript 8 | 9 | Default: 'js' 10 | 11 | Signature: 12 | 13 | ```typescript 14 | scriptType?: 'ts' | 'js'; 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.globalmodulepaths.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [globalModulePaths](./postcss-es-modules.modulesoptions.globalmodulepaths.md) 4 | 5 | ## ModulesOptions.globalModulePaths property 6 | 7 | To define paths for global modules, use the globalModulePaths option. It is an array with regular expressions defining the paths: 8 | 9 | Signature: 10 | 11 | ```typescript 12 | globalModulePaths?: Array; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.moduletype.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [moduleType](./postcss-es-modules.extendedstylesinjectoptions.moduletype.md) 4 | 5 | ## ExtendedStylesInjectOptions.moduleType property 6 | 7 | Generated code modules type. Options: - 'esm' - ecmascript 6 modules - 'cjs' - commonjs 8 | 9 | Default 'esm' 10 | 11 | Signature: 12 | 13 | ```typescript 14 | moduleType?: 'esm' | 'cjs'; 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.localsconvention.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [localsConvention](./postcss-es-modules.modulesoptions.localsconvention.md) 4 | 5 | ## ModulesOptions.localsConvention property 6 | 7 | Style of exported classnames. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' | ((originalClassName: string, generatedClassName: string, inputFile: string) => string); 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.scriptejectpath.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [scriptEjectPath](./postcss-es-modules.extendedstylesinjectoptions.scriptejectpath.md) 4 | 5 | ## ExtendedStylesInjectOptions.scriptEjectPath property 6 | 7 | The path where the script code will be ejected. This option is required if 'eject' value is set for the script type. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | scriptEjectPath?: string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.custom.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [custom](./postcss-es-modules.extendedstylesinjectoptions.custom.md) 4 | 5 | ## ExtendedStylesInjectOptions.custom property 6 | 7 | Custom injection script. Use this property to use custom styles to DOM/Node injection. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | custom?: { 13 | importStatement: string; 14 | injectStatement: string; 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylesinjectoptions.useconstructablestylesheet.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) > [useConstructableStylesheet](./css-es-modules.stylesinjectoptions.useconstructablestylesheet.md) 4 | 5 | ## StylesInjectOptions.useConstructableStylesheet property 6 | 7 | Use Constructable Stylesheet method of the styles injection to DOM if the browser supports Constructable Stylesheet. Default true. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | useConstructableStylesheet?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylesinjectoptions.usestyletag.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) > [useStyleTag](./css-es-modules.stylesinjectoptions.usestyletag.md) 4 | 5 | ## StylesInjectOptions.useStyleTag property 6 | 7 | Enable style tag method of the styles injection to the document. If the useConstructableStylesheet is enabled, this method will ba fallback if browser is not supports Constructable Stylesheets. Default true. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | useStyleTag?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI Build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js 12 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: '12' 23 | - run: yarn install 24 | - run: yarn build 25 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/utils/read-code-for-embed.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { readCodeForEmbed } from './read-code-for-embed'; 3 | 4 | describe('read-code-for-embed', () => { 5 | it ('should read ts source', () => { 6 | const code = readCodeForEmbed('ts', 'esm'); 7 | expect(code).contains('export function injectStyles'); 8 | }); 9 | it ('should read js source', () => { 10 | const code = readCodeForEmbed('js', 'esm'); 11 | expect(code).contains('export function injectStyles'); 12 | }); 13 | it ('should read js source', () => { 14 | const code = readCodeForEmbed('js', 'cjs'); 15 | expect(code).contains('exports.injectStyles ='); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.generatescopedname.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) > [generateScopedName](./postcss-es-modules.modulesoptions.generatescopedname.md) 4 | 5 | ## ModulesOptions.generateScopedName property 6 | 7 | To generate custom classes, use the generateScopedName callback, or just pass an interpolated string to the generateScopedName option: `generateScopedName: "[name]__[local]___[hash:base64:5]"` 8 | 9 | Signature: 10 | 11 | ```typescript 12 | generateScopedName?: string | ((name: string, filename: string, css: string) => string); 13 | ``` 14 | -------------------------------------------------------------------------------- /packages/postcss-node/api-extractor.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 4 | "mainEntryPointFilePath": "/dist/dist-types/index.d.ts", 5 | "apiReport": { 6 | "enabled": false 7 | }, 8 | "docModel": { 9 | "enabled": true 10 | }, 11 | "dtsRollup": { 12 | "enabled": false 13 | }, 14 | "messages": { 15 | "compilerMessageReporting": { 16 | "default": { 17 | "logLevel": "warning" 18 | } 19 | }, 20 | "extractorMessageReporting": { 21 | "default": { 22 | "logLevel": "warning" 23 | } 24 | }, 25 | "tsdocMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/css-es-modules/api-extractor.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 4 | "mainEntryPointFilePath": "/dist/dist-types/index.d.ts", 5 | "apiReport": { 6 | "enabled": false 7 | }, 8 | "docModel": { 9 | "enabled": true 10 | }, 11 | "dtsRollup": { 12 | "enabled": false 13 | }, 14 | "messages": { 15 | "compilerMessageReporting": { 16 | "default": { 17 | "logLevel": "warning" 18 | } 19 | }, 20 | "extractorMessageReporting": { 21 | "default": { 22 | "logLevel": "warning" 23 | } 24 | }, 25 | "tsdocMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/api-extractor.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 4 | "mainEntryPointFilePath": "/dist/dist-types/index.d.ts", 5 | "apiReport": { 6 | "enabled": false 7 | }, 8 | "docModel": { 9 | "enabled": true 10 | }, 11 | "dtsRollup": { 12 | "enabled": false 13 | }, 14 | "messages": { 15 | "compilerMessageReporting": { 16 | "default": { 17 | "logLevel": "warning" 18 | } 19 | }, 20 | "extractorMessageReporting": { 21 | "default": { 22 | "logLevel": "warning" 23 | } 24 | }, 25 | "tsdocMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/api-extractor.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 4 | "mainEntryPointFilePath": "/dist/dist-types/index.d.ts", 5 | "apiReport": { 6 | "enabled": false 7 | }, 8 | "docModel": { 9 | "enabled": true 10 | }, 11 | "dtsRollup": { 12 | "enabled": false 13 | }, 14 | "messages": { 15 | "compilerMessageReporting": { 16 | "default": { 17 | "logLevel": "warning" 18 | } 19 | }, 20 | "extractorMessageReporting": { 21 | "default": { 22 | "logLevel": "warning" 23 | } 24 | }, 25 | "tsdocMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) 4 | 5 | ## postcss-es-modules package 6 | 7 | ## Interfaces 8 | 9 | | Interface | Description | 10 | | --- | --- | 11 | | [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) | Styles loader options. | 12 | | [ModulesOptions](./postcss-es-modules.modulesoptions.md) | Css modules processing options. | 13 | | [Options](./postcss-es-modules.options.md) | The plugin options. | 14 | 15 | ## Variables 16 | 17 | | Variable | Description | 18 | | --- | --- | 19 | | [postcssEsModules](./postcss-es-modules.postcssesmodules.md) | Postcss plugin which converts the css code into the es module. | 20 | 21 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylescollector.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesCollector](./css-es-modules.stylescollector.md) 4 | 5 | ## StylesCollector interface 6 | 7 | The collector object. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export interface StylesCollector 13 | ``` 14 | 15 | ## Properties 16 | 17 | | Property | Type | Description | 18 | | --- | --- | --- | 19 | | [html](./css-es-modules.stylescollector.html.md) | string | Raw html which can be injected on ssr into the markup on the header. | 20 | | [ids](./css-es-modules.stylescollector.ids.md) | Record<string, true> | Stylesheets ids map. | 21 | | [raw](./css-es-modules.stylescollector.raw.md) | string | Raw css string. | 22 | 23 | -------------------------------------------------------------------------------- /docs/api/postcss-node.register.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-node](./postcss-node.md) > [register](./postcss-node.register.md) 4 | 5 | ## register() function 6 | 7 | Register the css files handle for node.js 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export declare function register(extensions?: Array, timeout?: number, bufferSize?: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | extensions | Array<string> | list of extensions, by default '.css', '.scss', '.sass', '.less', '.stylus' | 20 | | timeout | number | the timeout of processing | 21 | | bufferSize | number | the data buffer size | 22 | 23 | Returns: 24 | 25 | void 26 | 27 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.options.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [Options](./postcss-es-modules.options.md) 4 | 5 | ## Options interface 6 | 7 | The plugin options. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export interface Options 13 | ``` 14 | 15 | ## Properties 16 | 17 | | Property | Type | Description | 18 | | --- | --- | --- | 19 | | [inject?](./postcss-es-modules.options.inject.md) | [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) | (Optional) Css injection configuration. | 20 | | [modules?](./postcss-es-modules.options.modules.md) | [ModulesOptions](./postcss-es-modules.modulesoptions.md) | (Optional) Css modules configuration. | 21 | 22 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css } from 'lit-element'; 2 | // importing raw css styles, and css class names map 3 | import { css as rawCss, styles } from './index.css'; 4 | 5 | // simple web component 6 | class MyElement extends LitElement { 7 | static get styles() { 8 | // we will use lit-element css factory, where we will deliver the raw css stirng 9 | return css([rawCss] as any); 10 | } 11 | render() { 12 | // rendering with usage of class names 13 | return html`
14 | Hello word 15 |
`; 16 | } 17 | } 18 | 19 | // define web-component within the document 20 | customElements.define('my-element', MyElement); 21 | 22 | // render the component within body 23 | document.body.innerHTML = ''; 24 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.script.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [script](./postcss-es-modules.extendedstylesinjectoptions.script.md) 4 | 5 | ## ExtendedStylesInjectOptions.script property 6 | 7 | The way how the styles injector script will be referred from the generated source. Options: - `embed`: embedding loader script in the target source - `eject`: the loader script will be ejected to the provided scriptEjectPath - `import`: the loader script will be referred by the import statement 8 | 9 | Default: 'import' 10 | 11 | Signature: 12 | 13 | ```typescript 14 | script?: 'embed' | 'eject' | 'import'; 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/css-es-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "ESNext", 5 | "declarationDir": "dist/dist-types", 6 | "moduleResolution": "Node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "experimentalDecorators": true, 13 | "isolatedModules": true, 14 | "resolveJsonModule": true, 15 | "allowJs": true, 16 | "rootDir": "src", 17 | "baseUrl": "src", 18 | "skipLibCheck": true, 19 | }, 20 | "exclude": [ 21 | "src/**/*.spec.ts" 22 | ], 23 | "include": [ 24 | "src" 25 | ], 26 | "ts-node": { 27 | "transpileOnly": true, 28 | "compilerOptions": { 29 | "module": "CommonJS" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/examples/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules-example-cli", 3 | "version": "1.0.1", 4 | "private": true, 5 | "description": "Example of usage postcss-es-modules with the postcss cli", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "yarn test", 9 | "start": "node -r postcss-node/register src/index.js", 10 | "test": "yarn start" 11 | }, 12 | "devDependencies": { 13 | "postcss": "^8.2.13", 14 | "postcss-cli": "^8.3.1", 15 | "postcss-es-modules": "2.0.2", 16 | "postcss-node": "2.0.2", 17 | "css-es-modules": "1.0.2", 18 | "typescript": "^4.1.5", 19 | "ts-node": "^9.1.1" 20 | }, 21 | "postcss": { 22 | "plugins": { 23 | "postcss-es-modules": { 24 | "inject": { 25 | "moduleType": "cjs" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | * [Postcss cli](./cli/)
4 | Example of usage postcss-es-modules with the postcss cli. 5 | 6 | * [Postcss cli and typescript](./cli-typescript/)
7 | Example of usage postcss-es-modules with the postcss cli and typescript. 8 | 9 | * [Lit Element](./lit-element-webpack-typescript/)
10 | Example of usage postcss-es-modules with the [lit-element](https://lit-element.polymer-project.org/) 11 | library, typescript and webpack. 12 | 13 | * [Postcss-middleware and typescript](./postcss-middleware-typescript-scss/)
14 | Example of usage postcss-es-modules with the [postcss-middleware](https://github.com/jedmao/postcss-middleware#readme/) 15 | library, typescript and sass. 16 | 17 | * [React server side and client side rendering](./react-ssr-webpack-typescript/)
18 | Example of usage postcss-es-modules with the React library, typescript and webpack. 19 | With the server side and client side rendering. 20 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/utils/read-code-for-embed.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { join } from 'path'; 3 | import { ExtendedStylesInjectOptions } from '../options'; 4 | 5 | /** 6 | * Reading the source of injector. 7 | * @param scriptType - type of required script 8 | * @param moduleType - type of required modules types 9 | */ 10 | export const readCodeForEmbed = ( 11 | scriptType: ExtendedStylesInjectOptions['scriptType'], 12 | moduleType: ExtendedStylesInjectOptions['moduleType']): string => { 13 | const codeBasePath = require.resolve('css-es-modules'); 14 | const codePath = scriptType === 'ts' 15 | ? join(codeBasePath, '../../../src/inject-styles.ts') 16 | : moduleType === 'esm' 17 | ? join(codeBasePath, '../../dist-esm/inject-styles.js') 18 | : join(codeBasePath, '../../dist-cjs/inject-styles.js'); 19 | const code = readFileSync(codePath).toString('utf-8'); 20 | return `${code}\n`; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/postcss-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "ESNext", 5 | "declarationDir": "dist/dist-types", 6 | "moduleResolution": "Node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "experimentalDecorators": true, 13 | "isolatedModules": true, 14 | "resolveJsonModule": true, 15 | "allowSyntheticDefaultImports": true, 16 | "allowJs": true, 17 | "rootDir": "src", 18 | "baseUrl": "src", 19 | "lib": ["ES6", "DOM"], 20 | "skipLibCheck": true 21 | }, 22 | "exclude": [ 23 | "src/**/*.spec.ts" 24 | ], 25 | "include": [ 26 | "src" 27 | ], 28 | "ts-node": { 29 | "transpileOnly": true, 30 | "compilerOptions": { 31 | "module": "CommonJS" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.injectmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) > [injectMode](./postcss-es-modules.extendedstylesinjectoptions.injectmode.md) 4 | 5 | ## ExtendedStylesInjectOptions.injectMode property 6 | 7 | The mode of the styles injection. Options: - `lazy` - the stylesheet will be injected on the first use of the style class name within the code - `ondemand` - the stylesheet will be injected only when the `styles.inject()` method will be called - `instant` - the stylesheet will be injected on the module load - `none` - the stylesheet will be never injected, to inject it to the DOM you will need to import css content Default: 'lazy' 8 | 9 | Signature: 10 | 11 | ```typescript 12 | injectMode?: 'lazy' | 'ondemand' | 'instant' | 'none'; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/app/app.component.tsx: -------------------------------------------------------------------------------- 1 | import { createElement, useEffect, useState } from 'react'; 2 | import { AComponent } from './components/a.component'; 3 | import { BComponent } from './components/b.component'; 4 | 5 | // simple application 6 | export const App = ({ showBComponent }:{ showBComponent?: boolean }) => { 7 | 8 | // keeping the state of application 9 | const [showed, setShow] = useState(showBComponent); 10 | 11 | // this is just for reflect state within the url 12 | useEffect(() => { 13 | if (typeof window !== 'undefined') { 14 | window.history.pushState( 15 | {}, '', `${window.location.href.split('?')[0]}${showed ? '?showBComponent=true': ''}`); 16 | } 17 | }, [showed]); 18 | 19 | // rendering 20 | return
21 | 22 | 23 | {showed && } 24 |
25 | } 26 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/utils/runtime-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { defaultStylesInjectOptions } from 'css-es-modules'; 3 | import { prepareRuntimeOptions } from './runtime-options'; 4 | 5 | describe('runtime-options', () => { 6 | it ('should return undefined for inject undefined', () => { 7 | const options = prepareRuntimeOptions({ inject: undefined, modules: undefined}); 8 | expect(options).undefined; 9 | }); 10 | it ('should return undefined for inject default', () => { 11 | const options = prepareRuntimeOptions({ inject: defaultStylesInjectOptions, modules: undefined}); 12 | expect(options).undefined; 13 | }); 14 | it ('should return options if on of options is set', () => { 15 | const options = prepareRuntimeOptions({ inject: { useConstructableStylesheet: false}, modules: undefined}); 16 | expect(options).eql({ 17 | useConstructableStylesheet: false 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const common = { 4 | output: { 5 | path: resolve(__dirname, 'dist'), 6 | }, 7 | mode: 'development', 8 | devtool: 'source-map', 9 | resolve: { 10 | extensions: [ '.js', '.ts', '.tsx', '.css'] 11 | }, 12 | module: { 13 | rules: [ 14 | { test: /\.tsx?$/i, use: 'ts-loader' }, 15 | { test: /\.css$/i, use: 'postcss-loader' }, 16 | ], 17 | } 18 | } 19 | 20 | module.exports = [ 21 | { 22 | entry: './src/index-server.tsx', 23 | target: 'node', 24 | output: { 25 | filename: 'index-server.js', 26 | libraryTarget: 'commonjs' 27 | } 28 | }, 29 | { 30 | entry: './src/index-client.tsx', 31 | target: 'web', 32 | output: { 33 | filename: 'index-client.js' 34 | } 35 | }, 36 | ].map(i => ({...common, ...i, output: {...common.output, ...i.output}})); 37 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "ESNext", 5 | "declarationDir": "dist/dist-types", 6 | "moduleResolution": "Node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "noEmitHelpers": true, 15 | "isolatedModules": true, 16 | "resolveJsonModule": true, 17 | "allowSyntheticDefaultImports": true, 18 | "allowJs": true, 19 | "rootDir": "src", 20 | "baseUrl": "src", 21 | "skipLibCheck": true, 22 | "lib": ["ES6", "DOM"] 23 | }, 24 | "exclude": [ 25 | "src/**/*.spec.ts" 26 | ], 27 | "include": [ 28 | "src" 29 | ], 30 | "ts-node": { 31 | "transpileOnly": true, 32 | "compilerOptions": { 33 | "module": "CommonJS" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "ESNext", 5 | "declarationDir": "dist/dist-types", 6 | "skipLibCheck": true, 7 | "moduleResolution": "Node", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "strict": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "experimentalDecorators": true, 14 | "importHelpers": true, 15 | "noEmitHelpers": true, 16 | "isolatedModules": true, 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | "allowJs": true, 20 | "rootDir": "src", 21 | "baseUrl": "src", 22 | "lib": ["ES6", "DOM"] 23 | }, 24 | "exclude": [ 25 | "src/**/*.spec.ts" 26 | ], 27 | "include": [ 28 | "src" 29 | ], 30 | "ts-node": { 31 | "transpileOnly": true, 32 | "compilerOptions": { 33 | "module": "CommonJS" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.injectstyles.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [injectStyles](./css-es-modules.injectstyles.md) 4 | 5 | ## injectStyles() function 6 | 7 | Inject stylesheet to global on node or to the document DOM on the browser 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export declare function injectStyles(stylesheetKey: string, stylesheetBody: string, options?: StylesInjectOptions, serverSide?: boolean, shadowRoot?: ShadowRoot): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | stylesheetKey | string | the unique stylesheet key | 20 | | stylesheetBody | string | the stylesheet body | 21 | | options | [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) | inject options | 22 | | serverSide | boolean | force server/client approach | 23 | | shadowRoot | ShadowRoot | optional shadow root where styles will be injected | 24 | 25 | Returns: 26 | 27 | void 28 | 29 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/README.md: -------------------------------------------------------------------------------- 1 | # Cli typescript example 2 | Example of usage postcss-es-modules with the postcss cli and typescript. 3 | 4 | In this example we are simply using postcss cli to generate the javascript code, and then we 5 | are referring it from the typescript code. 6 | 7 | ## running 8 | ```bash 9 | yarn run 10 | ``` 11 | 12 | ## project structure 13 | ``` 14 | ├── src 15 | │ ├── index.css // css source code 16 | │ └── index.ts // typescript 17 | ├── package.json // project package.json 18 | └── tsconfig.json // minimal typescript configuration 19 | ``` 20 | 21 | ## ./src/index.css 22 | 23 | [./src/index.css](./src/index.css ':include :type=code') 24 | 25 | ## ./src/index.ts 26 | 27 | [./src/index.ts](./src/index.ts ':include :type=code') 28 | 29 | 30 | ## ./package.json 31 | 32 | [./package.json](./package.json ':include :type=code') 33 | 34 | > Please notice the settings for the plugin. As we are run this code directly under the ts-node we 35 | > are generating the typescript modules. 36 | 37 | ## ./tsconfig.json 38 | [./tsconfig.json](./tsconfig.json ':include :type=code') 39 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules-examples-lit-element-webpack-typescript", 3 | "version": "1.0.1", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "yarn compile && yarn test", 8 | "compile": "tsc --noEmit", 9 | "start": "webpack serve", 10 | "test": "ts-node ./test/index.ts" 11 | }, 12 | "dependencies": { 13 | "lit-element": "^2.4.0" 14 | }, 15 | "devDependencies": { 16 | "postcss": "^8.2.13", 17 | "postcss-es-modules": "2.0.2", 18 | "postcss-loader": "^5.2.0", 19 | "puppeteer": "^7.1.0", 20 | "ts-loader": "^9.1.1", 21 | "ts-node": "^9.1.1", 22 | "typescript": "^4.1.3", 23 | "webpack": "^5.36.1", 24 | "webpack-cli": "^4.6.0", 25 | "webpack-dev-server": "^3.11.2" 26 | }, 27 | "postcss": { 28 | "plugins": { 29 | "postcss-es-modules": { 30 | "inject": { 31 | "injectMode": "none" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/examples/cli/README.md: -------------------------------------------------------------------------------- 1 | # Cli example 2 | Example of usage postcss-es-modules with the postcss cli. 3 | 4 | In this example we are simply using postcss cli to generate the javascript code, and then we 5 | are referring it from the javascript code. 6 | 7 | ## running 8 | ```bash 9 | yarn run 10 | ``` 11 | 12 | ## project structure 13 | ``` 14 | ├── src 15 | │ ├── index.css // css source code 16 | │ └── index.js // javascript 17 | └── package.json // project package.json 18 | ``` 19 | 20 | ## ./src/index.css 21 | Css source code. 22 | 23 | [./src/index.css](./src/index.css ':include :type=code') 24 | 25 | ## ./src/index.js 26 | Javascript. 27 | 28 | [./src/index.js](./src/index.js ':include :type=code') 29 | 30 | ## ./package.json 31 | Project package.json 32 | 33 | [./package.json](./package.json ':include :type=code') 34 | 35 | > Please notice the settings for the plugin. As we are run this code directly under the node we 36 | > are generating the common js modules. 37 | 38 | > Please notice the node -r option. As we are imports css files we are using here postcss-node, which 39 | > provides easy way of importing the css files within the node.js. 40 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) 4 | 5 | ## css-es-modules package 6 | 7 | ## Functions 8 | 9 | | Function | Description | 10 | | --- | --- | 11 | | [collectStyles()](./css-es-modules.collectstyles.md) | Start collecting of styles. | 12 | | [injectStyles(stylesheetKey, stylesheetBody, options, serverSide, shadowRoot)](./css-es-modules.injectstyles.md) | Inject stylesheet to global on node or to the document DOM on the browser | 13 | 14 | ## Interfaces 15 | 16 | | Interface | Description | 17 | | --- | --- | 18 | | [StylesCollector](./css-es-modules.stylescollector.md) | The collector object. | 19 | | [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) | Inject options. | 20 | 21 | ## Variables 22 | 23 | | Variable | Description | 24 | | --- | --- | 25 | | [CSS\_GLOBAL\_KEY](./css-es-modules.css_global_key.md) | Global styles shared context property name. | 26 | | [CSS\_LOCALS\_KEY](./css-es-modules.css_locals_key.md) | Local styles shared context property name. | 27 | | [defaultStylesInjectOptions](./css-es-modules.defaultstylesinjectoptions.md) | Default options values. | 28 | 29 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { launch } from 'puppeteer'; 3 | import webpack from 'webpack'; 4 | import WebpackDevServer from 'webpack-dev-server'; 5 | import webpackConfig from '../webpack.config.js'; 6 | 7 | (async () => { 8 | try { 9 | const compiler = webpack(webpackConfig); 10 | const webpackServer = new WebpackDevServer(compiler); 11 | await new Promise(res => compiler.hooks.done.tap('test', res)); 12 | const server = webpackServer.listen(3001); 13 | await new Promise(res => server.on('listening', res)); 14 | const browser = await launch(); 15 | const page = await browser.newPage(); 16 | await page.goto(`http://localhost:3001`, {waitUntil: 'networkidle2'}); 17 | const div = await page.$('my-element'); 18 | const color = await page.evaluate(el => getComputedStyle(el.shadowRoot.firstElementChild).color, div); 19 | assert.strictEqual(color, 'rgb(255, 0, 0)', 20 | 'element should be red'); 21 | await browser.close(); 22 | server.close(); 23 | } catch (ex) { 24 | console.error(ex); 25 | process.exit(1); 26 | } 27 | process.exit(0); 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/examples/cli-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules-example-cli-typescript", 3 | "version": "1.0.1", 4 | "private": true, 5 | "description": "Example of usage postcss-es-modules with the postcss cli", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "yarn postcss && yarn compile && yarn test", 9 | "compile": "tsc --noEmit", 10 | "postcss": "postcss src/**/*.css --no-map --dir src --base src --ext css-module.ts", 11 | "start": "yarn build && ts-node src/index.ts", 12 | "test": "ts-node src/index.ts" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^16.11.26", 16 | "postcss": "^8.2.13", 17 | "postcss-cli": "^8.3.1", 18 | "postcss-es-modules": "2.0.2", 19 | "ts-node": "^9.1.1", 20 | "typescript": "^4.1.3" 21 | }, 22 | "postcss": { 23 | "plugins": { 24 | "postcss-es-modules": { 25 | "inject": { 26 | "script": "embed", 27 | "scriptType": "ts" 28 | }, 29 | "modules": { 30 | "attachOriginalClassName": true 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules-examples-react-ssr-webpack-typescript", 3 | "version": "1.0.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "yarn compile && webpack && yarn test", 7 | "compile": "tsc --noEmit", 8 | "start": "webpack && node ./dist/index-server.js", 9 | "test": "ts-node ./test/index" 10 | }, 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1" 15 | }, 16 | "devDependencies": { 17 | "@types/express": "^4.17.11", 18 | "@types/node": "^16.11.26", 19 | "postcss": "^8.2.13", 20 | "postcss-cli": "^8.3.1", 21 | "postcss-es-modules": "2.0.2", 22 | "postcss-loader": "^5.2.0", 23 | "puppeteer": "^7.1.0", 24 | "ts-loader": "^9.1.1", 25 | "ts-node": "^9.1.1", 26 | "typescript": "^4.1.3", 27 | "webpack": "^5.36.1", 28 | "webpack-cli": "^4.6.0", 29 | "webpack-dev-server": "^3.11.2" 30 | }, 31 | "postcss": { 32 | "plugins": { 33 | "postcss-es-modules": { 34 | "inject": { 35 | "scriptType": "ts" 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/api/css-es-modules.stylesinjectoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [css-es-modules](./css-es-modules.md) > [StylesInjectOptions](./css-es-modules.stylesinjectoptions.md) 4 | 5 | ## StylesInjectOptions interface 6 | 7 | Inject options. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export interface StylesInjectOptions 13 | ``` 14 | 15 | ## Properties 16 | 17 | | Property | Type | Description | 18 | | --- | --- | --- | 19 | | [useConstructableStylesheet?](./css-es-modules.stylesinjectoptions.useconstructablestylesheet.md) | boolean | (Optional) Use Constructable Stylesheet method of the styles injection to DOM if the browser supports Constructable Stylesheet. Default true. | 20 | | [useNodeGlobal?](./css-es-modules.stylesinjectoptions.usenodeglobal.md) | boolean | (Optional) Enable node.js global method of injection. Default true. | 21 | | [useNounce?](./css-es-modules.stylesinjectoptions.usenounce.md) | string | (Optional) The style nounce key. | 22 | | [useStyleTag?](./css-es-modules.stylesinjectoptions.usestyletag.md) | boolean | (Optional) Enable style tag method of the styles injection to the document. If the useConstructableStylesheet is enabled, this method will ba fallback if browser is not supports Constructable Stylesheets. Default true. | 23 | 24 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/utils/runtime-options.ts: -------------------------------------------------------------------------------- 1 | import { defaultStylesInjectOptions } from 'css-es-modules'; 2 | import { ExtendedStylesInjectOptions, Options } from '../options'; 3 | 4 | /** 5 | * Options for runtime type. 6 | */ 7 | export type RuntimeOptions = Pick; 8 | 9 | /** 10 | * Keys of runtime options. 11 | */ 12 | const runtimeOptionsKeys = 13 | ['useNounce', 'useConstructableStylesheet', 'useStyleTag', 'useNodeGlobal'] as const; 14 | 15 | /** 16 | * Preparing runtime options 17 | */ 18 | export const prepareRuntimeOptions = (options: Required): RuntimeOptions | undefined => { 19 | const loaderOptions = options.inject; 20 | let runtimeOptions: RuntimeOptions | undefined; 21 | if (loaderOptions) { 22 | let hasModifiedOptions = false; 23 | runtimeOptions = runtimeOptionsKeys.reduce((r, i) => { 24 | if (loaderOptions[i] !== undefined && loaderOptions[i] !== defaultStylesInjectOptions[i]) { 25 | hasModifiedOptions = true; 26 | return { 27 | ...r, 28 | [i]: loaderOptions[i] 29 | }; 30 | } 31 | return r; 32 | }, {} as RuntimeOptions); 33 | if (!hasModifiedOptions) { 34 | runtimeOptions = undefined; 35 | } 36 | } 37 | return runtimeOptions; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/worker.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | import postcss from 'postcss'; 3 | import postcssrc from 'postcss-load-config'; 4 | import v8 from 'v8'; 5 | import { parentPort, workerData } from 'worker_threads'; 6 | 7 | const INT32_BYTES = 4; 8 | 9 | let processOptions: ReturnType['options']; 10 | let processor: ReturnType; 11 | 12 | parentPort?.on('message', async (message) => { 13 | let data: { css?: string, ex?: any }; 14 | const {code, filename, messageBuffer} = message; 15 | try { 16 | if (!processor) { 17 | const {plugins, options} = postcssrc.sync({}); 18 | processOptions = options; 19 | processor = postcss(plugins); 20 | } 21 | const result = await processor.process(code, { 22 | ...(processOptions as any), 23 | from: filename 24 | }); 25 | data = { css: result.css }; 26 | } catch (ex) { 27 | data = { ex }; 28 | } 29 | const buf = v8.serialize(data); 30 | buf.copy(Buffer.from(messageBuffer), INT32_BYTES); 31 | const semaphore = new Int32Array(messageBuffer); 32 | await Atomics.store(semaphore, 0, buf.length); 33 | Atomics.notify(semaphore, 0) 34 | }); 35 | 36 | const workerReady = async () => { 37 | const { sharedBuffer } = workerData; 38 | const semaphore = new Int32Array(sharedBuffer) 39 | await Atomics.store(semaphore, 0, 0); 40 | Atomics.notify(semaphore, 0) 41 | } 42 | 43 | workerReady(); 44 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/builders/import-statement.ts: -------------------------------------------------------------------------------- 1 | import { dirname, relative } from 'path'; 2 | import { Builder, Root } from 'postcss'; 3 | import { defaultOptions, ExtendedStylesInjectOptions } from '../options'; 4 | 5 | /** 6 | * Builds import statement. 7 | */ 8 | const buildImportStatement = ( 9 | node: Root, builder: Builder, from: string, item: string, module: 'esm' | 'cjs' = defaultOptions.inject.moduleType) => { 10 | if (module === 'cjs') { 11 | builder(`const { injectStyles } = require('${from}');\n`, node); 12 | } else { 13 | builder(`import { ${item} } from '${from}';\n`, node); 14 | } 15 | }; 16 | 17 | /** 18 | * Builds import statement of injector. 19 | */ 20 | export const buildImportInjectStylesStatement = ( 21 | node: Root, builder: Builder, options: ExtendedStylesInjectOptions = {}): void => { 22 | if (options.script === 'eject' 23 | && options.scriptEjectPath 24 | && node.source 25 | && node.source.input.file) { 26 | let rel = relative(dirname(node.source.input.file), options.scriptEjectPath); 27 | rel = rel.replace(/\\/g, '/'); 28 | if (rel === '') { 29 | rel = '.' 30 | } 31 | if (!rel.startsWith('.')) { 32 | rel = `./${rel}`; 33 | } 34 | buildImportStatement(node, builder, `${rel}/inject-styles`, 'injectStyles', options.moduleType); 35 | } else { 36 | buildImportStatement(node, builder, 'css-es-modules', 'injectStyles', options.moduleType); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /docs/examples/lit-element-webpack-typescript/README.md: -------------------------------------------------------------------------------- 1 | # Lit element example 2 | Example of usage postcss-es-modules with the [lit-element](https://lit-element.polymer-project.org/) 3 | library, typescript and webpack. 4 | 5 | In this example we are simply using webpack postcss-loader for loading the raw css in to the 6 | typescript module. Then we are referring it from the web-component. 7 | 8 | ## running 9 | ```bash 10 | yarn run 11 | ``` 12 | 13 | ## project structure 14 | ``` 15 | ├── src 16 | │ ├── global.d.ts // global types declaration 17 | │ ├── index.css // css source code 18 | │ └── index.ts // typescript 19 | ├── index.html // the html index file 20 | ├── package.json // project package.json 21 | ├── tsconfig.json // minimal typescript config 22 | └── webpack.config.js // minimal webpack config 23 | ``` 24 | 25 | ## ./src/index.css 26 | 27 | [./src/index.css](./src/index.css ':include :type=code') 28 | 29 | ## ./src/global.d.ts 30 | 31 | [./src/global.d.ts](./src/global.d.ts ':include :type=code') 32 | 33 | ## ./src/index.ts 34 | 35 | [./src/index.ts](./src/index.ts ':include :type=code') 36 | 37 | ## ./index.html 38 | 39 | [./index.html](./index.html ':include :type=code') 40 | 41 | ## ./package.json 42 | 43 | [./package.json](./package.json ':include :type=code') 44 | 45 | > Please notice the settings for the plugin. As we are using the lit-element build-in styles injection 46 | > we are using injection mode `none`. 47 | 48 | ## ./tsconfig.json 49 | 50 | [./tsconfig.json](./tsconfig.json ':include :type=code') 51 | 52 | ## ./webpack.config.js 53 | 54 | [./webpack.config.js](./webpack.config.js ':include :type=code') 55 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/src/index-server.tsx: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { createElement } from 'react'; 3 | import { renderToString } from 'react-dom/server'; 4 | import { collectStyles, StylesCollector } from 'css-es-modules'; 5 | import { App } from './app/app.component'; 6 | 7 | /** 8 | * The template of page. 9 | * @param showBComponent - state of the application 10 | * @param styles - collected styles 11 | * @param html - the rendered html 12 | */ 13 | const template = (showBComponent: boolean, styles: StylesCollector, html: string) => ` 14 | 15 | 16 | ${styles.html} 17 | 20 | 21 | 22 |
${ html }
23 | 24 | 25 | `; 26 | 27 | // prepare app 28 | const app = express(); 29 | 30 | // handle request for the root 31 | app.get('/', (req, res) => { 32 | // taking the state from the url 33 | const showBComponent = !!req.query['showBComponent']; 34 | // sending the prerendered app 35 | res.send( 36 | template( 37 | showBComponent, 38 | // firstly start collecting styles 39 | collectStyles(), 40 | // then render application 41 | renderToString())); 42 | }); 43 | 44 | // statics 45 | app.use('/', express.static(__dirname)); 46 | 47 | // running app and expose server for testing 48 | export const server = app.listen(3001, () => console.log('http://localhost:3001')); 49 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/register.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'process'; 2 | import { defaultPostCssNodeExtensions, defaultResponseTimeout, defaultBufferSize } from './options'; 3 | import { CompilableModule, CssRenderSyncFn } from './types'; 4 | import { workerSyncFunction } from './worker-sync-function'; 5 | 6 | 7 | /** 8 | * Register the css files handle for node.js 9 | * @public 10 | * @param extensions - list of extensions, by default '.css', '.scss', '.sass', '.less', '.stylus' 11 | * @param timeout - the timeout of processing 12 | * @param bufferSize - the data buffer size 13 | */ 14 | export function register( 15 | extensions: Array = env.POSTCSS_NODE_EXT?.split(',') || defaultPostCssNodeExtensions, 16 | timeout: number = env.POSTCSS_NODE_TIMEOUT ? parseInt(env.POSTCSS_NODE_TIMEOUT) : defaultResponseTimeout, 17 | bufferSize: number = env.POSTCSS_NODE_BUFFER_SIZE ? parseInt(env.POSTCSS_NODE_BUFFER_SIZE) : defaultBufferSize): void { 18 | let renderCssFile: CssRenderSyncFn; 19 | const jsHandle = require.extensions['.js']; 20 | const cssHandle = ((module: CompilableModule, filename: string) => { 21 | const orgCompile = module._compile 22 | module._compile = function (code: string, fileName: string) { 23 | if (!renderCssFile) { 24 | renderCssFile = workerSyncFunction(__dirname + '/worker.js', timeout, bufferSize); 25 | } 26 | const { css , ex } = renderCssFile({ code, filename }); 27 | /* istanbul ignore else */ 28 | if (css) { 29 | return orgCompile.call(this, css, fileName) 30 | } else { 31 | throw ex; 32 | } 33 | }; 34 | return jsHandle(module, filename); 35 | }) as any; 36 | extensions.forEach(ext => require.extensions[ext] = cssHandle); 37 | } 38 | -------------------------------------------------------------------------------- /packages/postcss-node/src/lib/worker-sync-function.ts: -------------------------------------------------------------------------------- 1 | import v8 from 'v8'; 2 | import { Worker } from "worker_threads"; 3 | import { workerInit } from './worker-init'; 4 | 5 | const INT32_BYTES = 4 6 | const initBufferSize = 64 * 1024; 7 | 8 | /** 9 | * Creates sync function proxy. 10 | * @internal 11 | */ 12 | export function workerSyncFunction( 13 | filename: string, 14 | timeout: number, 15 | bufferSize: number): (inputData: P) => R { 16 | let worker: Worker | undefined; 17 | let terminateTimeout: any; 18 | return (inputData: P): R => { 19 | if (terminateTimeout) { 20 | clearTimeout(terminateTimeout); 21 | } 22 | if (!worker) { 23 | const initBuffer = new SharedArrayBuffer(initBufferSize) 24 | const initSemaphore = new Int32Array(initBuffer); 25 | worker = workerInit(filename, { sharedBuffer: initBuffer }); 26 | const initResponse = Atomics.wait(initSemaphore, 0, 0, 5000); 27 | /* istanbul ignore if */ 28 | if (initResponse === 'timed-out') { 29 | throw 'Worker init timeout'; 30 | } 31 | } 32 | const messageBuffer = new SharedArrayBuffer(bufferSize * 1024) 33 | const messageSemaphore = new Int32Array(messageBuffer); 34 | worker.postMessage({ 35 | ...inputData, 36 | messageBuffer 37 | }); 38 | const response = Atomics.wait(messageSemaphore, 0, 0, timeout); 39 | terminateTimeout = setTimeout(() => { 40 | /* istanbul ignore else */ 41 | if (worker) { 42 | worker.terminate() 43 | worker = undefined; 44 | terminateTimeout = undefined; 45 | } 46 | }); 47 | /* istanbul ignore if */ 48 | if (response === 'timed-out') { 49 | throw 'Worker timeout'; 50 | } 51 | const length = messageSemaphore[0]; 52 | return v8.deserialize(Buffer.from(messageBuffer, INT32_BYTES, length)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/eject.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { Options } from './options'; 3 | import { promises, existsSync } from 'fs'; 4 | 5 | let isEjecting = false; 6 | 7 | /** 8 | * Eject the loader code. 9 | * @internal 10 | */ 11 | export const eject = async (options: Required): Promise => { 12 | // resolving path to sources 13 | const codeBasePath = require.resolve('css-es-modules'); 14 | // determine the extension 15 | const extension = options.inject.scriptType === 'ts' 16 | ? '.ts' 17 | : '.js'; 18 | // determine source to eject 19 | const codePath = options.inject.scriptType === 'ts' 20 | ? join(codeBasePath, '../../../src') 21 | : options.inject.moduleType === 'esm' 22 | ? join(codeBasePath, '../../dist-esm') 23 | : join(codeBasePath, '../../dist-cjs'); 24 | // if we know where to eject and we are not during the ejection 25 | if (options.inject.scriptEjectPath && !isEjecting) { 26 | isEjecting = true; 27 | try { 28 | const injectCodePath = join(options.inject.scriptEjectPath, `inject-styles${extension}`); 29 | const collectCodePath = join(options.inject.scriptEjectPath, `collect-styles${extension}`); 30 | // create directories 31 | await promises.mkdir(options.inject.scriptEjectPath, {recursive: true}); 32 | // if not exist 33 | if (!existsSync(injectCodePath)) { 34 | // copy the inject-styles code 35 | await promises.copyFile( 36 | join(codePath, `inject-styles${extension}`), injectCodePath); 37 | } 38 | // if not exist 39 | if (!existsSync(collectCodePath)) { 40 | // copy the collect-styles code 41 | await promises.copyFile( 42 | join(codePath, `collect-styles${extension}`), collectCodePath); 43 | } 44 | } finally { 45 | // notify that we where finish 46 | isEjecting = false; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/css-es-modules/src/collect-styles.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { CSS_GLOBAL_KEY, CSS_LOCALS_KEY } from './inject-styles'; 3 | 4 | // globals declaration to avoid the typescript compile errors 5 | declare global { 6 | // Using `string | true` as second record generic because Window is type of `Window & typeof globalThis;` 7 | var $CSS$IN$JS$GLOBALS$: Record; 8 | var $CSS$IN$JS$LOCALS$: Record; 9 | 10 | interface Document { 11 | adoptedStyleSheets: CSSStyleSheet[]; 12 | } 13 | var global: typeof globalThis; 14 | } 15 | 16 | /** 17 | * The collector object. 18 | * @public 19 | */ 20 | export interface StylesCollector { 21 | /** 22 | * Raw css string. 23 | */ 24 | readonly raw: string; 25 | /** 26 | * Stylesheets ids map. 27 | */ 28 | readonly ids: Record; 29 | /** 30 | * Raw html which can be injected on ssr into the markup on the header. 31 | */ 32 | readonly html: string; 33 | } 34 | 35 | /** 36 | * Start collecting of styles. 37 | * @public 38 | */ 39 | export function collectStyles(): StylesCollector { 40 | // prepare new collection for the locals injection 41 | const collection: Record = {}; 42 | typeof global !== 'undefined' && (global[CSS_LOCALS_KEY] = collection); 43 | 44 | return { 45 | get raw() { 46 | const globalsCollection = global[CSS_GLOBAL_KEY]; 47 | const styles = Object.keys(globalsCollection || {}).reduce( 48 | (r, i) => `${r}\n${globalsCollection[i]}`, ''); 49 | return Object.keys(collection).reduce( 50 | (r, i) => `${r}\n${collection[i]}`, styles); 51 | }, 52 | get ids() { 53 | const ids = Object.keys(global[CSS_GLOBAL_KEY] || {}).reduce( 54 | (r, i) => ({ ...r, [i]: true }), {}); 55 | return Object.keys(collection).reduce( 56 | (r, i) => ({ ...r, [i]: true }), ids); 57 | }, 58 | get html() { 59 | return `` 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.modulesoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ModulesOptions](./postcss-es-modules.modulesoptions.md) 4 | 5 | ## ModulesOptions interface 6 | 7 | Css modules processing options. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export interface ModulesOptions 13 | ``` 14 | 15 | ## Properties 16 | 17 | | Property | Type | Description | 18 | | --- | --- | --- | 19 | | [attachOriginalClassName?](./postcss-es-modules.modulesoptions.attachoriginalclassname.md) | boolean | (Optional) If you want to still use the original class name next to local one. Default false. | 20 | | [exportGlobals?](./postcss-es-modules.modulesoptions.exportglobals.md) | boolean | (Optional) If you need to export global names via the module object along with the local ones, add the exportGlobals option. | 21 | | [generateScopedName?](./postcss-es-modules.modulesoptions.generatescopedname.md) | string \| ((name: string, filename: string, css: string) => string) | (Optional) To generate custom classes, use the generateScopedName callback, or just pass an interpolated string to the generateScopedName option: generateScopedName: "[name]__[local]___[hash:base64:5]" | 22 | | [globalModulePaths?](./postcss-es-modules.modulesoptions.globalmodulepaths.md) | Array<string \| RegExp> | (Optional) To define paths for global modules, use the globalModulePaths option. It is an array with regular expressions defining the paths: | 23 | | [hashPrefix?](./postcss-es-modules.modulesoptions.hashprefix.md) | string | (Optional) It's possible to add custom hash to generate more unique classes using the hashPrefix option. | 24 | | [localsConvention?](./postcss-es-modules.modulesoptions.localsconvention.md) | 'camelCase' \| 'camelCaseOnly' \| 'dashes' \| 'dashesOnly' \| ((originalClassName: string, generatedClassName: string, inputFile: string) => string) | (Optional) Style of exported classnames. | 25 | | [scopeBehaviour?](./postcss-es-modules.modulesoptions.scopebehaviour.md) | 'global' \| 'local' | (Optional) By default, the plugin assumes that all the classes are local. You can change this behaviour using the scopeBehaviour option. | 26 | 27 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/src/index.ts: -------------------------------------------------------------------------------- 1 | import postcss from "postcss"; 2 | import postcssrc from 'postcss-load-config'; 3 | import { env } from 'process'; 4 | 5 | type TransformSourceFn = ( 6 | originalSource: { toStrong(): string }, 7 | context: { url: string }, 8 | defaultTransformSource: TransformSourceFn 9 | ) => Promise<{ source: string }>; 10 | 11 | type GetFormatFn = ( 12 | url: string, 13 | context: { url: string }, 14 | defaultGetFormat: GetFormatFn 15 | ) => { format: string } 16 | 17 | const defaultPostCssNodeExtensions = ['.css', '.scss', '.sass', '.less', '.stylus']; 18 | const extensions: Array = env.POSTCSS_NODE_EXT?.split(',') || defaultPostCssNodeExtensions; 19 | let processOptions: ReturnType['options']; 20 | let processor: ReturnType; 21 | 22 | const useLoader = (url: string) => { 23 | return extensions.findIndex(ext => url.endsWith(ext)) > 0; 24 | } 25 | 26 | export const transformSource: TransformSourceFn = async ( 27 | originalSource, 28 | context, 29 | defaultTransformSource 30 | ) => { 31 | if (useLoader(context.url)) { 32 | 33 | const from = context.url.startsWith('file:///') ? context.url.substring(8) : context.url 34 | 35 | if (!processor) { 36 | const { plugins, options } = await postcssrc({}); 37 | processOptions = options; 38 | processor = postcss(plugins); 39 | } 40 | 41 | try { 42 | const result = await processor.process(originalSource.toString(), { 43 | ...(processOptions as any), 44 | from 45 | }); 46 | return { 47 | source: result.css 48 | } 49 | } catch (err) { 50 | console.error("postcss-node-loader: PostCSS compilation error"); 51 | console.error(err); 52 | throw err; 53 | } 54 | } 55 | 56 | return defaultTransformSource( 57 | originalSource, 58 | context, 59 | defaultTransformSource 60 | ); 61 | } 62 | 63 | export const getFormat: GetFormatFn = (url, context, defaultGetFormat) => { 64 | if (useLoader(url)) { 65 | return { 66 | format: "module", 67 | }; 68 | } else { 69 | return defaultGetFormat(url, context, defaultGetFormat); 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/plugin.ts: -------------------------------------------------------------------------------- 1 | import { isAbsolute } from 'path'; 2 | import postcss, { Plugin } from 'postcss'; 3 | import { eject } from './eject'; 4 | import { defaultOptions, Options } from './options'; 5 | import { createStringify } from './stringify'; 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | import postcssModules from 'postcss-modules'; 9 | 10 | /** 11 | * Postcss to es module plugin. 12 | * @internal 13 | */ 14 | export const plugin = (options: Options = {}): Plugin => { 15 | let ejected = false; 16 | 17 | // prepare options 18 | const opts = { 19 | modules: {...defaultOptions.modules, ...options.modules}, 20 | inject: {...defaultOptions.inject, ...options.inject} 21 | }; 22 | 23 | // return plugin body 24 | return { 25 | postcssPlugin: 'postcss-es-modules', 26 | OnceExit: async (css, { result }) => { 27 | // validate options 28 | if (options.inject?.script === 'eject' && !ejected) { 29 | ejected = true; 30 | if (!options.inject.scriptEjectPath) { 31 | throw 'The \'eject\' loader.script options requires also to set loader.scriptEjectPath.' + 32 | 'Please provide the path where the loader have to be ejected.'; 33 | } else if (!isAbsolute(options.inject.scriptEjectPath)) { 34 | throw 'The loader.scriptEjectPath has to be absolute path.'; 35 | } else { 36 | await eject(opts); 37 | } 38 | } 39 | 40 | // if no opts, create them 41 | /* istanbul ignore if */ 42 | if (!result.opts) { 43 | result.opts = {}; 44 | } 45 | // set custom stringifier 46 | result.opts.stringifier = createStringify(opts) 47 | // process css by the postcss-modules 48 | await postcss(postcssModules({ 49 | ...options.modules, 50 | getJSON: (cssFileName: string, map: Record) => { 51 | // so we are adding the comment with modules map 52 | css.append(postcss.comment({text: 'modules-map:' + JSON.stringify(map)})); 53 | } 54 | })).process(css, {from: result.opts.from}); 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /packages/css-es-modules/src/collect-styles.spec.ts: -------------------------------------------------------------------------------- 1 | import { collectStyles } from './collect-styles'; 2 | import { CSS_GLOBAL_KEY, CSS_LOCALS_KEY } from './inject-styles'; 3 | import { expect } from 'chai'; 4 | 5 | const key1 = 'key1'; 6 | const key2 = 'key2'; 7 | const styles1 = 'abc'; 8 | const styles2 = 'xyz'; 9 | const htmlRegExp = new RegExp(``, 's'); 10 | 11 | describe('collect-styles', () => { 12 | it('should collect global styles', () => { 13 | global[CSS_GLOBAL_KEY] = { 14 | [key1]: styles1 15 | }; 16 | const collector = collectStyles(); 17 | expect(collector.raw).contains(styles1); 18 | expect(collector.ids).eql({ 19 | [key1]: true, 20 | }); 21 | delete global[CSS_GLOBAL_KEY]; 22 | }); 23 | it('should collect local styles', () => { 24 | const collector = collectStyles(); 25 | global[CSS_LOCALS_KEY][key1] = styles1; 26 | expect(collector.raw).contains(styles1); 27 | expect(collector.ids).eql({ 28 | [key1]: true, 29 | }); 30 | delete global[CSS_LOCALS_KEY]; 31 | }); 32 | it('should collect local & global styles', () => { 33 | global[CSS_GLOBAL_KEY] = { 34 | [key2]: styles2 35 | }; 36 | const collector = collectStyles(); 37 | global[CSS_LOCALS_KEY][key1] = styles1; 38 | 39 | expect(collector.raw).contains(styles1); 40 | expect(collector.raw).contains(styles2); 41 | expect(collector.ids).eql({ 42 | [key1]: true, 43 | [key2]: true, 44 | }); 45 | delete global[CSS_LOCALS_KEY]; 46 | delete global[CSS_GLOBAL_KEY]; 47 | }); 48 | it('should deliver html', () => { 49 | global[CSS_GLOBAL_KEY] = { 50 | [key2]: styles2 51 | }; 52 | const collector = collectStyles(); 53 | global[CSS_LOCALS_KEY][key1] = styles1; 54 | expect(collector.html).match(htmlRegExp); 55 | const window = {}; 56 | (global as any)['window'] = window; 57 | eval(collector.html.match(htmlRegExp)[1]) 58 | expect(window[CSS_GLOBAL_KEY]).eql({ 59 | [key1]: true, 60 | [key2]: true, 61 | }); 62 | delete (global as any)['window']; 63 | delete global[CSS_LOCALS_KEY]; 64 | delete global[CSS_GLOBAL_KEY]; 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/builders/import-statement.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import { fake } from 'sinon' 3 | import sinonChai from 'sinon-chai' 4 | import { buildImportInjectStylesStatement } from './import-statement'; 5 | use(sinonChai); 6 | 7 | 8 | describe('import-statement', () => { 9 | it('without params, should generate esm import', () => { 10 | const builder = fake(); 11 | const node: any = {} 12 | buildImportInjectStylesStatement(node, builder); 13 | expect(builder).calledWith('import { injectStyles } from \'css-es-modules\';\n', node); 14 | }); 15 | it('should generate cjs import', () => { 16 | const builder = fake(); 17 | const node: any = {} 18 | buildImportInjectStylesStatement(node, builder, { moduleType: 'cjs' }); 19 | expect(builder).calledWith('const { injectStyles } = require(\'css-es-modules\');\n', node); 20 | }); 21 | 22 | it('should generate relative import if eject sibling', () => { 23 | const builder = fake(); 24 | const node: any = { source: { input: { file: '/a/b/d/a.js'}}} 25 | buildImportInjectStylesStatement(node, builder, { scriptEjectPath: '/a/b/c', script: 'eject' }); 26 | expect(builder).calledWith('import { injectStyles } from \'../c/inject-styles\';\n', node); 27 | }); 28 | 29 | it('should generate relative import if eject down', () => { 30 | const builder = fake(); 31 | const node: any = { source: { input: { file: '/a/b/d/a.js'}}} 32 | buildImportInjectStylesStatement(node, builder, { scriptEjectPath: '/a/b', script: 'eject' }); 33 | expect(builder).calledWith('import { injectStyles } from \'../inject-styles\';\n', node); 34 | }); 35 | 36 | it('should generate relative import if eject up', () => { 37 | const builder = fake(); 38 | const node: any = { source: { input: { file: '/a/b/c/a.js'}}} 39 | buildImportInjectStylesStatement(node, builder, { scriptEjectPath: '/a/b/c/d', script: 'eject' }); 40 | expect(builder).calledWith('import { injectStyles } from \'./d/inject-styles\';\n', node); 41 | }); 42 | 43 | it('should generate relative import if eject to same dir', () => { 44 | const builder = fake(); 45 | const node: any = { source: { input: { file: '/a/b/c/a.js'}}} 46 | buildImportInjectStylesStatement(node, builder, { scriptEjectPath: '/a/b/c', script: 'eject' }); 47 | expect(builder).calledWith('import { injectStyles } from \'./inject-styles\';\n', node); 48 | }); 49 | }) 50 | -------------------------------------------------------------------------------- /packages/postcss-node/test/test6.css: -------------------------------------------------------------------------------- 1 | .a { 2 | color: red; 3 | } 4 | .a { 5 | color: red; 6 | } 7 | .a { 8 | color: red; 9 | } 10 | .a { 11 | color: red; 12 | } 13 | .a { 14 | color: red; 15 | } 16 | .a { 17 | color: red; 18 | } 19 | .a { 20 | color: red; 21 | } 22 | .a { 23 | color: red; 24 | } 25 | .a { 26 | color: red; 27 | } 28 | .a { 29 | color: red; 30 | } 31 | .a { 32 | color: red; 33 | } 34 | .a { 35 | color: red; 36 | } 37 | .a { 38 | color: red; 39 | } 40 | .a { 41 | color: red; 42 | } 43 | .a { 44 | color: red; 45 | } 46 | .a { 47 | color: red; 48 | } 49 | .a { 50 | color: red; 51 | } 52 | .a { 53 | color: red; 54 | } 55 | .a { 56 | color: red; 57 | } 58 | .a { 59 | color: red; 60 | } 61 | .a { 62 | color: red; 63 | } 64 | .a { 65 | color: red; 66 | } 67 | .a { 68 | color: red; 69 | } 70 | .a { 71 | color: red; 72 | } 73 | .a { 74 | color: red; 75 | } 76 | .a { 77 | color: red; 78 | } 79 | .a { 80 | color: red; 81 | } 82 | .a { 83 | color: red; 84 | } 85 | .a { 86 | color: red; 87 | } 88 | .a { 89 | color: red; 90 | } 91 | .a { 92 | color: red; 93 | } 94 | .a { 95 | color: red; 96 | } 97 | .a { 98 | color: red; 99 | } 100 | .a { 101 | color: red; 102 | } 103 | .a { 104 | color: red; 105 | } 106 | .a { 107 | color: red; 108 | } 109 | .a { 110 | color: red; 111 | } 112 | .a { 113 | color: red; 114 | } 115 | .a { 116 | color: red; 117 | } 118 | .a { 119 | color: red; 120 | } 121 | .a { 122 | color: red; 123 | } 124 | .a { 125 | color: red; 126 | } 127 | .a { 128 | color: red; 129 | } 130 | .a { 131 | color: red; 132 | } 133 | .a { 134 | color: red; 135 | } 136 | .a { 137 | color: red; 138 | } 139 | .a { 140 | color: red; 141 | } 142 | .a { 143 | color: red; 144 | } 145 | .a { 146 | color: red; 147 | } 148 | .a { 149 | color: red; 150 | } 151 | .a { 152 | color: red; 153 | } 154 | .a { 155 | color: red; 156 | } 157 | .a { 158 | color: red; 159 | } 160 | .a { 161 | color: red; 162 | } 163 | .a { 164 | color: red; 165 | } 166 | .a { 167 | color: red; 168 | } 169 | .a { 170 | color: red; 171 | } 172 | .a { 173 | color: red; 174 | } 175 | .a { 176 | color: red; 177 | } 178 | .a { 179 | color: red; 180 | } 181 | .a { 182 | color: red; 183 | } 184 | .a { 185 | color: red; 186 | } 187 | .a { 188 | color: red; 189 | } 190 | .a { 191 | color: red; 192 | } 193 | .a { 194 | color: red; 195 | } 196 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/eject.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { existsSync, readFileSync } from 'fs'; 3 | import fsMock from 'mock-fs'; 4 | import { join } from 'path'; 5 | import { eject } from './eject'; 6 | 7 | describe('eject', () => { 8 | beforeEach(() => { 9 | const cssEsModulesPath = join(require.resolve('css-es-modules'), '../../../'); 10 | fsMock({ 11 | '/some/path': {}, 12 | '/some/pathWithFiles': { 13 | 'inject-styles.ts': 'file1', 14 | 'collect-styles.ts': 'file2' 15 | }, 16 | [cssEsModulesPath]: fsMock.load(cssEsModulesPath, { recursive: true, lazy: true}) 17 | }); 18 | }); 19 | afterEach(() => { 20 | fsMock.restore(); 21 | }) 22 | 23 | it('should eject ts code', async () => { 24 | await eject({ modules: {}, inject: { scriptType: 'ts', script: 'eject', scriptEjectPath: '/some/path' }}); 25 | expect(existsSync('/some/path/inject-styles.ts')).true 26 | expect(existsSync('/some/path/collect-styles.ts')).true 27 | }); 28 | 29 | it('should eject js code', async () => { 30 | await eject({ modules: {}, inject: { scriptType: 'js', script: 'eject', scriptEjectPath: '/some/path' }}); 31 | expect(existsSync('/some/path/inject-styles.js')).true 32 | expect(existsSync('/some/path/collect-styles.js')).true 33 | }); 34 | 35 | it('should eject js esm code', async () => { 36 | await eject({ modules: {}, inject: { scriptType: 'js', script: 'eject', moduleType: 'esm', scriptEjectPath: '/some/path' }}); 37 | expect(existsSync('/some/path/inject-styles.js')).true 38 | expect(existsSync('/some/path/collect-styles.js')).true 39 | }); 40 | 41 | it('should eject just once code', async () => { 42 | await Promise.all([ 43 | eject({ modules: {}, inject: { scriptType: 'ts', script: 'eject', scriptEjectPath: '/some/path' }}), 44 | eject({ modules: {}, inject: { scriptType: 'ts', script: 'eject', scriptEjectPath: '/some/path' }}) 45 | ]); 46 | expect(existsSync('/some/path/inject-styles.ts')).true 47 | expect(existsSync('/some/path/collect-styles.ts')).true 48 | }); 49 | 50 | it('should not overwrite', async () => { 51 | await eject({ modules: {}, inject: { scriptType: 'ts', script: 'eject', scriptEjectPath: '/some/pathWithFiles' }}); 52 | expect(readFileSync('/some/pathWithFiles/inject-styles.ts').toString('utf-8')).eq('file1'); 53 | expect(readFileSync('/some/pathWithFiles/collect-styles.ts').toString('utf-8')).eq('file2'); 54 | }); 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /docs/api/postcss-es-modules.extendedstylesinjectoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [postcss-es-modules](./postcss-es-modules.md) > [ExtendedStylesInjectOptions](./postcss-es-modules.extendedstylesinjectoptions.md) 4 | 5 | ## ExtendedStylesInjectOptions interface 6 | 7 | Styles loader options. 8 | 9 | Signature: 10 | 11 | ```typescript 12 | export interface ExtendedStylesInjectOptions extends StylesInjectOptions 13 | ``` 14 | Extends: StylesInjectOptions 15 | 16 | ## Properties 17 | 18 | | Property | Type | Description | 19 | | --- | --- | --- | 20 | | [custom?](./postcss-es-modules.extendedstylesinjectoptions.custom.md) | { importStatement: string; injectStatement: string; } | (Optional) Custom injection script. Use this property to use custom styles to DOM/Node injection. | 21 | | [injectMode?](./postcss-es-modules.extendedstylesinjectoptions.injectmode.md) | 'lazy' \| 'ondemand' \| 'instant' \| 'none' | (Optional) The mode of the styles injection. Options: - lazy - the stylesheet will be injected on the first use of the style class name within the code - ondemand - the stylesheet will be injected only when the styles.inject() method will be called - instant - the stylesheet will be injected on the module load - none - the stylesheet will be never injected, to inject it to the DOM you will need to import css content Default: 'lazy' | 22 | | [moduleType?](./postcss-es-modules.extendedstylesinjectoptions.moduletype.md) | 'esm' \| 'cjs' | (Optional) Generated code modules type. Options: - 'esm' - ecmascript 6 modules - 'cjs' - commonjsDefault 'esm' | 23 | | [script?](./postcss-es-modules.extendedstylesinjectoptions.script.md) | 'embed' \| 'eject' \| 'import' | (Optional) The way how the styles injector script will be referred from the generated source. Options: - embed: embedding loader script in the target source - eject: the loader script will be ejected to the provided scriptEjectPath - import: the loader script will be referred by the import statementDefault: 'import' | 24 | | [scriptEjectPath?](./postcss-es-modules.extendedstylesinjectoptions.scriptejectpath.md) | string | (Optional) The path where the script code will be ejected. This option is required if 'eject' value is set for the script type. | 25 | | [scriptType?](./postcss-es-modules.extendedstylesinjectoptions.scripttype.md) | 'ts' \| 'js' | (Optional) The script type. Options: - ts: typescript - js: javascriptDefault: 'js' | 26 | 27 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { launch, Page } from 'puppeteer'; 3 | import { server } from '../dist/index-server'; 4 | 5 | const getColorOfElement = async (page: Page, selector: string) => { 6 | const element = await page.$(selector); 7 | return page.evaluate(el => getComputedStyle(el).color, element); 8 | } 9 | 10 | const getAdoptedStyleSheetsCount = async (page: Page) => 11 | page.evaluate(() => document['adoptedStyleSheets'].length); 12 | 13 | 14 | const getStyleTag = async (page: Page) => { 15 | const styles = await page.$('style'); 16 | return page.evaluate(el => [ 17 | el.sheet.rules.length, 18 | el.sheet.rules[0]?.style?.color, 19 | el.sheet.rules[1]?.style?.color 20 | ], styles); 21 | }; 22 | 23 | 24 | (async () => { 25 | try { 26 | // running server 27 | await new Promise(res => server.on('listening', res)); 28 | 29 | // prepare browser 30 | const browser = await launch(); 31 | const page = await browser.newPage(); 32 | 33 | // testing of ssr of aComponent and csr of bComponent 34 | 35 | await page.goto(`http://localhost:3001`, {waitUntil: 'networkidle2'}); 36 | 37 | assert.deepStrictEqual(await getStyleTag(page), [1, 'blue', null], 38 | 'styles only for aComponent on the page'); 39 | 40 | assert.strictEqual(await getColorOfElement(page, 'div#aComponent'), 'rgb(0, 0, 255)', 41 | 'aComponent is blue'); 42 | 43 | assert.strictEqual(await getAdoptedStyleSheetsCount(page), 0, 44 | 'no adoptedStyleSheets as styles prerendered'); 45 | 46 | // opening second component 47 | const button = await page.$('button'); 48 | await button.click(); 49 | 50 | assert.strictEqual(await getAdoptedStyleSheetsCount(page), 1, 51 | 'added adoptedStyleSheets'); 52 | 53 | assert.strictEqual(await getColorOfElement(page, 'div#bComponent'), 'rgb(255, 0, 0)', 54 | 'bComponent is red'); 55 | 56 | 57 | // testing of ssr of aComponent and bComponent 58 | 59 | await page.goto(`http://localhost:3001?showBComponent=true`, {waitUntil: 'networkidle2'}); 60 | 61 | assert.deepStrictEqual(await getStyleTag(page), [2, 'blue', 'red'], 62 | 'styles for aComponent and bComponent are on the page'); 63 | 64 | assert.strictEqual(await getColorOfElement(page, 'div#aComponent'), 'rgb(0, 0, 255)', 65 | 'aComponent is blue'); 66 | 67 | assert.strictEqual(await getColorOfElement(page, 'div#bComponent'), 'rgb(255, 0, 0)', 68 | 'bComponent is red'); 69 | 70 | assert.strictEqual(await getAdoptedStyleSheetsCount(page), 0, 71 | 'no adoptedStyleSheets as styles prerendered'); 72 | 73 | await browser.close(); 74 | server.close(); 75 | } catch (ex) { 76 | console.error(ex); 77 | process.exit(1); 78 | } 79 | process.exit(0); 80 | })(); 81 | -------------------------------------------------------------------------------- /docs/examples/react-ssr-webpack-typescript/README.md: -------------------------------------------------------------------------------- 1 | # React example 2 | Example of usage postcss-es-modules with the React library, typescript and webpack. 3 | 4 | This is example of small application with the server and client side rendering. 5 | In this example we have two components, we can prerender both or the single component 6 | on server side, and we are sure that only referred styles are prerendered on server side. 7 | We can also run the second component on client side, and then the component will 8 | inject own styles it to the DOM. 9 | 10 | This example is using webpack and typescript. 11 | 12 | ## running 13 | ```bash 14 | yarn run 15 | ``` 16 | 17 | ## project structure 18 | ``` 19 | ├── src 20 | │ ├── app // isomorphic application code 21 | │ │ ├── components // ui components 22 | │ │ │ ├── a.component.css // styles of component A 23 | │ │ │ ├── a.component.tsx // component A 24 | │ │ │ ├── b.component.css // styles of component B 25 | │ │ │ └── b.component.tsx // component B 26 | │ │ └── app.component.tsx // application component 27 | │ ├── global.d.ts // global types declaration 28 | │ ├── index.scss // scss source code 29 | │ ├── index-client.ts // client side entry point 30 | │ └── index-server.ts // server side entry point 31 | ├── package.json // project package.json 32 | ├── tsconfig.json // minimal typescript config 33 | └── webpack.config.js // minimal webpack config 34 | ``` 35 | 36 | ## ./src/app/components/a.component.css 37 | 38 | [./src/app/components/a.component.css](./src/app/components/a.component.css ':include :type=code') 39 | 40 | ## ./src/app/components/a.component.tsx 41 | 42 | [./src/app/components/a.component.tsx](./src/app/components/a.component.tsx ':include :type=code') 43 | 44 | ## ./src/app/components/b.component.css 45 | 46 | [./src/app/components/b.component.css](./src/app/components/b.component.css ':include :type=code') 47 | 48 | ## ./src/app/components/b.component.tsx 49 | 50 | [./src/app/components/b.component.tsx](./src/app/components/b.component.tsx ':include :type=code') 51 | 52 | ## ./src/app/app.component.tsx 53 | 54 | [./src/app/app.component.tsx](./src/app/app.component.tsx ':include :type=code') 55 | 56 | ## ./src/global.d.ts 57 | 58 | [./src/global.d.ts ](./src/global.d.ts ':include :type=code') 59 | 60 | 61 | ## ./src/index-client.tsx 62 | 63 | [./src/index-client.tsx](./src/index-client.tsx ':include :type=code') 64 | 65 | ## ./src/index-server.tsx 66 | 67 | [./src/index-server.tsx](./src/index-server.tsx ':include :type=code') 68 | 69 | 70 | 71 | ## ./package.json 72 | 73 | [./package.json](./package.json ':include :type=code') 74 | 75 | > Please notice the settings for the plugin. As we are using the lit-element build-in styles injection 76 | > we are using injection mode `none`. 77 | 78 | ## ./tsconfig.json 79 | 80 | [./tsconfig.json](./tsconfig.json ':include :type=code') 81 | 82 | ## ./webpack.config.js 83 | 84 | [./webpack.config.js](./webpack.config.js ':include :type=code') 85 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/builders/styles-statement.ts: -------------------------------------------------------------------------------- 1 | import { Builder, Root } from 'postcss'; 2 | 3 | function getResolvedClassName(classMap: Record, className: string, attachOriginalClassName: boolean) { 4 | return `${classMap[className]}${attachOriginalClassName ? ` ${className}` : ''}`; 5 | } 6 | 7 | function getInjectStatement(injectStatement: string | undefined, hasRuntimeOptions: boolean, shadowRoot: boolean) { 8 | if (injectStatement === undefined) { 9 | injectStatement = `injectStyles(key, css, ${hasRuntimeOptions ? 'options' : 'undefined'}, undefined, ${shadowRoot ? 'shadowRoot' : 'undefined'});`; 10 | } else { 11 | injectStatement += ';'; 12 | } 13 | return injectStatement; 14 | } 15 | 16 | /** 17 | * Build the styles statement with lazy loading. 18 | */ 19 | export const buildLazyStylesStatement = ( 20 | builder: Builder, 21 | node: Root, 22 | classMap: Record, 23 | injectStatement: string | undefined, 24 | attachOriginalClassName: boolean, 25 | hasRuntimeOptions: boolean): void => { 26 | 27 | const injectStatement1 = getInjectStatement(injectStatement, hasRuntimeOptions, false); 28 | 29 | builder(`const styles = {\n`, node); 30 | // styles class names map 31 | Object.keys(classMap).forEach((className) => { 32 | builder(` get ['${className}']() { ${injectStatement1} ` 33 | + ` return '${getResolvedClassName(classMap, className, attachOriginalClassName)}'; },\n`, node); 34 | }); 35 | 36 | const injectStatement2 = getInjectStatement(injectStatement, hasRuntimeOptions, true); 37 | 38 | // inject method 39 | builder(` inject(shadowRoot) { ${injectStatement2} }\n`, node); 40 | builder(`};\n`, node); 41 | 42 | }; 43 | 44 | /** 45 | * Build the styles statement with on demand style loading. 46 | */ 47 | export const buildOnDemandStylesStatement = ( 48 | builder: Builder, 49 | node: Root, 50 | classMap: Record, 51 | injectStatement: string | undefined, 52 | attachOriginalClassName: boolean, 53 | hasRuntimeOptions: boolean): void => { 54 | 55 | injectStatement = getInjectStatement(injectStatement, hasRuntimeOptions, true); 56 | 57 | builder(`const styles = {\n`, node); 58 | // styles class names map 59 | Object.keys(classMap).forEach((className) => { 60 | builder(` ['${className}']: '${getResolvedClassName(classMap, className, attachOriginalClassName)}',\n`, node); 61 | }); 62 | // inject method 63 | builder(` inject(shadowRoot) { ${injectStatement} }\n`, node); 64 | builder(`};\n`, node); 65 | 66 | }; 67 | 68 | /** 69 | * Build the styles statement with on instant style loading. 70 | */ 71 | export const buildInstantStylesStatement = ( 72 | builder: Builder, 73 | node: Root, 74 | classMap: Record, 75 | injectStatement: string | undefined, 76 | attachOriginalClassName: boolean, 77 | hasRuntimeOptions: boolean): void => { 78 | 79 | buildOnDemandStylesStatement( 80 | builder, node, classMap, injectStatement, attachOriginalClassName, hasRuntimeOptions); 81 | builder(`styles.inject();\n`, node); 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /docs/css-es-modules/README.md: -------------------------------------------------------------------------------- 1 | # css-es-modules 2 | 3 | Helper package which is responsible for isomorphic css styles injection. 4 | This package is used by the [postcss-es-modules](https://github.com/majo44/postcss-es-modules) 5 | 6 | ## Overview 7 | This package provides two functions `injectStyles` and `collectStyles`. `injectStyles` function 8 | is responsible for attach provided css string into the DOM on browser side, or it is storing 9 | the css string within the node.js globals for the server side rendering. `collectStyles` function 10 | is responsible for collecting all used css strings within the container objects, which 11 | can be used for taking the styles and embed in to the server response on the server side rendering. 12 | 13 | ## Features 14 | * Framework agnostic 15 | * Javascript and Typescript support 16 | * Server side rendering 17 | * Lazy, on demand or instant styles injection 18 | 19 | ## Installation 20 | 21 | ```bash 22 | npm i css-es-modules 23 | ``` 24 | 25 | ## Usage 26 | 27 | ### Simple styles injection in to the DOM 28 | ```javascript 29 | import { injectStyles } from 'css-es-modules'; 30 | // injecting styles to DOM 31 | injectStyles('uniqueStylesheetKey', '.component { color: blue; }'); 32 | ``` 33 | ### Usage with React 34 | 35 | #### Component code 36 | 37 | ```javascript 38 | import { injectStyles } from 'css-es-modules'; 39 | import { useEffect, } from 'react'; 40 | 41 | // css content 42 | const css = '.component { color: blue; }'; 43 | 44 | /** 45 | * React Component. 46 | */ 47 | export const Component = () => { 48 | // you can call injectStyles multiple times, for same key it will inject the styles once 49 | injectStyles('uniqueStylesheetKey', css); 50 | return
Component
51 | } 52 | ``` 53 | 54 | #### Server side rendering 55 | 56 | ```javascript 57 | import express from 'express'; 58 | import { collectStyles } from 'css-es-modules'; 59 | import { renderToString } from 'react-dom/server'; 60 | import { Component } from '...'; 61 | 62 | // Express application 63 | const app = express(); 64 | 65 | /** 66 | * App html template 67 | * @param styles - the StylesCollector object 68 | * @param html - prerendered app markup 69 | */ 70 | const template = (styles, html) => ` 71 | 72 | ${ styles.html } 73 |
${ html }
74 | `; 75 | 76 | // handling request 77 | app.use((req, res) => { 78 | res.send( 79 | // render template 80 | template( 81 | // firstly start collecting styles 82 | collectStyles(), 83 | // then render application 84 | renderToString(createElement(App)))); 85 | }); 86 | 87 | // starting app 88 | app.listen(3000); 89 | ``` 90 | ## Options 91 | The injectStyles function is taking third optional parameter which is an object with fallowing 92 | properties: 93 | 94 | | Option | Type | Default | Description | 95 | | --- | --- | --- | --- | 96 | | `useNounce` | `string` | - | The style nounce key | 97 | | `useConstructableStylesheet` | `boolean` | true | Use Constructable Stylesheet for styles injection to DOM if the browser supports Constructable Stylesheet | 98 | | `useStyleTag` | `boolean` | true | Use \ tag for styles injection to DOM | 99 | | `useNodeGlobal` | `boolean` | true | Enable node.js global for collecting the styles, required for server side rendering | 100 | -------------------------------------------------------------------------------- /docs/postcss-node/README.md: -------------------------------------------------------------------------------- 1 | # postcss-node 2 | 3 | Package which allows import of css files under the node.js eg `require('styles.css')` 4 | This package should be used together with [postcss-es-modules](https://github.com/majo44/postcss-es-modules) 5 | 6 | ## Overview 7 | By using `require.extensions` this package register the handlers which on the fly, on runtime, 8 | for each `.css` file require is transpiling the css content by using [postcss](https://postcss.org/). 9 | To be able to load the `.css` file as javascript module, you should use 10 | [postcss-es-modules](https://github.com/majo44/postcss-es-modules) post css plugin. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm i postcss-node postcss-es-modules 16 | ``` 17 | 18 | ## Usage 19 | 20 | Firstly we need to provide the proper postcss configuration, you can add it to your package.json, or 21 | keep it in separate file eg: 22 | ```javascript 23 | /* postcss.config.js */ 24 | module.exports = { 25 | "plugins": { 26 | // this plugin will transform css into the js module 27 | "postcss-es-modules": { 28 | "inject": { 29 | // we will use the common js modules 30 | "moduleType": "cjs" 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ### Usage on the cli 38 | You can register the `.css/.sass` files handle just by the `-r` node.js option. 39 | ```bash 40 | node -r postcss-node/register test.js 41 | ``` 42 | ```javascript 43 | /* test.js */ 44 | const { styles, css } = require('./test.css'); 45 | console.log(css); 46 | console.log(styles.a); 47 | ``` 48 | ```css 49 | /* test.css */ 50 | .a { 51 | color: blue; 52 | } 53 | ``` 54 | 55 | ### Usage within the code 56 | Or you can register the handle manually within the code. 57 | 58 | ```bash 59 | node test.js 60 | ``` 61 | ```javascript 62 | /* test.js */ 63 | const { register } = require('postcss-node'); 64 | register(); 65 | const { styles, css } = require('./test.css'); 66 | console.log(css); 67 | console.log(styles.a); 68 | ``` 69 | ```css 70 | /* test.css */ 71 | .a { 72 | color: blue; 73 | } 74 | ``` 75 | 76 | ### Custom files extensions 77 | The `register` function is able to take the optional parameter, which will be the array of the 78 | file extensions which should be handled by that package. By default, `register` is 79 | attaching handlers for the '.css', '.scss', '.sass', '.less', '.stylus'. 80 | ```bash 81 | node test.js 82 | ``` 83 | ```javascript 84 | /* test.js */ 85 | const { register } = require('postcss-node'); 86 | register(['.acss']); 87 | const { styles, css } = require('./test.acss'); 88 | console.log(css); 89 | console.log(styles.a); 90 | ``` 91 | ```css 92 | /* test.acss */ 93 | .a { 94 | color: blue; 95 | } 96 | ``` 97 | 98 | > You can also provide the custom extensions by the `POSTCSS_NODE_EXT` environment variable like: 99 | > `cross-env POSTCSS_NODE_EXT=.acss,.bcss,.css` 100 | 101 | ### Other options 102 | The `register` function is able to take the two more optional parameter 103 | * timeout (`POSTCSS_NODE_TIMEOUT`) - the timeout of css processing, default 60000 ms 104 | * bufferSize (`POSTCSS_NODE_BUFFER_SIZE`) - the size of buffer used to transfer compiled code from 105 | the worker, in case of `RangeError [ERR_BUFFER_OUT_OF_BOUNDS]: "length" is outside of buffer bounds` 106 | error please increase this parameter, default 1024 kB 107 | 108 | ## Need a help ? 109 | If you have any problems, issues, ect. please use [github discussions](https://github.com/majo44/postcss-es-modules/discussions). 110 | -------------------------------------------------------------------------------- /packages/postcss-node/README.md: -------------------------------------------------------------------------------- 1 | # postcss-node 2 | 3 | Package which allows import of css files under the node.js eg `require('styles.css')` 4 | This package should be used together with [postcss-es-modules](https://github.com/majo44/postcss-es-modules) 5 | 6 | ## Overview 7 | By using `require.extensions` this package register the handlers which on the fly, on runtime, 8 | for each `.css` file require is transpiling the css content by using [postcss](https://postcss.org/). 9 | To be able to load the `.css` file as javascript module, you should use 10 | [postcss-es-modules](https://github.com/majo44/postcss-es-modules) post css plugin. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm i postcss-node postcss-es-modules 16 | ``` 17 | 18 | ## Usage 19 | 20 | Firstly we need to provide the proper postcss configuration, you can add it to your package.json, or 21 | keep it in separate file eg: 22 | ```javascript 23 | /* postcss.config.js */ 24 | module.exports = { 25 | "plugins": { 26 | // this plugin will transform css into the js module 27 | "postcss-es-modules": { 28 | "inject": { 29 | // we will use the common js modules 30 | "moduleType": "cjs" 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ### Usage on the cli 38 | You can register the `.css/.sass` files handle just by the `-r` node.js option. 39 | ```bash 40 | node -r postcss-node/register test.js 41 | ``` 42 | ```javascript 43 | /* test.js */ 44 | const { styles, css } = require('./test.css'); 45 | console.log(css); 46 | console.log(styles.a); 47 | ``` 48 | ```css 49 | /* test.css */ 50 | .a { 51 | color: blue; 52 | } 53 | ``` 54 | 55 | ### Usage within the code 56 | Or you can register the handle manually within the code. 57 | 58 | ```bash 59 | node test.js 60 | ``` 61 | ```javascript 62 | /* test.js */ 63 | const { register } = require('postcss-node'); 64 | register(); 65 | const { styles, css } = require('./test.css'); 66 | console.log(css); 67 | console.log(styles.a); 68 | ``` 69 | ```css 70 | /* test.css */ 71 | .a { 72 | color: blue; 73 | } 74 | ``` 75 | 76 | ### Custom files extensions 77 | The `register` function is able to take the optional parameter, which will be the array of the 78 | file extensions which should be handled by that package. By default, `register` is 79 | attaching handlers for the '.css', '.scss', '.sass', '.less', '.stylus'. 80 | ```bash 81 | node test.js 82 | ``` 83 | ```javascript 84 | /* test.js */ 85 | const { register } = require('postcss-node'); 86 | register(['.acss', '.bcss', '.css']); 87 | const { styles, css } = require('./test.acss'); 88 | console.log(css); 89 | console.log(styles.a); 90 | ``` 91 | ```css 92 | /* test.acss */ 93 | .a { 94 | color: blue; 95 | } 96 | ``` 97 | > You can also provide the custom extensions by the `POSTCSS_NODE_EXT` environment variable like: 98 | > `cross-env POSTCSS_NODE_EXT=.acss,.bcss,.css` 99 | 100 | ### Other options 101 | The `register` function is able to take the two more optional parameter 102 | * timeout (`POSTCSS_NODE_TIMEOUT`) - the timeout of css processing, default 60000 ms 103 | * bufferSize (`POSTCSS_NODE_BUFFER_SIZE`) - the size of buffer used to transfer compiled code from 104 | the worker, in case of `RangeError [ERR_BUFFER_OUT_OF_BOUNDS]: "length" is outside of buffer bounds` 105 | error please increase this parameter, default 1024 kB 106 | 107 | ## Need a help ? 108 | If you have any problems, issues, ect. please use [github discussions](https://github.com/majo44/postcss-es-modules/discussions). 109 | -------------------------------------------------------------------------------- /packages/css-es-modules/README.md: -------------------------------------------------------------------------------- 1 | # css-es-modules 2 | 3 | Helper package which is responsible for isomorphic css styles injection. 4 | This package is used by the [postcss-es-modules](https://github.com/majo44/postcss-es-modules) 5 | 6 | ## Overview 7 | This package provides two functions `injectStyles` and `collectStyles`. `injectStyles` function 8 | is responsible for attach provided css string into the DOM on browser side, or it is storing 9 | the css string within the node.js globals for the server side rendering. `collectStyles` function 10 | is responsible for collecting all used css strings within the container objects, which 11 | can be used for taking the styles and embed in to the server response on the server side rendering. 12 | 13 | ## Features 14 | * Framework agnostic 15 | * Javascript and Typescript support 16 | * Server side rendering 17 | * Lazy, on demand or instant styles injection 18 | 19 | ## Installation 20 | 21 | ```bash 22 | npm i css-es-modules 23 | ``` 24 | 25 | ## Usage 26 | 27 | ### Simple styles injection in to the DOM 28 | ```javascript 29 | import { injectStyles } from 'css-es-modules'; 30 | // injecting styles to DOM 31 | injectStyles('uniqueStylesheetKey', '.component { color: blue; }'); 32 | ``` 33 | ### Usage with React 34 | 35 | #### Component code 36 | 37 | ```javascript 38 | import { injectStyles } from 'css-es-modules'; 39 | import { useEffect, } from 'react'; 40 | 41 | // css content 42 | const css = '.component { color: blue; }'; 43 | 44 | /** 45 | * React Component. 46 | */ 47 | export const Component = () => { 48 | // you can call injectStyles multiple times, for same key it will inject the styles once 49 | injectStyles('uniqueStylesheetKey', css); 50 | return
Component
51 | } 52 | ``` 53 | 54 | #### Server side rendering 55 | 56 | ```javascript 57 | import express from 'express'; 58 | import { collectStyles } from 'css-es-modules'; 59 | import { renderToString } from 'react-dom/server'; 60 | import { Component } from '...'; 61 | 62 | // Express application 63 | const app = express(); 64 | 65 | /** 66 | * App html template 67 | * @param styles - the StylesCollector object 68 | * @param html - prerendered app markup 69 | */ 70 | const template = (styles, html) => ` 71 | 72 | ${ styles.html } 73 |
${ html }
74 | `; 75 | 76 | // handling request 77 | app.use((req, res) => { 78 | res.send( 79 | // render template 80 | template( 81 | // firstly start collecting styles 82 | collectStyles(), 83 | // then render application 84 | renderToString(createElement(App)))); 85 | }); 86 | 87 | // starting app 88 | app.listen(3000); 89 | ``` 90 | ## Options 91 | The injectStyles function is taking third optional parameter which is an object with fallowing 92 | properties: 93 | 94 | | Option | Type | Default | Description | 95 | | --- | --- | --- | --- | 96 | | `useNounce` | `string` | - | The style nounce key | 97 | | `useConstructableStylesheet` | `boolean` | true | Use Constructable Stylesheet for styles injection to DOM if the browser supports Constructable Stylesheet | 98 | | `useStyleTag` | `boolean` | true | Use \ tag for styles injection to DOM | 99 | | `useNodeGlobal` | `boolean` | true | Enable node.js global for collecting the styles, required for server side rendering | 100 | 101 | ## Next steps 102 | For more information please go to the [api reference](./api/index.md) documentation. 103 | 104 | ## Need a help ? 105 | If you have any problems, issues, ect. please use [github discussions](https://github.com/majo44/postcss-es-modules/discussions). 106 | -------------------------------------------------------------------------------- /packages/css-es-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-es-modules", 3 | "version": "1.1.0", 4 | "description": "Universal styles injection to work with postcss-es-modules", 5 | "keywords": [ 6 | "css-in-js", 7 | "css modules" 8 | ], 9 | "bugs": { 10 | "url": "https://github.com/majo44/postcss-es-modules/issues" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/majo44/postcss-es-modules.git" 15 | }, 16 | "license": "MIT", 17 | "author": { 18 | "name": "Paweł Majewski", 19 | "email": "majo44@gmail.com" 20 | }, 21 | "sideEffects": false, 22 | "main": "./dist/dist-cjs/index.js", 23 | "module": "./dist/dist-esm/index.js", 24 | "types": "./dist/dist-types/index.d.ts", 25 | "files": [ 26 | "dist", 27 | "src" 28 | ], 29 | "scripts": { 30 | "api": "api-extractor run --local && api-documenter markdown -i ./temp -o ./temp/api && copyfiles ./temp/api/*.* ../../docs/api -f", 31 | "api:update": "yarn compile:cjs && yarn api", 32 | "build": "yarn clean && yarn lint && yarn test:coverage && yarn compile && yarn api", 33 | "clean": "rimraf dist coverage temp", 34 | "compile": "yarn compile:cjs && yarn compile:esm", 35 | "compile:cjs": "tsc --outDir dist/dist-cjs --module CommonJS", 36 | "compile:esm": "tsc --outDir dist/dist-esm --module ESNext", 37 | "lint": "eslint ./src/**/*.ts --fix", 38 | "prepublishOnly": "yarn build", 39 | "test": "mocha src/**/*.ts", 40 | "test:coverage": "nyc yarn test", 41 | "test:watch": "yarn test --watch" 42 | }, 43 | "eslintConfig": { 44 | "parser": "@typescript-eslint/parser", 45 | "plugins": [ 46 | "@typescript-eslint", 47 | "eslint-plugin-tsdoc" 48 | ], 49 | "extends": [ 50 | "eslint:recommended", 51 | "plugin:@typescript-eslint/recommended" 52 | ], 53 | "rules": { 54 | "tsdoc/syntax": "error" 55 | }, 56 | "overrides": [ 57 | { 58 | "files": ["*.spec.ts"], 59 | "rules": { 60 | "@typescript-eslint/no-explicit-any": 0 61 | } 62 | } 63 | ] 64 | }, 65 | "mocha": { 66 | "require": [ 67 | "ts-node/register" 68 | ], 69 | "watchFiles": [ 70 | "test/**/*.ts", 71 | "src/**/*.ts" 72 | ] 73 | }, 74 | "nyc": { 75 | "all": true, 76 | "branches": 1, 77 | "check-coverage": true, 78 | "exclude": [ 79 | "src/**/*.spec.ts" 80 | ], 81 | "extension": [ 82 | ".ts" 83 | ], 84 | "functions": 1, 85 | "include": [ 86 | "src/**/*.ts" 87 | ], 88 | "lines": 1, 89 | "reporter": [ 90 | "text-summary", 91 | "html", 92 | "lcovonly" 93 | ], 94 | "statements": 1 95 | }, 96 | "devDependencies": { 97 | "@microsoft/api-documenter": "^7.12.1", 98 | "@microsoft/api-extractor": "^7.12.1", 99 | "@types/chai": "^4.2.14", 100 | "@types/jsdom": "^16.2.6", 101 | "@types/mocha": "^8.2.0", 102 | "@types/node": "^16.11.26", 103 | "@typescript-eslint/eslint-plugin": "^4.11.1", 104 | "@typescript-eslint/parser": "^4.11.1", 105 | "chai": "^4.2.0", 106 | "copyfiles": "^2.4.1", 107 | "eslint": "^7.16.0", 108 | "eslint-plugin-tsdoc": "^0.2.10", 109 | "jsdom": "^16.4.0", 110 | "mocha": "^8.2.1", 111 | "nyc": "^15.1.0", 112 | "replace-in-files-cli": "^1.0.0", 113 | "rimraf": "^3.0.2", 114 | "ts-mocha": "^8.0.0", 115 | "tslib": "^2.1.0", 116 | "typescript": "^4.1.3" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/postcss-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-node", 3 | "version": "2.0.2", 4 | "description": "Package which allows import of css/scss/lass/.. files under the node.js eg `require('styles.css')`", 5 | "keywords": [ 6 | "css-in-js", 7 | "css modules" 8 | ], 9 | "bugs": { 10 | "url": "https://github.com/majo44/postcss-es-modules/issues" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/majo44/postcss-es-modules.git" 15 | }, 16 | "license": "MIT", 17 | "author": { 18 | "name": "Paweł Majewski", 19 | "email": "majo44@gmail.com" 20 | }, 21 | "sideEffects": false, 22 | "main": "./index.js", 23 | "module": "./dist/dist-esm/index.js", 24 | "types": "./dist/dist-types/index.d.ts", 25 | "files": [ 26 | "dist", 27 | "register.js", 28 | "index.js" 29 | ], 30 | "scripts": { 31 | "api": "api-extractor run --local && api-documenter markdown -i ./temp -o ./temp/api && copyfiles ./temp/api/*.* ../../docs/api -f", 32 | "api:update": "yarn compile:cjs && yarn api", 33 | "build": "yarn clean && yarn lint && yarn test:coverage && yarn compile && yarn api", 34 | "clean": "rimraf dist coverage temp", 35 | "compile": "yarn compile:cjs && yarn compile:esm", 36 | "compile:cjs": "tsc --outDir dist/dist-cjs --module CommonJS", 37 | "compile:esm": "tsc --outDir dist/dist-esm --module ESNext", 38 | "lint": "eslint ./src/**/*.ts --fix", 39 | "prepublishOnly": "yarn build", 40 | "test": "mocha test/**/*.ts src/**/*.ts --timeout 5000", 41 | "test:coverage": "nyc yarn test", 42 | "test:watch": "yarn test --watch" 43 | }, 44 | "eslintConfig": { 45 | "ignorePatterns": ["*.js"], 46 | "parser": "@typescript-eslint/parser", 47 | "plugins": [ 48 | "@typescript-eslint", 49 | "eslint-plugin-tsdoc" 50 | ], 51 | "extends": [ 52 | "eslint:recommended", 53 | "plugin:@typescript-eslint/recommended" 54 | ], 55 | "rules": { 56 | "tsdoc/syntax": "error" 57 | }, 58 | "overrides": [ 59 | { 60 | "files": ["*.spec.ts"], 61 | "rules": { 62 | "@typescript-eslint/no-explicit-any": 0 63 | } 64 | } 65 | ] 66 | }, 67 | "mocha": { 68 | "require": [ 69 | "ts-node/register" 70 | ], 71 | "watchFiles": [ 72 | "test/**/*.ts", 73 | "src/**/*.ts" 74 | ] 75 | }, 76 | "nyc": { 77 | "all": true, 78 | "branches": 1, 79 | "check-coverage": true, 80 | "exclude": [ 81 | "src/**/*.spec.ts" 82 | ], 83 | "extension": [ 84 | ".ts" 85 | ], 86 | "functions": 1, 87 | "include": [ 88 | "src/**/*.ts" 89 | ], 90 | "lines": 1, 91 | "reporter": [ 92 | "text-summary", 93 | "html", 94 | "lcovonly" 95 | ], 96 | "statements": 1 97 | }, 98 | "dependencies": { 99 | "postcss-load-config": "^3.0.1", 100 | "tslib": "^2.1.0" 101 | }, 102 | "devDependencies": { 103 | "postcss-es-modules": "^2.0.2", 104 | "postcss": "^8.2.13", 105 | "@types/chai": "^4.2.14", 106 | "@types/mocha": "^8.2.0", 107 | "@types/node": "^16.11.26", 108 | "@typescript-eslint/eslint-plugin": "^4.11.1", 109 | "@typescript-eslint/parser": "^4.11.1", 110 | "chai": "^4.2.0", 111 | "copyfiles": "^2.4.1", 112 | "eslint": "^7.16.0", 113 | "eslint-plugin-tsdoc": "^0.2.10", 114 | "mocha": "^8.2.1", 115 | "nyc": "^15.1.0", 116 | "rimraf": "^3.0.2", 117 | "ts-node": "^9.1.1", 118 | "typescript": "^4.1.3", 119 | "mock-require": "^3.0.3", 120 | "@types/mock-require": "^2.0.0" 121 | }, 122 | "peerDependencies": { 123 | "postcss": "^8.0.0" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packages/postcss-node-loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-node-loader", 3 | "version": "0.10.1", 4 | "description": "Node.js loader which use postcss for modules transforming", 5 | "type": "module", 6 | "keywords": [ 7 | "css-in-js", 8 | "css modules" 9 | ], 10 | "bugs": { 11 | "url": "https://github.com/majo44/postcss-es-modules/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/majo44/postcss-es-modules.git" 16 | }, 17 | "license": "MIT", 18 | "author": { 19 | "name": "Paweł Majewski", 20 | "email": "majo44@gmail.com" 21 | }, 22 | "sideEffects": false, 23 | "main": "./index.js", 24 | "module": "./dist/dist-esm/index.js", 25 | "types": "./dist/dist-types/index.d.ts", 26 | "files": [ 27 | "dist", 28 | "index.js" 29 | ], 30 | "scripts": { 31 | "api": "api-extractor run --local && api-documenter markdown -i ./temp -o ./temp/api && copyfiles ./temp/api/*.* ../../docs/api -f", 32 | "api:update": "yarn compile:cjs && yarn api", 33 | "build": "yarn clean && yarn lint && yarn compile && yarn api", 34 | "clean": "rimraf dist coverage", 35 | "compile": "yarn compile:esm", 36 | "compile:esm": "tsc --outDir dist/dist-esm --module ESNext", 37 | "lint": "eslint ./src/**/*.ts --fix", 38 | "prepublishOnly": "yarn build" 39 | }, 40 | "eslintConfig": { 41 | "ignorePatterns": [ 42 | "*.js" 43 | ], 44 | "parser": "@typescript-eslint/parser", 45 | "plugins": [ 46 | "@typescript-eslint", 47 | "eslint-plugin-tsdoc" 48 | ], 49 | "extends": [ 50 | "eslint:recommended", 51 | "plugin:@typescript-eslint/recommended" 52 | ], 53 | "rules": { 54 | "tsdoc/syntax": "error" 55 | }, 56 | "overrides": [ 57 | { 58 | "files": [ 59 | "*.spec.ts" 60 | ], 61 | "rules": { 62 | "@typescript-eslint/no-explicit-any": 0 63 | } 64 | } 65 | ] 66 | }, 67 | "mocha": { 68 | "require": [ 69 | "ts-node/register" 70 | ], 71 | "watchFiles": [ 72 | "test/**/*.ts", 73 | "src/**/*.ts" 74 | ] 75 | }, 76 | "nyc": { 77 | "all": true, 78 | "branches": 1, 79 | "check-coverage": true, 80 | "exclude": [ 81 | "src/**/*.spec.ts" 82 | ], 83 | "extension": [ 84 | ".ts" 85 | ], 86 | "functions": 1, 87 | "include": [ 88 | "src/**/*.ts" 89 | ], 90 | "lines": 1, 91 | "reporter": [ 92 | "text-summary", 93 | "html", 94 | "lcovonly" 95 | ], 96 | "statements": 1 97 | }, 98 | "dependencies": { 99 | "postcss-load-config": "^3.0.1", 100 | "tslib": "^2.1.0" 101 | }, 102 | "devDependencies": { 103 | "@microsoft/api-documenter": "^7.12.1", 104 | "@microsoft/api-extractor": "^7.12.1", 105 | "@types/chai": "^4.2.14", 106 | "@types/md5": "^2.3.0", 107 | "@types/mocha": "^8.2.0", 108 | "@types/node": "^16.11.26", 109 | "@types/sinon": "^9.0.10", 110 | "@types/sinon-chai": "^3.2.5", 111 | "@typescript-eslint/eslint-plugin": "^4.11.1", 112 | "@typescript-eslint/parser": "^4.11.1", 113 | "chai": "^4.2.0", 114 | "copyfiles": "^2.4.1", 115 | "eslint": "^7.16.0", 116 | "eslint-plugin-tsdoc": "^0.2.10", 117 | "mocha": "^8.2.1", 118 | "nyc": "^15.1.0", 119 | "replace-in-files-cli": "^1.0.0", 120 | "rimraf": "^3.0.2", 121 | "sinon": "^9.2.4", 122 | "sinon-chai": "^3.5.0", 123 | "ts-node": "^9.1.1", 124 | "typescript": "^4.1.3", 125 | "mock-fs": "^4.13.0", 126 | "@types/mock-fs": "^4.13.0", 127 | "require-from-string": "^2.0.2", 128 | "@types/require-from-string": "^1.2.0", 129 | "mock-require": "^3.0.3", 130 | "@types/mock-require": "^2.0.0", 131 | "@csstools/postcss-sass": "^4.0.0", 132 | "cssnano": "^4.1.10", 133 | "postcss": "^8.2.6" 134 | }, 135 | "peerDependencies": { 136 | "postcss": "^8.0.0" 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/builders/styles-statement.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import { fake } from 'sinon' 3 | import sinonChai from 'sinon-chai' 4 | import { 5 | buildInstantStylesStatement, buildLazyStylesStatement, buildOnDemandStylesStatement 6 | } from './styles-statement'; 7 | use(sinonChai); 8 | 9 | const classMap = { 10 | 'a': 'b' 11 | }; 12 | 13 | describe('styles-statement', () => { 14 | 15 | it('should generate lazy statement', () => { 16 | let result = ''; 17 | const builder = fake((str) => result += str); 18 | const node: any = {} 19 | buildLazyStylesStatement(builder, node, classMap, undefined, false, false); 20 | expect(result).eq('const styles = {\n' + 21 | ' get [\'a\']() { injectStyles(key, css); return \'b\'; },\n' + 22 | ' inject() { injectStyles(key, css); }\n' + 23 | '};\n'); 24 | }); 25 | it('should generate lazy statement with options', () => { 26 | let result = ''; 27 | const builder = fake((str) => result += str); 28 | const node: any = {} 29 | buildLazyStylesStatement(builder, node, classMap, undefined, false, true); 30 | expect(result).eq('const styles = {\n' + 31 | ' get [\'a\']() { injectStyles(key, css, options); return \'b\'; },\n' + 32 | ' inject() { injectStyles(key, css, options); }\n' + 33 | '};\n'); 34 | }); 35 | 36 | it('should generate lazy statement with attaching original class', () => { 37 | let result = ''; 38 | const builder = fake((str) => result += str); 39 | const node: any = {} 40 | buildLazyStylesStatement(builder, node, classMap, undefined, true, false); 41 | expect(result).eq('const styles = {\n' + 42 | ' get [\'a\']() { injectStyles(key, css); return \'b a\'; },\n' + 43 | ' inject() { injectStyles(key, css); }\n' + 44 | '};\n'); 45 | }); 46 | it('should generate lazy statement with custom inject statement', () => { 47 | let result = ''; 48 | const builder = fake((str) => result += str); 49 | const node: any = {} 50 | buildLazyStylesStatement(builder, node, classMap, 'myInjector(css, key)', false, false); 51 | expect(result).eq('const styles = {\n' + 52 | ' get [\'a\']() { myInjector(css, key); return \'b\'; },\n' + 53 | ' inject() { myInjector(css, key); }\n' + 54 | '};\n'); 55 | }); 56 | it('should generate on demand statement', () => { 57 | let result = ''; 58 | const builder = fake((str) => result += str); 59 | const node: any = {} 60 | buildOnDemandStylesStatement(builder, node, classMap, undefined, false, false); 61 | expect(result).eq('const styles = {\n' + 62 | ' [\'a\']: \'b\',\n' + 63 | ' inject(shadowRoot) { injectStyles(key, css, undefined, shadowRoot); }\n' + 64 | '};\n'); 65 | }); 66 | it('should generate on demand statement with custom inject statement', () => { 67 | let result = ''; 68 | const builder = fake((str) => result += str); 69 | const node: any = {} 70 | buildOnDemandStylesStatement(builder, node, classMap, 'myInjector(css, key)', false, false); 71 | expect(result).eq('const styles = {\n' + 72 | ' [\'a\']: \'b\',\n' + 73 | ' inject(shadowRoot) { myInjector(css, key); }\n' + 74 | '};\n'); 75 | }); 76 | it('should generate instant statement', () => { 77 | let result = ''; 78 | const builder = fake((str) => result += str); 79 | const node: any = {} 80 | buildInstantStylesStatement(builder, node, classMap, undefined, false, false); 81 | expect(result).eq('const styles = {\n' + 82 | ' [\'a\']: \'b\',\n' + 83 | ' inject(shadowRoot) { injectStyles(key, css, undefined, shadowRoot); }\n' + 84 | '};\n' + 85 | 'styles.inject();\n'); 86 | }); 87 | it('should generate instant statement with custom inject statement', () => { 88 | let result = ''; 89 | const builder = fake((str) => result += str); 90 | const node: any = {} 91 | buildInstantStylesStatement(builder, node, classMap, 'myInjector(css, key)', false, false); 92 | expect(result).eq('const styles = {\n' + 93 | ' [\'a\']: \'b\',\n' + 94 | ' inject(shadowRoot) { myInjector(css, key); }\n' + 95 | '};\n' + 96 | 'styles.inject();\n'); 97 | }); 98 | }) 99 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-es-modules", 3 | "version": "2.2.0", 4 | "description": "Universal styles injection to work with postcss-es-modules", 5 | "keywords": [ 6 | "css-in-js", 7 | "css modules" 8 | ], 9 | "bugs": { 10 | "url": "https://github.com/majo44/postcss-es-modules/issues" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/majo44/postcss-es-modules.git" 15 | }, 16 | "license": "MIT", 17 | "author": { 18 | "name": "Paweł Majewski", 19 | "email": "majo44@gmail.com" 20 | }, 21 | "sideEffects": false, 22 | "main": "./index.js", 23 | "module": "./dist/dist-esm/index.js", 24 | "types": "./dist/dist-types/index.d.ts", 25 | "files": [ 26 | "dist", 27 | "index.js" 28 | ], 29 | "scripts": { 30 | "api": "api-extractor run --local && api-documenter markdown -i ./temp -o ./temp/api && copyfiles ./temp/api/*.* ../../docs/api -f", 31 | "api:update": "yarn compile:cjs && yarn api", 32 | "build": "yarn clean && yarn lint && yarn compile && yarn api", 33 | "clean": "rimraf dist coverage", 34 | "compile": "yarn compile:cjs && yarn compile:esm", 35 | "compile:cjs": "tsc --outDir dist/dist-cjs --module CommonJS", 36 | "compile:esm": "tsc --outDir dist/dist-esm --module ESNext", 37 | "lint": "eslint ./src/**/*.ts --fix", 38 | "prepublishOnly": "yarn build", 39 | "test": "mocha src/**/*.ts", 40 | "test:coverage": "nyc yarn test", 41 | "test:watch": "yarn test --watch" 42 | }, 43 | "eslintConfig": { 44 | "ignorePatterns": ["*.js"], 45 | "parser": "@typescript-eslint/parser", 46 | "plugins": [ 47 | "@typescript-eslint", 48 | "eslint-plugin-tsdoc" 49 | ], 50 | "extends": [ 51 | "eslint:recommended", 52 | "plugin:@typescript-eslint/recommended" 53 | ], 54 | "rules": { 55 | "tsdoc/syntax": "error" 56 | }, 57 | "overrides": [ 58 | { 59 | "files": ["*.spec.ts"], 60 | "rules": { 61 | "@typescript-eslint/no-explicit-any": 0 62 | } 63 | } 64 | ] 65 | }, 66 | "mocha": { 67 | "require": [ 68 | "ts-node/register" 69 | ], 70 | "watchFiles": [ 71 | "test/**/*.ts", 72 | "src/**/*.ts" 73 | ] 74 | }, 75 | "nyc": { 76 | "all": true, 77 | "branches": 1, 78 | "check-coverage": true, 79 | "exclude": [ 80 | "src/**/*.spec.ts" 81 | ], 82 | "extension": [ 83 | ".ts" 84 | ], 85 | "functions": 1, 86 | "include": [ 87 | "src/**/*.ts" 88 | ], 89 | "lines": 1, 90 | "reporter": [ 91 | "text-summary", 92 | "html", 93 | "lcovonly" 94 | ], 95 | "statements": 1 96 | }, 97 | "dependencies": { 98 | "css-es-modules": "1.0.2", 99 | "postcss-modules": "^4.0.0", 100 | "md5": "^2.3.0", 101 | "tslib": "^2.1.0" 102 | }, 103 | "devDependencies": { 104 | "@microsoft/api-documenter": "^7.12.1", 105 | "@microsoft/api-extractor": "^7.12.1", 106 | "@types/chai": "^4.2.14", 107 | "@types/md5": "^2.3.0", 108 | "@types/mocha": "^8.2.0", 109 | "@types/node": "^16.11.26", 110 | "@types/sinon": "^9.0.10", 111 | "@types/sinon-chai": "^3.2.5", 112 | "@typescript-eslint/eslint-plugin": "^4.11.1", 113 | "@typescript-eslint/parser": "^4.11.1", 114 | "chai": "^4.2.0", 115 | "copyfiles": "^2.4.1", 116 | "eslint": "^7.16.0", 117 | "eslint-plugin-tsdoc": "^0.2.10", 118 | "mocha": "^8.2.1", 119 | "nyc": "^15.1.0", 120 | "replace-in-files-cli": "^1.0.0", 121 | "rimraf": "^3.0.2", 122 | "sinon": "^9.2.4", 123 | "sinon-chai": "^3.5.0", 124 | "ts-node": "^9.1.1", 125 | "typescript": "^4.1.3", 126 | "mock-fs": "^4.13.0", 127 | "@types/mock-fs": "^4.13.0", 128 | "require-from-string":"^2.0.2", 129 | "@types/require-from-string": "^1.2.0", 130 | "mock-require": "^3.0.3", 131 | "@types/mock-require": "^2.0.0", 132 | "@csstools/postcss-sass": "^4.0.0", 133 | "cssnano": "^4.1.10", 134 | "postcss": "^8.2.6" 135 | }, 136 | "peerDependencies": { 137 | "postcss": "^8.0.0" 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/postcss-node/test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import requireMock from 'mock-require'; 2 | import { Worker } from "worker_threads"; 3 | import { expect } from 'chai'; 4 | import { defaultPostCssNodeExtensions } from '../src/lib/options'; 5 | 6 | // mocking workerInit as Worker cant be directly init from ts code 7 | requireMock('../src/lib/worker-init', { 8 | workerInit: (filename: string, workerData: any) => { 9 | workerData.__filename = filename.replace('.js', '.ts'); 10 | return new Worker(` 11 | const wk = require('worker_threads'); 12 | require('ts-node').register(); 13 | let file = wk.workerData.__filename; 14 | delete wk.workerData.__filename; 15 | require(file); 16 | `, { eval: true, workerData, execArgv: [] } 17 | ) 18 | } 19 | }); 20 | 21 | 22 | describe('postcss-node', () => { 23 | let orgHandlers; 24 | 25 | beforeEach(async () => { 26 | orgHandlers = defaultPostCssNodeExtensions.map(x => require.extensions[x]); 27 | }); 28 | 29 | afterEach(async () => { 30 | defaultPostCssNodeExtensions.forEach((x, i) => require.extensions[x] = orgHandlers[i]); 31 | }); 32 | 33 | it('should properly transform multiple css modules in sync flow', async () => { 34 | const { register } = await import('../src/lib/register'); 35 | register(); 36 | // eslint-disable-next-line @typescript-eslint/no-var-requires 37 | let { styles, css, key} = require('./test1.css'); 38 | expect(styles.a.length).gt(0); 39 | expect(css).contains('blue'); 40 | expect(key.length).gt(0); 41 | 42 | ({ styles, css, key} = require('./test2.css')); 43 | expect(styles.a.length).gt(0); 44 | expect(css).contains('red'); 45 | expect(key.length).gt(0); 46 | }); 47 | 48 | it('should properly transform multiple css modules in async flow', async () => { 49 | const { register } = await import('../src/lib/register'); 50 | register(); 51 | // eslint-disable-next-line @typescript-eslint/no-var-requires 52 | let { styles, css, key} = require('./test1.css'); 53 | expect(styles.a.length).gt(0); 54 | expect(css.length).gt(0); 55 | expect(key.length).gt(0); 56 | 57 | await new Promise(res => setTimeout(res, 1)); 58 | ({ styles, css, key} = require('./test2.css')); 59 | expect(styles.a.length).gt(0); 60 | expect(css.length).gt(0); 61 | expect(key.length).gt(0); 62 | }) 63 | 64 | it('should throw error on wrong css syntax', async () => { 65 | const { register } = await import('../src/lib/register'); 66 | register(); 67 | expect(() => require('./test3.css')).throw; 68 | }); 69 | 70 | 71 | it('should throw error on wrong postcss config syntax', async () => { 72 | process.env.TEST_ERROR_CASE = "true"; 73 | const { register } = await import('../src/lib/register'); 74 | register(); 75 | expect(() => require('./test1.css')).throw; 76 | delete process.env.TEST_ERROR_CASE; 77 | }); 78 | 79 | it('should support custom extensions', async () => { 80 | const { register } = await import('../src/lib/register'); 81 | register(['.bcss'], 1000, 512); 82 | // eslint-disable-next-line @typescript-eslint/no-var-requires 83 | const { styles, css, key} = require('./test4.bcss'); 84 | expect(styles.a.length).gt(0); 85 | expect(css.length).gt(0); 86 | expect(key.length).gt(0); 87 | }); 88 | 89 | it('should support custom extensions by env', async () => { 90 | process.env.POSTCSS_NODE_EXT = '.ccss'; 91 | const { register } = await import('../src/lib/register'); 92 | register(); 93 | // eslint-disable-next-line @typescript-eslint/no-var-requires 94 | const { styles, css, key} = require('./test4.ccss'); 95 | expect(styles.a.length).gt(0); 96 | expect(css.length).gt(0); 97 | expect(key.length).gt(0); 98 | delete process.env.POSTCSS_NODE_EXT; 99 | }); 100 | 101 | it('should support custom timeout by env', async () => { 102 | process.env.POSTCSS_NODE_TIMEOUT = '0'; 103 | const { register } = await import('../src/lib/register'); 104 | register(); 105 | // eslint-disable-next-line @typescript-eslint/no-var-requires 106 | expect(() => require('./test5.css')).throw('Worker timeout'); 107 | delete process.env.POSTCSS_NODE_TIMEOUT; 108 | }); 109 | 110 | it('should support custom buffer size by env', async () => { 111 | process.env.POSTCSS_NODE_BUFFER_SIZE = '1'; 112 | const { register } = await import('../src/lib/register'); 113 | register(); 114 | // eslint-disable-next-line @typescript-eslint/no-var-requires 115 | expect(() => require('./test6.css')).throw('"length" is outside of buffer bounds'); 116 | delete process.env.POSTCSS_NODE_BUFFER_SIZE; 117 | }); 118 | }) 119 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/options.ts: -------------------------------------------------------------------------------- 1 | import { defaultStylesInjectOptions, StylesInjectOptions } from 'css-es-modules'; 2 | 3 | /** 4 | * Css modules processing options. 5 | * @public 6 | */ 7 | export interface ModulesOptions { 8 | /** 9 | * If you want to still use the original class name next to local one. 10 | * Default false. 11 | */ 12 | attachOriginalClassName?: boolean; 13 | /** 14 | * By default, the plugin assumes that all the classes are local. 15 | * You can change this behaviour using the scopeBehaviour option. 16 | */ 17 | scopeBehaviour?: 'global' | 'local'; 18 | /** 19 | * To define paths for global modules, use the globalModulePaths option. 20 | * It is an array with regular expressions defining the paths: 21 | */ 22 | globalModulePaths?: Array; 23 | /** 24 | * To generate custom classes, use the generateScopedName callback, 25 | * or just pass an interpolated string to the generateScopedName option: 26 | * `generateScopedName: "[name]__[local]___[hash:base64:5]"` 27 | */ 28 | generateScopedName?: string | ((name: string, filename: string, css: string) => string); 29 | /** 30 | * It's possible to add custom hash to generate more unique classes using the hashPrefix option. 31 | */ 32 | hashPrefix?: string; 33 | /** 34 | * If you need to export global names via the module object along with the local ones, 35 | * add the exportGlobals option. 36 | */ 37 | exportGlobals?: boolean; 38 | /** 39 | * Style of exported classnames. 40 | */ 41 | localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' | 42 | ((originalClassName: string, generatedClassName: string, inputFile: string) => string); 43 | } 44 | 45 | /** 46 | * Styles loader options. 47 | * @public 48 | */ 49 | export interface ExtendedStylesInjectOptions extends StylesInjectOptions { 50 | /** 51 | * Generated code modules type. 52 | * Options: 53 | * - 'esm' - ecmascript 6 modules 54 | * - 'cjs' - commonjs 55 | * 56 | * Default 'esm' 57 | */ 58 | moduleType?: 'esm' | 'cjs'; 59 | /** 60 | * The mode of the styles injection. 61 | * Options: 62 | * - `lazy` - the stylesheet will be injected on the first use of the style class name within the code 63 | * - `ondemand` - the stylesheet will be injected only when the `styles.inject()` method will be called 64 | * - `instant` - the stylesheet will be injected on the module load 65 | * - `none` - the stylesheet will be never injected, to inject it to the DOM you will need to import css content 66 | * Default: 'lazy' 67 | */ 68 | injectMode?: 'lazy' | 'ondemand' | 'instant' | 'none'; 69 | /** 70 | * The way how the styles injector script will be referred from the generated source. 71 | * Options: 72 | * - `embed`: embedding loader script in the target source 73 | * - `eject`: the loader script will be ejected to the provided scriptEjectPath 74 | * - `import`: the loader script will be referred by the import statement 75 | * 76 | * Default: 'import' 77 | */ 78 | script?: 'embed' | 'eject' | 'import', 79 | /** 80 | * The script type. 81 | * Options: 82 | * - `ts`: typescript 83 | * - `js`: javascript 84 | * 85 | * Default: 'js' 86 | */ 87 | scriptType?: 'ts' | 'js', 88 | /** 89 | * The path where the script code will be ejected. This option is required if 'eject' 90 | * value is set for the script type. 91 | */ 92 | scriptEjectPath?: string, 93 | /** 94 | * Custom injection script. Use this property to use custom styles to DOM/Node injection. 95 | */ 96 | custom?: { 97 | /** 98 | * The statement for import required dependencies. 99 | * Eg: "import injectMyStyles from 'somelib'; 100 | */ 101 | importStatement: string; 102 | /** 103 | * The statement for executing the injection. 104 | * There are available two constants in the context: 105 | * - css - the raw css code 106 | * - key - unique key of the stylesheet 107 | * Eg: "injectMyStyles(css)" 108 | */ 109 | injectStatement: string; 110 | } 111 | } 112 | 113 | /** 114 | * The plugin options. 115 | * @public 116 | */ 117 | export interface Options { 118 | /** 119 | * Css modules configuration. 120 | */ 121 | modules?: ModulesOptions; 122 | /** 123 | * Css injection configuration. 124 | */ 125 | inject?: ExtendedStylesInjectOptions; 126 | } 127 | 128 | /** 129 | * Default values of the options. 130 | * @internal 131 | */ 132 | export const defaultOptions = { 133 | inject: { 134 | ...defaultStylesInjectOptions, 135 | moduleType: 'esm', 136 | script: 'import', 137 | scriptType: 'js', 138 | injectMode: 'lazy', 139 | }, 140 | modules: {} 141 | } as const; 142 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postcss-es-modules 6 | 7 | 8 | 9 | 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/stringify.ts: -------------------------------------------------------------------------------- 1 | import md5 from 'md5'; 2 | import postcss, { AnyNode, Builder, Stringifier } from 'postcss'; 3 | import { buildImportInjectStylesStatement } from './builders/import-statement'; 4 | import { 5 | buildInstantStylesStatement, 6 | buildLazyStylesStatement, 7 | buildOnDemandStylesStatement 8 | } from './builders/styles-statement'; 9 | import { Options } from './options'; 10 | import { readCodeForEmbed } from './utils/read-code-for-embed'; 11 | import { prepareRuntimeOptions } from './utils/runtime-options'; 12 | 13 | 14 | let embedCode: string | undefined; 15 | 16 | export const createStringify = (options: Required): Stringifier => { 17 | 18 | const { attachOriginalClassName } = options.modules; 19 | const { script, scriptType, moduleType, custom } = options.inject; 20 | const { importStatement, injectStatement } = custom || {}; 21 | 22 | const runtimeOptions = prepareRuntimeOptions(options); 23 | 24 | return (node: AnyNode, builder: Builder) => { 25 | /* istanbul ignore else */ 26 | if (node.type === 'root') { 27 | let classMap: Record | undefined; 28 | // looking for module-map comment 29 | node.walkComments(comment => { 30 | const cmt = comment.text; 31 | if (cmt.startsWith('modules-map:')) { 32 | classMap = JSON.parse(cmt.replace('modules-map:', '')); 33 | } 34 | }); 35 | // we handling only roots which contains module-map 36 | /* istanbul ignore else */ 37 | if (classMap) { 38 | // clone for removing comments, as we do not want to touch original root 39 | const cloned = node.clone(); 40 | // generate stylesheet unique key 41 | const sheetKey = md5(cloned.toString()); 42 | cloned.walkComments(c => { c.remove() }); 43 | // header text 44 | builder(`// File generated by the postcss-es-modules plugin. Please do not modify it !!!\n`) 45 | 46 | if (options.inject.injectMode !== 'none') { 47 | // if not none injectMode 48 | if (script === 'embed') { 49 | // if we have to embed injector code 50 | /* istanbul ignore else */ 51 | if (!embedCode) { 52 | // if code to inject is not loaded yet 53 | // we will read it 54 | embedCode = readCodeForEmbed(scriptType, moduleType); 55 | } 56 | // print in the injector code 57 | builder(embedCode, node); 58 | } else { 59 | // import statement 60 | if (importStatement) { 61 | // if there is custom importStatement, we will use it 62 | builder(importStatement, cloned) 63 | } else { 64 | // if not embedding, we will import the css-es-modules lib 65 | buildImportInjectStylesStatement(cloned, builder, options.inject); 66 | } 67 | } 68 | } 69 | 70 | // if there are custom runtime options 71 | // we will add them to runtime 72 | if (runtimeOptions) { 73 | builder(`const options = ${JSON.stringify(runtimeOptions)};\n`) 74 | } 75 | 76 | // key of stylesheet 77 | builder(`const key = '${sheetKey}';\n`, cloned); 78 | 79 | // raw css body 80 | builder('const css =`', cloned); 81 | postcss.stringify(cloned, builder); 82 | builder('`;\n', cloned); 83 | 84 | // styles object 85 | if (options.inject.injectMode === 'instant') { 86 | buildInstantStylesStatement( 87 | builder, cloned, classMap, injectStatement, !!attachOriginalClassName, !!runtimeOptions); 88 | } else if (options.inject.injectMode === 'ondemand') { 89 | buildOnDemandStylesStatement( 90 | builder, cloned, classMap, injectStatement, !!attachOriginalClassName, !!runtimeOptions); 91 | } else if (options.inject.injectMode === 'none') { 92 | buildOnDemandStylesStatement( 93 | builder, cloned, classMap, 94 | "throw \"This stylesheet can't be injected, instead please use exported css constant.\"", 95 | !!attachOriginalClassName, !!runtimeOptions); 96 | } else { 97 | buildLazyStylesStatement( 98 | builder, cloned, classMap, injectStatement, !!attachOriginalClassName, !!runtimeOptions); 99 | } 100 | 101 | // export statement 102 | if (moduleType === 'esm') { 103 | builder(`export { styles, css, key };\n`, cloned); 104 | builder(`export default styles;\n`, cloned); 105 | } else { 106 | builder(`module.exports = { styles, css, key, default: styles };\n`, cloned); 107 | } 108 | 109 | return; 110 | } 111 | } 112 | 113 | // in any other case we will use default stringify 114 | postcss.stringify(node, builder); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /packages/css-es-modules/src/inject-styles.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // globals declaration to avoid the typescript compile errors 4 | declare global { 5 | // Using `string | true` as second record generic because Window is type of `Window & typeof globalThis;` 6 | var $CSS$IN$JS$GLOBALS$: Record; 7 | var $CSS$IN$JS$LOCALS$: Record; 8 | 9 | interface ShadowRoot { 10 | $CSS$IN$JS$GLOBALS$: Record; 11 | adoptedStyleSheets: CSSStyleSheet[]; 12 | } 13 | 14 | interface CSSStyleSheet { 15 | replace(text: string): Promise; 16 | replaceSync(text: string): void; 17 | } 18 | interface Document { 19 | adoptedStyleSheets: CSSStyleSheet[]; 20 | } 21 | var global: typeof globalThis; 22 | } 23 | 24 | /** 25 | * Is node.js current env. 26 | */ 27 | const isNode = typeof global === 'object' && (typeof window === 'undefined' || 28 | /* istanbul ignore next */ 29 | global !== window as any); 30 | 31 | /** 32 | * Global styles shared context property name. 33 | * @public 34 | */ 35 | export const CSS_GLOBAL_KEY = '$CSS$IN$JS$GLOBALS$'; 36 | /** 37 | * Local styles shared context property name. 38 | * @public 39 | */ 40 | export const CSS_LOCALS_KEY = '$CSS$IN$JS$LOCALS$'; 41 | 42 | /** 43 | * Inject options. 44 | * @public 45 | */ 46 | export interface StylesInjectOptions { 47 | /** 48 | * The style nounce key. 49 | */ 50 | useNounce?: string; 51 | /** 52 | * Use Constructable Stylesheet method of the styles injection to DOM if the browser supports 53 | * Constructable Stylesheet. 54 | * Default true. 55 | */ 56 | useConstructableStylesheet?: boolean; 57 | /** 58 | * Enable style tag method of the styles injection to the document. 59 | * If the useConstructableStylesheet is enabled, this method will ba fallback if browser is not 60 | * supports Constructable Stylesheets. 61 | * Default true. 62 | */ 63 | useStyleTag?: boolean; 64 | /** 65 | * Enable node.js global method of injection. 66 | * Default true. 67 | */ 68 | useNodeGlobal?: boolean; 69 | } 70 | 71 | /** 72 | * Default options values. 73 | * @public 74 | */ 75 | export const defaultStylesInjectOptions: StylesInjectOptions = { 76 | useConstructableStylesheet: true, 77 | useNodeGlobal: true, 78 | useStyleTag: true, 79 | } 80 | 81 | /** 82 | * Inject stylesheet to global on node or to the document DOM on the browser 83 | * @public 84 | * @param stylesheetKey - the unique stylesheet key 85 | * @param stylesheetBody - the stylesheet body 86 | * @param options - inject options 87 | * @param shadowRoot - optional shadow root where styles will be injected 88 | * @param serverSide - force server/client approach 89 | */ 90 | export function injectStyles( 91 | stylesheetKey: string, 92 | stylesheetBody: string, 93 | options?: StylesInjectOptions, 94 | serverSide: boolean = isNode, 95 | shadowRoot?: ShadowRoot 96 | ): void { 97 | // prepare options 98 | const { useNodeGlobal, useConstructableStylesheet, useStyleTag, useNounce } = { 99 | ...defaultStylesInjectOptions, 100 | ...options 101 | } 102 | // if we are on the node.js 103 | // and node side injection is enabled 104 | // and style is not already registered globally 105 | if (serverSide && useNodeGlobal && !global[CSS_GLOBAL_KEY]?.[stylesheetKey]) { 106 | if (global[CSS_LOCALS_KEY]) { 107 | if(!global[CSS_LOCALS_KEY][stylesheetKey]) { 108 | // if there is a local css registry 109 | // injecting to locals registry 110 | global[CSS_LOCALS_KEY][stylesheetKey] = stylesheetBody; 111 | } 112 | } else { 113 | // if there is no local css registry and style is not already injected globally 114 | // injecting to global registry 115 | global[CSS_GLOBAL_KEY] = { 116 | ...global[CSS_GLOBAL_KEY], [stylesheetKey]: stylesheetBody 117 | } 118 | } 119 | return; 120 | } 121 | // if we are on the browser side 122 | // and style is not already registered 123 | // and client side injection is enabled 124 | if (!serverSide) { 125 | const context = shadowRoot || window; 126 | const adopter = shadowRoot || document; 127 | if (!context[CSS_GLOBAL_KEY]?.[stylesheetKey] 128 | && (useConstructableStylesheet || useStyleTag)) { 129 | // marking style as injected 130 | context[CSS_GLOBAL_KEY] = { 131 | ...context[CSS_GLOBAL_KEY], 132 | [stylesheetKey]: true 133 | } 134 | // if constructable stylesheet flag is enabled and available 135 | if (useConstructableStylesheet && adopter.adoptedStyleSheets) { 136 | // we are using constructable stylesheet injection method 137 | const stylesheet = new CSSStyleSheet(); 138 | stylesheet.replace(stylesheetBody); 139 | adopter.adoptedStyleSheets = [...adopter.adoptedStyleSheets, stylesheet]; 140 | return; 141 | } 142 | // if style tag flag is enabled 143 | if (useStyleTag) { 144 | // we are injecting stylesheet by styles tag 145 | const styleElement: HTMLStyleElement = document.createElement('style'); 146 | useNounce && styleElement.setAttribute('nounce', useNounce); 147 | /* istanbul ignore if */ 148 | if ((styleElement as any).styleSheet) { 149 | (styleElement as any).styleSheet.cssText += stylesheetBody 150 | } else { 151 | styleElement.textContent += stylesheetBody; 152 | } 153 | if (shadowRoot) { 154 | shadowRoot.appendChild(styleElement); 155 | } else { 156 | (document.head || 157 | /* istanbul ignore next */ 158 | document.getElementsByTagName('head')?.[0])?.appendChild(styleElement); 159 | } 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /packages/css-es-modules/src/inject-styles.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as jsdom from 'jsdom'; 3 | import { CSS_GLOBAL_KEY, CSS_LOCALS_KEY, injectStyles } from './inject-styles'; 4 | 5 | declare global { 6 | // eslint-disable-next-line @typescript-eslint/no-namespace 7 | namespace NodeJS { 8 | interface Global { 9 | window: Window; 10 | document: Document; 11 | } 12 | } 13 | // eslint-disable-next-line no-var 14 | var global: NodeJS.Global & typeof globalThis; 15 | } 16 | 17 | 18 | const key1 = 'key1'; 19 | const styles1 = '.a {color: red;}'; 20 | const styles2 = '.b {color: blue;}'; 21 | 22 | describe('inject-styles', () => { 23 | describe('on server side', () => { 24 | 25 | afterEach(() => { 26 | delete global[CSS_GLOBAL_KEY]; 27 | delete global[CSS_LOCALS_KEY]; 28 | }); 29 | 30 | it('should inject styles to the globals on the server', () => { 31 | injectStyles(key1, styles1); 32 | expect(global[CSS_GLOBAL_KEY][key1]).eql(styles1); 33 | expect(global[CSS_LOCALS_KEY]).undefined; 34 | }); 35 | it('should inject styles to the locals on the server', () => { 36 | global[CSS_LOCALS_KEY] = {}; 37 | injectStyles(key1, styles1); 38 | expect(global[CSS_LOCALS_KEY][key1]).eql(styles1); 39 | expect(global[CSS_GLOBAL_KEY]).undefined; 40 | }); 41 | it('should not inject styles to the globals on the server if already injected', () => { 42 | global[CSS_GLOBAL_KEY] = { 43 | [key1]: styles1 44 | }; 45 | injectStyles(key1, styles2); 46 | expect(global[CSS_GLOBAL_KEY][key1]).eql(styles1); 47 | expect(global[CSS_LOCALS_KEY]).undefined; 48 | }); 49 | it('should not inject styles on the server if node globals disabled', () => { 50 | injectStyles(key1, styles2, { useNodeGlobal: false }); 51 | expect(global[CSS_GLOBAL_KEY]).undefined; 52 | expect(global[CSS_LOCALS_KEY]).undefined; 53 | }); 54 | it('should not inject styles to the locals on the server if already injected', () => { 55 | global[CSS_LOCALS_KEY] = { 56 | [key1]: styles1 57 | }; 58 | injectStyles(key1, styles2); 59 | expect(global[CSS_LOCALS_KEY][key1]).eql(styles1); 60 | expect(global[CSS_GLOBAL_KEY]).undefined; 61 | }); 62 | }); 63 | 64 | describe('on browser side', () => { 65 | 66 | beforeEach(() => { 67 | const dom = new jsdom.JSDOM(''); 68 | (global as any).window = dom.window; 69 | (global as any).document = dom.window.document; 70 | }); 71 | 72 | afterEach(() => { 73 | delete (global as any).window; 74 | }); 75 | 76 | describe('with style tag', () => { 77 | it('should inject styles', async () => { 78 | injectStyles(key1, styles1, undefined, false); 79 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 80 | expect((window.document.querySelector('style').childNodes[0] as Text).nodeValue).eql(styles1); 81 | }); 82 | 83 | it('should inject styles only once', async () => { 84 | injectStyles(key1, styles1, undefined, false); 85 | injectStyles(key1, styles2, undefined, false); 86 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 87 | expect((window.document.querySelector('style').childNodes[0] as Text).nodeValue).eql(styles1); 88 | }); 89 | 90 | it('should not inject styles if style tag are disabled', async () => { 91 | injectStyles(key1, styles1, { useStyleTag: false }, false); 92 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 93 | expect(window.document.querySelector('style')).eql(null); 94 | }); 95 | 96 | it('should not inject styles if style tag disabled and constructable stylesheet are ', async () => { 97 | injectStyles(key1, styles1, { useStyleTag: false, useConstructableStylesheet: false}, false); 98 | expect(window[CSS_GLOBAL_KEY]).undefined; 99 | expect(window.document.querySelector('style')).eql(null); 100 | }); 101 | 102 | it('should use nounce', async () => { 103 | injectStyles(key1, styles1, { useNounce: '123' }, false); 104 | expect(window.document.querySelector('style').getAttribute('nounce')).eql('123'); 105 | }); 106 | }); 107 | 108 | describe('with constructableStylesheet', () => { 109 | 110 | beforeEach(() => { 111 | window.CSSStyleSheet.prototype.replace = async function (this: CSSStyleSheet, ctn: string) { 112 | const styleElement: HTMLStyleElement = document.createElement('style'); 113 | styleElement.innerHTML = ctn; 114 | window.document.head.appendChild(styleElement); 115 | Array.from(styleElement.sheet.cssRules) 116 | .map(r => r.cssText) 117 | .forEach(this.insertRule.bind(this)); 118 | styleElement.remove(); 119 | return this; 120 | } 121 | document.adoptedStyleSheets = []; 122 | global.CSSStyleSheet = window.CSSStyleSheet; 123 | }); 124 | 125 | afterEach(() => { 126 | delete window.CSSStyleSheet.prototype.replace; 127 | delete document.adoptedStyleSheets; 128 | delete global.CSSStyleSheet; 129 | }); 130 | 131 | it ('should inject styles', () => { 132 | injectStyles(key1, styles1, undefined, false); 133 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 134 | expect(document.adoptedStyleSheets[0].toString()).eq(styles1 + '\n'); 135 | }); 136 | 137 | it('should inject styles only once', async () => { 138 | injectStyles(key1, styles1, undefined, false); 139 | injectStyles(key1, styles2, undefined, false); 140 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 141 | expect(document.adoptedStyleSheets[0].toString()).eq(styles1 + '\n'); 142 | }); 143 | 144 | it('should use styleTag fallback if disabled', async () => { 145 | injectStyles(key1, styles1, { useConstructableStylesheet: false }, false); 146 | expect(window[CSS_GLOBAL_KEY][key1]).eq(true); 147 | expect(document.adoptedStyleSheets.length).eq(0); 148 | expect((window.document.querySelector('style').childNodes[0] as Text).nodeValue).eql(styles1); 149 | }); 150 | }) 151 | 152 | }); 153 | 154 | }) 155 | -------------------------------------------------------------------------------- /packages/postcss-es-modules/src/lib/plugin.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { StylesCollector, collectStyles } from 'css-es-modules'; 3 | import * as cssEsModule from 'css-es-modules'; 4 | import { join } from 'path'; 5 | import { ModuleKind, transpileModule } from 'typescript'; 6 | import { plugin } from './plugin'; 7 | import postcss from 'postcss'; 8 | import requireFromString from 'require-from-string'; 9 | import fsMock from 'mock-fs'; 10 | import requireMock from 'mock-require'; 11 | import cssnano from 'cssnano'; 12 | 13 | const rule = '{ color: blue; }'; 14 | const css = `.a ${rule}`; 15 | const processorOpt = { from: '/some/path/test.css' }; 16 | 17 | describe('plugin', () => { 18 | 19 | const checkModule = ( 20 | { collector, module, classes = [], cssRule = rule } : 21 | { collector?: StylesCollector, module: any, classes?: Array, cssRule?: string }) => { 22 | // check is rule body within the css 23 | expect(module.css).contains(cssRule); 24 | // check is key defined 25 | expect(module.key).not.undefined; 26 | // for each class name 27 | classes.forEach(c => { 28 | // check is mapped 29 | const className = module.styles[c]; 30 | expect(className.length).gt(1); 31 | // check is within the css 32 | expect(module.css).contains(className.split(' ')[0]); 33 | }) 34 | // check is collected 35 | if (collector) { 36 | // raw have to contain css 37 | expect(collector.raw).contains(module.css); 38 | // ids have to contains module key 39 | expect(collector.ids[module.key]).true; 40 | } 41 | } 42 | 43 | it('throw if eject mode but no eject path', async () => { 44 | try { 45 | await postcss( 46 | [plugin({ inject: { script: 'eject' } })] 47 | ).process(css, processorOpt); 48 | } catch (ex) { 49 | expect(ex).not.undefined 50 | return; 51 | } 52 | throw 'Wrong flow'; 53 | }); 54 | 55 | it('throw if eject mode but eject relative path', async () => { 56 | try { 57 | await postcss( 58 | [plugin({ inject: { script: 'eject', scriptEjectPath: './' } })] 59 | ).process(css, processorOpt); 60 | } catch (ex) { 61 | expect(ex).not.undefined 62 | return; 63 | } 64 | throw 'Wrong flow'; 65 | }); 66 | 67 | it('default options', async () => { 68 | const pcss = await postcss([plugin()]).process(css, processorOpt); 69 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 70 | const module = requireFromString(js.outputText); 71 | checkModule({ 72 | collector: collectStyles(), 73 | module, 74 | classes: ['a'] 75 | }); 76 | }); 77 | 78 | it('additional comments ', async () => { 79 | const pcss = await postcss([plugin()]).process(`/* comment */ ${css}`, processorOpt); 80 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 81 | const module = requireFromString(js.outputText); 82 | checkModule({ 83 | collector: collectStyles(), 84 | module, 85 | classes: ['a'] 86 | }); 87 | }); 88 | 89 | it('without css classes', async () => { 90 | const pcss = await postcss( 91 | [plugin()] 92 | ).process('html { color: blue; }', processorOpt); 93 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 94 | const module = requireFromString(js.outputText); 95 | const collector = collectStyles(); 96 | module.styles.inject(); 97 | checkModule({ collector, module }); 98 | }); 99 | 100 | describe('module', () => { 101 | it('cjs', async () => { 102 | const pcss = await postcss( 103 | [plugin({inject: { moduleType: 'cjs' }})] 104 | ).process(css, processorOpt); 105 | const module = requireFromString(pcss.css); 106 | checkModule({ 107 | collector: collectStyles(), 108 | module, 109 | classes: ['a'] 110 | }); 111 | }); 112 | it('cjs ts', async () => { 113 | const pcss = await postcss( 114 | [plugin({inject: { moduleType: 'cjs', scriptType: 'ts' }})] 115 | ).process(css, processorOpt); 116 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 117 | const module = requireFromString(js.outputText); 118 | checkModule({ 119 | collector: collectStyles(), 120 | module, 121 | classes: ['a'] 122 | }); 123 | }); 124 | it('esm ts', async () => { 125 | const pcss = await postcss( 126 | [plugin({inject: { moduleType: 'esm', scriptType: 'ts' }})] 127 | ).process(css, processorOpt); 128 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 129 | const module = requireFromString(js.outputText); 130 | checkModule({ 131 | collector: collectStyles(), 132 | module, 133 | classes: ['a'] 134 | }); 135 | }); 136 | }); 137 | 138 | describe('injectMode', () => { 139 | it('none', async () => { 140 | const pcss = await postcss( 141 | [plugin({ inject: { injectMode: 'none' } })] 142 | ).process(css, processorOpt); 143 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 144 | const module = requireFromString(js.outputText); 145 | checkModule({ module }); 146 | expect(() => module.styles.inject()).throw(); 147 | }); 148 | 149 | it('instant', async () => { 150 | const pcss = await postcss( 151 | [plugin({ inject: { injectMode: 'instant' } })] 152 | ).process(css, processorOpt); 153 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 154 | const collector = collectStyles(); 155 | expect(collector.raw).eq(''); 156 | const module = requireFromString(js.outputText); 157 | checkModule({ collector, module, classes: ['a'] }); 158 | }); 159 | 160 | it('ondemand', async () => { 161 | const pcss = await postcss( 162 | [plugin({ inject: { injectMode: 'ondemand' } })] 163 | ).process(css, processorOpt); 164 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 165 | const collector = collectStyles(); 166 | const module = requireFromString(js.outputText); 167 | expect(collector.raw).eq(''); 168 | module.styles.inject(); 169 | checkModule({ collector, module, classes: ['a'] }); 170 | }); 171 | }); 172 | 173 | describe('script', function () { 174 | 175 | it('embed', async () => { 176 | const pcss = await postcss( 177 | [plugin({ inject: { script: 'embed' } })] 178 | ).process(css, processorOpt); 179 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 180 | const module = requireFromString(js.outputText, '/some/path/test.js'); 181 | checkModule({ 182 | collector: collectStyles(), 183 | module, 184 | classes: ['a'] 185 | }); 186 | }); 187 | 188 | it('eject', async () => { 189 | const cssEsModulesPath = join(require.resolve('css-es-modules'), '../../../'); 190 | fsMock({ 191 | '/some/path': {}, 192 | [cssEsModulesPath]: fsMock.load(cssEsModulesPath, { recursive: true, lazy: true}) 193 | }); 194 | const pcss = await postcss( 195 | [plugin({ inject: { script: 'eject', scriptEjectPath: '/some/path' } })] 196 | ).process(css, processorOpt); 197 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 198 | requireMock('/some/path/inject-styles', cssEsModule); 199 | const module = requireFromString(js.outputText, '/some/path/test.js'); 200 | checkModule({ 201 | collector: collectStyles(), 202 | module, 203 | classes: ['a'] 204 | }); 205 | fsMock.restore(); 206 | requireMock.stopAll(); 207 | }); 208 | 209 | }); 210 | 211 | it('custom inject code', async () => { 212 | const pcss = await postcss( 213 | [plugin({ inject: { custom: { 214 | importStatement: 'import * as x from "inject-styles"', 215 | injectStatement: 'x.injectStyles(key,css)' 216 | } } })] 217 | ).process(css, processorOpt); 218 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 219 | requireMock('inject-styles', cssEsModule); 220 | const module = requireFromString(js.outputText, '/some/path/test.js'); 221 | checkModule({ 222 | collector: collectStyles(), 223 | module, 224 | classes: ['a'] 225 | }); 226 | requireMock.stopAll(); 227 | }); 228 | 229 | it('runtime options', async () => { 230 | const pcss = await postcss([plugin({ 231 | inject: { 232 | useNodeGlobal: false 233 | } 234 | })]).process(css, processorOpt); 235 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 236 | const module = requireFromString(js.outputText); 237 | const collector = collectStyles(); 238 | checkModule({ 239 | module, 240 | classes: ['a'] 241 | }); 242 | expect(collector.raw).eq(''); 243 | }); 244 | 245 | it('with other plugins', async () => { 246 | const pcss = await postcss([/*postCssSass(),*/ cssnano(), plugin()]).process(css, processorOpt); 247 | const js = transpileModule(pcss.css, {compilerOptions: {module: ModuleKind.CommonJS}}); 248 | const module = requireFromString(js.outputText); 249 | checkModule({ 250 | collector: collectStyles(), 251 | module, 252 | classes: ['a'], 253 | cssRule: '{color:#00f}' 254 | }); 255 | }).timeout(5000); 256 | }); 257 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-es-modules 2 | [Postcss](https://postcss.org/) plugin which transform css files in to the .js/.ts css modules. 3 | 4 | What is this? For example, you have the following CSS: 5 | 6 | ```css 7 | /* ./component.module.css */ 8 | .article { 9 | font-size: 16px; 10 | } 11 | .title { 12 | font-size: 24px; 13 | } 14 | .title:hover { 15 | color: red; 16 | } 17 | ``` 18 | After the transformation it will become like this: 19 | ```javascript 20 | /* ./component.module.css.js */ 21 | // File generated by the postcss-es-modules plugin. Please do not modify it !!! 22 | import { injectStyles } from 'css-es-modules'; 23 | const key = '77d26384-99a7-48e0-9f08-cd25a85864fb'; 24 | const css =`._article_9u0vb_1 { 25 | font-size: 16px; 26 | } 27 | ._title_9u0vb_9 { 28 | font-size: 24px; 29 | } 30 | ._title_9u0vb_9:hover { 31 | color: red; 32 | } 33 | `; 34 | const styles = { 35 | get ['article']() { injectStyles(key, css); return '_article_9u0vb_1 article'; }, 36 | get ['title']() { injectStyles(key, css); return '_title_9u0vb_9 title'; }, 37 | inject() { injectStyles(key, css); } 38 | }; 39 | export default styles; 40 | ``` 41 | Then that code can be referred from your component. 42 | ```javascript 43 | /* ./component.jsx */ 44 | import styles from './component.module.css.js'; 45 | export const Component = () => ( 46 |
47 |
Title
48 |
49 | ) 50 | ``` 51 | 52 | ## Description 53 | This plugin allows you to write the styles in *css/sass/less/...* syntax and transform it on the 54 | build time in the efficient *js/ts* code which can be simple referred from your component/application 55 | code. That code is responsible for attach the styles into the DOM (or to SSR pipeline) in most possible efficient way. 56 | This plugin also generates scoped class names, so you can be sure that your components styles will not leak 57 | in to the other parts of the application. 58 | 59 | ## Features 60 | * **Zero** runtime dependencies (on eject or embed mode, pleas look at the options) 61 | * Framework agnostic 62 | * Javascript and Typescript support 63 | * Server side rendering 64 | * Lazy, on demand, instant or none styles injection 65 | * Rich (optional) configuration 66 | 67 | ## Installation 68 | ```bash 69 | npm i postcss postcss-es-modules --save-dev 70 | ``` 71 | 72 | ## Usage 73 | 74 | Configure the postcss to use the plugin: 75 | ```javascript 76 | // postcss.config.js 77 | const { postcssEsModules } = require('postcss-es-modules'); 78 | module.exports = (ctx) => ({ 79 | plugins: [ 80 | postcssEsModules({ 81 | // options 82 | }), 83 | ] 84 | }) 85 | ``` 86 | > Please remember that this plugin is generating **non css syntax**, so it should be last on the list 87 | > of the used plugins. 88 | 89 | > This plugin is internally using *postcss-modules* plugin, so you do not have to add it by self. 90 | 91 | ### With the postcss-cli 92 | ```bash 93 | postcss src/**/*.module.css --dir src --base src --ext css.js 94 | ``` 95 | This command will generate the `.js` files by the post-cli, directly to your src directory. 96 | 97 | ### With the postcss-cli and typescript 98 | 99 | ```javascript 100 | // postcss.config.js 101 | const { postcssEsModules } = require('postcss-es-modules'); 102 | module.exports = (ctx) => ({ 103 | plugins: [ 104 | postcssEsModules({ inject: { scriptType: 'ts' } }), 105 | ] 106 | }) 107 | ``` 108 | ```bash 109 | postcss src/**/*.module.css --dir src --base src --ext css.ts 110 | ``` 111 | 112 | ### With the webpack/rollup/... 113 | There is nothing unique to use of this plugin with bundlers. 114 | 115 | ### With the webpack/rollup/... and typescript 116 | If you are using typescript, and you do not want to generate es css modules ahead but just 117 | let the bundler do the job, for solving typescript compilation errors please add global declaration 118 | to your project, like that: 119 | 120 | ```typescript 121 | /* ./global.d.ts */ 122 | declare module "*.css" { 123 | type Styles = { [className: string]: string; } & { inject(): void; } 124 | export const styles: Styles; 125 | export const key: string; 126 | export const css: string; 127 | export default styles; 128 | } 129 | ``` 130 | This will say to the compiler that each `*.css` import should be mapped to declared type. 131 | 132 | ### Other usage examples 133 | You can find more examples [here](https://majo44.github.io/postcss-es-modules/#/examples/). 134 | 135 | ## Options 136 | 137 | Here is the list of all available options, for more details please go to [Recipes](#recipes). 138 | 139 | | Option | Type | Default | Description | 140 | | --- | --- | --- | --- | 141 | | `inject` | `object` | - | The configuration fo the styles injection | 142 | | `inject.useNounce` | `string` | - | The style nounce key | 143 | | `inject.useConstructableStylesheet` | `boolean` | true | Use Constructable Stylesheet for styles injection to DOM if the browser supports Constructable Stylesheet | 144 | | `inject.useStyleTag` | `boolean` | true | Use \ tag for styles injection to DOM | 145 | | `inject.useNodeGlobal` | `boolean` | true | Enable node.js global for collecting the styles, required for server side rendering | 146 | | `inject.moduleType` | `'cjs'` / `'esm'` | `'esm'` | Generated code modules type. Options:
- `esm`: ecmascript 6 modules
- `cjs`: commonjs | 147 | | `inject.injectMode` | `'lazy'` / `'ondemand'` / `'instant'` / `'none'` | `'lazy'` | The mode of the styles injection. Options:
- `lazy` - the stylesheet will be injected on the first use of the style class name within the code
- `ondemand` - the stylesheet will be injected only when the `styles.inject()` method will be called
- `instant` - the stylesheet will be injected on the module load
- `none` - the stylesheet will be not injected, in order to inject it you will need to import `css` raw string from the module, and inject it manually | 148 | | `inject.script` | `'embed'` / `'eject'` / `'import'` | `'import'` | The way how the styles injector script will be referred from the generated source. Options:
- `embed`: embedding styles injector script in to the target source (so each generated file will contains the loader inside)
- `eject`: the styles injector script will be ejected to the provided `inject.scriptEjectPath`
- `import`: the styles injector script will be referred by the import statement | 149 | | `inject.scriptType` | `'ts'` / `'js'` | `'js'` | The generated script type. Options:
- `ts`: typescript
- `js`: javascript | 150 | | `inject.scriptEjectPath` | `string` | - | The path where the styles injector script code will be ejected. This option is required if `inject.script` is `eject` | 151 | | `inject.custom` | `object`| - | The custom style injector configuration | 152 | | `inject.custom.importStatement` | `string`| - | The custom style injector statement for import required dependencies. Eg: `"import { injectMyStyles } from 'somelib'";` | 153 | | `inject.custom.injectStatement` | `string`| - | The custom style injector statement for executing the injection. There are available two constants in the context:
- `css` - the raw css string code
- `key` - unique key of the stylesheet
Eg: `"injectMyStyles(css)"` | 154 | | `modules` | `object`| - | The CSS Modules options. It is inherits all options from the [postcss-modules](https://github.com/css-modules/postcss-modules) expect `getJSON` | 155 | | `modules.attachOriginalClassName` | `boolean`| false | The CSS Modules options. If you want to still use the original class name next to local one | 156 | 157 | ## Recipes 158 | 159 | ### Server side rendering 160 | Example of server side rendering with the React and Express.js: 161 | ```css 162 | /* ./index.css */ 163 | .app { 164 | background-color: red; 165 | } 166 | ``` 167 | 168 | ```typescript jsx 169 | // ./index.js 170 | const express = require('express'); 171 | const { createElement } = require('react'); 172 | const { renderToString } = require('react-dom/server'); 173 | const { collectStyles } = require('css-es-modules'); 174 | // imports the transformed css file 175 | const { styles } = require('./index.module.css.js'); 176 | 177 | const app = express(); 178 | 179 | // example component to render 180 | const App = () => createElement('div', { className: styles.app}, "Hello Word"); 181 | 182 | /** 183 | * App template 184 | * @param styles - the StylesCollector object 185 | * @param html - prerendered app markup 186 | */ 187 | const template = (styles, html) => ` 188 | 189 | ${styles.html} 190 |
${ html }
191 | `; 192 | 193 | // handling request 194 | app.get('/', (req, res) => { 195 | res.send( 196 | // render template 197 | template( 198 | // firstly start collecting styles 199 | collectStyles(), 200 | // then render application 201 | renderToString(createElement(App)))); 202 | }); 203 | 204 | // starting app 205 | app.listen(3000); 206 | ``` 207 | 208 | To run this example you have to transpile css file ahead. With the `inject.moduleType` set to `cjs`. 209 | The full working example you will find [here](https://majo44.github.io/postcss-es-modules/#/examples/react-ssr-webpack-typescript/). 210 | 211 | ### Lazy/On demand/Instant/None styles injection 212 | There are few modes how the styles injection can work. 213 | 214 | #### Lazy (default) 215 | The lazy injection means that the generated stylesheet will be not attached to the DOM/Node globals 216 | until some code will not call the getter of className. So even you are importing module with styles 217 | that styles are not applied to the application until some of the components will not use the class. 218 | This technique is very useful beacause on the server side rendering we will reder just critical 219 | stylesheets. 220 | 221 | ```javascript 222 | import { injectStyles } from 'css-es-modules'; 223 | const key = '77d26384-99a7-48e0-9f08-cd25a85864fb'; 224 | const css =`._title_9u0vb_9 { 225 | font-size: 24px; 226 | } 227 | `; 228 | const styles = { 229 | get ['title']() { injectStyles(key, css); return '_title_9u0vb_9 title'; }, 230 | inject() { injectStyles(key, css); } 231 | }; 232 | export default styles; 233 | ``` 234 | 235 | #### None 236 | In this mode the provided module will not provide any way for the styles attaching to the DOM/Node globals. 237 | So you will have to take exported raw `css` code and do that by self. You can use various libraries for that 238 | like `lit-element`. 239 | 240 | ```javascript 241 | const key = 'h6TLzUjXxsnSeNRWMPxAjG'; 242 | const css =`._title_6jm2u_1 { 243 | font-size: 24px; 244 | } 245 | `; 246 | const styles = { 247 | ['title']: '_title_6jm2u_1', 248 | inject() { throw "This stylesheet can't be injected, instead please use exported css constant." } 249 | }; 250 | export { styles, css, key }; 251 | export default styles; 252 | ``` 253 | 254 | #### On demand 255 | This mode gives possibility to call the styles.inject() method for manual on demand styles injection. 256 | ```javascript 257 | import { injectStyles } from 'css-es-modules'; 258 | const key = 'e1ph4XxYADCPaqpZhcgqRT'; 259 | const css =`._title_6jm2u_1 { 260 | font-size: 24px; 261 | } 262 | `; 263 | const styles = { 264 | ['title']: '_title_6jm2u_1', 265 | inject() { injectStyles(key, css); } 266 | }; 267 | export { styles, css, key }; 268 | export default styles; 269 | ``` 270 | #### Instant 271 | This mode is calls the styles.inject() method internally. 272 | ```javascript 273 | import { injectStyles } from 'css-es-modules'; 274 | const key = '63Jw35UDb1fWpxJiCNGuB9'; 275 | const css =`._title_6jm2u_1 { 276 | font-size: 24px; 277 | } 278 | `; 279 | const styles = { 280 | ['title']: '_title_6jm2u_1', 281 | inject() { injectStyles(key, css); } 282 | }; 283 | styles.inject(); 284 | export { styles, css, key }; 285 | export default styles; 286 | ``` 287 | 288 | ### Embedding or ejecting the injector code 289 | In some development workflows you can decide that you do not want to import the injector code from 290 | the dependencies library, in such case you have 2 options: 291 | 292 | #### Embedding 293 | By setting `embed` value of the `inject.script` option you will force transformer to embed the 294 | injector code within each transformed file in place. So the generated file will be much bigger but will 295 | not contain any import statements: 296 | ```javascript 297 | // File generated by the postcss-es-modules plugin. Please do not modify it !!! 298 | /* eslint-disable */ 299 | ... 300 | function injectStyles(stylesheetKey, stylesheetBody, options) { 301 | .... 302 | } 303 | 304 | const key = '7Ut3ZUGvF7pyP5RnMM8pzt'; 305 | const css =`._title_6jm2u_1 { 306 | font-size: 24px; 307 | } 308 | `; 309 | const styles = { 310 | get ['title']() { injectStyles(key, css); return '_title_6jm2u_1'; }, 311 | inject() { injectStyles(key, css); } 312 | }; 313 | export { styles, css, key }; 314 | export default styles; 315 | ``` 316 | This option can be very useful on the development process. 317 | 318 | #### Ejecting 319 | By setting `eject` value of the `inject.script` option you will force transformer to eject the injector 320 | code into provided `inject.scriptEjectPath`. 321 | 322 | Config: 323 | ```javascript 324 | const { postcssEsModules } = require('postcss-es-modules'); 325 | module.exports = (ctx) => ({ 326 | plugins: [ 327 | postcssEsModules({ 328 | inject: { 329 | script: "eject", 330 | scriptEjectPath: __dirname + "/src/styles-inject" 331 | } 332 | }), 333 | ] 334 | }) 335 | ``` 336 | > The `inject.scriptEjectPath` have to be an absolute path. 337 | 338 | Generated code: 339 | ```javascript 340 | import { injectStyles } from './styles-inject/inject-styles'; 341 | const key = 'hT8K48DCnQc2Z9FkPUDzb7'; 342 | const css =`._title_6jm2u_1 { 343 | font-size: 24px; 344 | } 345 | `; 346 | const styles = { 347 | get ['title']() { injectStyles(key, css); return '_title_6jm2u_1'; }, 348 | inject() { injectStyles(key, css); } 349 | }; 350 | export { styles, css, key }; 351 | export default styles; 352 | ``` 353 | Within the `.src/styles-inject/inject-styles` you will find ejected code of injector. 354 | 355 | > The ejection is not overwriting the existing files, you can eject code once, and modify it. 356 | > If you will get clean ejected code, please just delete old files. 357 | 358 | This option can be very useful on the development process. 359 | 360 | ## Next steps 361 | For more information please go to the [api reference](https://majo44.github.io/postcss-es-modules/#/api/) documentation 362 | or to the [examples](https://majo44.github.io/postcss-es-modules/#/examples/) section. 363 | 364 | ## Need a help ? 365 | If you have any problems, issues, ect. please use [github discussions](https://github.com/majo44/postcss-es-modules/discussions). 366 | --------------------------------------------------------------------------------