PageRenderTime 52ms CodeModel.GetById 15ms app.highlight 29ms RepoModel.GetById 2ms app.codeStats 0ms

/app/scripts/vendor/hm.js

https://bitbucket.org/ceoaliongroo/torredelprior
JavaScript | 459 lines | 439 code | 7 blank | 13 comment | 7 complexity | 1e89fc681249dae1c7c138c9b1931aa3 MD5 | raw file
  1/**
  2 * @license hm 0.2.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
  3 * Available via the MIT or new BSD license.
  4 * see: http://github.com/jrburke/require-hm for details
  5 */
  6
  7/*jslint plusplus: true, regexp: true */
  8/*global require, XMLHttpRequest, ActiveXObject, define, process, window,
  9console */
 10
 11define(['esprima', 'module'], function (esprima, module) {
 12    'use strict';
 13
 14    var fs, getXhr,
 15        progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
 16
 17        exportRegExp = /export\s+([A-Za-z\d\_]+)(\s+([A-Za-z\d\_]+))?/g,
 18        commentRegExp = /(\/\*([\s\S]*?)\*\/|[^\:]\/\/(.*)$)/mg,
 19        importModuleRegExp = /module|import/g,
 20        commaRegExp = /\,\s*$/,
 21        spaceRegExp = /\s+/,
 22        quoteRegExp = /['"]/,
 23        endingPuncRegExp = /[\,\;]\s*$/,
 24        moduleNameRegExp = /['"]([^'"]+)['"]/,
 25        startQuoteRegExp = /^['"]/,
 26        braceRegExp = /[\{\}]/g,
 27        buildMap = {},
 28
 29        fetchText = function () {
 30            throw new Error('Environment unsupported.');
 31        };
 32
 33    if (typeof window !== "undefined" && window.navigator && window.document) {
 34        // Browser action
 35        getXhr = function () {
 36            //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
 37            var xhr, i, progId;
 38            if (typeof XMLHttpRequest !== "undefined") {
 39                return new XMLHttpRequest();
 40            } else {
 41                for (i = 0; i < 3; i++) {
 42                    progId = progIds[i];
 43                    try {
 44                        xhr = new ActiveXObject(progId);
 45                    } catch (e) {}
 46
 47                    if (xhr) {
 48                        progIds = [progId];  // so faster next time
 49                        break;
 50                    }
 51                }
 52            }
 53
 54            if (!xhr) {
 55                throw new Error("getXhr(): XMLHttpRequest not available");
 56            }
 57
 58            return xhr;
 59        };
 60
 61        fetchText = function (url, callback) {
 62            var xhr = getXhr();
 63            xhr.open('GET', url, true);
 64            xhr.onreadystatechange = function (evt) {
 65                //Do not explicitly handle errors, those should be
 66                //visible via console output in the browser.
 67                if (xhr.readyState === 4) {
 68                    callback(xhr.responseText);
 69                }
 70            };
 71            xhr.send(null);
 72        };
 73    } else if (typeof process !== "undefined" &&
 74               process.versions &&
 75               !!process.versions.node) {
 76        //Using special require.nodeRequire, something added by r.js.
 77        fs = require.nodeRequire('fs');
 78        fetchText = function (path, callback) {
 79            callback(fs.readFileSync(path, 'utf8'));
 80        };
 81    }
 82
 83
 84    /**
 85     * Helper function for iterating over an array. If the func returns
 86     * a true value, it will break out of the loop.
 87     */
 88    function each(ary, func) {
 89        if (ary) {
 90            var i;
 91            for (i = 0; i < ary.length; i += 1) {
 92                if (ary[i] && func(ary[i], i, ary)) {
 93                    break;
 94                }
 95            }
 96        }
 97    }
 98
 99    /**
100     * Cycles over properties in an object and calls a function for each
101     * property value. If the function returns a truthy value, then the
102     * iteration is stopped.
103     */
104    function eachProp(obj, func) {
105        var prop;
106        for (prop in obj) {
107            if (obj.hasOwnProperty(prop)) {
108                if (func(obj[prop], prop)) {
109                    break;
110                }
111            }
112        }
113    }
114
115    /**
116     * Inserts the hm! loader plugin prefix if necessary. If
117     * there is already a ! in the string, then leave it be, and if it
118     * starts with a ! it means "use normal AMD loading for this dependency".
119     *
120     * @param {String} id
121     * @returns id
122     */
123    function cleanModuleId(id) {
124        id = moduleNameRegExp.exec(id)[1];
125        var index = id.indexOf('!');
126
127        if (index === -1) {
128            // Needs the hm prefix.
129            id = 'hm!' + id;
130        } else if (index === 0) {
131            //Normal AMD loading, strip off the ! sign.
132            id = id.substring(1);
133        }
134
135        return id;
136    }
137
138    function convertImportSyntax(tokens, start, end, moduleTarget) {
139        var token = tokens[start],
140            cursor = start,
141            replacement = '',
142            localVars = {},
143            moduleRef,
144            moduleId,
145            star,
146            currentVar;
147
148        //Convert module target to an AMD usable name. If a string,
149        //then needs to be accessed via require()
150        if (startQuoteRegExp.test(moduleTarget)) {
151            moduleId = cleanModuleId(moduleTarget);
152            moduleRef = 'require("' + moduleId + '")';
153        } else {
154            moduleRef = moduleTarget;
155        }
156
157        if (token.type === 'Punctuator' && token.value === '*') {
158            //import * from z
159            //If not using a module ID that is a require call, then
160            //discard it.
161            if (moduleId) {
162                star = moduleId;
163                replacement = '/*IMPORTSTAR:' + star + '*/\n';
164            } else {
165                throw new Error('import * on local reference ' + moduleTarget +
166                                ' no supported.');
167            }
168        } else if (token.type === 'Identifier') {
169            //import y from z
170            replacement += 'var ' + token.value + ' = ' +
171                            moduleRef + '.' + token.value + ';';
172        } else if (token.type === 'Punctuator' && token.value === '{') {
173            //import {y} from z
174            //import {x, y} from z
175            //import {x: localX, y: localY} from z
176            cursor += 1;
177            token = tokens[cursor];
178            while (cursor !== end && token.value !== '}') {
179                if (token.type === 'Identifier') {
180                    if (currentVar) {
181                        localVars[currentVar] = token.value;
182                        currentVar = null;
183                    } else {
184                        currentVar = token.value;
185                    }
186                } else if (token.type === 'Punctuator') {
187                    if (token.value === ',') {
188                        if (currentVar) {
189                            localVars[currentVar] = currentVar;
190                            currentVar = null;
191                        }
192                    }
193                }
194                cursor += 1;
195                token = tokens[cursor];
196            }
197            if (currentVar) {
198                localVars[currentVar] = currentVar;
199            }
200
201            //Now serialize the localVars
202            eachProp(localVars, function (localName, importProp) {
203                replacement += 'var ' + localName + ' = ' +
204                                moduleRef + '.' + importProp + ';\n';
205            });
206        } else {
207            throw new Error('Invalid import: import ' +
208                token.value + ' ' + tokens[start + 1].value +
209                ' ' + tokens[start + 2].value);
210        }
211
212        return {
213            star: star,
214            replacement: replacement
215        };
216    }
217
218    function convertModuleSyntax(tokens, i) {
219        //Converts `foo = 'bar'` to `foo = require('bar')`
220        var varName = tokens[i],
221            eq = tokens[i + 1],
222            id = tokens[i + 2];
223
224        if (varName.type === 'Identifier' &&
225                eq.type === 'Punctuator' && eq.value === '=' &&
226                id.type === 'String') {
227            return varName.value + ' = require("' + cleanModuleId(id.value) + '")';
228        } else {
229            throw new Error('Invalid module reference: module ' +
230                varName.value + ' ' + eq.value + ' ' + id.value);
231        }
232    }
233
234    function compile(path, text) {
235        var stars = [],
236            moduleMap = {},
237            transforms = {},
238            targets = [],
239            currentIndex = 0,
240            //Remove comments from the text to be scanned
241            scanText = text.replace(commentRegExp, ""),
242            transformedText = text,
243            transformInputText,
244            startIndex,
245            segmentIndex,
246            match,
247            tempText,
248            transformed,
249            tokens;
250
251        try {
252            tokens = esprima.parse(text, {
253                tokens: true,
254                range: true
255            }).tokens;
256        } catch (e) {
257            throw new Error('Esprima cannot parse: ' + path + ': ' + e);
258        }
259
260        each(tokens, function (token, i) {
261            if (token.type !== 'Keyword' && token.type !== 'Identifier') {
262                //Not relevant, skip
263                return;
264            }
265
266            var next = tokens[i + 1],
267                next2 = tokens[i + 2],
268                next3 = tokens[i + 3],
269                cursor = i,
270                replacement,
271                moduleTarget,
272                target,
273                convertedImport;
274
275            if (token.value === 'export') {
276                // EXPORTS
277                if (next.type === 'Keyword') {
278                    if (next.value === 'var' || next.value === 'let') {
279                        targets.push({
280                            start: token.range[0],
281                            end: next2.range[0],
282                            replacement: 'exports.'
283                        });
284                    } else if (next.value === 'function' && next2.type === 'Identifier') {
285                        targets.push({
286                            start: token.range[0],
287                            end: next2.range[1],
288                            replacement: 'exports.' + next2.value +
289                                         ' = function '
290                        });
291                    } else {
292                        throw new Error('Invalid export: ' + token.value +
293                                        ' ' + next.value + ' ' + tokens[i + 2]);
294                    }
295                } else if (next.type === 'Identifier') {
296                    targets.push({
297                        start: token.range[0],
298                        end: next.range[1],
299                        replacement: 'exports.' + next.value +
300                                     ' = ' + next.value
301                    });
302                } else {
303                    throw new Error('Invalid export: ' + token.value +
304                                        ' ' + next.value + ' ' + tokens[i + 2]);
305                }
306            } else if (token.value === 'module') {
307                // MODULE
308                // module Bar = "bar.js";
309                replacement = 'var ';
310                target = {
311                    start: token.range[0]
312                };
313
314                while (token.value === 'module' || (token.type === 'Punctuator'
315                        && token.value === ',')) {
316                    cursor = cursor + 1;
317                    replacement += convertModuleSyntax(tokens, cursor);
318                    token = tokens[cursor + 3];
319                    //Current module spec does not allow for
320                    //module a = 'a', b = 'b';
321                    //must end in semicolon. But keep this in case for later,
322                    //as comma separators would be nice.
323                    //esprima will throw if comma is not allowed.
324                    if ((token.type === 'Punctuator' && token.value === ',')) {
325                        replacement += ',\n';
326                    }
327                }
328
329                target.end = token.range[0];
330                target.replacement = replacement;
331                targets.push(target);
332            } else if (token.value === 'import') {
333                // IMPORT
334                //import * from z;
335                //import y from z;
336                //import {y} from z;
337                //import {x, y} from z;
338                //import {x: localX, y: localY} from z;
339                cursor = i;
340                //Find the "from" in the stream
341                while (tokens[cursor] &&
342                        (tokens[cursor].type !== 'Identifier' ||
343                        tokens[cursor].value !== 'from')) {
344                    cursor += 1;
345                }
346
347                //Increase cursor one more value to find the module target
348                moduleTarget = tokens[cursor + 1].value;
349                convertedImport = convertImportSyntax(tokens, i + 1, cursor - 1, moduleTarget);
350                replacement = convertedImport.replacement;
351                if (convertedImport.star) {
352                    stars.push(convertedImport.star);
353                }
354
355                targets.push({
356                    start: token.range[0],
357                    end: tokens[cursor + 3].range[0],
358                    replacement: replacement
359                });
360            }
361        });
362
363        //Now sort all the targets, but by start position, with the
364        //furthest start position first, since we need to transpile
365        //in reverse order.
366        targets.sort(function (a, b) {
367            return a.start > b.start ? -1 : 1;
368        });
369
370        //Now walk backwards through targets and do source modifications
371        //to AMD. Going backwards is important since the modifications will
372        //modify the length of the string.
373        each(targets, function (target, i) {
374            transformedText = transformedText.substring(0, target.start) +
375                              target.replacement +
376                              transformedText.substring(target.end, transformedText.length);
377        });
378
379        return {
380            text: "define(function (require, exports, module) {\n" +
381                  transformedText +
382                  '\n});',
383            stars: stars
384        };
385    }
386
387    function finishLoad(require, load, name, transformedText, text, isBuild) {
388        //Hold on to the transformed text if a build.
389        if (isBuild) {
390            buildMap[name] = transformedText;
391        }
392
393        load.fromText(name, transformedText);
394
395        if (module.config().logTransform) {
396            console.log("INPUT:\n" + text + "\n\nTRANSFORMED:\n" + transformedText);
397        }
398
399        //Give result to load. Need to wait until the module
400        //is fully parsed, which will happen after this
401        //execution.
402        require([name], function (value) {
403            load(value);
404        });
405    }
406
407    return {
408        version: '0.2.1',
409
410        write: function (pluginName, name, write) {
411            if (buildMap.hasOwnProperty(name)) {
412                var text = buildMap[name];
413                write.asModule(pluginName + "!" + name, text);
414            }
415        },
416
417        load: function (name, require, load, config) {
418            var path = require.toUrl(name + '.hm');
419            fetchText(path, function (text) {
420                var result = compile(path, text),
421                    transformedText = result.text;
422
423                //IE with conditional comments on cannot handle the
424                //sourceURL trick, so skip it if enabled.
425                /*@if (@_jscript) @else @*/
426                if (!config.isBuild) {
427                    transformedText += "\r\n//@ sourceURL=" + path;
428                }
429                /*@end@*/
430
431                if (result.stars && result.stars.length) {
432                    //First load any imports that require recursive analysis
433                    //TODO: this will break if there is a circular
434                    //dependency with each file doing an import * on each other.
435                    require(result.stars, function () {
436                        var i, star, mod, starText, prop;
437
438                        //Now fix up the import * items for each module.
439                        for (i = 0; i < result.stars.length; i++) {
440                            star = result.stars[i];
441                            starText = '';
442                            mod = arguments[i];
443                            for (prop in mod) {
444                                if (mod.hasOwnProperty(prop)) {
445                                    starText += 'var ' + prop + ' = require("' + star + '").' + prop + '; ';
446                                }
447                            }
448                            transformedText = transformedText.replace('/*IMPORTSTAR:' + star + '*/', starText);
449                        }
450
451                        finishLoad(require, load, name, transformedText, text, config.isBuild);
452                    });
453                } else {
454                    finishLoad(require, load, name, transformedText, text, config.isBuild);
455                }
456            });
457        }
458    };
459});