diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..e21bdca --- /dev/null +++ b/.jshintrc @@ -0,0 +1,93 @@ +{ + // From JSHint Default Configuration File + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() + "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : true, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "noyield" : false, // true: Tolerate generator functions with no yield statement in them. + "notypeof" : false, // true: Tolerate invalid typeof operator values + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jasmine" : false, // Jasmine + "jquery" : false, // jQuery + "mocha" : true, // Mocha + "mootools" : false, // MooTools + "node" : true, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "qunit" : false, // QUnit + "rhino" : false, // Rhino + "shelljs" : false, // ShellJS + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : { + // jasmine helpers + "expect": false, + "describe": false, + "beforeEach": false, + "it": false + } +} \ No newline at end of file diff --git a/README.md b/README.md index 335609e..21a5c2e 100644 --- a/README.md +++ b/README.md @@ -113,10 +113,10 @@ HTML as well as the body. Your template might look like this: My App - + - + ``` @@ -153,14 +153,24 @@ Note the plugin will throw an error if you specify both `template` _and_ The `o` variable in the template is the data that is passed in when the template is rendered. This variable has the following attributes: - `htmlWebpackPlugin`: data specific to this plugin - - `htmlWebpackPlugin.assets`: a massaged representation of the + - `htmlWebpackPlugin.files`: a massaged representation of the `assetsByChunkName` attribute of webpack's [stats](https://github.com/webpack/docs/wiki/node.js-api#stats) object. It contains a mapping from entry point name to the bundle filename, eg: ```json "htmlWebpackPlugin": { - "assets": { - "head": "assets/head_bundle.js", - "main": "assets/main_bundle.js" + "files": { + "css": [ "main.css" ], + "js": [ "assets/head_bundle.js", "assets/main_bundle.js"] + "chunks": { + "head": { + "entry": "assets/head_bundle.js", + "css": [ "main.css" ] + }, + "main": { + "entry": "assets/main_bundle.js", + "css": [] + }, + } } } ``` diff --git a/default_index.html b/default_index.html index e32adc3..7609a39 100644 --- a/default_index.html +++ b/default_index.html @@ -1,12 +1,15 @@ - + {%=o.htmlWebpackPlugin.options.title || 'Webpack App'%} + {% for (var css in o.htmlWebpackPlugin.files.css) { %} + + {% } %} - {% for (var chunk in o.htmlWebpackPlugin.assets) { %} - + {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %} + {% } %} diff --git a/index.js b/index.js index 0f63ba3..e74c4eb 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +'use strict'; var fs = require('fs'); var path = require('path'); var tmpl = require('blueimp-tmpl').tmpl; @@ -8,34 +9,37 @@ function HtmlWebpackPlugin(options) { HtmlWebpackPlugin.prototype.apply = function(compiler) { var self = this; - compiler.plugin('emit', function(compiler, callback) { - var webpackStatsJson = compiler.getStats().toJson(); + compiler.plugin('emit', function(compilation, callback) { + var webpackStatsJson = compilation.getStats().toJson(); var templateParams = {}; templateParams.webpack = webpackStatsJson; templateParams.htmlWebpackPlugin = {}; - templateParams.htmlWebpackPlugin.assets = self.htmlWebpackPluginAssets(compiler, webpackStatsJson); + templateParams.htmlWebpackPlugin.assets = self.htmlWebpackPluginLegacyAssets(compilation, webpackStatsJson); + templateParams.htmlWebpackPlugin.files = self.htmlWebpackPluginAssets(compilation, webpackStatsJson); templateParams.htmlWebpackPlugin.options = self.options; + // If the hash option is true append the webpack hash to all assets + templateParams.htmlWebpackPlugin.querystring = self.options.hash ? '?' + webpackStatsJson.hash : ''; var outputFilename = self.options.filename || 'index.html'; if (self.options.templateContent && self.options.template) { - compiler.errors.push(new Error('HtmlWebpackPlugin: cannot specify both template and templateContent options')); + compilation.errors.push(new Error('HtmlWebpackPlugin: cannot specify both template and templateContent options')); callback(); } else if (self.options.templateContent) { - self.emitHtml(compiler, self.options.templateContent, templateParams, outputFilename); + self.emitHtml(compilation, self.options.templateContent, templateParams, outputFilename); callback(); } else { var templateFile = self.options.template; if (!templateFile) { templateFile = path.join(__dirname, 'default_index.html'); } - compiler.fileDependencies.push(templateFile); + compilation.fileDependencies.push(templateFile); fs.readFile(templateFile, 'utf8', function(err, htmlTemplateContent) { if (err) { - compiler.errors.push(new Error('HtmlWebpackPlugin: Unable to read HTML template "' + templateFile + '"')); + compilation.errors.push(new Error('HtmlWebpackPlugin: Unable to read HTML template "' + templateFile + '"')); } else { - self.emitHtml(compiler, htmlTemplateContent, templateParams, outputFilename); + self.emitHtml(compilation, htmlTemplateContent, templateParams, outputFilename); } callback(); }); @@ -43,9 +47,14 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) { }); }; -HtmlWebpackPlugin.prototype.emitHtml = function(compiler, htmlTemplateContent, templateParams, outputFilename) { - var html = tmpl(htmlTemplateContent, templateParams); - compiler.assets[outputFilename] = { +HtmlWebpackPlugin.prototype.emitHtml = function(compilation, htmlTemplateContent, templateParams, outputFilename) { + var html; + try { + html = tmpl(htmlTemplateContent, templateParams); + } catch(e) { + compilation.errors.push(new Error('HtmlWebpackPlugin: template error ' + e)); + } + compilation.assets[outputFilename] = { source: function() { return html; }, @@ -55,24 +64,59 @@ HtmlWebpackPlugin.prototype.emitHtml = function(compiler, htmlTemplateContent, t }; }; -HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compiler, webpackStatsJson) { - var assets = {}; + +HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webpackStatsJson) { + var assets = { + // Will contain all js & css files by chunk + chunks: {}, + // Will contain all js files + js: [], + // Will contain all css files + css: [], + // Will contain the html5 appcache manifest files if it exists + manifest: Object.keys(compilation.assets).filter(function(assetFile){ + return path.extname(assetFile) === '.appcache'; + })[0] + }; + var publicPath = compilation.options.output.publicPath || ''; + for (var chunk in webpackStatsJson.assetsByChunkName) { - var chunkValue = webpackStatsJson.assetsByChunkName[chunk]; + assets.chunks[chunk] = {}; + + // Prepend the public path to all chunk files + var chunkFiles = [].concat(webpackStatsJson.assetsByChunkName[chunk]).map(function(chunkFile) { + return publicPath + chunkFile; + }); // Webpack outputs an array for each chunk when using sourcemaps - if (chunkValue instanceof Array) { - // Is the main bundle always the first element? - chunkValue = chunkValue[0]; - } + // But we need only the entry file + var entry = chunkFiles[0]; + assets.chunks[chunk].entry = entry; + assets.js.push(entry); - if (compiler.options.output.publicPath) { - chunkValue = compiler.options.output.publicPath + chunkValue; - } - assets[chunk] = chunkValue; + // Gather all css files + var css = chunkFiles.filter(function(chunkFile){ + return path.extname(chunkFile) === '.css'; + }); + assets.chunks[chunk].css = css; + assets.css = assets.css.concat(css); } return assets; }; +/** + * A helper to support the templates written for html-webpack-plugin <= 1.1.0 + */ +HtmlWebpackPlugin.prototype.htmlWebpackPluginLegacyAssets = function(compilation, webpackStatsJson) { + var assets = this.htmlWebpackPluginAssets(compilation, webpackStatsJson); + var legacyAssets = {}; + Object.keys(assets.chunks).forEach(function(chunkName){ + legacyAssets[chunkName] = assets.chunks[chunkName].entry; + }); + return legacyAssets; +}; + + + module.exports = HtmlWebpackPlugin; diff --git a/package.json b/package.json index 7c21803..233d48d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Simplifies creation of HTML files to serve your webpack bundles", "main": "index.js", "scripts": { - "test": "jshint *.js spec && jasmine-node --captureExceptions spec" + "test": "jshint -c .jshintrc *.js spec && jasmine-node --captureExceptions spec" }, "repository": { "type": "git", diff --git a/spec/HtmlWebpackPluginSpec.js b/spec/HtmlWebpackPluginSpec.js index e737806..091978c 100644 --- a/spec/HtmlWebpackPluginSpec.js +++ b/spec/HtmlWebpackPluginSpec.js @@ -1,3 +1,4 @@ +'use strict'; var path = require('path'); var fs = require('fs'); var webpack = require('webpack'); @@ -17,7 +18,7 @@ function testHtmlPlugin(webpackConfig, expectedResults, outputFile, done) { if (expectedResult instanceof RegExp) { expect(htmlContent).toMatch(expectedResult); } else { - expect(htmlContent).toContain(expectedResult); + expect(htmlContent).toContain(expectedResult.replace('%hash%', stats.hash)); } } done(); @@ -66,7 +67,7 @@ describe('HtmlWebpackPlugin', function() { }, plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'fixtures/test.html')})] }, - [' + + diff --git a/spec/fixtures/legacy_default_index.html b/spec/fixtures/legacy_default_index.html new file mode 100644 index 0000000..2595dd7 --- /dev/null +++ b/spec/fixtures/legacy_default_index.html @@ -0,0 +1,12 @@ + + + + + {%=o.htmlWebpackPlugin.options.title || 'Webpack App'%} + + + {% for (var chunk in o.htmlWebpackPlugin.assets) { %} + + {% } %} + + \ No newline at end of file diff --git a/spec/fixtures/test.html b/spec/fixtures/test.html index f7473d1..2764033 100644 --- a/spec/fixtures/test.html +++ b/spec/fixtures/test.html @@ -6,6 +6,6 @@

Some unique text

- +