Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full reload - Typescript + Babel #525

Closed
theduke opened this issue Mar 24, 2017 · 20 comments
Closed

Full reload - Typescript + Babel #525

theduke opened this issue Mar 24, 2017 · 20 comments
Labels

Comments

@theduke
Copy link

theduke commented Mar 24, 2017

I have a very minimal setup with Webpack, tslint, Typescript and Babel.

On every save I get the output:

Cannot apply update. Need to do a full reload!
(anonymous) @ app.js:15223
app.js:15224 [HMR] Error: Aborted because 165 is not accepted
Update propagation: 165 -> 431

I noticed that the module.hot.accept callback is never invoked.
I have followed the migration guide closely.

Running the dev server with: webpack-dev-server --hot.

Any hints?

app.tsx:

import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";

import App from "./components/app/App";

function startApp() {
  ReactDOM.render(
    <AppContainer>
      <App />
    </AppContainer>,
    document.getElementById("app-root"),
  );
}
startApp();

if (module.hot) {
  module.hot.accept("./components/app/App", () => { alert("accept"); startApp(); } );
}

App.tsx:

import React from "react";

export default class App extends React.Component<void, void> {
  public render() {
    return (
      <div>NO</div>
    );
  }
}

babelrc:

{
  "presets": [
    ["es2015", {"loose": true, "modules": false}],
    "stage-2"
  ],
  "plugins": ["react-hot-loader/babel"]
}

webpack.config.js:

const path = require("path");

const config = {
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx"],
    alias: {
      pos: path.resolve(__dirname, "src"),
      cmp: path.resolve(__dirname, "src/components"),
      assets: path.resolve(__dirname, "assets"),
    },
  },

  entry: {
    app: [
      "react-hot-loader/patch",
      "./src/app",
    ],
  },

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },

  module: {
    rules: [
      // tslint linting.
      { test: /\.tsx?$/, enforce: "pre", use: "tslint-loader" },
      // Typescript files.
      {
        test: /.tsx?$/,
        use: ["babel-loader", "ts-loader"],
        exclude: /node_modules/
      },
      // Images.
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name]_[hash].[ext]",
              publicPath: "/img/",
              outputPath: "img/",
            },
          },
        ],
      },
    ],
  },

  devServer: {
    contentBase: path.join(__dirname, "dist"),
    port: 9000,
    historyApiFallback: true,
  },
};
module.exports = config;
@theduke
Copy link
Author

theduke commented Mar 24, 2017

PS: I've also tried to disable the tslint-loader.
No effect.

@thomhos
Copy link

thomhos commented Mar 29, 2017

Hi,

Just checking in with the exact same thing happening :(

@seamc
Copy link

seamc commented Apr 1, 2017

My local, minimal boilerplate contains a few visible changes from your current setup. I pulled config settings from the @next's babel migration recommendations. Outside of that and of note, I use awesome-typescript-loader.

package.json

  "devDependencies": {
    "awesome-typescript-loader": "3.1.2",
    "babel-core": "^6.24.0",
    "babel-loader": "6.4.1",
    "babel-preset-es2015": "^6.24.0",
    "babel-preset-react": "^6.23.0",
    "babel-preset-stage-0": "^6.22.0",
    "react-hot-loader": "next",
    "typescript": "2.2.2",
    "webpack": "^2.3.2",
    "webpack-dev-server": "^2.4.2"
  },

webpack.config.js

module.exports = {
  entry: [
      'babel-polyfill',
      'react-hot-loader/patch',
      './src/index.tsx',
  ],

...

module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          'react-hot-loader/webpack', 'babel-loader', 'awesome-typescript-loader'
        ],
        exclude: /node_modules/,
      },
    ],
  }

tsconfig.json

{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "lib": ["es6", "dom"],
        "moduleResolution": "node",
        "sourceMap": true,
        "jsx": "preserve",
        "rootDir": "src",
        "allowSyntheticDefaultImports": true
    },
    "exclude": [
        "node_modules"
    ]
}

.babelrc

{
  "presets": [["es2015", { "modules": false }], "stage-0", "react"],
  "plugins": ["react-hot-loader/babel"]
}

@theduke
Copy link
Author

theduke commented Apr 8, 2017

@seamc so this setup is working for you?

@seamc
Copy link

seamc commented Apr 9, 2017

Yeah, here's a repo https://github.com/seamc/rhl-tsx-boilerplate

@DickyT
Copy link

DickyT commented Apr 10, 2017

@seamc thank u for the demo, do u think this problem can be solved without babel?

@DickyT
Copy link

DickyT commented Apr 11, 2017

@dizel3d
Copy link
Contributor

dizel3d commented Apr 29, 2017

@theduke I don't no why, but you must explicitly require new App component and put it into startApp argument in accept callback. I've fixed your example. Check it out, please.

import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";

import App from "./components/app/App";

function startApp(App) {
  ReactDOM.render(
    <AppContainer>
      <App />
    </AppContainer>,
    document.getElementById("app-root"),
  );
}
startApp(App);

