
eyeglass
Getting some npm in your Sass
eyeglass is a node-sass (github) extension manager built on top of npm. Using eyeglass, you can bring the power of node modules to your Sass files.
Installing eyeglass
# for programatic functionality
npm install eyeglass --save-dev
Adding eyeglass modules to your project
eyeglass modules are regular npm modules. Install them into your project just like any other item.
npm install my_eyeglass_module --save-dev
Once installed via npm, an eyeglass module can:
- Provide stylesheets that you can import with special node_module syntax.
- Add additional custom functions to Sass that are written in javascript.
In Sass files, you can reference the eyeglass module with standard Sass import syntax: @import "my_eyeglass_module/file";
. The my_eyeglass_module
will be resolved to the correct directory in your node modules, and the file will then resolve using the standard import rules for Sass.
Working with assets
It's quite common to need to refer to assets from within your
stylesheets. Eyeglass provides core support for exposing assets to your
stylesheets for your application or from an eyeglass module and
generating urls to those assets as well as making sure only those assets
that you actually use end up in your built application.
Exposing assets
The addSource
method on eyeglass.assets
is how you add assets to
your application. The path passed to asset-url()
is going to be
relative to the directory that you pass to addSource.
Given the following assets directory structure:
myproject/
└── assets/
├── images/
│ ├── foo/
│ │ └── image1.png
│ └── unused.gif
├── js/
│ └── app.js
└── scss/
└── app.scss
The simplest way to expose your assets to eyeglass is to add your assets
directory as an eyeglass asset source. Using a simple node script we can
compile a Sass file.
#!/usr/bin/env node
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"] }}
]
}
}
sass.render(eyeglass(options, sass), function(err, result) {
});
Referencing Assets
To reference an asset in your application or within your own module you
can simply @import "assets"
. To reference assets that are in a module
that you have a direct dependency on, you can @import "<module>/assets"
.
For example: @import "my-theme/assets"
would import the assets from
the my-theme
eyeglass module.
Importing assets for an application or module returns an automatically
generated Sass file that registers asset information with the eyeglass
assets Sass module.
Then you can refer to that asset using the fully qualified source url of
the asset. This url must include the module prefix when referencing the
asset. For example background: asset-url("images/foo.png")
would
import a file images/foo.png
that is relative to the assetsDir
.
To refer to an asset in your module, include the module name as a
directory prefix when invoking asset-url
. For example
asset-url("my-theme/icons/party.png")
would import the file
icons/party.png
that is exposed by the my-theme
module. Even within
the my-theme module, this prefix must be used when referring to the
assets of that module.
Astute readers will have noted that there is a possible namespace
collision if you have a directory in your application with the same name
as a module. This is on purpose: it lets you replace module assets
with your own assets if you need to do so by overriding them in your own
application.
Asset URL Manipulation
By default, eyeglass will namespace module asset urls according to their
eyeglass module name and both application and module assets urls will be
placed within folder specified by the assetsHttpPrefix
option.
However, an application or framework can chose to override the url
scheme for assets by defining an asset resolver.
Example: Adding a modification timestamp to assets as a query parameter.
eyeglass.assets.resolver(function(assetFile, assetUri, oldResolver, done) {
var fs = require("fs");
var mtime = fs.statSync(assetFile).mtime.getTime();
done(null, {
path: assetUri,
query: mtime.toString()
});
});
Example: hashing assets by md5sum.
eyeglass.assets.resolver(function(assetFile, assetUri, oldResolver, done) {
var path = require("path");
var fs = require("fs");
var md5 = require("MD5");
var prefix = "/" + eyeglass.options.assetsHttpPrefix + "/";
fs.readFile(assetFile, function(err, buffer) {
if (err) {
done(err);
} else {
done(null, {
path: prefix + md5(buffer) + path.extname(assetFile)
});
}
});
});
Asset Installation
By using Eyeglass's asset installation system, you can ensure that only
those assets that are referenced in your stylesheets will be part of
your application when it is built.
Once an asset's url is fully resolved, the asset probably needs to be
installed into a location from where it can be served as that url. The
simplest way to do this is to specify the buildDir
option to eyeglass.
Once that is specified the resolved url will be used to copy the file to
a location relative to the build directory.
In order to allow for asset pipeline integration (E.g. writing to a
Vinyl file) and more complex application needs, it's possible to chain
or override the default eyeglass asset installer.
Installer Example: Logging installed assets:
eyeglass.assets.installer(function(assetFile, assetUri, oldInstaller, cb) {
oldInstaller(assetFile, assetUri, function(err, result) {
if (err) {
console.log("Error installing '" + assetFile + "': " + err.toString());
} else {
console.log("Installed Asset '" + assetFile + "' => '" + result + "'");
}
cb(err, result);
});
});
More on Assets
The code samples here are actually derived from a simple eyeglass
project. You can view the actual code
as a gist.
Assets are complex and the asset configuration of Eyeglass is very
flexible. For more documentation see the asset documentation.
Writing an eyeglass module with Sass files
To create an eyeglass module with Sass files, place the files inside of a sass
directory in your npm module.
|- /
|- eyeglass-exports.js
|- package.json
|- sass
|- index.scss (or .sass)
eyeglass will automatically map the first directory of @import
statements to the correct node-module directory if there is a eyeglass
module with that eyeglass name. Because Sass uses a global namespace,
it's recommended that you namespace-prefix any mixins you create in
order to avoid collisions.
In keeping with node's conventions, eyeglass modules can create an
index.scss
file in any folder instead of defining a file of the same
name as a folder in order to be the main entry point for a sass module having
submodules.
Building sass files with eyeglass support
The easiest way to use eyeglass is to use an eyeglass-aware
build-tool plugin. The following plugins are available:
Integrating with other build systems
Eyeglass is designed to be easy to use with any node-sass based
compilation system.
var eyeglass = require("eyeglass");
var sass = require("node-sass")
var sassOptions = { ... } ;
sassOptions.eyeglass {
assets: {
sources: [
{directory: "assets", {pattern: "images/**/*"}},
{directory: "assets", {pattern: "fonts/**/*"}}
]
}
}
sass.render(eyeglass(sassOptions), function(error, result) {
if (error) {
} else {
}
});
Example: integration with grunt and grunt-sass
...
var eyeglass = require("eyeglass");
...
sass: {
options: eyeglass({sourceMap: true}),
dist: {
files: {
'public/css/main.css': 'sass/main.scss'
}
}
}
...
Writing an Eyeglass Module
node-sass allows you to register custom functions for advanced
functionality. Eyeglass allows any node modules that are tagged with
eyeglass-module
to be automatically loaded into eyeglass and makes
your module discoverable on
NPM. To tag your module as an
eyeglass module, add the eyeglass-module
keyword to your
package.json
.
{
...
"keywords": ["eyeglass-module", "sass", ...],
"eyeglass": {
"sassDir": "sass",
"exports": "eyeglass-exports.js",
"name": "greetings",
"needs": "^0.6.0"
},
...
}
In the "eyeglass"
option block in your package.json, you will declare
the eyeglass exports file and the semver dependency that your module has
on eyeglass itself using the "needs"
option. Failure to provide this
option will give your users a warning since eyeglass has no way to check
if your module is compatible with the currect eyeglass version.
Eyeglass Exports File
If your eyeglass module needs to define Sass functions in javascript,
you will need to make an eyeglass exports file. It is convention to name
this file eyeglass-exports.js
but any file name is allowed.
Below is an example eyeglass exports file:
"use strict";
var path = require("path");
module.exports = function(eyeglass, sass) {
return {
functions: {
"greetings-hello($name: 'World')": function(name, done) {
done(sass.types.String("Hello, " + name.getValue()));
}
}
}
};
If the eyeglass.exports
option is not found in package.json
eyeglass
will fall back to using the npm standard main
file declared in your
package.json. If your npm module has a main file meant to be used generally by
javascript, but no eyeglass exports file, then you can simply set
eyeglass.exports
option to false
in your package.json
.
Since all functions declared from javascript are global, it is best
practice to scope your function names to avoid naming conflicts. Then,
to simplify the naming of your functions for the normal case, provide a
sass file that when imported, unscopes the function names by wrapping
them.
@function hello($args...) {
@return greetings-hello($args...);
}
Specifying a name for @import that is different from your npm package name
If you need the top level import to be named differently than the name
of your npm module then you can specify a name
attribute for the
eyeglass object in your package.json. The following example would allow
@import "foo";
to import index.scss
from your package's sass
directory.
{
...
"name": "eyeglass-foo",
"eyeglass": {
"name": "foo"
}
...
}
Import-Once
Any sass files imported from your node modules will only ever be
imported once per CSS output file. Note that Sass files imported
from the Sass load path will have the standard Sass @import
behavior.
To disable the import-once behavior, you need to set the enableImportOnce
option to false:
var sass = require("node-sass");
var eyeglass = require("eyeglass");
var sassOptions = {
eyeglass: {
enableImportOnce: false
}
};
sass.render(eyeglass(sassOptions, sass));