react-native-webpack-server icon indicating copy to clipboard operation
react-native-webpack-server copied to clipboard

Fix hot module replacement for RN 0.12

Open elliottsj opened this issue 9 years ago • 32 comments

New in [email protected]: when using the Chrome debugger, the app bundle is now run inside a web worker: https://github.com/facebook/react-native/pull/1632, https://github.com/facebook/react-native/commit/8db35d492b846f51a758e8ee7e5e402c6bad3785

This means that an app bundle built with webpack's hot module replacement enabled will raise errors such as #99, and hot module replacement will fail.


I'm able to get HMR working on the rn-0.12-hmr branch by doing the following:

  • Use webpack-dev-middleware + webpack-hot-middleware instead of webpack/hot/only-dev-server + webpack-dev-server/client:

    webpack-dev-server depends on window.postMessage to broadcast "webpackHotUpdate" events to any loaded scripts (usually just 'webpack/hot/dev-server' or 'webpack/hot/only-dev-server'), which does not work in the context of a web worker since web workers have a different postMessage API. webpack-hot-middleware is an alternative which combines the functionality of both webpack/hot/only-dev-server and webpack-dev-server/client.

  • Patch webpack so it uses importScripts inside a web worker instead of the default behaviour of appending a <script> with the new hot-reloaded module: https://github.com/elliottsj/webpack/commit/743a00a8bc491f1900ce9f49150229183f874da1.

    ~~Known issue: this inexplicable error is thrown every time a module is hot reloaded:~~ Edit: turns out this was caused by adding webpack.HotModuleReplacementPlugin() to the config twice instead of once (-‸ლ)

    screenshot 2015-10-11 15 07 53

    ~~Not sure why this happens; the original error message & stack gets lost somewhere.~~

Try out rn-0.12-hmr/Examples/BabelES6 for a working example.

My patch to webpack seems quite hacky; I think the proper solution is to make something like 'WebWorkerMainTemplate.runtime.js', but I don't know enough about webpack internals to know what to do here.

Does anyone have any ideas on the proper way to do HMR from inside the web worker? cc @gaearon @sokra

elliottsj avatar Oct 11 '15 19:10 elliottsj

WebWorkerMainTemplate.runtime.js is the way to go. Settings target: "webworker" will enabled it. Should be pretty easy to write as importScripts is sync.

WebWorkerHotUpdateChunkTemplatePlugin.js is also needed to defined the hot update chunk format for web workers, but it can be equal to JsonpHotUpdateChunkTemplatePlugin.js.

If someone want to do a PR to webpack, I would be happy...

sokra avatar Oct 13 '15 15:10 sokra

Opened PR to support HMR in web workers: https://github.com/webpack/webpack/pull/1521

elliottsj avatar Oct 17 '15 22:10 elliottsj

I tried using your branch, but I get this warning the first time I change a file (and HMR doesn't work): screen shot 2015-10-20 at 12 50 51 ... screen shot 2015-10-20 at 12 51 12

Any opinions?

UPDATE: Nevermind. It worked with your webpack PR.

dapetcu21 avatar Oct 20 '15 09:10 dapetcu21

@dapetcu21 Hi, i'm stuck with this webworker error and maybe you can help me. Where did you put the "var worker = new Worker("bundle.js");"?

paolorovella avatar Oct 20 '15 19:10 paolorovella

@paolorovella You don't need var worker = new Worker("bundle.js");. The RN debugger will load the bundle into a web worker for you; try out the rn-0.12-hmr/Examples/BabelES6 example.

elliottsj avatar Oct 20 '15 19:10 elliottsj

I finally did it! I'm using RN 0.12 + react-native-webpack-server#rn-0.12-hmr + elliottsj/webpack#web-worker-hmr + webpack-hot-middleware without var worker = new Worker('bundle.js'); and without target:"webworker...thank you so much :)

paolorovella avatar Oct 20 '15 20:10 paolorovella

@paolorovella: Yup. Exactly same setup worked for me, as well. I was initially trying the rn-0.12-hack branch that was mentioned earlier.

dapetcu21 avatar Oct 20 '15 20:10 dapetcu21

Just pushed an update to rn-0.12-hmr/Examples/BabelES6: it now uses the changes from https://github.com/webpack/webpack/pull/1521 instead of the hack.

elliottsj avatar Oct 22 '15 00:10 elliottsj

Hi! Since i was very happy for the working HMR in my simulator, i've decided to try on my iPhone 6. Unfortunately it doesn't work and this is my console. Any ideas? schermata 2015-10-22 alle 17 37 28

paolorovella avatar Oct 22 '15 15:10 paolorovella

You need to change localhost to your computer's IP/hostname in AppDelegate.m

dapetcu21 avatar Oct 22 '15 15:10 dapetcu21

Also, webpack's publicPath should include your hostname as well

dapetcu21 avatar Oct 22 '15 15:10 dapetcu21

I've already done it but it doesn't work :/ if it can helps, if i go to http://localhost:8082/ i get "Cannot get /" but if i go to http://192.168.1.156:8082/ the page is not available.

paolorovella avatar Oct 22 '15 15:10 paolorovella

Firewall settings?

On 22 Oct 2015, at 17:49, Paolo Rovella [email protected] wrote:

I've already done it but it doesn't work :/ if it can helps, if i go to http://localhost:8082/ i get "Cannot get /" but if i go to http://192.168.1.156:8082/ the page is not available.

— Reply to this email directly or view it on GitHub.

ptomasroos avatar Oct 22 '15 15:10 ptomasroos

Firewall deactivated :) could it be a port forwarding router problem? 8082 closed port i mean

