├── .gitignore ├── test ├── fixtures │ ├── link │ ├── default │ │ ├── foo.unknown │ │ ├── .dotfile.coffee │ │ ├── foo │ │ │ ├── bar.coffee │ │ │ ├── baz.coffee │ │ │ ├── buz │ │ │ │ └── index.coffee │ │ │ └── bar │ │ │ │ └── baz.js │ │ ├── relative │ │ │ ├── b.js │ │ │ ├── a.js │ │ │ └── index.js │ │ ├── exported_property.coffee │ │ ├── custom_exports.js │ │ ├── circular │ │ │ ├── error.js │ │ │ ├── using_exports_b.js │ │ │ ├── using_exports_a.js │ │ │ ├── using_module_exports_b.js │ │ │ ├── using_module_exports_a.js │ │ │ └── index.js │ │ └── module.coffee │ ├── dependencies │ │ ├── zepto.js │ │ ├── backbone.js │ │ └── underscore.js │ ├── alternate │ │ ├── hello.alert │ │ └── nonsense.coffee │ ├── additional │ │ ├── hello.js │ │ └── foo │ │ │ └── bar.js │ └── eco │ │ └── hello.html.eco └── test_stitch.coffee ├── Cakefile ├── package.json ├── LICENSE ├── README.md ├── src └── stitch.coffee └── lib └── stitch.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /test/fixtures/link: -------------------------------------------------------------------------------- 1 | default -------------------------------------------------------------------------------- /test/fixtures/default/foo.unknown: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/default/.dotfile.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/dependencies/zepto.js: -------------------------------------------------------------------------------- 1 | // Zepto 2 | -------------------------------------------------------------------------------- /test/fixtures/alternate/hello.alert: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /test/fixtures/default/foo/bar.coffee: -------------------------------------------------------------------------------- 1 | bar = "baz" 2 | -------------------------------------------------------------------------------- /test/fixtures/dependencies/backbone.js: -------------------------------------------------------------------------------- 1 | // Backbone 2 | -------------------------------------------------------------------------------- /test/fixtures/dependencies/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore 2 | -------------------------------------------------------------------------------- /test/fixtures/additional/hello.js: -------------------------------------------------------------------------------- 1 | exports.hello = "hello"; 2 | -------------------------------------------------------------------------------- /test/fixtures/default/foo/baz.coffee: -------------------------------------------------------------------------------- 1 | exports.baz = "biz" 2 | -------------------------------------------------------------------------------- /test/fixtures/default/relative/b.js: -------------------------------------------------------------------------------- 1 | module.exports = "b"; 2 | -------------------------------------------------------------------------------- /test/fixtures/default/foo/buz/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.buz= "BUZ" -------------------------------------------------------------------------------- /test/fixtures/default/exported_property.coffee: -------------------------------------------------------------------------------- 1 | exports.foo = "bar" 2 | -------------------------------------------------------------------------------- /test/fixtures/additional/foo/bar.js: -------------------------------------------------------------------------------- 1 | exports.filename = "additional/foo/bar.js"; 2 | -------------------------------------------------------------------------------- /test/fixtures/default/foo/bar/baz.js: -------------------------------------------------------------------------------- 1 | exports.baz = require("foo/baz").baz; 2 | 3 | -------------------------------------------------------------------------------- /test/fixtures/default/relative/a.js: -------------------------------------------------------------------------------- 1 | exports.a = "a"; 2 | exports.b = require("./b"); 3 | -------------------------------------------------------------------------------- /test/fixtures/eco/hello.html.eco: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | hello <%= @name %>! 5 | -------------------------------------------------------------------------------- /test/fixtures/default/custom_exports.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return "foo"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/error.js: -------------------------------------------------------------------------------- 1 | exports.a = require('./using_exports_a'); 2 | throw 'hello'; 3 | -------------------------------------------------------------------------------- /test/fixtures/alternate/nonsense.coffee: -------------------------------------------------------------------------------- 1 | # This is some nonsense that should throw a parse error 2 | 3 | class for when if void 4 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/using_exports_b.js: -------------------------------------------------------------------------------- 1 | var a = require("./using_exports_a"); 2 | exports.b = function() { 3 | return a.a(); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/using_exports_a.js: -------------------------------------------------------------------------------- 1 | exports.a = function() { 2 | return "a"; 3 | }; 4 | exports.b = require("./using_exports_b").b; 5 | -------------------------------------------------------------------------------- /test/fixtures/default/module.coffee: -------------------------------------------------------------------------------- 1 | exports.foo = require("exported_property").foo 2 | exports.bar = require("custom_exports") 3 | exports.baz = require("foo/bar/baz").baz 4 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/using_module_exports_b.js: -------------------------------------------------------------------------------- 1 | A = require('./using_module_exports_a'); 2 | 3 | module.exports = { 4 | b: function() { 5 | return A.a() 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/default/relative/index.js: -------------------------------------------------------------------------------- 1 | exports.a = require("./a"); 2 | exports.custom = require("../custom_exports"); 3 | exports.baz = require("../foo/bar/baz").baz; 4 | exports.buz = require("../foo/bar/../buz").buz; 5 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/using_module_exports_a.js: -------------------------------------------------------------------------------- 1 | var B; 2 | 3 | module.exports = { 4 | a: function() { 5 | return 'a'; 6 | }, 7 | b: function() { 8 | return B.b() 9 | } 10 | }; 11 | 12 | B = require('./using_module_exports_b'); 13 | -------------------------------------------------------------------------------- /test/fixtures/default/circular/index.js: -------------------------------------------------------------------------------- 1 | exports.using_exports_a = require("./using_exports_a"); 2 | exports.using_exports_b = require("./using_exports_b"); 3 | exports.using_module_exports_a = require("./using_module_exports_a"); 4 | exports.using_module_exports_b = require("./using_module_exports_b"); 5 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {print} = require 'util' 2 | {spawn} = require 'child_process' 3 | 4 | build = (callback) -> 5 | coffee = spawn 'coffee', ['-c', '-o', 'lib', 'src'] 6 | coffee.stderr.on 'data', (data) -> 7 | process.stderr.write data.toString() 8 | coffee.stdout.on 'data', (data) -> 9 | print data.toString() 10 | coffee.on 'exit', (code) -> 11 | callback?() if code is 0 12 | 13 | task 'build', 'Build lib/ from src/', -> 14 | build() 15 | 16 | task 'test', 'Run tests', -> 17 | build -> 18 | process.chdir __dirname 19 | {reporters} = require 'nodeunit' 20 | reporters.default.run ['test'] 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "stitch" 2 | , "description": "Stitch your CommonJS modules together for the browser" 3 | , "contributors": 4 | [ "Josh Peek" 5 | , "Sam Stephenson" 6 | ] 7 | , "version": "0.3.3" 8 | , "licenses": [ 9 | { "type": "MIT" 10 | , "url": "http://github.com/sstephenson/stitch/raw/master/LICENSE" 11 | }] 12 | , "main": "./lib/stitch" 13 | , "repository": 14 | { "type": "git" 15 | , "url": "http://github.com/sstephenson/stitch.git" 16 | } 17 | , "dependencies": 18 | { "async": ">=0.1.15" 19 | , "underscore": ">=1.1.3" 20 | } 21 | , "devDependencies" : 22 | { "coffee-script" : ">= 1.3.0" 23 | , "nodeunit" : ">= 0.6.4" 24 | , "eco": ">= 1.1.0-rc-3" 25 | } 26 | , "engine": { "node": ">=0.6" } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Joshua Peek 2 | Copyright (c) 2010 Sam Stephenson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 |
4 | Develop and test your JavaScript applications as CommonJS modules in
5 | Node.js. Then __Stitch__ them together to run in the browser.
6 |
7 | npm install stitch
8 |
9 | Bundle code in lib/ and vendor/ and serve it with [Express](http://expressjs.com/):
10 |
11 | var stitch = require('stitch');
12 | var express = require('express');
13 |
14 | var package = stitch.createPackage({
15 | paths: [__dirname + '/lib', __dirname + '/vendor']
16 | });
17 |
18 | var app = express.createServer();
19 | app.get('/application.js', package.createServer());
20 | app.listen(3000);
21 |
22 | Or build it to a file:
23 |
24 | var stitch = require('stitch');
25 | var fs = require('fs');
26 |
27 | var package = stitch.createPackage({
28 | paths: [__dirname + '/lib', __dirname + '/vendor']
29 | });
30 |
31 | package.compile(function (err, source){
32 | fs.writeFile('package.js', source, function (err) {
33 | if (err) throw err;
34 | console.log('Compiled package.js');
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/src/stitch.coffee:
--------------------------------------------------------------------------------
1 | _ = require 'underscore'
2 | async = require 'async'
3 | fs = require 'fs'
4 |
5 | {extname, join, normalize} = require 'path'
6 |
7 | exports.compilers = compilers =
8 | js: (module, filename) ->
9 | content = fs.readFileSync filename, 'utf8'
10 | module._compile content, filename
11 |
12 | try
13 | CoffeeScript = require 'coffee-script'
14 | compilers.coffee = (module, filename) ->
15 | content = CoffeeScript.compile fs.readFileSync filename, 'utf8'
16 | module._compile content, filename
17 | catch err
18 |
19 | try
20 | eco = require 'eco'
21 | if eco.precompile
22 | compilers.eco = (module, filename) ->
23 | content = eco.precompile fs.readFileSync filename, 'utf8'
24 | module._compile "module.exports = #{content}", filename
25 | else
26 | compilers.eco = (module, filename) ->
27 | content = eco.compile fs.readFileSync filename, 'utf8'
28 | module._compile content, filename
29 | catch err
30 |
31 |
32 | exports.Package = class Package
33 | constructor: (config) ->
34 | @identifier = config.identifier ? 'require'
35 | @paths = config.paths ? ['lib']
36 | @dependencies = config.dependencies ? []
37 | @compilers = _.extend {}, compilers, config.compilers
38 |
39 | @cache = config.cache ? true
40 | @mtimeCache = {}
41 | @compileCache = {}
42 |
43 | compile: (callback) ->
44 | async.parallel [
45 | @compileDependencies
46 | @compileSources
47 | ], (err, parts) ->
48 | if err then callback err
49 | else callback null, parts.join("\n")
50 |
51 | compileDependencies: (callback) =>
52 | async.map @dependencies, fs.readFile, (err, dependencySources) =>
53 | if err then callback err
54 | else callback null, dependencySources.join("\n")
55 |
56 | compileSources: (callback) =>
57 | async.reduce @paths, {}, _.bind(@gatherSourcesFromPath, @), (err, sources) =>
58 | return callback err if err
59 |
60 | result = """
61 | (function(/*! Stitch !*/) {
62 | if (!this.#{@identifier}) {
63 | var modules = {}, cache = {}, require = function(name, root) {
64 | var path = expand(root, name), module = cache[path], fn;
65 | if (module) {
66 | return module.exports;
67 | } else if (fn = modules[path] || modules[path = expand(path, './index')]) {
68 | module = {id: path, exports: {}};
69 | try {
70 | cache[path] = module;
71 | fn(module.exports, function(name) {
72 | return require(name, dirname(path));
73 | }, module);
74 | return module.exports;
75 | } catch (err) {
76 | delete cache[path];
77 | throw err;
78 | }
79 | } else {
80 | throw 'module \\'' + name + '\\' not found';
81 | }
82 | }, expand = function(root, name) {
83 | var results = [], parts, part;
84 | if (/^\\.\\.?(\\/|$)/.test(name)) {
85 | parts = [root, name].join('/').split('/');
86 | } else {
87 | parts = name.split('/');
88 | }
89 | for (var i = 0, length = parts.length; i < length; i++) {
90 | part = parts[i];
91 | if (part == '..') {
92 | results.pop();
93 | } else if (part != '.' && part != '') {
94 | results.push(part);
95 | }
96 | }
97 | return results.join('/');
98 | }, dirname = function(path) {
99 | return path.split('/').slice(0, -1).join('/');
100 | };
101 | this.#{@identifier} = function(name) {
102 | return require(name, '');
103 | }
104 | this.#{@identifier}.define = function(bundle) {
105 | for (var key in bundle)
106 | modules[key] = bundle[key];
107 | };
108 | }
109 | return this.#{@identifier}.define;
110 | }).call(this)({
111 | """
112 |
113 | index = 0
114 | for name, {filename, source} of sources
115 | result += if index++ is 0 then "" else ", "
116 | result += JSON.stringify name
117 | result += ": function(exports, require, module) {#{source}}"
118 |
119 | result += """
120 | });\n
121 | """
122 |
123 | callback err, result
124 |
125 | createServer: ->
126 | (req, res, next) =>
127 | @compile (err, source) ->
128 | if err
129 | console.error "#{err.stack}"
130 | message = "" + err.stack
131 | res.writeHead 500, 'Content-Type': 'text/javascript'
132 | res.end "throw #{JSON.stringify(message)}"
133 | else
134 | res.writeHead 200, 'Content-Type': 'text/javascript'
135 | res.end source
136 |
137 |
138 | gatherSourcesFromPath: (sources, sourcePath, callback) ->
139 | fs.stat sourcePath, (err, stat) =>
140 | return callback err if err
141 |
142 | if stat.isDirectory()
143 | @getFilesInTree sourcePath, (err, paths) =>
144 | return callback err if err
145 | async.reduce paths, sources, _.bind(@gatherCompilableSource, @), callback
146 | else
147 | @gatherCompilableSource sources, sourcePath, callback
148 |
149 | gatherCompilableSource: (sources, path, callback) ->
150 | if @compilers[extname(path).slice(1)]
151 | @getRelativePath path, (err, relativePath) =>
152 | return callback err if err
153 |
154 | @compileFile path, (err, source) ->
155 | if err then callback err
156 | else
157 | extension = extname relativePath
158 | key = relativePath.slice(0, -extension.length)
159 | sources[key] =
160 | filename: relativePath
161 | source: source
162 | callback err, sources
163 | else
164 | callback null, sources
165 |
166 | getRelativePath: (path, callback) ->
167 | fs.realpath path, (err, sourcePath) =>
168 | return callback err if err
169 |
170 | async.map @paths, fs.realpath, (err, expandedPaths) ->
171 | return callback err if err
172 |
173 | for expandedPath in expandedPaths
174 | base = expandedPath + "/"
175 | if sourcePath.indexOf(base) is 0
176 | return callback null, sourcePath.slice base.length
177 | callback new Error "#{path} isn't in the require path"
178 |
179 | compileFile: (path, callback) ->
180 | extension = extname(path).slice(1)
181 |
182 | if @cache and @compileCache[path] and @mtimeCache[path] is @compileCache[path].mtime
183 | callback null, @compileCache[path].source
184 | else if compile = @compilers[extension]
185 | source = null
186 | mod =
187 | _compile: (content, filename) ->
188 | source = content
189 |
190 | try
191 | compile mod, path
192 |
193 | if @cache and mtime = @mtimeCache[path]
194 | @compileCache[path] = {mtime, source}
195 |
196 | callback null, source
197 | catch err
198 | if err instanceof Error
199 | err.message = "can't compile #{path}\n#{err.message}"
200 | else
201 | err = new Error "can't compile #{path}\n#{err}"
202 | callback err
203 | else
204 | callback new Error "no compiler for '.#{extension}' files"
205 |
206 | walkTree: (directory, callback) ->
207 | fs.readdir directory, (err, files) =>
208 | return callback err if err
209 |
210 | async.forEach files, (file, next) =>
211 | return next() if file.match /^\./
212 | filename = join directory, file
213 |
214 | fs.stat filename, (err, stats) =>
215 | @mtimeCache[filename] = stats?.mtime?.toString()
216 |
217 | if !err and stats.isDirectory()
218 | @walkTree filename, (err, filename) ->
219 | if filename
220 | callback err, filename
221 | else
222 | next()
223 | else
224 | callback err, filename
225 | next()
226 | , callback
227 |
228 | getFilesInTree: (directory, callback) ->
229 | files = []
230 | @walkTree directory, (err, filename) ->
231 | if err
232 | callback err
233 | else if filename
234 | files.push filename
235 | else
236 | callback err, files.sort()
237 |
238 |
239 | exports.createPackage = (config) ->
240 | new Package config
241 |
--------------------------------------------------------------------------------
/test/test_stitch.coffee:
--------------------------------------------------------------------------------
1 | sys = require "util"
2 | fs = require "fs"
3 | stitch = require "../."
4 |
5 | fixtureRoot = __dirname + "/fixtures"
6 | fixtures = fixtureRoot + "/default"
7 | altFixtures = fixtureRoot + "/alternate"
8 | addlFixtures = fixtureRoot + "/additional"
9 | ecoFixtures = fixtureRoot + "/eco"
10 | linkFixtures = fixtureRoot + "/link"
11 | fixtureCount = 17
12 |
13 | defaultOptions =
14 | identifier: "testRequire"
15 | paths: [fixtures]
16 | defaultPackage = stitch.createPackage defaultOptions
17 |
18 | additionalOptions =
19 | identifier: "testRequire"
20 | paths: [addlFixtures]
21 | additionalPackage = stitch.createPackage additionalOptions
22 |
23 | alternateOptions =
24 | paths: [altFixtures]
25 | alternatePackage = stitch.createPackage alternateOptions
26 |
27 | ecoOptions =
28 | identifier: "testRequire"
29 | paths: [ecoFixtures]
30 | ecoPackage = stitch.createPackage ecoOptions
31 |
32 | dependencyOptions =
33 | identifier: "testRequire"
34 | paths: [fixtures]
35 | dependencies: [
36 | fixtureRoot + "/dependencies/zepto.js"
37 | fixtureRoot + "/dependencies/underscore.js"
38 | fixtureRoot + "/dependencies/backbone.js"
39 | ]
40 | dependencyPackage = stitch.createPackage dependencyOptions
41 |
42 | linkOptions =
43 | identifier: "testRequire"
44 | paths: [linkFixtures]
45 | linkPackage = stitch.createPackage linkOptions
46 |
47 |
48 | load = (source, callback) ->
49 | (-> eval source).call module = {}
50 | callback? (source) -> (-> eval source).call module
51 | module.testRequire
52 |
53 | rescue = (callback) ->
54 | rescued = false
55 | try
56 | callback()
57 | catch err
58 | rescued = true
59 | rescued
60 |
61 |
62 | module.exports =
63 | "walk tree": (test) ->
64 | test.expect fixtureCount
65 |
66 | defaultPackage.walkTree fixtures, (err, file) ->
67 | if file
68 | test.ok file
69 | else
70 | test.done()
71 |
72 | "get files in tree": (test) ->
73 | test.expect 2
74 |
75 | defaultPackage.getFilesInTree fixtures, (err, files) ->
76 | test.ok !err
77 | test.same fixtureCount, files.length
78 | test.done()
79 |
80 | "get files in tree that does not exist": (test) ->
81 | test.expect 1
82 |
83 | defaultPackage.getFilesInTree fixtures + "/missing", (err, files) ->
84 | test.ok err
85 | test.done()
86 |
87 | "get files in empty directory": (test) ->
88 | test.expect 1
89 |
90 | dirname = fixtures + "/empty"
91 | fs.mkdirSync dirname, 0o0755
92 | defaultPackage.getFilesInTree dirname, (err, files) ->
93 | test.ok !err
94 | fs.rmdirSync dirname
95 | test.done()
96 |
97 | "compile file": (test) ->
98 | test.expect 2
99 |
100 | defaultPackage.compileFile __filename, (err, source) ->
101 | test.ok !err
102 | test.ok source.match(/\(function\(\) \{/)
103 | test.done()
104 |
105 | "compile file does not exist": (test) ->
106 | test.expect 1
107 |
108 | defaultPackage.compileFile "nosuchthing.coffee", (err, source) ->
109 | test.ok err
110 | test.done()
111 |
112 | "compile file with syntax error": (test) ->
113 | test.expect 1
114 |
115 | alternatePackage.compileFile altFixtures + "/nonsense.coffee", (err, source) ->
116 | test.ok err.toString().match(/SyntaxError/)
117 | test.done()
118 |
119 | "compile file with custom compiler": (test) ->
120 | test.expect 1
121 |
122 | options = Object.create alternateOptions
123 | options.compilers =
124 | alert: (module, filename) ->
125 | source = require('fs').readFileSync filename, 'utf8'
126 | source = "alert(#{sys.inspect source});"
127 | module._compile source, filename
128 | pkg = stitch.createPackage options
129 |
130 | pkg.compileFile altFixtures + "/hello.alert", (err, source) ->
131 | test.same "alert('hello world\\n');", source
132 | test.done()
133 |
134 | "compile file with unknown extension": (test) ->
135 | test.expect 1
136 |
137 | alternatePackage.compileFile altFixtures + "/hello.alert", (err, source) ->
138 | test.ok err.toString().match(/no compiler/)
139 | test.done()
140 |
141 | "get relative path": (test) ->
142 | test.expect 2
143 |
144 | defaultPackage.getRelativePath fixtures + "/foo/bar.coffee", (err, path) ->
145 | test.ok !err
146 | test.same 'foo/bar.coffee', path
147 | test.done()
148 |
149 | "compile generates valid javascript": (test) ->
150 | test.expect 2
151 |
152 | defaultPackage.compile (err, sources) ->
153 | test.ok !err
154 | testRequire = load sources
155 | test.ok typeof testRequire is "function"
156 | test.done()
157 |
158 | "compile module with custom exports": (test) ->
159 | test.expect 3
160 |
161 | defaultPackage.compile (err, sources) ->
162 | test.ok !err
163 | testRequire = load sources
164 | result = testRequire("custom_exports")
165 | test.ok typeof result is "function"
166 | test.same "foo", result()
167 | test.done()
168 |
169 | "compile module with exported property": (test) ->
170 | test.expect 2
171 |
172 | defaultPackage.compile (err, sources) ->
173 | test.ok !err
174 | testRequire = load sources
175 | test.same "bar", testRequire("exported_property").foo
176 | test.done()
177 |
178 | "compile module with requires": (test) ->
179 | test.expect 4
180 |
181 | defaultPackage.compile (err, sources) ->
182 | test.ok !err
183 | testRequire = load sources
184 | module = testRequire("module")
185 | test.same "bar", module.foo
186 | test.same "foo", module.bar()
187 | test.same "biz", module.baz
188 | test.done()
189 |
190 | "runtime require only loads files once": (test) ->
191 | test.expect 3
192 |
193 | defaultPackage.compile (err, sources) ->
194 | test.ok !err
195 | testRequire = load sources
196 | module = testRequire("module")
197 | test.ok !module.x
198 | module.x = "foo"
199 | test.same "foo", testRequire("module").x
200 | test.done()
201 |
202 | "look for module index if necessary": (test) ->
203 | test.expect 2
204 |
205 | defaultPackage.compile (err, sources) ->
206 | test.ok !err
207 | testRequire = load sources
208 | buz = testRequire("foo/buz").buz
209 | test.same buz, "BUZ"
210 | test.done()
211 |
212 | "modules can be defined at runtime": (test) ->
213 | test.expect 3
214 |
215 | defaultPackage.compile (err, sources) ->
216 | test.ok !err
217 | testRequire = load sources
218 |
219 | test.ok rescue -> testRequire("frob")
220 |
221 | testRequire.define
222 | "frob": (exports, require, module) ->
223 | exports.frob = require("foo/buz").buz
224 |
225 | test.same "BUZ", testRequire("frob").frob
226 | test.done()
227 |
228 | "multiple packages may share the same require namespace": (test) ->
229 | test.expect 5
230 |
231 | defaultPackage.compile (err, sources) ->
232 | test.ok !err
233 | testRequire = load sources, (load) ->
234 | additionalPackage.compile (err, sources) ->
235 | test.ok !err
236 | load sources
237 |
238 | test.same "hello", testRequire("hello").hello
239 | test.same "additional/foo/bar.js", testRequire("foo/bar").filename
240 | test.same "biz", testRequire("foo/bar/baz").baz;
241 | test.done()
242 |
243 | "relative require": (test) ->
244 | test.expect 6
245 |
246 | defaultPackage.compile (err, sources) ->
247 | test.ok !err
248 | testRequire = load sources
249 |
250 | relative = testRequire("relative")
251 | test.same "a", relative.a.a
252 | test.same "b", relative.a.b
253 | test.same "foo", relative.custom()
254 | test.same "biz", relative.baz
255 | test.same "BUZ", relative.buz
256 | test.done()
257 |
258 | "circular require": (test) ->
259 | test.expect 7
260 |
261 | defaultPackage.compile (err, sources) ->
262 | test.ok !err
263 | testRequire = load sources
264 |
265 | circular = testRequire("circular")
266 | test.same "a", circular.using_exports_a.a()
267 | test.same "a", circular.using_exports_b.b()
268 | test.same "a", circular.using_exports_a.b()
269 |
270 | test.same "a", circular.using_module_exports_a.a()
271 | test.same "a", circular.using_module_exports_b.b()
272 | test.same "a", circular.using_module_exports_a.b()
273 |
274 | test.done()
275 |
276 | "errors at require time don't leave behind a partially loaded cache": (test) ->
277 | test.expect 3
278 |
279 | defaultPackage.compile (err, sources) ->
280 | test.ok !err
281 | testRequire = load sources
282 |
283 | test.ok rescue -> testRequire("circular/error")
284 | test.ok rescue -> testRequire("circular/error")
285 | test.done()
286 |
287 | "dependencies option concatenates files in order": (test) ->
288 | test.expect 5
289 | dependencyPackage.compile (err, sources) ->
290 | test.ok !err
291 | lines = sources.split("\n").slice(0, 5)
292 |
293 | test.same "// Zepto", lines[0]
294 | test.same "// Underscore", lines[2]
295 | test.same "// Backbone", lines[4]
296 |
297 | testRequire = load sources
298 | test.ok testRequire("foo/bar/baz")
299 | test.done()
300 |
301 | "paths may be symlinks": (test) ->
302 | test.expect 2
303 | linkPackage.compile (err, sources) ->
304 | test.ok !err
305 | testRequire = load sources
306 | test.ok testRequire("foo/bar/baz")
307 | test.done()
308 |
309 | if stitch.compilers.eco
310 | module.exports["eco compiler"] = (test) ->
311 | test.expect 2
312 | ecoPackage.compile (err, sources) ->
313 | test.ok !err
314 | testRequire = load sources
315 |
316 | html = testRequire("hello.html")(name: "Sam").trim()
317 | test.same "hello Sam!", html.split("\n").pop()
318 | test.done()
319 |
320 |
--------------------------------------------------------------------------------
/lib/stitch.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.3.3
2 | (function() {
3 | var CoffeeScript, Package, async, compilers, eco, extname, fs, join, normalize, _, _ref,
4 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
5 |
6 | _ = require('underscore');
7 |
8 | async = require('async');
9 |
10 | fs = require('fs');
11 |
12 | _ref = require('path'), extname = _ref.extname, join = _ref.join, normalize = _ref.normalize;
13 |
14 | exports.compilers = compilers = {
15 | js: function(module, filename) {
16 | var content;
17 | content = fs.readFileSync(filename, 'utf8');
18 | return module._compile(content, filename);
19 | }
20 | };
21 |
22 | try {
23 | CoffeeScript = require('coffee-script');
24 | compilers.coffee = function(module, filename) {
25 | var content;
26 | content = CoffeeScript.compile(fs.readFileSync(filename, 'utf8'));
27 | return module._compile(content, filename);
28 | };
29 | } catch (err) {
30 |
31 | }
32 |
33 | try {
34 | eco = require('eco');
35 | if (eco.precompile) {
36 | compilers.eco = function(module, filename) {
37 | var content;
38 | content = eco.precompile(fs.readFileSync(filename, 'utf8'));
39 | return module._compile("module.exports = " + content, filename);
40 | };
41 | } else {
42 | compilers.eco = function(module, filename) {
43 | var content;
44 | content = eco.compile(fs.readFileSync(filename, 'utf8'));
45 | return module._compile(content, filename);
46 | };
47 | }
48 | } catch (err) {
49 |
50 | }
51 |
52 | exports.Package = Package = (function() {
53 |
54 | function Package(config) {
55 | this.compileSources = __bind(this.compileSources, this);
56 |
57 | this.compileDependencies = __bind(this.compileDependencies, this);
58 |
59 | var _ref1, _ref2, _ref3, _ref4;
60 | this.identifier = (_ref1 = config.identifier) != null ? _ref1 : 'require';
61 | this.paths = (_ref2 = config.paths) != null ? _ref2 : ['lib'];
62 | this.dependencies = (_ref3 = config.dependencies) != null ? _ref3 : [];
63 | this.compilers = _.extend({}, compilers, config.compilers);
64 | this.cache = (_ref4 = config.cache) != null ? _ref4 : true;
65 | this.mtimeCache = {};
66 | this.compileCache = {};
67 | }
68 |
69 | Package.prototype.compile = function(callback) {
70 | return async.parallel([this.compileDependencies, this.compileSources], function(err, parts) {
71 | if (err) {
72 | return callback(err);
73 | } else {
74 | return callback(null, parts.join("\n"));
75 | }
76 | });
77 | };
78 |
79 | Package.prototype.compileDependencies = function(callback) {
80 | var _this = this;
81 | return async.map(this.dependencies, fs.readFile, function(err, dependencySources) {
82 | if (err) {
83 | return callback(err);
84 | } else {
85 | return callback(null, dependencySources.join("\n"));
86 | }
87 | });
88 | };
89 |
90 | Package.prototype.compileSources = function(callback) {
91 | var _this = this;
92 | return async.reduce(this.paths, {}, _.bind(this.gatherSourcesFromPath, this), function(err, sources) {
93 | var filename, index, name, result, source, _ref1;
94 | if (err) {
95 | return callback(err);
96 | }
97 | result = "(function(/*! Stitch !*/) {\n if (!this." + _this.identifier + ") {\n var modules = {}, cache = {}, require = function(name, root) {\n var path = expand(root, name), module = cache[path], fn;\n if (module) {\n return module.exports;\n } else if (fn = modules[path] || modules[path = expand(path, './index')]) {\n module = {id: path, exports: {}};\n try {\n cache[path] = module;\n fn(module.exports, function(name) {\n return require(name, dirname(path));\n }, module);\n return module.exports;\n } catch (err) {\n delete cache[path];\n throw err;\n }\n } else {\n throw 'module \\'' + name + '\\' not found';\n }\n }, expand = function(root, name) {\n var results = [], parts, part;\n if (/^\\.\\.?(\\/|$)/.test(name)) {\n parts = [root, name].join('/').split('/');\n } else {\n parts = name.split('/');\n }\n for (var i = 0, length = parts.length; i < length; i++) {\n part = parts[i];\n if (part == '..') {\n results.pop();\n } else if (part != '.' && part != '') {\n results.push(part);\n }\n }\n return results.join('/');\n }, dirname = function(path) {\n return path.split('/').slice(0, -1).join('/');\n };\n this." + _this.identifier + " = function(name) {\n return require(name, '');\n }\n this." + _this.identifier + ".define = function(bundle) {\n for (var key in bundle)\n modules[key] = bundle[key];\n };\n }\n return this." + _this.identifier + ".define;\n}).call(this)({";
98 | index = 0;
99 | for (name in sources) {
100 | _ref1 = sources[name], filename = _ref1.filename, source = _ref1.source;
101 | result += index++ === 0 ? "" : ", ";
102 | result += JSON.stringify(name);
103 | result += ": function(exports, require, module) {" + source + "}";
104 | }
105 | result += "});\n";
106 | return callback(err, result);
107 | });
108 | };
109 |
110 | Package.prototype.createServer = function() {
111 | var _this = this;
112 | return function(req, res, next) {
113 | return _this.compile(function(err, source) {
114 | var message;
115 | if (err) {
116 | console.error("" + err.stack);
117 | message = "" + err.stack;
118 | res.writeHead(500, {
119 | 'Content-Type': 'text/javascript'
120 | });
121 | return res.end("throw " + (JSON.stringify(message)));
122 | } else {
123 | res.writeHead(200, {
124 | 'Content-Type': 'text/javascript'
125 | });
126 | return res.end(source);
127 | }
128 | });
129 | };
130 | };
131 |
132 | Package.prototype.gatherSourcesFromPath = function(sources, sourcePath, callback) {
133 | var _this = this;
134 | return fs.stat(sourcePath, function(err, stat) {
135 | if (err) {
136 | return callback(err);
137 | }
138 | if (stat.isDirectory()) {
139 | return _this.getFilesInTree(sourcePath, function(err, paths) {
140 | if (err) {
141 | return callback(err);
142 | }
143 | return async.reduce(paths, sources, _.bind(_this.gatherCompilableSource, _this), callback);
144 | });
145 | } else {
146 | return _this.gatherCompilableSource(sources, sourcePath, callback);
147 | }
148 | });
149 | };
150 |
151 | Package.prototype.gatherCompilableSource = function(sources, path, callback) {
152 | var _this = this;
153 | if (this.compilers[extname(path).slice(1)]) {
154 | return this.getRelativePath(path, function(err, relativePath) {
155 | if (err) {
156 | return callback(err);
157 | }
158 | return _this.compileFile(path, function(err, source) {
159 | var extension, key;
160 | if (err) {
161 | return callback(err);
162 | } else {
163 | extension = extname(relativePath);
164 | key = relativePath.slice(0, -extension.length);
165 | sources[key] = {
166 | filename: relativePath,
167 | source: source
168 | };
169 | return callback(err, sources);
170 | }
171 | });
172 | });
173 | } else {
174 | return callback(null, sources);
175 | }
176 | };
177 |
178 | Package.prototype.getRelativePath = function(path, callback) {
179 | var _this = this;
180 | return fs.realpath(path, function(err, sourcePath) {
181 | if (err) {
182 | return callback(err);
183 | }
184 | return async.map(_this.paths, fs.realpath, function(err, expandedPaths) {
185 | var base, expandedPath, _i, _len;
186 | if (err) {
187 | return callback(err);
188 | }
189 | for (_i = 0, _len = expandedPaths.length; _i < _len; _i++) {
190 | expandedPath = expandedPaths[_i];
191 | base = expandedPath + "/";
192 | if (sourcePath.indexOf(base) === 0) {
193 | return callback(null, sourcePath.slice(base.length));
194 | }
195 | }
196 | return callback(new Error("" + path + " isn't in the require path"));
197 | });
198 | });
199 | };
200 |
201 | Package.prototype.compileFile = function(path, callback) {
202 | var compile, extension, mod, mtime, source;
203 | extension = extname(path).slice(1);
204 | if (this.cache && this.compileCache[path] && this.mtimeCache[path] === this.compileCache[path].mtime) {
205 | return callback(null, this.compileCache[path].source);
206 | } else if (compile = this.compilers[extension]) {
207 | source = null;
208 | mod = {
209 | _compile: function(content, filename) {
210 | return source = content;
211 | }
212 | };
213 | try {
214 | compile(mod, path);
215 | if (this.cache && (mtime = this.mtimeCache[path])) {
216 | this.compileCache[path] = {
217 | mtime: mtime,
218 | source: source
219 | };
220 | }
221 | return callback(null, source);
222 | } catch (err) {
223 | if (err instanceof Error) {
224 | err.message = "can't compile " + path + "\n" + err.message;
225 | } else {
226 | err = new Error("can't compile " + path + "\n" + err);
227 | }
228 | return callback(err);
229 | }
230 | } else {
231 | return callback(new Error("no compiler for '." + extension + "' files"));
232 | }
233 | };
234 |
235 | Package.prototype.walkTree = function(directory, callback) {
236 | var _this = this;
237 | return fs.readdir(directory, function(err, files) {
238 | if (err) {
239 | return callback(err);
240 | }
241 | return async.forEach(files, function(file, next) {
242 | var filename;
243 | if (file.match(/^\./)) {
244 | return next();
245 | }
246 | filename = join(directory, file);
247 | return fs.stat(filename, function(err, stats) {
248 | var _ref1;
249 | _this.mtimeCache[filename] = stats != null ? (_ref1 = stats.mtime) != null ? _ref1.toString() : void 0 : void 0;
250 | if (!err && stats.isDirectory()) {
251 | return _this.walkTree(filename, function(err, filename) {
252 | if (filename) {
253 | return callback(err, filename);
254 | } else {
255 | return next();
256 | }
257 | });
258 | } else {
259 | callback(err, filename);
260 | return next();
261 | }
262 | });
263 | }, callback);
264 | });
265 | };
266 |
267 | Package.prototype.getFilesInTree = function(directory, callback) {
268 | var files;
269 | files = [];
270 | return this.walkTree(directory, function(err, filename) {
271 | if (err) {
272 | return callback(err);
273 | } else if (filename) {
274 | return files.push(filename);
275 | } else {
276 | return callback(err, files.sort());
277 | }
278 | });
279 | };
280 |
281 | return Package;
282 |
283 | })();
284 |
285 | exports.createPackage = function(config) {
286 | return new Package(config);
287 | };
288 |
289 | }).call(this);
290 |
--------------------------------------------------------------------------------