Slm is a template language for js. Port of Slim but slimmer :)
HAML -> Jade -> Slim -> Slm
- Elegant syntax
- Short syntax without closing tags (Using indentation instead)
- HTML style mode with closing tags
- Shortcut tags (based on css selectors)
- Safety
- Automatic HTML escaping by default
- Support
htmlSafe
attribute on String objects
- Highly configurable and extendable via plugins
- High performance
- Comparable speed to ECT and Hogan see benchmakrs
- Easy integration with hapijs
- Embedded engines like CoffeeScript, Markdown and Textile (TBD)
- Source: http://github.com/slm-lang/slm
- Bugs: http://github.com/slm-lang/slm/issues
- Highlighters:
- Atom: https://github.com/slm-lang/language-slm
- Sublime: https://github.com/slm-lang/sublime-slm
- Vim: https://github.com/slm-lang/vim-slm
- VS Code: https://github.com/mrmlnc/vscode-slm
- Vetur (Vue tooling for VSCode): https://github.com/vuejs/vetur
- Benchmark: https://github.com/slm-lang/template-benchmark#results
- Gulp: https://github.com/simnalamburt/gulp-slm
- Grunt: https://github.com/MichaelDanilov/grunt-slm
- Karma: https://github.com/looker/karma-slm-preprocessor
- Webpack: https://github.com/wealthbar/slm-loader
Install Slm with npm:
npm install slm --save
// in app.js
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'slm');
var Hapi = require('hapi');
var server = new Hapi.Server(3000);
server.views({
engines: {
'slm': require('slm')
},
basePath: __dirname + '/views',
compileOptions: {
basePath: __dirname + '/views',
useCache: false // disable internal cache - useful for development
},
isCached: false // disable hapi view cache
});
server.route({
method: 'GET', path: '/',
handler: function (request, reply) {
reply.view('index', {hello: "word"});
}
});
server.start(function () {
console.log('Server running at:', server.info.uri);
});
Here's a quick example to demonstrate what a Slm template looks like:
doctype html
html
head
title Slm Examples
meta name="keywords" content="template language"
meta name="author" content=this.author
javascript:
alert('Slm supports embedded javascript!')
body
h1 Markup examples
#content
p This example shows you how a basic Slm file looks.
== content()
- if this.items.length
table#items
- for item of this.items
tr
td.name = item.name
td.price = item.price
- else
p No items found Please add some inventory.
Thank you!
div id="footer"
== partial('footer')
| Copyright © ${this.year} ${this.author}
Indentation matters, but the indentation depth can be chosen as you like. If you want to first indent 2 spaces, then 5 spaces, it's your choice. To nest markup you only need to indent by one space, the rest is gravy.
The pipe tells Slm to just copy the line. It essentially escapes any processing. Each following line that is indented greater than the pipe is copied over.
body
p
|
This is a test of the text block.
The parsed result of the above:
<body><p>This is a test of the text block.</p></body>
The left margin is set at the indent of the pipe + one space. Any additional spaces will be copied over.
body
p
| This line is on the left margin.
This line will have one space in front of it.
This line will have two spaces in front of it.
And so on...
You can also embed html in the text line
- for (var a in this.articles)
| <tr><td>${a.name}</td><td>${a.description}</td></tr>
The single dot tells Slm to copy the line (similar to |
), but makes sure that a single trailing white space is appended.
You can write html tags directly in Slm which allows you to write your templates in a more html like style with closing tags or mix html and Slm style.
<html>
head
title Example
<body>
- if this.articles.length
table
- for (var a of this.articles)
<tr><td>${a.name}</td><td>${a.description}</td></tr>
</body>
</html>
The dash denotes control code. Examples of control code are loops and conditionals. Blocks are defined only by indentation.
If your js code needs to use multiple lines, append a backslash \
at the end of the lines. If your line ends with comma ,
(e.g because of a method call) you don't need the additional backslash before the linebreak.
Slm inserts (
and )
for if
, for
, else if
automatically. So you JS code is more readable.
body
- if !this.articles.length
| No inventory
The equal sign tells Slm it's a JS call that produces output to add to the buffer. If your JS code needs to use multiple lines, append a backslash \
at the end of the lines, for example:
= javascript_include_tag(\
"jquery",
"application")
If your line ends with comma ,
(e.g because of a method call) you don't need the additional backslash before the linebreak. For trailing or leading whitespace the modifiers >
and <
are supported.
- Output with trailing white space
=>
. Same as the single equal sign (=
), except that it adds a trailing white space. The legacy syntax='
is also supported. - Output with leading white space
=<
. Same as the single equal sign (=
), except that it adds a leading white space.
If you use Slm as express view engine, you have to make the function available to the view render, eg:
res.render('viewname', { items: some.result.data, foofunc: foofunc })
Then in your slm file you can call foofunc:
p = this.foofunc()
Same as the single equal sign (=
), but does not go through the escapeHtml
method. For trailing or leading whitespace the modifiers >
and <
are supported.
- Output without HTML escaping and trailing white space
==>
. Same as the double equal sign (==
), except that it adds a trailing white space. - Output without HTML escaping and leading white space
==<
. Same as the double equal sign (==
), except that it adds a leading white space.
To output data from the node side within Javascript there is a j
function:
javascript:
var user = ${=j(this.user)};
Use the forward slash for code comments - anything after it won't get displayed in the final render. Use /
for code comments and /!
for html comments
body
p
/ This line won't get displayed.
Neither does this line.
/! This will get displayed as html comments.
The parsed result of the above:
<body><p><!--This will get displayed as html comments.--></p></body>
Use the forward slash immediately followed by an exclamation mark for html comments (<!-- ... -->
).
/[if IE]
p Get a better browser.
renders as
<!--[if IE]><p>Get a better browser.</p><![endif]-->
The doctype tag is a special tag which can be used to generate the complex doctypes in a very simple way.
XML VERSION
doctype xml
<?xml version="1.0" encoding="utf-8" ?>
doctype xml ISO-8859-1
<?xml version="1.0" encoding="iso-8859-1" ?>
XHTML DOCTYPES
doctype html
<!DOCTYPE html>
doctype 5
<!DOCTYPE html>
doctype 1.1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
doctype strict
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
doctype frameset
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
doctype basic
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
doctype transitional
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
HTML 4 DOCTYPES
doctype strict
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
doctype frameset
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
"http://www.w3.org/TR/html4/frameset.dtd">
doctype transitional
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
You can close tags explicitly by appending a trailing /
.
img src="image.png"/
Note, that this is usually not necessary since the standard html tags (img, br, ...) are closed automatically.
You can force Slm to add a trailing whitespace after a tag by adding a >.
a> href='url1' Link1
a> href='url2' Link2
You can add a leading whitespace by adding <.
a< href='url1' Link1
a< href='url2' Link2
You can also combine both.
a<> href='url1' Link1
Sometimes you may want to be a little more compact and inline the tags.
ul
li.first: a href="/a" A link
li: a href="/b" B link
For readability, don't forget you can wrap the attributes.
ul
li.first: a[href="/a"] A link
li: a[href="/b"] B link
Either start on the same line as the tag
body
h1 id="headline" Welcome to my site.
Or nest it. You must use a pipe or an apostrophe to escape processing
body
h1 id="headline"
| Welcome to my site.
Can make the call on the same line
body
h1 id="headline" = this.pageHeadline
Or nest it.
body
h1 id="headline"
= this.pageHeadline
You write attributes directly after the tag. For normal text attributes you must use double "
or single quotes '
(Quoted attributes).
a href="http://slm-lang.com" title='Slm Homepage' Goto the Slm homepage
You can use text interpolation in the quoted attributes.
If a delimiter makes the syntax more readable for you,
you can use the characters (...)
, [...]
to wrap the attributes.
You can configure these symbols.
body
h1(id="logo") = this.pageLogo
h2[id="tagline" class="small tagline"] = this.pageTagline
If you wrap the attributes, you can spread them across multiple lines:
h2[id="tagline"
class="small tagline"] = this.pageTagline
You may use spaces around the wrappers and assignments:
h1 id = "logo" = page_logo
h2 [ id = "tagline" ] = this.pageTagline
Example:
a href="http://slm-lang.com" title='Slm Homepage' Goto the slm homepage
You can use text interpolation in the quoted attributes:
a href="http://${url}" Goto the ${url}
The attribute value will be escaped by default. Use == if you want to disable escaping in the attribute.
a href=="&"
You can break quoted attributes with backslash \
a data-title="help" data-content="extremely long help text that goes on\
and one and one and then starts over...."
Write the javascript code directly after the =
. If the code contains spaces you have to wrap
the code into parentheses (...)
. You can also directly write hashes {...}
and arrays [...]
.
body
table
- for var user of this.users
td id="user-${user.id}" class=user.role
a href=userAction(user, 'edit') Edit ${user.name}
a href=pathToUser(user) = user.name
The attribute value will be escaped by default. Use == if you want to disable escaping in the attribute.
a href==actionPath('start')
You can also break javascript attributes with backslash \
or trailing ,
as describe for control sections.
The attribute values true
, false
, null
and undefinded
are interpreted
as booleans. If you use the attribute wrapper you can omit the attribute assigment.
input type="text" disabled="disabled"
input type="text" disabled=true
input(type="text" disabled)
input type="text"
input type="text" disabled=false
input type="text" disabled=null
a.menu class="highlight" href="http://slm-lang.com/" Slm-lang.com
This renders as
<a class="menu highlight" href="http://slm-lang.com/">Slm-lang.com</a>
You can also use an Array
as attribute value and the array elements will be merged using the delimiter.
a class=['menu','highlight']
a class='menu','highlight'
Use ES6 interpolation. The text will be html escaped by default.
body
h1 Welcome ${current_user.name} to the show.
| Unescaped ${=content} is also possible.
To escape the interpolation (i.e. render as is)
body
h1 Welcome \${current_user.name} to the show.
Mixins allow you to create reusable blocks of Slm.
= mixin('paragraph')
p Hello from mixin!
.say
= mixin('paragraph')
They are compiled to functions and can take arguments:
= mixin('paragraph', 'name')
p Hello from ${this.name}!
.say
= mixin('paragraph', 'me')
Even with default values:
= mixin('paragraph', 'name = me')
p Hello from ${this.name}!
.say
= mixin('paragraph')
And from partial
:
// mixins.slm
= mixin('paragraph', 'name = me')
p Hello from ${this.name}!
// index.slm
= partial('mixins')
.say
= mixin('paragraph')
Slm is released under the MIT license.
- Andrew Stone, for Slim
- Magnus Holm, for Temple
- Daniel Mendler, for maintenance of both
- John Firebaugh, for Skim
- AnjLab, for such great team