paolorovella avatar Oct 22 '15 15:10 paolorovella

It could be that rnws binds only to localhost: app.listen('localhost', port)

dapetcu21 avatar Oct 22 '15 15:10 dapetcu21

@dapetcu21 i've changed in package.json -> HOT=1 ./node_modules/.bin/react-native-webpack-server start --hot --hostname 192.168.1.156 and now http://192.168.1.156:8082/index.ios.js gives me the bundle but when i update a component the HMR is not starting

paolorovella avatar Oct 22 '15 16:10 paolorovella

@dapetcu21 i've forgotten to set http://192.168.1.156:8082 in 'webpack-hot-middleware/client?path=http://192.168.1.156:8082/__webpack_hmr&overlay=false' now everything is working :) thank you so much

paolorovella avatar Oct 22 '15 17:10 paolorovella

@elliottsj You're missing webpack-hot-middleware in the package.json of https://github.com/mjohnston/react-native-webpack-server/blob/rn-0.12-hmr/Examples/BabelES6/package.json

niftylettuce avatar Oct 26 '15 19:10 niftylettuce

@elliottsj Hot reloading still doesn't seem to work - and now neither does LiveReload

niftylettuce avatar Oct 26 '15 19:10 niftylettuce

@elliottsj Here's some output if it helps... this occurs after I save a file (the debugger/websocket stuff is connected already, it looks like the hotpack files might not be reloading properly or triggering intent refresh or something, not sure...?)

webpack built 872a39c5ec3c5bdae6a4 in 441ms
Hash: 872a39c5ec3c5bdae6a4
Version: webpack 1.12.2
Time: 441ms
                                   Asset      Size  Chunks             Chunk Names
                            index.ios.js    117 kB       0  [emitted]  index.ios
                        index.android.js    104 kB       1  [emitted]  index.android
    0.dfd585fc6ce8bf86eb49.hot-update.js   3.79 kB       0  [emitted]  index.ios
    dfd585fc6ce8bf86eb49.hot-update.json  36 bytes          [emitted]
                        index.ios.js.map    160 kB       0  [emitted]  index.ios
0.dfd585fc6ce8bf86eb49.hot-update.js.map    4.2 kB       0  [emitted]  index.ios
                    index.android.js.map    140 kB       1  [emitted]  index.android
chunk    {0} index.ios.js, 0.dfd585fc6ce8bf86eb49.hot-update.js, index.ios.js.map, 0.dfd585fc6ce8bf86eb49.hot-update.js.map (index.ios) 86.1 kB [rendered]
chunk    {1} index.android.js, index.android.js.map (index.android) 73.9 kB [rendered]
webpack: bundle is now VALID.
[3:29:27 PM] <START> find dependencies
[3:29:27 PM] <END>   find dependencies (96ms)
[3:29:27 PM] <START> transform
transforming [========================================] 100% 314/314
[3:29:27 PM] <END>   transform (114ms)

niftylettuce avatar Oct 26 '15 19:10 niftylettuce

