I've been working on an effective way to streamline the way an application registers modules. All you really need is a few lines of code to create a simple module detector!
This is the structure that we're working with. This uses gulp to build! Inside the js folder:
+-- common
| +-- emitter.js
+-- modules
| +-- onefreshmodule.js
+-- templates
| +-- onefreshmoduletemplate.html
+-- main.js
Initializing the application
We don't have a big, fancy SPA (single page application) in this particular case. In our HTML, we create modules as elements. We find all elements with the class name module
. We will register these modules in javascript and inject these with instances of vue.js. This is the HTML for the modules <div class="module" data-module="OneFreshModule" data-options='{"theOptions":"asJson"}'></div>
.
//main.js
import _ from 'lodash/collection';
const modules = require('./modules/*.js', { mode: 'hash' });
const initializer = () => {
const modulesonpage = document.querySelectorAll('.module');
_.forEach(modulesonpage, (el) => {
const dataset = el.dataset;
if(dataset.module != null){
modules[dataset.module.toLowerCase()].initialize({el: el, options: dataset.options || {} });
}
});
};
initializer();
This tiny bit of code does a lot. We get all our js modules in an array list. We pull in lodash to loop through the module HTML elements pulled from the DOM. We only get the collection part of underscore, this saves a lot of space! We call an init function in each module. The main.js file is the backbone of our application and it's where we tie every component together.
Using browserify to build modules
//gulpfile.js
var gulp = require('gulp'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
globify = require('require-globify'),
babelify = require('babelify'),
browserify = require('browserify'),
stringify = require('stringify'),
watchify = require('watchify');
function compile(watch) {
var extensions = ['.js', '.html'];
var bundler = watchify(browserify({
entries: './js/src/main.js',
extensions: extensions,
debug: false,
transform: [babelify, globify, stringify]
}));
function rebundle() {
bundler.bundle()
.on('error', function (err) { console.error(err); this.emit('end'); })
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(gulp.dest('./js/dist/'));
}
if (watch) {
bundler.on('update', function () {
console.log('-> bundling...');
rebundle();
});
}
rebundle();
}
function watch() {
return compile(true);
};
gulp.task('build-modules', function () { return compile(); });
gulp.task('watch-modules', function () { return watch(); });
I use browserify to work in separated modules. My gulpfile has a couple of transforms in it. With babelify I can write modern javascript. I also use stringify and require-globify to do some other interesting stuff. Require-globify allows me to get all javascript modules in a folder and put them in an array. The key in the array is the file name and the value is the javascript content of the file. In each of my modules I export an initialize
function.
Showing off modules
//onefreshmodule.js
import Vue from 'vue';
import template from '../templates/onefreshmoduletemplate.html';
import emitter from '../common/emitter.js';
const view = {};
const model = {
awesomelist: ["Bruce Lee", "Jackie Chan", "John Cena"],
emitter: emitter
};
const initialize = (data) => {
const options = JSON.parse(data.options);
Object.assign(model, options);
view = new Vue({
el: data.el,
replace: false,
template: template,
data: model,
methods: {
sendAwesomeList(){
this.emitter.emit('awesomelistreciever', this.awesomelist);
}
}
});
}
export { initialize }
This is an example of a module and this module does nothing more than initialize a vue.js instance and basically activating the module. What you put in this initialize method is really up to its responsibility and can be really anything. In my case I just want a way to databind my objects, so I chose Vue.js to do that for me. To get the template for the vue.js instance we use the browserify transform stringify. This basically gets the contents of a HTML file when building. Very nifty if you want to separate your views and only have javascript in the javascript files (yes, I'm looking at you react!). This is also why I kept from using the vueify transform. I don't like the mix of code and view. But that's a matter of taste.
Another cool feature is the shimmed emitter inside browserify. This allows us to emit information to any module in the application. Very simple stuff!
//emitter.js
let EventEmitter = require('events').EventEmitter;
let emitter = new EventEmitter();
export default emitter;
//Example usage
import emitter from '../common/emitter.js';
const awesomelist = ["Bruce Lee", "Jackie Chan", "John Cena"];
emitter.emit('awesomelistreciever', awesomelist);
//In a tooootally different file!
emitter.on('awesomelistreciever', (list)=>{
console.log(list); //will print some cool fighters!
});
Discussing Flux and conclusion
Flux is really cool! While this example doesn't really adapt a flux architecture, it can easily be implemented in the future when the app grows. Specially with the emitter! It's a fresh breath of air when someone comes with an architecture rather than just another framework. I've been looking at state-based architecture for a little while now and I'm a fan.
This post doesn't really have an example in action. It rather shows off what you can do with modules in javascript. Structure is important and taxonomy is one of the most underestimated parts of front end development. I hope this post suits you well and give you inspiration to write modules and clean code!