diff --git a/.all-contributorsrc b/.all-contributorsrc index 6f82325f..14a8bf6b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,6 +1,6 @@ { - "projectName": "react-boilerplate", - "projectOwner": "react-boilerplate", + "projectName": "react-boilerplate-typescript", + "projectOwner": "react-boilerplate-typescript", "repoType": "github", "repoHost": "https://github.com", "files": [ @@ -10,10 +10,10 @@ "commit": true, "contributors": [ { - "login": "mxstbr", - "name": "Max Stoiber", - "avatar_url": "https://avatars0.githubusercontent.com/u/7525670?v=4", - "profile": "https://mxstbr.com", + "login": "Can-Sahin", + "name": "Can Sahin", + "avatar_url": "https://avatars2.githubusercontent.com/u/33245689?v=4", + "profile": "https://github.com/Can-Sahin", "contributions": [ "code", "doc", @@ -23,152 +23,16 @@ ] }, { - "login": "julienben", - "name": "Julien Benchetrit", - "avatar_url": "https://avatars2.githubusercontent.com/u/8948127?v=4", - "profile": "https://julien.engineering/", - "contributions": [ - "code", - "question", - "doc", - "review", - "maintenance" - ] - }, - { - "login": "gretzky", - "name": "Sara Federico", - "avatar_url": "https://avatars1.githubusercontent.com/u/15176096?v=4", - "profile": "http://sarafederi.co", - "contributions": [ - "code", - "review", - "question", - "doc", - "maintenance" - ] - }, - { - "login": "justingreenberg", - "name": "Justin Greenberg", - "avatar_url": "https://avatars1.githubusercontent.com/u/1539088?v=4", - "profile": "https://justingreenberg.com", + "login": "GrayStrider", + "name": "Gray Strider", + "avatar_url": "https://avatars0.githubusercontent.com/u/43771776?s=460&v=4", + "profile": "https://github.com/GrayStrider", "contributions": [ "code", + "tool", "review" ] }, - { - "login": "jwinn", - "name": "Jon Winn", - "avatar_url": "https://avatars3.githubusercontent.com/u/891726?v=4", - "profile": "https://github.com/jwinn", - "contributions": [ - "code", - "review" - ] - }, - { - "login": "Mensae", - "name": "Johan Meester", - "avatar_url": "https://avatars2.githubusercontent.com/u/474743?v=4", - "profile": "https://meester-johan.info/", - "contributions": [ - "code", - "test", - "doc" - ] - }, - { - "login": "Dattaya", - "name": "Yaroslav Kiliba", - "avatar_url": "https://avatars3.githubusercontent.com/u/387256?v=4", - "profile": "https://github.com/Dattaya", - "contributions": [ - "code" - ] - }, - { - "login": "gihrig", - "name": "Glen Ihrig", - "avatar_url": "https://avatars2.githubusercontent.com/u/1481063?v=4", - "profile": "https://github.com/gihrig", - "contributions": [ - "code" - ] - }, - { - "login": "somus", - "name": "Somasundaram Ayyappan", - "avatar_url": "https://avatars3.githubusercontent.com/u/1802828?v=4", - "profile": "https://github.com/somus", - "contributions": [ - "code" - ] - }, - { - "login": "oliverturner", - "name": "Oliver Turner", - "avatar_url": "https://avatars0.githubusercontent.com/u/21795?v=4", - "profile": "https://www.codedsignal.co.uk/", - "contributions": [ - "code" - ] - }, - { - "login": "samit4me", - "name": "Samuel Sharpe", - "avatar_url": "https://avatars3.githubusercontent.com/u/3248531?v=4", - "profile": "https://github.com/samit4me", - "contributions": [ - "code" - ] - }, - { - "login": "KarandikarMihir", - "name": "Mihir Karandikar", - "avatar_url": "https://avatars3.githubusercontent.com/u/17466938?v=4", - "profile": "https://karandikarmihir.github.io/", - "contributions": [ - "code" - ] - }, - { - "login": "v", - "name": "Vaibhav Verma", - "avatar_url": "https://avatars2.githubusercontent.com/u/627846?v=4", - "profile": "http://www.vverma.net", - "contributions": [ - "code" - ] - }, - { - "login": "sedubois", - "name": "Sébastien Dubois", - "avatar_url": "https://avatars1.githubusercontent.com/u/4217871?v=4", - "profile": "https://imagineclarity.com", - "contributions": [ - "code" - ] - }, - { - "login": "chaintng", - "name": "Chainarong Tangsurakit", - "avatar_url": "https://avatars2.githubusercontent.com/u/2979072?v=4", - "profile": "https://www.chaintng.com", - "contributions": [ - "code" - ] - }, - { - "login": "amilajack", - "name": "Amila Welihinda", - "avatar_url": "https://avatars1.githubusercontent.com/u/6374832?v=4", - "profile": "https://amilajack.com", - "contributions": [ - "code" - ] - }, { "login": "rajatkantinandi", "name": "Rajat Kanti Nandi", diff --git a/.eslintignore b/.eslintignore index c1220f92..4a7d2ad1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,6 @@ internals/scripts internals/generators internals/templates server/ -**/types.d.ts *.html node_modules coverage diff --git a/.eslintrc.js b/.eslintrc.js index 083bc82e..a6539c54 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,8 +7,19 @@ const prettierOptions = JSON.parse( module.exports = { parser: '@typescript-eslint/parser', - extends: ['airbnb-typescript', 'prettier', 'prettier/react'], - plugins: ['prettier', 'redux-saga', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'], + extends: [ + 'airbnb-typescript', + 'prettier', + 'prettier/react', + ], + plugins: [ + 'prettier', + 'redux-saga', + 'react', + 'react-hooks', + 'jsx-a11y', + '@typescript-eslint', + ], env: { jest: true, browser: true, @@ -25,11 +36,11 @@ module.exports = { }, rules: { 'jsx-no-lambda': 0, - 'semi': [2, 'always'], + semi: [2, 'always'], '@typescript-eslint/interface-name-prefix': 0, '@typescript-eslint/no-empty-interface': 0, 'object-shorthand': [0, 'never'], - 'quotes': [2, 'single'], + quotes: [2, 'single'], '@typescript-eslint/no-var-requires': 0, 'member-ordering': 0, 'object-literal-sort-keys': 0, @@ -57,7 +68,7 @@ module.exports = { 'import/no-dynamic-require': 0, 'import/no-extraneous-dependencies': 0, 'import/no-named-as-default': 0, - 'import/no-unresolved': 2, + 'import/no-unresolved': [2, { caseSensitive: false }], // ts already checks case sensitive imports 'import/no-webpack-loader-syntax': 0, 'import/prefer-default-export': 0, 'import/no-cycle': 1, @@ -116,10 +127,11 @@ module.exports = { webpack: { config: './internals/webpack/webpack.prod.babel.js', }, - 'typescript': { - 'alwaysTryTypes': true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` - 'directory': './tsconfig.json', + typescript: { + alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` + directory: './tsconfig.json', }, }, + 'import/ignore': ['types'], // Weirdly eslint cannot resolve exports in types folder (try removing this later) }, }; diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..1dab4ed4 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact = true diff --git a/README.md b/README.md index 50d24223..870d674c 100644 --- a/README.md +++ b/README.md @@ -1,183 +1,140 @@ -react boilerplate typescript banner - -
- -
Start your next react project in seconds
-
A highly scalable, offline-first foundation with the best DX and a focus on performance and best practices
- -
- -
- - - Dependency Status - - - - devDependency Status - - - - Build Status - - - - Test Coverage - - - Chat with us on Spectrum +react boilerplate typescript banner + +
+ +
Start your next react project in seconds
+ +
A highly scalable, offline-first foundation with the best DX and a focus on performance and best practices
+ +
+ +
+ + + + + +Dependency Status + -
-
- - - Backers - - - - Sponsors - - - Supported by Thinkmill - -
+ + + + +devDependency Status + + + + + + + +Build Status + + + + + + + +Test Coverage + + + + -
+Chat with us on Spectrum + +
-
- Created by Max Stoiber and maintained with ❤️ by an amazing team of developers.
---- +
-# Typescript + -This fork is the **fully-featured** typescript implementation of [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) with extra features and powers of `Typescript` + -Boilerplate version: `4.1` +Backers -#### Brief Overview + -- Detailed documentation: [**Typescript docs**](docs/general/typescript.md) + -- Typescript practices heavily depends on the guide -> react-typescript-guide + -- My projects built with this boilerplate `(if you are looking for advanced examples)`: - - [Example 1 (in-production)](https://github.com/International-Slackline-Association/Rankings-UI) `(boilerplate version: 3)` - - [Example 2 (in-production)](https://github.com/International-Slackline-Association/Web-Tools) `(boilerplate version: 4)` -- Want to deploy to AWS S3/CloudFront with a single script? -> [**S3 deployment script**](https://gist.github.com/Can-Sahin/d7de7e2ff5c1a39b82ced2d9bd7c60ae) +Sponsors ---- + -## Features + -
-
Quick scaffolding
-
Create components, containers, routes, selectors and sagas - and their tests - right from the CLI!
+Supported by Thinkmill -
Instant feedback
-
Enjoy the best DX (Developer eXperience) and code your app at the speed of thought! Your saved changes to the CSS and JS are reflected instantaneously without refreshing the page. Preserve application state even when you update something in the underlying code!
+
-
Predictable state management
-
Unidirectional data flow allows for change logging and time travel debugging.
+
-
Next generation JavaScript
-
Use template strings, object destructuring, arrow functions, JSX syntax and more.
+
-
Next generation CSS
-
Write composable CSS that's co-located with your components for complete modularity. Unique generated class names keep the specificity low while eliminating style clashes. Ship only the styles that are on the page for the best performance.
+
-
Industry-standard routing
-
It's natural to want to add pages (e.g. `/about`) to your application, and routing makes this possible.
+Created by Max Stoiber and maintained with ❤️ by an amazing team of developers. -
Industry-standard i18n internationalization support
-
Scalable apps need to support multiple languages, easily add and support multiple languages with `react-intl`.
+
-
Offline-first
-
The next frontier in performant web apps: availability without a network connection from the instant your users load the app.
+This is the **TYPESCRIPT** implementation of the original [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) with extra features and powers of `Typescript` -
Static code analysis
-
Focus on writing new features without worrying about formatting or code quality. With the right editor setup, your code will automatically be formatted and linted as you work.
+> ⚠️ Please refer to the [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) for the details and the documentation -
SEO
-
We support SEO (document head tags management) for search engines that support indexing of JavaScript content. (eg. Google)
- +--- + +Boilerplate version: `4.1` -But wait... there's more! +#### Brief overview of the typescript version -- _The best test setup:_ Automatically guarantee code quality and non-breaking - changes. (Seen a react app with 100% test coverage before?) -- _Native web app:_ Your app's new home? The home screen of your users' phones. -- _The fastest fonts:_ Say goodbye to vacant text. -- _Stay fast_: Profile your app's performance from the comfort of your command - line! -- _Catch problems:_ AppVeyor and TravisCI setups included by default, so your - tests get run automatically on Windows and Unix. +- Detailed documentation: [**Typescript docs**](docs/general/typescript.md) -There’s also a fantastic video on how to structure your React.js apps with scalability in mind. It provides rationale for the majority of boilerplate's design decisions. +* Typescript practices heavily depends on the guide -> react-typescript-guide -Keywords: React.js, Redux, Hot Reloading, ESNext, Babel, react-router, Offline First, ServiceWorker, `styled-components`, redux-saga, FontFaceObserver +- Example projects built with this boilerplate `(if you are looking for advanced examples)`: -## Quick start + - [Example 1 (in-production)](https://github.com/International-Slackline-Association/Rankings-UI) `(boilerplate version: 3)` -1. Make sure that you have Node.js v8.15.1 and npm v5 or above installed. -2. Clone this repo using `git clone --depth=1 https://github.com/can-sahin/react-boilerplate-typescript.git ` -3. Move to the appropriate directory: `cd `.
-4. Run `npm run setup` in order to install dependencies and clean the git repo.
- _At this point you can run `npm start` to see the example app at `http://localhost:3000`._ -5. Run `npm run clean` to delete the example app. + - [Example 2 (in-production)](https://github.com/International-Slackline-Association/Web-Tools) `(boilerplate version: 4)` -Now you're ready to rumble! +- Want to deploy to AWS S3/CloudFront with a single script? -> [**S3 deployment script**](https://gist.github.com/Can-Sahin/d7de7e2ff5c1a39b82ced2d9bd7c60ae) -> Please note that this boilerplate is **production-ready and not meant for beginners**! If you're just starting out with react or redux, please refer to https://github.com/petehunt/react-howto instead. If you want a solid, battle-tested base to build your next product upon and have some experience with react, this is the perfect start for you. +--- -## Documentation +## Getting Started & Features & Documentations and More... -- [**The Hitchhiker's Guide to `react-boilerplate`**](docs/general/introduction.md): An introduction for newcomers to this boilerplate. -- [Overview](docs/general): A short overview of the included tools -- [**Commands**](docs/general/commands.md): Getting the most out of this boilerplate -- [Testing](docs/testing): How to work with the built-in test harness -- [Styling](docs/css): How to work with the CSS tooling -- [Your app](docs/js): Supercharging your app with Routing, Redux, simple - asynchronicity helpers, etc. -- [**Troubleshooting**](docs/general/gotchas.md): Solutions to common problems faced by developers. +⚠️ +Refer to the [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) + +--- + +Keywords: React Boilerplate, Typescript, React.js, Redux, Hot Reloading, ESNext, Babel, react-router, Offline First, ServiceWorker, `styled-components`, redux-saga, FontFaceObserver ## Contributors -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): +> Only typescript contributors are displayed here. - - - - - - - - - - - - - - - - - - - - + +

Max Stoiber

💻 📖 🤔 👀 ⚠️

Julien Benchetrit

💻 💬 📖 👀 🚧

Sara Federico

💻 👀 💬 📖 🚧

Justin Greenberg

💻 👀

Jon Winn

💻 👀

Johan Meester

💻 ⚠️ 📖

Yaroslav Kiliba

💻

Glen Ihrig

💻

Somasundaram Ayyappan

💻

Oliver Turner

💻

Samuel Sharpe

💻

Mihir Karandikar

💻

Vaibhav Verma

💻

Sébastien Dubois

💻

Chainarong Tangsurakit

💻

Amila Welihinda

💻

Can Sahin

💻 📖 🤔 👀 ⚠️

Gray Strider

💻 🔧 👀

Rajat Kanti Nandi

🔧
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! @@ -186,31 +143,50 @@ This project follows the [all-contributors](https://github.com/all-contributors/ This project would not be possible without the support of these amazing folks. [**Become a sponsor**](https://opencollective.com/react-boilerplate) to get your company in front of thousands of engaged react developers and help us out! - - - - - - - - - - + + + + + + + + + + + + + + + + + + + --- - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ## License This project is licensed under the MIT license, Copyright (c) 2019 Maximilian + Stoiber. For more information see `LICENSE.md`. diff --git a/app/app.tsx b/app/app.tsx index f84d894a..6e587f56 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -27,6 +27,8 @@ import LanguageProvider from 'containers/LanguageProvider'; import '!file-loader?name=[name].[ext]!./images/favicon.ico'; import 'file-loader?name=.htaccess!./.htaccess'; +import { HelmetProvider } from 'react-helmet-async'; + import { translationMessages } from 'i18n'; import configureStore from './configureStore'; @@ -46,27 +48,31 @@ const initialState = {}; const store = configureStore(initialState, history); const MOUNT_NODE = document.getElementById('app') as HTMLElement; -const render = (messages: any, Component = App) => { - ReactDOM.render( - - - - - - - , - MOUNT_NODE, - ); +const ConnectedApp = (props: { messages: any }) => ( + + + + + + + + + +); +const render = (messages: any) => { + ReactDOM.render(, MOUNT_NODE); }; if (module.hot) { - module.hot.accept(['./i18n', './containers/App'], () => { + // Hot reloadable translation json files + // modules.hot.accept does not accept dynamic dependencies, + // have to be constants at compile-time + module.hot.accept(['./i18n'], () => { ReactDOM.unmountComponentAtNode(MOUNT_NODE); - // eslint-disable-next-line - const App = require('./containers/App').default; // https://github.com/webpack/webpack-dev-server/issues/100 - render(translationMessages, App); + render(translationMessages); }); } + // Chunked polyfill for browsers without Intl support if (!(window as any).Intl) { new Promise(resolve => { diff --git a/app/components/Header/tests/index.test.tsx b/app/components/Header/tests/index.test.tsx index 4d4349b5..2426255a 100755 --- a/app/components/Header/tests/index.test.tsx +++ b/app/components/Header/tests/index.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { IntlProvider } from 'react-intl'; -import { ConnectedRouter } from 'connected-react-router/immutable'; +import { ConnectedRouter } from 'connected-react-router'; import { createMemoryHistory } from 'history'; import Header from '../index'; diff --git a/app/configureStore.ts b/app/configureStore.ts index 80983651..18ad2db1 100644 --- a/app/configureStore.ts +++ b/app/configureStore.ts @@ -2,13 +2,15 @@ * Create the store with dynamic reducers */ -import { applyMiddleware, createStore } from 'redux'; +import { applyMiddleware, createStore, compose } from 'redux'; import { routerMiddleware } from 'connected-react-router'; +import { createInjectorsEnhancer, forceReducerReload } from 'redux-injectors'; import createSagaMiddleware from 'redux-saga'; -import { InjectedStore, ApplicationRootState } from 'types'; import { History } from 'history'; import { composeWithDevTools } from 'redux-devtools-extension'; + import createReducer from './reducers'; +import { InjectedStore, ApplicationRootState } from 'types'; export default function configureStore( initialState: ApplicationRootState | {} = {}, @@ -16,28 +18,36 @@ export default function configureStore( ) { const reduxSagaMonitorOptions = {}; const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions); + const { run: runSaga } = sagaMiddleware; + // Create the store with two middlewares + // 1. sagaMiddleware: Makes redux-sagas work + // 2. routerMiddleware: Syncs the location/URL path to the state const middlewares = [sagaMiddleware, routerMiddleware(history)]; - let enhancer = applyMiddleware(...middlewares); + const enhancers = [ + applyMiddleware(...middlewares), + createInjectorsEnhancer({ + createReducer, + runSaga, + }), + ]; + let enhancer; // If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them /* istanbul ignore next */ if (process.env.NODE_ENV !== 'production' && typeof window === 'object') { - enhancer = composeWithDevTools(enhancer); + enhancer = composeWithDevTools(...enhancers); + // NOTE: Uncomment the code below to restore support for Redux Saga + // Dev Tools once it supports redux-saga version 1.x.x + // if (window.__SAGA_MONITOR_EXTENSION__) + // reduxSagaMonitorOptions = { + // sagaMonitor: window.__SAGA_MONITOR_EXTENSION__, + // }; + } else { + enhancer = compose(...enhancers); } - // NOTE: Uncomment the code below to restore support for Redux Saga - // Dev Tools once it supports redux-saga version 1.x.x - // if (window.__SAGA_MONITOR_EXTENSION__) - // reduxSagaMonitorOptions = { - // sagaMonitor: window.__SAGA_MONITOR_EXTENSION__, - // }; - - // Create the store with two middlewares - // 1. sagaMiddleware: Makes redux-sagas work - // 2. routerMiddleware: Syncs the location/URL path to the state - const store = createStore( createReducer(), initialState, @@ -53,7 +63,7 @@ export default function configureStore( /* istanbul ignore next */ if (module.hot) { module.hot.accept('./reducers', () => { - store.replaceReducer(createReducer(store.injectedReducers)); + forceReducerReload(store); }); } diff --git a/app/containers/App/index.tsx b/app/containers/App/index.tsx index f59d4df3..31c95038 100644 --- a/app/containers/App/index.tsx +++ b/app/containers/App/index.tsx @@ -7,9 +7,10 @@ */ import * as React from 'react'; -import { Helmet } from 'react-helmet'; +import { Helmet } from 'react-helmet-async'; import styled from 'styles/styled-components'; import { Switch, Route } from 'react-router-dom'; +import { hot } from 'react-hot-loader/root'; import HomePage from 'containers/HomePage/Loadable'; import FeaturePage from 'containers/FeaturePage/Loadable'; @@ -28,7 +29,7 @@ const AppWrapper = styled.div` flex-direction: column; `; -export default function App() { +function App() { return ( - +