2 * @author Jacky Nguyen <jacky@sencha.com>
3 * @docauthor Jacky Nguyen <jacky@sencha.com>
7 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
8 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
9 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
11 # Asynchronous Loading #
15 + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
17 + Best possible debugging experience: error messages come with the exact file name and line number
20 + Dependencies need to be specified before-hand
22 ### Method 1: Explicitly include what you need: ###
25 Ext.require({String/Array} expressions);
27 // Example: Single alias
28 Ext.require('widget.window');
30 // Example: Single class name
31 Ext.require('Ext.window.Window');
33 // Example: Multiple aliases / class names mix
34 Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
37 Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
39 ### Method 2: Explicitly exclude what you don't need: ###
41 // Syntax: Note that it must be in this chaining format.
42 Ext.exclude({String/Array} expressions)
43 .require({String/Array} expressions);
45 // Include everything except Ext.data.*
46 Ext.exclude('Ext.data.*').require('*');
48 // Include all widgets except widget.checkbox*,
49 // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
50 Ext.exclude('widget.checkbox*').require('widget.*');
52 # Synchronous Loading on Demand #
55 + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
59 + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
60 + Must be from the same domain due to XHR restriction
61 + Need a web server, same reason as above
63 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
65 Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
67 Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
69 Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
71 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
72 existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
73 class and all its dependencies.
75 # Hybrid Loading - The Best of Both Worlds #
77 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
79 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
80 dependencies on demand as they're needed during run-time. For example: ###
82 Ext.onReady(function(){
83 var window = Ext.createWidget('window', {
90 title: 'Hello Dialog',
107 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
109 [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
111 [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
113 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
115 Ext.require('Ext.window.Window');
116 Ext.require('Ext.layout.container.Border');
120 Everything should now load via asynchronous mode.
124 It's important to note that dynamic loading should only be used during development on your local machines.
125 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
126 the whole process of transitioning from / to between development / maintenance and production as easy as
127 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
128 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
129 then include it on top of your application.
131 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
137 (function(Manager, Class, flexSetter, alias) {
141 isNonBrowser = typeof window === 'undefined',
142 isNodeJS = isNonBrowser && (typeof require === 'function'),
143 isPhantomJS = (typeof phantom !== 'undefined' && phantom.fs),
145 dependencyProperties = ['extend', 'mixins', 'requires'],
148 Loader = Ext.Loader = {
152 documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
155 * Flag indicating whether there are still files being loaded
161 * Maintain the queue for all dependencies. Each item in the array is an object of the format:
163 * requires: [...], // The required classes for this queue item
164 * callback: function() { ... } // The function to execute when all classes specified in requires exist
171 * Maintain the list of files that have already been handled so that they never get double-loaded
177 * Maintain the list of listeners to execute when all required scripts are fully loaded
183 * Contains optional dependencies to be loaded last
186 optionalRequires: [],
189 * Map of fully qualified class names to an array of dependent classes.
205 hasFileLoadError: false,
210 classNameToFilePathMap: {},
213 * An array of class names to keep track of the dependency loading order.
214 * This is not guaranteed to be the same everytime due to the asynchronous
215 * nature of the Loader.
228 * Whether or not to enable the dynamic dependency loading feature
230 * @cfg {Boolean} enabled
235 * @cfg {Boolean} disableCaching
236 * Appends current timestamp to script files to prevent caching
239 disableCaching: true,
242 * @cfg {String} disableCachingParam
243 * The get parameter name for the cache buster's timestamp.
246 disableCachingParam: '_dc',
249 * @cfg {Object} paths
250 * The mapping from namespaces to file paths
252 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
253 // loaded from ./layout/Container.js
255 'My': './src/my_own_folder' // My.layout.Container will be loaded from
256 // ./src/my_own_folder/layout/Container.js
258 * Note that all relative paths are relative to the current HTML document.
259 * If not being specified, for example, <code>Other.awesome.Class</code>
260 * will simply be loaded from <code>./Other/awesome/Class.js</code>
268 * Set the configuration for the loader. This should be called right after ext-core.js
269 * (or ext-core-debug.js) is included in the page, i.e:
271 <script type="text/javascript" src="ext-core-debug.js"></script>
272 <script type="text/javascript">
273 Ext.Loader.setConfig({
280 <script type="text/javascript">
283 Ext.onReady(function() {
284 // application code here
288 * Refer to {@link Ext.Loader#configs} for the list of possible properties
290 * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
291 * @return {Ext.Loader} this
294 setConfig: function(name, value) {
295 if (Ext.isObject(name) && arguments.length === 1) {
296 Ext.Object.merge(this.config, name);
299 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
306 * Get the config value corresponding to the specified name. If no name is given, will return the config object
307 * @param {String} name The config property name
308 * @return {Object/Mixed}
310 getConfig: function(name) {
312 return this.config[name];
319 * Sets the path of a namespace.
322 Ext.Loader.setPath('Ext', '.');
324 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
325 * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
326 * @return {Ext.Loader} this
330 setPath: flexSetter(function(name, path) {
334 path = require('fs').realpathSync(path);
338 this.config.paths[name] = path;
344 * Translates a className to a file path by adding the
345 * the proper prefix and converting the .'s to /'s. For example:
347 Ext.Loader.setPath('My', '/path/to/My');
349 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
351 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
354 'My': '/path/to/lib',
355 'My.awesome': '/other/path/for/awesome/stuff',
356 'My.awesome.more': '/more/awesome/path'
359 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
361 alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
363 alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
365 alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
367 * @param {String} className
368 * @return {String} path
371 getPath: function(className) {
373 paths = this.config.paths,
374 prefix = this.getPrefix(className);
376 if (prefix.length > 0) {
377 if (prefix === className) {
378 return paths[prefix];
381 path = paths[prefix];
382 className = className.substring(prefix.length + 1);
385 if (path.length > 0) {
389 return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
394 * @param {String} className
396 getPrefix: function(className) {
397 var paths = this.config.paths,
398 prefix, deepestPrefix = '';
400 if (paths.hasOwnProperty(className)) {
404 for (prefix in paths) {
405 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
406 if (prefix.length > deepestPrefix.length) {
407 deepestPrefix = prefix;
412 return deepestPrefix;
416 * Refresh all items in the queue. If all dependencies for an item exist during looping,
417 * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
421 refreshQueue: function() {
422 var ln = this.queue.length,
423 i, item, j, requires;
430 for (i = 0; i < ln; i++) {
431 item = this.queue[i];
434 requires = item.requires;
436 // Don't bother checking when the number of files loaded
437 // is still less than the array length
438 if (requires.length > this.numLoadedFiles) {
445 if (Manager.isCreated(requires[j])) {
446 // Take out from the queue
447 requires.splice(j, 1);
452 } while (j < requires.length);
454 if (item.requires.length === 0) {
455 this.queue.splice(i, 1);
456 item.callback.call(item.scope);
467 * Inject a script element to document's head, call onLoad and onError accordingly
470 injectScriptElement: function(url, onLoad, onError, scope) {
471 var script = document.createElement('script'),
473 onLoadFn = function() {
474 me.cleanupScriptElement(script);
477 onErrorFn = function() {
478 me.cleanupScriptElement(script);
482 script.type = 'text/javascript';
484 script.onload = onLoadFn;
485 script.onerror = onErrorFn;
486 script.onreadystatechange = function() {
487 if (this.readyState === 'loaded' || this.readyState === 'complete') {
492 this.documentHead.appendChild(script);
500 cleanupScriptElement: function(script) {
501 script.onload = null;
502 script.onreadystatechange = null;
503 script.onerror = null;
509 * Load a script file, supports both asynchronous and synchronous approaches
511 * @param {String} url
512 * @param {Function} onLoad
513 * @param {Scope} scope
514 * @param {Boolean} synchronous
517 loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
519 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
520 fileName = url.split('/').pop(),
521 isCrossOriginRestricted = false,
522 xhr, status, onScriptError;
524 scope = scope || this;
526 this.isLoading = true;
529 onScriptError = function() {
530 onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
533 if (!Ext.isReady && Ext.onDocumentReady) {
534 Ext.onDocumentReady(function() {
535 me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
539 this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
543 if (typeof XMLHttpRequest !== 'undefined') {
544 xhr = new XMLHttpRequest();
546 xhr = new ActiveXObject('Microsoft.XMLHTTP');
550 xhr.open('GET', noCacheUrl, false);
553 isCrossOriginRestricted = true;
556 status = (xhr.status === 1223) ? 204 : xhr.status;
558 if (!isCrossOriginRestricted) {
559 isCrossOriginRestricted = (status === 0);
562 if (isCrossOriginRestricted
567 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
568 "being loaded from a different domain or from the local file system whereby cross origin " +
569 "requests are not allowed due to security reasons. Use asynchronous loading with " +
570 "Ext.require instead.", synchronous);
572 else if (status >= 200 && status < 300
577 // Firebug friendly, file names are still shown even though they're eval'ed code
578 new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
583 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
584 "verify that the file exists. " +
585 "XHR status code: " + status, synchronous);
588 // Prevent potential IE memory leak
594 * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
595 * Can be chained with more `require` and `exclude` methods, eg:
597 Ext.exclude('Ext.data.*').require('*');
599 Ext.exclude('widget.button*').require('widget.*');
601 * @param {Array} excludes
602 * @return {Object} object contains `require` method for chaining
605 exclude: function(excludes) {
609 require: function(expressions, fn, scope) {
610 return me.require(expressions, fn, scope, excludes);
613 syncRequire: function(expressions, fn, scope) {
614 return me.syncRequire(expressions, fn, scope, excludes);
620 * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
621 * @param {String/Array} expressions Can either be a string or an array of string
622 * @param {Function} fn (Optional) The callback function
623 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
624 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
627 syncRequire: function() {
628 this.syncModeEnabled = true;
629 this.require.apply(this, arguments);
631 this.syncModeEnabled = false;
635 * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
636 * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
637 * @param {String/Array} expressions Can either be a string or an array of string
638 * @param {Function} fn (Optional) The callback function
639 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
640 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
643 require: function(expressions, fn, scope, excludes) {
644 var filePath, expression, exclude, className, excluded = {},
645 excludedClassNames = [],
646 possibleClassNames = [],
647 possibleClassName, classNames = [],
650 expressions = Ext.Array.from(expressions);
651 excludes = Ext.Array.from(excludes);
653 fn = fn || Ext.emptyFn;
655 scope = scope || Ext.global;
657 for (i = 0, ln = excludes.length; i < ln; i++) {
658 exclude = excludes[i];
660 if (typeof exclude === 'string' && exclude.length > 0) {
661 excludedClassNames = Manager.getNamesByExpression(exclude);
663 for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
664 excluded[excludedClassNames[j]] = true;
669 for (i = 0, ln = expressions.length; i < ln; i++) {
670 expression = expressions[i];
672 if (typeof expression === 'string' && expression.length > 0) {
673 possibleClassNames = Manager.getNamesByExpression(expression);
675 for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
676 possibleClassName = possibleClassNames[j];
678 if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
679 Ext.Array.include(classNames, possibleClassName);
685 // If the dynamic dependency feature is not being used, throw an error
686 // if the dependencies are not defined
687 if (!this.config.enabled) {
688 if (classNames.length > 0) {
690 sourceClass: "Ext.Loader",
691 sourceMethod: "require",
692 msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
693 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
698 if (classNames.length === 0) {
704 requires: classNames,
709 classNames = classNames.slice();
711 for (i = 0, ln = classNames.length; i < ln; i++) {
712 className = classNames[i];
714 if (!this.isFileLoaded.hasOwnProperty(className)) {
715 this.isFileLoaded[className] = false;
717 filePath = this.getPath(className);
719 this.classNameToFilePathMap[className] = filePath;
721 this.numPendingFiles++;
728 // Temporary support for hammerjs
730 var f = fs.open(filePath),
736 if (line.length === 0) {
746 this.onFileLoaded(className, filePath);
749 return Manager.get(className);
757 Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
758 Ext.Function.pass(this.onFileLoadError, [className, filePath]),
770 * @param {String} className
771 * @param {String} filePath
773 onFileLoaded: function(className, filePath) {
774 this.numLoadedFiles++;
776 this.isFileLoaded[className] = true;
778 this.numPendingFiles--;
780 if (this.numPendingFiles === 0) {
785 if (this.numPendingFiles <= 1) {
786 window.status = "Finished loading all dependencies, onReady fired!";
789 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
794 if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
795 var queue = this.queue,
797 i, ln, j, subLn, missingClasses = [], missingPaths = [];
799 for (i = 0, ln = queue.length; i < ln; i++) {
800 requires = queue[i].requires;
802 for (j = 0, subLn = requires.length; j < ln; j++) {
803 if (this.isFileLoaded[requires[j]]) {
804 missingClasses.push(requires[j]);
809 if (missingClasses.length < 1) {
813 missingClasses = Ext.Array.filter(missingClasses, function(item) {
814 return !this.requiresMap.hasOwnProperty(item);
817 for (i = 0,ln = missingClasses.length; i < ln; i++) {
818 missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
822 sourceClass: "Ext.Loader",
823 sourceMethod: "onFileLoaded",
824 msg: "The following classes are not declared even if their files have been " +
825 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
826 "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
835 onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
836 this.numPendingFiles--;
837 this.hasFileLoadError = true;
841 sourceClass: "Ext.Loader",
842 classToLoad: className,
844 loadingType: isSynchronous ? 'synchronous' : 'async',
853 addOptionalRequires: function(requires) {
854 var optionalRequires = this.optionalRequires,
857 requires = Ext.Array.from(requires);
859 for (i = 0, ln = requires.length; i < ln; i++) {
860 require = requires[i];
862 Ext.Array.include(optionalRequires, require);
871 triggerReady: function(force) {
872 var readyListeners = this.readyListeners,
873 optionalRequires, listener;
875 if (this.isLoading || force) {
876 this.isLoading = false;
878 if (this.optionalRequires.length) {
879 // Clone then empty the array to eliminate potential recursive loop issue
880 optionalRequires = Ext.Array.clone(this.optionalRequires);
882 // Empty the original array
883 this.optionalRequires.length = 0;
885 this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
889 while (readyListeners.length) {
890 listener = readyListeners.shift();
891 listener.fn.call(listener.scope);
893 if (this.isLoading) {
903 * Add a new listener to be executed when all required scripts are fully loaded
905 * @param {Function} fn The function callback to be executed
906 * @param {Object} scope The execution scope (<code>this</code>) of the callback function
907 * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
909 onReady: function(fn, scope, withDomReady, options) {
912 if (withDomReady !== false && Ext.onDocumentReady) {
916 Ext.onDocumentReady(oldFn, scope, options);
920 if (!this.isLoading) {
924 this.readyListeners.push({
933 * @param {String} className
935 historyPush: function(className) {
936 if (className && this.isFileLoaded.hasOwnProperty(className)) {
937 Ext.Array.include(this.history, className);
945 * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
946 * {@link Ext.Loader} for examples.
950 Ext.require = alias(Loader, 'require');
953 * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
956 * @method syncRequire
958 Ext.syncRequire = alias(Loader, 'syncRequire');
961 * Convenient shortcut to {@link Ext.Loader#exclude}
965 Ext.exclude = alias(Loader, 'exclude');
971 Ext.onReady = function(fn, scope, options) {
972 Loader.onReady(fn, scope, true, options);
975 Class.registerPreprocessor('loader', function(cls, data, continueFn) {
978 className = Manager.getName(cls),
979 i, j, ln, subLn, value, propertyName, propertyValue;
982 Basically loop through the dependencyProperties, look for string class names and push
983 them into a stack, regardless of whether the property's value is a string, array or object. For example:
985 extend: 'Ext.MyClass',
986 requires: ['Ext.some.OtherClass'],
988 observable: 'Ext.util.Observable';
991 which will later be transformed into:
994 requires: [Ext.some.OtherClass],
996 observable: Ext.util.Observable;
1001 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
1002 propertyName = dependencyProperties[i];
1004 if (data.hasOwnProperty(propertyName)) {
1005 propertyValue = data[propertyName];
1007 if (typeof propertyValue === 'string') {
1008 dependencies.push(propertyValue);
1010 else if (propertyValue instanceof Array) {
1011 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
1012 value = propertyValue[j];
1014 if (typeof value === 'string') {
1015 dependencies.push(value);
1020 for (j in propertyValue) {
1021 if (propertyValue.hasOwnProperty(j)) {
1022 value = propertyValue[j];
1024 if (typeof value === 'string') {
1025 dependencies.push(value);
1033 if (dependencies.length === 0) {
1034 // Loader.historyPush(className);
1039 var deadlockPath = [],
1040 requiresMap = Loader.requiresMap,
1044 Automatically detect deadlocks before-hand,
1045 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
1047 - A extends B, then B extends A
1048 - A requires B, B requires C, then C requires A
1050 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
1051 no matter how deep the path is.
1055 requiresMap[className] = dependencies;
1057 detectDeadlock = function(cls) {
1058 deadlockPath.push(cls);
1060 if (requiresMap[cls]) {
1061 if (Ext.Array.contains(requiresMap[cls], className)) {
1063 sourceClass: "Ext.Loader",
1064 msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
1065 deadlockPath[1] + "' " + "mutually require each other. Path: " +
1066 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
1070 for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
1071 detectDeadlock(requiresMap[cls][i]);
1076 detectDeadlock(className);
1081 Loader.require(dependencies, function() {
1082 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
1083 propertyName = dependencyProperties[i];
1085 if (data.hasOwnProperty(propertyName)) {
1086 propertyValue = data[propertyName];
1088 if (typeof propertyValue === 'string') {
1089 data[propertyName] = Manager.get(propertyValue);
1091 else if (propertyValue instanceof Array) {
1092 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
1093 value = propertyValue[j];
1095 if (typeof value === 'string') {
1096 data[propertyName][j] = Manager.get(value);
1101 for (var k in propertyValue) {
1102 if (propertyValue.hasOwnProperty(k)) {
1103 value = propertyValue[k];
1105 if (typeof value === 'string') {
1106 data[propertyName][k] = Manager.get(value);
1114 continueFn.call(me, cls, data);
1120 Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
1122 Manager.registerPostprocessor('uses', function(name, cls, data) {
1123 var uses = Ext.Array.from(data.uses),
1127 for (i = 0, ln = uses.length; i < ln; i++) {
1130 if (typeof item === 'string') {
1135 Loader.addOptionalRequires(items);
1138 Manager.setDefaultPostprocessorPosition('uses', 'last');
1140 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);