| Index: node_modules/vulcanize/lib/vulcan.js
|
| diff --git a/node_modules/vulcanize/lib/vulcan.js b/node_modules/vulcanize/lib/vulcan.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0d7e230add04c5c206444bfe6264e4081cdbedcf
|
| --- /dev/null
|
| +++ b/node_modules/vulcanize/lib/vulcan.js
|
| @@ -0,0 +1,356 @@
|
| +/**
|
| + * @license
|
| + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| + * Code distributed by Google as part of the polymer project is also
|
| + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| + */
|
| +
|
| +// jshint node: true
|
| +
|
| +var cssom = require('cssom');
|
| +var fs = require('fs');
|
| +var path = require('path');
|
| +var uglify = require('uglify-js');
|
| +var url = require('url');
|
| +var whacko = require('whacko');
|
| +
|
| +var constants = require('./constants.js');
|
| +var optparser = require('./optparser.js');
|
| +var pathresolver = require('./pathresolver');
|
| +var utils = require('./utils');
|
| +var setTextContent = utils.setTextContent;
|
| +var getTextContent = utils.getTextContent;
|
| +var searchAll = utils.searchAll;
|
| +
|
| +var read = {};
|
| +var options = {};
|
| +
|
| +// validate options with boolean return
|
| +function setOptions(optHash, callback) {
|
| + optparser.processOptions(optHash, function(err, o) {
|
| + if (err) {
|
| + return callback(err);
|
| + }
|
| + options = o;
|
| + callback();
|
| + });
|
| +}
|
| +
|
| +function exclude(regexes, href) {
|
| + return regexes.some(function(r) {
|
| + return r.test(href);
|
| + });
|
| +}
|
| +
|
| +function excludeImport(href) {
|
| + return exclude(options.excludes.imports, href);
|
| +}
|
| +
|
| +function excludeScript(href) {
|
| + return exclude(options.excludes.scripts, href);
|
| +}
|
| +
|
| +function excludeStyle(href) {
|
| + return exclude(options.excludes.styles, href);
|
| +}
|
| +
|
| +function readFile(file) {
|
| + var content = fs.readFileSync(file, 'utf8');
|
| + return content.replace(/^\uFEFF/, '');
|
| +}
|
| +
|
| +// inline relative linked stylesheets into <style> tags
|
| +function inlineSheets($, inputPath, outputPath) {
|
| + searchAll($, 'link[rel="stylesheet"]').each(function() {
|
| + var el = $(this);
|
| + var href = el.attr('href');
|
| + if (href && !excludeStyle(href)) {
|
| +
|
| + var rel = href;
|
| + var inputPath = path.dirname(options.input);
|
| + if (constants.ABS_URL.test(rel)) {
|
| + var abs = path.resolve(inputPath, path.join(options.abspath, rel));
|
| + rel = path.relative(options.outputDir, abs);
|
| + }
|
| +
|
| + var filepath = path.resolve(options.outputDir, rel);
|
| + // fix up paths in the stylesheet to be relative to the location of the style
|
| + var content = pathresolver.rewriteURL(path.dirname(filepath), outputPath, readFile(filepath));
|
| + var styleEl = whacko('<style>' + content + '</style>');
|
| + // clone attributes
|
| + styleEl.attr(el.attr());
|
| + // don't set href or rel on the <style>
|
| + styleEl.attr('href', null);
|
| + styleEl.attr('rel', null);
|
| + el.replaceWith(whacko.html(styleEl));
|
| + }
|
| + });
|
| +}
|
| +
|
| +function inlineScripts($, dir) {
|
| + searchAll($, constants.JS_SRC).each(function() {
|
| + var el = $(this);
|
| + var src = el.attr('src');
|
| + if (src && !excludeScript(src)) {
|
| +
|
| + var rel = src;
|
| + var inputPath = path.dirname(options.input);
|
| + if (constants.ABS_URL.test(rel)) {
|
| + var abs = path.resolve(inputPath, path.join(options.abspath, rel));
|
| + rel = path.relative(options.outputDir, abs);
|
| + }
|
| +
|
| + var filepath = path.resolve(dir, rel);
|
| + var content = readFile(filepath);
|
| + // NOTE: reusing UglifyJS's inline script printer (not exported from OutputStream :/)
|
| + content = content.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
|
| + el.replaceWith('<script>' + content + '</script>');
|
| + }
|
| + });
|
| +}
|
| +
|
| +function concat(filename) {
|
| + if (!read[filename]) {
|
| + read[filename] = true;
|
| + var $ = whacko.load(readFile(filename));
|
| + var dir = path.dirname(filename);
|
| + pathresolver.resolvePaths($, dir, options.outputDir, options.abspath);
|
| + processImports($);
|
| + inlineSheets($, dir, options.outputDir);
|
| + return $;
|
| + } else if (options.verbose) {
|
| + console.log('Dependency deduplicated');
|
| + }
|
| +}
|
| +
|
| +function processImports($, mainDoc) {
|
| + var bodyContent = [];
|
| + searchAll($, constants.IMPORTS).each(function() {
|
| + var el = $(this);
|
| + var href = el.attr('href');
|
| + if (!excludeImport(href)) {
|
| + var rel = href;
|
| + var inputPath = path.dirname(options.input);
|
| + if (constants.ABS_URL.test(rel)) {
|
| + var abs = path.resolve(inputPath, path.join(options.abspath, rel));
|
| + rel = path.relative(options.outputDir, abs);
|
| + }
|
| + var $$ = concat(path.resolve(options.outputDir, rel));
|
| + if (!$$) {
|
| + // remove import link
|
| + el.remove();
|
| + return;
|
| + }
|
| + // append import document head to main document head
|
| + el.replaceWith($$('head').html());
|
| + var bodyHTML = $$('body').html();
|
| + // keep the ordering of the import body in main document, before main document's body
|
| + bodyContent.push(bodyHTML);
|
| + } else if (!options.keepExcludes) {
|
| + // if the path is excluded for being absolute, then the import link must remain
|
| + var absexclude = options.abspath ? constants.REMOTE_ABS_URL : constants.ABS_URL;
|
| + if (!absexclude.test(href)) {
|
| + el.remove();
|
| + }
|
| + }
|
| + });
|
| + // prepend the import document body contents to the main document, in order
|
| + var content = bodyContent.join('\n');
|
| + // hide import body content in the main document
|
| + if (mainDoc && content) {
|
| + content = '<div hidden>' + content + '</div>';
|
| + }
|
| + $('body').prepend(content);
|
| +}
|
| +
|
| +function findScriptLocation($) {
|
| + var pos = $('body').last();
|
| + if (!pos.length) {
|
| + pos = $.root();
|
| + }
|
| + return pos;
|
| +}
|
| +
|
| +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 compressJS(content, inline) {
|
| + try {
|
| + var ast = uglify.parse(content);
|
| + return ast.print_to_string({inline_script: inline});
|
| + } catch (e) {
|
| + // return a useful error
|
| + var js_err = new Error('Compress JS Error');
|
| + js_err.detail = e.message + ' at line: ' + e.line + ' col: ' + e.col;
|
| + js_err.content = content;
|
| + js_err.toString = function() {
|
| + return this.message + '\n' + this.detail + '\n' + this.content;
|
| + };
|
| + throw js_err;
|
| + }
|
| +}
|
| +
|
| +function compressCSS(content) {
|
| + var out;
|
| + try {
|
| + var ast = cssom.parse(content);
|
| + out = ast.toString();
|
| + } catch (e) {
|
| + if (options.verbose) {
|
| + console.log('Error parsing CSS:', e.toString());
|
| + console.log('Falling back to removing newlines only');
|
| + }
|
| + out = content;
|
| + } finally {
|
| + return out.replace(/[\r\n]/g, '');
|
| + }
|
| +}
|
| +
|
| +function removeCommentsAndWhitespace($) {
|
| + function walk(node) {
|
| + var content, c;
|
| + if (!node) {
|
| + return;
|
| + } else if (isCommentOrEmptyTextNode(node)) {
|
| + $(node).remove();
|
| + return true;
|
| + } else if (node.type == 'script') {
|
| + // only run uglify on inline javascript scripts
|
| + if (!node.attribs.src && (!node.attribs.type || node.attribs.type == "text/javascript")) {
|
| + content = getTextContent(node);
|
| + setTextContent(node, compressJS(content, true));
|
| + }
|
| + } else if (node.type == 'style') {
|
| + content = getTextContent(node);
|
| + setTextContent(node, compressCSS(content));
|
| + } else if ((c = node.children)) {
|
| + for (var i = 0; i < c.length; i++) {
|
| + // since .remove() will modify this array, decrement `i` on successful comment removal
|
| + if (walk(c[i])) {
|
| + i--;
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // walk the whole AST from root
|
| + walk($.root().get(0));
|
| +}
|
| +
|
| +function writeFileSync(filename, data, eop) {
|
| + if (!options.outputHandler) {
|
| + fs.writeFileSync(filename, data, 'utf8');
|
| + } else {
|
| + options.outputHandler(filename, data, eop);
|
| + }
|
| +}
|
| +
|
| +function handleMainDocument() {
|
| + // reset shared buffers
|
| + read = {};
|
| + var content = options.inputSrc ? options.inputSrc.toString() : readFile(options.input);
|
| + var $ = whacko.load(content);
|
| + var dir = path.dirname(options.input);
|
| + pathresolver.resolvePaths($, dir, options.outputDir, options.abspath);
|
| + processImports($, true);
|
| + if (options.inline) {
|
| + inlineSheets($, dir, options.outputDir);
|
| + }
|
| +
|
| + if (options.inline) {
|
| + inlineScripts($, options.outputDir);
|
| + }
|
| +
|
| + searchAll($, constants.JS_INLINE).each(function() {
|
| + var el = $(this);
|
| + var content = getTextContent(el);
|
| + // find ancestor polymer-element node
|
| + var parentElement = el.closest('polymer-element').get(0);
|
| + if (parentElement) {
|
| + var match = constants.POLYMER_INVOCATION.exec(content);
|
| + var elementName = $(parentElement).attr('name');
|
| + if (match) {
|
| + var invocation = utils.processPolymerInvocation(elementName, match);
|
| + content = content.replace(match[0], invocation);
|
| + setTextContent(el, content);
|
| + }
|
| + }
|
| + });
|
| +
|
| + // strip noscript from elements, and instead inject explicit Polymer() invocation
|
| + // script, so registration order is preserved
|
| + searchAll($, constants.ELEMENTS_NOSCRIPT).each(function() {
|
| + var el = $(this);
|
| + var name = el.attr('name');
|
| + if (options.verbose) {
|
| + console.log('Injecting explicit Polymer invocation for noscript element "' + name + '"');
|
| + }
|
| + el.append('<script>Polymer(\'' + name + '\');</script>');
|
| + el.attr('noscript', null);
|
| + });
|
| +
|
| + // strip scripts into a separate file
|
| + if (options.csp) {
|
| + if (options.verbose) {
|
| + console.log('Separating scripts into separate file');
|
| + }
|
| +
|
| + // CSPify main page by removing inline scripts
|
| + var scripts = [];
|
| + searchAll($, constants.JS_INLINE).each(function() {
|
| + var el = $(this);
|
| + var content = getTextContent(el);
|
| + scripts.push(content);
|
| + el.remove();
|
| + });
|
| +
|
| + // join scripts with ';' to prevent breakages due to EOF semicolon insertion
|
| + var scriptName = path.basename(options.output, '.html') + '.js';
|
| + var scriptContent = scripts.join(';' + constants.EOL);
|
| + if (options.strip) {
|
| + scriptContent = compressJS(scriptContent, false);
|
| + }
|
| +
|
| + writeFileSync(path.resolve(options.outputDir, scriptName), scriptContent);
|
| + // insert out-of-lined script into document
|
| + findScriptLocation($).append('<script charset="utf-8" src="' + scriptName + '"></script>');
|
| + }
|
| +
|
| + deduplicateImports($);
|
| +
|
| + if (options.strip) {
|
| + removeCommentsAndWhitespace($);
|
| + }
|
| +
|
| + writeFileSync(options.output, $.html(), true);
|
| +}
|
| +
|
| +function deduplicateImports($) {
|
| + var imports = {};
|
| + searchAll($, constants.IMPORTS).each(function() {
|
| + var el = $(this);
|
| + var href = el.attr('href');
|
| + // TODO(dfreedm): allow a user defined base url?
|
| + var abs = url.resolve('http://', href);
|
| + if (!imports[abs]) {
|
| + imports[abs] = true;
|
| + } else {
|
| + if(options.verbose) {
|
| + console.log('Import Dependency deduplicated');
|
| + }
|
| + el.remove();
|
| + }
|
| + });
|
| +}
|
| +
|
| +exports.processDocument = handleMainDocument;
|
| +exports.setOptions = setOptions;
|
|
|