modular-js
Version:
Modular JavaScript.
280 lines (243 loc) • 6.68 kB
JavaScript
var fs = require('fs'),
UglifyJS = require('uglify-js'),
CoffeeScript = require('coffee-script'),
log = require('../util/debug').log('Modular');
log2 = require('../util/debug').log('Modular', 2);
var regex = {
extension: /\.[0-9a-zA-Z]+$/,
endSlash: /\/$/
}
function removeFileExtension(fileName) {
return fileName.replace(regex.extension, '');
}
function getFileExtension(fileName) {
var match = fileName.match(regex.extension);
if (match === null) {
throw 'file ' + fileName + ' has no extension';
}
return match.pop().replace(/^\./, '');
}
function realFolderPath(folderPath) {
return folderPath.replace(regex.endSlash, '') + '/';
}
function watch(path, callback) {
fs.watch(path, {interval: 1000}, callback);
}
function deepWatch(folder, callback) {
folder = realFolderPath(folder);
watch(folder, function watchListener(eventName, fileName) {
callback(eventName, folder + fileName);
});
var files, file, index;
files = fs.readdirSync(folder);
files.forEach(function(file, index) {
if (fs.statSync(folder + file).isDirectory()) {
deepWatch(folder + file, callback);
}
})
}
var Modular = new Object;
Modular.option = {
root: './',
output: './',
file: 'application.js',
compress: false,
extensions: ['js', 'coffee'],
watch: false
}
Modular.client = __dirname + '/client.js';
Modular.modules = [];
Modular.cache = '';
Modular.set = function(name, value) {
if (Modular.set[name]) {
return Modular.set[name](value);
}
Modular.option[name] = value;
return Modular;
}
Modular.set.file = function(name) {
name = name.split('.');
var ext = name.pop();
if (Modular.option.extensions.indexOf(ext) === -1) {
name.push(ext);
}
name.push('js');
name = name.join('.');
Modular.option.file = name;
return Modular;
}
Modular.set.root = function(path) {
path = realFolderPath(path);
Modular.option.root = path;
return Modular;
}
Modular.set.output = function(path) {
path = realFolderPath(path);
Modular.option.output = path;
return Modular;
}
Modular.set.watch = function() {
Modular.option.watch = true;
return Modular;
}
/* bundle modules */
Modular.build = function(buildListener) {
Modular.cleanBuild();
Modular.listModules(Modular.option.root);
Modular.loadClient(Modular.client);
Modular.loadModules(Modular.modules);
buildListener(Modular.cache);
}
/* reset all the data before build */
Modular.rebuild = function() {
Modular.reset();
Modular.build(function(result) {
Modular.writeBuild(result);
});
}
/* reset loaded data */
Modular.reset = function() {
Modular.cleanCache();
Modular.cleanModules();
}
/* reset cache to empty */
Modular.cleanCache = function() {
Modular.cache = '';
}
/* reset modules to an empty array */
Modular.cleanModules = function() {
Modular.modules = [];
}
Modular.isModule = function(file) {
var match, extension;
match = file.match(regex.extension);
if (match === null) {
return false;
}
extension = getFileExtension(file);
return ~Modular.option.extensions.indexOf(extension);
}
/* watch root and trigger the callback when module files changed */
Modular.watch = function(callback) {
deepWatch(Modular.option.root, function deepWatchListener(eventName, filePath) {
/* ignore none module files change */
if (!Modular.isModule(filePath)) {
return;
}
/* ignore build file change */
if (Modular.option.root + Modular.option.file === filePath) {
return;
}
callback(eventName, filePath);
});
}
/* remove build package before list modules */
Modular.cleanBuild = function() {
var build = Modular.option.output + Modular.option.file;
if (fs.existsSync(build)) {
fs.unlinkSync(build);
}
}
/* load client library into cache */
Modular.loadClient = function(client) {
Modular.cache += fs.readFileSync(client, 'utf8');
}
/* load modules into cache */
Modular.loadModules = function(modules) {
for (var i in modules) {
var path = modules[i];
var ext = path.split('.').pop();
var code;
if (ext === 'coffee') {
code = Modular.loadCoffee(path);
} else {
code = Modular.loadJS(path);
}
Modular.cache += Modular.wrapModule(path, code);
}
}
Modular.loadCoffee = function(path) {
var code = fs.readFileSync(path, 'utf8');
return CoffeeScript.compile(code);
}
Modular.loadJS = function(path) {
return fs.readFileSync(path, 'utf8');
}
/* write build into the file system */
Modular.writeBuild = function(content, writeBuildListener) {
if (typeof writeBuildListener === 'undefined') {
writeBuildListener = function() {}
}
var minify, file;
if (Modular.option.compress) {
minify = UglifyJS.minify(content, {
fromString: true,
mangle: true
})
content = minify.code
}
file = Modular.option.output + Modular.option.file;
fs.writeFile(file, content, function(err) {
if (err) {
throw err;
return writeBuildListener(false);
}
log2('build', file);
writeBuildListener(true);
});
}
/* wrap the code of a module */
Modular.wrapModule = function(path, code) {
// make relative into absolute path
path = '/' + path.replace(Modular.option.root, '');
// pop extension
path = path.split('.');
path.pop();
path = path.join('.');
var wrap = "\n";
wrap += "define('" + path + "'";
wrap += ', function(exports, require, module) {' + "\n" + code + '});';
wrap += "\n";
return wrap;
}
/* list out modules from a directory */
Modular.listModules = function(directory) {
directory = directory.replace(/\/$/, '');
var files = fs.readdirSync(directory);
var modules = Modular.selectModules(files);
modules.forEach(function(module) {
Modular.modules.push(directory + '/' + module);
})
Modular.detectDirectory(directory, files);
}
/* recursively read directories */
Modular.detectDirectory = function(directory, files) {
files.forEach(function(file) {
var path = directory + '/' + file;
var stat = fs.statSync(path);
if (stat.isDirectory()) Modular.listModules(path);
})
}
/* pick out modules from files */
Modular.selectModules = function(files) {
var modules = [];
files.forEach(function(file) {
var ext = file.split('.').pop();
if (~Modular.option.extensions.indexOf(ext)) modules.push(file);
});
return modules;
}
module.exports = exports = Object.create(Modular);
exports.build = function(path) {
if (typeof path !== 'undefined') {
Modular.set('root', path);
}
Modular.build(function(result) {
Modular.writeBuild(result);
})
if (Modular.option.watch) {
Modular.watch(function() {
Modular.rebuild();
})
}
}