cspify
Version:
Utility for Polymer, To avoid CSP issue. extract to external script file from embedded javascript code installed at bower path.
176 lines (135 loc) • 4.24 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var whacko = require('whacko');
var async = require('async');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var chalk = require('chalk');
console.verbose = false;
console.chat = function() {
if (console.verbose) {
console.log.apply(console, arguments);
}
};
// console.info(chalk.gray('Read component file: ' + chalk.blue.underline.bold(path.basename(filename))));
function FilepathMap () {
this.length = 0;
this.components = {};
};
FilepathMap.prototype.insert = function(filepath, param) {
if (this.find(filepath)) {
return this;
}
this.components[filepath] = path.basename(filepath);
this.length++;
return this;
};
FilepathMap.prototype.erase = function(filepath) {
if (this.find(filepath)) {
delete this.components[filepath];
this.length--;
}
return this;
};
FilepathMap.prototype.find = function(filepath) {
return this.components[filepath];
};
FilepathMap.prototype.empty = function() {
return this.length === 0;
};
function Bundler (opts) {
EventEmitter.call(this);
this.opts = opts || {};
// create a map for paths
this.components = new FilepathMap();
// bind queued data handling method
this.queue = async.queue(this.parseQueuedData.bind(this), 4);
return this;
};
util.inherits(Bundler, EventEmitter);
Bundler.prototype.parseQueuedData = function (task, done) {
var $ = task.$;
var $script = $('script:not([type]):not([src]), script[type="text/javascript"]:not([src])');
console.chat(chalk.gray.bold(path.basename(task.filename)), 'is working on');
// getting embedded script
if ($script && $script.length > 0) {
var scripts = '';
var scriptName = path.basename(task.filename, '.html') + '-0.js';
console.chat('\tWe found script blocks', chalk.green($script.length));
$script.each(function(a) {
scripts += $($script[a]).html();
});
// backup origin file
var backupName = task.filename + '.pre_csp';
fs.writeFileSync(backupName, fs.readFileSync(task.filename), 'utf8');
console.chat('\tBackup to', chalk.green(path.basename(backupName)));
// replace embedded script to outter
$script.replaceWith('<script src="' + path.basename(scriptName) + '"></script>');
fs.writeFileSync(path.join(path.dirname(task.filename), scriptName), scripts, 'utf8');
console.chat('\tExport embedded script to', chalk.green(scriptName));
// change the file updated
fs.writeFileSync(task.filename, $.html(), 'utf8');
} else {
console.chat(chalk.red('\tNot found script blocks'));
}
done(null, task);
}
Bundler.prototype.import = function (filename) {
var self = this;
var components = this.components;
// exclude the file that already read
if (components.find(filename)) {
return;
}
// register the file to the list that will be concatenated with output file
components.insert(filename);
// read and parse the file
fs.readFile(filename, 'utf8', function(err, data) {
if (err) {
if (components.erase(filename).empty()) {
self.emit('fin');
}
return;
}
var $ = whacko.load(data);
// recall import if there were linked existing
$('link[rel="import"][href]').each(function() {
var to = path.resolve(path.dirname(filename), $(this).attr('href'));
self.import(to);
});
// push parsed data to writing queue, then erase filename on the list
self.queue.push({
filename: filename,
basename: path.basename(filename),
$: $
}, function(err, task) {
if (components.erase(task.filename).empty()) {
self.emit('fin');
}
});
});
}
Bundler.prototype.tieup = function () {
this.opts.components.forEach(function(c) {
this.import(c);
}.bind(this));
return this;
}
function cspify(components, opts, done) {
var bundler = new Bundler({
components: components,
}).on('fin', function() {
if (done) {
done();
}
process.exit(0);
})
// set log option
console.verbose = opts.verbose;
// tie up components
bundler.tieup();
// waiting for process will be done
process.stdin.resume();
}
module.exports = cspify;