From 681383d351f41a2c1b8882424a71c39dc42bea48 Mon Sep 17 00:00:00 2001 From: Vladimir Kutepov Date: Tue, 3 Oct 2017 21:03:08 +0300 Subject: [PATCH] Improve developer experience; update react-error-overlay --- package.json | 4 +- src/client.js | 16 ++--- tools/README.md | 5 +- tools/lib/webpackHotDevClient.js | 42 ++++++++++++ tools/start.js | 14 +--- yarn.lock | 111 +++++-------------------------- 6 files changed, 74 insertions(+), 118 deletions(-) create mode 100644 tools/lib/webpackHotDevClient.js diff --git a/package.json b/package.json index ff20dde87..7dd7e3bab 100644 --- a/package.json +++ b/package.json @@ -99,8 +99,8 @@ "prettier": "^1.7.3", "raw-loader": "^0.5.1", "react-deep-force-update": "^2.1.1", - "react-error-overlay": "^1.0.9", - "react-hot-loader": "^3.0.0-beta.7", + "react-dev-utils": "^4.1.0", + "react-error-overlay": "^2.0.2", "react-test-renderer": "^16.0.0", "rimraf": "^2.6.2", "stylelint": "^8.1.1", diff --git a/src/client.js b/src/client.js index 111a0d43c..4e49d6cb4 100644 --- a/src/client.js +++ b/src/client.js @@ -19,8 +19,6 @@ import history from './history'; import { updateMeta } from './DOMUtils'; import router from './router'; -/* eslint-disable global-require */ - // Global (context) variables that can be easily accessed from any React component // https://facebook.github.io/react/docs/context.html const context = { @@ -40,8 +38,8 @@ const context = { }; const container = document.getElementById('app'); -let appInstance; let currentLocation = history.location; +let appInstance; // Switch off the native scroll restoration behavior and handle it manually // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration @@ -63,6 +61,7 @@ async function onLocationChange(location, action) { } currentLocation = location; + const isInitialRender = !action; try { // Traverses the list of routes in the order they are defined until // it finds the first route that matches provided URL path string @@ -83,14 +82,15 @@ async function onLocationChange(location, action) { return; } - appInstance = ReactDOM[action ? 'render' : 'hydrate']( + const renderReactApp = isInitialRender ? ReactDOM.hydrate : ReactDOM.render; + appInstance = renderReactApp( {route.component}, container, () => { - if (!action) { + if (isInitialRender) { const elem = document.getElementById('css'); if (elem) elem.parentNode.removeChild(elem); - return; // Initial render complete + return; } document.title = route.title; @@ -139,7 +139,7 @@ async function onLocationChange(location, action) { console.error(error); // Do a full page reload if error occurs during client-side navigation - if (action && currentLocation.key === location.key) { + if (!isInitialRender && currentLocation.key === location.key) { window.location.reload(); } } @@ -153,7 +153,7 @@ onLocationChange(currentLocation); // Enable Hot Module Replacement (HMR) if (module.hot) { module.hot.accept('./router', () => { - if (appInstance) { + if (appInstance && appInstance.updater.isMounted(appInstance)) { // Force-update the whole tree, including components that refuse to update deepForceUpdate(appInstance); } diff --git a/tools/README.md b/tools/README.md index 93bc2cabc..4eb495701 100644 --- a/tools/README.md +++ b/tools/README.md @@ -6,9 +6,8 @@ * Copies static files to the output folder (`copy.js`) * Launches [Webpack](https://webpack.github.io/) compiler in a watch mode (via [webpack-middleware](https://github.com/kriasoft/webpack-middleware)) * Launches Node.js server from the compiled output folder (`runServer.js`) -* Launches [Browsersync](https://browsersync.io/), - [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement), and - [React Hot Loader](https://github.com/gaearon/react-hot-loader) +* Launches [Browsersync](https://browsersync.io/) and + [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement) ### `yarn run build` (`build.js`) diff --git a/tools/lib/webpackHotDevClient.js b/tools/lib/webpackHotDevClient.js new file mode 100644 index 000000000..dcd99d269 --- /dev/null +++ b/tools/lib/webpackHotDevClient.js @@ -0,0 +1,42 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-present Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +// eslint-disable-next-line import/no-unresolved, import/extensions +import hotClient from 'webpack-hot-middleware/client?name=client&reload=true'; +import launchEditorEndpoint from 'react-dev-utils/launchEditorEndpoint'; +import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages'; +import { + reportBuildError, + dismissBuildError, + startReportingRuntimeErrors, + stopReportingRuntimeErrors, +} from 'react-error-overlay'; + +hotClient.useCustomOverlay({ + showProblems(type, errors) { + const formatted = formatWebpackMessages({ + errors, + warnings: [], + }); + + reportBuildError(formatted.errors[0]); + }, + clear() { + dismissBuildError(); + }, +}); + +startReportingRuntimeErrors({ + launchEditorEndpoint, + filename: '/assets/client.js', +}); + +if (module.hot) { + module.hot.dispose(stopReportingRuntimeErrors); +} diff --git a/tools/start.js b/tools/start.js index d620e643e..d555f8e6c 100644 --- a/tools/start.js +++ b/tools/start.js @@ -13,7 +13,7 @@ import browserSync from 'browser-sync'; import webpack from 'webpack'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; -import createLaunchEditorMiddleware from 'react-error-overlay/middleware'; +import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware'; import webpackConfig from './webpack.config'; import run, { format } from './run'; import clean from './clean'; @@ -66,16 +66,12 @@ let server; async function start() { if (server) return server; server = express(); - server.use(createLaunchEditorMiddleware()); + server.use(errorOverlayMiddleware()); server.use(express.static(path.resolve(__dirname, '../public'))); // Configure client-side hot module replacement const clientConfig = webpackConfig.find(config => config.name === 'client'); - clientConfig.entry.client = [ - 'react-error-overlay', - 'react-hot-loader/patch', - 'webpack-hot-middleware/client?name=client&reload=true', - ] + clientConfig.entry.client = ['./tools/lib/webpackHotDevClient'] .concat(clientConfig.entry.client) .sort((a, b) => b.includes('polyfill') - a.includes('polyfill')); clientConfig.output.filename = clientConfig.output.filename.replace( @@ -89,10 +85,6 @@ async function start() { clientConfig.module.rules = clientConfig.module.rules.filter( x => x.loader !== 'null-loader', ); - const { options } = clientConfig.module.rules.find( - x => x.loader === 'babel-loader', - ); - options.plugins = ['react-hot-loader/babel'].concat(options.plugins || []); clientConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), diff --git a/yarn.lock b/yarn.lock index cf11b2397..1aeb48268 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1728,14 +1728,7 @@ babel-register@^6.26.0, babel-register@^6.9.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.10.0" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.25.0, babel-runtime@^6.26.0: +babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.25.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1760,7 +1753,7 @@ babel-template@7.0.0-beta.2: babylon "7.0.0-beta.25" lodash "^4.2.0" -babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.7.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -2993,10 +2986,6 @@ dom-serializer@0, dom-serializer@~0.1.0: domelementtype "~1.1.1" entities "~1.1.1" -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -3203,12 +3192,6 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -error-stack-parser@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292" - dependencies: - stackframe "^0.3.1" - es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.0.tgz#3b00385e85729932beffa9163bbea1234e932914" @@ -4130,13 +4113,6 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -global@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" - dependencies: - min-document "^2.19.0" - process "~0.5.1" - globals@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/globals/-/globals-10.1.0.tgz#4425a1881be0d336b4a823a82a7be725d5dd987c" @@ -5685,7 +5661,7 @@ lodash@^3.10.0, lodash@^3.10.1, lodash@^3.9.3: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.1, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.6.1: +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.1, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -5890,12 +5866,6 @@ mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - dependencies: - dom-walk "^0.1.0" - minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -7112,10 +7082,6 @@ process@^0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" @@ -7126,7 +7092,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4: +prop-types@^15.5.10: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -7301,20 +7267,15 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-deep-force-update@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.0.tgz#1c5f36ea96bcbf411605ec063f36c568ea4df86c" - react-deep-force-update@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909" -react-dev-utils@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-3.1.1.tgz#09ae7209a81384248db56547e718e65bd3b20eb5" +react-dev-utils@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.1.0.tgz#f6f436febd3f4eeb538490319b51c875c1e35bc5" dependencies: address "1.0.2" - anser "1.4.1" babel-code-frame "6.22.0" chalk "1.1.3" cross-spawn "5.1.0" @@ -7323,17 +7284,17 @@ react-dev-utils@^3.1.0: filesize "3.5.10" global-modules "1.0.0" gzip-size "3.0.0" - html-entities "1.2.1" inquirer "3.2.1" is-root "1.0.0" opn "5.1.0" + react-error-overlay "^2.0.2" recursive-readdir "2.2.1" shell-quote "1.6.1" sockjs-client "1.1.4" strip-ansi "3.0.1" text-table "0.2.0" -react-dom@^16.0.0: +"react-dom@^15 || ^16", react-dom@^16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0.tgz#9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58" dependencies: @@ -7342,34 +7303,19 @@ react-dom@^16.0.0: object-assign "^4.1.1" prop-types "^15.6.0" -react-error-overlay@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-1.0.10.tgz#da8cd1eafac41afdca2a33792b23694ef6c528f1" +react-error-overlay@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-2.0.2.tgz#967b091962b17f5aeb4a60b1311b1736e1b6533d" dependencies: anser "1.4.1" babel-code-frame "6.22.0" - babel-runtime "6.23.0" - react-dev-utils "^3.1.0" + babel-runtime "6.26.0" + html-entities "1.2.1" + react "^15 || ^16" + react-dom "^15 || ^16" settle-promise "1.0.0" source-map "0.5.6" -react-hot-loader@^3.0.0-beta.7: - version "3.0.0-beta.7" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.0.0-beta.7.tgz#d5847b8165d731c4d5b30d86d5d4716227a0fa83" - dependencies: - babel-template "^6.7.0" - global "^4.3.0" - react-deep-force-update "^2.0.1" - react-proxy "^3.0.0-alpha.0" - redbox-react "^1.3.6" - source-map "^0.4.4" - -react-proxy@^3.0.0-alpha.0: - version "3.0.0-alpha.1" - resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07" - dependencies: - lodash "^4.6.1" - react-test-renderer@^16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15" @@ -7377,7 +7323,7 @@ react-test-renderer@^16.0.0: fbjs "^0.8.16" object-assign "^4.1.1" -react@^16.0.0: +"react@^15 || ^16", react@^16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" dependencies: @@ -7495,15 +7441,6 @@ recursive-readdir@2.2.1: dependencies: minimatch "3.0.3" -redbox-react@^1.3.6: - version "1.5.0" - resolved "https://registry.yarnpkg.com/redbox-react/-/redbox-react-1.5.0.tgz#04dab11557d26651bf3562a67c22ace56c5d3967" - dependencies: - error-stack-parser "^1.3.6" - object-assign "^4.0.1" - prop-types "^15.5.4" - sourcemapped-stacktrace "^1.1.6" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -7546,10 +7483,6 @@ regenerate@^1.3.2, regenerate@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" -regenerator-runtime@^0.10.0: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - regenerator-runtime@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" @@ -8215,12 +8148,6 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -sourcemapped-stacktrace@^1.1.6: - version "1.1.7" - resolved "https://registry.yarnpkg.com/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.7.tgz#17e05374ff78b71a9d89ad3975a49f22725ba935" - dependencies: - source-map "0.5.6" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -8268,10 +8195,6 @@ stable@~0.1.3: version "0.1.6" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.6.tgz#910f5d2aed7b520c6e777499c1f32e139fdecb10" -stackframe@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" - staged-git-files@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35"