Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Bundle Plugin for Fluxible
/**
* On server, just register all of the resources directly to the plugin. On the client, pass in a loader file that maps bundle
* name to webpack bundle require (a separate entry point). Then from your action, you can call `context.loadBundle` to load
* the bundle resources. On the server, the resources are already available but on the client they will be lazy loaded by
* webpack.
*/
'use strict';
var debug = require('debug')('BundlePlugin');
var PromiseLib = global.Promise || require('es6-promise').Promise;
module.exports = function bundleLoaderFactory(options) {
return new BundleLoader(options);
};
function BundleLoader(options) {
options = options || {};
this._app = options.app;
this._mainBundle = options.mainBundle;
this._enableMainBundle = typeof options.enableMainBundle !== 'undefined' ? options.enableMainBundle : true;
this._bundleLoader = options.bundleLoader;
// Registered later
this._actions = {};
this._components = {};
this._loadedBundles = {};
}
BundleLoader.prototype.name = 'BundlePlugin';
BundleLoader.prototype.plugContext = function () {
var context = new BundleLoaderContext(this);
return {
plugActionContext: function (actionContext) {
actionContext.getAction = context.getAction.bind(context);
actionContext.getComponent = context.getComponent.bind(context);
actionContext.getMainBundle = context.getMainBundle.bind(context);
actionContext.loadBundle = context.loadBundle.bind(context);
actionContext.loadBundles = context.loadBundles.bind(context);
},
plugComponentContext: function (componentContext) {
componentContext.getComponent = context.getComponent.bind(context);
},
dehydrate: function () {
return context.dehydrate();
},
rehydrate: function (state, callback) {
return context.rehydrate(state, callback);
}
};
};
BundleLoader.prototype.getMainBundle = function () {
return this._mainBundle;
};
/**
* Makes an action available for use in page routes
* @method registerAction
* @param {String} name Name of the action
* @param {Function} action action function
*/
BundleLoader.prototype.registerAction = function registerAction(name, action) {
debug(name + ' action registered');
this._actions[name] = action;
};
/**
* Makes a bundle lazy loadable
* @method registerBundle
* @param {String} name Name of the bundle
* @param {Function} bundle bundle object
*/
BundleLoader.prototype.registerBundle = function registerBundle(name, bundle) {
var self = this;
debug(name + ' bundle registered');
self._loadedBundles[name] = bundle;
if (bundle.actions) {
Object.keys(bundle.actions).forEach(function eachAction(actionName) {
self.registerAction(actionName, bundle.actions[actionName]);
});
}
if (bundle.controllerViews) {
Object.keys(bundle.controllerViews).forEach(function eachControllerViews(componentName) {
self.registerComponent(componentName, bundle.controllerViews[componentName]);
});
}
if (bundle.stores) {
Object.keys(bundle.stores).forEach(function eachStore(storeName) {
var store = bundle.stores[storeName];
self._app.registerStore(store);
});
}
};
/**
* Makes a component available for use in composites
* @method registerBundle
* @param {String} name Name of the component
* @param {Function} component React component
*/
BundleLoader.prototype.registerComponent = function registerComponent(name, component) {
debug(name + ' component registered');
this._components[name] = component;
};
/**
* Creates a serializable state of the plugin
* @method dehydrate
* @returns {Object}
*/
BundleLoader.prototype.dehydrate = function dehydrate() {
debug('dehydrate');
return {
mainBundle: this._mainBundle
};
};
/**
* Rehydrates the application and creates a new context with the state from the server
* @method rehydrate
* @param {Object} state
* @async
*/
BundleLoader.prototype.rehydrate = function rehydrate(state) {
debug('rehydrate', state);
var self = this;
self._mainBundle = state.mainBundle;
};
/**
* @class BundleLoaderContext
* @param {object} bundleLoader
* @constructor
*/
function BundleLoaderContext(bundleLoader) {
this.bundleLoader = bundleLoader;
this._bundleLoadPromises = {};
}
BundleLoaderContext.prototype.getMainBundle = function () {
return this.bundleLoader._mainBundle;
};
BundleLoaderContext.prototype.getAction = function (actionName) {
return this.bundleLoader._actions[actionName];
};
BundleLoaderContext.prototype.getComponent = function (componentName) {
return this.bundleLoader._components[componentName];
};
/**
* Loads a bundle rollup
* @method loadBundle
* @param {String} bundleName Bundle name
* @param {Function} [callback] Callback to be called after bundle is loaded
* @return {Promise}
* @async
*/
BundleLoaderContext.prototype.loadBundle = function loadBundle(bundleName, callback) {
debug('load ' + bundleName + ' bundle');
var self = this;
// Make sure we don't try to load the same bundle twice at the same time
if (!self._bundleLoadPromises[bundleName]) {
self._bundleLoadPromises[bundleName] = new PromiseLib(function (resolve, reject) {
// Check if the bundle is already registered first
if (self.bundleLoader._loadedBundles[bundleName]) {
resolve(self.bundleLoader._loadedBundles[bundleName]);
return;
}
// Now attempt to use the loader to lazy load the bundle
if (!self.bundleLoader._bundleLoader) {
reject(new Error('Loader does not exist'));
return;
}
if (!self.bundleLoader._bundleLoader[bundleName]) {
reject(new Error('Bundle ' + bundleName + ' is not available in the loader'));
return;
}
self.bundleLoader._bundleLoader[bundleName](function (err, bundle) {
if (err) {
reject(err);
return;
}
// Register the bundle and its resources
self.bundleLoader.registerBundle(bundleName, bundle);
resolve(bundle);
});
});
}
if (callback) {
self._bundleLoadPromises[bundleName].then(function (result) {
setImmediate(callback, undefined, result);
})['catch'](function (e) {
setImmediate(callback, e);
});
}
return self._bundleLoadPromises[bundleName];
};
/**
* Loads bundles via the loader method
* @method loadBundles
* @param {String|Array} bundleNames Names of bundles to load
* @param {Function} callback Called after all bundles have been loaded
* @async
*/
BundleLoaderContext.prototype.loadBundles = function loadBundles(bundleNames, callback) {
var self = this;
var currentLoadingPromises = [];
if (!Array.isArray(bundleNames)) {
bundleNames = [bundleNames];
}
debug('loading ' + bundleNames.join(', '));
bundleNames.forEach(function (bundleName) {
currentLoadingPromises.push(self.loadBundle(bundleName));
});
PromiseLib.all(currentLoadingPromises).then(function (result) {
debug('loaded ' + bundleNames.join(', '));
setImmediate(callback, null, result);
})['catch'](function (e) {
setImmediate(callback, e);
});
};
/**
* Creates a serializable state of the plugin
* @method dehydrate
* @returns {Object}
*/
BundleLoaderContext.prototype.dehydrate = function dehydrate() {
debug('dehydrate');
var bundlesLoaded = Object.keys(this._bundleLoadPromises);
if (this.bundleLoader._enableMainBundle) {
bundlesLoaded.push(this.bundleLoader._mainBundle); // Always load the main bundle
}
return {
loadedBundles: bundlesLoaded
};
};
/**
* Rehydrates the application and creates a new context with the state from the server
* @method rehydrate
* @param {Object} state
* @param {Function} callback
* @async
*/
BundleLoaderContext.prototype.rehydrate = function rehydrate(state, callback) {
debug('rehydrate', state);
var self = this;
self.loadBundles(state.loadedBundles, function (err) {
if (err) {
callback && callback(err);
return;
}
callback();
});
};
module.exports = function (context, payload, done) {
context.loadBundle('Nest', function () {
//Ensures that Nest components are loaded
context.executeAction(loadPageData, {}, function (err, pageData) {
context.dispatch('NEW_PAGE', pageData);
done();
});
});
}
module.exports = {
actions: {
getAbout: require('./actions/getAbout')
},
controllerViews: {
About: require('./components/About'),
BasicApp: require('./components/Application'),
Followers: require('./components/Followers'),
Home: require('./components/Home')
},
stores: {
AboutStore: require('./stores/AboutStore'),
ApplicationStore: require('./stores/ApplicationStore'),
FollowersStore: require('./stores/FollowersStore'),
TimeStore: require('./stores/TimeStore')
}
};
'use strict';
var debug = require('debug')('Loader');
module.exports = {
'basic': function (callback) {
debug('loading basic');
require('bundle!../bundle.js')(function (bundle) {
debug('loaded basic');
callback(null, bundle);
});
},
'Nest': function (callback) {
debug('loading Nest');
require('bundle!../bundles/Nest/bundle.js')(function (bundle) {
debug('loaded Nest');
callback(null, bundle);
});
}
};
@taylorjames

This comment has been minimized.

Copy link

taylorjames commented Sep 22, 2015

Do you have an example of how this can be implemented with a current fluxible app?

@taylorjames

This comment has been minimized.

Copy link

taylorjames commented Sep 23, 2015

@mridgway is there any way you can show me how I can use this in my app?

@ali1k

This comment has been minimized.

Copy link

ali1k commented May 12, 2016

I can't find where example-bundle.js is referred to and how we should setup webpack to use the loader?

@redonkulus

This comment has been minimized.

Copy link

redonkulus commented May 13, 2016

@ali1k so we have a Grunt task internally at yahoo that will take example-bundle.js and convert it into example-loader.js. The loader file then gets passed into the bundle plugin and lazy loaded later by webpack.

@ali1k

This comment has been minimized.

Copy link

ali1k commented May 18, 2016

@redonkulus do you have any plan to open source your bundling tool? I think it is a crucial feature to build an ecosystem of Fluxible-based components. This example is still incomplete and as others mentioned, without a complete example, users cannot really figure it out how to use dynamic module loading in Fluxible!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.