diff --git a/InitialChunksPlugin.js b/InitialChunksPlugin.js new file mode 100644 index 0000000..4dd62c4 --- /dev/null +++ b/InitialChunksPlugin.js @@ -0,0 +1,125 @@ +/* eslint-disable no-var,strict,vars-on-top */ +'use strict'; +var Promise = require('bluebird'); +var fs = Promise.promisifyAll(require('fs')); +var _ = require('lodash'); +var path = require('path'); + +function InitialChunksPlugin(options) { + // Default options + this.options = _.extend({ + publicPath: './static' + }, options); +} + +InitialChunksPlugin.prototype.apply = function(compiler) { + var self = this; + + compiler.plugin('after-emit', function(compilation, callback) { + var stats = compilation.getStats().toJson(); + var entrypoints = stats.entrypoints.js.assets || []; + + var htmlAsset = stats.assets.find(function(asset) { + return asset.name.match(/[.]html?$/); + }); + + console.log('entrypoints', entrypoints); + + if (!htmlAsset) { + return callback(); + } + + var htmlFile = htmlAsset.name; + var filename = path.resolve(compilation.compiler.context, self.options.publicPath, htmlFile); + + if (self.isHotUpdateCompilation(entrypoints)) { + return callback(); + } + + var tags = self.createTags(entrypoints); + + // If the template and the assets did not change we don't have to emit the html + var assetJson = JSON.stringify(entrypoints); + if (self.options.cache && assetJson === self.assetJson) { + return callback(); + } + + self.assetJson = assetJson; + + return Promise.props({ + size: fs.statAsync(filename), + source: fs.readFileAsync(filename, 'utf-8') + }) + .catch(function() { + return Promise.reject(new Error('InitialChunksPlugin: could not load file ' + filename)); + }) + .then(function(results) { + return fs.writeFileAsync(filename, self.injectAssetsIntoHtml(results.source, tags)); + }). + finally(function() { + callback(); + }); + }); +}; + + +InitialChunksPlugin.prototype.isHotUpdateCompilation = function(assets) { + return assets.length && assets.every(function(name) { + return /\.hot-update\.js$/.test(name); + }); +}; + +/** + * Turn a tag definition into a html string + */ +InitialChunksPlugin.prototype.createTags = function(assets) { + var tags = this.generateAssetTags(assets); + return tags.map(function(tagDefinition) { + var attributes = Object.keys(tagDefinition.attributes || {}).map(function(attributeName) { + return attributeName + '="' + tagDefinition.attributes[attributeName] + '"'; + }); + return '<' + [tagDefinition.tagName].concat(attributes).join(' ') + (tagDefinition.selfClosingTag ? '/' : '') + '>' + + (tagDefinition.innerHTML || '') + + (tagDefinition.closeTag ? '' + tagDefinition.tagName + '>' : ''); + }); +}; + +/** + * Injects the assets into the given html string + */ +InitialChunksPlugin.prototype.generateAssetTags = function(assets) { + // Turn script files into script tags + return assets.map(function(scriptPath) { + return { + tagName: 'script', + closeTag: true, + attributes: { + type: 'text/javascript', + src: scriptPath + } + }; + }); +}; + +/** + * Injects the assets into the given html string + */ +InitialChunksPlugin.prototype.injectAssetsIntoHtml = function(html, assetTags) { + var bodyRegExp = /(<\/body>)/i; + + if (assetTags.length) { + if (bodyRegExp.test(html)) { + // Append assets to body element + html = html.replace(bodyRegExp, function(match) { + return assetTags + match; + }); + } else { + // Append scripts to the end of the file if no
element exists: + html += assetTags; + } + } + + return html; +}; + +module.exports = InitialChunksPlugin; diff --git a/client/index.html b/client/index.html index 13f5b8a..8ef08d7 100644 --- a/client/index.html +++ b/client/index.html @@ -4,11 +4,8 @@