ams
Version:
ams - asset management system - plugin enabled build tool with jquery like API
294 lines (251 loc) • 7.17 kB
JavaScript
/*
* Read json files generated by tracker-server, do preprocessing stuff,
* recreate or symlink static files if they has changed.
*/
var $ = require('sharedjs'),
n = require('natives'),
deps = require('./deps'),
utils = require('./utils');
/**
* Default options object
* @type {Object}
* @export
*/
exports.options = {
build: {
root: null // root of src files
},
find: {
detect: null, // detect deps using static require() parser
root: null, // path where to look, this.root is default
pattern: /\.[\w]+$/, // is used if detect is not defined
filter: null, // is used if detect is not defined
rec: true, // recursive search
paths: null // like require.paths to resolve deps
},
// processors options
process: {
uglifyjs: true,
cssvendor: true,
dataimage: true,
cssimport: true,
cssabspath: true,
htmlabspath: true,
cssmin: true,
jstransport: true,
texttransport: true
},
// we have to ensure, that processors run in correct order, because f.e.
// dataimg will not work if abspath with external url was done before
processorsOrder: [
'jstransport', 'uglifyjs',
'cssvendor', 'dataimage', 'cssimport','cssmin', 'cssabspath',
'htmlabspath', 'texttransport'
],
combine: null // {css: '/path/to/file'}
};
/**
* Create new build.
* @param {string} root path to sources root.
* @constructor
*/
function Build(root) {
if (!root) {
throw new Error('root is not defined');
}
this._now = Date.now();
this.aliases = {};
this.options = $.extend(true, {}, exports.options);
// remove last slash if contained
this.options.build.root = this.root = root.replace(/\/$/, '');
this.paths = [root];
this.data = {};
}
var proto = Build.prototype;
/**
* Find files.
* @param {Object} opts exports.options.find.
* @this Build
* @return {Build} this.
*/
proto.find = function(opts) {
var o = this.options.find = $.extend({}, exports.options.find, opts),
paths;
if (o.paths) {
this.paths = this.paths.concat(o.paths);
}
if (o.detect) {
paths = deps.find(o.detect, o.paths, this.aliases);
} else {
paths = utils.findSync(o.root || this.root, o.pattern, o.rec);
}
paths.forEach(function(path) {
if ((!o.filter || !o.filter.test(path)) &&
(!o.pattern || o.pattern.test(path))) {
this.data[path] = n.fs.readFileSync(path);
}
}, this);
return this;
};
/**
* Set aliases.
* F.e. if you want to require('jquery'), but the path is jquery/jquery
*
* @param {String|Object} src src path or aliases map
* @param {String} [target] target path
* @return {Build} this
*/
proto.alias = function(src, target) {
if (src && target) {
this.aliases[src] = target;
} else if (typeof src == 'object') {
$.extend(this.aliases, src);
}
return this;
};
/**
* Add one file from given path, optionally define the target dir
* @param {string|Array} path to file.
* @this Build
* @return {Build} this.
*/
proto.add = function add(path) {
if (Array.isArray(path)) {
path.forEach(this.add, this);
return this;
}
// path is relative
if (path.substr(0, 1) !== '/') {
path = n.path.join(process.cwd(), path);
}
this.data[path] = n.fs.readFileSync(path);
return this;
};
/**
* Run processors on files previously readed.
* @param {Object|Function} opts exports.options.process
* or custom processing function.
* @this Build
* @return {Build} this.
*/
proto.process = function(opts) {
var o, path,
self = this;
if (typeof opts === 'function') {
for (path in this.data) {
opts.call(
this,
path,
this.data[path].toString('utf-8')
);
}
return this;
}
o = $.extend(this.options.process || {}, opts);
this.options.processorsOrder.forEach(function(name) {
var path, proc, filter;
if (!o[name]) {
return;
}
proc = require('./processors/' + name);
// merge options with processor options
o[name] = $.extend({}, proc.options, o[name]);
filter = o[name].filter;
for (path in self.data) {
// only process files if they mach processor pattern
if (o[name].pattern.test(path) &&
// check if the path should not be filtered
(!filter || !filter.test(path))) {
self.data[path] = proc.run.call(
self,
path,
self.data[path].toString('utf-8'),
o[name]
);
}
}
});
return this;
};
/**
* Combine files.
* @param {Object} opts exports.options.combine.
* @this Build
* @return {Build} this.
*/
proto.combine = function(opts) {
var o = $.extend(this.options.combine || {}, opts),
ext, combinePath, path, regex;
for (ext in o) {
combinePath = o[ext];
regex = new RegExp('\\.' + ext + '$');
for (path in this.data) {
if (regex.test(path)) {
if (typeof this.data[combinePath] !== 'string') {
this.data[combinePath] = '';
}
this.data[combinePath] += this.data[path] + '\n';
// delete original file reference, to avoid this
// file will be included in any further steps again
delete this.data[path];
}
}
}
return this;
};
/**
* Clean up directory
* @param {string} path to the dir.
* @this Build
* @return {Build} this.
*/
proto.cleanup = function(path) {
utils.rmSync(path);
return this;
};
/**
* Write files to disk.
* @param {string} targetRoot abs. path to target dir.
* @this Build
* @return {Build} this.
*/
proto.write = function(targetRoot) {
var root = this.root,
path, targetPath, data;
if (!targetRoot) {
throw new Error('target dir have to be specified');
}
for (path in this.data) {
// make relative path
if (path.substr(0, 1) === '/') {
targetPath = path.replace(root, '');
} else {
targetPath = path;
}
// make abs path to target file
targetPath = n.path.join(targetRoot, targetPath);
utils.mkdirSync(n.path.dirname(targetPath));
n.fs.writeFileSync(targetPath, this.data[path]);
}
return this;
};
/**
* Show success message
* @this Build
* @param {string} msg optional message text.
* @return {Build} this.
*/
proto.end = function(msg) {
msg || (msg = 'Build was successfull');
n.util.log(msg + ', runtime: ' + (Date.now() - this._now) + ' ms.');
return this;
};
/**
* Create a new build.
* @param {string} root path to src root.
* @return {Build} instance of Build.
* @export
*/
exports.create = function(root) {
return new Build(root);
};