blink
v5.0.1
Published
Blink converts Node.js modules into CSS and provides a CSS Authoring Framework, with BEM support.
Downloads
179
Readme
Blink converts Node.js modules into CSS.
Introduction
If you landed here, you're probably a front-end web developer of some kind. You know how to write JavaScript. You might even have a favorite CSS preprocessor. Sure, they allow you to write variables and functions in some form or another, but they require you learn their domain-specific language, which often falls short of a full-blown language. You scour their documentation, struggling to find a solutions to problems you already know how to solve in JavaScript. We keep looking for ways to introduce logic into our CSS, so why not just use JavaScript?
Compass provides some great cross-browser mixins to takes care of vendor prefixes, but it falls short in that you have to remember to use them and they add considerable bloat if not implemented properly (i.e., @extend). Blink aims to solve this problem with overrides that enforce developers to use them without even knowing it. For example, overriding the display
property would mean that anyone using display: inline-block;
would automatically extend the inlineBlock extender. This means you can use the latest and greatest techniques of CSS development without trying to remember when you need vendor prefixes for certain CSS declarations.
With blink, browser support is a configuration setting, so when your browser support requirements change, none of your source code has to change. All you do is update your configuration to support different browser versions by setting the minimum versions you wish to support.
Blink is just getting started, so stay tuned for any updates.
Features
- 100% code coverage
- 4.0 Code Climate GPA
- Runs on Node
- Gulp plugin
- Grunt plugin
- Middleware
- Browserified
- OOCSS with BEM Syntax
- Rules
- Includes
- Mixins
- Overrides
- Extenders
- Responders
- Plugins
- TypeScript Source
- CLI
- API
- Express middleware
- Spriting
Getting started
Installation
Example
At its simplest, blink lets you write CSS with a simple object initializer.
This object needs to be exported with either module.exports
in Node or just exports
in the browser.
Here's a quick and dirty example:
exports = {
foo: {
bar: 'baz'
}
};
This would generate the following CSS:
foo {
bar: baz;
}
From here, you'll want look at the full list of features for a comprehensive overview of what-all blink has to offer.
Runs on Node
Unlike most CSS preprocessors out there, blink does not transpile a DSL into CSS. Blink code gets compiled directly as a Node module, giving you access to all JavaScript syntax for free. This, of course, includes variables and functions, as well as file I/O. The possibilities are endless.
Gulp plugin
Blink is, itself, a gulp plugin. As with any gulp plugin, files can be piped to other gulp plugins before being written to their final destination. Blink supports vinyl files in buffer mode only (streams not supported).
var blink = require('blink');
var gulp = require('gulp');
gulp.task('styles', function() {
return gulp.src('styles/**/*.js')
.pipe(blink('styles.css', /* options */))
.on('error', function(err) {
// handle error
})
.pipe(gulp.dest('dist/styles'));
});
Note: gulp-blink has been deprecated in favor of using the blink module directly.
Browserified
For those wishing to transpile blink files into CSS in the browser, a Browserified version of blink is available in the dist folder, also available as a bower component.
$ bower install --save blink
Include the script in your web page:
<script src="/bower_components/blink/dist/blink.js"/>
Compile your block:
var foo = new blink.Block('foo', { bar: 'baz' });
blink(foo, function(err, css) {
console.log(css);
});
You can also compile a string of source code as long as you export the rule with exports
.
var foo = "exports = new blink.Block('foo', { bar: 'baz' });";
blink(foo, function(err, css) {
console.log(css);
});
OOCSS with BEM
Blink is designed with BEM syntax in mind. You can create blocks, elements and modifiers and their CSS selectors will be generated for you. You can configure your BEM format however you want, but the default naming convention follows that which is defined in MindBEMding – getting your head 'round BEM syntax.
Here's an example of a block with both an element and a modifier:
///<reference path="./node_modules/blink/blink.d.ts"/>
import blink = require('blink');
var btn = new blink.Block('btn', {
min: {
width: 80
},
elements: [
new blink.Element('foreground', {
color: 'black'
})
],
modifiers: [
new blink.Modifier('wide', {
min: {
width: 120
}
})
]
});
export = btn;
This would generate the following CSS:
.btn {
min-width: 80px;
}
.btn__foreground {
color: black;
}
.btn--wide {
min-width: 120px;
}
Rules
The Rule class allows you to specify a standard CSS rule and can be useful when styling page defaults.
///<reference path="./node_modules/blink/blink.d.ts"/>
import blink = require('blink');
var normalize = [
new blink.Rule('html', {
font: {
family: 'sans-serif'
}
}),
new blink.Rule('body', {
margin: 0
}),
new blink.Rule('a:active, a:hover', {
outline: 0
})
// ...
];
export = normalize;
You are encouraged to use BEM blocks for all of your components. There's nothing stopping you from using basic rules, but you should avoid them if at all possible.
Includes
Blink supports includes, but not in the way you might think. Includes are just functions that return an array of declarations. As such, you should lean against using them at all costs. Why? Say your include spits out 10 declarations. Every time you include that function you'll add another 10 lines of CSS to your file. Instead, use extenders and overrides when possible.
You might be wondering why blink supports includes at all if you aren't supposed to use them. This is because blink uses includes in the background to make extenders work. As such, includes may be removed at a future date, if they can be worked out of the extender logic.
Still, if you find yourself needing an include, refer to the Rule spec for a working example.
Mixins
If you're coming from Sass, you might be familiar with mixins. Really, Sass mixins are no different than functions in JavaScript; thus, blink supports them. All you have to do is create a function that returns an array of declarations. This is, in fact, how extenders and overrides work.
Overrides
Overrides are named function [factories](http://en.wikipedia.org/wiki/Factory_(object-oriented_programming).
The function that is returned can be used for the purpose of generating any number of CSS declarations.
This enables you to override existing CSS properties or create your own.
For example, say you wanted to override the CSS color
property to always convert colors into hsl
. You could do that!
Maybe you want to create a new clearfix
property that, when set to true, generates 3 CSS declarations. Good news – that one already exists!
Let's take an in-depth look at the box-sizing override. Here's how you would go about writing it from scratch.
function boxSizing() {}
export = boxSizing;
Overrides must be named functions with a unique name.
This name is what allows the blink compiler to reuse the override in other rules where it is called; thus, never generating the same code twice.
If you're coming from Sass, you'll know that you have to go out of your way to get this kind of functionality with @extend
.
With blink, you get it for free.
Let's return a function.
import blink = require('blink');
function boxSizing(value: string) {
var override = <blink.Override>((config: blink.Configuration) => {
});
override.args = arguments;
return override;
}
export = boxSizing;
We've imported the blink library to gain access to the Override interface, as well as the Configuration class.
We're accepting a value: string
argument, because we know it can be one of content-box
, padding-box
, border-box
or inherit
. It doesn't accept numeric values or anything like that. See box-sizing on MDN.
Next, we created a new function and assigned it to the override
variable.
The function must be assigned args
before it is returned. This copies the arguments
that were originally sent to the override.
Remember how I said your override never generates the same code twice? More accurately, your override, when called with the same arguments, never generates the same code twice.
Copying the arguments is what allows this process to work properly.
The box-sizing
property is actually quite simple. All we need to do is add some vendor prefixes.
Fortunately, blink has an experimental extender for that.
Let's work it into this override.
import blink = require('blink');
function boxSizing(value: string) {
var override = <blink.Override>((config: blink.Configuration) => {
return blink.config.extenders.experimental('box-sizing', value, {
official: true,
webkit: !(
config.chrome >= 10 &&
config.safari >= 5.1 &&
config.android >= 4
),
moz: !(
config.firefox >= 29 &&
config.firefoxMobile >= 29
)
})(config);
});
override.args = arguments;
return override;
}
export = boxSizing;
The experimental extender is quite handy, but we still need to know which vendor prefixes to generate. Refer to MDN's box-sizing browser compatability table for just that information. Once you implement the correct browser versions, anyone using your override will only generate the vendor prefixes they intend to support. If they change their configuration file to drop support for older browsers, it could generate no vendor prefixes at all. It really depends on the configuration. This is a blink paradigm that you would do well to follow.
Overrides are registered on the configuration object. If you wish to extend the configuration, you can do so by providing a plugin module.
Note: override names are dasherized for you (e.g., boxSizing overrides the box-sizing
property).
Extenders
Extenders are named function factories, just like overrides.
In fact, they are exactly the same. The only difference is how they are used.
Extenders cannot be called directly from the rule body, like overrides.
Instead, they are typically called other extenders or from overrides, as we did with the box-sizing
override.
The rules for extenders are just the same.
Here's an example of a fill
extender that fills its container:
import blink = require('blink');
function fill() {
var extender = <blink.Extender>(() => {
return [
['position', 'absolute'],
['top', '0'],
['right', '0'],
['bottom', '0'],
['left', '0']
];
});
extender.args = arguments;
return extender;
}
export = fill;
Pretty simple, right? You might choose to create a fill
override that, internally, calls this extender.
This would make sense for a rule body of { fill: true }
.
Now, let's talk about building your own extenders. Here's how you would go about building an inlineBlock
extender.
import blink = require('blink');
function inlineBlock() {
var extender = <blink.Extender>(() => {
return ['display', 'inline-block'];
});
}
export = inlineBlock;
This is all fine and good, but it's pretty useless. We can add a verticalAlign
option to make it more dynamic.
import blink = require('blink');
function inlineBlock(options?: {
verticalAlign?: string;
}) {
options = options || {};
var extender = <blink.Extender>((config: blink.Configuration) => {
var decs = [];
decs.push(['display', 'inline-block']);
if (options.verticalAlign !== null) {
decs.push(['vertical-align', options.verticalAlign || 'middle']);
}
return decs;
});
extender.args = arguments;
return extender;
}
export = inlineBlock;
Great, but what about inline-block CSS hacks? Glad you asked! You can gain access to the configuration for a case like this.
import blink = require('blink');
function inlineBlock(options?: {
verticalAlign?: string;
}) {
options = options || {};
var extender = <blink.Extender>((config: blink.Configuration) => {
var decs = [];
if (config.firefox < 3) {
decs.push(['display', '-moz-inline-stack']);
}
decs.push(['display', 'inline-block']);
if (options.verticalAlign !== null) {
decs.push(['vertical-align', options.verticalAlign || 'middle']);
}
if (config.ie < 8) {
decs.push(['*vertical-align', 'auto']);
decs.push(['zoom', '1']);
decs.push(['*display', 'inline']);
}
return decs;
});
extender.args = arguments;
return extender;
}
export = inlineBlock;
Now, that's a nice extender! Once you change your configuration to support newer browsers, the CSS hacks disappear. No need to change any of your source code. It's all about the configuration.
As with overrides, extenders are registered on the configuration object. If you wish to extend the configuration, you can do so by providing a plugin module.
Extender registration
It's important for you to know that, behind the scenes, blink is really smart about extender registration. It doesn't just register your extender by function name, but also by the arguments you pass in. This means if you extend inlineBlock({ verticalAlign: 'top' })
50 times and inlineBlock({ verticalAlign: 'bottom' })
20 times, only two rules will be generated. Different input yields different output, so it has to generate two rules for this scenario.
Responders
Responders currently only support MediaAtRules, which allow you to create responsive websites. Here's an example of a basic responder:
///<reference path="./node_modules/blink/blink.d.ts"/>
import blink = require('blink');
var foo = new blink.Block('foo', {
respond: [
new blink.MediaAtRule('screen and (max-width: 320)', {
width: 100
})
]
});
export = foo;
This generates the following CSS:
@media screen and (max-width: 320) {
.foo {
width: 100px;
}
}
Unlike Sass, at the time of this writing, blink supports extenders inside of media queries. Blink also merges similar media queries for you. So feel free to go crazy with some complicated responders!
Plugins
Plugins can be defined in the configuration like so:
{
"plugins": ["yourname.overrides"]
}
If you were to publish an npm package under the name yourname.overrides
and if you wanted to override the boxSizing override that blink already provides, you could do it like so:
function plugin() {
this.overrides.boxSizing = require('./overrides/boxSizing');
return this;
}
Now, every time someone declares box-sizing: whatever
your override will be called with whatever
as the first and only argument. The returned set of declarations will replace the original one. In this case, however, box-sizing
does nothing with arguments.
Node API
With all the new build tools and taks runners springing up, blink was built with that in mind, that various tools would need access to the compiled results without writing any files to the disk.
TypeScript Source
Since blink source code is written in TypeScript, you don't need to constantly look-up documentation to gain insight as to how you can use the blink API. Unfortunately, although there is TypeScript support for other editors, you won't get the powerful feature of Intellisense unless you are using Visual Studio.
BTW, you can write your blink files in TypeScript or JavaScript. It really doesn't matter as long as it ends up in JavaScript.
Getting Started
CLI Usage
See blink-cli
Library Usage
$ npm install --save-dev blink
///<reference path="./node_modules/blink/blink.d.ts"/>
import blink = require('blink');
Refer to the blink TypeScript definition for a list of available public methods.
Spriting
As blink is built on Node.js, any spriting tools available for Node.js can be implemented quite easily. Some of these tools, like Spritesmith allow you to not only generate sprites, but compute off the dimensions of the source images that build the sprites. This makes your CSS highly maintainable. For example, when Design gives you replacement images all you should have to do is drop them in the sprites folder without changing a single line of your blink source.
License
Released under the MIT license.