Skip to content

Instantly share code, notes, and snippets.

@jonathaningram
Last active August 29, 2015 14:23
Show Gist options
  • Save jonathaningram/d1aaeb8b9314094e0d21 to your computer and use it in GitHub Desktop.
Save jonathaningram/d1aaeb8b9314094e0d21 to your computer and use it in GitHub Desktop.
Updating react-to-html-webpack-plugin to allow hashed JS assets to be resolved in the HTML template
var React = require('react');
var evaluate = require('eval');
// srcPath renamed to src since it can either be a filename or a chunk name
function ReactToHtmlWebpackPlugin(destPath, src, options) {
this.src = src;
this.destPath = destPath;
this.options = typeof options === 'object' ? options : {};
}
ReactToHtmlWebpackPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compiler, done) {
try {
var asset = compiler.assets[this.src];
if (asset === undefined) {
// START NEW CODE
var webpackStatsJson = compiler.getStats().toJson();
var chunkValue = webpackStatsJson.assetsByChunkName[this.src];
if (!chunkValue) {
throw new Error('Output file not found: "' + this.src + '"');
}
// 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];
}
asset = compiler.assets[chunkValue];
// END NEW CODE
}
var source = asset.source();
var Component = evaluate(source);
var html = React.renderToString(React.createElement(Component));
var template = this.options.template;
if (template != null && typeof template !== 'function') {
throw new Error('Template must be a function');
}
var output = typeof template === 'function' ?
template({
html: html,
assets: getAssetsFromCompiler(compiler)
}) :
html;
compiler.assets[this.destPath] = createAssetFromContents(output);
} catch (err) {
return done(err);
}
done();
}.bind(this));
}
// Shamelessly stolen from html-webpack-plugin - Thanks @ampedandwired :)
var getAssetsFromCompiler = function(compiler) {
var assets = {};
var webpackStatsJson = compiler.getStats().toJson();
for (var chunk in webpackStatsJson.assetsByChunkName) {
var chunkValue = webpackStatsJson.assetsByChunkName[chunk];
console.log(chunk, chunkValue);
// 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];
}
if (compiler.options.output.publicPath) {
chunkValue = compiler.options.output.publicPath + chunkValue;
}
assets[chunk] = chunkValue;
}
return assets;
};
var createAssetFromContents = function(contents) {
return {
source: function() {
return contents;
},
size: function() {
return contents.length;
}
};
};
module.exports = ReactToHtmlWebpackPlugin;
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ReactToHtmlPlugin = require('react-to-html-webpack-plugin');
var path = require('path');
var ejs = require('ejs');
var fs = require('fs');
module.exports = {
entry: {
main: './src/index.js'
},
output: {
filename: '[name]-[hash].js',
path: path.resolve('./server/dist'),
libraryTarget: 'umd'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader?optional[]=runtime&stage=0', exclude: /(node_modules|bower_components)/ },
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?module&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') },
{ test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css!less') },
{ test: /\.svg$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml&name=assets/[sha512:hash:base64:7].[ext]" },
{ test: /\.png$/, loader: "url-loader?limit=10000&mimetype=image/png&name=assets/[sha512:hash:base64:7].[ext]" },
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url?limit=10000&minetype=application/font-woff&name=assets/[sha512:hash:base64:7].[ext]" },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&name=assets/[sha512:hash:base64:7].[ext]" }
]
},
postcss: [
require('autoprefixer-core'),
require('postcss-color-rebeccapurple')
],
resolve: {
modulesDirectories: ['node_modules', 'bower_components']
},
plugins: [
new ExtractTextPlugin('style.css', { allChunks: true }),
// Note: "index.js" is no longer used, instead use "main" which is the name of the entry
// Using "index.js" would result in the file not found error because it has been hashed
new ReactToHtmlPlugin('index.html', 'main', {
template: ejs.compile(fs.readFileSync(__dirname + '/src/template.ejs', 'utf-8'))
})
]
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment