modular-css API
You can use modular-css
a variety of ways. The easiest is with one of the bundler integrations, which exist for most popular bundlers in use today.
#Bundlers
#rollup
Rollup support for modular-css is provided by the @modular-css/rollup package
.
#Install
> npm i @modular-css/rollup --save-dev
#Usage
#API
const bundle = await rollup({
input : "./index.js",
plugins : [
require("@modular-css/rollup")()
]
});
#rollup.config.js
import css from "@modular-css/rollup";
export default {
input : "./index.js",
output : {
dest : "./gen/bundle.js",
format : "umd"
},
plugins : [
css()
]
};
#Options
#dev
Enable dev mode. In dev mode the default export of a CSS file will be a Proxy
instead of a bare object. Attempts to access non-existant properties on the proxy will throw a ReferenceError
to assist in catching incorrect usage.
#include
/exclude
A minimatch pattern, or an array of minimatch patterns, relative to process.cwd()
. include
defaults to **/*.css
.
#json
Boolean/String to determine if JSON files containing all exported classes & values should be output. If set to true
will write out to a file named exports.json
. If a String
will write out to that file name. Defaults to false
.
#map
Boolean to determine if inline source maps should be included. Defaults to true
.
To force the creation of external source maps set the value to { inline : false }
.
#meta
Boolean/String to determine if chunk metadata should be output. If set to true will write out a file named metadata.json
. If a String
will write out to that file name. Defaults to false
.
Currently the only metadata being written is CSS dependencies, but that may change in the future.
#namedExports
By default this plugin will create both a default export and named export
s for each class in a CSS file. You can disable this by setting namedExports
to false
.
#styleExport
By default this plugin will extract and bundle CSS in a separate file. If you would like the styles from each imported CSS file to be exported as a string for use in JS, you can enable this by setting styleExport
to true
. If you are using this option the after
& done
hooks will not run against the exported styles, you should perform any additional CSS transformations in the processing
hook instead.
import { styles } from "./styles.css";
Enable styleExport
will also disable the plugin from emitting any assets as well as sourcemaps (unless you explicitly opt-in to sourcemaps via the map
option)
#processor
Pass an already-instantiated Processor
instance to the rollup plugin. It will then add any files found when traversing the modules to it and both the rollup-discovered and any already-existing files will be output in the final CSS.
#empties
Set to a falsey value to disable outputting of empty CSS files. This is most common when a file contains only @value
s and then a minifier is used which strips out the file heading comments. Defaults to true
.
#Shared Options
All other options are passed to the underlying Processor
instance, see the Processor Options.
#vite
Vite support for modular-css is provided by @modular-css/vite
.
NOTE: To work around vite's built-in CSS handling you will need to use a file extension other than .css
for modular-css files. The default for this plugin is .mcss
, but you can use include
to change that.
#Install
> npm i @modular-css/vite --save-dev
#Usage
// vite.config.js
import mcss from "@modular-css/vite";
export default {
plugins : [
mcss()
]
};
#Options
#include
/exclude
A minimatch pattern, or an array of minimatch patterns, relative to process.cwd()
. include
defaults to **/*.mcss
.
#processor
Pass an already-instantiated Processor
instance to the rollup plugin. It will then add any files found when traversing the modules to it and both the rollup-discovered and any already-existing files will be output in the final CSS.
#Shared Options
All other options are passed to the underlying Processor
instance, see the Processor Options.
#sveltekit
SvelteKit support for modular-css is provided by @modular-css/vite
(since SvelteKit is powered by vite).
NOTE: To work around vite's built-in CSS handling you will need to use a file extension other than .css
for modular-css files. The default for this plugin is .mcss
, but you can use include
to change that.
#Install
> npm i @modular-css/vite --save-dev
#Usage
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import mcss from "@modular-css/vite";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit : {
adapter : adapter(),
// Add mcss vite plugin to the underlying vite instance
vite : {
plugins : [
mcss(),
]
},
},
};
export default config;
If you'd like to use the modular-css svelte preprocessor with SvelteKit (and you definitely should because it'll make your site faster) you can use the instructions for @modular-css/svelte
with vite
.
#Options
See the vite
options.
#webpack
Webpack 2/3/4 support for modular-css is provided by @modular-css/webpack
.
NOTE: webpack@5
is untested but may be functional. If you try it out report back in Discord!
This package contains two entry points, you will need to use both in tandem for things to work!
-
@modular-css/webpack/plugin
provides a webpack plugin you can use to transform imported.css
files into lookup objects while outputting CSS to disk. -
@modular-css/webpack/loader
provides the file loader that does the actual transformation on files.
#Install
> npm i @modular-css/webpack --save-dev
#Usage
// webpack.config.js
const path = require("path");
const CSSPlugin = require("@modular-css/webpack/plugin");
module.exports = {
entry : "./input.js",
output : {
path : path.resolve(__dirname, "dist"),
filename : "./output.js"
},
module : {
rules : [{
test : /.css$/,
use : "@modular-css/webpack/loader"
}]
},
plugins : [
new CSSPlugin({
css : "./output.css",
json : "./output.json"
})
]
});
#Plugin Options
#css
Location to write the generated CSS file to, relative to output.path
just like output.filename
#json
Location to write out the JSON mapping file to, relative to output.path
just like output.filename
#processor
Pass an already-instantiated Processor
instance to the Webpack plugin. It will then add any files found when traversing the modules to it and both the Webpack-discovered and any already-existing files will be output in the final CSS.
#Shared Options
All other options are passed to the underlying Processor
instance, see the Processor Options..
#Loader Options
#namedExports
By default this plugin will create both a default export and named export
s for each class in a CSS file. You can disable this by setting namedExports
to false
.
module : {
rules : [{
test : /.css$/,
use : {
loader : "@modular-css/webpack/loader",
options : {
namedExports : false
}
}
}]
},
#browserify
Browserify support for modular-css is provided by @modular-css/browserify
.
This plugin can be combined with the factor-bundle
plugin to output a common CSS file as well as bundle-specific CSS files.
@modular-css/browserify
will use the basedir
passed to browserify as it's cwd
parameter.
#Install
$ npm i @modular-css/browserify --save-dev
#Usage
#CLI
$ browserify -p [ @modular-css/browserify --css "./style.css" ] entry.js
#API
var browserify = require("browserify"),
build;
build = browserify("./entry.js");
build.plugin("@modular-css/browserify", {
css : "./style.css",
});
#factor-bundle
@modular-css/browserify
is fully factor-bundle
aware and will output correctly-partitioned CSS bundles to match the JS bundles created by factor-bundle
.
WARNING: Due to how factor-bundle
works the @modular-css/browserify
must be applied to the Browserify object before factor-bundle
.
CLI
$ browserify home.js account.js -p [ @modular-css/browserify --css gen/common.css ] -p [ factor-bundle -o gen/home.js -o gen/account.js ] -o bundle/common.js
API
var build = browserify([
"./home.js",
"./account.js"
]);
// NOTE modular-css applied before factor-bundle, it won't work otherwise!
build.plugin("@modular-css/browserify", {
css : "./gen/common.css"
});
build.plugin("factor-bundle", {
outputs : [
"./gen/home.js",
"./get/account.js"
]
});
#Options
#css
Location to write the generated CSS file to.
#Shared Options
All other options are passed to the underlying Processor
instance, see the Processor Options.
#Direct Usage
#JS API
The heart of modular-css
, the JS API is a Processor
that will be fed files, transform them, then barf out their bones...
Or, you know, their CSS. One of those for sure.
#Usage
Instantiate a new Processor
instance, call .file(<path>)
or .string(<name>, <contents>)
methods, and then use the returned Promise to get access to the results/output.
const Processor = require("modular-css");
const processor = new Processor({
// See "API Options" for valid options to pass to the Processor constructor
});
// Add entries, either from disk using .file() or as strings with .string()
Promise.all([
processor.file("./entry.css").then((result) => {
// result contains
// id : Absolute path of the file that was added
// file : Absolute path of hte file that was added
// files : metadata about the file hierarchy,
// details : metadata aboutthe file that was added,
// exports : Scoped selector mappings for the file that was added
}),
processor.string("./fake-file.css", ".class { color: red; }")
])
.then(() => {
// Once all files are added, use .output() to get at the rewritten CSS
return processor.output();
})
.then((result) => {
// result.css : Combined CSS output
// result.map : Source map data if enabled
// result.compositions - All files and their composed dependencies
});
#processor.file(<path>)
Pass a file path to the processor instance for processing. Returns a promise that resolves to details about the file that was added.
#processor.string(<name>, <css>)
Pass a name and css
string to the processor instance for processing. Returns a promise that resolves to details about the file that was added.
#processor.root(<name>, Root)
Add a file to the Processor
instance using an already-parsed Postcss Root
object to avoid unnecessary reparsing. Returns a promise that resolves to details about the file that was added.
#Processor Options
#rewrite
Enable or disable the usage of postcss-url
to correct any URL references within the CSS. The value of rewrite
will be passed to postcss-url
to allow for configuration of the plugin.
Default: true
// On by default, so this will have rewritten url() references
new Processor();
// A falsey value will disable the usage of postcss-url,
// url() references will not be changed
new Processor({ rewrite : false });
// Configure postcss-url
new Processor({
rewrite : {
url : "inline"
}
});
#map
Enable source map generation. Can also be passed to .output()
.
Default: false
// Inline source map
new Processor({
map : true
});
// External source map
new Processor({
map : {
inline : false
}
});
#cwd
Specify the current working directory for this Processor instance, used when resolving composes
/@value
rules that reference other files.
Default: process.cwd()
new Processor({
cwd : path.join(process.cwd(), "/sub/dir")
})
#namer
Specify a function (that takes filename
& selector
as arguments) to produce scoped selectors.
Can also pass a string that will be require()
'd and evaluated, it should return the namer function.
Default: Function that returns "mc" + unique-slug(<file>) + "_" + selector
new Processor({
namer : function(file, selector) {
return file.replace(/[:/\ .]/g, "") + "_" + selector;
}
});
// or
new Processor({
namer : "@modular-css/shortnames"
});
#resolvers
If you want to provide your own file resolution logic you can pass an array of resolver functions. Each resolver function receives three arguments:
src
, the file that includedfile
file
, the file path being included bysrc
resolve
, the default resolver function
Resolver functions should either return an absolute path or a falsey value. They must also be synchronous.
Default: See resolve.js for the default implementation.
new Processor({
resolvers : [
(src, file, resolve) => ,
require("@modular-css/path-resolver")(
"./some/other/path"
),
],
});
#loadFile
Specify a function that will be responsible for taking a file path and returning the text contents of the file. The function can be synchronous, return a promise, or use async
/await
.
The default implementation uses fs
to read files off the local filesystem.
new Processor({
async loadFile(id) {
const text = await goGetItFromSomewhere(id);
return text;
},
});
#exportGlobals
By default identifiers wrapped in :global(...)
are exported for ease of referencing via JS. By setting exportGlobals
to false
that behavior can be deactivated. Mostly useful to avoid warnings when global CSS properties are not valid JS identifiers.
new Processor({
exportGlobals : false
});
/* exportGlobals: true */
.a {}
:global(.b) {}
/* Outputs
{
"a" : "mc12345_a",
"b" : "b"
}
*/
/* exportGlobals: false */
.a {}
:global(.b) {}
/* Outputs
{
"a" : "mc12345_a"
}
*/
#dupewarn
Boolean value that determines whether or not the Processor instance will issue warnings for duplicate seeming files (identical path with only case variations). If you're using a case-sensitive filesystem feel free to disable by setting it to false
.
Default: true
, so warnings are emitted.
new Processor({
dupewarn : true
});
#Properties
#.files
Returns an object keyed by absolute file paths of all known files in the Processor
instance.
#.options
Returns the options object passed to the Processor
augmented with the defaults.
#Processor API
#.string(file, css)
Returns a promise. Add file
to the Processor
instance with css
contents.
#.file(file)
Returns a promise. Add file
to the Processor
instance, reads contents from disk using fs
.
#.output([files])
Returns a promise. Finalize processing of all added CSS and create combined CSS output file. Optionally allows for combining a subset of the loaded files by passing a single file or array of files.
WARNING: Calling .output()
before any preceeding .file(...)
/.string(...)
calls have resolved their returned promises will return a rejected promise. See usage for an example of correct usage.
#.remove([files])
Remove files from the Processor
instance. Accepts a single file or array of files.
#.fileDependencies([file])
Returns an array of file paths. Accepts a single file argument to get the dependencies for, will return entire dependency graph in order if argument is omitted.
#.invalidate(file)
Marks a file as stale, if that file is re-added either directly or as a dependency it will be reloaded from disk instead of the Processor cache.
#.has(file)
Checks if the Processor instance knows about a file.
#.normalize(file)
Uses the built-in path normalization settings to normalize the case of a file path.
#.resolve(src, file)
Resolves file
from src
, using any of the specified resolvers
.
#globbing API
A JS API for using glob patterns with modular-css
.
#Install
$ npm i @modular-css/glob
#Usage
const glob = require("@modular-css/glob");
// returns a filled-out Processor instance you can use
const processor = await glob({
search : [
"**/*.css"
]
})
#Options
search
Array of glob patterns to pass to globule
for searching.
#Shared Options
All other options are passed to the underlying Processor
instance, see the Processor Options.
#CLI
CLI interface to modular-css
.
#Install
$ npm i @modular-css/cli
#Usage
$ modular-css [options] <glob>...
Options
--dir, -d <dir> Directory to search from [process cwd]
--out, -o <file> File to write output CSS to [stdout]
--json, -j <file> File to write output compositions JSON to
--map, -m Include inline source map in output
--rewrite, -r Control rewriting of url() references in CSS
--help Show this help
#Other Tools
#svelte preprocessor
Svelte preprocessor support for modular-css
.
Process inline <style type="text/m-css">
or <link>
or import styles from "./foo.css";
references inside your Svelte components using the full power of modular-css
. Dynamic references will be replaced where possible with static ones, allowing for greater compile-time optimizations, smaller bundles, and even faster runtime performance.
#Example
#<style>
processing
Turns this
<div class="{css.main}">
<h1 class="{css.title}">Title</h1>
</div>
<!-- type attribute is **required** -->
<style type="text/m-css">
.main {
/* ... */
}
.title {
/* ... */
}
</style>
into this by running it through modular-css
and then statically replacing everything possible for zero-cost run-time styling.
<div class="abc123_main">
<h1 class="abc123_title">Title</h1>
</div>
#<link>
processing
You can also use <link href="./file.css" />
tags to reference CSS external to the component, but the component must have only one <link>
(links to URLs are fine and ignored) or <style>
. Combining them is not supported.
<link href="./style.css" />
<div class="{css.main}">
<h1 class="{css.title}">Title</h1>
</div>
#import
processing
It'll even check your ES Module import
statements and use those.
<div class="{styles.main}">
<h1 class="{styles.title}">Title</h1>
</div>
<script>
// Only default exports are supported, but it will use the name you
// give it instead of the hardcoded css like the other approaches
import styles from "./style.css";
</script>
#Install
> npm i @modular-css/svelte -D
#Usage
You can use the svelte preprocessor in almost every environment where you're using svelte. Here are a few examples of common usage to help get you started.
#via svelte.preprocess()
const filename = "./Component.svelte";
const { processor, preprocess } = require("@modular-css/svelte")({
// Processor options
// ...
});
const processed = await svelte.preprocess(
fs.readFileSync(filename, "utf8"),
{ preprocess, filename },
);
const result = await processor.output();
fs.writeFileSync("./dist/bundle.css", result.css);
#via rollup
// rollup.config.js
const { preprocess, processor } = require("@modular-css/svelte")({
// Processor options
// ...
});
module.exports = {
input : "./Component.svelte",
output : {
format : "es",
file : "./dist/bundle.js"
},
plugins : [
require("rollup-plugin-svelte")({
preprocess,
}),
require("@modular-css/rollup")({
processor,
}),
]
};
#via vite
// vite.config.js
import { defineConfig } from "vite";
import preprocessor from "@modular-css/svelte";
import mcssVite from "@modular-css/vite";
// Set up the svelte preprocessor and get a reference to the
// mcss processor so we can pass it into the vite plugin
const { preprocess, processor } = preprocessor({
// Default is .css but we need .mcss because of vite
include : /.mcss$/i,
// Other processor options
// ...
});
export default defineConfig({
plugins : [
mcssVite({
processor,
}),
],
});
#Options
#strict
If true
whenever a missing replacement is found like {css.doesnotexist}
an error will be thrown aborting the file processing. Defaults to false
.
#procesor
Pass a previously-created @modular-css/processor
instance into the preprocessor. Will not pass through any other options to the processor if this is set, but strict
will still be honored by the preprocessor.
#Shared Options
All options are passed to the underlying Processor
instance, see the Processor Options.
#postcss
@modular-css/postcss
provides a postcss plugin that can be used like any other. It will output a message with a type
of modular-css-exports
containing all the exported class compositions.
#Install
> npm i @modular-css/postcss
#Usage
#API
const postcss = require("postcss");
const processor = postcss([
require("@modular-css/postcss")({
json : "./path/to/output.json"
})
]);
const result = await processor.process("<css>")
// result.css
// result.map
// result.messages.find((msg) => msg.type === "modular-css-exports")
// etc
#config
> postcss --config postcss.json input.css
{
"output" : "out.css",
"@modular-css/postcss": {
"json" : "./path/to/output.json"
}
}
#CLI
> postcss --use @modular-css/postcss input.css
#Options
#json
Write the class composition data to this location on disk.
#Shared Options
All other options are passed to the underlying Processor
instance, see Processor Options.
#Other Utilities
#Path Aliasing
A resolver for modular-css that will let you resolve file references against named aliases. Useful to avoid code like
@value foo from "../../../../../../../../some/other/directory/file.css";
which is annoying to write, annoying to read, and also super-brittle.
#Install
$ npm i @modular-css/path-aliases --save-dev
#Usage
Pass as part of the resolvers
array in the modular-css
options (via JS API/Rollup/Browserify/WebPack/etc). When modular-css
is trying to resolve @value
or composes
file references it'll replace the alias keys with their path value for file lookups.
import Processor from "@modular-css/processor";
import aliases from "@modular-css/path-aliases";
const processor = new Processor({
resolvers : [
aliases({
aliases : {
one : "./path/one",
path : "../../some/other/path"
}
})
]
});
which allows you to write CSS like this.
@value one from "one/one.css";
.a {
composes: path from "path/path.css";
}
#Options
#aliases
A object
consisting of key/value pairs of alias names to file paths. Paths can be relative to the cwd
of the Processor
instance or absolute paths.
#Namer
Tiny classnames for modular-css
production builds!
#Install
> npm install @modular-css/shortnames
#Usage
#JS API
const Processor = require("@modular-css/processor");
const processor = new Processor({
namer : require("@modular-css/shortnames")()
});
// ...
#Browserify
build.plugin("@modular-css/browserify", {
css : "./style.css",
namer : require("@modular-css/shortnames")()
});
// ...
#Rollup
rollup({
entry : "./index.js",
plugins : [
require("@modular-css/rollup")({
css : "./gen/index.css",
namer : require("@modular-css/shortnames")()
})
]
});
#Example output
/* one.css */
.alert {}
.notification {}
/* two.css */
.title {}
.heading .subheading {}
becomes
/* output.css */
.AA {}
.AB {}
.BA {}
.BB .BC {}
#Path resolver
A resolver for modular-css
that will let you resolve file references against arbitrary paths. Useful to avoid code like
@value foo from "../../../../../../../../some/other/directory/file.css";
which is annoying to write, annoying to read, and also super-brittle.
#Install
$ npm i @modular-css/path-resolver
#Usage
Pass as part of the resolvers
array in the modular-css
options (via JS API/Rollup/Browserify/WebPack/etc). When modular-css
is trying to resolve @value
or composes
file references it'll use the default node resolution algorithm against whichever paths you specified.
const Processor = require("@modular-css/processor");
const paths = require("@modular-css/path-resolver");
const processor = new Processor({
resolvers : [
paths({
paths : [
"./path/one",
"../../some/other/path"
]
})
]
});
#Options
#paths
An array of string file paths, they can be relative to the cwd
of the Processor
instance or absolute paths.
#Lifecycle Hooks
There are 4 built-in ways to extend the functionality of modular-css
, the lifecycle hooks. They all can be used to add any number of PostCSS Plugins to modular-css
at specific points in the processing cycle.
#before
The before
hook is run before a CSS file is ever processed by modular-css
, so it provides access to rewrite files if they aren't actually CSS or contain non-standard syntax. Plugins like postcss-nested
go well here.
#Usage
Specify an array of PostCSS plugins to be run against each file before it is processed. Plugin will be passed a from
option.
new Processor({
before : [ require("postcss-import") ]
});
#processing
The processing
hook is run after modular-css
has parsed the file, but before any response to processor.string
or processor.file
is returned. Plugins in this hook have a special power: they can change the exports of the file.
This works by having the plugin push an object onto the result.messages
array. Here's a very simple example:
new Processor({
processing : [
(css, result) => {
result.messages.push({
plugin : "modular-css-exporter",
exports : {
a : true,
b : false
}
});
}
]
})
The plugin
field must begin with "modular-css-export", and the exports
field should be the object to be mixed into the exports of the CSS file. It will be added last, so it can be used to override the default exports if desired.
#Usage
Specify an array of PostCSS plugins to be run against each file during processing. Plugin will be passed a from
option.
new Processor({
processing : [ require("postcss-import") ]
});
#after
The after
hook is run once the output location for the CSS is known, but before all the files are combined. By default it will run postcss-url
to rebase file references based on the final output location, but this can be disabled using the rewrite
option.
Since all manipulations on the file are complete at this point it is a good place to run plugins like postcss-import
to inline @import
rules. The rules inlined in this way won't be scoped so it's a convenient way to pull in 3rd party code which can be included in the selector heirarchy via composes
.
@import "bootstrap.css";
/* Will export as "btn .abc123_button" */
.button {
composes: global(btn);
}
#Usage
Specify an array of PostCSS plugins to be run after files are processed, but before they are combined. Plugin will be passed a to
and from
option.
Default: []
⚠ postcss-url
automatically runs after any plugins defined in the after
hook. To disable it use the rewrite
option.
new Processor({
after : [ require("postcss-someplugin") ]
});
#done
The done
hook is run after all of the constituent files are combined into a single stylesheet. This makes it a good place to add tools like cssnano
that need access to the entire stylesheet to be able to accurately optimize the CSS.
#Usage
Specify an array of PostCSS plugins to be run against the complete combined CSS. Plugin will be passed a to
option.
new Processor({
done : [ require("cssnano")()]
});