modular-css
APIYou 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.
Rollup support for modular-css
.
> npm i @modular-css/rollup
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()
]
};
common
File name to use in case there are any CSS dependencies that appear in multiple bundles. Defaults to โcommon.cssโ.
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
.
All other options are passed to the underlying Processor
instance, see the Processor Options.
Webpack 2/3/4 support for modular-css
.
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.
> npm i @modular-css/webpack
// 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"
})
]
});
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.
All other options are passed to the underlying Processor
instance, see the Processor 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 support for modular-css
.
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.
$ npm i @modular-css/browserify
css
Location to write the generated CSS file to.
All other options are passed to the underlying Processor
instance, see the Processor Options.
$ browserify -p [ @modular-css/browserify --css "./style.css" ] entry.js
var browserify = require("browserify"),
build;
build = browserify("./entry.js");
build.plugin("@modular-css/browserify", {
css : "./style.css",
});
@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
.
$ 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
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"
]
});
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.
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.
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 included file
file
, the file path being included by src
resolve
, the default resolver functionResolver functions should either return an absolute path or a falsey value. They must also be synchronous.
Default: See /src/lib/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
});
.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.
.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
.
A JS API for using glob patterns with modular-css
.
$ npm i @modular-css/glob
const glob = require("@modular-css/glob");
// returns a filled-out Processor instance you can use
const processor = await glob({
search : [
"**/*.css"
]
})
search
Array of glob patterns to pass to globule
for searching.
All other options are passed to the underlying Processor
instance, see the Processor Options.
CLI interface to modular-css
.
$ npm i @modular-css/cli
$ 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
Svelte preprocessor support for modular-css
. Process inline <style>
or <link>
references inside your Svelte components using the full power of modular-css
while also providing compile-time optimizations for smaller bundles and even faster runtime performance!
Turns this
<div class="{css.main}">
<h1 class="{css.title}">Title</h1>
</div>
<style>
.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>
You could 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.
> npm i @modular-css/svelte -D
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);
@modular-css/rollup
const rollup = require("rollup").rollup;
const { preprocess, processor } = require("@modular-css/svelte")({
// Processor options
});
const bundle = await rollup({
input : "./Component.svelte",
plugins : [
require("rollup-plugin-svelte")({
preprocess,
}),
require("@modular-css/rollup")({
processor,
common : "common.css",
}),
]
});
// bundle.write will also write out the CSS to the path specified in the `css` arg
bundle.write({
format : "es",
file : "./dist/bundle.js"
});
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,
common : "common.css",
}),
]
};
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.
warnOnUnused
If true
any classes in the source CSS that arenโt directly utilized by the template (or indirectly used via composes
) will be logged out as warnings.
โ ๏ธโก๐โ ๏ธ๐โกโ ๏ธ Please note that this functionality is EXPERIMENTAL and subject to change. The first iteration of this feature is very basic and may lead to considerable false-positive warnings. Use your best judgement before removing CSS! โ ๏ธโก๐โ ๏ธ๐โกโ ๏ธ
All options are passed to the underlying Processor
instance, see the Processor Options.
@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.
> npm i @modular-css/postcss
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
> postcss --config postcss.json input.css
{
"output" : "out.css",
"@modular-css/postcss": {
"json" : "./path/to/output.json"
}
}
> postcss --use modular-css/postcss input.css
json
Write the class composition data to this location on disk.
All other options are passed to the underlying Processor
instance, see Processor Options.
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
hookThe 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.
before
hookSpecify 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
hookThe 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.
processing
hookSpecify 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
hookThe 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);
}
after
hookSpecify 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
hookThe 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.
done
hookSpecify 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")()]
});