postcss-es-modules
v2.2.0
Published
Universal styles injection to work with postcss-es-modules
Downloads
26,854
Readme
postcss-es-modules
Postcss plugin which transform css files in to the .js/.ts css modules.
What is this? For example, you have the following CSS:
/* ./component.module.css */
.article {
font-size: 16px;
}
.title {
font-size: 24px;
}
.title:hover {
color: red;
}
After the transformation it will become like this:
/* ./component.module.css.js */
// File generated by the postcss-es-modules plugin. Please do not modify it !!!
import { injectStyles } from 'css-es-modules';
const key = '77d26384-99a7-48e0-9f08-cd25a85864fb';
const css =`._article_9u0vb_1 {
font-size: 16px;
}
._title_9u0vb_9 {
font-size: 24px;
}
._title_9u0vb_9:hover {
color: red;
}
`;
const styles = {
get ['article']() { injectStyles(key, css); return '_article_9u0vb_1 article'; },
get ['title']() { injectStyles(key, css); return '_title_9u0vb_9 title'; },
inject() { injectStyles(key, css); }
};
export default styles;
Then that code can be referred from your component.
/* ./component.jsx */
import styles from './component.module.css.js';
export const Component = () => (
<div className={styles.article}>
<div className={styles.title}>Title</div>
</div>
)
Description
This plugin allows you to write the styles in css/sass/less/... syntax and transform it on the build time in the efficient js/ts code which can be simple referred from your component/application code. That code is responsible for attach the styles into the DOM (or to SSR pipeline) in most possible efficient way. This plugin also generates scoped class names, so you can be sure that your components styles will not leak in to the other parts of the application.
Features
- Zero runtime dependencies (on eject or embed mode, pleas look at the options)
- Framework agnostic
- Javascript and Typescript support
- Server side rendering
- Lazy, on demand, instant or none styles injection
- Rich (optional) configuration
Installation
npm i postcss postcss-es-modules --save-dev
Usage
Configure the postcss to use the plugin:
// postcss.config.js
const { postcssEsModules } = require('postcss-es-modules');
module.exports = (ctx) => ({
plugins: [
postcssEsModules({
// options
}),
]
})
Please remember that this plugin is generating non css syntax, so it should be last on the list of the used plugins.
This plugin is internally using postcss-modules plugin, so you do not have to add it by self.
With the postcss-cli
postcss src/**/*.module.css --dir src --base src --ext css.js
This command will generate the .js
files by the post-cli, directly to your src directory.
With the postcss-cli and typescript
// postcss.config.js
const { postcssEsModules } = require('postcss-es-modules');
module.exports = (ctx) => ({
plugins: [
postcssEsModules({ inject: { scriptType: 'ts' } }),
]
})
postcss src/**/*.module.css --dir src --base src --ext css.ts
With the webpack/rollup/...
There is nothing unique to use of this plugin with bundlers.
With the webpack/rollup/... and typescript
If you are using typescript, and you do not want to generate es css modules ahead but just let the bundler do the job, for solving typescript compilation errors please add global declaration to your project, like that:
/* ./global.d.ts */
declare module "*.css" {
type Styles = { [className: string]: string; } & { inject(): void; }
export const styles: Styles;
export const key: string;
export const css: string;
export default styles;
}
This will say to the compiler that each *.css
import should be mapped to declared type.
Other usage examples
You can find more examples here.
Options
Here is the list of all available options, for more details please go to Recipes.
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| inject
| object
| - | The configuration fo the styles injection |
| inject.useNounce
| string
| - | The style nounce key |
| inject.useConstructableStylesheet
| boolean
| true | Use Constructable Stylesheet for styles injection to DOM if the browser supports Constructable Stylesheet |
| inject.useStyleTag
| boolean
| true | Use <style> tag for styles injection to DOM |
| inject.useNodeGlobal
| boolean
| true | Enable node.js global for collecting the styles, required for server side rendering |
| inject.moduleType
| 'cjs'
/ 'esm'
| 'esm'
| Generated code modules type. Options: - esm
: ecmascript 6 modules - cjs
: commonjs |
| 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 |
| 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 |
| inject.scriptType
| 'ts'
/ 'js'
| 'js'
| The generated script type. Options:- ts
: typescript- js
: javascript |
| inject.scriptEjectPath
| string
| - | The path where the styles injector script code will be ejected. This option is required if inject.script
is eject
|
| inject.custom
| object
| - | The custom style injector configuration |
| inject.custom.importStatement
| string
| - | The custom style injector statement for import required dependencies. Eg: "import { injectMyStyles } from 'somelib'";
|
| 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 stylesheetEg: "injectMyStyles(css)"
|
| modules
| object
| - | The CSS Modules options. It is inherits all options from the postcss-modules expect getJSON
|
| modules.attachOriginalClassName
| boolean
| false | The CSS Modules options. If you want to still use the original class name next to local one |
Recipes
Server side rendering
Example of server side rendering with the React and Express.js:
/* ./index.css */
.app {
background-color: red;
}
// ./index.js
const express = require('express');
const { createElement } = require('react');
const { renderToString } = require('react-dom/server');
const { collectStyles } = require('css-es-modules');
// imports the transformed css file
const { styles } = require('./index.module.css.js');
const app = express();
// example component to render
const App = () => createElement('div', { className: styles.app}, "Hello Word");
/**
* App template
* @param styles - the StylesCollector object
* @param html - prerendered app markup
*/
const template = (styles, html) => `
<html lang="en">
<head>${styles.html}</head>
<body><div id="app">${ html }</div></body>
</html>`;
// handling request
app.get('/', (req, res) => {
res.send(
// render template
template(
// firstly start collecting styles
collectStyles(),
// then render application
renderToString(createElement(App))));
});
// starting app
app.listen(3000);
To run this example you have to transpile css file ahead. With the inject.moduleType
set to cjs
.
The full working example you will find here.
Lazy/On demand/Instant/None styles injection
There are few modes how the styles injection can work.
Lazy (default)
The lazy injection means that the generated stylesheet will be not attached to the DOM/Node globals until some code will not call the getter of className. So even you are importing module with styles that styles are not applied to the application until some of the components will not use the class. This technique is very useful beacause on the server side rendering we will reder just critical stylesheets.
import { injectStyles } from 'css-es-modules';
const key = '77d26384-99a7-48e0-9f08-cd25a85864fb';
const css =`._title_9u0vb_9 {
font-size: 24px;
}
`;
const styles = {
get ['title']() { injectStyles(key, css); return '_title_9u0vb_9 title'; },
inject() { injectStyles(key, css); }
};
export default styles;
None
In this mode the provided module will not provide any way for the styles attaching to the DOM/Node globals.
So you will have to take exported raw css
code and do that by self. You can use various libraries for that
like lit-element
.
const key = 'h6TLzUjXxsnSeNRWMPxAjG';
const css =`._title_6jm2u_1 {
font-size: 24px;
}
`;
const styles = {
['title']: '_title_6jm2u_1',
inject() { throw "This stylesheet can't be injected, instead please use exported css constant." }
};
export { styles, css, key };
export default styles;
On demand
This mode gives possibility to call the styles.inject() method for manual on demand styles injection. This mode should be used in case when you wants to use Shardow DOM.
import { injectStyles } from 'css-es-modules';
const key = 'e1ph4XxYADCPaqpZhcgqRT';
const css =`._title_6jm2u_1 {
font-size: 24px;
}
`;
const styles = {
['title']: '_title_6jm2u_1',
inject(shadowRoot) { injectStyles(key, css, undefined, shadowRoot); }
};
export { styles, css, key };
export default styles;
As you see the generated inject function accept optional shadowRoot
parameter,
by that within your WebComponent you will be able to inject styles into shadowRoot.
Instant
This mode is calls the styles.inject() method internally.
import { injectStyles } from 'css-es-modules';
const key = '63Jw35UDb1fWpxJiCNGuB9';
const css =`._title_6jm2u_1 {
font-size: 24px;
}
`;
const styles = {
['title']: '_title_6jm2u_1',
inject() { injectStyles(key, css); }
};
styles.inject();
export { styles, css, key };
export default styles;
Embedding or ejecting the injector code
In some development workflows you can decide that you do not want to import the injector code from the dependencies library, in such case you have 2 options:
Embedding
By setting embed
value of the inject.script
option you will force transformer to embed the
injector code within each transformed file in place. So the generated file will be much bigger but will
not contain any import statements:
// File generated by the postcss-es-modules plugin. Please do not modify it !!!
/* eslint-disable */
...
function injectStyles(stylesheetKey, stylesheetBody, options) {
....
}
const key = '7Ut3ZUGvF7pyP5RnMM8pzt';
const css =`._title_6jm2u_1 {
font-size: 24px;
}
`;
const styles = {
get ['title']() { injectStyles(key, css); return '_title_6jm2u_1'; },
inject() { injectStyles(key, css); }
};
export { styles, css, key };
export default styles;
This option can be very useful on the development process.
Ejecting
By setting eject
value of the inject.script
option you will force transformer to eject the injector
code into provided inject.scriptEjectPath
.
Config:
const { postcssEsModules } = require('postcss-es-modules');
module.exports = (ctx) => ({
plugins: [
postcssEsModules({
inject: {
script: "eject",
scriptEjectPath: __dirname + "/src/styles-inject"
}
}),
]
})
The
inject.scriptEjectPath
have to be an absolute path.
Generated code:
import { injectStyles } from './styles-inject/inject-styles';
const key = 'hT8K48DCnQc2Z9FkPUDzb7';
const css =`._title_6jm2u_1 {
font-size: 24px;
}
`;
const styles = {
get ['title']() { injectStyles(key, css); return '_title_6jm2u_1'; },
inject() { injectStyles(key, css); }
};
export { styles, css, key };
export default styles;
Within the .src/styles-inject/inject-styles
you will find ejected code of injector.
The ejection is not overwriting the existing files, you can eject code once, and modify it. If you will get clean ejected code, please just delete old files.
This option can be very useful on the development process.
Next steps
For more information please go to the api reference documentation or to the examples section.
Need a help ?
If you have any problems, issues, ect. please use github discussions.