Repository: Polymer/vulcanize
Pull request #256 (2015-09-30 23:02:52)

Custom protocol handling.


Patch:
diff --git a/.gitignore b/.gitignore
index 2a5e850..10884ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 node_modules
 example/bower_components
+**/.DS_Store
diff --git a/README.md b/README.md
index ae3259a..6a4d05e 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ for this step).
 - `--inline-scripts`: Inline external scripts.
 - `--inline-css`: Inline external stylesheets.
 - `--add-import <path>`: Add this import to the target HTML before vulcanizing. Can be used multiple times.
+- `--redirect <uri>|<path>`: Takes an argument in the form of URI|PATH where url is a URI composed of a protocol, hostname, and path and PATH is a local filesystem path to replace the matched URI part with. Multiple redirects may be specified; the earliest ones have the highest priority.
 - `--strip-comments`: Strips all HTML comments not containing an @license from the document.
 - `--no-implicit-strip`: *DANGEROUS*! Avoid stripping imports of the transitive dependencies of imports specified with `--exclude`. May result in duplicate javascript inlining.
 
diff --git a/bin/vulcanize b/bin/vulcanize
index b996d04..e02ed3c 100755
--- a/bin/vulcanize
+++ b/bin/vulcanize
@@ -31,6 +31,7 @@ var help = [
   '  --exclude <path>: exclude a subpath from root. Use multiple times to exclude multiple paths. Tags to excluded paths are kept.',
   '  --strip-exclude: Exclude a subpath and strip the link that includes it.',
   '  --strip-comments: Strips all HTML comments not containing an @license from the document',
+  '  --redirect <uri>|<path>: Takes an argument in the form of URI|PATH where url is a URI composed of a protocol, hostname, and path and PATH is a local filesystem path to replace the matched URI part with. Multiple redirects may be specified; the earliest ones have the highest priority.',
   '  --no-implicit-strip: DANGEROUS! Avoid stripping imports of the transitive dependencies of imports specified with `--exclude`. May result in duplicate javascript inlining.',
   'Examples:',
   '  The command',
@@ -72,6 +73,7 @@ var args = nopt(
     version: Boolean,
     abspath: String,
     exclude: [String, Array],
+    redirect: [String, Array],
     'add-import': [String, Array],
     'strip-exclude': [String, Array],
     'strip-comments': Boolean,
@@ -124,6 +126,7 @@ function stringToRegExp(str) {
 
 args.addedImports = args['add-import'] || [];
 args.excludes = args.exclude || [];
+args.redirect = args.redirect || [];
 args.stripExcludes = args['strip-exclude'] || [];
 args.stripComments = args['strip-comments'];
 args.implicitStrip = !args['no-implicit-strip'];
diff --git a/lib/vulcan.js b/lib/vulcan.js
index 033e475..a71d0e7 100644
--- a/lib/vulcan.js
+++ b/lib/vulcan.js
@@ -30,7 +30,7 @@ var Promise = global.Promise || require('es6-promise').Promise;
  */
 var singleton;
 
-function buildLoader(abspath, excludes) {
+function buildLoader(abspath, excludes, redirects) {
   var loader = new hyd.Loader();
   var fsOptions = {};
   if (abspath) {
@@ -45,6 +45,31 @@ function buildLoader(abspath, excludes) {
       loader.addResolver(new hyd.NoopResolver(r));
     });
   }
+  var redirectOptions = {};
+  if (abspath) {
+    redirectOptions.root =  path.resolve(abspath);
+    redirectOptions.basePath = '/';
+  }
+  var redirectConfigs = [];
+  for (var i = 0; i < redirects.length; i++) {
+    var split = redirects[i].split('|');
+    var uri = url.parse(split[0]);
+    var replacement = split[1];
+    if (!uri || !replacement) {
+      throw new Error("Invalid redirect config: " + redirects[i]);
+    }
+    var redirectConfig = new hyd.RedirectResolver.ProtocolRedirect({
+        protocol: uri.protocol,
+        hostname: uri.hostname,
+        path: uri.pathname,
+        redirectPath: replacement
+      });
+    redirectConfigs.push(redirectConfig);
+  }
+  if (redirectConfigs.length > 0) {
+    redirectOptions.redirects = redirectConfigs;
+    loader.addResolver(new hyd.RedirectResolver(redirectOptions));
+  }
   return loader;
 }
 
@@ -60,8 +85,9 @@ var Vulcan = function Vulcan(opts) {
   this.enableCssInlining = Boolean(opts.inlineCss);
   this.enableScriptInlining = Boolean(opts.inlineScripts);
   this.inputUrl = String(opts.inputUrl) === opts.inputUrl ? opts.inputUrl : '';
+  this.redirects = Array.isArray(opts.redirects) ? opts.redirects : [];
   if (!opts.loader) {
-    this.loader = buildLoader(this.abspath, this.excludes);
+    this.loader = buildLoader(this.abspath, this.excludes, this.redirects);
   } else {
     this.loader = opts.loader;
   }
@@ -266,7 +292,7 @@ Vulcan.prototype = {
 
   getImplicitExcludes: function getImplicitExcludes(excludes) {
     // Build a loader that doesn't have to stop at our excludes, since we need them.
-    var loader = buildLoader(this.abspath, null);
+    var loader = buildLoader(this.abspath, null, this.redirects);
     var analyzer = new hyd.Analyzer(true, loader);
     var analyzedExcludes = [];
     excludes.forEach(function(exclude) {
diff --git a/package.json b/package.json
index a39ece0..bcbf6ca 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
   "dependencies": {
     "dom5": "^1.1.0",
     "es6-promise": "^2.1.0",
-    "hydrolysis": "^1.19.0",
+    "hydrolysis": "^1.19.1",
     "nopt": "^3.0.1",
     "path-posix": "^1.0.0"
   },


Test patch:
diff --git a/test/html/custom-protocol.html b/test/html/custom-protocol.html
new file mode 100644
index 0000000..6c5069e
--- /dev/null
+++ b/test/html/custom-protocol.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="import" href="chrome://imports/base.html">
+    <link rel="import" href="biz://cool/comments.html">
+    <title></title>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/test/test.js b/test/test.js
index c03d1d7..fe7f827 100644
--- a/test/test.js
+++ b/test/test.js
@@ -484,6 +484,26 @@ suite('Vulcan', function() {
     });
   });
 
+  suite('Redirect', function() {
+    test('Redirected paths load properly', function(done) {
+      var options = {
+        redirects: [
+          'chrome://imports/|test/html/imports/',
+          'biz://cool/|test/html'
+        ]
+      };
+
+      var callback = function(err, doc) {
+        if (err) {
+          return done(err);
+        }
+        assert(doc);
+        done();
+      };
+      process(path.resolve('test/html/custom-protocol.html'), callback, options);
+    });
+  });
+
   suite('Excludes', function() {
 
     var excluded = preds.AND(