A browserify transform for css (with vitamins): Use css sources like any other module but without forgo the benefits of pre-processing and live source reload.
var myCss = require('foo/my.css')
myCss() // Inject the css in document
myCss(elsewhere) // To inject css elsewhere
myCss(elsewhere, 'tv') // Media query...
myCss.update('h1 { }') // Update source for all injected instances
myCss.onChange(function(css) { }) // Watch for change (hey, you like live source reload ?)
console.log('Source: %s', myCss) // Use as a string
See cssy, htmly and lrio exemple
require('foo/common.css')
.url()
rebasing.require()
and inject another css module for you.Want a nice cocktail recipe ? Use source map in combination with cssy's live source reload feature and chrome's in-browser edition.
npm install --save cssy
Then add cssy transform to your package.json
. Encapsulate your css processing logic inside your package: use browserify.transform field.
{
// ...
"browserify": { "transform": [ "cssy" ] ] }
}
In you package.json
you can add some options for the current package ([ "cssy", { /* options */ } ]
)
parser
{Array|String}: One or many path to module that export a parser
processor
{Array|String}: One or many path to module that export a Css processor
import
{Boolean}: Allow @import behavior (default: true)match
{String|Array}: Filter which file cssy must handle. See Regex filter
Path inside parser
and processor
options are either relative to package.json or npm package.
Example:
// ...
"browserify": { "transform": [
[ "cssy",
{
"parser" : [
"./mySassParser",
"./myLessParser"
],
"processor": "./support/myCssProcessor",
"import" : false,
"match" : ["\\.(css|mycss)$",i]
}
]
]}
At application level you can change some global cssy behaviors
var cssy = require('cssy')
cssy.config({
// Enable css minification (default: false)
minify: false,
// Enable source map in the generated source (default: true)
sourcemap: true
})
Css minification is done with CSSWring. Feel free to use another one inside a global post-processor.
@import
at-rules (see Import css)Each function that transform a css source (parser, global pre/post processor, processor), receive a context object:
src
{String}: Css sourcefilename
{String}: Css source filepath (relative to process.cwd()
)config
{Object}: The cssy's transform configuration
map
{Object}: The css source map (undefined for the parsers: as they must provide it)Each function used in cssy (parser, global pre/post processor, processor) use the same API and may be asynchronous or synchronous.
// Asynchronous parser, processor, pre/post processor:
module.exports = function (ctx, done) {
// ... change the ctx object ...
// Return ctx
done(null, ctx)
// Or if something wrong happened
done(new Error('oups...'))
}
// Synchronous parser, processor, pre/post processor:
module.exports = function (ctx) {
// ... change the ctx object ...
return ctx;
}
Cssy provide parsers for common css meta-languages. But, cssy does not depend on them so you can install only what you use, and control version according to you needs. For instance, to use less sources with cssy, add less to your package dependencies (npm install --save less
).
If you need something more specific, you may add your own parser.
Parser's job is to read a source from any format (sass, stylus, less, whatever), and to return a css source and a source-map.
For cssy, a processor is an function that transform a cssy context object to another cssy context object. Like for browserify's transform cssy processor are applied only on the sources of the current package. (See too Global pre/post processor)
Here with postcss but you can use any other library (Preferably with good source-map support)
var autoprefixer = require('autoprefixer');
var postcssCalc = require('postcss-calc');
var customProperties = require("postcss-custom-properties")
var postcss = require('postcss');
module.exports = function(ctx, done) {
var result = postcss()
.use(customProperties())
.use(postcssCalc())
.use(autoprefixer.postcss)
.process(ctx.src, {
map : { prev : ctx.map } // Preserve source map !
});
ctx.src = result.css;
ctx.map = result.map.toJSON();
done(null, ctx)
}
Global pre/post processor must be used only at application level (where you bundle your application) for things like global url()
rebasing, optimizations for production, etc. Pre/post processor share the same api than cssy processor.
var cssy = require('cssy')
// Add one or many pre-processors
cssy.pre(function(ctx, done) {
// ... Applied on every source handled by cssy
})
// Add one or many post-processors
cssy.post(function(ctx, done) {
// ... Applied on every source handled by cssy
})
See too use cssy plugin to add pre/post processor
Cssy provide a tiny live source reload mechanism based on websocket for development purpose only.
Classic live source reload for css usually require that all (builded) css source are accessible via an url. This is not convenient with bundled css sources that comes from anywhere in the dependency tree. And this is almost impraticable if css sources are injected somewhere else than the main document (shadow root for instance) without reloading all the page.
Just attach cssy to the http(s) server that serve the application, cssy will do the rest: (browserify bundler must in the same process):
var http = require('http')
var cssy = require('cssy')
var server = http.createServer(/* your application */).listen(8080);
cssy.live(server);
To trigger a change on a css source, just call the change listener returned by cssy.attachServer()
:
var cssyChangeListener = cssy.attachServer(server);
cssyChangeListener('path/to/source.css');
Here is an example with chockidar :
require('chokidar')
.watch('.', {ignored: /[\/\\]\./})
.on('change', cssy.attachServer(server))
Unless you set the import option to false, @import
at-rules works like a require()
.
For instance with two css file, app.css
and print.css
:
/* app.css */
@import "print.css" print;
body { /* ... */ }
/* print.css */
.not-printable { display: none }
When you inject app.css
, print.css
will be injected too with the media query print
Default filter is all css files and most meta-css-language files: /\.(css|sass|scss|less|styl)$/i
. You can set the match option to filter which file cssy must handle.
match
option is either a String or an Array used to instantiate a new regular expression:
"\\.myCss$"
become /\.myCss$/
["\\.myCss$","i"]
become /\.myCss$/i
{
// ... package.json ...
"browserify": {
"transform": [
// Match all *.mycss files in src/css
[ "cssy", { match: ["src\\/css\\/.*\\.mycss$","i"] } ]
]
}
}
Cssy can be used as a browserify plugin to set global behaviors:
var browserify = require('browserify');
var cssy = require('cssy');
var b = browserify('./app.css')
b.plugin(cssy, {
// Global configuration:
minify: true,
sourcemap: false,
// See live source reload:
live: myHttpServerInstance,
// See pre/post processor (function or path to a processor module):
pre: [
'./myPreprocessor',
function anotherPreprocessor(ctx) { return ctx }
],
post: 'post-processor',
// See remedy:
// - Use current package cssy config:
remedy: true,
// - Use set remedy config:
remedy: {
processor: './processor' // (function or path to a processor module)
match: /css$/i,
import: false
}
})
Browserify use subarg syntaxe. See too browserify plugin
browserify ./app.css -p [ \
cssy \
--minify \
--no-sourcemap # sourcemap = false \
--live './server.js' \
--pre './myPreprocessor' \
--pre 'another-preprocessor' # repeat for an array \
--post 'post-processor' \
--remedy # enable remedy ... \
--remedy [ # ... or use subarg \
--processor './processor' \
--no-import \
--match 'css$' --match 'i' \
] \
]
Cssy's remedy is a solution to use libraries that does not export their css sources as commonjs modules
Browserify transforms are scoped to the current package, not its dependency: and that's a good thing !
From module-deps readme: (...) the transformations you specify will not be run for any files in node_modules/. This is because modules you include should be self-contained and not need to worry about guarding themselves against transformations that may happen upstream.
However, if you want to use some parts of a library like semantic-ui, cssy provide a solution at application level (where you bundle your application): the remedy global transform.
If remedy options is true
cssy will use the cssy configuration from the package.json
closest to the current working directory :
// Add remedy to a browserify instance (bundler)
bundler.plugin(cssy, { remedy: true })
But you can set specific options has described in the cssy plugin section
// Add remedy to a browserify instance (bundler)
bundler.plugin(cssy, { remedy: { processor: 'myprocessor' } })
Remedy options are the same of the cssy's transform options.
Usually, when browserify encounter a source he can't handle correctly (such css) you see something like SyntaxError: Unexpected token ILLEGAL
CssyBrowser is the object exported by a module handled by cssy:
var myCss = require('./my.css');
// myCss is a CssyBrowser
A CssyBrowser instance can be used as:
CssyBrowser.toString()
that return the css source.myCss()
,
myCss(element)
or myCss(element, 'media query')
.return {Object
}
See CssyBrowser.insert()
Insert css source in the DOM
Create and append a <style>
element in the dom at to
. If the source contain
@import
at-rules, imported CssyBrowser modules are injected too.
The content of all the injected <style>
element is binded to css source
change: When .update()
is called by you or by the cssy's live source
reload server.
Parameters:
[to] {HTMLElement
|ShadowRoot
}
Where to inject the style. Default to document's head.
[media] {String
}
Set the media attribute of the injected style tag
return {Object
}
An object with:
element
{HTMLElement}: The style
element insertedchildren
{Array}: The other CssyBrowser instances imported
and injected by this instanceremove
{Function}: Remove injected style
element and all
other CssyBrowser instances imported
other CssyBrowser instances importedUpdate current css source
Each inject style element are updated too
Parameters:
String
}Listen for css source changes
Parameters:
Function
}
Change listener. Receive new css source
Change listener. Receive new css sourceDetach change listener
Parameters:
Function
}Get the imported CssyBrowser instance (based on
@import
at-rules)
return {Array
}
Array of CssyBrowser instance
Override default toString()
return {String
}
The current css source
License: The MIT license