Invalid HMR message: {"action":"built","time":441,"hash":"872a39c5ec3c5bdae6a4","warnings":[],"errors":[],"modules":{"0":"multi index.android","1":"./~/react-native/Libraries/react-native/react-native.js","2":"./~/react-transform-hmr/~/react-proxy/~/lodash/lang/isObject.js","3":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/isArrayLike.js","4":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/isObjectLike.js","5":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/getNative.js","6":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/isLength.js","7":"(webpack)/buildin/module.js","8":"./~/babel-runtime/helpers/interop-require-default.js","9":"./~/react-transform-hmr/lib/index.js","10":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/isIndex.js","11":"./~/react-transform-hmr/~/react-proxy/~/lodash/lang/isArguments.js","12":"./~/react-transform-hmr/~/react-proxy/~/lodash/lang/isArray.js","13":"./~/babel-runtime/~/core-js/library/modules/$.core.js","14":"./~/react-transform-hmr/~/react-proxy/~/lodash/function/restParam.js","15":"./~/react-transform-hmr/~/react-proxy/~/lodash/object/keys.js","16":"external "NativeModules"","17":"./~/react-native-button/Button.js","18":"./~/react-native-button/coalesceNonElementChildren.js","19":"./~/react-native-form/index.js","20":"./~/babel-runtime/core-js/object/assign.js","21":"./~/babel-runtime/helpers/extends.js","22":"./~/babel-runtime/~/core-js/library/fn/object/assign.js","23":"./~/babel-runtime/~/core-js/library/modules/$.assign.js","24":"./~/babel-runtime/~/core-js/library/modules/$.cof.js","25":"./~/babel-runtime/~/core-js/library/modules/$.def.js","26":"./~/babel-runtime/~/core-js/library/modules/$.defined.js","27":"./~/babel-runtime/~/core-js/library/modules/$.fails.js","28":"./~/babel-runtime/~/core-js/library/modules/$.global.js","29":"./~/babel-runtime/~/core-js/library/modules/$.iobject.js","30":"./~/babel-runtime/~/core-js/library/modules/$.js","31":"./~/babel-runtime/~/core-js/library/modules/$.to-object.js","32":"./~/babel-runtime/~/core-js/library/modules/es6.object.assign.js","33":"./~/react-transform-hmr/~/global/window.js","34":"./~/react-transform-hmr/~/react-proxy/modules/bindAutoBindMethods.js","35":"./~/react-transform-hmr/~/react-proxy/modules/createClassProxy.js","36":"./~/react-transform-hmr/~/react-proxy/modules/createPrototypeProxy.js","37":"./~/react-transform-hmr/~/react-proxy/modules/deleteUnknownAutoBindMethods.js","38":"./~/react-transform-hmr/~/react-proxy/modules/index.js","39":"./~/react-transform-hmr/~/react-proxy/~/lodash/array/difference.js","40":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/SetCache.js","41":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/arrayPush.js","42":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/assignWith.js","43":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseAssign.js","44":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseCopy.js","45":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseDifference.js","46":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseFlatten.js","47":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseIndexOf.js","48":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/baseProperty.js","49":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/bindCallback.js","50":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/cacheIndexOf.js","51":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/cachePush.js","52":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/createAssigner.js","53":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/createCache.js","54":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/getLength.js","55":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/indexOfNaN.js","56":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/isIterateeCall.js","57":"./~/react-transform-hmr/~/react-proxy/~/lodash/internal/shimKeys.js","58":"./~/react-transform-hmr/~/react-proxy/~/lodash/lang/isFunction.js","59":"./~/react-transform-hmr/~/react-proxy/~/lodash/lang/isNative.js","60":"./~/react-transform-hmr/~/react-proxy/~/lodash/object/assign.js","61":"./~/react-transform-hmr/~/react-proxy/~/lodash/object/keysIn.js","62":"./~/react-transform-hmr/~/react-proxy/~/lodash/utility/identity.js","63":"./~/react-transform-hmr/~/react-proxy/~/react-deep-force-update/lib/index.js","64":"external "ActionSheetIOS"","65":"external "ActivityIndicatorIOS"","66":"external "AdSupportIOS"","67":"external "AlertIOS"","68":"external "Animated"","69":"external "AppRegistry"","70":"external "AppStateIOS"","71":"external "AsyncStorage"","72":"external "BackAndroid"","73":"external "CameraRoll"","74":"external "DatePickerIOS"","75":"external "Dimensions"","76":"external "DrawerLayoutAndroid"","77":"external "Easing"","78":"external "EdgeInsetsPropType"","79":"external "Image"","80":"external "ImagePickerIOS"","81":"external "InteractionManager"","82":"external "LayoutAnimation"","83":"external "LinkedStateMixin"","84":"external "LinkingIOS"","85":"external "ListView"","86":"external "MapView"","87":"external "Modal"","88":"external "Navigator"","89":"external "NavigatorIOS"","90":"external "NetInfo"","91":"external "PanResponder"","92":"external "PickerIOS"","93":"external "PixelRatio"","94":"external "Platform"","95":"external "PointPropType"","96":"external "ProgressBarAndroid"","97":"external "ProgressViewIOS"","98":"external "PushNotificationIOS"","99":"external "RCTDeviceEventEmitter"","100":"external "RCTNativeAppEventEmitter"","101":"external "React"","102":"external "ReactComponentWithPureRenderMixin"","103":"external "ReactDefaultPerf"","104":"external "ReactFragment"","105":"external "ReactTestUtils"","106":"external "ReactUpdates"","107":"external "ScrollView"","108":"external "SegmentedControlIOS"","109":"external "Settings"","110":"external "SliderIOS"","111":"external "StatusBarIOS"","112":"external "StyleSheet"","113":"external "SwitchAndroid"","114":"external "SwitchIOS"","115":"external "TabBarIOS"","116":"external "Text"","117":"external "TextInput"","118":"external "ToastAndroid"","119":"external "ToolbarAndroid"","120":"external "TouchableHighlight"","121":"external "TouchableNativeFeedback"","122":"external "TouchableOpacity"","123":"external "TouchableWithoutFeedback"","124":"external "VibrationIOS"","125":"external "View"","126":"external "WebView"","127":"external "cloneWithProps"","128":"external "processColor"","129":"external "requireNativeComponent"","130":"external "update"","131":"./src/main.android.js","132":"./src/main.ios.js","133":"./~/react-native-webpack-server/hot/entry.js","134":"(webpack)-hot-middleware/client-overlay.js","135":"(webpack)-hot-middleware/client.js?path=http://localhost:8082/__webpack_hmr&overlay=false","136":"(webpack)-hot-middleware/~/querystring/decode.js","137":"(webpack)-hot-middleware/~/querystring/encode.js","138":"(webpack)-hot-middleware/~/querystring/index.js","139":"(webpack)-hot-middleware/~/strip-ansi/index.js","140":"(webpack)-hot-middleware/~/strip-ansi/~/ansi-regex/index.js","141":"(webpack)-hot-middleware/process-update.js"}} ReferenceError: hotDownloadManifest is not defined

