UNPKG

10.2 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 // loop until we find a string
148 while (next.type !== 'String' && next.value !== ')' && i < length - 3) {
149 next = tokens[++i];
150 }
151 i -= 2;
152 }
153 next = tokens[i + 2];
154 if (next.type !== 'String') {
155 continue;
156 }
157 var value = next.value.replace(/['"]+/g, '');
158 if (resolve.isCore(value)) {
159 if (this.options.web) {
160 // use special modules if available
161 var specialModule = core[value];
162 if (specialModule.length === 0) {
163 throw new Error('There is no web version of the core module ' + value + '!');
164 }
165 var module = resolve.sync(specialModule, {basedir: __dirname});
166 if (!this.files[module]) {
167 this.reloadFile(module, path.dirname(module));
168 }
169 dependencies.push({module: module, value: value});
170 } else {
171 dependencies.push(value); // regular node importing
172 }
173 } else {
174 try {
175 var module;
176 if (this.options.web) {
177 module = browserResolve.sync(value, {filename: file, extensions: this.options.extensions});
178 } else {
179 module = resolve.sync(value, {basedir: directory || this.options.directory, extensions: this.options.extensions});
180 }
181 if (!this.files[module]) {
182 this.reloadFile(module, path.dirname(module));
183 }
184 dependencies.push({module: module, value: value});
185 } catch (e) {
186 console.error('Failed to load module', value, e.message);
187 }
188 }
189 }
190 }
191 return dependencies;
192};
193
194Bundler.prototype.needsRebundle = function () {
195 if (this._bundling) {
196 return;
197 }
198 this._bundling = true;
199 setTimeout(() => {
200 this._bundling = false;
201 this.bundle();
202 }, this.options.wait); // allow other processes to run and cancel
203};
204
205Bundler.prototype.bundle = function () {
206 var start = Date.now();
207 this.emit('bundleStart', start);
208 var array = [];
209 var i = 0;
210 var reverseIds = {};
211 // first loop, assign ids
212 for (var property in this.files) {
213 if (this.files.hasOwnProperty(property)) {
214 if (reverseIds[property]) {
215 continue; // already bundled
216 }
217 reverseIds[property] = i++;
218 }
219 }
220 // second loop, build code
221 for (var property in this.files) {
222 if (this.files.hasOwnProperty(property)) {
223 // todo check if previously bundled
224 var bundled = this.files[property];
225 var dependencies = {};
226
227 for (var j = 0, length = bundled.dependencies.length; j < length; j++) {
228 var dependency = bundled.dependencies[j];
229 if (typeof dependency !== 'string') {
230 dependencies[dependency.value] = reverseIds[dependency.module];
231 }
232 }
233
234 var event = {
235 file: property,
236 content: bundled.contents
237 };
238 if (bundled.changed) {
239 this.emit('bundleFile', event);
240 bundled.changed = false;
241 }
242 if (path.extname(event.file) === '.json') {
243 array.push(reverseIds[bundled.file] + `: [function (exports, require, module, global) {module.exports = ${event.content}}, ${JSON.stringify(dependencies)}]`);
244 } else {
245 array.push(reverseIds[bundled.file] + `: [function (exports, require, module, global) {${event.content}}, ${JSON.stringify(dependencies)}]`);
246 }
247 }
248 }
249 var header = mainHeader;
250 header = header.replace('/*main*/', this.main);
251 header = header.replace('/*core*/', this.options.web ? '' : 'var core = ' + JSON.stringify(Object.keys(core)) + ';');
252
253 var text = '(' + header + ')({' + array.join(',') + '}, this);';
254 if (this.options.strict) {
255 text = '\'use strict\';\n' + text;
256 }
257
258 var event = {content: text, time: Date.now() - start, files: array.length};
259 this.emit('bundle', event);
260 if (typeof this.options.out === 'string') {
261 fs.writeFileSync(this.options.out, event.content);
262 } else if (this.options.out) {
263 this.options.out(event.content);
264 } else {
265 process.stdout.write(event.content);
266 }
267};
268
269module.exports = Bundler;
270
271function BundledFile(file, directory, bundler) {
272 this.file = file;
273 this.directory = directory;
274 this.bundler = bundler;
275 this.changed = false;
276}
277
278BundledFile.prototype.checkContents = function () {
279 try {
280 var contents = fs.readFileSync(this.file);
281 var newHash;
282 if (!this.contents || this.contents.length != contents.length || this.hash != (newHash = calculateHash(contents))) {
283 this.hash = newHash;
284 this.contents = contents;
285 this.changed = true;
286 var event = {contents: this.contents, file: this.file};
287 this.bundler.emit('fileUpdate', event);
288 this.contents = event.contents;
289 this.dependencies = this.bundler.loadDependencies(this.contents, this.file, this.directory);
290 this.bundler.needsRebundle();
291 }
292 } catch (e) {
293 e.message = this.file + '\n' + (e.message || '');
294 throw e;
295 }
296};
297
298function calculateHash(contents) {
299 return crypto.createHash('sha512').update(contents).digest('hex');
300}