UNPKG

10.1 kBJavaScriptView Raw
1if (process.env.NODE_ENV === 'development') {
2 require('longjohn');
3}
4
5var fs = require('fs');
6var path = require('path');
7var crypto = require('crypto');
8var esprima = require('esprima');
9var resolve = require('resolve');
10var browserResolve = require('browser-resolve');
11var events = require('events');
12
13var core = {
14 "assert": "assert",
15 "buffer_ieee754": "buffer-browserify/buffer_ieee754",
16 "buffer": "buffer-browserify",
17 "child_process": "",
18 "cluster": "",
19 "console": "console-browserify",
20 "constants": "constants-browserify",
21 "crypto": "crypto-browserify",
22 "_debugger": "",
23 "dgram": "",
24 "dns": "",
25 "domain": "",
26 "events": "events",
27 "freelist": "",
28 "fs": "browserify-fs",
29 "http": "http-browserify",
30 "https": "https-browserify",
31 "_linklist": "",
32 "module": "",
33 "net": "",
34 "os": "os-browserify",
35 "path": "path-browserify",
36 "punycode": "punycode",
37 "querystring": "querystring",
38 "readline": "",
39 "repl": "",
40 "stream": "stream-browserify",
41 "string_decoder": "string_decoder",
42 "sys": "",
43 "timers": "timers-browserify",
44 "tls": "",
45 "tty": "tty-browserify",
46 "url": "url",
47 "util": "util",
48 "vm": "vm-browserify",
49 "zlib": "browserify-zlib"
50};
51
52var mainHeader = function (main, globalVal) {
53 globalVal = typeof global !== 'undefined' ? global : globalVal;
54 var cached = {};
55 var loading = {};
56 /*core*/
57 function bundleRequire(id) {
58 var bundle = main[id];
59 if (!bundle) {
60 throw new Error('Invalid module id ' + id);
61 }
62 if (cached[id]) {
63 return cached[id];
64 }
65 if (loading[id]) {
66 return loading[id];
67 }
68 var module = {exports: loading[id] = {}};
69 bundle[0].call(module.exports, module.exports, function bundlerRequire(moduleName) {
70 if (typeof core != 'undefined' && core.indexOf(moduleName) > -1) {
71 return require(moduleName); // fallback to node require
72 }
73 return bundleRequire(bundle[1][moduleName] || moduleName);
74 }, module, globalVal);
75 loading[id] = null;
76 return cached[id] = module.exports;
77 }
78
79 bundleRequire(0);
80}.toString();
81
82function Bundler(options) {
83 events.EventEmitter.call(this);
84 var defaults = {
85 directory: process.cwd(), // the main directory
86 strict: false, // if to append 'use strict'; in the header
87 web: false, // if to add core node modules
88 extensions: ['.js', '.json'], // files to require
89 out: null, // file to save as
90 wait: 1 // how long to wait
91 };
92 if (options) {
93 for (var property in defaults) {
94 if (defaults.hasOwnProperty(property) && options.hasOwnProperty(property)) {
95 defaults[property] = options[property];
96 }
97 }
98 }
99 this.options = defaults;
100}
101
102Bundler.prototype = events.EventEmitter.prototype;
103
104Bundler.prototype.files = {};
105
106Bundler.prototype.addFile = function (file, directory) {
107 if (!fs.existsSync(file)) {
108 throw new Error('File ' + file + ' not found');
109 }
110 if (!directory) {
111 file = path.join(this.options.directory, file);
112 this.main = file;
113 }
114 var bundled = this.files[file] = new BundledFile(file, directory || '', this);
115 bundled.checkContents(); // check AFTER setting file
116 this.needsRebundle();
117};
118
119Bundler.prototype.reloadFile = function (file, directory) {
120 var bundled = this.files[file];
121 if (bundled) {
122 bundled.checkContents();
123 } else {
124 this.addFile(file, directory);
125 }
126};
127
128Bundler.prototype.loadDependencies = function (contents, file, directory) {
129 var tokens;
130 try {
131 tokens = esprima.tokenize(contents);
132 } catch (e) {
133 console.error('Failed to tokenize ' + file, e.stack);
134 return [];
135 }
136 var dependencies = [];
137 for (var i = 0, length = tokens.length; i < length; i++) {
138 var token = tokens[i];
139 if (token.type === 'Identifier' && token.value === 'require' && i < length - 3) {
140 // check the require statement, it should start with a (, have a string, then end with a )
141 var next = tokens[i + 1];
142 if (next.type !== 'Punctuator' || next.value !== '(') {
143 continue;
144 }
145 next = tokens[i + 3];
146 if (next.type !== 'Punctuator' || next.value !== ')') {
147 continue;
148 }
149 next = tokens[i + 2];
150 if (next.type !== 'String') {
151 continue;
152 }
153 var value = next.value.replace(/['"]+/g, '');
154 if (resolve.isCore(value)) {
155 if (this.options.web) {
156 // use special modules if available
157 var specialModule = core[value];
158 if (specialModule.length === 0) {
159 throw new Error('There is no web version of the core module ' + value + '!');
160 }
161 var module = resolve.sync(specialModule, {basedir: __dirname});
162 if (!this.files[module]) {
163 this.reloadFile(module, path.dirname(module));
164 }
165 dependencies.push({module: module, value: value});
166 } else {
167 dependencies.push(value); // regular node importing
168 }
169 } else {
170 try {
171 var module;
172 if (this.options.web) {
173 module = browserResolve.sync(value, {filename: file, extensions: this.options.extensions});
174 } else {
175 module = resolve.sync(value, {basedir: directory || this.options.directory, extensions: this.options.extensions});
176 }
177 if (!this.files[module]) {
178 this.reloadFile(module, path.dirname(module));
179 }
180 dependencies.push({module: module, value: value});
181 } catch (e) {
182 console.error('Failed to load module', value, e.message);
183 }
184 }
185 }
186 }
187 return dependencies;
188};
189
190Bundler.prototype.needsRebundle = function () {
191 if (this._bundling) {
192 return;
193 }
194 this._bundling = true;
195 setTimeout(() => {
196 this._bundling = false;
197 this.bundle();
198 }, this.options.wait); // allow other processes to run and cancel
199};
200
201Bundler.prototype.bundle = function () {
202 var start = Date.now();
203 this.emit('bundleStart', start);
204 var array = [];
205 var i = 0;
206 var reverseIds = {};
207 // first loop, assign ids
208 for (var property in this.files) {
209 if (this.files.hasOwnProperty(property)) {
210 if (reverseIds[property]) {
211 continue; // already bundled
212 }
213 reverseIds[property] = i++;
214 }
215 }
216 // second loop, build code
217 for (var property in this.files) {
218 if (this.files.hasOwnProperty(property)) {
219 // todo check if previously bundled
220 var bundled = this.files[property];
221 var dependencies = {};
222
223 for (var j = 0, length = bundled.dependencies.length; j < length; j++) {
224 var dependency = bundled.dependencies[j];
225 if (typeof dependency !== 'string') {
226 dependencies[dependency.value] = reverseIds[dependency.module];
227 }
228 }
229
230 var event = {
231 file: property,
232 content: bundled.contents
233 };
234 if (bundled.changed) {
235 this.emit('bundleFile', event);
236 bundled.changed = false;
237 }
238 if (path.extname(event.file) === '.json') {
239 array.push(reverseIds[bundled.file] + `: [function (exports, require, module, global) {module.exports = ${event.content}}, ${JSON.stringify(dependencies)}]`);
240 } else {
241 array.push(reverseIds[bundled.file] + `: [function (exports, require, module, global) {${event.content}}, ${JSON.stringify(dependencies)}]`);
242 }
243 }
244 }
245 var header = mainHeader;
246 header = header.replace('/*main*/', this.main);
247 header = header.replace('/*core*/', this.options.web ? '' : 'var core = ' + JSON.stringify(Object.keys(core)) + ';');
248
249 var text = '(' + header + ')({' + array.join(',') + '}, this);';
250 if (this.options.strict) {
251 text = '\'use strict\';\n' + text;
252 }
253
254 var event = {content: text, time: Date.now() - start, files: array.length};
255 this.emit('bundle', event);
256 if (typeof this.options.out === 'string') {
257 fs.writeFileSync(this.options.out, event.content);
258 } else if (this.options.out) {
259 this.options.out(event.content);
260 } else {
261 process.stdout.write(event.content);
262 }
263};
264
265module.exports = Bundler;
266
267function BundledFile(file, directory, bundler) {
268 this.file = file;
269 this.directory = directory;
270 this.bundler = bundler;
271 this.changed = false;
272}
273
274BundledFile.prototype.checkContents = function () {
275 try {
276 var contents = fs.readFileSync(this.file);
277 var newHash;
278 if (!this.contents || this.contents.length != contents.length || this.hash != (newHash = calculateHash(contents))) {
279 this.hash = newHash;
280 this.contents = contents;
281 this.changed = true;
282 var event = {contents: this.contents, file: this.file};
283 this.bundler.emit('fileUpdate', event);
284 this.contents = event.contents;
285 this.dependencies = this.bundler.loadDependencies(this.contents, this.file, this.directory);
286 this.bundler.needsRebundle();
287 }
288 } catch (e) {
289 e.message = this.file + '\n' + (e.message || '');
290 throw e;
291 }
292};
293
294function calculateHash(contents) {
295 return crypto.createHash('sha512').update(contents).digest('hex');
296}