I am trying to find the best way to auto reload react component changes (mostly very basic HTML updates) in PROD without restarting.
I am using react HMR for my dev environment and everything is working as expected.
For PROD, I am currently using webpack --watch and also I created a simple plugin (using a GIT comment) to inject the bundle.js dynamically in to my EJS.
Whenever the app detects a change, it reloads the webpack and also injects the updated bundle.js (With new hash value) in to my EJS template and I can see the change after refreshing the page.
My only issue is with server side caching. I use import statements to import the React components and when the page loads initially in the browser I still see the old value and that value is eventually replaced by the updated value after the client side script loads. I see the old value for a moment before the new value is seen on the screen. I tried multiple ways to clear the server side cache and I couldnt fix that issue.
I wrote a simple function which would watch for file changes and decache the imports and also does a reload of the server but this didn't change a thing. I still see the old value (from react component) being load first on the browser before seeing the updated value. Any help is appreciated.
fs.watchFile('./common/components', function (curr, prev) {
try {
const testFolder = './common/components';
fs.readdir(testFolder, (err, files) => {
try {
files.forEach(file => {
console.log("Decached -> "+file.toString().replace('.js',''))
decache('../common/components/' + file.toString().replace('.js',''))
console.log("Reloading the server");
});
} catch (e) {
console.log(e);
}
})
reloadServer.reload();
} catch (e) {
console.log(e);
}
})
I import the react components as below
import CategoryHeader from "./CategoryHeader";
webpack config:
const path = require('path')
const webpack = require('webpack')
var PROD = JSON.parse(process.env.PROD_ENV || '0');
var CompressionPlugin = require('compression-webpack-plugin');
var BundleJsReplacePlugin = require('./plugins/UpdateBundleJsPlugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var DeleteCachedFiles = require('./plugins/DeleteCachedFiles');
module.exports = {
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
},
devtool: PROD ? false : 'eval',
output: {
path: path.join(__dirname, "dist"),
publicPath: "/global-search/static/",
filename: "output.[hash].bundle.js",
chunkFilename: "[id].[hash].bundle.js"
},
entry: PROD ? [
'babel-polyfill',
'./client/index.js'
] : [
'babel-polyfill',
'webpack-hot-middleware/client',
'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors
'./client/index.js'
],
plugins: PROD ? [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
},
}),
new DeleteCachedFiles(),
new BundleJsReplacePlugin(),
new CleanWebpackPlugin(['dist/*'],{"watch": true}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]),
new webpack.NoErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: true,
unused: true,
dead_code: true, // big one--strip code that will never execute
warnings: false, // good for prod apps so users can't peek behind curtain
drop_debugger: true,
conditionals: true,
evaluate: true,
drop_console: true, // strips console statements
sequences: true,
booleans: true,
compressor: {
screw_ie8: true,
warnings: false
},
output: {
comments: false,
screw_ie8: true
}
}),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.css$|\.html$/,
threshold: 10240,
minRatio: 0.8
})
] : [
new BundleJsReplacePlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('dev')
},
}), new webpack.ProgressPlugin(function (percentage, message) {
var MOVE_LEFT = new Buffer('1b5b3130303044', 'hex').toString();
var CLEAR_LINE = new Buffer('1b5b304b', 'hex').toString();
process.stdout.write(CLEAR_LINE + Math.round(percentage * 100) + '%: ' + message + MOVE_LEFT);
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.css$|\.html$/,
threshold: 10240,
minRatio: 0.8
})
],
module: {
loaders: PROD ? [
{
test: /\.(js|jsx)$/,
include: [path.join(__dirname, 'common'), path.join(__dirname, 'client')],
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true,
presets: ["es2015", "react", "react-optimize"],
plugins: [["transform-react-constant-elements"],
["transform-react-inline-elements"],
['transform-object-rest-spread'],
['transform-class-properties'],
["transform-decorators-legacy"],
["transform-runtime"],
['react-transform', {
transforms: [
{
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react'],
},
],
}],
],
}
},
] : [
{
test: /\.(js|jsx)$/,
include: __dirname,
exclude: /(node_modules)/,
loader: 'babel',
query: {
cacheDirectory: true,
presets: ["es2015", "react", "react-optimize"],
plugins: [["transform-react-constant-elements"],
["transform-react-inline-elements"],
['transform-object-rest-spread'],
['transform-class-properties'],
["transform-decorators-legacy"],
["transform-runtime"],
['react-transform', {
transforms: [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'],
}, {
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react'],
},
],
}],
],
}
},
]
}
}
Plugin to insert bundle.js dynamically to EJS template:
function UpdateBundleJsPlugin(options) {
// Setup the plugin instance with options...
}
UpdateBundleJsPlugin.prototype.apply = function(compiler) {
compiler.plugin("done", function(statsData) {
const stats = statsData.toJson();
if (!stats.errors.length) {
const htmlFileName = "search.ejs";
const html = fs.readFileSync(path.join('./views',htmlFileName), "utf8");
// need to read the final js bundle file to replace in the distributed index.html file
const asset = stats.assets[0];
let htmlOutput = html.replace(/static\/.*bundle\.js/, 'static/'+asset.name);
fs.writeFileSync(
path.join('./views', htmlFileName),
htmlOutput);
}
});
};
via Learner
No comments:
Post a Comment