├── .gitignore ├── .jshintrc ├── LICENSE ├── Makefile ├── README.md ├── import-macros.sjs ├── index.js ├── package.json └── specs ├── assert.sjs ├── id.sjs └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | specs/client.bundle.js 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": "vars", 4 | "asi": true, 5 | "expr": true, 6 | "globalstrict": true, 7 | "globals": { 8 | "window": false, 9 | "Buffer": false, 10 | "require": false, 11 | "process": false, 12 | "module": false, 13 | "exports": false, 14 | "console": false, 15 | "document": false, 16 | "setTimeout": false, 17 | "__filename": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andrey Popp 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = ./node_modules/.bin 2 | REPO = $(shell cat .git/config | grep url | xargs echo | sed -E 's/^url = //g') 3 | REPONAME = $(shell echo $(REPO) | sed -E 's_.+:([a-zA-Z0-9_\-]+)/([a-zA-Z0-9_\-]+)\.git_\1/\2_') 4 | 5 | install link: 6 | @npm $@ 7 | 8 | test: 9 | @$(BIN)/mocha -b -R spec ./specs/*.js 10 | 11 | lint: 12 | @$(BIN)/jshint *.js 13 | 14 | release-patch: test 15 | @$(call release,patch) 16 | 17 | release-minor: test 18 | @$(call release,minor) 19 | 20 | release-major: test 21 | @$(call release,major) 22 | 23 | publish: 24 | git push --tags origin HEAD:master 25 | npm publish 26 | 27 | define release 28 | VERSION=`node -pe "require('./package.json').version"` && \ 29 | NEXT_VERSION=`node -pe "require('semver').inc(\"$$VERSION\", '$(1)')"` && \ 30 | node -e "\ 31 | var j = require('./package.json');\ 32 | j.version = \"$$NEXT_VERSION\";\ 33 | var s = JSON.stringify(j, null, 2);\ 34 | require('fs').writeFileSync('./package.json', s);" && \ 35 | git commit -m "release $$NEXT_VERSION" -- package.json && \ 36 | git tag "$$NEXT_VERSION" -m "release $$NEXT_VERSION" 37 | endef 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sweetify 2 | 3 | Browserify transform for using Sweet.js macros. 4 | 5 | ## Installation and usage 6 | 7 | Install via npm: 8 | 9 | % npm install sweetify 10 | 11 | and then use with browserify: 12 | 13 | % browserify -t sweetify ./mycode.sjs 14 | 15 | Sweeten transform kicks in only for files with `.sjs` extension. 16 | 17 | ## Importing macros 18 | 19 | You can use a restricted form of ES6 module import statement to import macros in 20 | current module's scope: 21 | 22 | import macros from './my-macros.sjs' 23 | import macros from 'some-pkg' 24 | 25 | // use everything exported from ./my-macros.sjs or some-pkg node module 26 | 27 | ## Example 28 | 29 | As an example, you can see how you can re-use macros already present on npm: 30 | 31 | % npm install sparkler 32 | 33 | Then create a source file called `sparkler-example.sjs`: 34 | 35 | import macros from 'sparkler/macros'; 36 | 37 | function myPatterns { 38 | case 42 => 'The meaning of life' 39 | case a @ String => 'Hello ' + a 40 | case [...front, back] => back.concat(front) 41 | case { foo: 'bar', x, 'y' } => x 42 | case Email{ user, domain: 'foo.com' } => user 43 | case (a, b, ...rest) => rest 44 | case [...{ x, y }] => _.zip(x, y) 45 | case x @ Number if x > 10 => x 46 | } 47 | 48 | This example uses excellent [sparkler](https://npmjs.org/package/sparkler) 49 | macros library which implements pattern matching for JavaScript. Just run 50 | 51 | % browserify -t sweetify ./sparkler-example.sjs 52 | 53 | to produce a JavaScript which you can run in a browser or any other runtime (all 54 | pattern matching constructs are compiler into plain JS constructs). 55 | -------------------------------------------------------------------------------- /import-macros.sjs: -------------------------------------------------------------------------------- 1 | let import = macro { 2 | rule { macros from $id:lit } => {} 3 | rule {} => { import } 4 | } 5 | 6 | export import; 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var mapAsync = require('map-async'); 5 | var sourceMap = require('convert-source-map'); 6 | var through = require('through'); 7 | var sweet = require('sweet.js'); 8 | var resolve = require('browser-resolve'); 9 | 10 | var isSJS = /.+\.sjs$/; 11 | 12 | var eliminateIncludeMacros = sweet.loadNodeModule( 13 | process.cwd(), 14 | require.resolve('./import-macros.sjs')); 15 | 16 | function extractMacroIncludes(src) { 17 | var tokens = sweet.expand(src); 18 | var mods = []; 19 | 20 | var tok, mod; 21 | 22 | for (var i = 0, len = tokens.length; i < len; i++) { 23 | 24 | tok = tokens[i]; 25 | if (!(tok && tok.token.type === 4 && tok.token.value === 'import')) 26 | continue; 27 | 28 | tok = tokens[i + 1]; 29 | if (!(tok && tok.token.type === 3 && tok.token.value === 'macros')) 30 | continue; 31 | 32 | tok = tokens[i + 2]; 33 | if (!(tok && tok.token.type === 3 && tok.token.value === 'from')) 34 | continue; 35 | 36 | tok = mod = tokens[i + 3]; 37 | if (!(tok && tok.token.type === 8)) 38 | continue; 39 | 40 | mods.push(mod.token.value); 41 | } 42 | return mods; 43 | } 44 | 45 | function resolveMany(ids, opts, cb) { 46 | return mapAsync(ids, function(id, cb) { 47 | resolve(id, opts, cb); 48 | }, cb); 49 | } 50 | 51 | function loadModules(filenames, cb) { 52 | return mapAsync(filenames, function(filename, cb) { 53 | fs.readFile(filename, 'utf8', function(err, contents) { 54 | var mod; 55 | 56 | if (err) 57 | return cb(err); 58 | 59 | try { 60 | mod = sweet.loadModule(contents); 61 | } catch(e) { 62 | return cb(e); 63 | } 64 | 65 | cb(null, mod); 66 | }); 67 | }, cb); 68 | } 69 | 70 | function resolveAndLoadModules(ids, opts, cb) { 71 | resolveMany(ids, opts, function(err, filenames) { 72 | if (err) 73 | return cb(err); 74 | loadModules(filenames, cb); 75 | }); 76 | } 77 | 78 | module.exports = function(filename, opts) { 79 | if (!isSJS.exec(filename)) 80 | return through(); 81 | 82 | opts = opts || {}; 83 | 84 | var buffer = ''; 85 | 86 | return through( 87 | function(chunk) { buffer += chunk; }, 88 | function() { 89 | var stream = this; 90 | var includes; 91 | 92 | try { 93 | includes = extractMacroIncludes(buffer); 94 | } catch(e) { 95 | return stream.emit('error', e); 96 | } 97 | 98 | // load macros for all files 99 | if (opts.modules && opts.modules.length) { 100 | opts.modules.forEach(function(moduleName){ 101 | if (includes.indexOf(moduleName) === -1) { 102 | includes.push(moduleName); 103 | } 104 | }); 105 | } 106 | 107 | resolveAndLoadModules(includes, {filename: filename}, function(err, modules) { 108 | if (err) { 109 | return stream.emit('error', err); 110 | } 111 | 112 | var r; 113 | 114 | modules.unshift(eliminateIncludeMacros); 115 | 116 | try { 117 | r = sweet.compile(buffer, { 118 | modules: modules, 119 | sourceMap: true, 120 | filename: filename 121 | }); 122 | } catch(e) { 123 | return stream.emit('error', e); 124 | } 125 | 126 | var map = sourceMap.fromJSON(r.sourceMap); 127 | map.sourcemap.sourcesContent = [buffer]; 128 | 129 | stream.queue(r.code + '\n' + map.toComment()); 130 | stream.queue(null); 131 | }); 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sweetify", 3 | "version": "0.4.0", 4 | "description": "Browserify transform to use Sweet.js macros", 5 | "main": "index.js", 6 | "dependencies": { 7 | "browser-resolve": "1.3.2", 8 | "sweet.js": "0.7.1", 9 | "through": "2.3.4", 10 | "map-async": "~0.1.1", 11 | "convert-source-map": "0.4.1" 12 | }, 13 | "devDependencies": { 14 | "browserify": "5.10.1", 15 | "semver": "3.0.1", 16 | "mocha": "1.21.4", 17 | "jshint": "2.5.5", 18 | "esprima": "1.2.2" 19 | }, 20 | "scripts": { 21 | "test": "mocha -R spec specs/*.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/andreypopp/sweetify" 26 | }, 27 | "keywords": [ 28 | "browserify", 29 | "transform", 30 | "sweet.js", 31 | "macro", 32 | "macros", 33 | "browserify-transform" 34 | ], 35 | "author": "Andrey Popp <8mayday@gmail.com>", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/andreypopp/sweetify/issues" 39 | }, 40 | "homepage": "https://github.com/andreypopp/sweetify" 41 | } -------------------------------------------------------------------------------- /specs/assert.sjs: -------------------------------------------------------------------------------- 1 | import macros from './id.sjs'; 2 | 3 | var x = 12; 4 | var y = id x; 5 | 6 | function z() { 7 | throw id new Error('x'); 8 | } 9 | 10 | z(); 11 | -------------------------------------------------------------------------------- /specs/id.sjs: -------------------------------------------------------------------------------- 1 | macro id { 2 | case {id $x } => { 3 | return #{ 4 | $x 5 | } 6 | } 7 | } 8 | 9 | export id; 10 | -------------------------------------------------------------------------------- /specs/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var assert = require('assert'); 3 | var browserify = require('browserify'); 4 | var esprima = require('esprima'); 5 | var sweetify = require('../index'); 6 | 7 | describe('sweetify', function() { 8 | 9 | it('works', function(done) { 10 | 11 | browserify(path.join(__dirname, 'assert.sjs')) 12 | .transform(sweetify) 13 | .bundle(function(err, contents) { 14 | if (err) return done(err); 15 | assert.ok(contents); 16 | try { 17 | assert.ok(esprima.parse(contents)); 18 | } catch (e) { 19 | return done(e); 20 | } 21 | done(); 22 | }); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------