diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index f8b7c64c40..d3ab2c4e1e 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -424,6 +424,7 @@ module.exports = { async: false, checkSyntacticErrors: true, tsconfig: paths.appTsConfig, + tslint: require.resolve('tslint-config-react-app'), compilerOptions: { module: 'esnext', moduleResolution: 'node', diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 5165f1a984..1a72cee05a 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -544,6 +544,7 @@ module.exports = { async: false, checkSyntacticErrors: true, tsconfig: paths.appTsConfig, + tslint: require.resolve('tslint-config-react-app'), compilerOptions: { module: 'esnext', moduleResolution: 'node', diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 05d784169f..ef3e783bdc 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -66,6 +66,9 @@ "sass-loader": "7.1.0", "style-loader": "0.23.0", "terser-webpack-plugin": "1.1.0", + "tslint": "5.11.0", + "tslint-config-react-app": "^1.0.0", + "tslint-microsoft-contrib": "5.2.1", "url-loader": "1.1.1", "webpack": "4.19.1", "webpack-dev-server": "3.1.9", diff --git a/packages/react-scripts/template-typescript/src/index.tsx b/packages/react-scripts/template-typescript/src/index.tsx index 0c5e75da1c..d2da559ee4 100644 --- a/packages/react-scripts/template-typescript/src/index.tsx +++ b/packages/react-scripts/template-typescript/src/index.tsx @@ -9,4 +9,5 @@ ReactDOM.render(, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: http://bit.ly/CRA-PWA +// tslint:disable-next-line:no-floating-promises serviceWorker.unregister(); diff --git a/packages/react-scripts/template-typescript/src/serviceWorker.ts b/packages/react-scripts/template-typescript/src/serviceWorker.ts index c0b1310519..5bead095ad 100644 --- a/packages/react-scripts/template-typescript/src/serviceWorker.ts +++ b/packages/react-scripts/template-typescript/src/serviceWorker.ts @@ -39,105 +39,100 @@ export function register(config?: Config) { return; } - window.addEventListener('load', () => { + window.addEventListener('load', async () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + await checkValidServiceWorker(swUrl, config); // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit http://bit.ly/CRA-PWA' - ); - }); + await navigator.serviceWorker.ready; + + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config); + await registerValidSW(swUrl, config); } }); } } -function registerValidSW(swUrl: string, config?: Config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } +async function registerValidSW(swUrl: string, config?: Config) { + try { + const registration = await navigator.serviceWorker.register(swUrl); + + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (!installingWorker) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); } } - }; + } }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + }; + } catch (error) { + console.error('Error during service worker registration:', error); + } } -function checkValidServiceWorker(swUrl: string, config?: Config) { +async function checkValidServiceWorker(swUrl: string, config?: Config) { // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + try { + const response = await fetch(swUrl); + + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + await unregister(); + window.location.reload(); + } else { + // Service worker found. Proceed as normal. + await registerValidSW(swUrl, config); + } + } catch { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + } } -export function unregister() { +export async function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); + const registration = await navigator.serviceWorker.ready; + await registration.unregister(); } } diff --git a/packages/react-scripts/template-typescript/tslint.json b/packages/react-scripts/template-typescript/tslint.json new file mode 100644 index 0000000000..7493191548 --- /dev/null +++ b/packages/react-scripts/template-typescript/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "tslint-config-react-app" +} diff --git a/packages/react-scripts/template/src/serviceWorker.js b/packages/react-scripts/template/src/serviceWorker.js index 2283ff9ced..e43723e3c5 100644 --- a/packages/react-scripts/template/src/serviceWorker.js +++ b/packages/react-scripts/template/src/serviceWorker.js @@ -31,105 +31,100 @@ export function register(config) { return; } - window.addEventListener('load', () => { + window.addEventListener('load', async () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + await checkValidServiceWorker(swUrl, config); // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit http://bit.ly/CRA-PWA' - ); - }); + await navigator.serviceWorker.ready; + + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config); + await registerValidSW(swUrl, config); } }); } } -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' - ); +async function registerValidSW(swUrl, config) { + try { + const registration = await navigator.serviceWorker.register(swUrl); + + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (!installingWorker) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); } } - }; + } }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + }; + } catch (error) { + console.error('Error during service worker registration:', error); + } } -function checkValidServiceWorker(swUrl, config) { +async function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + try { + const response = await fetch(swUrl); + + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + await unregister(); + window.location.reload(); + } else { + // Service worker found. Proceed as normal. + await registerValidSW(swUrl, config); + } + } catch { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + } } -export function unregister() { +export async function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); + const registration = await navigator.serviceWorker.ready; + await registration.unregister(); } } diff --git a/packages/tslint-config-react-app/index.js b/packages/tslint-config-react-app/index.js new file mode 100644 index 0000000000..c12b806ecf --- /dev/null +++ b/packages/tslint-config-react-app/index.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +module.exports = { + defaultSeverity: 'warning', + rulesDirectory: 'tslint-microsoft-contrib', + rules: { + // https://palantir.github.io/tslint/rules/ + 'await-promise': true, + 'new-parens': true, + 'no-angle-bracket-type-assertion': true, + 'no-conditional-assignment': true, + 'no-debugger': true, + 'no-duplicate-super': true, + 'no-duplicate-switch-case': true, + 'no-duplicate-variable': true, + 'no-eval': true, + 'no-floating-promises': true, + 'no-for-in-array': true, + 'no-implicit-dependencies': [true, 'dev'], + 'no-invalid-template-strings': true, + 'no-invalid-this': true, + 'no-namespace': true, + 'no-sparse-arrays': true, + 'no-string-throw': true, + 'no-switch-case-fall-through': true, + 'no-unused-expression': [true, 'allow-fast-null-checks'], + // DEPRECATED. Recommended to use TS 'noUnusedLocals' for now + // "no-unused-variable": true, + 'triple-equals': true, + 'use-isnan': true, + + // https://github.com/Microsoft/tslint-microsoft-contrib + 'react-a11y-anchors': true, + 'react-a11y-aria-unsupported-elements': true, + 'react-a11y-event-has-role': true, + 'react-a11y-image-button-has-alt': true, + 'react-a11y-img-has-alt': true, + 'react-a11y-props': true, + 'react-a11y-proptypes': true, + 'react-a11y-role': true, + 'react-a11y-role-has-required-aria-props': true, + 'react-a11y-role-supports-aria-props': true, + }, +}; diff --git a/packages/tslint-config-react-app/package.json b/packages/tslint-config-react-app/package.json new file mode 100644 index 0000000000..4fb5b10aa7 --- /dev/null +++ b/packages/tslint-config-react-app/package.json @@ -0,0 +1,15 @@ +{ + "name": "tslint-config-react-app", + "version": "1.0.0", + "description": "TSLint configuration used by Create React App", + "repository": "facebook/create-react-app", + "license": "MIT", + "bugs": { + "url": "https://github.com/facebook/create-react-app/issues" + }, + "main": "index.js", + "peerDependencies": { + "tslint": "5.x", + "tslint-microsoft-contrib": "5.x" + } +}