Cssy

View the Project on GitHub nodys/cssy

cssy Build Status NPM version

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

Features

Want a nice cocktail recipe ? Use source map in combination with cssy's live source reload feature and chrome's in-browser edition.

Installation

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" ] ] }
}

Options

In you package.json you can add some options for the current package ([ "cssy", { /* options */ } ])

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]
      }
    ]
  ]}

Global configuration

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.

Internal workflow

  1. Parser Cssy try each parser to transform any kind of source to css (from stylus, less, sass, etc.) with a source map.
  2. Global pre-processor: Cssy call each Global pre-processor
  3. processor: Cssy call each local processor
  4. Global post-processor: Cssy call each Global post-processor
  5. @import: If enable cssy extract @import at-rules (see Import css)
  6. minify: If enable cssy minify css source
  7. source map: If enable cssy add the source-map
  8. live reload: If enable cssy add live source reload client to the generated bundle

Context object

Each function that transform a css source (parser, global pre/post processor, processor), receive a context object:

Functions

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;
}

Css meta-language: sass, stylus, less, ...

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

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.

Processor

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)

Processor example

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

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

Live source reload

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);

Use your own file watcher

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))

Import css

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

Regex filter

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:

{
  // ... package.json ...
  "browserify": {
    "transform": [
      // Match all *.mycss files in src/css
      [ "cssy", {  match: ["src\\/css\\/.*\\.mycss$","i"] } ]
    ]
  }
}

Cssy plugin

Cssy can be used as a browserify plugin to set global behaviors:

Using browserify api

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
  }
})

Using browserify command

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'                          \
    ]                                                     \
]

Remedy

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.

Enable remedy:

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:

Remedy options are the same of the cssy's transform options.

Symptoms indicating a need for a remedy

Usually, when browserify encounter a source he can't handle correctly (such css) you see something like SyntaxError: Unexpected token ILLEGAL


CssyBrowser API

CssyBrowser()

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:

return {Object} See CssyBrowser.insert()

CssyBrowser.insert([to], [media])

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:

return {Object} An object with:

CssyBrowser.update(src)

Update current css source

Each inject style element are updated too

Parameters:

CssyBrowser.onChange(listener)

Listen for css source changes

Parameters:

CssyBrowser.offChange(listener)

Detach change listener

Parameters:

CssyBrowser.getImports()

Get the imported CssyBrowser instance (based on @import at-rules)

return {Array} Array of CssyBrowser instance

CssyBrowser.toString()

Override default toString()

return {String} The current css source


License: The MIT license