Last active
January 11, 2024 06:05
-
-
Save mridgway/53745e0da019058cc277 to your computer and use it in GitHub Desktop.
Bundle Plugin for Fluxible
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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); | |
}); | |
} | |
}; |
@mridgway is there any way you can show me how I can use this in my app?
I can't find where example-bundle.js
is referred to and how we should setup webpack to use the loader?
@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.
@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
Do you have an example of how this can be implemented with a current fluxible app?