From 5771dac3c9827062037bc1f9fbb5a76ad82b8a6c Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 8 May 2015 19:26:36 +0200 Subject: [PATCH 1/8] Add support for favicon and apple-touch-icon Fixes #28 --- CHANGES.md | 4 ++ README.md | 21 +++++++ default_index.html | 6 ++ index.js | 39 +++++++++++++ package.json | 2 + spec/HtmlWebpackPluginSpec.js | 85 +++++++++++++++++++++++++++++ spec/fixtures/apple-touch-icon.png | Bin 0 -> 3959 bytes spec/fixtures/appleTouchIcon.js | 1 + spec/fixtures/favicon.ico | Bin 0 -> 766 bytes spec/fixtures/favicon.js | 1 + 10 files changed, 159 insertions(+) create mode 100755 spec/fixtures/apple-touch-icon.png create mode 100644 spec/fixtures/appleTouchIcon.js create mode 100755 spec/fixtures/favicon.ico create mode 100644 spec/fixtures/favicon.js diff --git a/CHANGES.md b/CHANGES.md index 19eed1e..d16fd67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ Change History ============== +HEAD +---- +* Add `favicon.ico` and `apple-touch-icon.png` to the HTML + v1.2.0 ------ * Set charset using HTML5 meta attribute diff --git a/README.md b/README.md index c7765f3..e45f80c 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,27 @@ If you have any css assets in webpack's output (for example, css extracted with the [ExtractTextPlugin](https://github.com/webpack/extract-text-webpack-plugin)) then these will be included with `` tags in the HTML head. +Icons - Favicon & Apple Touch Icon +---------------------------------- + +The plugin will automatically pick up if there is a file named `favicon.ico` +or `apple-touch-icon.png` included in the build, and automatically add them +to the HTML. + +```html + + + + + Webpack App + + + + + + + +``` Configuration ------------- diff --git a/default_index.html b/default_index.html index 2dd1749..9bad69a 100644 --- a/default_index.html +++ b/default_index.html @@ -3,6 +3,12 @@ {%=o.htmlWebpackPlugin.options.title || 'Webpack App'%} + {% if (o.htmlWebpackPlugin.files.favicon) { %} + + {% } %} + {% if (o.htmlWebpackPlugin.files.appleTouchIcon) { %} + + {% } %} {% for (var css in o.htmlWebpackPlugin.files.css) { %} {% } %} diff --git a/index.js b/index.js index e9245da..1d87618 100644 --- a/index.js +++ b/index.js @@ -70,6 +70,32 @@ HtmlWebpackPlugin.prototype.emitHtml = function(compilation, htmlTemplateContent }; }; +HtmlWebpackPlugin.prototype.getAssetPathFromModuleName = function(publicPath, modules) { + var filenameRegexp = [/^favicon\.ico($|\?)/, /^apple-touch-icon\.png($|\?)/]; + + return _.chain(modules) + .filter(function (module) { + // If the module failed to load, skip it to properly propagate the error + if (module.failed) { + return false; + } + + var basename = path.basename(module.name); + return _.some(filenameRegexp, function(regexp) { + return regexp.test(basename); + }); + }) + .map(function (module) { + // If the assets is not base64-encoded + if (module.assets.length) { + return [path.parse(module.name).name, publicPath + module.assets[0]]; + } + + return [path.parse(module.name).name, module.source.substring(18, module.source.length - 1)]; + }) + .zipObject() + .value(); +}; HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webpackStatsJson, includedChunks, excludedChunks) { var self = this; @@ -148,6 +174,11 @@ HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webp // requires the same css. assets.css = _.uniq(assets.css); + assets.extraFiles = self.getAssetPathFromModuleName(publicPath, webpackStatsJson.modules); + + assets.favicon = assets.extraFiles.favicon; + assets.appleTouchIcon = assets.extraFiles['apple-touch-icon']; + return assets; }; @@ -173,6 +204,14 @@ HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, templateParams styles = styles.map(function(stylePath) { return ''; }); + // If there is an apple touch icon present, add it above any link-tags + if (assets.appleTouchIcon) { + styles.unshift(''); + } + // If there is a favicon present, add it above any link-tags + if (assets.favicon) { + styles.unshift(''); + } // Append scripts to body element html = html.replace(/(<\/body>)/i, function (match) { return scripts.join('') + match; diff --git a/package.json b/package.json index 09be008..bb62da2 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,12 @@ "devDependencies": { "css-loader": "^0.12.0", "extract-text-webpack-plugin": "^0.7.1", + "file-loader": "^0.8.1", "jasmine-node": "^1.14.5", "jshint": "^2.7.0", "rimraf": "^2.3.3", "style-loader": "^0.12.2", + "url-loader": "^0.5.5", "webpack": "^1.8.11" }, "dependencies": { diff --git a/spec/HtmlWebpackPluginSpec.js b/spec/HtmlWebpackPluginSpec.js index d6c1a5c..a7f2af7 100644 --- a/spec/HtmlWebpackPluginSpec.js +++ b/spec/HtmlWebpackPluginSpec.js @@ -486,4 +486,89 @@ describe('HtmlWebpackPlugin', function() { / - - -``` - Configuration ------------- You can pass a hash of configuration options to `HtmlWebpackPlugin`. @@ -86,7 +64,8 @@ Allowed values are as follows: You can specify a subdirectory here too (eg: `assets/admin.html`). - `template`: A html template (supports [blueimp templates](https://github.com/blueimp/JavaScript-Templates)). - `templateContent`: A html string or a function returning the html (supports [blueimp templates](https://github.com/blueimp/JavaScript-Templates)). -- `inject`: Inject all assets into the given `template` or `templateContent`. +- `inject`: Inject all assets into the given `template` or `templateContent`. +- `favicon`: Adds the given favicon path to the output html. - `minify`: Set to true or pass a [html-minifier](https://github.com/kangax/html-minifier#options-quick-reference) options object to minify the output. - `hash`: if `true` then append a unique webpack compilation hash to all included scripts and css files. This is useful for cache busting. @@ -133,58 +112,64 @@ once in your plugins array: Writing Your Own Templates -------------------------- If the default generated HTML doesn't meet your needs you can supply -your own [blueimp template](https://github.com/blueimp/JavaScript-Templates). -The [default template](https://github.com/ampedandwired/html-webpack-plugin/blob/master/default_index.html) -is a good starting point for writing your own. +your own template. The easiest way is to use the `inject` option and pass a custom html file. +The html-webpack-plugin will automatically inject all necessary css, js, manifest +and favicon files into the markup. + +```javascript +plugins: [ + new HtmlWebpackPlugin({ + inject: true, + template: 'my-index.html' + }) +] +``` -Let's say for example you wanted to put a webpack bundle into the head of your -HTML as well as the body. Your template might look like this: ```html My App - - ``` -To use this template, configure the plugin like this: -```javascript -{ - entry: 'index.js', - output: { - path: 'dist', - filename: 'index_bundle.js' - }, - plugins: [ - new HtmlWebpackPlugin({ - template: 'src/assets/my_template.html' - }) - ] -} -``` - Alternatively, if you already have your template's content in a String, you can pass it to the plugin using the `templateContent` option: ```javascript plugins: [ new HtmlWebpackPlugin({ + inject: true, templateContent: templateContentString }) ] ``` +You can use the [blueimp template](https://github.com/blueimp/JavaScript-Templates) syntax out of the box. +If the `inject` feature doesn't fit your needs and you want full control over the asset placement use the [default template](https://github.com/ampedandwired/html-webpack-plugin/blob/master/default_index.html) +as a starting point for writing your own. + The `templateContent` option can also be a function to use another template language like jade: ```javascript plugins: [ new HtmlWebpackPlugin({ - templateContent: function(templateParams, webpackCompiler) { + templateContent: function(templateParams, compilation) { // Return your template content synchronously here + return '..'; + } + }) +] +``` +Or the async version: +```javascript +plugins: [ + new HtmlWebpackPlugin({ + templateContent: function(templateParams, compilation, callback) { + // Return your template content synchronously here + callback(null, '..'); } }) ] From 3428698e54701b3899f4f616bc5fcb8f7af9f40f Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Sat, 16 May 2015 14:57:11 +0200 Subject: [PATCH 7/8] Fix typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b002be..5f775bb 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ Or the async version: plugins: [ new HtmlWebpackPlugin({ templateContent: function(templateParams, compilation, callback) { - // Return your template content synchronously here + // Return your template content asynchronously here callback(null, '..'); } }) From ce279debd525105f764250224c5c5e99791cb180 Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Sun, 17 May 2015 20:02:57 +0200 Subject: [PATCH 8/8] Deprecate htmlWebpackPlugin.assets --- index.js | 26 ++++++++++++++++++-------- spec/HtmlWebpackPluginSpec.js | 15 ++++++++++----- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 630d964..36ae451 100644 --- a/index.js +++ b/index.js @@ -24,13 +24,23 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) { }) // Generate the html .then(function() { - var templateParams = {}; - templateParams.webpack = webpackStatsJson; - templateParams.htmlWebpackPlugin = {}; - templateParams.htmlWebpackPlugin.assets = self.htmlWebpackPluginLegacyAssets(compilation, webpackStatsJson); - templateParams.htmlWebpackPlugin.files = self.htmlWebpackPluginAssets(compilation, webpackStatsJson, self.options.chunks, self.options.excludeChunks); - templateParams.htmlWebpackPlugin.options = self.options; - templateParams.webpackConfig = compilation.options; + var templateParams = { + webpack: webpackStatsJson, + webpackConfig: compilation.options, + htmlWebpackPlugin: { + files: self.htmlWebpackPluginAssets(compilation, webpackStatsJson, self.options.chunks, self.options.excludeChunks), + options: self.options, + } + }; + // Deprecate templateParams.htmlWebpackPlugin.assets + var assets = self.htmlWebpackPluginLegacyAssets(compilation, webpackStatsJson); + Object.defineProperty(templateParams.htmlWebpackPlugin, 'assets', { + get: function() { + compilation.errors.push('htmlWebpackPlugin.assets is deprecated - please use htmlWebpackPlugin.files instead'); + return assets; + } + }); + // Get/generate html return self.getTemplateContent(compilation, templateParams) .then(function(htmlTemplateContent) { @@ -110,7 +120,7 @@ HtmlWebpackPlugin.prototype.emitHtml = function(compilation, htmlTemplateContent } catch(e) { return Promise.reject(new Error('HtmlWebpackPlugin: template error ' + e)); } - + // Inject link and script elements into an existing html file if (this.options.inject) { html = this.injectAssetsIntoHtml(html, templateParams); diff --git a/spec/HtmlWebpackPluginSpec.js b/spec/HtmlWebpackPluginSpec.js index 73a8aac..1896fce 100644 --- a/spec/HtmlWebpackPluginSpec.js +++ b/spec/HtmlWebpackPluginSpec.js @@ -8,11 +8,16 @@ var HtmlWebpackPlugin = require('../index.js'); var OUTPUT_DIR = path.join(__dirname, '../dist'); -function testHtmlPlugin(webpackConfig, expectedResults, outputFile, done, hasErrors) { +function testHtmlPlugin(webpackConfig, expectedResults, outputFile, done, expectErrors) { outputFile = outputFile || 'index.html'; webpack(webpackConfig, function(err, stats) { expect(err).toBeFalsy(); - expect(stats.hasErrors()).toBe(!!hasErrors); + var compilationErrors = (stats.compilation.errors || []).join('\n'); + if (expectErrors) { + expect(compilationErrors).not.toBe(''); + } else { + expect(compilationErrors).toBe(''); + } var htmlContent = fs.readFileSync(path.join(OUTPUT_DIR, outputFile)).toString(); for (var i = 0; i < expectedResults.length; i++) { var expectedResult = expectedResults[i]; @@ -167,10 +172,10 @@ describe('HtmlWebpackPlugin', function() { }, plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'fixtures/legacy.html')})] }, - ['