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

How to integrate this into non-browser environments? #79

Closed
shirakaba opened this issue Apr 22, 2020 · 32 comments
Closed

How to integrate this into non-browser environments? #79

shirakaba opened this issue Apr 22, 2020 · 32 comments

Comments

@shirakaba
Copy link

I tried integrating this for my custom React renderer which uses a JS environment more similar to Node (e.g. uses global rather than window). After I aliased global as window, it got stumped by the lack of an addEventListener API:

file: node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/errorEventHandlers.js:85:10: JS ERROR TypeError: global.addEventListener is not a function. (In 'global.addEventListener(eventType, eventHandler)', 'global.addEventListener' is undefined)

What are the browser dependencies, and can anything be done to decouple it from the browser?

@pmmmwh
Copy link
Owner

pmmmwh commented Apr 23, 2020

What are the browser dependencies, and can anything be done to decouple it from the browser?

As of now, the core of plugin depends only on the location API (window.location) for reload functionality (and socket routing, if you're using WDS). The error overlay integration further depends on window.addEventListener and window.removeEventListener (and DOM APIs for rendering), because we listens on the error and the unhandledrejection events.

I'm not sure about your use case, but in reality you could disable the error overlay integration or use a custom error overlay setup to correctly wire up error-like events listening (only on 0.3.0). For the part related to the location API, I'm working on a feature to not force reload when unaccepted updates happen, so after that lands we should be somehow decoupled from the browser.

@pmmmwh
Copy link
Owner

pmmmwh commented Apr 26, 2020

Can anything be done to decouple it from the browser?

@shirakaba
I've read a bit on the Node.js process API and it seems that the uncaughtExceptionMonitor event would cover what an error overlay requires. However, I'm not sure if that's something that is generally available for custom JS-based renderers. Would like your thoughts before I take any further action.

@dangreen
Copy link

@pmmmwh in dev environment with ssr this plugin cause the error ReferenceError: window is not defined

@pmmmwh
Copy link
Owner

pmmmwh commented May 26, 2020

@pmmmwh in dev environment with ssr this plugin cause the error ReferenceError: window is not defined

Can you provide more background information, like where the error originates, whether the plugin is applied for the server bundle, etc.?

Also, see #36 for some solutions others have figured out.

@shirakaba
Copy link
Author

@pmmmwh Sorry for disappearing! Another project has taken up my full attention for the last month, but it should be finished in a few weeks, after which time I can dedicate more attention back to this.

Thank you very much for responding with details. I'll answer a few things now:

As of now, the core of plugin depends only on the location API (window.location) for reload functionality (and socket routing, if you're using WDS).

Do you mean specifically just window.location.reload(), or a few other things as well? I'd have to research what the NativeScript equivalent would be (will likely involve interrogating the core team). Might have something to do with View.prototype._handleLivesync and global.__onLiveSync.

I've read a bit on the Node.js process API and it seems that the uncaughtExceptionMonitor event would cover what an error overlay requires.

I presume here we're talking about the runtime running the app, rather than the runtime running Webpack?

The JS context in NativeScript apps doesn't implement (the majority of) the Node.js APIs, for example process. NativeScript uses forks of both JavaScriptCore (for iOS) and V8 (for Android; but will soon be used for iOS instead of JSC).

NativeScript seems to have the following approach (copied below). I think it applies equally to both JS errors and native errors:

var application = require("application");

application.on(application.uncaughtErrorEvent, function (args) {
    if (args.android) {
        // For Android applications, args.android is an NativeScriptError.
        console.log("NativeScriptError: " + args.android);
    } else if (args.ios) {
        // For iOS applications, args.ios is NativeScriptError.
        console.log("NativeScriptError: " + args.ios);
    }
});

@pmmmwh
Copy link
Owner

pmmmwh commented May 28, 2020

Do you mean specifically just window.location.reload(), or a few other things as well?

Specifically just window.location.reload() - that's the "de-facto" way of bailing out of HMR updates in the Webpack ecosystem for the browser. I'm not sure if this concept translates to the native world though.

In the next version I should be able to not bail out by default (in fact, there's only a single call to window.location.reload() now left in the code base).

I presume here we're talking about the runtime running the app, rather than the runtime running Webpack?

Yup. I think for your case, you'll have to either implement a custom error overlay integration or disable it (Are DOM APIs available in NativeScript?).

I think it would look something like this (I only skimmed through the docs so this might not work):

import * as ErrorOverlay from '@pmmmwh/react-refresh-webpack-plugin/src/overlay';
import * as application from 'tns-core-modules/application';

// Copy everything from './src/runtime/ErrorOverlayEntry.js',
// except the lines calling `registerSomethingHandler`.

application.on(application.uncaughtErrorEvent, (args) => {
    ErrorOverlay.handleRuntimeError(args.error);
});

Or maybe even better, if you use trace.error everywhere for errors:

import * as ErrorOverlay from '@pmmmwh/react-refresh-webpack-plugin/src/overlay';
import * as traceModule from 'tns-core-modules/trace';

// Copy everything from './src/runtime/ErrorOverlayEntry.js',
// except the lines calling `registerSomethingHandler`.

const errorHandler: traceModule.ErrorHandler = {
  handlerError(error) {
    traceModule.write(err, 'unhandled-error', type.error);
    ErrorOverlay.handleRuntimeError(error);
  },
}

traceModule.setErrorHandler(errorHandler);

@shirakaba
Copy link
Author

Specifically just window.location.reload() - that's the "de-facto" way of bailing out of HMR updates in the Webpack ecosystem for the browser. I'm not sure if this concept translates to the native world though.

When you say "bailing out of HMR updates", are you referring to the case when a hot update cannot practically be applied, so the best response available is to restart the app afresh and load in the latest bundle? Thus, on web, window.location.reload() fits that role?

If so, I'll have to ask what the closest equivalent is to that in NativeScript. In React Native, it's surely the action that happens when you force a reload of the JS bundle, but NativeScript doesn't to my knowledge have something like that (at least not so conveniently) yet.

Yup. I think for your case, you'll have to either implement a custom error overlay integration or disable it (Are DOM APIs available in NativeScript?).

NativeScript doesn't implement HTML Elements like <div>, nor does it implement DOM – but React NativeScript does implement (has borrowed from NativeScript Vue, actually) a DOM interface, so it conforms to the DOM model and implements Elements. So we're missing things like document and window but we've got things like element.addEventListener.

I'm sure the task of translating the error overlay to React NativeScript won't be a show-stopper 🙂 NativeScript does at least implement CSS and flexbox.

I think it would look something like this (I only skimmed through the docs so this might not work):

Thank you very much for these code snippets (and even diving into the NativeScript codebase)! That's an enormous help. I haven't actually tried using the trace module myself yet (though I've seen it being used in NativeScript Vue), but it may indeed be a better fit than application.uncaughtErrorEvent. I'll look into both of them and assess which one makes more sense.

So it seems like the path forward would be:

  1. For an initial sanity test, disable the error overlay altogether and check whether React NativeScript hits any other obstacles at runtime;
  2. Determine a NativeScript equivalent to window.location.reload and polyfill that. Again, for an initial sanity test, I could just throw an obvious error to kill the app and force it to restart;
  3. Port ErrorOverlay to React NativeScript, and invoke it upon uncaught/unhandled errors.

In the next version I should be able to not bail out by default (in fact, there's only a single call to window.location.reload() now left in the code base).

Would it be possible to expose an option for providing one's own implementation of reload (rather than having to create a shim that clobbers window.location.reload())?

Also: I'm not sure how to provide a custom ErrorOverlay to the React Refresh plugin. I can see that the plugin (I guess we're talking about the Webpack plugin rather than the Babel plugin?) accepts an options object, and that it has an options.overlay field...

... But as it's Node.js reading and running webpack.config.js and constructing the React Refresh plugin instance, how is my React NativeScript implementation of ErrorOverlay (which can only run in a NativeScript environment, not a Node.js environment) supposed to be passed in?

It's likely I'm missing some piece of logic..!

@pmmmwh
Copy link
Owner

pmmmwh commented May 29, 2020

Determine a NativeScript equivalent to window.location.reload and polyfill that. Again, for an initial sanity test, I could just throw an obvious error to kill the app and force it to restart;

Would it be possible to expose an option for providing one's own implementation of reload (rather than having to create a shim that clobbers window.location.reload())?

After #104 lands the call to window.location.reload() will be eliminated altogether, so you can skip these steps too.

How is my React NativeScript implementation of ErrorOverlay (which can only run in a NativeScript environment, not a Node.js environment) supposed to be passed in?

You can write it as if it is in a NativeScript environment - the implementation will be injected via webpack.ProvidePlugin in the resulted bundle so it will only run with the bundle.

@shirakaba
Copy link
Author

After #104 lands the call to window.location.reload() will be eliminated altogether, so you can skip these steps too.

That's brilliant! Thanks so much 🙇‍♂️

You can write it as if it is in a NativeScript environment - the implementation will be injected via webpack.ProvidePlugin in the resulted bundle so it will only run with the bundle.

That seems like magic! Will I even be able to make imports (I'll need to build the UI using imports such as import { FlexboxLayout } from "@nativescript/core";) in my implementation of ErrorOverlay? Just that I haven't used ProvidePlugin before.

@pmmmwh
Copy link
Owner

pmmmwh commented May 29, 2020

That seems like magic! Will I even be able to make imports (I'll need to build the UI using imports such as import { FlexboxLayout } from "@nativescript/core";) in my implementation of ErrorOverlay?

Yes, it should "just-work" like any other code you write.

Edit: Also, #104 has landed in v0.4.0-beta.2.

@shirakaba
Copy link
Author

@pmmmwh Awesome, sounds like all the ingredients are there for it to work. It might be a few weekends before I'm free to try integrating it again (or maybe I'll even find a moment this weekend), but I'll certainly report back once I've tried! Thanks for your thorough response and timely refactors!

@shirakaba
Copy link
Author

@pmmmwh I've come back to this and have installed version 0.4.0-beta.2. Here's the error I get upon my Webpack build:

file:///app/bundle.js:4367:13: JS ERROR ReferenceError: Can't find variable: $RefreshReg$
at ./testComponents/navigation.tsx(file:///app/bundle.js:4397:34)
at __webpack_require__(file: app/webpack/bootstrap:770:0)
at fn(file: app/webpack/bootstrap:120:0)
at file: app/testComponents/AppContainer.tsx:1:0
at ./testComponents/AppContainer.tsx(file:///app/bundle.js:3254:34)
at __webpack_require__(file: app/webpack/bootstrap:770:0)
at fn(file: app/webpack/bootstrap:120:0)
at file: app/app.ts:1:31
at ./app.ts(file:///app/bundle.js:3139:34)
at __webpack_require__(file: app/webpack/bootstrap:770:0)
at fn(file: app/webpack/bootstrap:120:0)
at file:///app/bundle.js:4405:37
at __webpack_require__(file: app/webpack/bootstrap:770:0)
at checkDeferredModules(file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback(file: app/webpack/bootstrap:30:0)
at anonymous(file:///app/bundle.js:2:61)
at evaluate([native code])
at moduleEvaluation([native code])
at [native code]
at asyncFun<…>

Here's my webpack config, on the branch fast-refresh: react-nativescript.webpack.config.js. It inherits from (and overrides) this webpack.config.js. Sorry for the complexity (that's the way NativeScript webpack configs are).

Possibly related issues:
#92
#36

And PR:
#102.

In #92, with regards to a similar error for $RefreshSig$, you mentioned:

My guess is that you've included the react-refresh/babel plugin to process node_modules. This will break because some code (as used by Webpack and WDS) will inevitably run before the plugin.

However, in my case I have excluded node_modules in my rule using babel-loader.

Do you have any ideas what could be the problem here?

@shirakaba
Copy link
Author

shirakaba commented Jun 7, 2020

Update: by adding these aliases with DefinePlugin, I was able to squash that error. The app now builds and runs! 🥳

new webpack.DefinePlugin({
    "window": "global.global",
    "process.env.NODE_ENV": JSON.stringify(production ? "production" : "development"),
})

However, even though changes are detected and updates applied (I believe by HotModuleReplacementPlugin), the component on-screen does not visually update.

Webpack compilation complete. Watching for file changes.
Webpack build done!
Successfully transferred bundle.86e97e9f3df714734087.hot-update.js on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Successfully transferred 86e97e9f3df714734087.hot-update.json on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Refreshing application on device 57DE46EC-8E05-41EF-964C-AF0686B7586B...
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Checking for updates to the bundle with hmr hash 86e97e9f3df714734087.
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: The following modules were updated:
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR:          ↻ ./testComponents/navigation.tsx
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Successfully applied update with hmr hash 86e97e9f3df714734087. App is up to date.
Successfully synced application uk.co.birchlabs.rnssample on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.

When I was using React Hot Loader, that console output would have been considered healthy and would have led to visual updates. What might be the problem here?

@shirakaba
Copy link
Author

Have an idea. Just checking...

@shirakaba
Copy link
Author

shirakaba commented Jun 7, 2020

No luck. I think my Webpack config has some issues not relating to Fast Refresh that I need to iron out first. I'll look into those first.

EDIT: My Webpack config looks fine as far as I can see...

@pmmmwh
Copy link
Owner

pmmmwh commented Jun 8, 2020

When I was using React Hot Loader, that console output would have been considered healthy and would have led to visual updates. What might be the problem here?

Can you try 0.4.0-beta.3? It contains the fixes in #102 and should remove the need for you to mock window as global.global. I would assume that $RefreshReg$ and $RefreshSig$ calls aren't inferred correctly when mocking global scope.

@shirakaba
Copy link
Author

shirakaba commented Jun 11, 2020

@pmmmwh Sorry for the delay – and thanks for the changes!

I've just tried with 0.4.0-beta.3, removing my mocking of window as global.global. It's far happier now (the Webpack build on each hot update is far faster, where before it looked like it was rebuilding the whole app or something).

However, saved changes to my app code are still not resulting in visual changes in the app.

Here is my Webpack output, which looks healthy (from experience with React Hot Loader):

$ tns run ios --emulator
Searching for devices...
Preparing project...
File change detected. Starting incremental webpack compilation...
Starting type checking service...
Using 1 worker with 4096MB memory limit

webpack is watching the files…

Hash: f08eb4f022d21345b176
Version: webpack 4.27.1
Time: 35400ms
Built at: 11/06/2020 23:01:58
                                            Asset       Size                                          Chunks             Chunk Names
                                        bundle.js    391 KiB                                          bundle  [emitted]  bundle
          images/RNTester_Thumbnails/bandaged.png   5.42 KiB                                                  [emitted]  
              images/RNTester_Thumbnails/call.png   8.12 KiB                                                  [emitted]  
           images/RNTester_Thumbnails/dislike.png   4.04 KiB                                                  [emitted]  
              images/RNTester_Thumbnails/fist.png   5.64 KiB                                                  [emitted]  
           images/RNTester_Thumbnails/flowers.png   9.48 KiB                                                  [emitted]  
             images/RNTester_Thumbnails/heart.png   6.89 KiB                                                  [emitted]  
              images/RNTester_Thumbnails/like.png    4.2 KiB                                                  [emitted]  
            images/RNTester_Thumbnails/liking.png   6.59 KiB                                                  [emitted]  
             images/RNTester_Thumbnails/party.png   8.37 KiB                                                  [emitted]  
              images/RNTester_Thumbnails/poke.png   4.81 KiB                                                  [emitted]  
         images/RNTester_Thumbnails/superlike.png   6.79 KiB                                                  [emitted]  
           images/RNTester_Thumbnails/victory.png   6.89 KiB                                                  [emitted]  
                                     package.json   81 bytes                                                  [emitted]  
                                       runtime.js   75.2 KiB                                         runtime  [emitted]  runtime
tns_modules/tns-core-modules/inspector_modules.js  874 bytes  tns_modules/tns-core-modules/inspector_modules  [emitted]  tns_modules/tns-core-modules/inspector_modules
                                        vendor.js   8.37 MiB                                          vendor  [emitted]  vendor
Entrypoint bundle = runtime.js vendor.js bundle.js
Entrypoint tns_modules/tns-core-modules/inspector_modules = runtime.js vendor.js tns_modules/tns-core-modules/inspector_modules.js
[0] multi ../node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ReactRefreshEntry.js ./app.ts 40 bytes {bundle} [built]
[1] multi ../node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ReactRefreshEntry.js inspector_modules 40 bytes {tns_modules/tns-core-modules/inspector_modules} [built]
[../../react-nativescript/dist/client/ComponentTree.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/client/ComponentTree.js 4.24 KiB {bundle} [built]
[../../react-nativescript/dist/client/HostConfig.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/client/HostConfig.js 19.6 KiB {bundle} [built]
[../../react-nativescript/dist/client/ReactNativeScriptComponent.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/client/ReactNativeScriptComponent.js 20.8 KiB {bundle} [built]
[../../react-nativescript/dist/client/ReactPortal.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/client/ReactPortal.js 2.45 KiB {bundle} [built]
[../../react-nativescript/dist/index.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/index.js 6.46 KiB {bundle} [built]
[../../react-nativescript/dist/nativescript-vue-next/runtime/nodes.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/nativescript-vue-next/runtime/nodes.js 16.1 KiB {bundle} [built]
[../../react-nativescript/dist/shared/Logger.js] /Users/jamie/Documents/git/react-nativescript/react-nativescript/dist/shared/Logger.js 2.29 KiB {bundle} [built]
[./ sync ^\.\/app\.(css|scss|less|sass)$] . sync nonrecursive ^\.\/app\.(css|scss|less|sass)$ 174 bytes {bundle} [built]
[./ sync recursive (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$] . sync (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$ 187 bytes {bundle} [built]
[./app.css] 1.66 KiB {bundle} [optional] [built]
[./app.ts] 3.29 KiB {bundle} [built]
[./testComponents/AppContainer.tsx] 3.66 KiB {bundle} [built]
[./testComponents/navigation.tsx] 8.68 KiB {bundle} [built]
    + 372 hidden modules
Webpack compilation complete. Watching for file changes.
Webpack build done!
Project successfully prepared (ios)
Successfully transferred all files on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Restarting application on device 57DE46EC-8E05-41EF-964C-AF0686B7586B...
CONSOLE WARN file: node_modules/@nativescript/core/ui/tab-view/tab-view.ios.js:18:0: Objective-C class name "UITabBarControllerImpl" is already in use - using "UITabBarControllerImpl1" instead.
CONSOLE WARN file: node_modules/@nativescript/core/ui/tab-view/tab-view.ios.js:79:0: Objective-C class name "UITabBarControllerDelegateImpl" is already in use - using "UITabBarControllerDelegateImpl1" instead.
CONSOLE WARN file: node_modules/@nativescript/core/ui/tab-view/tab-view.ios.js:117:0: Objective-C class name "UINavigationControllerDelegateImpl" is already in use - using "UINavigationControllerDelegateImpl1" instead.
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Hot Module Replacement Enabled. Waiting for signal.
NativeScript debugger has opened inspector socket on port 18183 for uk.co.birchlabs.rnssample.
Successfully synced application uk.co.birchlabs.rnssample on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
CONSOLE LOG file: node_modules/@nativescript/core/inspector_modules.ios.js:1:0: Loading inspector modules...
CONSOLE LOG file: node_modules/@nativescript/core/inspector_modules.ios.js:6:0: Finished loading inspector modules.
NativeScript debugger attached.

# I saved a change at this point.

File change detected. Starting incremental webpack compilation...
Hash: 1d94f1a3c933da800f70
Version: webpack 4.27.1
Time: 2898ms
Built at: 11/06/2020 23:02:26
                                    Asset      Size   Chunks             Chunk Names
bundle.f08eb4f022d21345b176.hot-update.js  15.5 KiB   bundle  [emitted]  bundle
                                bundle.js   391 KiB   bundle  [emitted]  bundle
     f08eb4f022d21345b176.hot-update.json  48 bytes           [emitted]  
                               runtime.js  75.2 KiB  runtime  [emitted]  runtime
 + 2 hidden assets
Entrypoint bundle = runtime.js vendor.js bundle.js bundle.f08eb4f022d21345b176.hot-update.js
Entrypoint tns_modules/tns-core-modules/inspector_modules = runtime.js vendor.js tns_modules/tns-core-modules/inspector_modules.js
[./ sync ^\.\/app\.(css|scss|less|sass)$] . sync nonrecursive ^\.\/app\.(css|scss|less|sass)$ 174 bytes {bundle} [built]
[./ sync recursive (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$] . sync (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$ 187 bytes {bundle} [built]
[./testComponents/navigation.tsx] 8.68 KiB {bundle} [built]
    + 384 hidden modules
Webpack compilation complete. Watching for file changes.
Webpack build done!
Successfully transferred bundle.f08eb4f022d21345b176.hot-update.js on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Successfully transferred f08eb4f022d21345b176.hot-update.json on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Refreshing application on device 57DE46EC-8E05-41EF-964C-AF0686B7586B...
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Checking for updates to the bundle with hmr hash f08eb4f022d21345b176.
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: The following modules were updated:
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR:          ↻ ./testComponents/navigation.tsx
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Successfully applied update with hmr hash f08eb4f022d21345b176. App is up to date.
Successfully synced application uk.co.birchlabs.rnssample on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.

EDIT: Here's the latest commit of my fast-refresh branch, if it helps.

@shirakaba
Copy link
Author

shirakaba commented Jun 11, 2020

Here is also a quick printout of what react-nativescript.webpack.config.js resolves to (as of commit 043abdb, which at this moment is the latest commit on the fast-refresh branch), with the following quick-and-dirty logging approach:

console.log("baseConfig:", util.inspect(baseConfig, {showHidden: false, depth: null, colors: true }));
console.log("All env variables:", env);
console.log("--hmr", hmr);
console.log("--production", production);
baseConfig: {
  mode: 'development',
  context: '/Users/jamie/Documents/git/react-nativescript/sample/app',
  externals: [],
  watchOptions: {
    ignored: [
      '/Users/jamie/Documents/git/react-nativescript/sample/app/App_Resources',
      '**/.*'
    ]
  },
  target: [Function: nativescriptTarget],
  entry: {
    bundle: './app.ts',
    'tns_modules/tns-core-modules/inspector_modules': 'inspector_modules'
  },
  output: {
    pathinfo: false,
    path: '/Users/jamie/Documents/git/react-nativescript/sample/platforms/ios/sample/app',
    sourceMapFilename: '[file].map',
    libraryTarget: 'commonjs2',
    filename: '[name].js',
    globalObject: 'global',
    hashSalt: '1591918765585'
  },
  resolve: {
    extensions: [ '.ts', '.tsx', '.js', '.jsx', '.scss', '.css' ],
    modules: [
      '/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@nativescript/core',
      '/Users/jamie/Documents/git/react-nativescript/sample/node_modules',
      'node_modules/@nativescript/core',
      'node_modules'
    ],
    alias: {
      '~': '/Users/jamie/Documents/git/react-nativescript/sample/app',
      'tns-core-modules': '@nativescript/core',
      'react-dom': 'react-nativescript'
    },
    symlinks: true
  },
  resolveLoader: { symlinks: false },
  node: {
    http: false,
    timers: false,
    setImmediate: false,
    fs: 'empty',
    __dirname: false
  },
  devtool: 'inline-source-map',
  optimization: {
    runtimeChunk: 'single',
    noEmitOnErrors: true,
    splitChunks: {
      cacheGroups: {
        vendor: {
          name: 'vendor',
          chunks: 'all',
          test: [Function: test],
          enforce: true
        }
      }
    },
    minimize: false,
    minimizer: [
      TerserPlugin {
        options: {
          test: /\.m?js(\?.*)?$/i,
          chunkFilter: [Function: chunkFilter],
          warningsFilter: [Function: warningsFilter],
          extractComments: false,
          sourceMap: true,
          cache: true,
          cacheKeys: [Function: cacheKeys],
          parallel: true,
          include: undefined,
          exclude: undefined,
          minify: undefined,
          terserOptions: {
            output: { comments: false, semicolons: false },
            compress: { collapse_vars: true, sequences: true }
          }
        }
      }
    ]
  },
  module: {
    rules: [
      {
        include: '/Users/jamie/Documents/git/react-nativescript/sample/app/app.ts',
        use: [
          {
            loader: 'nativescript-dev-webpack/bundle-config-loader',
            options: {
              loadCss: true,
              unitTesting: undefined,
              appFullPath: '/Users/jamie/Documents/git/react-nativescript/sample/app',
              projectRoot: '/Users/jamie/Documents/git/react-nativescript/sample',
              ignoredFiles: []
            }
          }
        ]
      },
      {
        test: /\.(ts|tsx|js|jsx|css|scss|html|xml)$/,
        use: 'nativescript-dev-webpack/hmr/hot-loader'
      },
      {
        test: /\.(html|xml)$/,
        use: 'nativescript-dev-webpack/xml-namespace-loader'
      },
      {
        test: /\.css$/,
        use: 'nativescript-dev-webpack/css2json-loader'
      },
      {
        test: /\.scss$/,
        use: [ 'nativescript-dev-webpack/css2json-loader', 'sass-loader' ]
      },
      {
        test: /\.[jt]s(x?)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              sourceMaps: 'inline',
              babelrc: false,
              presets: [ '@babel/env', '@babel/typescript', '@babel/react' ],
              plugins: [
                '/Users/jamie/Documents/git/react-nativescript/sample/node_modules/react-refresh/babel.js',
                [
                  '@babel/plugin-proposal-class-properties',
                  { loose: true }
                ]
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    DefinePlugin {
      definitions: {
        __DEV__: 'true',
        __TEST__: 'false',
        'process.env.NODE_ENV': '"development"'
      }
    },
    CleanWebpackPlugin {
      paths: [
        '/Users/jamie/Documents/git/react-nativescript/sample/platforms/ios/sample/app/**/*'
      ],
      options: {
        verbose: false,
        allowExternal: false,
        dry: false,
        root: '/Users/jamie/Documents/git/react-nativescript/sample'
      }
    },
    { apply: [Function: apply] },
    GenerateNativeScriptEntryPointsPlugin {
      appEntryName: 'bundle',
      files: {}
    },
    NativeScriptWorkerPlugin {
      options: {},
      [Symbol(NATIVESCRIPT_WORKER_PLUGIN_SYMBOL)]: true
    },
    PlatformFSPlugin {
      platform: 'ios',
      platforms: [ 'ios', 'android' ],
      ignore: []
    },
    WatchStateLoggerPlugin {},
    HotModuleReplacementPlugin {
      options: {},
      multiStep: undefined,
      fullBuildTimeout: 200,
      requestTimeout: 10000
    },
    ReactRefreshPlugin {
      options: {
        exclude: /node_modules/,
        forceEnable: undefined,
        include: /\.([jt]sx?|flow)$/,
        overlay: false,
        useLegacyWDSSockets: undefined
      }
    }
  ]
}
All env variables: {
  hmr: true,
  ios: true,
  appPath: 'app',
  appResourcesPath: 'app/App_Resources',
  sourceMap: true
}
--hmr true
--production undefined

@shirakaba
Copy link
Author

shirakaba commented Jun 12, 2020

Of interest:

If I insert, and save, a console.log("test"); statement into the render method of any React class component, it does not run upon the HMR update being applied.

However, if I insert and save that same statement into the main body of the .tsx file, it does run.

So HMR in general is working; it just seems that React components aren't applying those HMR updates.

@ersinakinci
Copy link

ersinakinci commented Jun 13, 2020

I came here from the internet while trying to get this plugin to work with SSR. For anyone else struggling to figure it out:

  1. Install 0.4.0-beta.3 or above.
  2. As of writing, you need to disable the overlay in your SSR build with { overlay: false } in the plugin's options. It appears that feat: set react-refresh globals on Webpack require instead of global scope #102 didn't refactor references to window inside the overlay code (which kinda makes sense, since you don't need an overlay in SSR).

@pmmmwh, I think that the plugin should fail gracefully with a helpful error message if the overlay is enabled in a non-browser environment. Ideally it shouldn't fail at all and/or do a no-op, but I don't know what are the implications of ignoring the overlay altogether (e.g., is the overlay the only way to get errors from the plugin?) I read in another issue that you're looking to overhaul the overlay code, so perhaps this could be rolled into the new implementation?

Thanks for all your hard work on this awesome plugin 😄.

@pmmmwh
Copy link
Owner

pmmmwh commented Jun 13, 2020

It just seems that React components aren't applying those HMR updates.

@shirakaba I think I've found the culprit.

react-refresh depends on developer tools integration, which needs to be manually injected via injectIntoDevTools from react-reconciler. react-nativescript doesn't do that yet, so all the scheduleReactRefresh calls from the plugin essentially become no-ops.

You'll have to somehow add this code snippet somewhere (presumably within the start function?):

const { version } = require('./package.json');

reactReconcilerInst.injectIntoDevTools({
  bundleType: __DEV__ ? 1 : 0,
  rendererPackageName: 'react-nativescript',
  version,
});

@shirakaba
Copy link
Author

@pmmmwh You're a genius!! It's working! 🤯🚀

I actually remember writing that mysterious line in a long time ago, to support the old React Devtools – but ended up taking it out, I believe because I was instructed that the new React Devtools didn't require it anymore. Incredible intuition, @pmmmwh and thank you so much!

Now for a stretch goal, I'm wondering how to get the Error Overlay working.

Current setup

Here's my current setup:

react-nativescript.webpack.config.js

if(hmr){
    baseConfig.plugins.push(new ReactRefreshWebpackPlugin({
        overlay: {
            // I'm placing these files alongside the sample app for now; will move them into the library itself later.
            // '/Users/jamie/Documents/git/react-nativescript/sample/ErrorOverlayEntry.js'
            entry: require.resolve('./ErrorOverlayEntry'),
            // '/Users/jamie/Documents/git/react-nativescript/sample/errorOverlay.js'
            module: require.resolve('./errorOverlay'),
        },
    }));
}

sample/ErrorOverlayEntry.js

// NativeScript implements the Console APIs.
function showCompileError(webpackErrorMessage) {
    console.error(`[errorEntry.showCompileError]`, webpackErrorMessage);
}
function clearCompileErrors() {
    console.error(`[errorEntry.clearCompileErrors]`);
}

/* Seems like I needn't export these? Correct me if I'm wrong. */
// module.exports = {
//     showCompileError: showCompileError,
//     clearCompileErrors: clearCompileErrors
// };

sample/errorOverlay.js

// NativeScript implements the Console APIs.
function handleRuntimeError(error) {
    console.error(`[errorOverlay.handleRuntimeError]`, error);
}
function clearRuntimeErrors() {
    console.error(`[errorOverlay.clearRuntimeErrors]`);
}

/* Seems like I needn't export these? Correct me if I'm wrong. */
// module.exports = {
//     handleRuntimeError: handleRuntimeError,
//     clearRuntimeErrors: clearRuntimeErrors
// };

Error at runtime

It doesn't run, however, due to the error:

TypeError: __react_refresh_utils__.getModuleExports is not a function. (In '__react_refresh_utils__.getModuleExports(module)', '__react_refresh_utils__.getModuleExports' is undefined)

I've looked into the Webpack plugin's code and couldn't see why getModuleExports, whose function declaration is here, might be undefined. Maybe I'm misreading it or something.

...
[./app.css] 1.66 KiB {bundle} [optional] [built]
[./app.ts] 3.29 KiB {bundle} [built]
[./testComponents/AppContainer.tsx] 1.57 KiB {bundle} [built]
    + 376 hidden modules
Webpack compilation complete. Watching for file changes.
Webpack build done!
Project successfully prepared (ios)
Successfully transferred all files on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Restarting application on device 57DE46EC-8E05-41EF-964C-AF0686B7586B...
***** Fatal JavaScript exception - application has been terminated. *****
Native stack trace:
1   0x10587b60e NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
2   0x1058cd5f4 -[TNSRuntime executeModule:referredBy:]
3   0x10516edc3 main
4   0x7fff51a231fd start
5   0x1
JavaScript stack trace:
file:///app/bundle.js:3578:64
at ../errorOverlay.js(file:///app/bundle.js:3605:34)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at ../node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/refreshUtils.js(file:///app/vendor.js:62730:60)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at ../ErrorOverlayEntry.js(file:///app/bundle.js:3550:126)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at file:///app/bundle.js:3869:20
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at checkDeferredModules(file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback(file: app/webpack/bootstrap:30:0)
at anonymous(file:///app/bundle.js:2:61)
at evaluate([native code])
at moduleEvaluation([native code])
at [native code]
at asyncFunctionResume([native code])
at [native code]
at promiseReactionJob([native code])
JavaScript error:
file:///app/bundle.js:3578:64: JS ERROR TypeError: __react_refresh_utils__.getModuleExports is not a function. (In '__react_refresh_utils__.getModuleExports(module)', '__react_refresh_utils__.getModuleExports' is undefined)
(CoreFoundation) *** Terminating app due to uncaught exception 'NativeScript encountered a fatal error: TypeError: __react_refresh_utils__.getModuleExports is not a function. (In '__react_refresh_utils__.getModuleExports(module)', '__react_refresh_utils__.getModuleExports' is undefined)
at
file:///app/bundle.js:3578:64
at ../errorOverlay.js(file:///app/bundle.js:3605:34)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at ../node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/refreshUtils.js(file:///app/vendor.js:62730:60)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at ../ErrorOverlayEntry.js(file:///app/bundle.js:3550:126)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at file:///app/bundle.js:3869:20
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at checkDeferredModules(file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback(file: app/webpack/bootstrap:30:0)
at anonymous(<…>
NativeScript caught signal 6.
Native Stack:
1   0x1058cc251 sig_handler(int)
2   0x7fff51c005fd _sigtramp
3   0x7fff51af4f39 itoa64
4   0x7fff51af0b7c abort
5   0x7fff4f9f7858 abort_message
6   0x7fff4f9e8cbf demangling_unexpected_handler()
7   0x7fff50ba8c0b _objc_terminate()
8   0x7fff4f9f6c87 std::__terminate(void (*)())
9   0x7fff4f9f940b __cxa_get_exception_ptr
10  0x7fff4f9f93d2 __cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*)
11  0x7fff50ba8ad6 _objc_exception_destructor(void*)
12  0x10587bb4f NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
13  0x1058cd5f4 -[TNSRuntime executeModule:referredBy:]
14  0x10516edc3 main
15  0x7fff51a231fd start
16  0x1
JS Stack:

@pmmmwh
Copy link
Owner

pmmmwh commented Jun 15, 2020

It doesn't run

Ahh! This is an undiscovered bug in the plugin. I'll fix it and release 0.4.0-beta.5.

Edit: @shirakaba Can you try patching the changes from #116 first to see if the project builds? I'll need a bit more time to experiment with a few approaches.

Edit again: released in 0.4.0-beta.5.

@shirakaba
Copy link
Author

shirakaba commented Jun 15, 2020

@pmmmwh Thank you very much for the debugging, refactor, and extra release!

By installing 0.4.0-beta.5, the app survives startup and I can see clearRuntimeErrors() being called in the console upon successfully applying a HMR update!

My setup

// react-nativescript.webpack.config.js
if(hmr){
    baseConfig.plugins.push(new ReactRefreshWebpackPlugin({
        overlay: {
            // I'm placing these files alongside the sample app for now; will move them into the library itself later.
            // '/Users/jamie/Documents/git/react-nativescript/sample/ErrorOverlayEntry.js'
            entry: require.resolve('./ErrorOverlayEntry'),
            // '/Users/jamie/Documents/git/react-nativescript/sample/errorOverlay.js'
            module: require.resolve('./errorOverlay'),
        },
    }));
}
// ErrorOverlayEntry.js
function showCompileError(webpackErrorMessage) {
    console.error(`[errorEntry.showCompileError]`, webpackErrorMessage);
}
function clearCompileErrors() {
    console.error(`[errorEntry.clearCompileErrors]`);
}

module.exports = {
    showCompileError: showCompileError,
    clearCompileErrors: clearCompileErrors
};
// errorOverlay.js
function handleRuntimeError(error) {
    console.error(`[errorOverlay.handleRuntimeError]`, error);
}
function clearRuntimeErrors() {
    console.error(`[errorOverlay.clearRuntimeErrors]`);
}

module.exports = {
    handleRuntimeError: handleRuntimeError,
    clearRuntimeErrors: clearRuntimeErrors
};

Still not fully working

However, I'm unsure whether any of the other functions are being used or not.

showCompileError() not being called

When I force a compile error by writing some invalid JSX, it's babel-loader that responds, rather than the error overlay (showCompileError() is not called):

File change detected. Starting incremental webpack compilation...
Hash: 5646959a2371491fa5b2
Version: webpack 4.27.1
Time: 235ms
Built at: 15/06/2020 19:26:41
 6 assets
Entrypoint bundle = runtime.js vendor.js bundle.js bundle.176a2be936dff1b9b83b.hot-update.js
Entrypoint tns_modules/tns-core-modules/inspector_modules = runtime.js vendor.js tns_modules/tns-core-modules/inspector_modules.js
[./ sync ^\.\/app\.(css|scss|less|sass)$] . sync nonrecursive ^\.\/app\.(css|scss|less|sass)$ 174 bytes {bundle} [built]
[./ sync recursive (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$] . sync (?<!\bApp_Resources\b.*)(?<!\.\/\btests\b\/.*?)\.(xml|css|js|kt|(?<!\.d\.)ts|(?<!\b_[\w-]*\.)scss)$ 187 bytes {bundle} [built]
[./testComponents/AppContainer.tsx] 5.65 KiB {bundle} [built] [failed] [1 error]
    + 388 hidden modules

ERROR in ./testComponents/AppContainer.tsx
Module build failed (from ../node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/jamie/Documents/git/react-nativescript/sample/app/testComponents/AppContainer.tsx: Unexpected token (6:4)

  4 |     return (
  5 |         <stackLayout backgroundColor="green"
> 6 |     );
    |     ^
  7 | }
  8 | 
  9 | export default AppContainer;
    at Object._raise (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:746:17)
    at Object.raiseWithData (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:739:17)
    at Object.raise (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:733:17)
    at Object.unexpected (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:8807:16)
    at Object.jsxParseIdentifier (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4412:12)
    at Object.jsxParseNamespacedName (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4422:23)
    at Object.jsxParseAttribute (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4506:22)
    at Object.jsxParseOpeningElementAfterName (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4527:28)
    at Object.jsxParseOpeningElementAfterName (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:7067:18)
    at Object.jsxParseOpeningElementAt (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4520:17)
    at Object.jsxParseElementAt (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4552:33)
    at Object.jsxParseElement (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4626:17)
    at Object.parseExprAtom (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4633:19)
    at Object.parseExprSubscripts (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9656:23)
    at Object.parseMaybeUnary (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9636:21)
    at Object.parseMaybeUnary (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6877:20)
    at Object.parseExprOps (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9506:23)
    at Object.parseMaybeConditional (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9479:23)
    at Object.parseMaybeAssign (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9434:21)
    at /Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6808:39
    at Object.tryParse (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:8844:20)
    at Object.parseMaybeAssign (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6808:18)
    at Object.parseParenAndDistinguishExpression (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:10267:28)
    at Object.parseExprAtom (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:10007:21)
    at Object.parseExprAtom (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:4638:20)
    at Object.parseExprSubscripts (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9656:23)
    at Object.parseMaybeUnary (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9636:21)
    at Object.parseMaybeUnary (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6877:20)
    at Object.parseExprOps (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9506:23)
    at Object.parseMaybeConditional (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9479:23)
    at Object.parseMaybeAssign (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9434:21)
    at Object.parseMaybeAssign (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6822:20)
    at Object.parseExpression (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:9386:23)
    at Object.parseReturnStatement (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:11523:28)
    at Object.parseStatementContent (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:11204:21)
    at Object.parseStatementContent (/Users/jamie/Documents/git/react-nativescript/sample/node_modules/@babel/parser/lib/index.js:6575:18)
 @ ./app.ts 50:0-57 51:58-70
 @ multi ../node_modules/@pmmmwh/react-refresh-webpack-plugin/src/runtime/ReactRefreshEntry.js ../ErrorOverlayEntry.js ./app.ts

handleRuntimeError() not being called

When I force a runtime error (by setting backgroundColor to an invalid colour name), the app crashes without handleRuntimeError() being called:

Webpack compilation complete. Watching for file changes.
Webpack build done!

Successfully transferred bundle.cba957faf253c896408d.hot-update.js on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Successfully transferred cba957faf253c896408d.hot-update.json on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.
Refreshing application on device 57DE46EC-8E05-41EF-964C-AF0686B7586B...
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Checking for updates to the bundle with hmr hash cba957faf253c896408d.
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: The following modules were updated:
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR:          ↻ ./testComponents/AppContainer.tsx
CONSOLE INFO file: node_modules/nativescript-dev-webpack/hot.js:3:0: HMR: Successfully applied update with hmr hash cba957faf253c896408d. App is up to date.

CONSOLE LOG file: app/src/shared/Logger.ts:5:16: prepareUpdate() with type: stackLayout NSVElement:StackLayout(1)
CONSOLE ERROR file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:10801:0: The above error occurred in the <stackLayout> component:
in stackLayout (created by AppContainer)
in AppContainer
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
***** Fatal JavaScript exception - application has been terminated. *****
Native stack trace:
1   0x104e4660e NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
2   0x104e87548 NativeScript::FFICallback<NativeScript::ObjCMethodCallback>::ffiClosureCallback(ffi_cif*, void*, void**, void*)
3   0x105888222 ffi_closure_unix64_inner
4   0x105888c4a ffi_closure_unix64
5   0x7fff23da14b4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
6   0x7fff23da114e __CFRunLoopDoTimer
7   0x7fff23da07aa __CFRunLoopDoTimers
8   0x7fff23d9b3fe __CFRunLoopRun
9   0x7fff23d9a944 CFRunLoopRunSpecific
10  0x7fff38ba6c1a GSEventRunModal
11  0x7fff48c8b9ec UIApplicationMain
12  0x105888a8d ffi_call_unix64
13  0x10a0b9790
JavaScript stack trace:
Color(file: node_modules/@nativescript/core/color/color-common.js:28:0)
at Color(file: node_modules/@nativescript/core/color/color.ios.js:6:0)
at valueConverter(file: node_modules/@nativescript/core/ui/styling/style-properties.js:618:81)
at set(file: node_modules/@nativescript/core/ui/core/properties/properties.js:670:0)
at set(file: node_modules/@nativescript/core/ui/core/view/view-common.js:552:0)
at file: node_modules/set-value/index.js:46:0
at file: app/src/nativescript-vue-next/runtime/nodes.ts:247:12
at setValueForProperty(file: app/src/client/NativeScriptPropertyOperations.ts:148:34)
at updateDOMProperties(file: app/src/client/ReactNativeScriptComponent.ts:271:32)
at updateProperties(file: app/src/client/ReactNativeScriptComponent.ts:500:24)
at commitUpdate(file: app/src/client/HostConfig.ts:421:25)
at commitWork(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:11925:0)
at commitMutationEffects(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:14189:0)
at invokeGuardedCallbackImpl(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:10557:0)
at invokeGuardedCallback(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:10733:0)
at commitRootImpl(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:13922:0)
at commitRootImpl([native code])
at unstable_runWithPriority(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/node_modules/scheduler/cjs/scheduler.development.js:653:0)
at commitRoot(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:13794:0)
at finishSyncRender(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:13192:0)
at performSyncWorkOnRoot(file: app/Users/jamie/Documents/git/react-nativescript/react-nativescript/node_modules/react-reconciler/cjs/react-reconciler.development.js:13178:0)
at performSyncWork<…>
JavaScript error:
file: node_modules/@nativescript/core/color/color-common.js:28:0: JS ERROR Error: Invalid color: sdgfhj
***** Fatal JavaScript exception - application has been terminated. *****
Native stack trace:
1   0x104e4660e NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
2   0x104e985f4 -[TNSRuntime executeModule:referredBy:]
3   0x104739dc3 main
4   0x7fff51a231fd start
5   0x1
JavaScript stack trace:
UIApplicationMain(file: node_modules/@nativescript/core/application/application.ios.js:312:0)
at run(file: node_modules/@nativescript/core/application/application.ios.js:312:0)
at start(file: app/src/index.ts:136:8)
at file:///app/bundle.js:4542:68
at ./app.ts(file:///app/bundle.js:4631:34)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at file:///app/bundle.js:4756:37
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at checkDeferredModules(file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback(file: app/webpack/bootstrap:30:0)
at anonymous(file:///app/bundle.js:2:61)
at evaluate([native code])
at moduleEvaluation([native code])
at [native code]
at asyncFunctionResume([native code])
at [native code]
at promiseReactionJob([native code])
JavaScript error:
file: node_modules/@nativescript/core/application/application.ios.js:312:0: JS ERROR Error
***** Fatal JavaScript exception - application has been terminated. *****
Native stack trace:
1   0x104e4660e NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
2   0x104e95f76 -[TNSRuntimeInspector reportFatalError:]
3   0x10473c24b TNSInspectorUncaughtExceptionHandler
4   0x7fff23e3d36d __handleUncaughtException
5   0x7fff50ba8c05 _objc_terminate()
6   0x7fff4f9f6c87 std::__terminate(void (*)())
7   0x7fff4f9f940b __cxa_get_exception_ptr
8   0x7fff4f9f93d2 __cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*)
9   0x7fff50ba8ad6 _objc_exception_destructor(void*)
10  0x104e46b4f NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
11  0x104e985f4 -[TNSRuntime executeModule:referredBy:]
12  0x104739dc3 main
13  0x7fff51a231fd start
14  0x1
JavaScript stack trace:
JavaScript error:
JS ERROR Error: NativeScript encountered a fatal error: Error
at
UIApplicationMain(file: node_modules/@nativescript/core/application/application.ios.js:312:0)
at run(file: node_modules/@nativescript/core/application/application.ios.js:312:0)
at start(file: app/src/index.ts:136:8)
at file:///app/bundle.js:4542:68
at ./app.ts(file:///app/bundle.js:4631:34)
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at fn(file: app/webpack/bootstrap:120:0)
at file:///app/bundle.js:4756:37
at __webpack_require__(file: app/webpack/bootstrap:774:0)
at checkDeferredModules(file: app/webpack/bootstrap:43:0)
at webpackJsonpCallback(file: app/webpack/bootstrap:30:0)
at anonymous(file:///app/bundle.js:2:61)
at evaluate([native code])
at moduleEvaluation([native code])
at [native code]
at asyncFunctionResume([native code])
at [native code]
at promiseReactionJob([native code])
NativeScript caught signal 6.
Native Stack:
1   0x104e97251 sig_handler(int)
2   0x7fff51c005fd _sigtramp
3   0x7fff51af4f39 itoa64
4   0x7fff51af0b7c abort
5   0x7fff4f9f7858 abort_message
6   0x7fff4f9f6cad std::__terminate(void (*)())
7   0x7fff4f9f940b __cxa_get_exception_ptr
8   0x7fff4f9f93d2 __cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*)
9   0x7fff50ba8ad6 _objc_exception_destructor(void*)
10  0x104e46b4f NativeScript::reportFatalErrorBeforeShutdown(JSC::ExecState*, JSC::Exception*, bool)
11  0x104e985f4 -[TNSRuntime executeModule:referredBy:]
12  0x104739dc3 main
13  0x7fff51a231fd start
14  0x1
JS Stack:
Successfully synced application uk.co.birchlabs.rnssample on device 57DE46EC-8E05-41EF-964C-AF0686B7586B.

Unclear whether clearCompileErrors() would be called

I'm unsure what would lead to clearCompileErrors() being called. Presumably it's dependent on having initially handled a compile error.


Am I forcing these errors in the wrong way, or is there more to be tweaked?

@shirakaba
Copy link
Author

And a thought does occur: do I need to add a React Error Boundary before runtime and compile-time errors will be handled by the error overlay?

@pmmmwh
Copy link
Owner

pmmmwh commented Jun 15, 2020

Is there more to be tweaked?

Yes.

  • ErrorOverlayEntry don't have to export the functions, but have to wire them up to the actual error handlers.
  • For runtime errors it would probably be something like this.
  • For compile errors I'm not sure if it's do-able, especially when NativeScript uses the Node.js IPC API to signal compile status. A workaround is to subscribe to the done event with a custom plugin.
  • For clearCompileError, you will first need to get compile errors wired up then run it whenever a change is detected.
  • No, you don't need to add an Error Boundary.

It will be quite a bit of work because of the complexity of the build system especially when APIs are unavailable in the actual NS runtime 😢

@shirakaba
Copy link
Author

shirakaba commented Jun 15, 2020

Thanks for clarifying!

Supporting compile errors may be beyond the effort I’d be happy to go to, then. Involves several areas of Node and Webpack crossover that I’m totally unfamiliar with. Can come back to it all much later if there’s ever sufficient demand, of course.

At the moment, the app crashes upon any runtime error. If I did support handleRuntimeError(), would it prevent my app crashing? Or just improve the presentation of the stack trace?

If there’s no great benefit to implementing handleRuntimeError(), I’d be tempted to leave it all to the current command-line error logging, and just turn off the error overlay altogether.

@pmmmwh
Copy link
Owner

pmmmwh commented Dec 31, 2020

Hey! Sorry that I totally missed this issue.

Now that 0.4.x is in stable, did you manage to check if it works?

At the moment, the app crashes upon any runtime error. If I did support handleRuntimeError(), would it prevent my app crashing? Or just improve the presentation of the stack trace?

No - it is just for pure presentation. All logic related to the overlay is for presentation of errors.

@shirakaba
Copy link
Author

shirakaba commented Dec 31, 2020

@pmmmwh No worries, I've been having success with it since "@pmmmwh/react-refresh-webpack-plugin": "^0.4.0-beta.5",, and I've found that v0.4.3 is working nicely. I've not put any more effort into the stretch goals of error handling (the Node IPC stuff sounds pretty daunting, for one thing), but may come back to it eventually if there's ever demand (nobody has mentioned a thing, in practice 😅). Thanks for all your help on this 🙂

@pmmmwh
Copy link
Owner

pmmmwh commented Dec 31, 2020

Would you mind if I close this?

@shirakaba
Copy link
Author

@pmmmwh Sure thing, let's close it; I can open it (or another issue) in future if something actionable comes up.

@KurtGokhan
Copy link

It just seems that React components aren't applying those HMR updates.

@shirakaba I think I've found the culprit.

react-refresh depends on developer tools integration, which needs to be manually injected via injectIntoDevTools from react-reconciler. react-nativescript doesn't do that yet, so all the scheduleReactRefresh calls from the plugin essentially become no-ops.

You'll have to somehow add this code snippet somewhere (presumably within the start function?):

const { version } = require('./package.json');

reactReconcilerInst.injectIntoDevTools({
  bundleType: __DEV__ ? 1 : 0,
  rendererPackageName: 'react-nativescript',
  version,
});

For lost wanderers looking for an answer, this is what I have been missing and it finally worked for me.

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

No branches or pull requests

5 participants