UNPKG

raptor

Version:

RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.

418 lines (344 loc) 18.2 kB
define.Class( "raptor/optimizer/PageBundles", ['raptor'], function(raptor, require, exports, module) { "use strict"; var forEachEntry = raptor.forEachEntry; var forEach = raptor.forEach; var BundleMappings = require('raptor/optimizer/BundleMappings'); var promises = require('raptor/promises'); var AsyncRequire = function(name) { this.name = name; this.requires = []; this.requiresByName = {}; this.bundles = []; this.bundlesByKey = {}; }; AsyncRequire.prototype = { getName: function() { return this.name; }, addRequire: function(name) { if (!this.requiresByName[name]) { this.requiresByName[name] = true; this.requires.push(name); } }, addBundle: function(bundle) { var bundleKey = bundle.getKey(); if (!this.bundlesByKey[bundleKey]) { this.bundlesByKey[bundleKey] = true; this.bundles.push(bundle); } }, getBundles: function() { return this.bundles; }, getBundleKeys: function() { var bundleKeys = []; forEach(this.bundles, function(bundle) { bundleKeys.push(bundle.getKey()); }); return bundleKeys; }, hasRequires: function() { return this.requires.length > 0; }, getRequires: function() { return this.requires; }, forEachBundle: function(callback, thisObj) { forEach(this.bundles, callback, thisObj); }, forEachRequire: function(callback, thisObj) { forEach(this.requires, callback, thisObj); } }; /** * */ var PageBundles = function(config) { this.pageName = config.pageName; this.pageBundleName = config.pageName.replace(/^[^A-Za-z0-9_\-\.]*/g, ''); this.inPlaceDeploymentEnabled = config.inPlaceDeploymentEnabled === true; this.bundlingEnabled = config.bundlingEnabled !== false; this.sourceUrlResolver = config.sourceUrlResolver; this.enabledExtensions = config.enabledExtensions; this.packageManifest = config.packageManifest; this.checksumsEnabled = config.checksumsEnabled; this.context = config.context || {}; this.bundleMappings = new BundleMappings(this.enabledExtensions); if (config.bundleMappings) { this.bundleMappings.setParentBundleMappings(config.bundleMappings); } this.bundleLookup = {}; this.asyncBundleLookup = {}; this.bundlesBySlot = {}; this.bundleCount = 0; this.asyncRequiresByName = {}; }; PageBundles.prototype = { build: function() { var optimizer = require('raptor/optimizer'), config = this.config, bundleMappings = this.bundleMappings, bundlingEnabled = this.bundlingEnabled, // array of all asynchronous package manifests asyncPackages = [], deferred =promises.defer(), isDone = false, _this = this; function done() { if (isDone) { return; } isDone = true; if (deferred) { deferred.resolve(_this); } } function onError(e) { if (isDone) { return; } isDone = true; deferred.reject(e); } // This map keeps track of which packages are NOT asynchronous var initialPageManifestsLookup = {}; // STEP 1: // Put all of the dependencies into bundles and keep track of // packages that are asynchronous. var promise = optimizer.forEachDependency({ dependencies: this.dependencies, packages: this.packageManifest, recursive: true, //We want to make sure every single dependency is part of a bundle enabledExtensions: this.enabledExtensions, context: this.context, handlePackage: function(manifest, context) { if (context.async === true) { asyncPackages.push(manifest); //We'll handle async dependencies later } else { initialPageManifestsLookup[manifest.getName()] = true; } }, handleDependency: function(dependency, context) { if (context.i18n && dependency.type === 'i18n') { // i18n dependencies aren't actually real dependencies. // These "dependencies" are post-processed to build an i18n module // for each locale that is enabled. // store reference to i18n dependency in the I18nContext context.i18n.addDependency(dependency); return; } var bundle = bundleMappings.getBundleForDependency(dependency); if (!bundle) { // this dependency has not been mapped to a bundle yet var sourceResource = dependency.getResource(); if (this.inPlaceDeploymentEnabled) { //Create a bundle with a single dependency for each dependency if (dependency.isInPlaceDeploymentAllowed() && sourceResource && sourceResource.exists()) { var sourceUrl; if (this.sourceUrlResolver) { sourceUrl = this.sourceUrlResolver(sourceResource.getFilePath()); } if (!this.sourceUrlResolver || sourceUrl) { bundle = bundleMappings.addDependencyToBundle(dependency, sourceResource.getURL(), undefined, context.slot); if (sourceUrl) { bundle.url = sourceUrl; } bundle.sourceResource = sourceResource; bundle.sourceDependency = dependency; bundle.inPlaceDeployment = true; } } } if (dependency.isExternalResource()) { bundle = bundleMappings.addDependencyToBundle(dependency, dependency.getUrl(), undefined, context.slot); } if (!bundle && (this.bundlingEnabled === false || this.inPlaceDeploymentEnabled)) { var targetBundleName; if (sourceResource) { if (this.checksumsEnabled) { targetBundleName = sourceResource.getName(); } else { targetBundleName = sourceResource.getPath(); } } else { targetBundleName = dependency.getKey(); } bundle = bundleMappings.addDependencyToBundle(dependency, targetBundleName, undefined, context.slot); bundle.dependencySlotInUrl = false; if (this.inPlaceDeploymentEnabled) { bundle.sourceResource = sourceResource; bundle.sourceDependency = dependency; if (!sourceResource) { bundle.requireChecksum = true; } } } if (!bundle) { //Make sure the dependency is part of a bundle. If it not part of a preconfigured bundle then put it in a page-specific bundle bundle = bundleMappings.addDependencyToBundle(dependency, this.pageBundleName + (context.async ? "-async" : ""), undefined, context.slot); } } if (context.async === true) { return; //Don't add bundles associated with async dependencies to the page bundles (those bundles will be added to the async metadata) } // At this point we have added the dependency to a bundle and we know the bundle is not asynchronous this.addBundleToSlot(bundle); }, thisObj: this }); // STEP 2: // Create localization bundles function buildLocalizationBundles() { var deferred = promises.defer(); _this.context.i18n.buildBundles(_this, function(err, result) { if (err) { deferred.reject(err); } else { if (result) { // add each asynchronous package to list of packages to load asynchronously // (non-asynchronous packages will have already been added to output slots) if (result.packages) { result.packages.forEach(function(i18nPackage) { if (i18nPackage.async) { asyncPackages.push(i18nPackage); } }); } } deferred.resolve(); } }); } var asyncRequires = this.asyncRequiresByName; function getAsyncRequire(name) { var asyncRequire = asyncRequires[name]; if (!asyncRequire) { asyncRequire = asyncRequires[name] = new AsyncRequire(name); } return asyncRequire; } // STEP 3: // Build asynchronous bundles function buildAsyncPageBundles() { var promise = optimizer.forEachDependency({ packages: asyncPackages, recursive: true, //We want to make sure we pull in all recursive dependencies for async bundles enabledExtensions: _this.enabledExtensions, context: _this.context, handlePackage: function(manifest, context) { if (!context.parentPackage) { return; } if (initialPageManifestsLookup[manifest.getName()]) { // this package was already handled because it is // not asynchronous return false; } getAsyncRequire(manifest.getName()); var asyncRequire = getAsyncRequire(context.parentPackage.getName()); asyncRequire.addRequire(manifest.getName()); }, handleDependency: function(dependency, context) { var bundle = bundleMappings.getBundleForDependency(dependency); if (!bundle) { throw raptor.createError(new Error('No bundle found for dependency "' + dependency + '"')); } var bundleKey = bundle.getKey(); if (!this.bundleLookup[bundleKey]) { //Check if this async dependency is part of a page bundle //This bundle is an asynchronous only bundle if (!context.parentPackage) { throw raptor.createError(new Error("Illegal state. Asynchronous dependency is not part of a package")); } this.asyncBundleLookup[bundleKey] = bundle; var asyncRequire = getAsyncRequire(context.parentPackage.getName()); asyncRequire.addBundle(bundle); } }, thisObj: _this }); promise.then(done, onError); } if (this.context.i18n) { promise = promise.then(buildLocalizationBundles, onError); } promise .then(buildAsyncPageBundles) .fail(onError); return deferred.promise; }, addBundleToSlot: function(bundle) { /* * Add the bundle to a page slot if it has not already been added */ var bundleLookupKey = bundle.getKey(); if (!this.bundleLookup[bundleLookupKey]) { this.bundleLookup[bundleLookupKey] = bundle; this.bundleCount++; var bundlesForSlot = this.bundlesBySlot[bundle.getSlot()]; if (!bundlesForSlot) { bundlesForSlot = this.bundlesBySlot[bundle.getSlot()] = { css: [], js: [] }; } if (bundle.isJavaScript()) { bundlesForSlot.js.push(bundle); } else if (bundle.isStyleSheet()){ bundlesForSlot.css.push(bundle); } else { throw raptor.createError(new Error("Invalid content for bundle: " + bundle.getContentType())); } } }, getBundleMappings: function() { return this.bundleMappings; }, forEachBundle: function(callback, thisObj) { forEachEntry(this.bundleLookup, function(bundleKey, bundle) { callback.call(thisObj, bundle); }, this); }, forEachAsyncBundle: function(callback, thisObj) { forEachEntry(this.asyncBundleLookup, function(bundleKey, bundle) { callback.call(thisObj, bundle); }, this); }, forEachBundleIter: function() { return this.forEachBundle.bind(this); }, forEachAsyncBundleIter: function() { return this.forEachAsyncBundle.bind(this); }, /* * This method is used to retrieve information about asynchronous * dependencies so that it can be added to loader metadata. */ forEachAsyncRequire: function(callback, thisObj) { forEachEntry(this.asyncRequiresByName, function(name, asyncRequire) { callback.call(thisObj, asyncRequire); }); }, hasAsyncRequires: function() { return !require('raptor/objects').isEmpty(this.asyncRequiresByName); }, getBundleCount: function() { return this.bundleCount; }, getPackageManifests: function() { return this.packageManifests; }, setSourceUrlResolver: function(sourceUrlResolver) { this.sourceUrlResolver = sourceUrlResolver; } }; return PageBundles; });