| Index: tools/vulcanize/node_modules/vulcanize/lib/vulcan.js
|
| diff --git a/tools/vulcanize/node_modules/vulcanize/lib/vulcan.js b/tools/vulcanize/node_modules/vulcanize/lib/vulcan.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..95da85f08609f0727ac0c689495c908239344300
|
| --- /dev/null
|
| +++ b/tools/vulcanize/node_modules/vulcanize/lib/vulcan.js
|
| @@ -0,0 +1,331 @@
|
| +/*
|
| + * Copyright 2013 The Polymer Authors. All rights reserved.
|
| + * Use of this source code is governed by a BSD-style
|
| + * license that can be found in the LICENSE file.
|
| + */
|
| +
|
| +var fs = require('fs');
|
| +var path = require('path');
|
| +var url = require('url');
|
| +var cheerio = require('cheerio');
|
| +var nopt = require('nopt');
|
| +var EOL = require('os').EOL;
|
| +
|
| +var ABS_URL = /(^data:)|(^http[s]?:)|(^\/)/;
|
| +var DEFAULT_OUTPUT = 'vulcanized.html';
|
| +var ELEMENTS = 'polymer-element';
|
| +var IMPORTS = 'link[rel="import"][href]';
|
| +var POLYMER = 'script[src $= "polymer.js"], script[src $= "polymer.min.js"]';
|
| +var SCRIPT_SRC = /<script src=["']([^"']+)["']><\/script>/g;
|
| +var URL = /url\([^)]*\)/g;
|
| +var URL_ATTR = ['href', 'src', 'action', 'style'];
|
| +var URL_ATTR_SEL = '[' + URL_ATTR.join('],[') + ']';
|
| +var URL_TEMPLATE = '{{.*}}';
|
| +
|
| +var excludes = {};
|
| +
|
| +var import_buffer = [];
|
| +var imports_before_polymer = [];
|
| +var read = {};
|
| +var options = {};
|
| +
|
| +// validate options with boolean return
|
| +function setOptions(optHash) {
|
| + if (!optHash.input || !fs.existsSync(optHash.input)) {
|
| + console.error('No input file given!');
|
| + return;
|
| + }
|
| +
|
| + excludes = {
|
| + imports: [ABS_URL],
|
| + scripts: [ABS_URL],
|
| + styles: [ABS_URL]
|
| + };
|
| +
|
| + if (optHash.excludes) {
|
| + var e = optHash.excludes;
|
| + if (e.imports && Array.isArray(e.imports)) {
|
| + e.imports.forEach(function(r) {
|
| + excludes.imports.push(new RegExp(r));
|
| + });
|
| + } else {
|
| + console.error('Malformed import exclude config');
|
| + return;
|
| + }
|
| + }
|
| +
|
| + if (!optHash.output) {
|
| + console.warn('Default output to vulcanized.html' + (optHash.csp ? ' and vulcanized.js' : '') + ' in the input directory.');
|
| + optHash.output = path.resolve(path.dirname(optHash.input), DEFAULT_OUTPUT);
|
| + }
|
| +
|
| + optHash.outputDir = path.dirname(optHash.output);
|
| + options = optHash;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +function exclude(regexes, href) {
|
| + return regexes.some(function(r) {
|
| + return r.test(href);
|
| + });
|
| +}
|
| +
|
| +function excludeImport(href) {
|
| + return exclude(excludes.imports, href);
|
| +}
|
| +
|
| +function excludeScript(href) {
|
| + return exclude(excludes.scripts, href);
|
| +}
|
| +
|
| +function excludeStyle(href) {
|
| + return exclude(excludes.styles, href);
|
| +}
|
| +
|
| +function resolvePaths($, input, output) {
|
| + var assetPath = path.relative(output, input);
|
| + assetPath = assetPath.split(path.sep).join('/') + '/';
|
| + // resolve attributes
|
| + $(URL_ATTR_SEL).each(function() {
|
| + URL_ATTR.forEach(function(a) {
|
| + var val = this.attr(a);
|
| + if (val) {
|
| + if (val.search(URL_TEMPLATE) < 0) {
|
| + if (a === 'style') {
|
| + this.attr(a, rewriteURL(input, output, val));
|
| + } else {
|
| + this.attr(a, rewriteRelPath(input, output, val));
|
| + }
|
| + }
|
| + }
|
| + }, this);
|
| + });
|
| + $('style').each(function() {
|
| + // directly update the textnode child of <style>
|
| + // equivalent to <style>.textContent
|
| + this[0].children[0].data = rewriteURL(input, output, this.text());
|
| + });
|
| + $(ELEMENTS).each(function() {
|
| + this.attr('assetpath', assetPath);
|
| + });
|
| +}
|
| +
|
| +function rewriteRelPath(inputPath, outputPath, rel) {
|
| + if (ABS_URL.test(rel)) {
|
| + return rel;
|
| + }
|
| + var abs = path.resolve(inputPath, rel);
|
| + var relPath = path.relative(outputPath, abs);
|
| + return relPath.split(path.sep).join('/');
|
| +}
|
| +
|
| +function rewriteURL(inputPath, outputPath, cssText) {
|
| + return cssText.replace(URL, function(match) {
|
| + var path = match.replace(/["']/g, "").slice(4, -1);
|
| + path = rewriteRelPath(inputPath, outputPath, path);
|
| + return 'url(' + path + ')';
|
| + });
|
| +}
|
| +
|
| +function readDocument(docname) {
|
| + if (options.verbose) {
|
| + console.log('Reading:', docname);
|
| + }
|
| + var content = fs.readFileSync(docname, 'utf8');
|
| + return cheerio.load(content);
|
| +}
|
| +
|
| +// inline relative linked stylesheets into <style> tags
|
| +function inlineSheets($, inputPath, outputPath) {
|
| + $('link[rel="stylesheet"]').each(function() {
|
| + var href = this.attr('href');
|
| + if (href && !excludeStyle(href)) {
|
| + var filepath = path.resolve(inputPath, href);
|
| + // fix up paths in the stylesheet to be relative to the output path
|
| + var content = rewriteURL(path.dirname(filepath), outputPath, fs.readFileSync(filepath, 'utf8'));
|
| + var styleDoc = cheerio.load('<style>' + content + '</style>');
|
| + var polymerScope = this.attr('polymer-scope');
|
| + if (polymerScope) {
|
| + styleDoc('style').attr('polymer-scope', polymerScope);
|
| + }
|
| + this.replaceWith(styleDoc.html());
|
| + }
|
| + });
|
| +}
|
| +
|
| +function concat(filename) {
|
| + if (!read[filename]) {
|
| + read[filename] = true;
|
| + var $ = readDocument(filename);
|
| + var dir = path.dirname(filename);
|
| + processImports($, dir);
|
| + inlineSheets($, dir, options.outputDir);
|
| + resolvePaths($, dir, options.outputDir);
|
| + import_buffer.push($.html());
|
| + } else {
|
| + if (options.verbose) {
|
| + console.log('Dependency deduplicated');
|
| + }
|
| + }
|
| +}
|
| +
|
| +function processImports($, prefix) {
|
| + $(IMPORTS).each(function() {
|
| + var href = this.attr('href');
|
| + if (excludeImport(href)) {
|
| + // rewrite href to be deduplicated later
|
| + this.attr('href', rewriteRelPath(prefix, options.outputDir, href));
|
| + imports_before_polymer.push(this);
|
| + } else {
|
| + concat(path.resolve(prefix, href));
|
| + }
|
| + }).remove();
|
| +}
|
| +
|
| +function findScriptLocation($) {
|
| + var pos = $(POLYMER).last().parent();
|
| + if (!pos.length) {
|
| + pos = $('body');
|
| + }
|
| + if (!pos.length) {
|
| + pos = $.root();
|
| + }
|
| + return pos;
|
| +}
|
| +
|
| +function insertImport($, storedPosition, importText) {
|
| + var pos = storedPosition;
|
| + var operation = 'before';
|
| + if (!pos.length) {
|
| + // before polymer script in <head>
|
| + pos = $('head ' + POLYMER).last();
|
| + }
|
| + if (!pos.length) {
|
| + // at the bottom of head
|
| + pos = $('head').last();
|
| + operation = 'append';
|
| + }
|
| + if (!pos.length) {
|
| + // at the bottom of <html>
|
| + pos = $.root();
|
| + operation = 'append';
|
| + }
|
| + pos[operation](importText);
|
| +}
|
| +
|
| +function insertInlinedImports($, storedPosition, importText) {
|
| + var pos = storedPosition;
|
| + var operation = 'before';
|
| + if (!pos.length) {
|
| + pos = $('body').last();
|
| + operation = 'prepend';
|
| + }
|
| + if (!pos.length) {
|
| + pos = $.root();
|
| + }
|
| + pos[operation](importText);
|
| +}
|
| +
|
| +function inlineScripts(htmlstring, dir) {
|
| + return htmlstring.replace(SCRIPT_SRC, function(match, src) {
|
| + var out = match;
|
| + if (!excludeScript(src)) {
|
| + out = '<script>' + fs.readFileSync(path.resolve(dir, src), 'utf8') + '</script>';
|
| + }
|
| + return out;
|
| + });
|
| +}
|
| +
|
| +// arguments are (index, node), where index is unnecessary
|
| +function isCommentOrEmptyTextNode(_, node) {
|
| + if (node.type === 'comment'){
|
| + return true;
|
| + } else if (node.type === 'text') {
|
| + // return true if the node is only whitespace
|
| + return !((/\S/).test(node.data));
|
| + }
|
| +}
|
| +
|
| +function removeCommentsAndWhitespace($) {
|
| + $('head').contents().filter(isCommentOrEmptyTextNode).remove();
|
| + $('body').contents().filter(isCommentOrEmptyTextNode).remove();
|
| +}
|
| +
|
| +function handleMainDocument() {
|
| + // reset shared buffers
|
| + import_buffer = [];
|
| + imports_before_polymer = [];
|
| + read = {};
|
| + var $ = readDocument(options.input);
|
| + var dir = path.dirname(options.input);
|
| + // find the position of the last import's nextSibling
|
| + var import_pos = $(IMPORTS).last().next();
|
| + processImports($, dir);
|
| + resolvePaths($, dir, options.outputDir);
|
| + var output = import_buffer.join(EOL);
|
| +
|
| + // strip scripts into a separate file
|
| + if (options.csp) {
|
| + if (options.verbose) {
|
| + console.log('Separating scripts into separate file');
|
| + }
|
| + var scripts = [];
|
| + var scripts_after_polymer = [];
|
| + var tempoutput = cheerio.load(output);
|
| +
|
| + tempoutput('script').each(function() {
|
| + var src = this.attr('src');
|
| + if (src) {
|
| + // external script
|
| + if (excludeScript(src)) {
|
| + // put an absolute path script after polymer.js in main document
|
| + scripts_after_polymer.push(this.html());
|
| + } else {
|
| + scripts.push(fs.readFileSync(path.resolve(options.outputDir, src), 'utf8'));
|
| + }
|
| + } else {
|
| + // inline script
|
| + scripts.push(this.text());
|
| + }
|
| + }).remove();
|
| + output = tempoutput.html();
|
| + // join scripts with ';' to prevent breakages due to EOF semicolon insertion
|
| + var script_name = path.basename(options.output, '.html') + '.js';
|
| + fs.writeFileSync(path.resolve(options.outputDir, script_name), scripts.join(';' + EOL), 'utf8');
|
| + scripts_after_polymer.push('<script src="' + script_name + '"></script>');
|
| + findScriptLocation($).append(EOL + scripts_after_polymer.join(EOL) + EOL);
|
| + }
|
| +
|
| + imports_before_polymer = deduplicateImports(imports_before_polymer);
|
| + insertImport($, import_pos, imports_before_polymer.join(EOL) + EOL);
|
| + insertInlinedImports($, import_pos, output);
|
| + if (options.strip) {
|
| + removeCommentsAndWhitespace($);
|
| + }
|
| + var outhtml = $.html();
|
| + if (!options.csp && options.inline) {
|
| + outhtml = inlineScripts(outhtml, options.outputDir);
|
| + }
|
| + fs.writeFileSync(options.output, outhtml, 'utf8');
|
| +}
|
| +
|
| +function deduplicateImports(importArray) {
|
| + var imports = {};
|
| + return importArray.filter(function(im) {
|
| + var href = im.attr('href');
|
| + // TODO(dfreedm): allow a user defined base url?
|
| + var abs = url.resolve('http://', href);
|
| + if (!imports[abs]) {
|
| + imports[abs] = true;
|
| + return true;
|
| + } else if(options.verbose) {
|
| + console.log('Import Dependency deduplicated');
|
| + }
|
| + }).map(function(im) {
|
| + return cheerio.html(im);
|
| + });
|
| +}
|
| +
|
| +exports.processDocument = handleMainDocument;
|
| +exports.setOptions = setOptions;
|
|
|