From 7afacfc2cbf3913035b8c744b0195c6b7bfe3159 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=91=A8=E6=B1=89=E6=88=90?= <z308114274@gmail.com>
Date: Tue, 3 Jul 2018 08:53:17 +0800
Subject: [PATCH] add favicon (#502)

---
 http.js               |  5 +++++
 index.js              |  4 +++-
 lib/cmd-build.js      | 52 +++++++++++++++++++++++++++----------------
 lib/graph-document.js |  9 ++++++++
 lib/graph-favicon.js  | 22 ++++++++++++++++++
 5 files changed, 72 insertions(+), 20 deletions(-)
 create mode 100644 lib/graph-favicon.js

diff --git a/http.js b/http.js
index 7a357349..79d493cd 100644
--- a/http.js
+++ b/http.js
@@ -36,6 +36,11 @@ function start (entry, opts) {
     if (name === 'styles:bundle') emitter.emit('styles:bundle', node)
   })
 
+  router.route(/^\/(favicon\.)(ico|png|gif)$/, function (req, res, params) {
+    var filename = params[1] + params[2]
+    pump(send(req, filename), res)
+  })
+
   router.route(/^\/manifest\.json$/, function (req, res, params) {
     compiler.manifest(function (err, node) {
       if (err) {
diff --git a/index.js b/index.js
index daf8e9d8..816f1587 100644
--- a/index.js
+++ b/index.js
@@ -12,6 +12,7 @@ var ServerRender = require('./ssr')
 
 var assetsNode = require('./lib/graph-assets')
 var documentNode = require('./lib/graph-document')
+var faviconNode = require('./lib/graph-favicon')
 var manifestNode = require('./lib/graph-manifest')
 var reloadNode = require('./lib/graph-reload')
 var scriptNode = require('./lib/graph-script')
@@ -100,12 +101,13 @@ function Bankai (entry, opts) {
   })
 
   // Insert nodes into the graph.
-  var documentDependencies = [ 'assets:list', 'manifest:bundle', 'styles:bundle', 'scripts:bundle' ]
+  var documentDependencies = [ 'assets:list', 'manifest:bundle', 'styles:bundle', 'scripts:bundle', 'favicon:bundle' ]
 
   if (opts.reload) {
     documentDependencies.push('reload:bundle')
     this.graph.node('reload', reloadNode)
   }
+  this.graph.node('favicon', faviconNode)
   this.graph.node('assets', assetsNode)
   this.graph.node('documents', documentDependencies, documentNode)
   this.graph.node('manifest', manifestNode)
diff --git a/lib/cmd-build.js b/lib/cmd-build.js
index 28cd879c..0b31aaa0 100644
--- a/lib/cmd-build.js
+++ b/lib/cmd-build.js
@@ -50,6 +50,7 @@ function build (entry, outdir, opts) {
       var stepName = nodeName + ':' + edgeName
       if (stepName === 'assets:list') writeAssets('assets')
       if (stepName === 'documents:list') writeDocuments('documents')
+      if (stepName === 'favicon:bundle') writeFavicon()
       if (stepName === 'scripts:bundle') writeScripts('scripts')
       if (stepName === 'service-worker:bundle') {
         var filename = compiler.graph.metadata.serviceWorker
@@ -76,32 +77,27 @@ function build (entry, outdir, opts) {
         var dirname = utils.dirname(dest)
 
         if (dirname === dest) {
-          copy(done)
+          copy(src, dest, compiler.dirname, done)
         } else {
           mkdirp(dirname, function (err) {
             if (err) return done(err)
-            copy(done)
+            copy(src, dest, compiler.dirname, done)
           })
         }
+      }
+    }
 
-        // Node <= 8.x does not have fs.copyFile(). This API is cool because
-        // on some OSes it is zero-copy all the way; e.g. never leaves the
-        // kernel! :D
-        function copy (done) {
-          fsCompare.mtime(src, dest, function (err, diff) {
-            if (err) return done(err)
-            if (diff === 1) {
-              if (fs.copyFile) fs.copyFile(src, dest, fin)
-              else pump(fs.createReadStream(src), fs.createWriteStream(dest), fin)
-            }
-          })
-          function fin (err) {
-            if (err) return done(err)
-            created(compiler.dirname, dest)
-            done()
-          }
-        }
+    function writeFavicon () {
+      var favicon = compiler.graph.data.favicon.bundle.buffer.toString()
+      if (favicon.length === 0) {
+        return
       }
+      var src = path.join(compiler.dirname, favicon)
+      var dest = path.join(outdir, favicon)
+      copy(src, dest, compiler.dirname, function (err) {
+        if (err) return log.error(err)
+        completed('favicon')
+      })
     }
 
     function writeScripts (step) {
@@ -182,6 +178,24 @@ function build (entry, outdir, opts) {
       }
     }
 
+    // Node <= 8.x does not have fs.copyFile(). This API is cool because
+    // on some OSes it is zero-copy all the way; e.g. never leaves the
+    // kernel! :D
+    function copy (src, dest, dirname, done) {
+      fsCompare.mtime(src, dest, function (err, diff) {
+        if (err) return done(err)
+        if (diff === 1) {
+          if (fs.copyFile) fs.copyFile(src, dest, fin)
+          else pump(fs.createReadStream(src), fs.createWriteStream(dest), fin)
+        }
+      })
+      function fin (err) {
+        if (err) return done(err)
+        created(dirname, dest)
+        done()
+      }
+    }
+
     function created (basedir, filename) {
       var relative = path.relative(process.cwd(), filename)
       log.info(`${clr('created', 'magenta')}: ${relative}`)
diff --git a/lib/graph-document.js b/lib/graph-document.js
index d81cbd91..d064cbf6 100644
--- a/lib/graph-document.js
+++ b/lib/graph-document.js
@@ -108,6 +108,7 @@ function node (state, createEdge) {
         }),
         preloadTag(),
         loadFontsTag({ fonts: fonts, base: base }),
+        faviconsTag({ favicon: state.favicon.bundle.buffer }),
         manifestTag({ base: base }),
         descriptionTag({ description: state.manifest.bundle.description }),
         themeColorTag({ color: state.manifest.bundle.color }),
@@ -197,6 +198,14 @@ function manifestTag (opts) {
   `.replace(/\n +/g, '')
 }
 
+function faviconsTag (opts) {
+  var favicon = opts.favicon.toString()
+  if (favicon.length > 0) {
+    return `<link rel="icon" type="image/x-icon" href="${favicon}">`
+  }
+  return ``
+}
+
 function viewportTag () {
   return `
     <meta charset="utf-8">
diff --git a/lib/graph-favicon.js b/lib/graph-favicon.js
new file mode 100644
index 00000000..56f0a255
--- /dev/null
+++ b/lib/graph-favicon.js
@@ -0,0 +1,22 @@
+var path = require('path')
+var utils = require('./utils')
+
+module.exports = node
+
+function node (state, createEdge) {
+  var dirname = state.metadata.dirname
+  var filenames = [
+    'favicon.ico',
+    'favicon.png',
+    'favicon.gif'
+  ]
+
+  // find favicon at the root
+  utils.find(dirname, filenames, function (err, filename) {
+    if (err || filename === void 0) {
+      createEdge('bundle', Buffer.from(''))
+      return
+    }
+    createEdge('bundle', Buffer.from(path.relative(dirname, filename)))
+  })
+}