Slinky
If you write single-page rich client apps, Slinky is here to make your life easier. For development, it provides a static file server that transparently handles compiled languages like CoffeeScript and SASS while supporting advanced features like dependency management, proxying and automatic browser reloads. And once you're ready to deploy, Slinky will compile, concatenate, and minify your sources, leaving you ready to push to production.
What can slinky do for you?
Slinky Server
- Transparently compiles sources for a variety of languages
- Supports the LiveReload protocol, for instant browser updates
- Includes a customizable proxy, so your dev environment can mirror production
- Includes support for HTML5 pushState based apps
Slinky Builder
- Keeps track of the proper include order of your scripts and styles
- Compiles, minifies and concatenates JavaScript and CSS
Slinky is not a framework, and it does not want to control your source code. Its goal is to help you when you want it—and get out of the way when you don't. It endeavors to be sufficiently flexible to support a wide variety of development styles.
Quick start
$ gem install slinky
$ cd ~/my/awesome/project/src
$ slinky start
[hardcore web development action]
$ slinky build -o ../pub
$ scp -r ../pub/ myserver.com:/var/www/project
The details
- Transparent compilation
- LiveReload/Guard support
- Script & style management
- Specifying order
- Dependencies
- Configuration
- PushState
- Proxies
- Ignores
- Products
- Path matching
Transparent compilation
The Slinky server will transparently compile various front-end languages for you, providing a smooth transition from development to production. What does this mean? When Slinky sees a request for a file that doesn't exist (say, "/scripts/core.js") it will look for a file that can be compiled into that ("/scripts/core.coffee"), compile it, then return the result. This allows you to write code without concern for which files are "native" and which need compilation.
Currently supported languages include:
- CoffeeScript
- HAML
- SASS/SCSS
- LESS
- JSX (react templates)
- ClojureScript (experimental)
Adding support for new languages is simple, and pull requests are welcome.
LiveReload/Guard support
The typical edit-save-reload cycle of web development can be tedious,
especially when trying to get your CSS just right. What if you could
reduce that to just edit-save? LiveReload
allows just that. Slinky includes built-in support for LiveReload
service. All you need to do is run a browser extension (available
here for Safari, Chrome and
Firefox) or include a little script (http://go.livereload.com/mobile).
In addition to reloading your app whenever a source file changes,
LiveReload supports hot reloading of CSS, letting you tweak your
styles with ease. If don't want the LiveReload server running,
disabling it is a simple --no-livereload
away.
Script & style management
Slinky can manage all of your javascript and css files if you want it
to, serving them up individually during development and concatenating
and minifying them for production. To support this, Slinky recognizes
slinky_scripts
in your HTML/HAML files. For example, when Slinky
sees this:
!!!5
%html
%head
slinky_scripts
slinky_styles
%body
%h1 Hello, world!
it will compile the HAML to HTML and replace slinky_styles with the
appropriate HTML. You can also disable minification with the
--dont-minify
option or the dont_minify: true
configuration
option. slinky_scripts
and slinky_styles
are conveniences built on
top of the full product system.
Specifying order
Often scripts and styles depend on being included in the page
in a particular order. For this, we need the slinky_require
directive.
For example, consider the case of two coffeescript files, A.coffee and B.coffee. A includes a class definition that B depends upon, so we want to make sure that A comes before B in the concatenation order.
File A.coffee:
class A
hello: (thing) -> "Hello, " + thing
File B.coffee:
slinky_require("A.coffee")
alert (new A).hello("world")
We can also do this in CSS/SASS/SC SS:
/* slinky_require("reset.css")
a
color: red
Dependencies
As HAML and SASS scripts can include external content as part of their build process, you may want certain files to be recompiled whenever other files change. For example, you may use mustache templates defined each in their own file, but have set up your HAML file to include them all into the HTML. Thus when one of the mustache files changes, you would like the HAML file to be recompiled so that the templates will also be updated.
These relationships are specified as "dependencies," and like requirements
they are incdicated through a special slinky_depends("file")
directive in
your source files. For our template example, the index.haml files might look
like this:
slinky_depends("scripts/templates/*.mustache")
!!!5
%html
%head
%title My App
slinky_styles
slinky_scripts
- Dir.glob("./scripts/templates/*.mustache") do |f|
- name = File.basename(f).split(".")[0..-2].join(".")
%script{:id => name, :type => "text/x-handlebars-template"}= File.read(f)
%body
The paths in slinky_depends
can be either relative or absolute. If
relative, they support simple globbing (as in the example above). If
absolute, they support
the full path matching syntax.
Configuration
Slinky can optionally be configured using a yaml file. By default, it
looks for a file called slinky.yaml
in the source directory, but you
can also supply a file name on the command line using -c
.
Most of what can be specified on the command line is also available in the configuration file. Here's a fully-decked out config:
pushstate:
"/app1": "/index.html"
"/app2": "/index2.html"
proxy:
"/test1": "http://127.0.0.1:8000"
"/test2": "http://127.0.0.1:7000"
produce:
"/scripts.js":
include:
- "*.js"
exclude:
- "/script/vendor/"
- "/script/jquery.js"
"/styles.css":
include:
- "*.css"
port: 5555
src_dir: "src/"
build_dir: "build/"
no_proxy: true
no_livereload: true
livereload_port: 5556
dont_minify: true
# enable browser error injection (experimental)
enable_browser_errors: true
Most are self explanatory, but a few of the options merit further attention:
PushState
PushState is a new Javascript API that gives web apps more control over the browser's history, making possible single-page javascript applications that retain the advantages of their multi-page peers without resorting to hacks like hash urls. The essential idea is this: when a user navigates to a conceptually different "page" in the app, the URL should be updated to reflect that so that behaviors such as deep-linking and history navigation work properly.
For this to work, however, the server must be able to return the content of your main HTML page for arbitrary paths, as otherwise when a user tries to reload a pushstate-enabled web app they would receive a 404. Slinky supports multiple pushState paths using the pushstate configuration option:
pushstate:
"/": "/index.html"
"/app1": "/app1/index.haml"
"/app2": "/app2.haml"
Here, the key of the hash is a URL prefix, while the value is the file
that should actually be displayed for non-existent requests that begin
with the key. In the case of conflicting rules, the more specific one
wins. For this config, instead of returning a 404 for a path like
/this/file/does/not/exist
, Slinky will send the content of
/index.html
, leaving your JavaScript free to render the proper view for
that content. Similarly, a request for /app1/photo/1/edit
, assuming
such file does not exist, will return /app1/index.haml
.
Proxies
Slinky has a built-in proxy server which lets you test ajax requests with your actual backend servers without violating the same-origin policy. To set it up, your slinky.yaml file will look something like this:
proxy:
"/login": "http://127.0.0.1:4567/login"
"/search":
to: "http://127.0.0.1:4567/search"
lag: 2000
What does this mean? We introduce the list of proxy rules using the
proxy
key. Each rule is a key value pair. The key is a url prefix to
match against. The first rule is equivalent to the regular expression
/\/login.*/
, and will match paths like /login/user
and
/login/path/to/file.html
. The value is either a url to pass the
request on to or a hash containing configuration (one of which must be
a to
field). Currently a lag
field is also supported. This delays
the request by the specified number of milliseconds in order to
simulate the latency associated with remote servers.
An example: we have some javascript code which makes an AJAX GET
request to /search/widgets?q=foo
. When Slinky gets the request it
will see that it has a matching proxy rule, rewrites the request
appropriately (changing paths and hosts) and sends it on to the backend
server (in this case, 127.0.0.1:4567). Once it gets a response it will
wait until 2 seconds has elapsed since slinky itself received the
request and finally returns the response back to the browser.
Ignores
Ignores are deprecated and will be removed in the next major release. Use the new product system instead.
By default slinky will include every javascript and css file it finds into the combined scripts.js and styles.css files. However, it may be that for some reason you want to keep some files separate and handle them manually. The ignore directive lets you do that, by telling the system to skip over any files or directories listed. For example:
ignore:
- script/vendor
- css/reset.css
This will causes everything in the script/vendor directory to be ignored by slinky, as well as the reset.css file.
Products
New in 0.8: use master to get them now
Products are the outputs of the build system. Most files are just
copied to the build directory, but you may want some to undergo
further processing. For simplicity, Slinky defines two default
products which you have seen above: /scripts.js
and /styles.css
.
These are defined like this:
produce:
"/scripts.js":
include:
- "*.js"
"/styles.css":
include:
- "*.css"
Products are defined by an output path (in this case /scripts.js
and
/styles.css
), a set of paths to include, and a set of paths to
exclude (with gitignore-style glob patterns supported; see
here for the match rules). In development mode, all
of the files included in a product will be included in your html
separately. When built in production mode, they will all be minified
and concatenated into a single output file. We can also create our own
products:
produce:
"/test/test.js":
include:
- "*_test.js"
"/main.js":
include:
- "*.js"
exclude:
- "vendor/jquery*.js"
- "*_test.js"
"/main.css":
include:
- "*.css"
exclude:
- "vendor/boostrap.css"
This config will produce three products in the build directory:
test/test.js
, which will include all files ending in _test.js
,
main.js
which includes all .js files except jquery and test files,
and main.css
which includes all css files except for boostrap.css in
the vendor directory. Custom products can be included in your HTML
like this:
<html>
<head>
slinky_product("/main.js")
slinky_product("/main.css")
</head>
...
The default product directives (slinky_scripts
and slinky_styles
)
are merely sugar for slinky_product("/scripts.js")
and
slinky_product("/styles.css")
.
Path matching
Several slinky config features involve specifying paths, with support for globbing. These are interpreted similarly to .gitignore rules. The full specification is:
- If the pattern ends with a slash, it will only match directories;
e.g.
foo/
would match a directoryfoo/
but not a filefoo
. In a file context, matching a directory is equivalent to matching all files under that directory, recursively. - If the pattern does not contain a slash, slinky treats it as a
relative pathname which can match files in any directory. For
example, the rule
test.js
will matching/test.js
and/component/test.js
. - If the pattern begins with a slash, it will be treated as an absolute path starting at the root of the source directory.
- If the pattern does not begin with a slash, but does contain one or
more slashes, it will be treated as a path relative to any
directory. For example,
test/*.js
will match/test/main.js
, and/component/test/component.js
, but notmain.js
. - A single star
*
in a pattern will match any number of characters within a single path component. For example,/test/*.js
will match/test/main_test.js
but not/test/component/test.js
. - A double star
**
will match any number of characters including path separators. For example/scripts/**/main.js
will match any file namedmain.js
under the/scripts
directory, including/scripts/main.js
and/scripts/component/main.js
.