Repository: Polymer/vulcanize
Pull request #165 (2015-04-24 15:58:47)

Inline external stylesheets inside dom-modules.


Patch:
diff --git a/README.md b/README.md
index b38d5fc..9c123dd 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ This will install `vulcanize` to `/usr/local/bin/vulcanize`.
 - `--strip-excludes`: Exclude a subpath and remove any links referencing it.
 - `--implicit-strip`: Like specifying --strip-excludes for all the transitive dependencies of imports specified with `--exclude`.
 - `--inline-scripts`: Inline external scripts.
+- `--inline-css`: Inline external stylesheets.
 
 ## Usage
 The command
diff --git a/bin/vulcanize b/bin/vulcanize
index 8a103d3..2263585 100755
--- a/bin/vulcanize
+++ b/bin/vulcanize
@@ -26,6 +26,7 @@ var help = [
   '  -v|--version: print version number',
   '  -p <arg>|--abspath <arg>: use <arg> as the "webserver root", make all adjusted urls absolute',
   '  --inline-scripts: Inline external scripts',
+  '  --inline-css: Inline external stylesheets',
   '  --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.',
   '  --implicit-strip: Equivalent to setting --strip-excludes for the transitive dependencies of imports specified in --exclude',
@@ -71,7 +72,8 @@ var args = nopt(
     exclude: [String, Array],
     'strip-exclude': [String, Array],
     'implicit-strip': Boolean,
-    'inline-scripts': Boolean
+    'inline-scripts': Boolean,
+    'inline-css': Boolean
   },
   {
     '-h': ['--help'],
@@ -120,6 +122,7 @@ args.excludes = args.exclude || [];
 args.stripExcludes = args['strip-exclude'] || [];
 args.implicitStrip = args['implicit-strip'];
 args.inlineScripts = args['inline-scripts'];
+args.inlineCss = args['inline-css'];
 
 vulcan.setOptions(args);
 vulcan.process(target, function(err, content) {
diff --git a/example/bower.json b/example/bower.json
index cb74481..39680bc 100644
--- a/example/bower.json
+++ b/example/bower.json
@@ -14,6 +14,6 @@
     "tests"
   ],
   "dependencies": {
-    "polymer": "Polymer/polymer#0.8.0-rc.2"
+    "polymer": "Polymer/polymer#0.8.0-rc.7"
   }
 }
diff --git a/example/external.css b/example/external.css
new file mode 100644
index 0000000..77e6d56
--- /dev/null
+++ b/example/external.css
@@ -0,0 +1,3 @@
+h1 {
+  color: blue;
+};
\ No newline at end of file
diff --git a/example/index.html b/example/index.html
index 9d31ffe..1f9af4c 100644
--- a/example/index.html
+++ b/example/index.html
@@ -8,7 +8,9 @@
 </head>
 <body>
   <dom-module id="my-foo">
+    <link rel="import" type="css" href="external.css">
     <template>
+      <h1>LargeText</h1>
       <f-f></f-f>
     </template>
   </dom-module>
diff --git a/example/sub/import.html b/example/sub/import.html
index d24e5b7..76b68db 100644
--- a/example/sub/import.html
+++ b/example/sub/import.html
@@ -5,7 +5,7 @@
     <a href="https://www.google.com">Search</a>
     <br>
     <img src="cat.jpg">
-    <img src="http://placekitten.com/g/300/300">
+    <img src="http://placekitten.com/g/200/300">
   </template>
 </dom-module>
 <script>
diff --git a/lib/matchers.js b/lib/matchers.js
index 2f29dd9..7ffcb18 100644
--- a/lib/matchers.js
+++ b/lib/matchers.js
@@ -32,6 +32,17 @@ var jsMatcher = p.AND(
   )
 );
 
+var externalStyle = p.AND(
+  p.hasTagName('link'),
+  p.hasAttrValue('rel', 'stylesheet')
+);
+  // polymer specific external stylesheet
+var polymerExternalStyle = p.AND(
+  p.hasTagName('link'),
+  p.hasAttrValue('rel', 'import'),
+  p.hasAttrValue('type', 'css')
+);
+
 var styleMatcher = p.AND(
   p.hasTagName('style'),
   p.OR(
@@ -63,6 +74,8 @@ module.exports = {
   urlAttrs: urlAttrs,
   JS: jsMatcher,
   CSS: styleMatcher,
+  CSS_LINK: externalStyle,
+  POLY_CSS_LINK: polymerExternalStyle,
   JS_SRC: p.AND(p.hasAttr('src'), jsMatcher),
   JS_INLINE: p.AND(p.NOT(p.hasAttr('src')), jsMatcher),
   commentOrEmptyText: function(node) {
diff --git a/lib/vulcan.js b/lib/vulcan.js
index deceb18..1692146 100644
--- a/lib/vulcan.js
+++ b/lib/vulcan.js
@@ -178,6 +178,26 @@ function inlineScripts(doc, href) {
   return Promise.all(scriptPromises).then(function(){ return doc; });
 }
 
+
+// inline scripts into document, returns a promise resolving to document.
+function inlineCss(doc, href) {
+  var URL = require('url');
+  var css_links = dom5.queryAll(doc, matchers.POLY_CSS_LINK);
+  var cssPromises = css_links.map(function(link) {
+    var tag = link;
+    var src = dom5.getAttribute(tag, 'href');
+    var uri = URL.resolve(href, src);
+    // let the loader handle the requests
+    return Options.loader.request(uri).then(function(content) {
+      var style = dom5.constructors.element('style');
+      dom5.setTextContent(style, '\n' + content + '\n');
+      dom5.replace(tag, style);
+    });
+  });
+  // When all style imports are read, return the document
+  return Promise.all(cssPromises).then(function(){ return doc; });
+}
+
 function getImplicitExcludes(excludes) {
   // Build a loader that doesn't have to stop at our excludes, since we need them.
   var loader = buildLoader(Options.abspath, null);
@@ -237,6 +257,12 @@ function _process(target, cb) {
       return inlineScripts(doc, target);
     });
   }
+  if (Options.inlineCss) {
+
+    chain = chain.then(function(doc) {
+      return inlineCss(doc, target);
+    });
+  }
   chain.then(function(flatDoc) {
     cb(null, dom5.serialize(flatDoc));
   }).catch(cb);
diff --git a/package.json b/package.json
index cf26759..80c6c62 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,9 @@
     "vulcanize": "bin/vulcanize"
   },
   "dependencies": {
-    "dom5": "^1.0.1",
+    "dom5": "^1.0.2",
     "es6-promise": "^2.1.0",
-    "hydrolysis": "^1.1.0",
+    "hydrolysis": "^1.3.0",
     "nopt": "^3.0.1"
   },
   "devDependencies": {


Test patch:
diff --git a/test/html/imports/simple-import.html b/test/html/imports/simple-import.html
index 044ff38..d122b8e 100644
--- a/test/html/imports/simple-import.html
+++ b/test/html/imports/simple-import.html
@@ -8,8 +8,8 @@
     subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
 -->
 <dom-module id="my-element">
+  <link rel="import" type="css" href="simple-style.css">
   <template>
-  <link rel="stylesheet" href="simple-style.css" shim-shadowdom>
   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 630 630">
     <rect x="0" y="0" width="630" height="630" fill="#3c790a"/>
     <path d="m212 497c11"/>
diff --git a/test/test.js b/test/test.js
index 387289e..72efec4 100644
--- a/test/test.js
+++ b/test/test.js
@@ -239,7 +239,8 @@ suite('Vulcan', function() {
       var imports = preds.AND(
         preds.hasTagName('link'),
         preds.hasAttrValue('rel', 'import'),
-        preds.hasAttr('href')
+        preds.hasAttr('href'),
+        preds.NOT(preds.hasAttrValue('type', 'css'))
       );
       process(inputPath, function(err, doc) {
         if (err) {
@@ -378,7 +379,8 @@ suite('Vulcan', function() {
       );
       var stylesheet = preds.AND(
         preds.hasTagName('link'),
-        preds.hasAttrValue('rel', 'stylesheet'),
+        preds.hasAttrValue('rel', 'import'),
+        preds.hasAttrValue('type', 'css'),
         preds.hasAttrValue('href', '/html/imports/simple-style.css')
       );
       var callback = function(err, doc) {
@@ -436,13 +438,13 @@ suite('Vulcan', function() {
     });
   });
 
-  suite('Inline Scripts', function(done) {
+  suite('Inline Scripts', function() {
     var options = {
       inlineScripts: true
     };
     var matchers = require('../lib/matchers');
 
-    test('All scripts are inlined', function() {
+    test('All scripts are inlined', function(done) {
       var callback = function(err, doc) {
         if (err) {
           return done(err);
@@ -454,4 +456,22 @@ suite('Vulcan', function() {
       process(inputPath, callback, options);
     });
   });
+
+  suite('Inline CSS', function() {
+    var options = {
+      inlineCss: true
+    };
+    var matchers = require('../lib/matchers');
+    test('All styles are inlined', function(done) {
+      var callback = function(err, doc) {
+        if (err) {
+          return done(err);
+        }
+        var links = dom5.queryAll(doc, matchers.POLY_CSS_LINK);
+        assert.equal(links.length, 0);
+        done();
+      };
+      process(inputPath, callback, options);
+    });
+  });
 });