niftylettuce avatar Oct 26 '15 19:10 niftylettuce

screen shot 2015-10-26 at 4 38 26 pm

niftylettuce avatar Oct 26 '15 20:10 niftylettuce

Here's my webpack.config.js:


var path = require('path');
var webpack = require('webpack');

var config = {
  context: __dirname,
  debug: true,
  devtool: 'source-map',
  watch: true,
  entry: {
    'index.ios': ['./src/main.ios.js']
    'index.android': ['./src/main.android.js'],
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js',
  },
  module: {
    loaders: [
      {
        test: /\.(js|jsx|es6)$/,
        loader: 'babel-loader',
        include: [
          path.resolve(__dirname, 'src'),
          path.resolve(__dirname, 'node_modules/react-native-button'),
          path.resolve(__dirname, 'node_modules/react-native-form')
        ],
        query: {
          optional: [
            'runtime'
          ],
          stage: 0,
          plugins: []
        }
      }
    ]
  },
  resolve: { extensions: ['', '.js', '.jsx', '.es6'] },
  plugins: []
};

// Hot loader
if (process.env.HOT) {
  config.devtool = 'source-map';
  config.entry['index.ios'].unshift(
    'react-native-webpack-server/hot/entry',
    'webpack-hot-middleware/client?path=http://localhost:8082/__webpack_hmr&overlay=false'
  );
  config.output.publicPath = 'http://localhost:8082/';
  config.plugins.unshift(
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  );
  config.module.loaders[0].query.plugins.push('react-transform');
  config.module.loaders[0].query.extra = {
    'react-transform': {
      transforms: [
        {
          transform: 'react-transform-hmr',
          imports: ['react-native'],
          locals: ['module']
        }
      ]
    }
  };
}

