├── .editorconfig
├── .gitignore
├── Compiler.js
├── LICENSE
├── Parser.js
├── README.md
├── bin
└── jadum
├── camelCase.js
├── jadum.js
├── package.json
├── runtime.js
└── test
├── compiled
├── listing.jade.js
└── listing.jadum.js
├── fixture
├── item.jade
└── listing.jade
└── index.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/Compiler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var jade = require('jade');
4 | var util = require('util');
5 | var BaseCompiler = jade.Compiler;
6 |
7 | function Compiler () {
8 | BaseCompiler.apply(this, arguments);
9 | }
10 |
11 | util.inherits(Compiler, BaseCompiler);
12 |
13 | Compiler.prototype.visitEach = function (each) {
14 | this.buf.push(''
15 | + '// iterate ' + each.obj + '\n'
16 | + ';(function(){\n'
17 | + ' var $$local = locals["' + each.val + '"];\n'
18 | + ' var $$obj = ' + each.obj + ';\n'
19 | + ' if (\'number\' == typeof $$obj.length) {\n');
20 |
21 | if (each.alternative) {
22 | this.buf.push(' if ($$obj.length) {');
23 | }
24 |
25 | this.buf.push(''
26 | + ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
27 | + ' var ' + each.val + ' = locals["' + each.val + '"] = $$obj[' + each.key + '];\n');
28 |
29 | this.visit(each.block);
30 |
31 | this.buf.push(' }\n');
32 |
33 | if (each.alternative) {
34 | this.buf.push(' } else {');
35 | this.visit(each.alternative);
36 | this.buf.push(' }');
37 | }
38 |
39 | this.buf.push(''
40 | + ' } else {\n'
41 | + ' var $$l = 0;\n'
42 | + ' for (var ' + each.key + ' in $$obj) {\n'
43 | + ' $$l++;'
44 | + ' var ' + each.val + ' = locals["' + each.val + '"] = $$obj[' + each.key + '];\n');
45 |
46 | this.visit(each.block);
47 |
48 | this.buf.push(' }\n');
49 | if (each.alternative) {
50 | this.buf.push(' if ($$l === 0) {');
51 | this.visit(each.alternative);
52 | this.buf.push(' }');
53 | }
54 |
55 | this.buf.push(''
56 | + ' locals["' + each.val + '"] = $$local;\n'
57 | + ' }\n}).call(this);\n');
58 | };
59 |
60 | module.exports = Compiler;
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2014 Nicolas Bevacqua
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var jade = require('jade');
6 | var util = require('util');
7 | var nodes = jade.nodes;
8 | var BaseParser = jade.Parser;
9 |
10 | function Parser (a,b,c) {
11 | BaseParser.apply(this, arguments);
12 | }
13 |
14 | util.inherits(Parser, BaseParser);
15 |
16 | Parser.prototype.parseInclude = function () {
17 | var tok = this.expect('include').val.trim();
18 | var file = this.resolvePath(tok, 'include');
19 | var mod = tok.indexOf('.') === 0 ? tok : './' + tok;
20 |
21 | this.dependencies.push(file);
22 |
23 | return new nodes.Code('buf.push(require("' + mod + '").call(this, locals));');
24 | };
25 |
26 | module.exports = Parser;
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jadum
2 |
3 | > A lean Jade compiler that understands Browserify and reuses partials
4 |
5 | Works just like `jade`, except that the CLI compiles views producing a syntax like below.
6 |
7 | ```js
8 | module.exports = function (model) {
9 | // view rendering...
10 | }
11 | ```
12 |
13 | The other difference is that the traditional Jade compiler inlines partials when it finds `include` statements, whereas `jadum` uses `require` statements to reuse partials, saving precious bytes in client-side code.
14 |
15 | ## Install
16 |
17 | ```shell
18 | npm install -S jadum
19 | ```
20 |
21 | ## CLI
22 |
23 | The CLI works the same way as the one in `jade`, but it always compiles views for the client-side, as Common.JS modules.
24 |
25 | ```shell
26 | jadum views/**/* -o .bin
27 | ```
28 |
29 | ## API
30 |
31 | The API is the same as the API in Jade, but it produces `require` statements instead of inlining `include` statements.
32 |
33 | ## Comparison
34 |
35 | Take as input these two files: `listing.jade` and `item.jade`.
36 |
37 | ```jade
38 | h1 This is some awesome listing!
39 | ul
40 | each item in ['a', 'b', 'c']
41 | include item
42 | ```
43 |
44 | ```jade
45 | li
46 | span Here comes item:
47 | span Note that this is quite a big template file
48 | span But it's included everywhere, because Jade!
49 | span So that's awesome... but compiling becomes an issue...
50 | span=item
51 | ```
52 |
53 | As the `item.jade` template grows, every template that depends on it will grow twice as much, as the included template _(`item.jade`)_ **is inlined twice** in its parent _(`listing.jade`)_ template! Here is the output of `jade`.
54 |
55 | ```js
56 | function template(locals) {
57 | var buf = [];
58 | var jade_mixins = {};
59 | var jade_interp;
60 |
61 | buf.push("
This is some awesome listing!
");
62 | // iterate ['a', 'b', 'c']
63 | ;(function(){
64 | var $$obj = ['a', 'b', 'c'];
65 | if ('number' == typeof $$obj.length) {
66 |
67 | for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
68 | var item = $$obj[$index];
69 |
70 | buf.push("- Here comes item:Note that this is quite a big template fileBut it's included everywhere, because Jade!So that's awesome... but compiling becomes an issue..." + (jade.escape(null == (jade_interp = item) ? "" : jade_interp)) + "
");
71 | }
72 |
73 | } else {
74 | var $$l = 0;
75 | for (var $index in $$obj) {
76 | $$l++; var item = $$obj[$index];
77 |
78 | buf.push("- Here comes item:Note that this is quite a big template fileBut it's included everywhere, because Jade!So that's awesome... but compiling becomes an issue..." + (jade.escape(null == (jade_interp = item) ? "" : jade_interp)) + "
");
79 | }
80 |
81 | }
82 | }).call(this);
83 |
84 | buf.push("
");;return buf.join("");
85 | }
86 | ```
87 |
88 | If we use `jadum`, however, we don't have these issues. We'll always have a fixed size determined by the length of the path passed to `require` statements. Pretty neat! Since `jadum` understands Browserify it won't hope for the best, in terms of expecting the Jade runtime to be globally included in your code. That's why there's the extra `require` at the top for the runtime. Browserify will take over when compiling the template, making sure it's only included once. Furthermore, if you use thee template in various places, then it will only be included once. That's where you save the vast majority of bytes.
89 |
90 | ```js
91 | var jade = require("jadum/runtime");
92 | module.exports = function listing(locals) {
93 | var buf = [];
94 | var jade_mixins = {};
95 | var jade_interp;
96 | ;var locals_for_with = (locals || {});(function (require) {
97 | buf.push("This is some awesome listing!
");
98 | // iterate ['a', 'b', 'c']
99 | ;(function(){
100 | var $$local = locals["item"];
101 | var $$obj = ['a', 'b', 'c'];
102 | if ('number' == typeof $$obj.length) {
103 |
104 | for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
105 | var item = locals["item"] = $$obj[$index];
106 |
107 | buf.push(require("./item").call(this, locals));
108 | }
109 |
110 | } else {
111 | var $$l = 0;
112 | for (var $index in $$obj) {
113 | $$l++; var item = locals["item"] = $$obj[$index];
114 |
115 | buf.push(require("./item").call(this, locals));
116 | }
117 |
118 | locals["item"] = $$local;
119 | }
120 | }).call(this);
121 |
122 | buf.push("
");}.call(this,"require" in locals_for_with?locals_for_with.require:typeof require!=="undefined"?require:undefined));;return buf.join("");
123 | }
124 | ```
125 |
126 | # License
127 |
128 | MIT
129 |
--------------------------------------------------------------------------------
/bin/jadum:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | var argv = require('minimist')(process.argv.slice(2), {
6 | alias: {
7 | output: ['o', 'out'],
8 | pretty: 'p',
9 | watch: 'w'
10 | }
11 | });
12 | var fs = require('fs');
13 | var path = require('path');
14 | var glob = require('glob');
15 | var contra = require('contra');
16 | var mkdirp = require('mkdirp');
17 | var monocle = require('monocle')();
18 | var jade = require('..');
19 | var files = argv._;
20 | var extname = '.js';
21 | var re = /\.jade$/;
22 | var options = {};
23 |
24 | // worth a shot, tell jade it can use ultramarked for this thing
25 | try {
26 | var transformers = require('jade/node_modules/transformers');
27 | transformers.markdown.engines.push('megamark');
28 | } catch (e) {
29 | }
30 |
31 | if (argv.obj) {
32 | options = JSON.parse(argv.obj);
33 | }
34 |
35 | options.compileDebug = argv.debug !== false;
36 | options.out = argv.out || argv.o;
37 | options.pretty = argv.pretty || argv.p;
38 |
39 | if (files.length === 1 && files[0].indexOf('*') !== -1) {
40 | files = glob.sync(files[0]); // `npm run` doesn't expand the pattern :(
41 | }
42 |
43 | contra.each(files, renderFile);
44 |
45 | if (argv.watch) {
46 | monocle.watchFiles({
47 | files: files,
48 | listener: listener
49 | });
50 | }
51 |
52 | function renderFile (file, done) {
53 | contra.waterfall([
54 | function (next) {
55 | fs.lstat(file, next);
56 | },
57 | function (stat, next) {
58 | if ((stat.isFile() || stat.isSymbolicLink()) && re.test(file)) {
59 | render(next);
60 | } else if (stat.isDirectory()) {
61 | dir(next);
62 | } else {
63 | next();
64 | }
65 | }
66 | ], done);
67 |
68 | function render (done) {
69 | var prefix = 'var jade = require("jadum/runtime");\nmodule.exports = ';
70 | var fn;
71 |
72 | fs.readFile(file, 'utf8', gotData);
73 |
74 | function gotData (err, data) {
75 | if (err) {
76 | done(err); return;
77 | }
78 | options.filename = file;
79 | fn = jade.compileClient(data, options);
80 | file = file.replace(re, extname);
81 | if (options.out) {
82 | file = path.join(options.out, file);
83 | }
84 | var dir = path.resolve(path.dirname(file));
85 | mkdirp(dir, ensuredDir);
86 | }
87 |
88 | function ensuredDir (err) {
89 | if (err) {
90 | done(err); return;
91 | }
92 | fs.writeFile(file, prefix + fn, 'utf8', done);
93 | }
94 | }
95 |
96 | function dir (done) {
97 | contra.waterfall([
98 | function (next) {
99 | fs.readdir(file, next);
100 | },
101 | function (files, next) {
102 | contra.each(files.map(expand), renderFile, next);
103 | }
104 | ], done);
105 | }
106 |
107 | function expand (filename) {
108 | return file + '/' + filename;
109 | }
110 | }
111 |
112 | function listener (file) {
113 | var relative = path.relative(process.cwd(), file.absolutePath);
114 | console.log('jadum: Changes detected in %s, recompiling.', relative);
115 | renderFile(relative);
116 | }
117 |
--------------------------------------------------------------------------------
/camelCase.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var spaces = /\s+/g;
4 | var dashes = /[-_]+/g;
5 | var dashesLeadTrail = /^-|-$/g;
6 | var invalid = /[^\x20\x2D0-9A-Z\x5Fa-z\xC0-\xD6\xD8-\xF6\xF8-\xFF]/g;
7 | var accentCodePoints = /[\xC0-\xFF]/g;
8 | var accents = [
9 | [/[\xC0-\xC5]/g, 'A'],
10 | [/[\xC6]/g, 'AE'],
11 | [/[\xC7]/g, 'C'],
12 | [/[\xC8-\xCB]/g, 'E'],
13 | [/[\xCC-\xCF]/g, 'I'],
14 | [/[\xD0]/g, 'D'],
15 | [/[\xD1]/g, 'N'],
16 | [/[\xD2-\xD6\xD8]/g, 'O'],
17 | [/[\xD9-\xDC]/g, 'U'],
18 | [/[\xDD]/g, 'Y'],
19 | [/[\xDE]/g, 'P'],
20 | [/[\xE0-\xE5]/g, 'a'],
21 | [/[\xE6]/g, 'ae'],
22 | [/[\xE7]/g, 'c'],
23 | [/[\xE8-\xEB]/g, 'e'],
24 | [/[\xEC-\xEF]/g, 'i'],
25 | [/[\xF1]/g, 'n'],
26 | [/[\xF2-\xF6\xF8]/g, 'o'],
27 | [/[\xF9-\xFC]/g, 'u'],
28 | [/[\xFE]/g, 'p'],
29 | [/[\xFD\xFF]/g, 'y']
30 | ];
31 |
32 | function slugify (text) {
33 | if (text.search(accentCodePoints) === -1) {
34 | return text;
35 | }
36 | return translate(text, accents);
37 | }
38 |
39 | function translate (text, translations) {
40 | return translations.reduce(function (text, a) {
41 | return text.replace(a[0], a[1]);
42 | }, text);
43 | }
44 |
45 | function slug (text) {
46 | return slugify(text)
47 | .replace(invalid, '-') // remove invalid chars
48 | .replace(spaces, '-') // collapse whitespace and replace by '-'
49 | .replace(dashes, '-') // collapse dashes
50 | .replace(dashesLeadTrail, '') // remove leading or trailing dashes
51 | .trim()
52 | .toLowerCase();
53 | }
54 |
55 | function upper (m, g) {
56 | return g.toUpperCase();
57 | }
58 |
59 | function camelCase (text) {
60 | return slug(text).replace(/-(.)/g, upper);
61 | }
62 |
63 | module.exports = camelCase;
64 |
--------------------------------------------------------------------------------
/jadum.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var jade = require('jade');
5 | var Compiler = require('./Compiler');
6 | var Parser = require('./Parser');
7 | var camelCase = require('./camelCase');
8 | var api = {};
9 |
10 | function compileClient (text, o) {
11 | var options = o;
12 | if (options === void 0) {
13 | options = {};
14 | }
15 | options.compiler = Compiler;
16 | options.parser = Parser;
17 | return jade.compileClient(text, options).replace('template', name(options.filename));
18 | }
19 |
20 | function name (file) {
21 | return camelCase(path.basename(file, '.jade'));
22 | }
23 |
24 | function map (key) {
25 | api[key] = jade[key];
26 | }
27 |
28 | Object.keys(jade).forEach(map);
29 |
30 | api.compileClient = compileClient;
31 |
32 | module.exports = api;
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jadum",
3 | "description": "A lean Jade compiler that understands Browserify and reuses partials",
4 | "version": "1.4.2",
5 | "homepage": "https://github.com/bevacqua/jadum",
6 | "author": {
7 | "name": "Nicolas Bevacqua",
8 | "email": "nico@bevacqua.io",
9 | "url": "http://bevacqua.io"
10 | },
11 | "main": "jadum.js",
12 | "license": "MIT",
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/bevacqua/jadum.git"
16 | },
17 | "bin": {
18 | "jadum": "bin/jadum"
19 | },
20 | "scripts": {
21 | "compare": "mkdir -p test/compiled && jadum test/fixture/listing.jade && mv test/fixture/listing.js test/compiled/listing.jadum.js && jade test/fixture/listing.jade --client --no-debug && mv test/fixture/listing.js test/compiled/listing.jade.js",
22 | "test": "node test/*.js"
23 | },
24 | "dependencies": {
25 | "contra": "^1.7.0",
26 | "glob": "^7.1.1",
27 | "jade": "^1.7.0",
28 | "minimist": "^1.1.0",
29 | "mkdirp": "^0.5.0",
30 | "monocle": "^1.1.51"
31 | },
32 | "devDependencies": {
33 | "tape": "^4.6.3"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/runtime.js:
--------------------------------------------------------------------------------
1 | module.exports = require('jade/runtime');
2 |
--------------------------------------------------------------------------------
/test/compiled/listing.jade.js:
--------------------------------------------------------------------------------
1 | function template(locals) {
2 | var buf = [];
3 | var jade_mixins = {};
4 | var jade_interp;
5 |
6 | buf.push("This is some awesome listing!
");
7 | // iterate ['a', 'b', 'c']
8 | ;(function(){
9 | var $$obj = ['a', 'b', 'c'];
10 | if ('number' == typeof $$obj.length) {
11 |
12 | for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
13 | var item = $$obj[$index];
14 |
15 | buf.push("- Here comes item:Note that this is quite a big template fileBut it's included everywhere, because Jade!So that's awesome... but compiling becomes an issue..." + (jade.escape(null == (jade_interp = item) ? "" : jade_interp)) + "
");
16 | }
17 |
18 | } else {
19 | var $$l = 0;
20 | for (var $index in $$obj) {
21 | $$l++; var item = $$obj[$index];
22 |
23 | buf.push("- Here comes item:Note that this is quite a big template fileBut it's included everywhere, because Jade!So that's awesome... but compiling becomes an issue..." + (jade.escape(null == (jade_interp = item) ? "" : jade_interp)) + "
");
24 | }
25 |
26 | }
27 | }).call(this);
28 |
29 | buf.push("
");;return buf.join("");
30 | }
31 |
--------------------------------------------------------------------------------
/test/compiled/listing.jadum.js:
--------------------------------------------------------------------------------
1 | var jade = require("jadum/runtime");
2 | module.exports = function listing(locals) {
3 | var buf = [];
4 | var jade_mixins = {};
5 | var jade_interp;
6 | ;var locals_for_with = (locals || {});(function (require) {
7 | buf.push("This is some awesome listing!
");
8 | // iterate ['a', 'b', 'c']
9 | ;(function(){
10 | var $$local = locals["item"];
11 | var $$obj = ['a', 'b', 'c'];
12 | if ('number' == typeof $$obj.length) {
13 |
14 | for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) {
15 | var item = locals["item"] = $$obj[$index];
16 |
17 | buf.push(require("./item").call(this, locals));
18 | }
19 |
20 | } else {
21 | var $$l = 0;
22 | for (var $index in $$obj) {
23 | $$l++; var item = locals["item"] = $$obj[$index];
24 |
25 | buf.push(require("./item").call(this, locals));
26 | }
27 |
28 | locals["item"] = $$local;
29 | }
30 | }).call(this);
31 |
32 | buf.push("
");}.call(this,"require" in locals_for_with?locals_for_with.require:typeof require!=="undefined"?require:undefined));;return buf.join("");
33 | }
--------------------------------------------------------------------------------
/test/fixture/item.jade:
--------------------------------------------------------------------------------
1 | li
2 | span Here comes item:
3 | span Note that this is quite a big template file
4 | span But it's included everywhere, because Jade!
5 | span So that's awesome... but compiling becomes an issue...
6 | span=item
7 |
--------------------------------------------------------------------------------
/test/fixture/listing.jade:
--------------------------------------------------------------------------------
1 | h1 This is some awesome listing!
2 | ul
3 | each item in ['a', 'b', 'c']
4 | include item
5 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var test = require('tape');
5 | var jadum = require('..');
6 |
7 | function read (file) {
8 | return fs.readFileSync(file, 'utf8');
9 | }
10 |
11 | test(function (t) {
12 | var file = 'test/fixture/listing.jade';
13 | var compiled = jadum.compileClient(read(file), { filename: file });
14 | var prefix = 'var jade = require("jadum/runtime");\nmodule.exports = ';
15 | var expected = read('test/compiled/listing.jadum.js');
16 |
17 | t.equal(prefix + compiled, expected);
18 | t.end();
19 | });
20 |
--------------------------------------------------------------------------------