Sassy
NODE INTERACTIVE 2015
DAVID KHOURSHID, COUNSYL
function double(val) {
return val * 2;
}
_.map([1, 2, 3], double);
// => [2, 4, 6]
@function double($val) {
@return $val * 2;
}
$foo: _map((1, 2, 3), double);
// => (2, 4, 6)
1s + 10ms = 1010ms
10deg + 10rad = 582.9578deg
#777 + #888 = white
Powerful languages inhibit information reuse.
Use the least powerful language suitable for expressing information, constraints or programs on the World Wide Web.
PRINCIPLE
GOOD PRACTICE
var sass = require('node-sass');
sass.render({
file: scss_filename,
// options...
}, function(err, result) {
// render CSS
});
{ file: ... }
{ contents: ... }
sass.render({
file: 'path/to/stylesheet.scss',
importer: (url, prev, done) => {
if (url == 'goat-styles') {
return {
file: 'path/to/secret/goat.css'
};
}
return sass.NULL;
}
}, (err, res) => { ... });
// path/to/stylesheet.scss
@import 'goat-styles';
// ... will import path/to/secret/goat.css
@import 'path/to/partial';
// ... will do default import behavior
sass.render({
file: 'path/to/stylesheet.scss',
functions: {
'pow($val, $exp: 1)': (val, exp) => {
let jsVal = val.getValue();
let jsExp = val.getValue();
let result = Math.pow(jsVal, jsExp);
return sass.types.Number(result);
}
}
}, (err, res) => { ... });
// path/to/stylesheet.scss
$foo: pow(2, 3);
test {
font-size: $foo * 1px;
}
// CSS
test {
font-size: 8px;
}
var path = require("path");
var sass = require("node-sass");
var eyeglass = require("eyeglass");
var rootDir = __dirname;
var assetsDir = path.join(rootDir, "assets");
var options = { ... node-sass options ... };
options.eyeglass = {
root: rootDir,
buildDir: path.join(rootDir, "dist"),
assets: {
httpPrefix: "assets",
sources: [
{
directory: assetsDir,
globOpts: {
ignore: ["**/*.js", "**/*.scss"]
}
}
]
}
}
// Standard node-sass rendering of a single file.
sass.render(eyeglass(options, sass),
function(err, result) {
// handle results
});
{
...
"keywords": ["eyeglass-module", "sass", ...],
"eyeglass": {
"sassDir": "sass",
"exports": "eyeglass-exports.js",
"name": "greetings",
"needs": "^0.6.0"
},
...
}
// eyeglass-exports.js
var path = require("path");
module.exports = function(eyeglass, sass) {
return {
functions: {
"greetings-hello($name: 'World')": (name, done) => {
done(sass.types.String("Hello, " + name.getValue()));
}
}
}
};
npm install eyeglass-math
@import 'math';
// Import the Eyeglass module name
@import 'math';
test {
font-size: pow(2, 3) * 1px;
pi: $PI;
}
// Result
test {
font-size: 8px;
pi: 3.1415;
}
npm install sassport
import sassport from 'sassport';
import sassportMath from 'sassport-math';
sassport([ sassportMath ])
.render({
file: 'main.scss',
// other Sass options
}, (err, result) => {
// output the CSS
});
// If you want asset management...
sassport([ ... ])
.assets(__dirname + '/assets', 'public/assets')
.render( ... );
npm install sassport
.functions
.variables
.exports
.loaders
import sassport from 'sassport';
const wrap = sassport.wrap;
const myModule = sassport.module('greetings')
.functions({
'greetings-hello($name: "World")': wrap(
(name) => `Hello, ${name}!`
)
});
export default myModule;
sassport.wrap(fn[, opts])
// Without sassport.wrap
import sass from 'node-sass';
sassport.module('greeting')
.functions({
'greet($val)': (val) => {
let jsVal = val.getValue();
let jsResult = 'Hello, ' + jsVal;
return sass.types.String(jsResult);
}
})
// With sassport.wrap
const wrap = sassport.wrap;
sassport.module('greeting')
.functions({
'greet($val)': wrap((val) => {
return "Hello, " + val;
})
})
sassport.wrapAll(obj[, opts])
method: call with $args
property: no $args
const wrapAll = sassport.wrapAll;
const mathModule = sassport.module('math')
.functions({
'Math($method, $args...)': wrapAll(Math)
});
export default mathModule;
test {
font-size: Math(pow, 2, 3) * 1px;
pi: Math(PI);
}
// Result CSS:
test {
font-size: 8px;
pi: 3.1415;
}
// path/to/my-colors.js
const colors = {
primary: '#C0FF33',
secondary: '#B4D455'
};
export default colors;
// path/to/stylesheet.scss
// Just like Node require()!
$colors: require('path/to/my-colors');
$primary-color: map-get($colors, primary);
.foo {
color: $primary-color;
&:hover {
color: lighten($primary-color, 10%);
}
}
// Result CSS:
.foo {
color: #c0ff33;
}
.foo:hover {
color: #d0ff66;
}
import eyeglass from 'eyeglass';
module.exports = function(eyeglass, sass) {
return {
sassDir: path.join(
__dirname, "stylesheets"),
assets: eyeglass.assets.export(
path.join(__dirname, "images"))
};
};
import sassport from 'sassport';
module.exports = sassport.module('test')
.exports({
'default': path.join(
__dirname, 'stylesheets/main.scss'),
'images': path.join(
__dirname, 'images')
});
@import 'test/images';
// other SCSS code here...
@import "test/images";
.test {
background: asset-url("test/images/foo.jpg");
}
// Can list all assets
.all-assets {
app-assets: asset-list();
test-assets: asset-list("test");
}
@import "test/images";
.test {
background: resolve-url("test/images/foo.jpg");
}
// Can also get local path
$image-path: resolve-path("images/bar.jpg", "test");
// index.js
import sassport from 'sassport';
import imageSize from 'image-size';
const wrap = sassport.wrap;
sassport()
.functions({
'size-of($path)': wrap((path) => {
return sizeOf(path);
})
})
.assets('./assets', 'public/assets')
.render({
file: 'stylesheet.scss'
}, function(err, res) {
console.log(res.css.toString());
});
// stylesheet.scss
$image-path: 'sassport-sm.png';
$image-size: size-of(resolve-path($image-path));
.my-image {
background-image: resolve-url($image-path);
width: map-get($image-size, 'width') * 1px;
height: map-get($image-size, 'height') * 1px;
}
// Result CSS
.my-image {
background-image:
url(public/assets/sassport-sm.png);
width: 145px;
height: 175px;
}
NODE INTERACTIVE 2015
DAVID KHOURSHID, COUNSYL
GOAT ANY QUESTIONS?