// Production config
if (process.env.NODE_ENV === 'production') {
  config.plugins.push(new webpack.optimize.OccurrenceOrderPlugin());
  config.plugins.push(new webpack.optimize.UglifyJsPlugin());
}

module.exports = config;

Here's my package.json:

{
  "name": "App",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "bundle": "react-native-webpack-server bundle",
    "start": "react-native-webpack-server start",
    "hot": "HOT=1 react-native-webpack-server start --hot",
    "start-android-webpack-server": "HOT=1 react-native-webpack-server start --hot -e index.android -P 9090 -p 9091 -w 9092",
    "install-app-to-android-device": "cd android && ./gradlew installDebug",
    "setup-reverse-tcp-for-android-device": "adb reverse tcp:8081 tcp:9090",
    "launch-android-app-on-device": "cd android && adb shell am start -n com.\"$npm_package_name\"/.MainActivity",
    "android": "npm run-script setup-reverse-tcp-for-android-device && npm run-script install-app-to-android-device && npm run-script launch-android-app-on-device && echo \"Please Reload JS on the app from the menu after the webpack server starts below\" && npm run-script start-android-webpack-server"
  },
  "dependencies": {
    "react-native": "~0.12.0",
    "react-native-button": "^1.2.1",
    "react-native-form": "^0.1.6"
  },
  "devDependencies": {
    "babel-core": "^5.8.25",
    "babel-loader": "^5.3.2",
    "babel-plugin-react-transform": "^1.1.1",
    "babel-runtime": "^5.8.25",
    "eslint-plugin-react": "^3.6.1",
    "react-native-webpack-server": "mjohnston/react-native-webpack-server#rn-0.12-hmr",
    "react-transform-hmr": "^1.0.1",
    "webpack": "elliottsj/webpack#web-worker-hmr"
  }
}

niftylettuce avatar Oct 26 '15 20:10 niftylettuce

@niftylettuce You also need target: 'webworker' in your webpack config, e.g. https://github.com/mjohnston/react-native-webpack-server/blob/rn-0.12-hmr/Examples/BabelES6/webpack.config.js#L7

I'm happy to help on Discord#react-native-webpack if you need more help :smile:

elliottsj avatar Oct 26 '15 20:10 elliottsj

That didn't work, I got hotModule not defined error above if I put webworker:true in config On Oct 26, 2015 4:53 PM, "Spencer Elliott" [email protected] wrote:

@niftylettuce https://github.com/niftylettuce You also need target: 'webworker' in your webpack config, e.g. https://github.com/mjohnston/react-native-webpack-server/blob/rn-0.12-hmr/Examples/BabelES6/webpack.config.js#L7

I'm happy to help on Discord#react-native-webpack http://www.reactiflux.com/ if you need more help [image: :smile:]

— Reply to this email directly or view it on GitHub https://github.com/mjohnston/react-native-webpack-server/issues/103#issuecomment-151281886 .

niftylettuce avatar Oct 26 '15 21:10 niftylettuce

Got it working! :balloon:

niftylettuce avatar Oct 27 '15 00:10 niftylettuce

Then share it! Step by step :-)

Sent from my iPhone

On 27 Oct 2015, at 01:09, niftylettuce [email protected] wrote:

Got it working!

— Reply to this email directly or view it on GitHub.

ptomasroos avatar Oct 27 '15 05:10 ptomasroos

@niftylettuce Did you make it worked? I have the same issue with you: Invalid HMR message: .... I also add target: 'webworker' to config file but nothing happen.

Please share your tips.

quangrau avatar Nov 03 '15 16:11 quangrau

@niftylettuce I also have the same issue as you. How did you make it work? Hope you can help us, thanks!

RyGuyM avatar Nov 04 '15 17:11 RyGuyM

I don't recommend to use hotloading until it has complete Android and iOS support. The headache and time ensued for me getting it to work, but now having to turn it off is not worth having it. Just CMD+R and reload. You don't need experimental hot reloading. On Nov 4, 2015 12:55 PM, "RyGuyM" [email protected] wrote:

@niftylettuce https://github.com/niftylettuce I also have the same issue as you. How did you make it work? Hope you can help us, thanks!

— Reply to this email directly or view it on GitHub https://github.com/mjohnston/react-native-webpack-server/issues/103#issuecomment-153808955 .

niftylettuce avatar Nov 04 '15 17:11 niftylettuce