source: Dev/trunk/src/client/util/build/transforms/depsScan.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 20.0 KB
Line 
1define([
2        "require",
3        "../buildControl",
4        "../fileUtils",
5        "../removeComments",
6        "dojo/json",
7        "dojo/_base/lang",
8        "dojo/_base/loader",
9        "../fs"
10], function(require, bc, fileUtils, removeComments, json, lang, syncLoader, fs){
11        return function(resource){
12                var
13                        newline = bc.newline,
14
15                        mix = function(dest, src){
16                                dest = dest || {};
17                                for(var p in src){
18                                        dest[p] = src[p];
19                                }
20                                return dest;
21                        },
22
23                        absMid = 0,
24
25                        aggregateDeps = [],
26
27                        defineApplied = 0,
28
29                        simulatedDefine = function(mid, dependencies, factory){
30                                defineApplied = 1;
31                                var arity = arguments.length,
32                                        args = 0,
33                                        defaultDeps = ["require", "exports", "module"];
34
35                                // TODO: add the factory scan?
36                                if(bc.factoryScan && arity == 1 && typeof mid === 'function'){
37                                        dependencies = [];
38                                        mid.toString()
39                                                .replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, "")
40                                                .replace(/require\(["']([\w\!\-_\.\/]+)["']\)/g, function(match, dep){
41                                                        dependencies.push(dep);
42                                                });
43                                        args = [0, defaultDeps.concat(dependencies), mid];
44                                        resource.text = resource.text.replace(/define\s*\(/, 'define(["' + args[1].join('","') + '"],');
45                                }
46
47                                if(!args){
48                                        args = arity == 1 ? [0, defaultDeps, mid] :
49                                                (arity == 2 ? (mid instanceof Array ? [0, mid, dependencies] : [mid, defaultDeps, dependencies]) :
50                                                        [mid, dependencies, factory]);
51                                }
52
53                                if(args[1].some(function(item){
54                                        return !lang.isString(item);
55                                })){
56                                        throw new Error("define dependency vector contains elements that are not of type string.");
57                                }
58
59                                absMid = args[0];
60                                aggregateDeps = aggregateDeps.concat(args[1]);
61                        },
62
63                        _tag_simulatedDefine = simulatedDefine.amd = {
64                                vendor:"dojotoolkit.org",
65                                context:"build"
66                        },
67
68                        simulatedRequire = function(depsOrConfig, callbackOrDeps){
69                                // add contents of deps vector to aggregateDeps iff it contains no relative ids; do not process deps property in config
70                                var hasRelativeIds = function(deps){ return deps.some(function(item){ return /^\./.test(item); }); };
71                                if(lang.isArray(depsOrConfig) && !hasRelativeIds(depsOrConfig)){
72                                        aggregateDeps = aggregateDeps.concat(depsOrConfig);
73                                }else if(lang.isArray(callbackOrDeps) && !hasRelativeIds(callbackOrDeps)){
74                                        aggregateDeps = aggregateDeps.concat(callbackOrDeps);
75                                }
76                        },
77
78                        slashName = function(dottedName){
79                                return dottedName.replace(/\./g, "/");
80                        },
81
82                        pluginStrategyRequired =
83                                // truthy if dojo.loadInit|require[After]If|platformRequire detected that cannot be resolved at build-time; falsy otherwise
84                                0,
85
86                        dojoProvides =
87                                // vector of modules dojo.provide'd by the resource
88                                [],
89
90                        dojoRequires =
91                                // vector of modules dojo.require'd by the resource
92                                [],
93
94                        simulatedDojo =
95                                // the dojo legacy loader API
96                                {
97                                        require:function(moduleName, omitModuleCheck){
98                                                dojoRequires.push(slashName(moduleName));
99                                        },
100                                        provide:function(moduleName){
101                                                dojoProvides.push(slashName(moduleName));
102                                        },
103                                        requireLocalization: function(moduleName, bundleName, locale){
104                                                aggregateDeps.push("dojo/i18n!" + slashName(moduleName) + "/nls/" + (!locale || /root/i.test(locale) ? "" : locale + "/") + slashName(bundleName));
105                                        },
106                                        platformRequire:function(modMap){
107                                                pluginStrategyRequired = 1;
108                                                (modMap.common || []).concat((bc.platform && modMap[bc.platform]) || []).forEach(function(item){
109                                                        dojoRequires.push(lang.isArray(item) ? slashName(item[0]) : slashName(item));
110                                                });
111                                        },
112                                        loadInit:function(callback){
113                                                pluginStrategyRequired = 1;
114                                                callback();
115                                        },
116                                        requireIf:function(expr, moduleName, omitModuleCheck){
117                                                pluginStrategyRequired = 1;
118                                                expr && dojoRequires.push(slashName(moduleName));
119                                        },
120                                        requireAfterIf:function(expr, moduleName, omitModuleCheck){
121                                                pluginStrategyRequired = 1;
122                                                expr && dojoRequires.push(slashName(moduleName));
123                                        }
124                                },
125
126                        evaluatorWithNoRuntime =
127                                new Function("dojo", "__text", "eval(__text);"),
128
129                        applyLegacyCalls = function(callList){
130                                var evaluator;
131                                if(resource.pack.runtime){
132                                        // if a runtime is provided, then a special evaluator has to be constructed
133                                        var runtime = resource.pack.runtime,
134                                                args = [],
135                                                params = [],
136                                                p;
137                                        runtime.dojo = mix(runtime.dojo, simulatedDojo);
138                                        for(p in runtime){
139                                                args.push(runtime[p]);
140                                                params.push(p);
141                                        }
142                                        evaluator = new Function("__bc", "__args", "__text", "(function(" + params.join(",") + "){ eval(__text); }).apply(__bc, __args);");
143                                        args = [bc, args];
144                                }else{
145                                        args = [simulatedDojo];
146                                        evaluator = evaluatorWithNoRuntime;
147                                }
148
149                                // apply the legacy API calls
150                                var results = callList.map(function(application){
151                                        try{
152                                                evaluator.apply(bc, args.concat(application));
153                                                return 0;
154                                        }catch(e){
155                                                pluginStrategyRequired = 1;
156                                                return [e, application];
157                                        }
158                                });
159
160                                // report the results
161                                results.forEach(function(item){
162                                        if(item){
163                                                bc.log("legacyFailedEval", ["module", resource.mid, "text", item[0], "error", item[1]]);
164                                        }
165                                });
166                        },
167
168                        tagAbsMid = function(absMid){
169                                if(absMid && absMid!=resource.mid){
170                                        bc.log("amdInconsistentMid", ["module", resource.mid, "specified", absMid]);
171                                }
172                                if(absMid){
173                                        resource.tag.hasAbsMid = 1;
174                                }
175                        },
176
177                        processPureAmdModule = function(){
178                                // find the dependencies for this resource using the fast path if the module says it's OK
179                                // pure AMD says the module can be executed in the build environment
180                                // note: the user can provide a build environment with TODO
181                                try{
182                                        if(resource.mid!="dojo/_base/loader" && /dojo\.(require|provide)\s*\(/.test(removeComments(resource.text))){
183                                                bc.log("amdPureContainedLegacyApi", ["module", resource.mid]);
184                                        }
185                                        (new Function("define", "require", resource.text))(simulatedDefine, simulatedRequire);
186                                        tagAbsMid(absMid);
187                                }catch (e){
188                                        bc.log("amdFailedEval", ["module", resource.mid, "error", e]);
189                                }
190                        },
191
192                        convertToStrings = function(text){
193                                var strings = [],
194
195                                        // a DFA, the states...
196                                        spaces = "spaces",
197                                        string = "string",
198                                        endOfString = "endOfString",
199                                        done = "done",
200                                        error = "error",
201
202                                        // the machine...
203                                        dfa = {
204                                                spaces:function(c){
205                                                        if(/\s/.test(c)){
206                                                                return spaces;
207                                                        }
208                                                        if(c=="'" || c=='"'){
209                                                                quoteType = c;
210                                                                current = "";
211                                                                return string;
212                                                        }
213                                                        if(c==0){
214                                                                return done;
215                                                        }
216                                                        return error;
217                                                },
218                                                string:function(c){
219                                                        if(c==quoteType){
220                                                                strings.push(current);
221                                                                return "endOfString";
222                                                        }else{
223                                                                current+= c;
224                                                                return "string";
225                                                        }
226                                                },
227                                                endOfString:function(c){
228                                                        if(/\s/.test(c)){
229                                                                return endOfString;
230                                                        }
231                                                        if(c==0){
232                                                                return done;
233                                                        }
234                                                        if(c==","){
235                                                                return spaces;
236                                                        }
237                                                        return error;
238                                                }
239                                        },
240
241                                        state = spaces,
242
243                                        quoteType, current;
244
245
246                                for(var i = 0; i<text.length; i++){
247                                        state = dfa[state](text.charAt(i));
248                                        if(state==error){
249                                                return 0;
250                                        }
251                                }
252                                if(dfa[state](0)!=error){
253                                        return strings;
254                                }
255                                return 0;
256                        },
257
258                        processPossibleAmdWithRegExs = function(text){
259                                // look for AMD define and/or require; require must not have relative mids; require signature with config argument is not discovered
260                                // (remember, a config could have a string or regex that could have an unmatched right "}", so there is not way to guarantee we can find the correct
261                                // end of the config arg without parsing)
262
263                                var amdCallCount =
264                                                // the number of AMD applications found
265                                                0,
266
267                                        defineExp=
268                                                // look for define applications with an optional string first arg and an optional array second arg;
269                                                // notice the regex stops after the second arg
270                                                // a test run in the console
271                                                // test = [
272                                                //      'define("test")',
273                                                //      'define("test", ["test1"])',
274                                                //      'define("test", ["test1", "test2"])',
275                                                //      'define(["test1"])',
276                                                //      'define(["test1", "test2"])',
277                                                //      'define("test", ["test1"], function(test){ hello;})',
278                                                //      'define("test", function(test){ hello;})',
279                                                //      'define(["test1"], function(test){ hello;})',
280                                                //      'define(function(test){ hello;})',
281                                                //      'define({a:1})'
282                                                // ]
283                                                //                                        2                                       3              4                                5
284                                                /(^|\s)define\s*\(\s*(["'][^'"]+['"])?\s*(,)?\s*(\[[^\]]*?\])?\s*(,)?/g,
285
286                                        result;
287                                while((result = defineExp.exec(text)) != null){
288                                        try{
289                                                if(result[2]){
290                                                        // first arg a string
291                                                        if(result[3]){
292                                                                // first arg a module id
293                                                                if(result[5]){
294                                                                        // (mid, deps, <factory>)
295                                                                        result = result[0] + "{})";
296                                                                }else if(result[4]){
297                                                                        // (mid, <factory:array value>)
298                                                                        result = result[0] + ")";
299                                                                }else{
300                                                                        // (mid, <factory>)
301                                                                        result = result[0] + "{})";
302                                                                }
303                                                        }else{
304                                                                // (<factory:string-value>)
305                                                                result = result[0]      + ")";
306                                                        }
307                                                }else if(result[4]){
308                                                        // first arg an array
309                                                        if(result[5]){
310                                                                // (deps, <factory>)
311                                                                result = result[0] + "{})";
312                                                        }else{
313                                                                // (<factory:array-value>)
314                                                                result = result[0] + ")";
315                                                        }
316                                                }else{
317                                                        //just a factory
318                                                        result = "define({})";
319                                                }
320                                                amdCallCount++;
321                                                (new Function("define", result))(simulatedDefine);
322                                                tagAbsMid(absMid);
323                                        }catch(e){
324                                                amdCallCount--;
325                                                bc.log("amdFailedDefineEval", ["module", resource.mid, "text", result, "error", e]);
326                                        }
327                                }
328
329                                var requireExp=
330                                                // look for require applications with an array for the first arg; notice the regex stops after the first arg and config signature is not processed
331                                                /(^|\s)require\s*\(\s*\[([^\]]*?)\]/g;
332                                while((result = requireExp.exec(text)) != null){
333                                        var mids = convertToStrings(result[2]);
334                                        if(mids){
335                                                amdCallCount++;
336                                                aggregateDeps = aggregateDeps.concat(mids.filter(function(item){return item.charAt(0)!=".";}));
337                                        }
338                                }
339                                return amdCallCount;
340                        },
341
342                        evalNlsResource = function(resource){
343                                var bundleValue = 0;
344                                try{
345                                        function simulatedDefine(a1, a2){
346                                                if(lang.isString(a1) && lang.isObject(a2)){
347                                                        tagAbsMid(a1);
348                                                        bundleValue = a2;
349                                                }else if(lang.isObject(a1)){
350                                                        bundleValue = a1;
351                                                }
352                                        }
353                                        (new Function("define", resource.text))(simulatedDefine);
354                                        if(bundleValue){
355                                                resource.bundleValue = bundleValue;
356                                                resource.bundleType = "amd";
357                                                return;
358                                        }
359                                }catch(e){
360                                        // TODO: consider a profile flag to cause errors to be logged
361                                }
362                                try{
363                                        bundleValue = (new Function("return " + resource.text + ";"))();
364                                        if(lang.isObject(bundleValue)){
365                                                resource.bundleValue = bundleValue;
366                                                resource.bundleType = "legacy";
367                                                return;
368                                        }
369                                }catch(e){
370                                        // TODO: consider a profile flag to cause errors to be logged
371                                }
372
373                                // if not building flattened layer bundles, then it's not necessary for the bundle
374                                // to be evaluable; still run processPureAmdModule to compute possible dependencies
375                                processPureAmdModule();
376                                if(!defineApplied){
377                                        bc.log("i18nImproperBundle", ["module", resource.mid]);
378                                }
379                        },
380
381                        processNlsBundle = function(){
382                                // either a v1.x sync bundle or an AMD NLS bundle
383
384                                // compute and remember the set of localized bundles; attach this info to the root bundle
385                                var match = resource.mid.match(/(^.*\/nls\/)(([^\/]+)\/)?([^\/]+)$/),
386                                        prefix = resource.prefix = match[1],
387                                        locale = resource.locale = match[3],
388                                        bundle = resource.bundle = match[4],
389                                        rootPath = prefix + bundle,
390                                        rootBundle = bc.amdResources[rootPath];
391
392                                // if not root, don't process any localized bundles; a missing root bundle serves as a signal
393                                // to other transforms (e.g., writeAmd) to ignore this bundle family
394                                if(!rootBundle){
395                                        bc.log("i18nNoRoot", ["bundle", resource.mid]);
396                                        return;
397                                }
398                                // accumulate all the localized versions in the root bundle
399                                if(!rootBundle.localizedSet){
400                                        rootBundle.localizedSet = {};
401                                }
402
403                                // try to compute the value of the bundle; sets properties bundleValue and bundleType
404                                evalNlsResource(resource);
405
406                                if((bc.localeList || resource.bundleType=="legacy") && !resource.bundleValue){
407                                        // profile is building flattened layer bundles or converting a legacy-style bundle
408                                        // to an AMD-style bundle; either way, we need the value of the bundle
409                                        bc.log("i18nUnevaluableBundle", ["module", resource.mid]);
410                                }
411
412                                if(resource.bundleType=="legacy" && resource===rootBundle && resource.bundleValue){
413                                        resource.bundleValue = {root:resource.bundleValue};
414                                }
415
416                                if(resource!==rootBundle){
417                                        rootBundle.localizedSet[locale] = resource;
418                                }
419                        },
420
421                        interningDojoUriRegExpString =
422                                // the following is a direct copy from the v1.6- build util; this is so janky, we dare not touch
423                                //23                                                             4                                5                                                                                                             6                                          78                                   9                                                 0                       1
424                                "(((templatePath|templateCssPath)\\s*(=|:)\\s*)dojo\\.(module)?Url\\(|dojo\\.cache\\s*\\(\\s*)\\s*?[\\\"\\']([\\w\\.\\/]+)[\\\"\\'](([\\,\\s]*)[\\\"\\']([\\w\\.\\/-]*)[\\\"\\'])?(\\s*,\\s*)?([^\\)]*)?\\s*\\)",
425
426                        interningGlobalDojoUriRegExp = new RegExp(interningDojoUriRegExpString, "g"),
427
428                        interningLocalDojoUriRegExp = new RegExp(interningDojoUriRegExpString),
429
430                        internStrings = function() {
431                                var getText = function(src){
432                                                return fs.readFileSync(src, "utf8");
433                                        },
434                                        skipping = [],
435                                        notFound = [],
436                                        nothing = [];
437
438                                resource.text = resource.text.replace(interningGlobalDojoUriRegExp, function(matchString){
439
440                                        var parts = matchString.match(interningLocalDojoUriRegExp);
441
442                                        var textModuleInfo = bc.getSrcModuleInfo(fileUtils.catPath(parts[6].replace(/\./g, "/"), parts[9]), 0, true);
443                                        if(bc.internSkip(textModuleInfo.mid, resource)){
444                                                return matchString;
445                                        }
446
447                                        var textModule = bc.resources[textModuleInfo.url];
448                                        if(!textModule){
449                                                notFound.push(textModuleInfo.url);
450                                                return matchString;
451                                        }
452
453                                        // note: it's possible the module is being processed by a set of transforms that don't add a
454                                        // getText method (e.g., copy); therefore, we provide one for these cases
455                                        var text = (textModule.getText && textModule.getText()) || getText(textModule.src);
456                                        if(!text){
457                                                nothing.push(textModule.src);
458                                                return matchString;
459                                        }
460
461                                        text = json.stringify(text);
462
463                                        if(matchString.indexOf("dojo.cache") != -1){
464                                                //Handle dojo.cache-related interning.
465                                                var endContent = parts[11];
466                                                if(!endContent){
467                                                        endContent = text;
468                                                }else{
469                                                        var braceIndex = endContent.indexOf("{");
470                                                        if(braceIndex != -1){
471                                                                endContent = endContent.substring(0, braceIndex + 1)
472                                                                        + 'value: ' + text + ','
473                                                                        + endContent.substring(braceIndex + 1, endContent.length);
474                                                        }
475                                                }
476                                                return 'dojo.cache("' + parts[6] + '", "' + parts[9] + '", ' + endContent + ')';
477                                        }else if(parts[3] == "templatePath"){
478                                                //Replace templatePaths
479                                                return "templateString" + parts[4] + text;
480                                        }else{
481                                                //Dealing with templateCssPath; not doing this anymore
482                                                return matchString;
483                                        }
484                                });
485                                if(skipping.length || notFound.length || nothing.length){
486                                        var logArgs = ["module", resource.mid];
487                                        if(skipping.length){
488                                                logArgs.push("skipping", skipping);
489                                        }
490                                        if(notFound.length){
491                                                logArgs.push("not found", notFound);
492                                        }
493                                        if(nothing.length){
494                                                logArgs.push("nothing to intern", nothing);
495                                        }
496                                        bc.log("internStrings", logArgs);
497                                }
498                        },
499
500                        processWithRegExs = function(){
501                                // try to figure out if the module is legacy or AMD and then process the loader applications found
502                                //
503                                // Warning: the process is flawed because regexs will find things that are not there and miss things that are,
504                                // and there is no way around this without a proper parser.      Note however, this kind of process has been in use
505                                // with the v1.x build system from the beginning.
506                                //
507                                // TODO: replace this process with a parser
508                                //
509                                // do it the unreliable way; first try to find "dojo.provide" et al since those names are less likely
510                                // to be overloaded than "define" and "require"
511                                if(bc.internStrings){
512                                        internStrings();
513                                }
514
515                                var text =
516                                                // apply any replacements before processing
517                                                resource.getText(),
518
519                                        names =
520                                                bc.scopeNames,
521
522                                        extractResult =
523                                                // a vector of legacy loader API applications as pairs of [function-name, complete-function-application-text] + the following two properties
524                                                //       * text: the original text with all dojo.loadInit applications preceeded by 0 &&, thereby causing those applications to be discarded by the minifier
525                                                //       * extractText: all legacy loader applications, with all dojo.loadInit applications moved to the beginning
526                                                // See dojo.js
527                                                syncLoader.extractLegacyApiApplications(text, removeComments(text));
528                                if(!extractResult.extractText && processPossibleAmdWithRegExs(removeComments(text))){
529                                        // zero legacy calls detected *and* at least one AMD call detected; therefore, assume it's AMD
530                                        bc.log("amdNotPureContainedNoLegacyApi", ["module", resource.mid]);
531                                        return;
532                                }
533                                bc.log("legacyAssumed", ["module", resource.mid]);
534
535                                if(!extractResult){
536                                        // no legacy API calls to worry about; therefore...
537                                        resource.getText = function(){ return "define(" + json.stringify(names) + ", function(" + names.join(",") + "){" + newline + text + "});" + newline; };
538                                        return;
539                                }
540                                // apply the legacy calls in a special environment
541                                applyLegacyCalls(extractResult[2]);
542
543                                // check for multiple or irrational dojo.provides
544                                if(dojoProvides.length){
545                                        if(dojoProvides.length>1){
546                                                bc.log("legacyMultipleProvides", ["module", resource.mid, "provides", dojoProvides]);
547                                        }
548                                        dojoProvides.forEach(function(item){
549                                                if(item.replace(/\./g, "/")!=resource.mid){
550                                                        bc.log("legacyImproperProvide", ["module", resource.mid, "provide", item]);
551                                                }
552                                        });
553                                }
554
555                                if(pluginStrategyRequired){
556                                        // some loadInit and/or require[After]If and/or platformRequire applications that could not be resolved at build time
557                                        bc.log("legacyUsingLoadInitPlug", ["module", resource.mid]);
558
559                                        // construct and start the synthetic plugin resource
560                                        var pluginText, mid, pluginResource, pluginResourceId;
561                                        pluginText =
562                                                "// generated by build app" + newline +
563                                                "define([], {" + newline +
564                                                "\tnames:" + json.stringify(names) + "," + newline +
565                                                "\tdef:function(" + names.join(",") + "){" + newline + extractResult[1] + "}" + newline +
566                                                "});" + newline;
567                                        mid = resource.mid + "-loadInit";
568                                        pluginResource = mix(mix({}, resource), {
569                                                src:resource.src.substring(0, resource.src.length-3) + "-loadInit.js",
570                                                dest:bc.getDestModuleInfo(mid).url,
571                                                mid:mid,
572                                                tag:{loadInitResource:1},
573                                                deps:[],
574                                                getText:function(){ return pluginText; }
575                                        });
576                                        bc.start(pluginResource);
577
578                                        pluginResourceId = "dojo/loadInit!" + mid;
579                                        aggregateDeps.push(pluginResourceId);
580                                }else if(dojoRequires.length){
581                                        aggregateDeps.push("dojo/require!" + dojoRequires.join(","));
582                                }
583                                aggregateDeps = names.concat(aggregateDeps);
584                                // need to use extractResult[0] since it may delete the dojo.loadInit applications
585                                resource.getText = function(){ return "// wrapped by build app" + newline + "define(" + json.stringify(aggregateDeps) + ", function(" + names.join(",") + "){" + newline + extractResult[0] + newline + "});" + newline; };
586                        };
587
588                // scan the resource for dependencies
589                if(resource.tag.nls){
590                        processNlsBundle();
591                }else if(resource.tag.amd || /\/\/>>\s*pure-amd/.test(resource.text)){
592                        processPureAmdModule();
593                }else{
594                        processWithRegExs();
595                }
596
597                // resolve the dependencies into modules
598                var deps = resource.deps;
599                resource.aggregateDeps = aggregateDeps;
600                aggregateDeps.forEach(function(dep){
601                        if(!(/^(require|exports|module)$/.test(dep))){
602                                try{
603                                        var module = bc.getAmdModule(dep, resource);
604                                        if(lang.isArray(module)){
605                                                module.forEach(function(module){ deps.push(module); });
606                                        }else if(module){
607                                                deps.push(module);
608                                        }else{
609                                                bc.log("amdMissingDependency", ["module", resource.mid, "dependency", dep]);
610                                        }
611                                }catch(e){
612                                        bc.log("amdMissingDependency", ["module", resource.mid, "dependency", dep, "error", e]);
613                                }
614                        }
615                });
616
617        };
618});
Note: See TracBrowser for help on using the repository browser.