pmmmwh / react-refresh-webpack-plugin

A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.
MIT License
3.14k stars 194 forks source link

ReactRefresh doesn't reload parents folder #251

Closed CaiqueR closed 3 years ago

CaiqueR commented 3 years ago

I have this folder structure:

my-application/src/frontend/App.jsx utils/util.js

If I import util.js, do any change, and if I use util.js in App.jsx I get the following message: image

I have to force refresh with CTRL + R to see changes

pmmmwh commented 3 years ago

Hi - is the utils.js processed by the same Babel/Webpack pipeline? If not changes like this are out of bounds to Babel/Webpack, which means that we cannot inject the proper metadata in it to process a refresh.

Also I suppose you're using hotOnly so that browser won't force refresh on update bail outs?

CaiqueR commented 3 years ago

My webpack.config looks like this:

config: {
  entry: [
    'webpack-hot-middleware/client?path=http://localhost:9004/__webpack_hmr',
    './servicos-campo/src/frontend/servicos-campo.js',
  ],
  target: 'web',
  output: {
    publicPath: process.env.ASSET_PATH || '/',
    filename: 'scripts.js',
    path: path.resolve(__dirname, 'build/frontend'),
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new ReactRefreshWebpackPlugin({
      overlay: false,
      exclude: /node_modules/,
    }),
  ],
  devServer: {
    hot: true,
  },
  optimization: {
    removeAvailableModules: false,
    removeEmptyChunks: false,
    splitChunks: false,
  },

  devtool: isProduction ? 'source-map' : 'eval',
  mode: environment,
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.scss$/,
        use: [
          isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: isProduction,
              url: (url) => {
                if (url[0] === '/') {
                  return false;
                }

                return true;
              },
            },
          },
          isProduction && {
            loader: 'postcss-loader',
            options: {
              sourceMap: isProduction,
              postcssOptions: {
                plugins: [['autoprefixer']],
              },
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: isProduction,
            },
          },
        ].filter(Boolean),
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot|)(\?.*$|$)/,
        use: 'file-loader',
      },
    ],
  },
  resolve: {
    extensions: ['*', '.js', '.jsx'],
    fallback: { path: false },
  },
  plugins,
  }
},

I use SSR and webpack-dev-middleware

pmmmwh commented 3 years ago

Can you create a reproducible example?

CaiqueR commented 3 years ago

Can you create a reproducible example?

How can I do this? Would you like me to create a repository showing my code? Or a video demonstrating that Reload doesn't work?

pmmmwh commented 3 years ago

Can you create a reproducible example?

How can I do this? Would you like me to create a repository showing my code? Or a video demonstrating that Reload doesn't work?

A repository would be very helpful 🙏🏻

CaiqueR commented 3 years ago

Can you create a reproducible example?

How can I do this? Would you like me to create a repository showing my code? Or a video demonstrating that Reload doesn't work?

A repository would be very helpful 🙏🏻

