├── .circleci └── config.yml ├── .gitignore ├── Dockerfile ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ ├── fileTransform.js │ └── typescriptTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── docs └── 20180505210245.png ├── nginx-site.conf ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── images │ └── tinylog.png ├── index.html └── manifest.json ├── scripts ├── build.js ├── start.js └── test.js ├── src └── app │ ├── api │ ├── assets.ts │ ├── auth.ts │ ├── axios.ts │ ├── commonData.ts │ ├── host.ts │ ├── pages.ts │ └── realtime.ts │ ├── components │ ├── .gitkeep │ ├── ContentHeader │ │ ├── index.tsx │ │ └── style.css │ ├── Detail │ │ ├── index.tsx │ │ └── style.css │ ├── Echart │ │ ├── Bar │ │ │ └── index.tsx │ │ ├── ChinaMap │ │ │ └── index.tsx │ │ ├── DoughnutPie │ │ │ └── index.tsx │ │ ├── PvUvLine │ │ │ └── index.tsx │ │ └── SmoothedLine │ │ │ └── index.tsx │ ├── Footer │ │ ├── index.tsx │ │ └── style.css │ └── Title │ │ ├── index.tsx │ │ └── style.css │ ├── config │ └── path.ts │ ├── constants │ └── .gitkeep │ ├── containers │ ├── App │ │ └── index.tsx │ ├── Auth │ │ ├── signIn.tsx │ │ ├── signUp.tsx │ │ └── style.css │ ├── Container │ │ └── index.tsx │ ├── Modules │ │ ├── AssetsMsg │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── BaseMsg │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── Error │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── Home │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── PageMsg │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── RealTime │ │ │ ├── index.css │ │ │ └── overView.tsx │ │ ├── Referrer │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── System │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── User │ │ │ ├── index.css │ │ │ └── index.tsx │ │ └── WebPerformance │ │ │ ├── index.css │ │ │ └── index.tsx │ ├── Root │ │ └── index.tsx │ ├── View.tsx │ ├── View2.tsx │ └── Website │ │ ├── index.css │ │ └── index.tsx │ ├── index.css │ ├── index.tsx │ ├── interfaces │ └── index.ts │ ├── layout │ ├── Header │ │ ├── index.tsx │ │ └── style.css │ └── Sider │ │ └── index.tsx │ ├── macarons.js │ ├── models │ └── index.ts │ ├── registerServiceWorker.ts │ ├── reset.css │ └── stores │ ├── AssetsStore.ts │ ├── AuthStore.ts │ ├── CommonDataStore.ts │ ├── HostStore.ts │ ├── OverViewStore.ts │ ├── PageStore.ts │ ├── RealTimeStore.ts │ ├── RouterStore.ts │ ├── TimerStore.ts │ ├── TokenStore.ts │ └── index.ts ├── tsconfig.json ├── tsconfig.test.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | image_config: &image_config 2 | 3 | IMAGE_NAME: tinylog-ui 4 | 5 | IMAGE_TAG: latest 6 | 7 | version: 2 8 | jobs: 9 | 10 | build: 11 | machine: true 12 | 13 | environment: 14 | <<: *image_config 15 | 16 | steps: 17 | - checkout 18 | 19 | - run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 20 | 21 | - run: docker build -t $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_TAG . 22 | 23 | - run: docker push $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_TAG && sleep 10 24 | 25 | - store_artifacts: 26 | path: Dockerfile 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:stretch 2 | MAINTAINER Ruiming Zhuang 3 | 4 | ENV NGINX_VERSION 1.12.1-1~stretch 5 | ENV NJS_VERSION 1.12.1.0.1.10-1~stretch 6 | 7 | RUN apt-get update \ 8 | && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 \ 9 | && \ 10 | NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \ 11 | found=''; \ 12 | for server in \ 13 | ha.pool.sks-keyservers.net \ 14 | hkp://keyserver.ubuntu.com:80 \ 15 | hkp://p80.pool.sks-keyservers.net:80 \ 16 | pgp.mit.edu \ 17 | ; do \ 18 | echo "Fetching GPG key $NGINX_GPGKEY from $server"; \ 19 | apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \ 20 | done; \ 21 | test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \ 22 | apt-get remove --purge -y gnupg1 && apt-get -y --purge autoremove && rm -rf /var/lib/apt/lists/* \ 23 | && echo "deb http://nginx.org/packages/debian/ stretch nginx" >> /etc/apt/sources.list \ 24 | && apt-get update \ 25 | && apt-get install --no-install-recommends --no-install-suggests -y \ 26 | nginx=${NGINX_VERSION} \ 27 | nginx-module-xslt=${NGINX_VERSION} \ 28 | nginx-module-geoip=${NGINX_VERSION} \ 29 | nginx-module-image-filter=${NGINX_VERSION} \ 30 | nginx-module-njs=${NJS_VERSION} \ 31 | gettext-base \ 32 | && rm -rf /var/lib/apt/lists/* 33 | 34 | # forward request and error logs to docker log collector 35 | RUN ln -sf /dev/stdout /var/log/nginx/access.log \ 36 | && ln -sf /dev/stderr /var/log/nginx/error.log 37 | 38 | COPY package.json /tmp/package.json 39 | COPY package-lock.json /tmp/package-lock.json 40 | RUN cd /tmp && NPM_CONFIG_LOGLEVEL=warn yarn install 41 | COPY . /tmp 42 | RUN cd /tmp && yarn run build 43 | RUN rm -rf /usr/share/nginx/html && mv /tmp/build /usr/share/nginx/html 44 | COPY nginx-site.conf /etc/nginx/conf.d/default.conf 45 | 46 | EXPOSE 80 47 | CMD nginx -g 'daemon off;' 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React结合TypeScript和Mobx初体验 2 | 3 | ![banner](./docs/20180505210245.png) 4 | 5 | ## 为什么要使用TypeScript 6 | 7 | ### 侦测错误 8 | 9 | 通过静态类型检测可以尽早检测出程序中隐藏的的逻辑错误,对于JavaScript动态的弱类型语言,虽然灵活性高,但是对于初学者来说,如果不熟悉JavaScript内部的语言机制,很容易造成隐藏的事故。但是通过TypeScript的静态类型检测可以规避这些问题,因为其能够约束变量产生的类型。结合IDE编辑器可以推导变量对应的类型以及内部的结构,提高代码的健壮性和可维护性。 10 | 11 | ### 抽象 12 | 13 | 类型系统能够强化规范编程,TypeScript提供定义接口。在开发大型复杂的应用软件时十分重要,一个系统模块可以抽象的看做一个TypeScript定义的接口。让设计脱离实现,最终体现出一种 IDL(接口定义语言,Interface Define Language),让程序设计回归本质。 14 | 15 | ### 文档 16 | 17 | TypeScript可以自动根据类型标注生成文档,对于简单的功能实现都不需要编写注释。 18 | 19 | ## 为什么要使用Mobx 20 | 21 | ### MobX 和 Redux 的比较 22 | 23 | 先要明白 mobx 和 redux 的定位是不同的。redux 管理的是 (STORE -> VIEW -> ACTION) 的整个闭环,而 mobx 只关心 STORE -> VIEW 的部分。 24 | 25 | Redux优缺点: 26 | 27 | - 数据流流动很自然,因为任何 dispatch 都会触发广播,依据对象引用是否变化来控制更新粒度。 28 | 29 | - 通过充分利用时间回溯的特征,可以增强业务的可预测性与错误定位能力。 30 | 31 | - 时间回溯代价高,因为每次都要更新引用,除非增加代码复杂度,或使用 immutable。 32 | 33 | - 时间回溯的另一个代价是 action 与 reducer 完全脱节,原因是可回溯必然不能保证引用关系。 34 | 35 | - 引入中间件,解决异步带来的副作用,业务逻辑或多或少参杂着 magic。 36 | 37 | - 灵活利用中间件,可以通过约定完成许多复杂的工作。 38 | 39 | - 对 typescript 支持困难。 40 | 41 | Mobx优缺点: 42 | 43 | - 数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新,但避免了粒度控制烦恼。 44 | 45 | - 没有时间回溯能力,因为数据只有一份引用。自始至终一份引用,不需要 immutable,也没有复制对象的额外开销。 46 | 47 | - 数据流动由函数调用一气呵成,便于调试。 48 | 49 | - 业务开发不是脑力活,而是体力活,少一些 magic,多一些效率。 50 | 51 | - 由于没有 magic,所以没有中间件机制,没法通过 magic 加快工作效率(这里 magic 是指 action 分发到 reducer 的过程)。 52 | 53 | - 完美支持 typescript。 54 | 55 | SO: 前端数据流不太复杂的情况,使用 Mobx,因为更加清晰,也便于维护;如果前端数据流极度复杂,建议谨慎使用 Redux,通过中间件减缓巨大业务复杂度 56 | 57 | ## 使用Create-React-App来建立TypeScript的环境 58 | 59 | ```bash 60 | npm i -g create-react-app 61 | create-react-app tinylog-ui --scripts-version=react-scripts-ts 62 | cd tinylog-ui/ 63 | npm start 64 | npm run eject 65 | ``` 66 | 67 | TPS: 最后一个命令使用eject将所有内建的配置暴露出来 68 | 69 | 通过create-react-app可以很方便地对整个项目完成环境初始化,如果愿意折腾TypeScript和webpack的环境可以试试,这里忽略webpack和TypeScript的环境搭建过程,而是使用create-react-app来实现环境搭建。 70 | 71 | ## 加入React-Router 72 | 73 | 单页应用怎么可以没有前端路由呢,所以我们要加入React-Rotuer, 这里使用的React-Router的版本是v4.2.0 74 | 75 | ### 路由配置使用姿势 76 | 77 | 对于React-Router,这里使用到的模块有Router, Route, Switch 78 | 79 | >React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。 80 | 81 | 代码如下: 82 | 83 | ```tsx 84 | import * as React from 'react'; 85 | import * as ReactDOM from 'react-dom'; 86 | import { Router, Route, Switch } from 'react-router'; 87 | import { createBrowserHistory } from 'history'; 88 | import registerServiceWorker from './registerServiceWorker'; 89 | import { Root } from './containers/Root'; 90 | import './index.css'; 91 | import Container from './containers/Container'; 92 | import SignIn from './containers/Auth/signIn'; 93 | import SignUp from './containers/Auth/signUp'; 94 | 95 | const history = createBrowserHistory(); 96 | 97 | ReactDOM.render( 98 | 99 | 100 | 101 | 105 | 109 | 113 | 114 | 115 | , 116 | document.getElementById('root') as HTMLElement 117 | ); 118 | registerServiceWorker(); 119 | 120 | ``` 121 | ### 页面的编写 122 | 123 | 这里描述一写Container这个组件的编写 124 | 125 | ```tsx 126 | import * as React from 'react'; 127 | import Header from '../../layout/Header'; 128 | import { IAuth } from '../../interfaces'; 129 | import { Route, Switch } from 'react-router'; 130 | import App from '../App'; 131 | import Website from '../Website'; 132 | 133 | // 这部分是坑点,一开始不知道配置,后发现react-rotuer的4.0版本下需要配置prop的接口 134 | interface Container extends RouteComponentProps<{}> { 135 | } 136 | 137 | class Container extends React.Component { 138 | render () { 139 | return ( 140 |
141 |
142 | 143 | 144 | 145 | 146 |
147 | ) 148 | } 149 | } 150 | 151 | export default Container; 152 | ``` 153 | 154 | 这样,当我们访问url为'/'的时候,默认会进入Container,其中Container里面是一层子页面,会匹配url,如果url为'/website', 则进入Website页面,若为'/',则进入App页面。 155 | 156 | 具体关于React-Router的使用请阅读[React-Router文档](https://reacttraining.com/react-router/) 157 | 158 | ## 加入Mobx 159 | 160 | ```bash 161 | npm i mobx react-mobx mobx-react-router -S 162 | ``` 163 | 164 | ### 重新修改index.tsx的入口配置 165 | 166 | ```tsx 167 | import * as React from 'react'; 168 | import * as ReactDOM from 'react-dom'; 169 | import { Router, Route, Switch } from 'react-router'; 170 | import { createBrowserHistory } from 'history'; 171 | import { useStrict } from 'mobx'; 172 | import { Provider } from 'mobx-react'; 173 | import { RouterStore, syncHistoryWithStore } from 'mobx-react-router'; 174 | // 定义需要使用到的store来进行数据状态的管理 175 | import { 176 | TokenStore, 177 | AuthStore, 178 | HostStore, 179 | OverViewStore, 180 | AssetsStore, 181 | CommonDataStore, 182 | PageStore, 183 | RealTimeStore 184 | } from './stores'; 185 | import registerServiceWorker from './registerServiceWorker'; 186 | import { Root } from './containers/Root'; 187 | import './index.css'; 188 | import Container from './containers/Container'; 189 | import SignIn from './containers/Auth/signIn'; 190 | import SignUp from './containers/Auth/signUp'; 191 | // 引入Echarts 192 | import './macarons'; 193 | import 'echarts/map/js/world'; 194 | 195 | // 开启mobx的严格模式,规范数据修改操作只能在action中进行 196 | useStrict(true); 197 | 198 | const browserHistory = createBrowserHistory(); 199 | const routerStore = new RouterStore(); 200 | // 同步路由与mobx的数据状态 201 | const history = syncHistoryWithStore(browserHistory, routerStore); 202 | const rootStore = { 203 | token: new TokenStore(), 204 | auth: new AuthStore(), 205 | host: new HostStore(), 206 | overview: new OverViewStore(), 207 | assets: new AssetsStore(), 208 | commmon: new CommonDataStore(), 209 | page: new PageStore(), 210 | realtime: new RealTimeStore(), 211 | router: routerStore 212 | }; 213 | 214 | ReactDOM.render( 215 | 216 | 217 | 218 | 219 | 223 | 227 | 231 | 232 | 233 | 234 | , 235 | document.getElementById('root') as HTMLElement 236 | ); 237 | registerServiceWorker(); 238 | ``` 239 | 240 | ### Container容器的修改 241 | 242 | ```tsx 243 | import * as React from 'react'; 244 | import Header from '../../layout/Header'; 245 | import { IAuth } from '../../interfaces'; 246 | import { Route, Switch } from 'react-router'; 247 | // 使用inject和observer来进行数据监听和数据依赖声明 248 | import { inject, observer } from 'mobx-react'; 249 | import App from '../App'; 250 | import Website from '../Website'; 251 | 252 | interface Container extends IAuth { 253 | } 254 | 255 | @inject('router', 'auth') 256 | @observer 257 | class Container extends React.Component { 258 | render () { 259 | return ( 260 |
261 |
262 | 263 | 264 | 265 | 266 |
267 | ) 268 | } 269 | } 270 | 271 | export default Container; 272 | ``` 273 | 274 | > @observable 可以在实例字段和属性 getter 上使用。 对于对象的哪部分需要成为可观察的,@observable 提供了细粒度的控制。 275 | 276 | > @inject 相当于Provider 的高阶组件。可以用来从 React 的context中挑选 store 作为 prop 传递给目标组件 277 | 278 | ### 组件的接口定义 279 | 280 | ```ts 281 | import { RouteComponentProps } from 'react-router'; 282 | import { 283 | RouterStore, 284 | AuthStore 285 | } from '../stores'; 286 | 287 | export interface IBase extends RouteComponentProps<{}> { 288 | router: RouterStore; 289 | } 290 | 291 | export interface IAuth extends IBase { 292 | auth: AuthStore; 293 | } 294 | 295 | ``` 296 | 297 | ### Store的配置 298 | 299 | 先看一下RouterStore: 300 | 301 | ```ts 302 | import { History } from 'history'; 303 | import { RouterStore as BaseRouterStore, syncHistoryWithStore } from 'mobx-react-router'; 304 | 305 | // 路由状态同步 306 | class RouterStore extends BaseRouterStore { 307 | public history; 308 | constructor(history?: History) { 309 | super(); 310 | if (history) { 311 | this.history = syncHistoryWithStore(history, this); 312 | } 313 | } 314 | } 315 | 316 | export default RouterStore; 317 | ``` 318 | 319 | 然后是AuthStore: 320 | 321 | ```ts 322 | import { ISignIn, ISignUp } from './../interfaces/index'; 323 | import { observable, action } from 'mobx'; 324 | import api from '../api/auth'; 325 | import { IUser } from '../models'; 326 | 327 | // 登录注册状态 328 | class AuthStore { 329 | @observable token; 330 | @observable id; 331 | @observable email; 332 | constructor () { 333 | this.id = ''; 334 | this.token = ''; 335 | this.email = ''; 336 | } 337 | setLocalStorage ({ id, token, email }: IUser) { 338 | localStorage.setItem('id', id); 339 | localStorage.setItem('token', token); 340 | localStorage.setItem('email', email); 341 | } 342 | clearStorage () { 343 | localStorage.clear(); 344 | } 345 | @action async signIn (data: ISignIn) { 346 | try { 347 | const { data: res } = await api.signIn(data); 348 | this.id = res.data.id; 349 | this.token = res.data.token; 350 | this.email = res.data.email; 351 | this.setLocalStorage({ 352 | id: this.id, 353 | token: this.token, 354 | email: this.email 355 | }); 356 | return res; 357 | } catch (error) { 358 | return error; 359 | } 360 | } 361 | 362 | @action async signUp (data: ISignUp) { 363 | try { 364 | const { data: res } = await api.signUp(data); 365 | this.id = res.data.id; 366 | this.token = res.data.token; 367 | this.email = res.data.email; 368 | this.setLocalStorage({ 369 | id: this.id, 370 | token: this.token, 371 | email: this.email 372 | }); 373 | return res; 374 | } catch (error) { 375 | return error; 376 | } 377 | } 378 | 379 | @action signOut () { 380 | this.id = ''; 381 | this.token = ''; 382 | this.email = ''; 383 | this.clearStorage() 384 | } 385 | } 386 | 387 | export default AuthStore; 388 | ``` 389 | 390 | Auth是用于网站的登录注册事件以及对应的Token的数据状态保存,登录注册事件的接口请求等操作。 391 | 392 | 393 | 具体的有关Mobx的用法请阅读[Mobx文档](https://cn.mobx.js.org/) 394 | 395 | ## 目录结构 396 | 397 | ``` 398 | app 399 | ├── api 后端提供的接口数据请求 400 | ├── components 编写的可复用组件 401 | ├── config 侧边栏以及导航栏配置 402 | ├── constants 常量编写 403 | ├── interfaces 接口编写 404 | ├── layout 布局外框 405 | ├── stores mobx的数据状态管理 406 | ├── index.css 全局样式 407 | ├── index.tsx 页面入口 408 | ├── reset.css 浏览器重置样式 409 | 410 | ``` 411 | 412 | 本项目使用了Ant-Design来作为依赖的组件库,具体怎么使用以及配置请参考[Ant-Design](https://ant.design/docs/react/use-in-typescript-cn) 413 | 414 | 到这里其实以及完成对React下TypeScript结合React-Router和Mobx的配置。具体的业务模块如何编写有兴趣可以参阅项目[tinylog-ui](https://github.com/tinylog/tinylog-ui) 415 | 416 | 417 | 个人表达能力有限,无法描述得太清晰,请见谅! -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce( 82 | (env, key) => { 83 | env[key] = JSON.stringify(raw[key]); 84 | return env; 85 | }, 86 | {} 87 | ), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/typescriptTransform.js: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | 'use strict'; 4 | 5 | const tsJestPreprocessor = require('ts-jest/preprocessor'); 6 | 7 | module.exports = tsJestPreprocessor; 8 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right