├── .gitignore
├── .npmignore
├── README.md
├── app.js
├── bin
├── cli.js
└── quickstart
├── browser.js
├── browser
└── process.js
├── lib
└── quickstart.js
├── main.js
├── package.json
├── runtime
├── browser.js
└── node.js
├── test
├── main.js
├── parsers
│ ├── empty.js
│ └── error.js
├── resolver.js
├── root
│ ├── circular-a.js
│ ├── circular-b.js
│ ├── false.js
│ ├── index.js
│ ├── invalid-javascript.js
│ ├── invalid-require.js
│ ├── lib
│ │ └── fs.js
│ ├── main.js
│ ├── module.empty
│ ├── module.json
│ ├── module.txt
│ ├── new.js
│ ├── node_modules
│ │ ├── five
│ │ │ ├── browser.js
│ │ │ ├── main.js
│ │ │ ├── new.js
│ │ │ ├── old.js
│ │ │ └── package.json
│ │ ├── four
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── one
│ │ │ ├── index.js
│ │ │ ├── new.js
│ │ │ ├── old.js
│ │ │ ├── one.js
│ │ │ └── package.json
│ │ ├── seven
│ │ │ ├── lib
│ │ │ │ └── index.js
│ │ │ └── package.json
│ │ ├── six
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── three
│ │ │ ├── lib
│ │ │ │ └── three.js
│ │ │ └── package.json
│ │ └── two
│ │ │ ├── lib.js
│ │ │ ├── lib
│ │ │ ├── index.js
│ │ │ └── lib.js
│ │ │ ├── lib2
│ │ │ └── index.js
│ │ │ ├── package.json
│ │ │ └── two.js
│ ├── old.js
│ ├── package.json
│ ├── test-browser.js
│ ├── test-circular.js
│ ├── test-json.js
│ └── test-node.js
├── sequence.js
└── transforms
│ ├── error.js
│ └── passthrough.js
├── transforms
├── inject-globals.js
└── require-dependencies.js
└── util
├── konsole.js
├── messages.js
├── program.js
├── resolver.js
├── sequence.js
└── transport.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /coverage
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spotify/quickstart/f9f12f3e3dc2a041e4d8ad7b5b46a2d9494d919c/.npmignore
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Deprecated]
2 |
3 | QuickStart is no longer maintained. It will still exist in npm, but no more updates will happen. We no longer use QuickStart at Spotify, and instead recommend other bundlers such as [Browserify](http://browserify.org/) or [Webpack](https://webpack.github.io/).
4 |
5 | # [QuickStart](http://spotify.github.io/quickstart)
6 |
7 | A CommonJS module resolver, loader and compiler for node.js and browsers.
8 |
9 | ## Features
10 |
11 | * Runs in node.js **and browsers**.
12 | * Supports (most) node builtins and globals.
13 | * SpiderMonkey AST based Plugin system.
14 | * Stylish logs.
15 |
16 | ## General Usage
17 |
18 | ### Install QuickStart globally (for the cli)
19 |
20 | ```
21 | npm install quickstart -g
22 | ```
23 |
24 | ### Add index.html, package.json for your application
25 |
26 | ```
27 | cd my-awesome-app
28 | ```
29 |
30 | index.html
31 | ```html
32 |
33 |
34 |
35 | Awesomeness
36 |
37 |
38 |
39 |
40 | ```
41 |
42 | package.json
43 | ```json
44 | {
45 | "name": "my-awesome-app"
46 | }
47 | ```
48 |
49 | ### Install needed npm packages, QuickStart and plugins locally
50 |
51 | ```
52 | npm install underscore --save
53 | npm install quickstart some-quickstart-transform --save-dev
54 | ```
55 |
56 | ### Build a development QuickStart file.
57 |
58 | ```
59 | quickstart --transforms some-quickstart-transform --self > quickstart.js
60 | ```
61 |
62 | QuickStart will build a standalone QuickStart compiler (for browsers) that includes plugins.
63 | If you want to install or remove QuickStart plugins, or change options, repeat this step.
64 |
65 | After that, simply link `quickstart.js` in the `` of a document. It will compile and load your application at runtime in the browser.
66 |
67 | ### Deploy
68 |
69 | ```
70 | quickstart --transforms some-quickstart-transform > awesome.js
71 | ```
72 |
73 | This will create a compiled application for deployment.
74 |
75 | Now simply replace `quickstart.js` with `awesome.js`
76 |
77 | ```html
78 |
79 | ```
80 |
81 | ## Entry Point
82 |
83 | QuickStart always starts compiling your application from the entry point.
84 | This value might be read from these locations in this order:
85 |
86 | 1. Manually specified `main` option (command line or node.js).
87 | 2. Resolved automatically with the built-in node-style module resolver.
88 |
89 | ## Plugin system
90 |
91 | QuickStart has two types of plugins: transforms and parsers.
92 |
93 | * Parser plugins transform a specific type of source code to a SpiderMonkey AST object.
94 | * Transform plugins transform a SpiderMonkey AST object to a SpiderMonkey AST object.
95 |
96 | ## node.js interface
97 |
98 | ```js
99 | var quickstart = require('quickstart');
100 |
101 | // the quickstart function returns a promise.
102 | quickstart({/* options */}).then(function(compiled) {
103 | var ast = compiled.ast;
104 | var source = compiled.source;
105 | var sourceMap = compiled.sourceMap;
106 | // print the generated JavaScript, or print / work with the abstract syntax tree, work with sourceMaps, etc.
107 | });
108 | ```
109 |
110 | ### options
111 |
112 | Note: options might be augmented with the parameter `--config jsonFile.json`. It defaults to quickstart.json, and will be ignored if not found.
113 |
114 | Command line options look the same, except hyphenated.
115 |
116 | ```js
117 | {
118 | runtime: 'quickstart/runtime/browser', // override the default runtime, defaults to quickstart/runtime/browser
119 | transforms: [], // which transforms to use, defaults to none
120 | parsers: {}, // which parsers to use for each file extension, defaults to none, except embedded ones such as .js and .json.
121 | compress: false, // optimize and mangle the ast and JavaScript output
122 | output: true, // generates the (compressed if {compress: true}) JavaScript output, defaults to true
123 | sourceMap: false, // generates the (compressed if {compress: true}) source map, defaults to false
124 | self: false, // compiles the QuickStart compiler instead of the current app, defaults to false
125 | main: false, // override the application's main, defaults to the QuickStart resolver
126 | warnings: true // display warning messages, defaults to true
127 | }
128 | ```
129 |
130 | ## command line interface
131 |
132 | ```
133 | quickstart --help
134 | ```
135 |
136 | ### options
137 |
138 | ```
139 | --runtime runtimeModule # override the default runtime
140 | --transforms transformModule # which transforms to use
141 | --parsers ext=parserModule # which parsers to use
142 | --compress # optimize and mangle the ast and JavaScript output
143 | --output # generates the (compressed if `--compress` is set) JavaScript output, defaults to true
144 | --source-map # generates the (compressed if `--compress` is set) source map, defaults to false
145 | --self # compiles the QuickStart compiler instead of the current app, defaults to false
146 | --main ./path/to/entry-point # override the application's entry point, defaults to the QuickStart resolver
147 | --warnings # display warnings messages, defaults to true
148 | --ast ./path/to/source.ast # writes the ast to a file or *STDOUT*, defaults to false
149 | ```
150 |
151 | When `--output` is set to a string, it will send the JavaScript output to that file instead of STDOUT.
152 | ```
153 | quickstart --output output.js
154 | ```
155 |
156 | When `--source-map` is set to a string, it will send the source map output to that file instead of STDOUT.
157 | ```
158 | quickstart --source-map output.map > output.js
159 | ```
160 |
161 | When `--source-map` is set without a value, and `--output` is set, it will append an inline base64 encoded source map to the output.
162 | ```
163 | quickstart --source-map > output.js
164 | quickstart --source-map --output output.js
165 | ```
166 |
167 | When `--source-map` is set and `--output` is unset (`--no-output`) it will write the source map to STDOUT (no value) or the file (value).
168 | ```
169 | quickstart --no-output --source-map > output.map
170 | quickstart --no-output --source-map output.map
171 | ```
172 |
173 | When `--ast` is set without a value the ast is printed to STDOUT.
174 | ```
175 | quickstart --ast > output.ast
176 | ```
177 |
178 | This is useful, for instance, to pipe the AST to UglifyJS or any other program that accepts a SpiderMonkey AST:
179 | ```
180 | quickstart --ast --source-map | uglifyjs --spidermonkey > out.js
181 | ```
182 |
183 | Note: the `--source-map` option must be set if you need location information in the AST (to have UglifyJS generate a source map, for instance).
184 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var quickstart = require('quickstart');
4 | var config = require('./@config.json');
5 | quickstart(config);
6 |
--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 |
5 | var requireRelative = require('require-relative');
6 |
7 | var clc = require('cli-color');
8 |
9 | var pathogen = require('pathogen');
10 |
11 | var isString = require('mout/lang/isString');
12 | var forIn = require('mout/object/forIn');
13 | var mixIn = require('mout/object/mixIn');
14 | var isArray = require('mout/lang/isArray');
15 | var camelCase = require('mout/string/camelCase');
16 |
17 | var Konsole = require('../util/konsole');
18 | var Messages = require('../util/messages');
19 |
20 | var compile = require('../');
21 | var manifest = require('../package.json');
22 |
23 | // # argv
24 | module.exports = function(argv) {
25 |
26 | var root = argv.root;
27 |
28 | if (argv.config == null) argv.config = pathogen.resolve(root, './quickstart.json');
29 |
30 | if (argv.o != null) {
31 | argv.output = argv.o;
32 | delete argv.o;
33 | }
34 |
35 | if (argv.parsers) {
36 | var parsers = {};
37 | if (!isArray(argv.parsers)) argv.parsers = [argv.parsers];
38 | argv.parsers.forEach(function(parser) {
39 | var parts = parser.split('=');
40 | parsers[parts[0].trim()] = parts[1].trim();
41 | });
42 | argv.parsers = parsers;
43 | }
44 |
45 | if (argv.transforms) {
46 | if (!isArray(argv.transforms)) argv.transforms = [argv.transforms];
47 | }
48 |
49 | var jsonConf;
50 |
51 | if (/\.json$/.test(argv.config)) try {
52 | jsonConf = requireRelative(pathogen(argv.config), root);
53 | } catch(e) {}
54 |
55 | var options = {};
56 |
57 | // augment options with config file, if specified and valid
58 | if (jsonConf) mixIn(options, jsonConf);
59 |
60 | // augment options with argv
61 | forIn(argv, function(value, name) {
62 | if (name.length > 1 && name !== 'config' && name !== 'version' && name !== 'help') {
63 | options[camelCase(name)] = value;
64 | }
65 | });
66 |
67 | if (!options.parsers) options.parsers = {};
68 | if (!options.transforms) options.transforms = [];
69 |
70 | // set warnings to true by default
71 | if (options.warnings == null) options.warnings = true;
72 |
73 | // clean up options.output
74 | if (options.output == null) options.output = true;
75 |
76 | // # help konsole
77 |
78 | var help = new Konsole('log');
79 |
80 | var logo = 'QuickStart ' + clc.white('v' + manifest.version);
81 |
82 | help.group(logo);
83 | help.write('');
84 |
85 | help.write(clc.green('--self '),
86 | 'compile quickstart for browser compilation. defaults to', clc.red('false'));
87 |
88 | help.write(clc.green('--root '),
89 | 'use this as the root of each path. defaults to', clc.blue(pathogen.cwd()));
90 |
91 | help.write(clc.green('--main '),
92 | 'override the default entry point. defaults to', clc.red('false'));
93 |
94 | help.write(clc.green('--output, -o '),
95 | 'output the compiled source to a file or STDOUT. defaults to', clc.blue('STDOUT'));
96 |
97 | help.write(clc.green('--source-map '),
98 | 'output the source map to a file, STDOUT or inline. defaults to', clc.red('false'));
99 |
100 | help.write(clc.green('--ast '),
101 | 'output the Esprima generated AST to a file or STDOUT. defaults to', clc.red('false'));
102 |
103 | help.write(clc.green('--optimize '),
104 | 'feed transforms with an optimized ast. defaults to', clc.red('false'));
105 |
106 | help.write(clc.green('--compress '),
107 | 'compress the resulting AST using esmangle. defaults to', clc.red('false'));
108 |
109 | help.write(clc.green('--runtime '),
110 | 'specify a custom runtime. defaults to', clc.red('false'));
111 |
112 | help.write(clc.green('--config '),
113 | 'specify a configuration json file to augment command line options. defaults to', clc.red('false'));
114 |
115 | help.write(clc.green('--warnings '),
116 | 'display warning messages. defaults to', clc.blue('true'));
117 |
118 | help.write(clc.green('--help, -h '),
119 | 'display this help screen');
120 |
121 | help.write(clc.green('--version, -v'),
122 | 'display the current version');
123 |
124 | help.write('');
125 |
126 | help.groupEnd(); // QuickStart
127 |
128 | // # help command
129 |
130 | var printOptions = {
131 | last: clc.whiteBright('└─'),
132 | item: clc.whiteBright('├─'),
133 | join: clc.whiteBright(' '),
134 | line: clc.whiteBright('│'),
135 | spcr: clc.whiteBright(' ')
136 | };
137 |
138 | if (argv.help || argv.h) {
139 | help.print(' ');
140 | process.exit(0);
141 | }
142 |
143 | // # version command
144 |
145 | if (argv.version || argv.v) {
146 | console.log('v' + manifest.version);
147 | process.exit(0);
148 | }
149 |
150 | // # beep beep
151 |
152 | var beep = function() {
153 | process.stderr.write('\x07'); // beep!
154 | };
155 |
156 | // # format messages
157 |
158 | function format(type, statement) {
159 |
160 | if (type === 'group' || type === 'groupCollapsed') {
161 | if ((/warning/i).test(statement)) {
162 | if (options.warnings) konsole.group(clc.yellow(statement));
163 | } else if ((/error/i).test(statement)) {
164 | konsole.group(clc.red(statement));
165 | } else {
166 | konsole.group(statement);
167 | }
168 | return;
169 | }
170 |
171 | if (type === 'groupEnd') {
172 | konsole.groupEnd();
173 | return;
174 | }
175 |
176 | var message, source, line, column, id;
177 |
178 | message = statement.message;
179 |
180 | id = statement.id;
181 |
182 | source = statement.source;
183 | line = statement.line;
184 | column = statement.column;
185 |
186 | if (source != null) {
187 | source = clc.green(pathogen.resolve(root, source));
188 | if (line != null) {
189 | source += ' on line ' + line;
190 | if (column != null) {
191 | source += ', column' + column;
192 | }
193 | }
194 | message = message ? [message, source].join(' ') : source;
195 | }
196 |
197 | switch (type) {
198 | case 'error':
199 | konsole.write(clc.red(id + ': ') + message);
200 | break;
201 | case 'warn':
202 | if (options.warnings) konsole.write(clc.yellow(id + ': ') + message);
203 | break;
204 | case 'time':
205 | konsole.write(clc.blue(id + ': ') + message);
206 | break;
207 | default:
208 | konsole.write(id + ': ' + message);
209 | break;
210 | }
211 |
212 | }
213 |
214 | var messages = new Messages(logo);
215 | var konsole = new Konsole('error');
216 |
217 | var compilation = messages.group('Compilation');
218 | compilation.time('time');
219 |
220 | var optionsGroup = messages.group('Options');
221 | forIn(options, function(value, key) {
222 | var string = JSON.stringify(value);
223 | if (string) optionsGroup.log({ id: key, message: string });
224 | });
225 |
226 | compile(options, messages).then(function(compiled) {
227 |
228 | var ast = compiled.ast;
229 | var source = compiled.source;
230 | var sourceMap = compiled.sourceMap;
231 |
232 | if (source) source = '/* compiled with ' + manifest.name + '@' + manifest.version + ' */' + source;
233 |
234 | if (options.output && options.sourceMap === true) {
235 | sourceMap = JSON.stringify(sourceMap);
236 | source += '\n//# sourceMappingURL=data:application/json;base64,' + new Buffer(sourceMap).toString('base64');
237 | compilation.log({ id: 'source map', message: 'embedded' });
238 | }
239 |
240 | if (isString(options.sourceMap)) {
241 | var sourceMapPath = pathogen.resolve(root, options.sourceMap);
242 | fs.writeFileSync(pathogen.sys(sourceMapPath), JSON.stringify(sourceMap));
243 | if (options.output) source += '\n//# sourceMappingURL=' + pathogen.relative(root, sourceMapPath);
244 |
245 | compilation.log({ id: 'sourceMap', message: 'file written', source: pathogen.relative(root, sourceMapPath) });
246 | }
247 |
248 | if (isString(options.output)) {
249 | var sourcePath = pathogen.sys(pathogen.resolve(root, options.output));
250 | fs.writeFileSync(sourcePath, source);
251 | compilation.log({ id: 'source', message: 'file written', source: pathogen.relative(root, sourcePath) });
252 | }
253 |
254 | if (isString(options.ast)) {
255 | var astPath = pathogen.sys(pathogen.resolve(root, options.ast));
256 | fs.writeFileSync(astPath, JSON.stringify(ast));
257 |
258 | compilation.log({ id: 'ast', message: 'file written', source: pathogen.relative(root, astPath) });
259 | }
260 |
261 | if (options.ast === true) {
262 | console.log(JSON.stringify(ast));
263 |
264 | compilation.log({ id: 'ast', message: 'stdout' });
265 | } else if (options.output === true) {
266 | console.log(source);
267 |
268 | compilation.log({ id: 'source', message: 'stdout' });
269 | } else if (options.sourceMap === true) {
270 | console.log(sourceMap);
271 |
272 | compilation.log({ id: 'sourceMap', message: 'stdout' });
273 | }
274 |
275 | compilation.timeEnd('time', options.self ? 'compiled itself in' : 'compiled in', true, true);
276 |
277 | messages.print(format);
278 | konsole.print(printOptions);
279 | messages.reset();
280 |
281 | beep();
282 |
283 | }).catch(function(error) {
284 |
285 | messages.print(format);
286 | konsole.print(printOptions);
287 |
288 | beep(); beep(); // beepbeep!
289 | process.nextTick(function() {
290 | throw error;
291 | });
292 | return;
293 | });
294 |
295 | };
296 |
--------------------------------------------------------------------------------
/bin/quickstart:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var pathogen = require('pathogen');
4 | var minimist = require('minimist');
5 | var requireRelative = require('require-relative');
6 |
7 | var argv = minimist(process.argv.slice(2));
8 | // clean up options.root, save root
9 | var root = argv.root = argv.root ? pathogen.resolve(argv.root + '/') : pathogen.cwd();
10 |
11 | var cli;
12 |
13 | try {
14 | cli = requireRelative('quickstart/bin/cli', root);
15 | } catch(e) {
16 | cli = require('./cli');
17 | }
18 |
19 | cli(argv);
20 |
--------------------------------------------------------------------------------
/browser.js:
--------------------------------------------------------------------------------
1 | /* jshint evil: true */
2 | 'use strict';
3 |
4 |
5 | // This is the main for browsers, and runs in the browser only.
6 |
7 | var pathogen = require('pathogen');
8 | var escodegen = require('escodegen');
9 | var esprima = require('esprima');
10 |
11 | var forEach = require('mout/array/forEach');
12 | var forIn = require('mout/object/forIn');
13 | var map = require('mout/object/map');
14 |
15 | var QuickStart = require('./lib/quickstart');
16 | var program = require('./util/program');
17 | var Messages = require('./util/messages');
18 |
19 | var version = require('./package.json').version;
20 |
21 | var noop = function(){};
22 |
23 | if (!global.console) global.console = {};
24 | if (!console.log) console.log = noop;
25 | if (!console.warn) console.warn = console.log;
26 | if (!console.error) console.error = console.log;
27 | if (!console.group) console.group = console.log;
28 | if (!console.groupCollapsed) console.groupCollapsed = console.group;
29 | if (!console.groupEnd) console.groupEnd = noop;
30 |
31 | var root = pathogen.cwd();
32 |
33 | var theme = {
34 | red: 'color: #d44;',
35 | green: 'color: #6b4;',
36 | blue: 'color: #49d;',
37 | yellow: 'color: #f90;',
38 | grey: 'color: #666;',
39 | greyBright: 'color: #999;',
40 | bold: 'font-weight: bold;'
41 | };
42 |
43 | var format = function(type, statement) {
44 | if (type === 'group' || type === 'groupCollapsed') {
45 |
46 | var color;
47 |
48 | if ((/warning/i).test(statement)) {
49 | color = theme.yellow + theme.bold;
50 | } else if ((/error/i).test(statement)) {
51 | color = theme.red + theme.bold;
52 | } else {
53 | color = theme.grey + theme.bold;
54 | }
55 |
56 | console[type]('%c' + statement, color);
57 |
58 | return;
59 | }
60 |
61 | if (type === 'groupEnd') {
62 | console.groupEnd();
63 | return;
64 | }
65 |
66 | var message, source, line, column, id;
67 |
68 | message = statement.message;
69 |
70 | var colors = [theme.grey];
71 |
72 | id = statement.id;
73 |
74 | source = statement.source;
75 | line = statement.line;
76 | column = statement.column;
77 |
78 | if (source != null) {
79 | source = ' %c' + location.origin + pathogen.resolve(root, source);
80 | if (line != null) {
81 | source += ':' + line;
82 | if (column != null) {
83 | source += ':' + column;
84 | }
85 | }
86 | message += source;
87 | colors.push(theme.green);
88 | }
89 |
90 | message = '%c' + id + ': %c' + message;
91 |
92 | switch (type) {
93 | case 'error':
94 | colors.unshift(theme.red);
95 | console.error.apply(console, [message].concat(colors));
96 | break;
97 |
98 | case 'warn':
99 | colors.unshift(theme.yellow);
100 | console.warn.apply(console, [message].concat(colors));
101 | break;
102 |
103 | case 'time':
104 | colors.unshift(theme.blue);
105 | console.log.apply(console, [message].concat(colors));
106 | break;
107 |
108 | default:
109 | colors.unshift(theme.grey);
110 | console.log.apply(console, [message].concat(colors));
111 | break;
112 | }
113 | };
114 |
115 | function generate(module) {
116 | var sourceURL = '\n//# sourceURL=';
117 |
118 | var output = escodegen.generate(module.ast, {
119 | format: {
120 | indent: { style: ' ' },
121 | quotes: 'single'
122 | }
123 | });
124 |
125 | output += sourceURL + module.path.substr(2);
126 | return new Function('require', 'module', 'exports', 'global', output);
127 | }
128 |
129 | module.exports = function(config) {
130 | var parsers = {};
131 | var transforms = [];
132 |
133 | // config(parsers|transforms) contains resolved paths we can just require().
134 |
135 | forIn(config.parsers, function(id, ext) {
136 | var parser = require(id);
137 | parsers[ext] = parser;
138 | });
139 |
140 | forEach(config.transforms, function(id) {
141 | var transform = require(id);
142 | transforms.push(transform);
143 | });
144 |
145 | var messages = new Messages;
146 | var compilation = messages.group('Compilation');
147 | compilation.time('time');
148 |
149 | var quickstart = new QuickStart({
150 | messages: messages,
151 | parsers: parsers,
152 | transforms: transforms,
153 | loc: !!config.sourceMap,
154 | defaultPath: config.defaultPath,
155 | root: root
156 | });
157 |
158 | console.group("%cQuick%cStart " + "%cv" + version, theme.red, theme.grey, theme.greyBright);
159 |
160 | console.groupCollapsed('%cXMLHttpRequests', theme.grey + theme.bold);
161 |
162 | var modules = quickstart.modules;
163 | var runtimeData = config.runtimeData;
164 | var runtimePath = config.runtimePath;
165 |
166 | return quickstart.require(root, config.main).then(function(id) {
167 | console.groupEnd(); // XMLHttpRequests
168 |
169 | var done;
170 |
171 | if (config.sourceMap) {
172 | compilation.log({ id: 'sourceMap', message: 'embedded' });
173 |
174 | var runtimeTree = esprima.parse(runtimeData, {loc: true, source: runtimePath});
175 |
176 | var tree = program(id, modules, runtimeTree);
177 |
178 | var sourceMappingURL = '\n//# sourceMappingURL=data:application/json;base64,';
179 |
180 | var output = escodegen.generate(tree, {
181 | format: {
182 | indent: { style: ' ' },
183 | quotes: 'single'
184 | },
185 | sourceMap: true,
186 | sourceMapRoot: location.origin + root,
187 | sourceMapWithCode: true
188 | });
189 |
190 | var source = output.code + sourceMappingURL + btoa(JSON.stringify(output.map));
191 |
192 | done = function() { return global.eval(source); };
193 |
194 | } else {
195 |
196 | var sourceURL = '\n//# sourceURL=';
197 |
198 | var evaluated = map(modules, generate);
199 |
200 | var runtimeFn = new Function('main', 'modules', runtimeData + sourceURL + runtimePath.substr(2));
201 |
202 | done = function() { return runtimeFn(id, evaluated); };
203 | }
204 |
205 | compilation.timeEnd('time', 'compiled in', true, true);
206 | messages.print(format).reset();
207 | console.groupEnd(); // QuickStart
208 |
209 | setTimeout(function() { done(); }, 1);
210 |
211 | }).catch(function(error) {
212 |
213 | console.groupEnd(); // XMLHttpRequests
214 | messages.print(format).reset();
215 | console.groupEnd(); // QuickStart
216 |
217 | setTimeout(function() { throw error; }, 1); // app error;
218 |
219 | });
220 |
221 | };
222 |
--------------------------------------------------------------------------------
/browser/process.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.title = document.title;
4 | exports.browser = true;
5 |
6 | exports.cwd = function() {
7 | return location.pathname.split(/\/+/g).slice(0, -1).join('/') || '/';
8 | };
9 |
--------------------------------------------------------------------------------
/lib/quickstart.js:
--------------------------------------------------------------------------------
1 | /* global -Promise */
2 | 'use strict';
3 |
4 | var Promise = require('promise');
5 |
6 | var prime = require('prime');
7 | var pathogen = require('pathogen');
8 |
9 | var mixIn = require('mout/object/mixIn');
10 | var append = require('mout/array/append');
11 | var find = require('mout/array/find');
12 |
13 | var esprima = require('esprima');
14 |
15 | var sequence = require('../util/sequence').use(Promise);
16 | var transport = require('../util/transport');
17 | var Resolver = require('../util/resolver');
18 | var Messages = require('../util/messages');
19 |
20 | var requireDependencies = require('../transforms/require-dependencies');
21 |
22 | var isNative = Resolver.isNative;
23 |
24 | // built in parsers
25 | var parsers = {
26 | // The default string parser.
27 | // When a type is unknown it will be processed as a string.
28 | txt: function(path, text) {
29 | var tree = esprima.parse('module.exports = ""');
30 | tree.body[0].expression.right = {
31 | type: 'Literal',
32 | value: text
33 | };
34 | return tree;
35 | },
36 |
37 | // The default JavaScript parser.
38 | // When a file extension is .js it will be processed as JavaScript using esprima.
39 | js: function(path, text) {
40 | return esprima.parse(text, {loc: this.loc, source: path});
41 | },
42 |
43 | // The default JSON parser.
44 | // When a file extension is .json it will be processed as JavaScript using esprima.
45 | // No location information is necessary, as this is simply JSON.
46 | json: function(path, json) {
47 | return esprima.parse('module.exports = ' + json);
48 | }
49 |
50 | };
51 |
52 | var QuickStart = prime({
53 |
54 | constructor: function QuickStart(options) {
55 | if (!options) options = {};
56 | this.options = options;
57 |
58 | // Tells esprima to store location information.
59 | // This is enabled when source maps are enabled.
60 | // Using this makes esprima slower, but it's necessary for source maps.
61 | this.loc = !!options.loc;
62 | // Override the working directory, defaults to cwd.
63 | this.root = options.root ? pathogen.resolve(options.root) : pathogen.cwd();
64 |
65 | this.index = 0;
66 |
67 | this.node = !!options.node;
68 |
69 | this.resolver = new Resolver({
70 | browser: !this.node,
71 | defaultPath: options.defaultPath
72 | });
73 |
74 | // Initialize the modules object.
75 | this.modules = {};
76 |
77 | // Store plugins.
78 |
79 | this.parsers = mixIn({}, parsers, options.parsers);
80 | this.transforms = append(append([], options.transforms), [requireDependencies]);
81 |
82 | this.packages = {};
83 |
84 | this.messages = options.messages || new Messages;
85 |
86 | this.cache = {
87 | parse: {}
88 | };
89 | },
90 |
91 | // > resolve
92 | resolve: function(from, required) {
93 | var self = this;
94 |
95 | var messages = self.messages;
96 |
97 | var dir1 = pathogen.dirname(from);
98 |
99 | var selfPkg = /^quickstart$|^quickstart\//;
100 | if (selfPkg.test(required)) {
101 | required = pathogen(required.replace(selfPkg, pathogen(__dirname + '/../')));
102 | }
103 |
104 | return self.resolver.resolve(dir1, required).then(function(resolved) {
105 |
106 | if (isNative(resolved)) {
107 | // resolved to native module, try to resolve from quickstart.
108 | var dir2 = pathogen(__dirname + '/');
109 | return (dir1 !== dir2) ? self.resolver.resolve(dir2, required) : resolved;
110 | } else {
111 | return resolved;
112 | }
113 | }).catch(function(error) {
114 | messages.group('Errors').error({
115 | id: 'ResolveError',
116 | message: 'unable to resolve ' + required,
117 | source: pathogen.relative(self.root, from)
118 | });
119 | throw error;
120 | });
121 | },
122 |
123 | // resolve > transport > parse
124 | require: function(from, required) {
125 | var self = this;
126 |
127 | return self.resolve(from, required).then(function(resolved) {
128 | if (isNative(resolved)) return resolved;
129 | if (resolved === false) return false;
130 |
131 | return self.analyze(from, required, resolved).then(function() {
132 | return self.include(resolved);
133 | });
134 | });
135 | },
136 |
137 | // transport > parse
138 | include: function(path) {
139 | var self = this;
140 |
141 | var messages = self.messages;
142 |
143 | var uid = self.uid(path);
144 |
145 | var module = self.modules[uid];
146 | if (module) return Promise.resolve(uid);
147 |
148 | return transport(path).then(function(data) {
149 | return self.parse(path, data);
150 | }, function(error) {
151 | messages.group('Errors').error({
152 | id: 'TransportError',
153 | message: 'unable to read',
154 | source: pathogen.relative(self.root, path)
155 | });
156 | throw error;
157 | }).then(function() {
158 | return uid;
159 | });
160 | },
161 |
162 | analyze: function(from, required, resolved) {
163 | var self = this;
164 |
165 | var packages = self.packages;
166 | var messages = self.messages;
167 | var root = self.root;
168 |
169 | return self.resolver.findRoot(resolved).then(function(path) {
170 | return transport.json(path + 'package.json').then(function(json) {
171 | return { json : json, path: path };
172 | });
173 | }).then(function(result) {
174 | var path = result.path;
175 | var name = result.json.name;
176 | var version = result.json.version;
177 |
178 | path = pathogen.relative(root, path);
179 |
180 | var pkg = packages[name] || (packages[name] = []);
181 |
182 | var same = find(pkg, function(obj) {
183 | return (obj.path === path);
184 | });
185 |
186 | if (same) return;
187 |
188 | var instance = { version: version, path: path };
189 | pkg.push(instance);
190 |
191 | if (pkg.length > 1) {
192 | var group = messages.group('Warnings');
193 |
194 | // warn about the first at length 2
195 | if (pkg.length === 2) group.warn({
196 | id: name,
197 | message: 'duplicate v' + pkg[0].version + ' found',
198 | source: pkg[0].path
199 | });
200 |
201 | // warn about every subsequent
202 | group.warn({
203 | id: name,
204 | message: 'duplicate v' + version + ' found',
205 | source: path
206 | });
207 | }
208 |
209 | }, function() { }); // into the void
210 | },
211 |
212 | uid: function(full) {
213 | return pathogen.relative(this.root, full);
214 | },
215 |
216 | // > parse
217 | parse: function(full, data) {
218 | var self = this;
219 |
220 | var cache = self.cache.parse;
221 | if (cache[full]) return cache[full];
222 |
223 | var modules = self.modules;
224 | var messages = self.messages;
225 |
226 | var uid = self.uid(full);
227 |
228 | var relative = pathogen.relative(self.root, full);
229 |
230 | var module = modules[uid] = { uid: uid };
231 |
232 | var extname = pathogen.extname(full).substr(1);
233 |
234 | var parse = (extname && self.parsers[extname]) || self.parsers.txt;
235 |
236 | // Process flow
237 |
238 | // use Promise.resolve so that the (possibly) public parser can return a promise or a syntax tree.
239 | return cache[full] = Promise.resolve().then(function() {
240 | // 1. process the code to AST, based on extension
241 | return parse.call(self, relative, data);
242 | }).catch(function(error) {
243 |
244 | messages.group('Errors').error({
245 | id: 'ParseError',
246 | message: error.message,
247 | source: relative
248 | });
249 |
250 | throw error;
251 | }).then(function(tree) {
252 | // 2. transform the AST, based on specified transforms
253 | return self.transform(relative, tree);
254 | }).then(function(tree) {
255 | // 4. callback with module object
256 | module.ast = tree;
257 | module.path = relative;
258 | return module;
259 | });
260 |
261 | },
262 |
263 | // Apply transformations.
264 | transform: function(path, tree) {
265 | var self = this;
266 | // these are applied as a promises reduce operation
267 | return sequence.reduce(self.transforms, function(tree, transform) {
268 | return transform.call(self, path, tree);
269 | }, tree);
270 | }
271 |
272 | });
273 |
274 | module.exports = QuickStart;
275 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /* global -Promise */
2 | 'use strict';
3 |
4 | // This is the main for node.js, and runs in node.js only.
5 |
6 | var Promise = require('promise');
7 |
8 | var pathogen = require('pathogen');
9 |
10 | var esprima = require('esprima');
11 | var escodegen = require('escodegen');
12 | var esmangle = require('esmangle');
13 |
14 | var QuickStart = require('./lib/quickstart');
15 |
16 | var Resolver = require('./util/resolver');
17 | var program = require('./util/program');
18 | var sequence = require('./util/sequence').use(Promise);
19 | var transport = require('./util/transport');
20 | var Messages = require('./util/messages');
21 |
22 | var injectGlobals = require('./transforms/inject-globals');
23 |
24 | var resolver = new Resolver;
25 |
26 | function compileSelf(options, appRuntimePath, appRuntimeData, messages) {
27 |
28 | var root = options.root;
29 | if (options.browserSourceMap == null) options.browserSourceMap = true;
30 |
31 | // Instantiate QuickStart with the root specified in options
32 | // which gives the QuickStart compiler a different root to work on
33 | // it defaults to the current working directory.
34 | var compiler = new QuickStart({
35 | messages: messages,
36 | loc: !!options.sourceMap,
37 | root: root,
38 | node: false,
39 | parsers: {},
40 | transforms: [injectGlobals]
41 | });
42 |
43 | // Configuration object that we will inject in the compiled compiler
44 | // as the compiler never reads from external sources for portability, except for builtins.
45 | // This includes the app's runtime as a string, parser and transforms (populated in a later step)
46 | // and some options.
47 | var config = {
48 | main: options.main ? pathogen(options.main) : './',
49 | runtimeData: appRuntimeData,
50 | runtimePath: pathogen.relative(root, appRuntimePath),
51 | sourceMap: !!options.browserSourceMap
52 | };
53 |
54 | if (options.defaultPath) config.defaultPath = pathogen.relative(root, options.defaultPath);
55 |
56 | var runtimePath = pathogen(__dirname + '/runtime/browser.js');
57 |
58 | return sequence.map(options.parsers, function(path) {
59 |
60 | return compiler.require(root, path);
61 |
62 | }).then(function(parsers) {
63 | // all the resolved parser paths
64 | config.parsers = parsers || {};
65 |
66 | return sequence.map(options.transforms, function(path) {
67 |
68 | return compiler.require(root, path);
69 |
70 | });
71 |
72 | }).then(function(transforms) {
73 |
74 | // all the resolved transforms paths
75 | config.transforms = transforms || [];
76 |
77 | // Add a fake module called @config.json, in the app root, with the config object we created
78 |
79 | var configPath = pathogen.resolve(root, './@config.json');
80 | var configData = JSON.stringify(config);
81 | transport.cache.get[configPath] = Promise.resolve(configData);
82 | return compiler.parse(configPath, configData);
83 |
84 | }).then(function() {
85 |
86 | return transport(pathogen(__dirname + '/app.js'));
87 |
88 | }).then(function(appData) {
89 |
90 | return compiler.parse(pathogen.resolve(root, './@app.js'), appData);
91 |
92 | }).then(function(module) {
93 | return transport(runtimePath).then(function(runtimeData) {
94 | return {
95 | main: module.uid,
96 | modules: compiler.modules,
97 | runtimePath: runtimePath,
98 | runtimeData: runtimeData
99 | };
100 | });
101 | });
102 |
103 | }
104 |
105 | function compileApp(options, appRuntimePath, appRuntimeData, messages) {
106 |
107 | var root = options.root;
108 |
109 | var transforms;
110 | var parsers;
111 |
112 | return sequence.map(options.parsers, function(parserPath) {
113 |
114 | return resolver.resolve(root, parserPath).then(require).catch(function(error) {
115 | messages.group('Errors').error({
116 | id: 'RequireError',
117 | message: 'unable to require parser ' + parserPath,
118 | source: './'
119 | });
120 | // load error
121 | throw error;
122 | });
123 |
124 | }).then(function(parserModules) {
125 | parsers = parserModules;
126 |
127 | return sequence.map(options.transforms, function(transformPath) {
128 | return resolver.resolve(root, transformPath).then(require).catch(function(error) {
129 | messages.group('Errors').error({
130 | id: 'RequireError',
131 | message: 'unable to require transform ' + transformPath,
132 | source: './'
133 | });
134 | // load error
135 | throw error;
136 | });
137 |
138 | });
139 |
140 | }).then(function(transformModules) {
141 |
142 | transforms = transformModules;
143 |
144 | // instantiate QuickStart with parsers, transforms and options
145 | var compiler = new QuickStart({
146 | messages: messages,
147 | transforms: transforms,
148 | parsers: parsers,
149 | root: root,
150 | loc: !!options.sourceMap,
151 | node: options.node,
152 | defaultPath: options.defaultPath
153 | });
154 |
155 | return compiler.require(root, options.main ? pathogen(options.main) : './').then(function(main) {
156 |
157 | return {
158 | main: main,
159 | modules: compiler.modules,
160 | runtimePath: appRuntimePath,
161 | runtimeData: appRuntimeData
162 | };
163 |
164 | });
165 |
166 | });
167 | }
168 |
169 | function compile(options, messages) {
170 |
171 | if (options == null) options = {};
172 |
173 | if (!messages) messages = new Messages;
174 |
175 | // options.output should default to true.
176 | if (options.output == null) options.output = true;
177 |
178 | var root = options.root = options.root ? pathogen.resolve(options.root) : pathogen.cwd();
179 | if (options.defaultPath) options.defaultPath = pathogen.resolve(root, options.defaultPath + '/');
180 |
181 | var appRuntimePath;
182 |
183 | // Get the appropriate runtime path based on options.
184 | if (options.runtime) appRuntimePath = options.runtime; // let it resolve
185 | else if (options.node) appRuntimePath = pathogen.resolve(__dirname, './runtime/node.js');
186 | else appRuntimePath = pathogen.resolve(__dirname, './runtime/browser.js');
187 |
188 | return resolver.resolve(root, appRuntimePath).then(function(resolved) {
189 | return appRuntimePath = resolved;
190 | }).then(transport).then(function(appRuntimeData) {
191 |
192 | // building with --self means compiling a compiler for a browser.
193 | if (options.self) return compileSelf(options, appRuntimePath, appRuntimeData, messages);
194 | // otherwise we compile the app.
195 | return compileApp(options, appRuntimePath, appRuntimeData, messages);
196 |
197 | }).then(function(compileResult) {
198 |
199 | var main = compileResult.main;
200 | var modules = compileResult.modules;
201 | var runtimePath = compileResult.runtimePath;
202 | var runtimeData = compileResult.runtimeData;
203 |
204 | var runtimeSource = pathogen.relative(root, runtimePath);
205 |
206 | return Promise.resolve().then(function() {
207 |
208 | var runtimeTree = esprima.parse(runtimeData, {
209 | loc: !!options.sourceMap,
210 | source: runtimeSource
211 | });
212 |
213 | // program the full ast
214 | return program(main, modules, runtimeTree);
215 |
216 | }).catch(function(error) {
217 | messages.group('Errors').error({
218 | id: 'ParseError',
219 | message: 'unable to parse',
220 | source: pathogen.relative(root, runtimePath)
221 | });
222 | throw error;
223 | });
224 |
225 | }).then(function(tree) {
226 | // compress
227 |
228 | if (!options.compress) return tree;
229 |
230 | return Promise.resolve().then(function() {
231 |
232 | tree = esmangle.optimize(tree);
233 | tree = esmangle.mangle(tree);
234 |
235 | return tree;
236 |
237 | }).catch(function(error) {
238 | messages.group('Errors').error({
239 | id: 'CompressionError',
240 | message: error.message
241 | });
242 | throw error;
243 | });
244 |
245 | }).then(function(tree) {
246 |
247 | return Promise.resolve().then(function() {
248 |
249 | var output = escodegen.generate(tree, {
250 | format: options.compress ? {
251 | compact: true,
252 | parentheses: false
253 | } : {
254 | indent: { style: ' ' },
255 | quotes: 'single'
256 | },
257 | sourceMap: !!options.sourceMap,
258 | sourceMapWithCode: true
259 | });
260 |
261 | // trigger the callback with everything:
262 | // the sourceTree (AST) JavaScript as a string, and the sourceMap.
263 |
264 | return {
265 | ast: tree,
266 | source: output.code,
267 | sourceMap: output.map && output.map.toJSON()
268 | };
269 |
270 | }).catch(function(error) {
271 | messages.group('Errors').error({
272 | id: 'GenerationError',
273 | message: error.message
274 | });
275 | throw error;
276 | });
277 |
278 | });
279 | }
280 |
281 | module.exports = compile;
282 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quickstart",
3 | "version": "1.2.2",
4 | "description": "CommonJS module compiler for node.js and browsers",
5 | "main": "./main.js",
6 | "browser": {
7 | "./main": "./browser",
8 | "assert": "assert",
9 | "buffer": "buffer",
10 | "console": "console-browserify",
11 | "constants": "constants-browserify",
12 | "crypto": "crypto-browserify",
13 | "domain": "domain-browser",
14 | "events": "events",
15 | "http": "http-browserify",
16 | "https": "https-browserify",
17 | "os": "os-browserify/browser",
18 | "path": "path-browserify",
19 | "punycode": "punycode",
20 | "querystring": "querystring-es3",
21 | "stream": "stream-browserify",
22 | "_stream_duplex": "readable-stream/duplex",
23 | "_stream_passthrough": "readable-stream/passthrough",
24 | "_stream_readable": "readable-stream/readable",
25 | "_stream_transform": "readable-stream/transform",
26 | "_stream_writable": "readable-stream/writable",
27 | "string_decoder": "string_decoder",
28 | "sys": "util",
29 | "timers": "timers-browserify",
30 | "tty": "tty-browserify",
31 | "url": "url",
32 | "util": "util",
33 | "vm": "vm-browserify",
34 | "zlib": "browserify-zlib"
35 | },
36 | "bin": {
37 | "quickstart": "./bin/quickstart"
38 | },
39 | "scripts": {
40 | "test": "istanbul cover _mocha test -- -R spec"
41 | },
42 | "dependencies": {
43 | "promise": "~5.0.0",
44 | "prime": "^0.4.2",
45 | "agent": "^0.2.0",
46 | "pathogen": "^0.1.5",
47 | "mout": "^0.9.1",
48 | "esprima": "~1.2.1",
49 | "escodegen": "^1.3.2",
50 | "estraverse": "~1.5.0",
51 | "escope": "~1.0.1",
52 | "esmangle": "~1.0.1",
53 | "microseconds": "^0.1.0",
54 | "cli-color": "~0.3.2",
55 | "require-relative": "^0.8.7",
56 | "minimist": "~0.1.0",
57 | "util": "~0.10.3",
58 | "assert": "~1.1.1",
59 | "url": "~0.10.1",
60 | "path-browserify": "0.0.0",
61 | "buffer": "~2.3.0",
62 | "https-browserify": "0.0.0",
63 | "tty-browserify": "0.0.0",
64 | "constants-browserify": "0.0.1",
65 | "os-browserify": "~0.1.2",
66 | "string_decoder": "~0.10.25-1",
67 | "domain-browser": "~1.1.1",
68 | "querystring-es3": "~0.2.1-0",
69 | "punycode": "~1.2.4",
70 | "events": "~1.0.1",
71 | "stream-browserify": "~1.0.0",
72 | "readable-stream": "~1.0.27-1",
73 | "vm-browserify": "0.0.4",
74 | "timers-browserify": "~1.0.1",
75 | "console-browserify": "~1.1.0",
76 | "http-browserify": "~1.3.2",
77 | "browserify-zlib": "~0.1.4",
78 | "crypto-browserify": "~2.1.8"
79 | },
80 | "devDependencies": {
81 | "mocha": "^1.18.2",
82 | "istanbul": "^0.2.9",
83 | "chai": "~1.9.1",
84 | "chai-as-promised": "~4.1.1"
85 | },
86 | "homepage": "http://spotify.github.io/quickstart",
87 | "repository": "https://github.com/spotify/quickstart",
88 | "author": {
89 | "name": "Valerio Proietti",
90 | "email": "kamicane@gmail.com"
91 | },
92 | "contributors": [
93 | {
94 | "name": "Johannes Koggdal",
95 | "email": "johannes@koggdal.com"
96 | },
97 | {
98 | "name": "Daniel Herzog",
99 | "email": "daniel.herzog@gmail.com"
100 | }
101 | ],
102 | "licenses": [
103 | {
104 | "type": "Apache-2.0",
105 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
106 | }
107 | ]
108 | }
109 |
--------------------------------------------------------------------------------
/runtime/browser.js:
--------------------------------------------------------------------------------
1 | /*
2 | Browser runtime
3 | */'use strict';
4 | /* global main, modules, -require, -module, -exports, -global */
5 |
6 | var cache = require.cache = {};
7 |
8 | function require(id) {
9 | var module = cache[id];
10 | if (!module) {
11 | var moduleFn = modules[id];
12 | if (!moduleFn) throw new Error('module ' + id + ' not found');
13 | module = cache[id] = {};
14 | var exports = module.exports = {};
15 | moduleFn.call(exports, require, module, exports, window);
16 | }
17 | return module.exports;
18 | }
19 |
20 | require.resolve = function(resolved) {
21 | return resolved;
22 | };
23 |
24 | require.node = function() {
25 | return {};
26 | };
27 |
28 | require(main);
29 |
--------------------------------------------------------------------------------
/runtime/node.js:
--------------------------------------------------------------------------------
1 | /*
2 | Node.js runtime
3 | */'use strict';
4 | /* global main, modules */
5 |
6 | var cache = _require.cache = {};
7 |
8 | function _require(id) {
9 | var module = cache[id];
10 | if (!module) {
11 | var moduleFn = modules[id];
12 | if (!moduleFn) throw new Error('module ' + id + ' not found');
13 | module = cache[id] = {};
14 | var exports = module.exports = {};
15 | moduleFn.call(exports, _require, module, exports, global);
16 | }
17 | return module.exports;
18 | }
19 |
20 | _require.node = require;
21 |
22 | _require.resolve = function(resolved) {
23 | return resolved;
24 | };
25 |
26 | _require(main);
27 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false, evil: true */
2 | /* global describe, it */
3 |
4 | var chai = require('chai');
5 | chai.use(require('chai-as-promised'));
6 | var expect = chai.expect;
7 |
8 | /*
9 | useful methods
10 | ├─ .to.be.fulfilled
11 | ├─ .to.be.rejected
12 | ├─ .to.be.rejectedWith()
13 | ├─ .to.eventually.equal()
14 | ├─ .to.eventually.eql()
15 | └─ .to.become()
16 | */
17 |
18 | var quickstart = require('../');
19 |
20 | // these test check the output of required modules.
21 |
22 | describe('quickstart', function() {
23 |
24 | it('should compile a simple module, using the browser resolver', function() {
25 |
26 | return expect(quickstart({
27 | transforms: ['../transforms/passthrough'],
28 | parsers: { empty: '../parsers/empty' },
29 | sourceMap: true,
30 | root: __dirname + '/root/',
31 | runtime: '../../runtime/node',
32 | main: './test-browser'
33 | }).then(function(result) {
34 | expect(result.source).to.be.a('string');
35 | expect(result.ast).to.be.an('object');
36 | expect(result.sourceMap).to.be.an('object');
37 | eval(result.source);
38 | })).to.be.fulfilled;
39 |
40 | });
41 |
42 | it('should compile a simple module, using the node resolver', function() {
43 |
44 | return expect(quickstart({
45 | transforms: ['../transforms/passthrough'],
46 | parsers: { empty: '../parsers/empty' },
47 | compress: true,
48 | node: true,
49 | root: __dirname + '/root/',
50 | main: './test-node'
51 | }).then(function(result) {
52 | expect(result.source).to.be.a('string');
53 | expect(result.ast).to.be.an('object');
54 | eval(result.source);
55 | })).to.be.fulfilled;
56 |
57 | });
58 |
59 | it('should compile itself', function() {
60 |
61 | return expect(quickstart({
62 | self: true,
63 | output: false,
64 | root: __dirname + '/../'
65 | }).then(function(result) {
66 | expect(result.ast).to.be.an('object');
67 | })).to.be.fulfilled;
68 |
69 | });
70 |
71 | it('should handle browser require-based errors', function() {
72 |
73 | return expect(quickstart({
74 | output: false,
75 | compress: true,
76 | root: __dirname + '/root/',
77 | main: './invalid-require.js'
78 | })).to.be.rejected;
79 |
80 | });
81 |
82 | it('should handle node require-based errors', function() {
83 |
84 | return expect(quickstart({
85 | root: __dirname + '/root/',
86 | main: './invalid-require.js',
87 | node: true
88 | })).to.be.rejected;
89 |
90 | });
91 |
92 | it('should handle javascript errors', function() {
93 |
94 | return expect(quickstart({
95 | root: __dirname + '/root/',
96 | main: './invalid-javascript.js'
97 | })).to.be.rejected;
98 |
99 | });
100 |
101 | it('should handle not found transforms errors', function() {
102 |
103 | return expect(quickstart({
104 | self: true,
105 | transforms: ['./does-not-exist'],
106 | root: __dirname + '/../'
107 | })).to.be.rejected;
108 |
109 | });
110 |
111 | it('should handle not found parsers errors', function() {
112 |
113 | return expect(quickstart({
114 | self: true,
115 | parsers: { empty: './does-not-exist' },
116 | root: __dirname + '/../'
117 | })).to.be.rejected;
118 |
119 | });
120 |
121 | it('should handle transforms plugin errors', function() {
122 |
123 | return expect(quickstart({
124 | transforms: ['../transforms/error'],
125 | root: __dirname + '/root/'
126 | })).to.be.rejected;
127 |
128 | });
129 |
130 | it('should handle parser plugin errors', function() {
131 |
132 | return expect(quickstart({
133 | parsers: { empty: '../parsers/error' },
134 | root: __dirname + '/root/'
135 | })).to.be.rejected;
136 |
137 | });
138 |
139 | it('should require circular dependencies', function() {
140 |
141 | return expect(quickstart({
142 | root: __dirname + '/root/',
143 | runtime: '../../runtime/node',
144 | main: './test-circular'
145 | }).then(function(result) {
146 | eval(result.source);
147 | })).to.be.fulfilled;
148 |
149 | });
150 |
151 | it('should require JSON files', function() {
152 |
153 | return expect(quickstart({
154 | root: __dirname + '/root/',
155 | runtime: '../../runtime/node',
156 | main: './test-json'
157 | }).then(function(result) {
158 | eval(result.source);
159 | })).to.be.fulfilled;
160 |
161 | });
162 |
163 | });
164 |
--------------------------------------------------------------------------------
/test/parsers/empty.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/*path, text*/) {
4 | return {
5 | "type": "Program",
6 | "body": []
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/test/parsers/error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/*path, text*/) {
4 | throw new Error('parser error');
5 | };
6 |
--------------------------------------------------------------------------------
/test/resolver.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 | /* global describe, it */
3 |
4 | var chai = require('chai');
5 | chai.use(require('chai-as-promised'));
6 | var expect = chai.expect;
7 |
8 | /*
9 | useful methods
10 | ├─ .to.be.fulfilled
11 | ├─ .to.be.rejected
12 | ├─ .to.be.rejectedWith()
13 | ├─ .to.eventually.equal()
14 | ├─ .to.eventually.eql()
15 | └─ .to.become()
16 | */
17 |
18 | var pathogen = require('pathogen');
19 |
20 | var Resolver = require('../util/resolver');
21 |
22 | var resolver = new Resolver;
23 |
24 | var resolve = function(path) {
25 | return resolver.resolve(pathogen(__dirname + '/root/'), path);
26 | };
27 |
28 | var relative = function(path) {
29 | return pathogen.relative(pathogen(__dirname + '/root/'), path);
30 | };
31 |
32 | describe('QuickStart Resolver', function() {
33 |
34 | it('should resolve a relative module', function() {
35 | return expect(resolve('./index').then(relative)).to.eventually.equal('./index.js');
36 | });
37 |
38 | it('should resolve the main', function() {
39 | return expect(resolve('./').then(relative)).to.eventually.equal('./main.js');
40 | });
41 |
42 | it('should resolve a package with implicit index.js main', function() {
43 | return expect(resolve('one').then(relative)).to.eventually.equal('./node_modules/one/index.js');
44 | });
45 |
46 | it('should resolve a package with specified main', function() {
47 | return expect(resolve('two').then(relative)).to.eventually.equal('./node_modules/two/two.js');
48 | });
49 |
50 | it('should resolve a package with specified module', function() {
51 | return expect(resolve('two/two').then(relative)).to.eventually.equal('./node_modules/two/two.js');
52 | });
53 |
54 | it('should resolve a package with specified module giving priority to file matches', function() {
55 | return expect(resolve('two/lib').then(relative)).to.eventually.equal('./node_modules/two/lib.js');
56 | });
57 |
58 | it('should resolve a package with specified module giving priority to folder matches', function() {
59 | return expect(resolve('two/lib/').then(relative)).to.eventually.equal('./node_modules/two/lib/index.js');
60 | });
61 |
62 | it('should resolve a package with specified module index.js', function() {
63 | return expect(resolve('two/lib2').then(relative)).to.eventually.equal('./node_modules/two/lib2/index.js');
64 | });
65 |
66 | it('should resolve a package with specified module index.js', function() {
67 | return expect(resolve('two/lib2/').then(relative)).to.eventually.equal('./node_modules/two/lib2/index.js');
68 | });
69 |
70 | it('should use the browser field to resolve faux packages', function() {
71 | return expect(resolve('fs').then(relative)).to.eventually.equal('./lib/fs.js');
72 | });
73 |
74 | it('should use the browser field to swap local modules', function() {
75 | return expect(resolve('./old.js').then(relative)).to.eventually.equal('./new.js');
76 | });
77 |
78 | it('should resolve browser keys to swap local modules', function() {
79 | return expect(resolve('./old').then(relative)).to.eventually.equal('./new.js');
80 | });
81 |
82 | it('should use the browser field to resolve main modules', function() {
83 | return expect(resolve('three').then(relative)).to.eventually.equal('./node_modules/three/lib/three.js');
84 | });
85 |
86 | it('should use the browser field to resolve package modules', function() {
87 | return expect(resolve('one/old').then(relative)).to.eventually.equal('./node_modules/one/new.js');
88 | });
89 |
90 | it('should use the browser field to resolve falsy modules to false', function() {
91 | return expect(resolve('./false')).to.eventually.equal(false);
92 | });
93 |
94 | it('should use the browser field to resolve falsy packages to false', function() {
95 | return expect(resolve('four')).to.eventually.equal(false);
96 | });
97 |
98 | it('should ignore empty browser fields', function() {
99 | return expect(resolve('six').then(relative)).to.eventually.equal('./node_modules/six/index.js');
100 | });
101 |
102 | it('should evaluate main fields as folders too', function() {
103 | return expect(resolve('seven').then(relative)).to.eventually.equal('./node_modules/seven/lib/index.js');
104 | });
105 |
106 | it('should resolve a package with specified main overridden by the browser field', function() {
107 | return expect(resolve('five').then(relative)).to.eventually.equal('./node_modules/five/browser.js');
108 | });
109 |
110 | it('should resolve a package with specified module overridden by the browser field', function() {
111 | return expect(resolve('five/old').then(relative)).to.eventually.equal('./node_modules/five/new.js');
112 | });
113 |
114 | it('should return an error for unresolvable modules', function() {
115 | return expect(resolve('./null')).to.be.rejected;
116 | });
117 |
118 | it('should return an error for unresolvable packages', function() {
119 | return expect(resolve('non-existing-package')).to.be.rejected;
120 | });
121 |
122 | it('should resolve unmatched core_modules to the base value', function() {
123 | return expect(resolve('_debugger')).to.eventually.equal('_debugger');
124 | });
125 |
126 | it('should handle absolute paths (windows)', function() {
127 | var paths = resolver._paths('C:\\Users\\username\\app\\node_modules\\package\\lib\\');
128 | for (var i = 0, l = paths.length; i < l; i++) {
129 | expect(paths[i].substr(0, 2)).to.equal('C:');
130 | }
131 | });
132 |
133 | it('should handle absolute paths (unix)', function() {
134 | var paths = resolver._paths('/Users/username/app/node_modules/package/lib/');
135 | for (var i = 0, l = paths.length; i < l; i++) {
136 | expect(paths[i].substr(0, 1)).to.equal('/');
137 | }
138 | });
139 |
140 | it('should always have a trailing slash in paths', function() {
141 | var resolver = new Resolver({defaultPath: 'folder'});
142 | var paths = resolver._paths('folder');
143 | for (var i = 0, l = paths.length; i < l; i++) {
144 | expect(paths[i].substr(-1, 1)).to.equal('/');
145 | }
146 | });
147 |
148 | });
149 |
--------------------------------------------------------------------------------
/test/root/circular-a.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | module.exports = Readable;
6 | var Writable = require('./circular-b');
7 |
8 | expect(Readable).to.be.a('function');
9 | expect(Readable.name).to.equal('Readable');
10 | expect(Writable).to.be.a('function');
11 | expect(Writable.name).to.equal('Writable');
12 |
13 | function Readable() {};
14 |
--------------------------------------------------------------------------------
/test/root/circular-b.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | module.exports = Writable;
6 | var Readable = require('./circular-a');
7 |
8 | expect(Readable).to.be.a('function');
9 | expect(Readable.name).to.equal('Readable');
10 | expect(Writable).to.be.a('function');
11 | expect(Writable.name).to.equal('Writable');
12 |
13 | function Writable() {}
14 |
--------------------------------------------------------------------------------
/test/root/false.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'false';
4 |
--------------------------------------------------------------------------------
/test/root/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'index';
4 |
--------------------------------------------------------------------------------
/test/root/invalid-javascript.js:
--------------------------------------------------------------------------------
1 | this is not a good idea. ever.
2 |
3 | // this is so invalid omg
4 |
--------------------------------------------------------------------------------
/test/root/invalid-require.js:
--------------------------------------------------------------------------------
1 | require('this-does-not-exist');
2 | require('./this-does-not-exist');
3 |
--------------------------------------------------------------------------------
/test/root/lib/fs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.readFile = function() {};
4 |
--------------------------------------------------------------------------------
/test/root/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var old = require('./old'); // routing module
4 | var fs = require('fs'); // routing native module
5 | require('events'); // non-routing native module
6 |
7 | // non-resolving require
8 | var events = 'events';
9 | require(events);
10 |
11 | var json = require('./module.json'); // json module
12 | var text = require('./module.txt'); // text module
13 | var empty = require('./module.empty'); // custom module
14 |
15 | module.exports = 'main';
16 |
--------------------------------------------------------------------------------
/test/root/module.empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spotify/quickstart/f9f12f3e3dc2a041e4d8ad7b5b46a2d9494d919c/test/root/module.empty
--------------------------------------------------------------------------------
/test/root/module.json:
--------------------------------------------------------------------------------
1 | {
2 | "animals": [
3 |
4 | {
5 | "name": "fox",
6 | "color": "brown",
7 | "quick": true,
8 | "lazy": false,
9 | "jumps": "dog"
10 | },
11 |
12 | {
13 | "name": "dog",
14 | "color": null,
15 | "quick": false,
16 | "lazy": true,
17 | "jumps": null
18 | }
19 |
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/test/root/module.txt:
--------------------------------------------------------------------------------
1 | The quick brown fox jumped over the lazy dog.
2 |
--------------------------------------------------------------------------------
/test/root/new.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'new';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/five/browser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'five/browser';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/five/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'five/main';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/five/new.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'five/new';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/five/old.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'five/old';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/five/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "five",
3 | "main": "./main.js",
4 | "browser": {
5 | "./main": "./browser",
6 | "./old": "./new"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/root/node_modules/four/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'four/index';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/four/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "four"
3 | }
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/one/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'one/index';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/one/new.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'one/new';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/one/old.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'one/old';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/one/one.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'one/one';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/one/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "one",
3 | "browser": {
4 | "./old.js": "./new.js"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/root/node_modules/seven/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = "seven/index";
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/seven/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "lib"
3 | }
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/six/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = "six/index";
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/six/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "browser": {},
3 | "main": "index"
4 | }
5 |
--------------------------------------------------------------------------------
/test/root/node_modules/three/lib/three.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'three/lib/three';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/three/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three",
3 | "browser": "./lib/three.js"
4 | }
5 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/lib.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'two/lib';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'two/lib/index';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/lib/lib.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'two/lib/lib';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/lib2/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'two/lib2/index';
4 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "two",
3 | "main": "two.js"
4 | }
5 |
--------------------------------------------------------------------------------
/test/root/node_modules/two/two.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'two/two';
4 |
--------------------------------------------------------------------------------
/test/root/old.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = 'old';
4 |
--------------------------------------------------------------------------------
/test/root/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "main": "main.js",
4 | "browser": {
5 | "fs": "./lib/fs",
6 | "./old": "./new",
7 | "./invalid-route": "./anything-really",
8 | "one/old": "./new",
9 | "./false": false,
10 | "four": false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/root/test-browser.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | var old = require('./old'); // routing module
6 | expect(old).to.equal('new');
7 |
8 | var oneOld = require('one/old'); // routing module
9 | expect(oneOld).to.equal('one/new');
10 |
11 | var fs = require('fs'); // routing native module
12 | expect(fs).to.be.an('object');
13 |
14 | var events = require('events').EventEmitter; // routing native module
15 | expect(events).to.be.a('function');
16 |
17 | var f = require('./false');
18 | expect(f).to.be.an('object');
19 |
20 | // non-resolving require
21 | try {
22 | var xoxo = '_';
23 | require(xoxo);
24 | } catch (e) {
25 | expect(e instanceof Error).to.be.ok;
26 | }
27 |
28 | var json = require('./module.json'); // json module
29 | expect(json).to.be.an('object');
30 |
31 | var text = require('./module.txt'); // text module
32 | expect(text).to.be.a('string');
33 |
34 | var empty = require('./module.empty'); // custom module
35 | expect(empty).to.be.an('object');
36 |
37 | module.exports = 'test-browser';
38 |
--------------------------------------------------------------------------------
/test/root/test-circular.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | var Readable = require('./circular-a');
6 | var Writable = require('./circular-b');
7 |
8 | expect(Readable).to.be.a('function');
9 | expect(Writable).to.be.a('function');
10 |
11 | module.exports = 'test-circular';
12 |
--------------------------------------------------------------------------------
/test/root/test-json.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | var jsonModule = require('./module.json');
6 | expect(jsonModule).to.be.an('object');
7 | expect(jsonModule.animals).to.be.an('array');
8 |
9 | var animals = require('./module.json').animals;
10 | expect(animals).to.be.an('array');
11 |
12 | var animals2 = require('./module.json')['animals'];
13 | expect(animals2).to.be.an('array');
14 |
15 | var hasProperty = require('./module.json').hasOwnProperty('animals');
16 | expect(hasProperty).to.equal(true);
17 |
18 | module.exports = 'test-json';
19 |
--------------------------------------------------------------------------------
/test/root/test-node.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 |
3 | var expect = require('chai').expect;
4 |
5 | var old = require('./old'); // routing module
6 | expect(old).to.equal('old');
7 |
8 | var oneOld = require('one/old'); // routing module
9 | expect(oneOld).to.equal('one/old');
10 |
11 | var fs = require('fs'); // routing native module
12 | expect(fs).to.be.an('object');
13 |
14 | var _debugger = require('_debugger'); // non-routing native module
15 | expect(_debugger).to.be.an('object');
16 |
17 | var f = require('./false');
18 | expect(f).to.equal('false');
19 |
20 | // non-resolving require
21 | try {
22 | var xoxo = 'xoxo';
23 | require(xoxo);
24 | } catch (e) {
25 | expect(e instanceof Error).to.be.ok;
26 | }
27 |
28 | var json = require('./module.json'); // json module
29 | expect(json).to.be.an('object');
30 |
31 | var text = require('./module.txt'); // text module
32 | expect(text).to.be.a('string');
33 |
34 | var empty = require('./module.empty'); // custom module
35 | expect(empty).to.be.an('object');
36 |
37 | module.exports = 'test-node';
38 |
--------------------------------------------------------------------------------
/test/sequence.js:
--------------------------------------------------------------------------------
1 | /* jshint strict:false */
2 | /* global describe, it */
3 |
4 | var chai = require('chai');
5 | chai.use(require('chai-as-promised'));
6 | var expect = chai.expect;
7 |
8 | /*
9 | useful methods
10 | ├─ .to.be.fulfilled
11 | ├─ .to.be.rejected
12 | ├─ .to.be.rejectedWith()
13 | ├─ .to.eventually.equal()
14 | ├─ .to.eventually.eql()
15 | └─ .to.become()
16 | */
17 |
18 | var Promise = global.Promise || require('promise');
19 | var sequence = require('../util/sequence').use(Promise);
20 |
21 | // utility functions to test async
22 | var resolve = function(value, time) {
23 | return new Promise(function(resolve, reject) {
24 | setTimeout(function() {
25 | resolve(value);
26 | }, time || 10);
27 | });
28 | };
29 |
30 | var error = new Error;
31 |
32 | // always rejects with the same error to asses correct error rejection,
33 | // so I can throw inside handlers and have them show as rejection failures
34 | var reject = function(time) {
35 | return new Promise(function(resolve, reject) {
36 | setTimeout(function() {
37 | reject(error);
38 | }, time || 10);
39 | });
40 | };
41 |
42 | describe('sequence', function() {
43 |
44 | describe('find', function() {
45 |
46 | it('should fulfill', function() {
47 |
48 | return expect(sequence.find([1,2,3,4], function(value) {
49 | if (value === 4) return resolve(value);
50 | return reject();
51 | })).to.become(4);
52 |
53 | });
54 |
55 | it('should fulfill with values', function() {
56 | return expect(sequence.find([1,2,3,4], function(value) {
57 | if (value === 4) return value;
58 | throw error;
59 | })).to.become(4);
60 | });
61 |
62 | it('should reject', function() {
63 |
64 | return expect(sequence.find([1,2,3,4], function(value) {
65 | if (value < 5) return reject();
66 | return resolve(value);
67 | })).to.be.rejectedWith(error);
68 |
69 | });
70 |
71 | it('should reject with values', function() {
72 | return expect(sequence.find([1,2,3,4], function(value) {
73 | if (value < 5) throw error;
74 | return value;
75 | })).to.be.rejectedWith(error);
76 | });
77 |
78 | });
79 |
80 | describe('filter', function() {
81 |
82 | it('should fulfill', function() {
83 |
84 | return expect(sequence.filter([1,2,3,4], function(value) {
85 | if (value === 2) return reject();
86 | return resolve(value * 2);
87 | })).to.eventually.eql([2,6,8]);
88 |
89 | });
90 |
91 | it('should fulfill with values', function() {
92 |
93 | return expect(sequence.filter([1,2,3,4], function(value) {
94 | if (value === 2) throw error;
95 | return value * 2;
96 | })).to.eventually.eql([2,6,8]);
97 |
98 | });
99 |
100 | it('should fulfill empty', function() {
101 |
102 | return expect(sequence.filter([1,2,3,4], function(value) {
103 | return reject();
104 | })).to.eventually.be.empty;
105 |
106 | });
107 |
108 | it('should fulfill empty with values', function() {
109 |
110 | return expect(sequence.filter([1,2,3,4], function(value) {
111 | throw error;
112 | })).to.eventually.be.empty;
113 |
114 | });
115 |
116 | });
117 |
118 | describe('map', function() {
119 |
120 | it('should fulfill', function() {
121 |
122 | return expect(sequence.map([1,2,3,4], function(value) {
123 | return resolve(value * 2);
124 | })).to.eventually.eql([2,4,6,8]);
125 |
126 | });
127 |
128 | it('should fulfill with values', function() {
129 |
130 | return expect(sequence.map([1,2,3,4], function(value) {
131 | return value * 2;
132 | })).to.eventually.eql([2,4,6,8]);
133 |
134 | });
135 |
136 | it('should reject and not visit after rejection', function() {
137 |
138 | return expect(sequence.map([1,2,3,4], function(value) {
139 | expect(value).to.not.equal(3);
140 | expect(value).to.not.equal(4);
141 | if (value === 2) return reject();
142 | return resolve(value * 2);
143 | })).to.be.rejectedWith(error);
144 |
145 | });
146 |
147 | it('should reject with values, and not visit after rejection', function() {
148 |
149 | return expect(sequence.map([1,2,3,4], function(value) {
150 | expect(value).to.not.equal(3);
151 | expect(value).to.not.equal(4);
152 | if (value === 2) throw error;
153 | return value * 2;
154 | })).to.be.rejectedWith(error);
155 |
156 | });
157 |
158 | });
159 |
160 | describe('all', function() {
161 |
162 | it('should fulfill', function() {
163 |
164 | return expect(sequence.all([1,2,3,4], function(value) {
165 | return resolve(value * 2);
166 | })).to.eventually.eql([2,4,6,8]);
167 |
168 | });
169 |
170 | it('should fulfill with values', function() {
171 |
172 | return expect(sequence.all([1,2,3,4], function(value) {
173 | return value * 2;
174 | })).to.eventually.eql([2,4,6,8]);
175 |
176 | });
177 |
178 | it('should reject all', function() {
179 |
180 | return expect(sequence.all([1,2,3,4], function(value) {
181 | if (value === 2) return reject();
182 | return resolve(value * 2);
183 | })).to.be.rejectedWith(error);
184 |
185 | });
186 |
187 | it('should reject with values', function() {
188 |
189 | return expect(sequence.all([1,2,3,4], function(value) {
190 | if (value === 2) throw error;
191 | return value * 2;
192 | })).to.be.rejectedWith(error);
193 |
194 | });
195 |
196 | });
197 |
198 | describe('every', function() {
199 |
200 | it('should fulfill', function() {
201 |
202 | return expect(sequence.every([1,2,3,4], function(value) {
203 | return resolve(value * 2);
204 | })).to.eventually.eql([2,4,6,8]);
205 |
206 | });
207 |
208 | it('should fulfill with values', function() {
209 |
210 | return expect(sequence.every([1,2,3,4], function(value) {
211 | return value * 2;
212 | })).to.eventually.eql([2,4,6,8]);
213 |
214 | });
215 |
216 | it('should reject', function() {
217 |
218 | return expect(sequence.every([1,2,3,4], function(value) {
219 | if (value === 2) return reject();
220 | return resolve(value * 2);
221 | })).to.be.rejectedWith(error);
222 |
223 | });
224 |
225 | it('should reject with values', function() {
226 |
227 | return expect(sequence.every([1,2,3,4], function(value) {
228 | if (value === 2) throw error;
229 | return value * 2;
230 | })).to.be.rejectedWith(error);
231 |
232 | });
233 |
234 | });
235 |
236 | describe('reduce', function() {
237 |
238 | it('should fulfill', function() {
239 |
240 | return expect(sequence.reduce([2,3,4], function(previous, value) {
241 | return resolve(previous + value);
242 | }, 1)).to.eventually.equal(10);
243 |
244 | });
245 |
246 | it('should fulfill with values', function() {
247 |
248 | return expect(sequence.reduce([2,3,4], function(previous, value) {
249 | return previous + value;
250 | }, 1)).to.eventually.equal(10);
251 |
252 | });
253 |
254 | it('should reject and not visit after rejection', function() {
255 |
256 | return expect(sequence.reduce([2,3,4], function(previous, value) {
257 | expect(value).to.not.equal(4);
258 | if (value === 3) return reject();
259 | return resolve(previous + value);
260 | }, 1)).to.be.rejectedWith(error);
261 |
262 | });
263 |
264 | it('should reject with values, and not visit after rejection', function() {
265 |
266 | return expect(sequence.reduce([2,3,4], function(previous, value) {
267 | expect(value).to.not.equal(4);
268 | if (value === 3) throw error;
269 | return previous + value;
270 | }, 1)).to.be.rejectedWith(error);
271 |
272 | });
273 |
274 | });
275 |
276 | describe('race', function() {
277 |
278 | it('should fulfill', function() {
279 |
280 | return expect(sequence.race([40, 20, 60], function(time) {
281 | if (time === 40) reject(time);
282 | return resolve(time, time);
283 | })).to.eventually.equal(20);
284 |
285 | });
286 |
287 | it('should fulfill with values', function() {
288 |
289 | return expect(sequence.race([40, 20, 60], function(time) {
290 | if (time === 60) throw error;
291 | return time;
292 | })).to.eventually.equal(40);
293 |
294 | });
295 |
296 | it('should reject', function() {
297 |
298 | return expect(sequence.race([40, 20, 60], function(time) {
299 | if (time === 20) return reject(time);
300 | return resolve(time, time);
301 | })).to.be.rejectedWith(error);
302 |
303 | });
304 |
305 | it('should reject with values', function() {
306 |
307 | return expect(sequence.race([40, 20, 60], function(time) {
308 | if (time === 40) throw error;
309 | return time;
310 | })).to.be.rejectedWith(error);
311 |
312 | });
313 |
314 | });
315 |
316 | });
317 |
--------------------------------------------------------------------------------
/test/transforms/error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/*path, ast*/) {
4 | throw new Error('transform error');
5 | };
6 |
--------------------------------------------------------------------------------
/test/transforms/passthrough.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(path, tree) {
4 | return tree;
5 | };
6 |
--------------------------------------------------------------------------------
/transforms/inject-globals.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var esprima = require('esprima');
4 | var escope = require('escope');
5 |
6 | var pathogen = require('pathogen');
7 |
8 | var contains = require('mout/array/contains');
9 | var map = require('mout/array/map');
10 |
11 | var Syntax = esprima.Syntax;
12 |
13 | var declaration = function(name, init) {
14 | return {
15 | type: Syntax.VariableDeclaration,
16 | declarations: [{
17 | type: Syntax.VariableDeclarator,
18 | id: {
19 | type: Syntax.Identifier,
20 | name: name
21 | },
22 | init: init
23 | }],
24 | kind: "var"
25 | };
26 | };
27 |
28 | var express = function(string) {
29 | return esprima.parse(string).body[0].expression;
30 | };
31 |
32 | function injectGlobals(path, tree) {
33 | var self = this;
34 |
35 | // escope
36 | var global = escope.analyze(tree, { optimistic: true }).scopes[0];
37 | var variables = map(global.variables, function(v) { return v.name; });
38 | var references = map(global.through, function(r) { return r.identifier.name; });
39 |
40 | var needsProcess = false;
41 | var needsFileName = false;
42 | var needsDirName = false;
43 |
44 | if (!contains(variables, 'process') && contains(references, 'process')) {
45 | needsProcess = true;
46 | }
47 |
48 | if (!contains(variables, '__dirname') && contains(references, '__dirname')) {
49 | needsDirName = true;
50 | }
51 |
52 | if (!contains(variables, '__filename') && contains(references, '__filename')) {
53 | needsFileName = true;
54 | }
55 |
56 | var processName = needsProcess ? 'process' : '__process';
57 |
58 | var processInit = {
59 | type: Syntax.CallExpression,
60 | callee: {
61 | type: Syntax.Identifier,
62 | name: 'require'
63 | },
64 | arguments: [{
65 | type: Syntax.Literal,
66 | value: 'quickstart/browser/process'
67 | }]
68 | };
69 |
70 | var index = 0;
71 |
72 | if (!self.node && (needsProcess || needsFileName || needsDirName)) {
73 | tree.body.splice(index++, 0, declaration(processName, processInit));
74 | }
75 |
76 | // declare __dirname if found
77 | if (needsDirName) {
78 | var dirnameExpression =
79 | '(' + processName + '.cwd() + "' + pathogen.dirname(path).slice(1, -1) + '").replace(/\\/+/g, "/")';
80 | tree.body.splice(index++, 0, declaration('__dirname', express(dirnameExpression)));
81 | }
82 |
83 | // declare __fileName if found
84 | if (needsFileName) {
85 | var filenameExpression =
86 | '(' + processName + '.cwd() + "' + path.slice(1) + '").replace(/\\/+/g, "/")';
87 |
88 | tree.body.splice(index++, 0, declaration('__filename', express(filenameExpression)));
89 | }
90 |
91 | // declare Buffer if found
92 | if (!self.node && !contains(variables, 'Buffer') && contains(references, 'Buffer')) {
93 | var bufferExpression = '(require("buffer").Buffer)';
94 | tree.body.splice(index++, 0, declaration('Buffer', express(bufferExpression)));
95 | }
96 |
97 | return tree;
98 | }
99 |
100 | module.exports = injectGlobals;
101 |
--------------------------------------------------------------------------------
/transforms/require-dependencies.js:
--------------------------------------------------------------------------------
1 | /* global -Promise */
2 | 'use strict';
3 |
4 | var esprima = require('esprima');
5 | var estraverse = require('estraverse');
6 | var Promise = require('promise');
7 | var pathogen = require('pathogen');
8 |
9 | var sequence = require('../util/sequence').use(Promise);
10 |
11 | var transport = require('../util/transport');
12 | var Resolver = require('../util/resolver');
13 |
14 | var isNative = Resolver.isNative;
15 |
16 | var Syntax = esprima.Syntax;
17 |
18 | var traverse = estraverse.traverse;
19 |
20 | var express = function(string) {
21 | return esprima.parse(string).body[0].expression;
22 | };
23 |
24 | // Find dependencies in the AST, callback the modified AST.
25 | function requireDependencies(path, tree) {
26 | var self = this;
27 |
28 | // Finds valid require and resolve nodes.
29 | var requireNodes = [];
30 | var resolveNodes = [];
31 |
32 | traverse(tree, { enter: function(node, parent) {
33 | // callexpression, one argument
34 | if (node.type !== Syntax.CallExpression || node.arguments.length !== 1) return;
35 |
36 | var argument = node.arguments[0];
37 |
38 | // literal require
39 | if (argument.type !== Syntax.Literal) return;
40 |
41 | var callee = node.callee;
42 |
43 | if (callee.type === Syntax.Identifier && callee.name === 'require') {
44 |
45 | requireNodes.push({
46 | node: node,
47 | value: argument.value,
48 | parent: parent
49 | });
50 |
51 | } else if (
52 | // require.resolve
53 | callee.type === Syntax.MemberExpression && callee.object.type === Syntax.Identifier &&
54 | callee.object.name === 'require' && callee.property.type === Syntax.Identifier &&
55 | callee.property.name === 'resolve'
56 | ) {
57 | resolveNodes.push({
58 | node: node,
59 | value: argument.value,
60 | parent: parent
61 | });
62 | }
63 | }});
64 |
65 | // require nodes
66 |
67 | var requireNodesPromise = sequence.every(requireNodes, function(result) {
68 |
69 | var requireNode = result.node;
70 | var requireNodeValue = result.value;
71 |
72 | return self.require(pathogen(self.root + path), requireNodeValue).then(function(uid) {
73 | if (uid) {
74 |
75 | // replace native requires with require.node
76 | if (isNative(uid)) requireNode.callee = {
77 | type: Syntax.MemberExpression,
78 | computed: false,
79 | object: {
80 | type: Syntax.Identifier,
81 | name: 'require'
82 | },
83 | property: {
84 | type: Syntax.Identifier,
85 | name: 'node'
86 | }
87 | };
88 |
89 | requireNode.arguments[0].value = uid; // replace require argument
90 | } else { // false
91 | // replace not found requires with {}
92 | // this happens when requires are nullified by the browser field
93 | requireNode.type = Syntax.ObjectExpression;
94 | requireNode.properties = [];
95 | delete requireNode.callee;
96 | delete requireNode.arguments;
97 | }
98 | });
99 | });
100 |
101 | var resolveNodesPromise = sequence.every(resolveNodes, function(result) {
102 | var resolveNode = result.node;
103 | var resolveNodeValue = result.value;
104 | return self.require(pathogen(self.root + path), resolveNodeValue).then(function(uid) {
105 | resolveNode.arguments[0].value = uid; // replace require.resolve argument
106 | });
107 | });
108 |
109 | // wait for those sequences to finish then return the tree
110 | return sequence.every([requireNodesPromise, resolveNodesPromise]).then(function() {
111 | return tree;
112 | });
113 | }
114 |
115 | module.exports = requireDependencies;
116 |
--------------------------------------------------------------------------------
/util/konsole.js:
--------------------------------------------------------------------------------
1 | /*
2 | Konsole
3 | A small utility to mimic console.group / groupEnd in node.js
4 | prints table characters like this:
5 | Group 1
6 | ├ message1
7 | ├ message2
8 | ├ Nested Group 1
9 | │ └ Super Nested Group 1
10 | │ ├ message3
11 | │ └ message4
12 | └ Nested Group 2
13 | ├ Super Nested Group 2
14 | │ ├ message5
15 | │ └ message6
16 | └ message7
17 | Group 2
18 | ├ message8
19 | └ message9
20 | */'use strict';
21 |
22 | var prime = require('prime');
23 | var isString = require('mout/lang/isString');
24 |
25 | var slice = Array.prototype.slice;
26 |
27 | var Message = prime({
28 |
29 | constructor: function(type, value, parent) {
30 | this.type = type;
31 | this.value = value;
32 | this.parent = parent;
33 | },
34 |
35 | print: function(last, opts) {
36 | if (!this.parent) return this; // don't print messages without a parent.
37 |
38 | if (isString(opts)) {
39 | var str = opts;
40 | opts = {};
41 | opts.last = opts.item = opts.join = opts.line = opts.spcr = str;
42 | } else {
43 | opts = opts || {};
44 | if (!opts.last) opts.last = '└─';
45 | if (!opts.item) opts.item = '├─';
46 | if (!opts.join) opts.join = ' ';
47 | if (!opts.line) opts.line = '│';
48 | if (!opts.spcr) opts.spcr = ' ';
49 | }
50 |
51 | var isRoot = !this.parent.parent;
52 |
53 | if (isRoot) {
54 | console[this.type](this.value);
55 | } else {
56 | // if printing as last item use a different character
57 | var chr = [last ? opts.last : opts.item];
58 | var parent = this;
59 | // recurse parents up
60 | while ((parent = parent.parent)) {
61 | var grand = parent.parent;
62 | // break when no parent of grandparent is found.
63 | if (!grand.parent) break;
64 | // if parent is the last parent use a different character
65 | var isParentLastOfGrand = (grand.values[grand.values.length - 1] === parent);
66 | chr.unshift(isParentLastOfGrand ? opts.spcr : opts.line);
67 | }
68 | console[this.type](chr.join(opts.join), this.value);
69 | }
70 |
71 | return this;
72 | }
73 | });
74 |
75 | var Group = prime({
76 |
77 | inherits: Message,
78 |
79 | constructor: function(type, name, parent) {
80 | Group.parent.constructor.call(this, type, name, parent);
81 | this.values = [];
82 | },
83 |
84 | push: function(stmnt) {
85 | this.values.push(stmnt);
86 | return this;
87 | },
88 |
89 | print: function(last, opts) {
90 | Group.parent.print.call(this, last, opts);
91 | var values = this.values;
92 | for (var i = 0; i < values.length; i++) values[i].print(i === values.length - 1, opts);
93 | return this;
94 | }
95 |
96 | });
97 |
98 | var Konsole = prime({
99 |
100 | constructor: function(type) {
101 | this.type = type;
102 | this.history = [this.current = new Group(this.type)];
103 | },
104 |
105 | write: function() {
106 | var parent = this.current;
107 | parent.push(new Message(this.type, slice.call(arguments).join(' '), parent));
108 | return this;
109 | },
110 |
111 | group: function(name) {
112 | var parent = this.current;
113 | var group = this.current = new Group(this.type, name, parent);
114 | parent.push(group);
115 | this.history.unshift(group);
116 | return this;
117 | },
118 |
119 | groupEnd: function() {
120 | var history = this.history;
121 | if (history.length > 1) {
122 | history.shift();
123 | this.current = history[0];
124 | }
125 | return this;
126 | },
127 |
128 | print: function(opts) {
129 | this.history[0].print(true, opts);
130 | return this;
131 | }
132 |
133 | });
134 |
135 | Konsole.Message = Message;
136 | Konsole.Group = Group;
137 |
138 | module.exports = Konsole;
139 |
--------------------------------------------------------------------------------
/util/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | simple messages
3 | - log / error / warn
4 | - messages = new Messages;
5 | - messages.group(groupName).log(message);
6 | - messages.group(groupName) === message.group(groupName);
7 | - messages.group(groupName).group(subGroupName).log(message);
8 | - messages.group(groupName).group(subGroupName).log(message);
9 | - messages.log(message);
10 | - messages.print(printer); // printer is a simple function that gets (type, message).
11 | */'use strict';
12 |
13 | var prime = require('prime');
14 |
15 | var forIn = require('mout/object/forIn');
16 | var forEach = require('mout/array/forEach');
17 | var size = require('mout/object/size');
18 |
19 | var microseconds = require('microseconds');
20 |
21 | var Message = prime({
22 |
23 | constructor: function(type, statement) {
24 | this.type = type;
25 | this.statement = statement;
26 | }
27 |
28 | });
29 |
30 | var Messages = prime({
31 |
32 | constructor: function(name) {
33 | this.name = name;
34 | this.reset();
35 | },
36 |
37 | group: function(name, collapsed) {
38 | var group = this.groups[name] || (this.groups[name] = new Messages(name));
39 | group.collapsed = !!collapsed;
40 | return group;
41 | },
42 |
43 | groupCollapsed: function(name) {
44 | return this.group(name, true);
45 | },
46 |
47 | error: function(statement) {
48 | this.messages.push(new Message('error', statement));
49 | return this;
50 | },
51 |
52 | warn: function(statement) {
53 | this.messages.push(new Message('warn', statement));
54 | return this;
55 | },
56 |
57 | info: function(statement) {
58 | this.messages.push(new Message('info', statement));
59 | return this;
60 | },
61 |
62 | log: function(statement) {
63 | return this.info(statement);
64 | },
65 |
66 | time: function(id) {
67 | this.timeStamps[id] = microseconds.now();
68 | return this;
69 | },
70 |
71 | timeEnd: function(id, name) {
72 | var timestamp = this.timeStamps[id];
73 | if (timestamp) {
74 | var end = microseconds.since(timestamp);
75 | var timeStampString = microseconds.parse(end).toString();
76 | this.messages.push(new Message('time', {id: name || id, message: timeStampString}));
77 | }
78 | return this;
79 | },
80 |
81 | print: function(format) {
82 | if (!this.messages.length && !size(this.groups)) return;
83 |
84 | if (this.name) format(this.collapsed ? 'groupCollapsed' : 'group', this.name);
85 |
86 | forIn(this.groups, function(group) {
87 | group.print(format);
88 | });
89 |
90 | forEach(this.messages, function(message) {
91 | format(message.type, message.statement);
92 | });
93 |
94 | if (this.name) format('groupEnd');
95 |
96 | return this;
97 | },
98 |
99 | reset: function() {
100 | this.messages = [];
101 | this.groups = {};
102 | this.timeStamps = {};
103 | }
104 |
105 | });
106 |
107 | module.exports = Messages;
108 |
--------------------------------------------------------------------------------
/util/program.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var esprima = require('esprima');
4 | var forIn = require('mout/object/forIn');
5 |
6 | var Syntax = esprima.Syntax;
7 |
8 | // This generates the full single-file AST, with the runtime and modules.
9 | // main and modules will get fed to the runtime.
10 | module.exports = function program(main, modules, runtime){
11 | var Program = esprima.parse('(function(main, modules) {})({})');
12 |
13 | var Runtime = runtime;
14 | Runtime.type = Syntax.BlockStatement; // change to a BlockStatement
15 | Program.body[0].expression.callee.body = Runtime;
16 |
17 | var ProgramArguments = Program.body[0].expression.arguments;
18 | var ObjectExpression = ProgramArguments[0];
19 | ProgramArguments.unshift({
20 | type: Syntax.Literal,
21 | value: main
22 | });
23 |
24 | forIn(modules, function(module, id) {
25 | var tree = module.ast;
26 | var ModuleProgram = esprima.parse('(function(require, module, exports, global){})');
27 | tree.type = Syntax.BlockStatement; // change to a BlockStatement
28 | ModuleProgram.body[0].expression.body = tree;
29 |
30 | ObjectExpression.properties.push({
31 | type: Syntax.Property,
32 | key: {
33 | type: Syntax.Literal,
34 | value: id
35 | },
36 | value: ModuleProgram.body[0].expression,
37 | kind: 'init'
38 | });
39 | });
40 |
41 | return Program;
42 | };
43 |
--------------------------------------------------------------------------------
/util/resolver.js:
--------------------------------------------------------------------------------
1 | /* global -Promise*/
2 | 'use strict';
3 |
4 | var pathModule = require('path');
5 | var pathogen = require('pathogen');
6 | var prime = require('prime');
7 |
8 | var Promise = require('promise');
9 |
10 | var isString = require('mout/lang/isString');
11 | var isPlainObject = require('mout/lang/isPlainObject');
12 | var contains = require('mout/array/contains');
13 |
14 | var transport = require('./transport');
15 |
16 | var sequence = require('./sequence').use(Promise);
17 |
18 | // absolute path
19 | var absRe = /^(\/|.+:\/)/;
20 | // relative path
21 | var relRe = /^(\.\/|\.\.\/)/;
22 |
23 | // implementation of http://nodejs.org/api/modules.html#modules_all_together
24 |
25 | var natives = [
26 | '_debugger', '_linklist', 'assert', 'buffer', 'child_process', 'console', 'constants', 'crypto',
27 | 'cluster', 'dgram', 'dns', 'domain', 'events', 'freelist', 'fs', 'http', 'https', 'module',
28 | 'net', 'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream', '_stream_readable',
29 | '_stream_writable', '_stream_duplex', '_stream_transform', '_stream_passthrough',
30 | 'string_decoder', 'sys', 'timers', 'tls', 'tty', 'url', 'util', 'vm', 'zlib'
31 | ];
32 |
33 | var isNative = function(pkg) {
34 | return contains(natives, pkg);
35 | };
36 |
37 | var Resolver = prime({
38 |
39 | constructor: function(options) {
40 | if (!options) options = {};
41 |
42 | this.browser = options.browser == null ? true : !!options.browser;
43 | this.nodeModules = options.nodeModules || 'node_modules';
44 | this.defaultPath = options.defaultPath ? pathogen.resolve(options.defaultPath) : null;
45 | },
46 |
47 | // resolve required from a specific location
48 | // follows the browser field in package.json
49 | resolve: function(from, required) {
50 | from = pathogen.resolve(pathogen.dirname(from));
51 |
52 | if (isNative(required)) {
53 | if (!this.browser) return Promise.resolve(required);
54 | else return this._findRoute(this._paths(from), required);
55 | } else {
56 | if (!this.browser) return this._resolve(from, required);
57 | else return this._resolveAndRoute(from, required);
58 | }
59 | },
60 |
61 | _resolveAndRoute: function(from, required) {
62 | var self = this;
63 |
64 | return self._resolve(from, required).then(function(resolved) {
65 | return self._route(from, resolved);
66 | });
67 | },
68 |
69 | // resolve required from a specific location
70 | // does not follow the browser field in package.json
71 | _resolve: function(from, required) {
72 | if (relRe.test(required)) return this._load(pathogen.resolve(from, required));
73 | else if (absRe.test(required)) return this._load(required);
74 | else return this._package(from, required);
75 | },
76 |
77 | _findRouteInBrowserField: function(browser, path, resolved) {
78 | var self = this;
79 |
80 | return sequence(browser, function(value, key, control) {
81 |
82 | if (isNative(key)) {
83 |
84 | if (key === resolved) {
85 | if (!value) control.resolve(false);
86 | else self._resolveAndRoute(path, value).then(control.resolve, control.reject);
87 | } else {
88 | control.save(null).continue();
89 | }
90 |
91 | } else {
92 |
93 | self._resolve(path, key).then(function(res) {
94 | if (res === resolved) {
95 | if (!value) control.resolve(false);
96 | else self.resolve(path, value).then(control.resolve, control.reject);
97 | } else {
98 | control.save(null).continue();
99 | }
100 | }, function() {
101 | control.save(null).continue();
102 | });
103 |
104 | }
105 |
106 | });
107 | },
108 |
109 | _findRoute: function(paths, resolved) {
110 | var self = this;
111 | return sequence(paths, function(path, i, control) {
112 |
113 | transport.json(path + 'package.json').then(function(json) {
114 |
115 | if (isPlainObject(json.browser)) {
116 |
117 | self._findRouteInBrowserField(json.browser, path, resolved).then(function(route) {
118 | if (route == null) control.save(resolved).continue(); // no route found
119 | else control.resolve(route);
120 | }, control.reject);
121 |
122 | } else {
123 |
124 | control.save(resolved).continue();
125 |
126 | }
127 |
128 | }, function(/*read json error*/) {
129 | control.save(resolved).continue();
130 | });
131 |
132 | });
133 |
134 | },
135 |
136 | // finds the routed entry in the browser field in package.json
137 | // when not found it will simply callback resolved
138 | _route: function(from, resolved) {
139 | var self = this;
140 |
141 | var paths = self._paths(from);
142 |
143 | return self.findRoot(resolved).then(function(path) {
144 | if (paths[0] !== path) paths.unshift(path);
145 | return self._findRoute(paths, resolved);
146 | });
147 | },
148 |
149 | // resolves either a file or a directory to a module, based on pattern
150 | _load: function(required) {
151 | var self = this;
152 | var promise = Promise.reject();
153 | if (!(/\/$/).test(required)) promise = self._file(required);
154 | return promise.catch(function() {
155 | return self._directory(required);
156 | });
157 | },
158 |
159 | // resolves a file name to a module
160 | _file: function(required) {
161 | var exts = ['.js'];
162 |
163 | if (pathogen.extname(required)) exts.unshift('');
164 |
165 | return sequence.find(exts, function(ext) {
166 | var path = required + ext;
167 | return transport(required + ext).then(function() {
168 | return path;
169 | });
170 | });
171 | },
172 |
173 | // resolves a directory to a module
174 | // takes into account the main field (or browser field as string)
175 | _directory: function(full) {
176 | var self = this;
177 |
178 | return transport.json(full + 'package.json').catch(function() {
179 | return {};
180 | }).then(function(json) {
181 | var main = isString(json.browser) ? json.browser : json.main;
182 | if (main == null) return self._file(pathogen.resolve(full, 'index'));
183 | return self._load(pathogen.resolve(full, main));
184 | });
185 | },
186 |
187 | // resolves a packaged require to a module
188 | _package: function(from, required) {
189 | var self = this;
190 |
191 | var split = required.split('/');
192 | var packageName = split.shift();
193 |
194 | // make it into an explicit folder if it's only a package
195 | if (required.indexOf('/') === -1) required += '/';
196 |
197 | return self.resolvePackage(from, packageName).then(function(jsonPath) {
198 | return self._load(pathogen.dirname(jsonPath) + split.join('/'));
199 | });
200 | },
201 |
202 | // generates a list of possible node modules paths
203 | _paths: function(path) {
204 | var node_modules = this.nodeModules;
205 |
206 | var paths = [];
207 | var parts = (path).split('/').slice(1, -1);
208 | var drive = new pathogen(path).drive;
209 |
210 | for (var i = parts.length, part; i; part = parts[i--]) {
211 | if (part === node_modules) continue;
212 | var dir = drive + '/' + parts.slice(0, i).join('/') + '/';
213 | paths.push(dir);
214 | }
215 | paths.push(drive + '/');
216 | if (this.defaultPath) paths.push(this.defaultPath + '/');
217 | return paths;
218 | },
219 |
220 | resolvePackage: function(path, packageName) {
221 | var self = this;
222 | var node_modules = self.nodeModules;
223 | var nodePath = (typeof process !== 'undefined') && process.env && process.env.NODE_PATH;
224 |
225 | path = pathogen.resolve(pathogen.dirname(path));
226 |
227 | var paths = self._paths(path);
228 |
229 | paths = paths.map(function(path) {
230 | return path + node_modules;
231 | });
232 |
233 | if (nodePath) {
234 | paths = nodePath.split(pathModule.delimiter).filter(function(path) {
235 | return !!path;
236 | }).concat(paths);
237 | }
238 |
239 | return sequence.find(paths, function(path) {
240 | var jsonPath = path + '/' + packageName + '/package.json';
241 |
242 | return transport(jsonPath).then(function() {
243 | return jsonPath;
244 | });
245 |
246 | });
247 | },
248 |
249 | // find the package root of a specified file (or dir)
250 | findRoot: function(path) {
251 | var self = this;
252 | var paths = self._paths(pathogen.resolve(pathogen.dirname(path)));
253 |
254 | return sequence.find(paths, function(path) {
255 | return transport(path + 'package.json').then(function() {
256 | return path;
257 | });
258 | });
259 |
260 | }
261 |
262 | });
263 |
264 | Resolver.natives = natives;
265 | Resolver.isNative = isNative;
266 |
267 | module.exports = Resolver;
268 |
--------------------------------------------------------------------------------
/util/sequence.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var forEach = require('mout/collection/forEach');
4 | var isInteger = require('mout/lang/isInteger');
5 | var isArray = require('mout/lang/isArray');
6 | var fillIn = require('mout/object/fillIn');
7 |
8 | function Control(collection, length, keys, values, next, resolve, reject) {
9 |
10 | var pending = true;
11 | var remaining = length;
12 |
13 | var caught, saved;
14 |
15 | var done = function() {
16 | pending = false;
17 | if (caught) {
18 | reject(caught.error);
19 | } else if (saved) {
20 | resolve(saved.value);
21 | } else {
22 | if (isArray(collection)) {
23 | var result = [];
24 | for (var i = 0; i < length; i++) if (i in collection) result.push(collection[i]);
25 | resolve(result);
26 | } else {
27 | resolve(collection);
28 | }
29 | }
30 | };
31 |
32 | this.resolve = function(value) {
33 | if (pending) {
34 | pending = false;
35 | resolve(value);
36 | }
37 | return this;
38 | };
39 |
40 | this.reject = function(error) {
41 | if (pending) {
42 | pending = false;
43 | reject(error);
44 | }
45 | return this;
46 | };
47 |
48 | this.collect = function(index, value) {
49 | if (pending) {
50 | collection[keys[index]] = value;
51 | if (!--remaining) done();
52 | }
53 | return this;
54 | };
55 |
56 | this.catch = function(error) {
57 | if (pending) {
58 | caught = { error: error };
59 | if (!--remaining) done();
60 | }
61 | return this;
62 | };
63 |
64 | this.save = function(value) {
65 | if (pending) {
66 | saved = { value: value };
67 | if (!--remaining) done();
68 | }
69 | return this;
70 | };
71 |
72 | this.skip = function() {
73 | if (pending && !--remaining) done();
74 | return this;
75 | };
76 |
77 | this.continue = function() {
78 | if (pending) next();
79 | return this;
80 | };
81 |
82 | }
83 |
84 | var identity = function(promise) { return promise; };
85 |
86 | function use(Promise) {
87 |
88 | if (!Promise) throw new Error('sequence needs a Promise implementation');
89 |
90 | function sequence(list, iterator, previous) {
91 | var length = 0;
92 | var keys = [];
93 | var values = [];
94 |
95 | forEach(list, function(value, key) {
96 | values.push(value);
97 | keys.push(key);
98 | length++;
99 | });
100 |
101 | if (!length) return Promise.resolve(previous);
102 |
103 | var index = 0;
104 |
105 | var collection = (isInteger(list.length)) ? [] : {};
106 |
107 | return new Promise(function(resolve, reject) {
108 |
109 | var control = new Control(collection, length, keys, values, next, resolve, reject);
110 |
111 | function next() {
112 | if (index === length) return;
113 | var current = index++;
114 |
115 | var key = keys[current];
116 | var value = values[current];
117 |
118 | var ctrl = fillIn({
119 | index: current,
120 | last: index === length,
121 | collect: function(value) {
122 | return control.collect(current, value);
123 | }
124 | }, control);
125 |
126 | previous = iterator(value, key, ctrl, previous);
127 | }
128 |
129 | next();
130 | });
131 |
132 | }
133 |
134 | // find
135 | // ├─ sequential execution
136 | // ├─ resolves with a value when one iterator is resolveed
137 | // └─ rejects with one error when the last iterator is rejected
138 | sequence.find = function find(values, iterator) {
139 | if (!iterator) iterator = identity;
140 |
141 | return sequence(values, function(value, key, control) {
142 | Promise.resolve().then(function() {
143 | return iterator(value, key);
144 | }).then(control.resolve, function(error) {
145 | control.catch(error).continue();
146 | });
147 | });
148 | };
149 |
150 | // filter
151 | // ├─ parallel execution
152 | // └─ always resolves with an array of values (or empty array) with the value of every resolveed iterator.
153 | sequence.filter = function filter(values, iterator) {
154 | if (!iterator) iterator = identity;
155 |
156 | return sequence(values, function(value, key, control) {
157 | Promise.resolve().then(function() {
158 | return iterator(value, key);
159 | }).then(control.collect, control.skip);
160 | control.continue();
161 | });
162 | };
163 |
164 | // map
165 | // ├─ sequential execution
166 | // ├─ resolves with an array of values representing every resolveed iterator.
167 | // └─ rejects when one iterator rejects
168 | sequence.map = function map(values, iterator) {
169 | if (!iterator) iterator = identity;
170 |
171 | return sequence(values, function(value, key, control) {
172 | Promise.resolve().then(function() {
173 | return iterator(value, key);
174 | }).then(function(value) {
175 | control.collect(value).continue();
176 | }, control.reject);
177 | });
178 | };
179 |
180 | // every
181 | // ├─ parallel execution
182 | // ├─ resolves with an array of values when all iterators are resolveed
183 | // ├─ rejects with the last error when one iterator is rejected
184 | // ├─ same as map, but parallel.
185 | // ├─ executes all iterators
186 | // └─ waits for all iterators to be either resolveed or rejected before resolving or rejecting.
187 | sequence.every = function every(values, iterator) {
188 | if (!iterator) iterator = identity;
189 |
190 | return sequence(values, function(value, key, control) {
191 | Promise.resolve().then(function() {
192 | return iterator(value, key);
193 | }).then(control.collect, control.catch);
194 | control.continue();
195 | });
196 | };
197 |
198 | // some
199 | // ├─ parallel execution
200 | // ├─ resolves with an array of values when some iterators are resolveed
201 | // ├─ rejects when no iterators are resolved
202 | // ├─ executes all iterators
203 | // └─ waits for all iterators to be either resolveed or rejected before resolving or rejecting.
204 | sequence.some = function some(values, iterator) {
205 | if (!iterator) iterator = identity;
206 |
207 | var found = false;
208 | return sequence(values, function(value, key, control) {
209 | Promise.resolve().then(function() {
210 | return iterator(value, key);
211 | }).then(function(value) {
212 | found = true;
213 | control.collect(value);
214 | }, function(error) {
215 | if (control.last && !found) control.reject(error);
216 | else control.skip();
217 | });
218 | control.continue();
219 | });
220 | };
221 |
222 | // all
223 | // ├─ parallel execution
224 | // ├─ resolves with an array of values when all iterators are resolveed
225 | // ├─ rejects with the first error when one iterator is rejected
226 | // ├─ same as map, but parallel
227 | // └─ executes all iterators
228 | sequence.all = function all(values, iterator) {
229 | if (!iterator) iterator = identity;
230 |
231 | return sequence(values, function(value, key, control) {
232 | Promise.resolve().then(function() {
233 | return iterator(value, key);
234 | }).then(control.collect, control.reject);
235 | control.continue();
236 | });
237 | };
238 |
239 | // reduce
240 | // ├─ sequential execution
241 | // ├─ resolves with one value when all iterators are resolveed
242 | // └─ rejects with one error when one iterator is rejected
243 | sequence.reduce = function reduce(values, iterator, init) {
244 | if (!iterator) iterator = identity;
245 |
246 | return sequence(values, function(value, key, control, promise) {
247 | return promise.then(function(resolved) {
248 | return iterator(resolved, value, key);
249 | }).then(function(value) {
250 | control.save(value).continue();
251 | return value;
252 | }, control.reject);
253 | }, Promise.resolve(init));
254 | };
255 |
256 | // race
257 | // ├─ parallel execution
258 | // ├─ resolves with first resolveed iterator
259 | // └─ rejects with first rejected iterator
260 | sequence.race = function race(values, iterator) {
261 | if (!iterator) iterator = identity;
262 |
263 | return sequence(values, function(value, key, control) {
264 | Promise.resolve().then(function() {
265 | return iterator(value, key);
266 | }).then(control.resolve, control.reject);
267 | control.continue();
268 | });
269 | };
270 |
271 | return sequence;
272 |
273 | }
274 |
275 | exports.use = use;
276 |
--------------------------------------------------------------------------------
/util/transport.js:
--------------------------------------------------------------------------------
1 | /* global -Promise */
2 | 'use strict';
3 |
4 | var fs = require('fs');
5 | var pathogen = require('pathogen');
6 | var Promise = require('promise');
7 |
8 | var transport;
9 | var cache = { get: {}, json: {} };
10 |
11 | if ('readFile' in fs) {
12 |
13 | transport = function get(url) {
14 | var cached = cache.get[url];
15 | if (cached) return cached;
16 |
17 | return cache.get[url] = new Promise(function(fulfill, reject) {
18 | fs.readFile(pathogen.sys(url), 'utf-8', function(error, data) {
19 | error ? reject(error) : fulfill(data);
20 | });
21 | });
22 | };
23 |
24 | } else {
25 |
26 | // conditional require for node.js, this could be a non-node friendly package.
27 | var agent = require('agent');
28 |
29 | // this goes easy on the browser
30 | agent.MAX_REQUESTS = 4;
31 |
32 | // we don't want agent to automatically decode json, to match the readFile behavior.
33 | agent.decoder('application/json', null);
34 |
35 | transport = function get(url) {
36 | var cached = cache.get[url];
37 | if (cached) return cached;
38 |
39 | return cache.get[url] = new Promise(function(fulfill, reject) {
40 |
41 | agent.get(url, function(error, response) {
42 | if (error) return reject(error);
43 | var status = response.status;
44 | if (status >= 300 || status < 200) return reject(new Error('GET ' + url + ' ' + status));
45 |
46 | if (pathogen.extname(url) !== '.html' && response.header['Content-Type'] === 'text/html') {
47 | // reject mismatching html content types (in case of file listing, treat as error)
48 | reject(new Error('GET ' + url + ' content-type mismatch'));
49 | } else {
50 | fulfill(response.body);
51 | }
52 |
53 | });
54 |
55 | });
56 |
57 | };
58 |
59 | }
60 |
61 | transport.json = function json(url) {
62 | var cached = cache.json[url];
63 | if (cached) return cached;
64 | return cache.json[url] = transport(url).then(JSON.parse);
65 | };
66 |
67 | transport.cache = cache;
68 |
69 | module.exports = transport;
70 |
--------------------------------------------------------------------------------