I'm sorry, but I can't share the repository, because it's not mine, it's the company I work for :( Have another way to help me :(

pmmmwh commented 3 years ago

I'm sorry, but I can't share the repository, because it's not mine, it's the company I work for :( Have another way to help me :(

You don't actually have to share the repository - I just need to know how the code is structured. Stuff like Webpack/Babel config, how directories/files are structured (names can be dummy), React/Webpack/React-refresh version, etc.

A reproduction of the actual setup with dummy stuff basically.

Is that possible?


Also - are you using webpack-dev-server or webpack-hot-middleware?

CaiqueR commented 3 years ago

I'm using webpack-hot-middleware

This is my .babelrc:

{
  "presets": [
    "@babel/preset-env",
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ],
  "plugins": ["angular-inline-template", "angularjs-annotate"],
  "env": {
    "test": {
      "plugins": ["@babel/plugin-transform-runtime"]
    }
  }
}

My express looks like this:

/* eslint-disable import/no-extraneous-dependencies */

'use strict';

const path = require('path');

const express = require('express');
const helmet = require('helmet');
const morgan = require('morgan');
const dotenv = require('dotenv');
const mongoose = require('mongoose');

const result = dotenv.config();

if (result.error) {
  throw result.error;
}

const clientIp = require('../middlewares/client-ip');
const errorMiddleware = require('../middlewares/error-handler');

module.exports = function ({ staticPaths, ssr, apiPaths, cwd, options, publicPaths, webpackConfig } = {}) {
  const app = express();

  if (process.env.NODE_ENV === 'development') {
    const chokidar = require('chokidar');
    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    const webpackHotMiddleware = require('webpack-hot-middleware');

    const compiler = webpack(webpackConfig);

    app.use(
      webpackDevMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        serverSideRender: true,
      })
    );

    app.use(webpackHotMiddleware(compiler));

    const watcher = chokidar.watch([cwd, './shared/backend', './shared/regras-negocio', './shared/utils']);

    watcher.on('ready', () => {
      watcher.on('all', () => {
        // eslint-disable-next-line no-console
        console.log('🚀 -- Reloading server...');

        deleteMongooseCache();

        deleteRequireCache();

        // eslint-disable-next-line no-console
        console.log('😀 -- Server reloaded.');
      });
    });
  }

  app.use(
    helmet({
      contentSecurityPolicy: false,
    })
  );

  app.use(morgan(':date[iso] :method :url :status :response-time ms - :res[content-length]'));
  app.use(clientIp);

  if (staticPaths) {
    staticPaths.forEach((staticPath) => {
      app.use(express.static(staticPath));
    });
  }

  legacyCompatibilityLayerLaboratorio(app);

  if (apiPaths) {
    if (publicPaths && Array.isArray(publicPaths)) {
      publicPaths.forEach((publicPath) => {
        apiPaths.push(path.join(__dirname, '../api', publicPath));
      });
    }

    app.use('/api', (req, res, next) => {
      require('./api')(apiPaths, options)(req, res, next);
    });
  }

  app.use(errorMiddleware);

  if (ssr) {
    app.get('/**', ssr);
  }

  return app;
};

function legacyCompatibilityLayerLaboratorio(app) {
  app.use('/legado/**', (req, res) => {
    const redirectTo = req.originalUrl.replace('/legado', '');

    res.redirect(redirectTo);
  });
}

function deleteMongooseCache() {
  mongoose.connections.forEach((connection) => {
    const modelNames = Object.keys(connection.models);

    modelNames.forEach((modelName) => {
      delete connection.models[modelName];
    });

    const collectionNames = Object.keys(connection.collections);

    collectionNames.forEach((collectionName) => {
      delete connection.collections[collectionName];
    });
  });

  const modelSchemaNames = Object.keys(mongoose.modelSchemas);

  modelSchemaNames.forEach((modelSchemaName) => {
    delete mongoose.modelSchemas[modelSchemaName];
  });
}

function deleteRequireCache() {
  Object.keys(require.cache).forEach((id) => {
    const localId = id.substr(process.cwd().length);

    if (!localId.match(/[\/\\]backend[\/\\]/)) {
      return;
    }

    delete require.cache[id];
  });
}

Version of my packages:

{
    "react": "17.0.0,
    "react-dom": "17.0.0",
    "react-redux": "7.2.1",
    "react-router": "5.2.0",
    "react-router-dom": "5.2.0",
    "@pmmmwh/react-refresh-webpack-plugin": "0.4.2",
    "@babel/core": "7.12.3",
    "@babel/plugin-transform-runtime": "7.12.1",
    "@babel/preset-env": "7.12.1",
    "@babel/preset-react": "7.12.1",
    "react-refresh": "0.9.0",
}

I have Redux and also presents the same message and does not do the Hot Reload

pmmmwh commented 3 years ago

You're missing the react-refresh/babel plugin in your Babel config 😄

CaiqueR commented 3 years ago

Still not working :(

It is very strange, because in the terminal it appears that it was compiled 2020-11-20_08-44

But in the browser it shows: 2020-11-20_08-51

zhangchao828 commented 3 years ago

+1

zhangchao828 commented 3 years ago
// my babel-loader
{
      test: /\.(js|ts)x?$/,
      loader: 'babel-loader',
      exclude: (file) => {
        return /node_modules/.test(file) && !file.includes('my-module')
      },
      options: babelOptions,
    }

ReactRefreshPluginOptions have a 'exclude' config,it is default to /node_modules/

but 'my-module' is in node_modules ,How can i do,

pmmmwh commented 3 years ago

Still not working :(

It is very strange, because in the terminal it appears that it was compiled 2020-11-20_08-44

But in the browser it shows: 2020-11-20_08-51

Can you check if the modules in the parent directory is being processed by this plugin (by looking at the bundle) - it should contain traces of code as shown here.

ReactRefreshPluginOptions have a 'exclude' config,it is default to /node_modules/

but 'my-module' is in node_modules ,How can i do,

Something like this?

new ReactRefreshWebpackPlugin({
  exclude: /node_modules(?!.*(my-module))/
})
CaiqueR commented 3 years ago

Still not working :( It is very strange, because in the terminal it appears that it was compiled 2020-11-20_08-44 But in the browser it shows: 2020-11-20_08-51

Can you check if the modules in the parent directory is being processed by this plugin (by looking at the bundle) - it should contain traces of code as shown here.

How can I do this?

pmmmwh commented 3 years ago

How can I do this?

Try to build a development bundle that emits to disk (i.e. just run webpack with your development config) - then look at the main bundled file, search for file paths outside of your current folder (like utils/something.js), then inspect the code there.

CaiqueR commented 3 years ago

How can I do this?

Try to build a development bundle that emits to disk (i.e. just run webpack with your development config) - then look at the main bundled file, search for file paths outside of your current folder (like utils/something.js), then inspect the code there.

I think I discovered the problem.

I have these folder structure: image

And my index.js looks like this:

'use strict';

module.exports = {
  ...require('./array-helpers'),
  ...require('./async-helpers'),
  ...require('./csv-parser'),
  ...require('./date-helpers'),
  ...require('./formatters'),
  ...require('./getters-setters'),
  ...require('./json-parser'),
  ...require('./property-checkers'),
  ...require('./query-string'),
  ...require('./sort'),
  ...require('./tree-helper'),
  ...require('./validators'),
};

If I import a file like array-helpers in frontend with those configs the message appear, but if I comment the line ...require('./array-helpers') and import the file array-helper.js instead index.js works right.

Is there any way to fix that without chaging my code? If not, I will have to change all source code and this is not viable

pmmmwh commented 3 years ago

Are you using CommonJS or ES Modules (i.e. require + module.exports or import + export), or are you mixing both?

Export * have implications that is very difficult for use to fix because there is no explicit boundaries on what is exported nor does it provide statically analysable export names.

CaiqueR commented 3 years ago

Are you using CommonJS or ES Modules (i.e. require + module.exports or import + export), or are you mixing both?

Export * have implications that is very difficult for use to fix because there is no explicit boundaries on what is exported nor does it provide statically analysable export names.

I am using both. As in my repository I have the backend and frontend together, in the backend I use commonjs and in the frontend esmodules.

So, perhaps the most ideal would be to use named exports?

pmmmwh commented 3 years ago

So, perhaps the most ideal would be to use named exports?

Yes! Mixed CJS + ESM interop is a bit flimsy to my understanding, or you can try out Webpack 5 since they did a lot of work to keep that tight (they also do export analysis for * now I think).

CaiqueR commented 3 years ago

Yes! Mixed CJS + ESM interop is a bit flimsy to my understanding, or you can try out Webpack 5 since they did a lot of work to keep that tight (they also do export analysis for * now I think).

I see, I think the issue can be closed then. I will try to migrate my code base to named exports