Monday, 17 April 2017

What is the best way to autor eload React components in PROD (Server side rendering)

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