if (module.hot) {
  module.hot.accept("./components/app/App", () => { alert("accept"); startApp(require("./components/app/App").default); } );
}

I had the same problem. So I took react-hot-boilerplate and transformed it into typescript with minimal changes. Then hot-reloading stopped working anymore. Everything was fine in console, but changes weren't happening on page actually.
image
Then I fixed the issue like this. But I really don't understand roots of the problem. Does anyone know?

@dizel3d
Copy link
Contributor

dizel3d commented May 9, 2017

Further to my previous comment I found the issue. In case of Typescript __REACT_HOT_LOADER__.register isn't invoked for default. I've compared result bundles of equivalent TS and JS sources:
image
It looks like a bug in react-hot-loader.

@dizel3d
Copy link
Contributor

dizel3d commented May 9, 2017

I added a test to reproduce the issue.

@jetpack3331
Copy link

@seamc way is working

@Kaijun
Copy link

Kaijun commented Jul 5, 2017

@dizel3d you saved my day! i don't know why it works, and i'm also wondering why the others don't need it indeed...

@Kledal
Copy link

Kledal commented Aug 27, 2017

@dizel3d
It is because of typescript and the default export. If you export the App without default and import it like this, it works without the require and .default :

import {App} from "./components/app/App";

@sjy
Copy link

sjy commented Nov 29, 2017

One general workround:

if (module.hot) {
      // accept all changes, evaluate the whole js file
      module.hot.accept();
      render(Root);
}

related #413

@gregberge
Copy link
Collaborator

gregberge commented Dec 25, 2017

React Hot Loader v4 provides a good TypeScript support using Babel. I added an example with Typescript.

@Place1
Copy link

Place1 commented Jan 11, 2018

I just had this problem, here's what I ad to use to fix it:

const mount = document.getElementById('mount');

const render = (Component: any) => {
  ReactDOM.render(<Component />, mount);
}

render(App);

if (module.hot) {
  module.hot.accept('./App', () => {
    render(require('./App').default); // the .default is important
  });
}

I found that just doing render(require('./App')); resulted in a full page reload, but doing render(require('./App').default); gave me the hot-reloading goodness I was expecting!

@nranas
Copy link

nranas commented Jan 11, 2018

I experienced a similar issue where the hot reloading worked the first time after I started the dev server but every subsequent change triggered a full reload since the root component (index.tsx) wasn't accepted. When I switched to awesome-typescript-loader instead of ts-loader it started working with the exact same config.

@charlieharris1
Copy link

charlieharris1 commented Jan 13, 2018

If you implement one of the fixes above and are still seeing full reloads React Router V3 may be the cause.

@olee
Copy link

olee commented Jan 24, 2018

I noticed the same issue as #525 (comment) - default exports are NOT registered (most of the time?) and because of that hot reloading for those components does not work.
So this does not work:

import * as React from 'react';
import { withStyles, WithStyles } from 'material-ui/styles';

const styles = {...};

export const MyComponent: React.SFC<WithStyles<'title'>> = () => <h1 className={classes.title}>Hello world!</h1>;

export default withStyles(styles)(MyComponent);

However this one works:

const MyStyledComponent = withStyles(styles)(MyComponent);
export default MyStyledComponent ;

Maybe someone could a loader or something that detects those kind of e default export where an expression is used and rewrite them so they use a temporary const?

EDIT: Also the tag "fixed in next" is wrong - I just tried upgrading and can confirm it does NOT work:

var styles = function styles(theme) {
  return {...};
};
exports.default = _styles_1.withStyles(styles)(exports.AppLayout); 
(function () {
  var reactHotLoader = __webpack_require__("./node_modules/react-hot-loader/patch.js").default;
  var leaveModule = __webpack_require__("./node_modules/react-hot-loader/patch.js").leaveModule;
  if (!reactHotLoader) {
    return;
  }
  reactHotLoader.register(styles, "styles", "D:\\dev\\web\\haushalt-tracker\\src\\components\\core\\AppLayout.tsx");
  leaveModule(module);
})();

The default export is not registered.

EDIT 2:
Ok this is interesting - even though no register call for the default export is generated, hot reloading seems to work. So I guess switching to react-hot-loader@next solves this problem.

EDIT 3:
Ok - it does not work correctly - it just does not display a warning any more it seems.
When I try the fixed version, my component is correctly updated while keeping it's state.
If I try it without the const fix with @next version, it kills the state (which it does not otherwise).

So this issue needs to be fixed in v3 AND v4.

@andrewdavidcostello
Copy link

andrewdavidcostello commented Feb 14, 2018

Just in case someone has the same issue I was having. If you target ES6 with Typescript then apply the hot reload loader it won't work as if messes with the scope of the classes, not sure on the underlying issue.

loaders: [
	"react-hot-loader/webpack",
        "ts-loader"
],

Does not work.

loaders: [
	"react-hot-loader/webpack",
	{
		loader: "babel-loader",
		query: {
			presets: [
				['es2015', { 'modules': false }]
			]
		}
	},
	"ts-loader"
],

Does work and retains the state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests