├── src ├── features │ ├── app │ │ ├── constants │ │ │ ├── app.conf.js │ │ │ ├── api.conf.js │ │ │ ├── proxy.conf.js │ │ │ └── env.conf.js │ │ ├── layouts │ │ │ ├── simple │ │ │ │ ├── index.less │ │ │ │ └── index.jsx │ │ │ ├── default │ │ │ │ ├── index.less │ │ │ │ └── index.jsx │ │ │ └── index.js │ │ ├── redux │ │ │ ├── types.js │ │ │ ├── actions.js │ │ │ ├── store.js │ │ │ └── reducers.js │ │ ├── containers │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── service │ │ │ └── index.js │ │ ├── router │ │ │ └── index.js │ │ └── utils │ │ │ └── fetch.js │ ├── auth │ │ ├── components │ │ │ └── .gitkeep │ │ ├── constants │ │ │ └── .gitkeep │ │ ├── containers │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── redux │ │ │ ├── actions.js │ │ │ ├── config.js │ │ │ ├── controllers │ │ │ │ ├── userLogin.js │ │ │ │ └── userLogout.js │ │ │ └── reducers.js │ │ ├── router │ │ │ └── index.js │ │ └── service │ │ │ └── index.js │ ├── common │ │ ├── constants │ │ │ └── .gitkeep │ │ ├── components │ │ │ └── .gitkeep │ │ ├── redux │ │ │ ├── actions.js │ │ │ ├── config.js │ │ │ ├── reducers.js │ │ │ └── controllers │ │ │ │ └── getDictList.js │ │ ├── containers │ │ │ └── not-found │ │ │ │ ├── index.less │ │ │ │ └── index.jsx │ │ ├── service │ │ │ └── index.js │ │ └── router │ │ │ └── index.js │ └── home │ │ ├── components │ │ └── .gitkeep │ │ ├── constants │ │ └── .gitkeep │ │ ├── service │ │ └── index.js │ │ ├── redux │ │ ├── actions.js │ │ ├── config.js │ │ ├── controllers │ │ │ ├── getDataItem.js │ │ │ └── getDataList.js │ │ └── reducers.js │ │ ├── containers │ │ ├── index.less │ │ └── index.jsx │ │ └── router │ │ └── index.js └── index.js ├── .browserslistrc ├── .eslintignore ├── public ├── favicon.ico ├── static │ ├── img │ │ ├── logo.png │ │ └── app-arch.png │ ├── css │ │ └── variable.less │ └── js │ │ ├── shims │ │ ├── es6-sham.min.js │ │ └── es6-shim.min.js │ │ ├── flexible │ │ ├── flexible.min.js │ │ └── flexible.js │ │ └── fastclick │ │ ├── fastclick.min.js │ │ └── fastclick.js ├── manifest.json └── index.html ├── .eslintrc ├── .editorconfig ├── jsconfig.json ├── .babelrc ├── .gitignore ├── config ├── pnpTs.js ├── modules.js ├── paths.js ├── env.js ├── webpackDevServer.config.js └── webpack.config.js ├── LICENSE ├── README.md ├── package.json └── scripts ├── local.js ├── dev.js ├── st.js ├── uat.js └── prod.js /src/features/app/constants/app.conf.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/auth/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/auth/constants/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/common/constants/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/home/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/home/constants/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/app/layouts/simple/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/common/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/home/service/index.js: -------------------------------------------------------------------------------- 1 | export const service = {}; -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.2% 2 | not dead 3 | not op_mini all 4 | not ie <= 9 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | config/* 2 | scripts/* 3 | public/* 4 | node_modules/* 5 | node_modules/**/node_modules -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle-git/react-app-boilerplate/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "no-restricted-globals": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle-git/react-app-boilerplate/HEAD/public/static/img/logo.png -------------------------------------------------------------------------------- /src/features/common/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { action as getDictList } from '@/features/common/redux/controllers/getDictList' -------------------------------------------------------------------------------- /src/features/app/redux/types.js: -------------------------------------------------------------------------------- 1 | export { type as getDictListType } from '@/features/common/redux/controllers/getDictList' 2 | -------------------------------------------------------------------------------- /public/static/img/app-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle-git/react-app-boilerplate/HEAD/public/static/img/app-arch.png -------------------------------------------------------------------------------- /src/features/app/redux/actions.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | 3 | export const clearRedux = () => ReduxFactory.clearRedux() 4 | -------------------------------------------------------------------------------- /src/features/common/containers/not-found/index.less: -------------------------------------------------------------------------------- 1 | @import "~@res/css/variable.less"; 2 | 3 | .not-found-page { 4 | color: @black; 5 | text-align: center; 6 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/features/auth/containers/index.less: -------------------------------------------------------------------------------- 1 | .user-login-page { 2 | .profile { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | margin-top: 240px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/features/auth/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { action as userLogin } from '@/features/auth/redux/controllers/userLogin' 2 | export { action as userLogout } from '@/features/auth/redux/controllers/userLogout' 3 | -------------------------------------------------------------------------------- /src/features/app/containers/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.less' 3 | 4 | export default ({children}) => ( 5 |
6 | {children} 7 |
8 | ) 9 | -------------------------------------------------------------------------------- /src/features/home/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { action as getDataList } from '@/features/home/redux/controllers/getDataList' 2 | export { action as getDataItem } from '@/features/home/redux/controllers/getDataItem' 3 | -------------------------------------------------------------------------------- /src/features/home/redux/config.js: -------------------------------------------------------------------------------- 1 | // Redux的配置项(用于构建action和initialState) 2 | export default { 3 | feature: 'home', 4 | actionKeys: [ 5 | { key: 'list', data: [] }, 6 | { key: 'item', data: {} } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/features/app/constants/api.conf.js: -------------------------------------------------------------------------------- 1 | import { DataApi } from 'm2-core' 2 | 3 | export default DataApi({ 4 | 'getDictList': '/dict', 5 | 'getDataList': '/home/data_list', 6 | 'getDataItem': '/home/data_item', 7 | }, '/api') 8 | -------------------------------------------------------------------------------- /src/features/auth/redux/config.js: -------------------------------------------------------------------------------- 1 | // Redux的配置项(用于构建action和initialState) 2 | export default { 3 | feature: 'auth', 4 | actionKeys: [ 5 | { key: 'loginUser', data: { username: '', authenticated: false }, cache: true } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/features/app/service/index.js: -------------------------------------------------------------------------------- 1 | export { service as commonService } from '@/features/common/service' 2 | export { service as homeService } from '@/features/home/service' 3 | export { service as authService } from '@/features/auth/service' 4 | -------------------------------------------------------------------------------- /src/features/common/service/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { get } from '@/features/app/utils/fetch' 3 | import api from '@/features/app/constants/api.conf' 4 | 5 | export const service = { 6 | getDictList: () => get(api.getDictList, { loading: false }) 7 | }; -------------------------------------------------------------------------------- /src/features/home/containers/index.less: -------------------------------------------------------------------------------- 1 | @import "~@res/css/variable.less"; 2 | 3 | .home-page { 4 | color: @black; 5 | text-align: center; 6 | padding-top: 10px; 7 | img { 8 | width: 480px; 9 | height: 320px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "target": "es2017" 5 | }, 6 | "exclude": [ 7 | "node_modules", 8 | "www", 9 | "scripts" 10 | ], 11 | "include": [ 12 | "src" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/features/app/layouts/simple/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppContainer from '@/features/app/containers' 3 | import './index.less' 4 | 5 | export default ({children}) => ( 6 | 7 | {children} 8 | 9 | ) 10 | -------------------------------------------------------------------------------- /src/features/home/router/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from '@/features/home/containers' 2 | 3 | // 多布局时,此处可导出数组,指定每种布局对应的页面 4 | export default { 5 | path: '', 6 | name: '首页模块', 7 | children: [ 8 | { name: '默认页', component: HomePage, default: true } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/features/auth/router/index.js: -------------------------------------------------------------------------------- 1 | import UserLoginPage from '@/features/auth/containers' 2 | 3 | export default { 4 | path: 'auth', 5 | name: '认证模块', 6 | layout: 'simple', 7 | children: [ 8 | { name: '登录页', component: UserLoginPage, path: 'login', public: true } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/features/app/layouts/default/index.less: -------------------------------------------------------------------------------- 1 | @import "~@res/css/variable.less"; 2 | 3 | header, footer { 4 | background: @bg-blue; 5 | height: 40px; 6 | line-height: 40px; 7 | text-align: center; 8 | } 9 | 10 | footer { 11 | position: fixed; 12 | width: 100%; 13 | bottom: 0; 14 | } 15 | -------------------------------------------------------------------------------- /src/features/common/containers/not-found/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './index.less' 3 | 4 | export default () => ( 5 |
6 |

404

7 | 8 |
9 | ) 10 | -------------------------------------------------------------------------------- /src/features/common/redux/config.js: -------------------------------------------------------------------------------- 1 | // Redux的配置项(用于构建action和initialState) 2 | export default { 3 | feature: 'common', 4 | actionKeys: [ 5 | { key: 'dict', data: [], async: false, cache: false, emit: false } 6 | // data: 初始值,async: 处理异步请求,cache: 数据持久保存(防刷新), emit: 等待数据请求后使用DataBus.on处理其他逻辑 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/features/app/redux/store.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import rootReducer from '@/features/app/redux/reducers' 3 | 4 | const store = ReduxFactory.createStore(rootReducer) 5 | const checkIsAuth = () => store.getState().auth.loginUser.authenticated 6 | 7 | export default { 8 | store, 9 | checkIsAuth 10 | } 11 | -------------------------------------------------------------------------------- /src/features/app/layouts/default/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AppContainer from '@/features/app/containers' 3 | import './index.less' 4 | 5 | export default ({children}) => ( 6 | 7 |
这里可以放Header
8 | {children} 9 | 10 |
11 | ) 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app" 4 | ], 5 | "plugins": [ 6 | [ 7 | "@babel/plugin-proposal-decorators", 8 | { 9 | "legacy": true 10 | } 11 | ], 12 | [ 13 | "@babel/plugin-proposal-class-properties", 14 | { 15 | "loose": true 16 | } 17 | ] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/features/common/router/index.js: -------------------------------------------------------------------------------- 1 | import NotFoundPage from '@/features/common/containers/not-found' 2 | 3 | // 多布局时,此处可导出数组,指定每种布局对应的页面 4 | export default { 5 | path: '', 6 | name: '公共模块', 7 | layout: 'simple', 8 | children: [ 9 | // public: true 代表当前页面不需要认证 10 | { path: '404', component: NotFoundPage, public: true } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/features/app/layouts/index.js: -------------------------------------------------------------------------------- 1 | import DefaultLayout from '@/features/app/layouts/default' 2 | import SimpleLayout from '@/features/app/layouts/simple' 3 | 4 | export default [ 5 | // 可配置多个布局,建议配置路由前缀,如未配置则以name作为前缀 6 | { name: 'simple', layout: SimpleLayout, prefix: 'app' }, 7 | // 默认布局放在最后,且只能有一个 8 | { name: 'default', layout: DefaultLayout, default: true } 9 | ] 10 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/features/home/redux/controllers/getDataItem.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/home/redux/config' 3 | 4 | const params = { config, actionKey: 'item' } 5 | 6 | export const action = (itemId) => ReduxFactory.createAction(params, { itemId }) 7 | export const reducer = (state, action) => ReduxFactory.createReducer(state, action, params, () => action.payload); 8 | -------------------------------------------------------------------------------- /src/features/auth/redux/controllers/userLogin.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/auth/redux/config' 3 | 4 | const params = { config, actionKey: 'loginUser', actionType: 'login' } 5 | 6 | export const action = (data) => ReduxFactory.createAction(params, data) 7 | export const reducer = (state, action) => ReduxFactory.createReducer(state, action, params, () => action.payload) 8 | -------------------------------------------------------------------------------- /src/features/auth/redux/controllers/userLogout.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/auth/redux/config' 3 | 4 | const params = { config, actionKey: 'loginUser', actionType: 'logout' } 5 | 6 | export const action = (data) => ReduxFactory.createAction(params, data) 7 | export const reducer = (state, action) => ReduxFactory.createReducer(state, action, params, () => action.payload) 8 | -------------------------------------------------------------------------------- /src/features/auth/service/index.js: -------------------------------------------------------------------------------- 1 | import { DataUtil } from 'm2-core' 2 | 3 | export const service = { 4 | // 模拟登录,注销 5 | login(username, password) { 6 | return new Promise((resolve, reject) => { 7 | resolve({ 8 | username, 9 | token: DataUtil.randomString() 10 | }) 11 | }) 12 | }, 13 | logout() { 14 | return new Promise((resolve, reject) => { 15 | resolve(true) 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/features/app/constants/proxy.conf.js: -------------------------------------------------------------------------------- 1 | const proxy = require('http-proxy-middleware') 2 | module.exports = function(app) { 3 | app.use( 4 | proxy('/api/mock', { 5 | target: 'https://mockapi.com:8080/', // needed for mock api to resolve cross-domain 6 | // changeOrigin: true, // needed for virtual hosted sites 7 | // ws: true, // proxy websockets 8 | pathRewrite: { 9 | '^/api/mock': '/api' 10 | } 11 | }) 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/features/app/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import commonReducer from '@/features/common/redux/reducers' 3 | import authReducer from '@/features/auth/redux/reducers' 4 | import homeReducer from '@/features/home/redux/reducers' 5 | 6 | const reducerMap = { 7 | common: commonReducer, 8 | auth: authReducer, 9 | home: homeReducer 10 | } 11 | 12 | export default (state, action) => ReduxFactory.createAppReducer(reducerMap, state, action); 13 | -------------------------------------------------------------------------------- /src/features/common/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/common/redux/config' 3 | import { reducer as getDictListReducer } from '@/features/common/redux/controllers/getDictList' 4 | 5 | const reducers = [ 6 | getDictListReducer 7 | ] 8 | 9 | const initialState = ReduxFactory.createInitialState(config) 10 | 11 | export default (state = initialState, action) => ReduxFactory.createFeatureReducer(reducers, state, action) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /node_modules/**/node_modules 6 | /.pnp 7 | .pnp.js 8 | .vscode 9 | .idea 10 | yarn.lock 11 | package-lock.json 12 | 13 | # testing 14 | /coverage 15 | 16 | # production 17 | /www 18 | /platforms 19 | /plugins 20 | 21 | # misc 22 | .DS_Store 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie9' 2 | import 'react-app-polyfill/stable' 3 | import React from 'react' 4 | import { render } from 'm2-react' 5 | import { Root } from 'm2-redux' 6 | import AppRouter from '@/features/app/router' 7 | import AppStore from '@/features/app/redux/store' 8 | 9 | render( 10 | 11 | ) 12 | 13 | // 多个组件渲染使用components参数 14 | /* 15 | render({ 16 | components: [ 17 | , 18 | 19 | ] 20 | }) 21 | */ 22 | -------------------------------------------------------------------------------- /src/features/common/redux/controllers/getDictList.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/common/redux/config' 3 | import { commonService } from '@/features/app/service' 4 | 5 | const params = { config, actionKey: 'dict' } 6 | const promise = commonService.getDictList 7 | 8 | export const action = () => ReduxFactory.createAsyncAction(promise, params) 9 | export const reducer = (state, action) => ReduxFactory.createAsyncReducer(state, action, params) 10 | export const type = ReduxFactory.createActionType(params) 11 | -------------------------------------------------------------------------------- /src/features/auth/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/auth/redux/config' 3 | import { reducer as userLoginReducer } from '@/features/auth/redux/controllers/userLogin' 4 | import { reducer as userLogoutReducer } from '@/features/auth/redux/controllers/userLogout' 5 | 6 | const reducers = [ 7 | userLoginReducer, 8 | userLogoutReducer 9 | ] 10 | 11 | const initialState = ReduxFactory.createInitialState(config) 12 | 13 | export default (state = initialState, action) => ReduxFactory.createFeatureReducer(reducers, state, action) 14 | -------------------------------------------------------------------------------- /src/features/home/redux/controllers/getDataList.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/home/redux/config' 3 | 4 | const params = { config, actionKey: 'list' } 5 | // 此处如果需要对同一actionKey进行多个Redux业务处理,务必在每个业务中加上不同的actionType以示区分 6 | // const params = { config, actionKey: 'list', actionType: 'get' } 7 | // const params = { config, actionKey: 'list', actionType: 'remove' } 8 | 9 | export const action = () => ReduxFactory.createAction(params) 10 | export const reducer = (state, action) => ReduxFactory.createReducer(state, action, params, () => action.payload); 11 | -------------------------------------------------------------------------------- /src/features/home/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { ReduxFactory } from 'm2-redux' 2 | import config from '@/features/home/redux/config' 3 | import { reducer as getDataListReducer } from '@/features/home/redux/controllers/getDataList' 4 | import { reducer as getDataItemReducer } from '@/features/home/redux/controllers/getDataItem' 5 | 6 | const reducers = [ 7 | getDataListReducer, 8 | getDataItemReducer 9 | ] 10 | 11 | const initialState = ReduxFactory.createInitialState(config) 12 | 13 | export default (state = initialState, action) => ReduxFactory.createFeatureReducer(reducers, state, action) 14 | -------------------------------------------------------------------------------- /public/static/css/variable.less: -------------------------------------------------------------------------------- 1 | /* Font */ 2 | @font-12: .12rem; 3 | @font-13: .13rem; 4 | @font-14: .14rem; 5 | @font-15: .15rem; 6 | @font-16: .16rem; 7 | @font-18: .18rem; 8 | @font-24: .24rem; 9 | @font-600: 600; 10 | @font-400: 400; 11 | 12 | /* Color */ 13 | @white: #fff; 14 | @black: #333; 15 | @gray: #999; 16 | @red: #ff0d0d; 17 | @blue: #0081ec; 18 | @dark-blue: #247adb; 19 | @bg-gray: #f4f6f9; 20 | @bg-white: #f5f5f5; 21 | @bg-blue: #c5e6fe; 22 | @border-gray: #ccc; 23 | @border-light-gray: #efeff4; 24 | @link-gray: #7f8990; 25 | @divider-gray: #a2a2a2; 26 | 27 | /* Size(Margin,Padding,Width,Height,Border) */ 28 | @size-1: .01rem; 29 | @size-4: .04rem; 30 | @size-8: .08rem; 31 | @size-16: .16rem; 32 | @size-24: .24rem; 33 | -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/features/app/constants/env.conf.js: -------------------------------------------------------------------------------- 1 | export default { 2 | local: { 3 | api: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi', 4 | // 支持多api的形式(请求时配置apiKey, 如:'mock', 'app'等) 5 | // api: { 6 | // mock: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi', 7 | // app: 'https://m2-app.server.com/service_api' 8 | // } 9 | }, 10 | dev: { 11 | api: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi' 12 | }, 13 | st: { 14 | api: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi' 15 | }, 16 | uat: { 17 | api: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi' 18 | }, 19 | prod: { 20 | api: 'https://easy-mock.com/mock/5cd97b7ceebf633b5de54c25/billingapi' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/features/app/containers/index.less: -------------------------------------------------------------------------------- 1 | @import "~@res/css/variable.less"; 2 | 3 | body { 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | .app-container { 9 | background: @white; 10 | } 11 | 12 | .btn { 13 | font-size: 12px; 14 | height: 30px; 15 | line-height: 30px; 16 | padding: 0 30px; 17 | border-radius: 200px; 18 | font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 19 | text-decoration: none; 20 | text-align: center; 21 | cursor: pointer; 22 | border: none; 23 | color: #fff; 24 | outline: none; 25 | margin-right: 5px; 26 | &.btn-info { 27 | background-color: #1B9AF7; 28 | border-color: #1B9AF7; 29 | } 30 | &.btn-primary { 31 | background-color: #7B72E9; 32 | border-color: #7B72E9; 33 | } 34 | &.btn-danger { 35 | background-color: #FF4351; 36 | border-color: #FF4351; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/features/app/router/index.js: -------------------------------------------------------------------------------- 1 | import { loadRoutesConfig } from 'm2-react' 2 | import App from '@/features/app/containers' 3 | // import { loadLayoutRoutesConfig } from 'm2-react' 4 | // import layouts from '@/features/app/layouts' 5 | import commonRouter from '@/features/common/router' 6 | import homeRouter from '@/features/home/router' 7 | import authRouter from '@/features/auth/router' 8 | 9 | // 支持单一布局的配置(不需要单独配置layouts, 只需要App作为容器即可) 10 | const routes = loadRoutesConfig(App, [ 11 | commonRouter, 12 | homeRouter, 13 | authRouter 14 | ]) 15 | 16 | export default { 17 | routes, 18 | routeType: 'hash', 19 | redirectUrl: '/auth/login', 20 | redirect404: '/404' 21 | } 22 | 23 | // const routes = loadLayoutRoutesConfig(layouts, [ 24 | // commonRouter, 25 | // homeRouter, 26 | // authRouter 27 | // ]) 28 | 29 | // export default { 30 | // routes, 31 | // routeType: 'hash', 32 | // redirectUrl: '/app/auth/login', 33 | // redirect404: '/app/404' 34 | // } 35 | -------------------------------------------------------------------------------- /src/features/auth/containers/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { DataStorage, SYMMETRIC_CRYPTO_TYPE } from 'm2-core' 3 | import { connect } from 'm2-redux' 4 | import { authService } from '@/features/app/service' 5 | import { userLogin } from '@/features/auth/redux/actions' 6 | import './index.less' 7 | 8 | @connect({ actions: { userLogin } }) 9 | class UserLoginPage extends React.Component { 10 | async handleLogin() { 11 | const result = await authService.login('Miracle', 123) 12 | if (result) { 13 | const { userLogin, history } = this.props 14 | const { username, token }= result 15 | 16 | DataStorage.set('app-access-token', token, { encryptType: SYMMETRIC_CRYPTO_TYPE.DES }) 17 | userLogin({ username, authenticated: true }) 18 | history.push('') 19 | } 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 |
26 | 27 |
28 |
29 | ) 30 | } 31 | } 32 | 33 | export default UserLoginPage 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Miracle He 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/miracle-git/react-app-boilerplate.git). 2 | 3 | ## Available Scripts 4 | 5 | First clone the boilerplate into your application: 6 | 7 | ### `git clone https://github.com/miracle-git/react-app-boilerplate.git my-react-app` 8 | ### `cd my-react-app` 9 | ### `npm install` or `yarn install` (recommend) 10 | 11 | In the project directory, you can run: 12 | 13 | ### `npm start` or `yarn start` (recommend) 14 | 15 | Runs the app in the development mode.
16 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 17 | 18 | The page will reload if you make edits.
19 | You will also see any lint errors in the console. 20 | 21 | ### `npm run build` or `yarn build` (recommend) 22 | 23 | Builds the app for production to the `build` folder.
24 | It correctly bundles React in production mode and optimizes the build for the best performance. 25 | 26 | The build is minified and the filenames include the hashes.
27 | Your app is ready to be deployed! 28 | 29 | You can learning the M2 architecture via visiting: 30 | https://app.yinxiang.com/fx/c541e662-a573-47e0-af9c-b98d4ca52714 31 | -------------------------------------------------------------------------------- /src/features/home/containers/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'm2-redux' 3 | import { authService } from '@/features/app/service' 4 | import { clearRedux } from '@/features/app/redux/actions' 5 | import { userLogout } from '@/features/auth/redux/actions' 6 | import { getDataList, getDataItem } from '@/features/home/redux/actions' 7 | import './index.less' 8 | 9 | @connect({ reducers: ['home', 'auth'], actions: { getDataList, getDataItem, clearRedux, userLogout } }) 10 | class HomePage extends React.Component { 11 | state = { 12 | // define the stateless data 13 | } 14 | 15 | fields = { 16 | // define the immutable data 17 | } 18 | 19 | static propTypes = { 20 | // define props data type 21 | } 22 | 23 | componentDidMount() { 24 | // fetch the data from server 25 | // this.props.getDataList() 26 | } 27 | 28 | async handleLogout() { 29 | const result = await authService.logout() 30 | if (result) { 31 | const { userLogout, clearRedux, history } = this.props 32 | userLogout({ username: '', authenticated: false }) 33 | clearRedux() 34 | history.push('/auth/login') 35 | } 36 | } 37 | 38 | render() { 39 | const { loginUser } = this.props.auth 40 | 41 | return ( 42 |
43 | Welcome,{loginUser.username} 44 | 45 |

React project has been ready, let's start!!!!

46 | React Architect 47 |
48 | ) 49 | } 50 | } 51 | 52 | export default HomePage 53 | -------------------------------------------------------------------------------- /src/features/app/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import { DataType, DataFetch, DataUtil, FETCH_DEFAULT_OPTIONS } from 'm2-core' 2 | import env from '@/features/app/constants/env.conf' 3 | 4 | const _fetch = (url, options) => { 5 | const _opts = { 6 | ...FETCH_DEFAULT_OPTIONS, 7 | ...options 8 | } 9 | 10 | const $indicator = document.getElementById('indicator') 11 | _opts.loading && ($indicator.style.display = 'block') 12 | 13 | return new Promise((resolve, reject) => { 14 | return DataFetch.request(url, { 15 | env, 16 | // apiKey: 'app', // 当存在多个api接口时,配置默认的apiKey(大多数场景使用的api服务) 17 | ..._opts 18 | }).then(res => { 19 | _opts.loading && ($indicator.style.display = 'none') 20 | if (res.code === 0) { 21 | if (res.result) { 22 | if (_opts.key) { 23 | if (DataType.isArray(res.result)) { 24 | res.result.map(item => item._key = DataUtil.randomString(_opts.keyLen)) 25 | } else if (DataType.isArray(res.result.list)) { 26 | res.result.list.map(item => item._key = DataUtil.randomString(_opts.keyLen)) 27 | } 28 | } 29 | resolve(res.result) 30 | } else { 31 | resolve(res) 32 | } 33 | } else { 34 | reject({ 35 | title: `接口:[${url}]调用失败`, 36 | message: res.msg 37 | }) 38 | } 39 | }).catch(err => { 40 | _opts.loading && ($indicator.style.display = 'none') 41 | reject({ 42 | title: `接口:[${url}]调用失败`, 43 | message: err.msg || err 44 | }) 45 | }) 46 | }) 47 | } 48 | 49 | export const get = (url, options = {}) => _fetch(url, options) 50 | export const post = (url, options = {}) => _fetch(url, {...options, method: 'post'}) 51 | -------------------------------------------------------------------------------- /public/static/js/shims/es6-sham.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * https://github.com/paulmillr/es6-shim 3 | * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com) 4 | * and contributors, MIT License 5 | * es6-sham: v0.35.4 6 | * see https://github.com/paulmillr/es6-shim/blob/0.35.3/LICENSE 7 | * Details and documentation: 8 | * https://github.com/paulmillr/es6-shim/ 9 | */ 10 | (function(t,e){if(typeof define==="function"&&define.amd){define(e)}else if(typeof exports==="object"){module.exports=e()}else{t.returnExports=e()}})(this,function(){"use strict";var t=new Function("return this;");var e=t();var r=e.Object;var n=Function.call.bind(Function.call);var o=Function.toString;var i=String.prototype.match;var f=function(t){try{t();return false}catch(e){return true}};var u=function(){return!f(function(){r.defineProperty({},"x",{get:function(){}})})};var a=!!r.defineProperty&&u();(function(){if(r.setPrototypeOf){return}var t=r.getOwnPropertyNames;var e=r.getOwnPropertyDescriptor;var n=r.create;var o=r.defineProperty;var i=r.getPrototypeOf;var f=r.prototype;var u=function(r,n){t(n).forEach(function(t){o(r,t,e(n,t))});return r};var a=function(t,e){return u(n(e),t)};var c,s;try{c=e(f,"__proto__").set;c.call({},null);s=function(t,e){c.call(t,e);return t}}catch(p){c={__proto__:null};if(c instanceof r){s=a}else{c.__proto__=f;if(c instanceof r){s=function(t,e){t.__proto__=e;return t}}else{s=function(t,e){if(i(t)){t.__proto__=e;return t}else{return a(t,e)}}}}}r.setPrototypeOf=s})();if(a&&function foo(){}.name!=="foo"){r.defineProperty(Function.prototype,"name",{configurable:true,enumerable:false,get:function(){if(this===Function.prototype){return""}var t=n(o,this);var e=n(i,t,/\s*function\s+([^(\s]*)\s*/);var f=e&&e[1];r.defineProperty(this,"name",{configurable:true,enumerable:false,writable:false,value:f});return f}})}}); 11 | //# sourceMappingURL=es6-sham.map 12 | -------------------------------------------------------------------------------- /public/static/js/flexible/flexible.min.js: -------------------------------------------------------------------------------- 1 | (function(win,lib){var doc=win.document;var docEl=doc.documentElement;var metaEl=doc.querySelector('meta[name="viewport"]');var flexibleEl=doc.querySelector('meta[name="flexible"]');var dpr=0;var scale=0;var tid;var flexible=lib.flexible||(lib.flexible={});if(metaEl){console.warn("将根据已有的meta标签来设置缩放比例");var match=metaEl.getAttribute("content").match(/initial\-scale=([\d\.]+)/);if(match){scale=parseFloat(match[1]);dpr=parseInt(1/scale)}}else{if(flexibleEl){var content=flexibleEl.getAttribute("content");if(content){var initialDpr=content.match(/initial\-dpr=([\d\.]+)/);var maximumDpr=content.match(/maximum\-dpr=([\d\.]+)/);if(initialDpr){dpr=parseFloat(initialDpr[1]);scale=parseFloat((1/dpr).toFixed(2))}if(maximumDpr){dpr=parseFloat(maximumDpr[1]);scale=parseFloat((1/dpr).toFixed(2))}}}}if(!dpr&&!scale){var isAndroid=win.navigator.appVersion.match(/android/gi);var isIPhone=win.navigator.appVersion.match(/iphone/gi);var isIPad=win.navigator.appVersion.match(/ipad/gi);var devicePixelRatio=win.devicePixelRatio;if(isIPhone){if(devicePixelRatio>=3&&(!dpr||dpr>=3)){dpr=3}else{if(devicePixelRatio>=2&&(!dpr||dpr>=2)){dpr=2}else{dpr=1}}}else{dpr=2}scale=1/dpr}docEl.setAttribute("data-dpr",dpr);if(!metaEl){metaEl=doc.createElement("meta");metaEl.setAttribute("name","viewport");metaEl.setAttribute("content","initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", user-scalable=no");if(docEl.firstElementChild){docEl.firstElementChild.appendChild(metaEl)}else{var wrap=doc.createElement("div");wrap.appendChild(metaEl);doc.write(wrap.innerHTML)}}function IsPC(){var userAgentInfo=navigator.userAgent;var Agents=new Array("Android","iPhone","SymbianOS","Windows Phone","iPad","iPod");var flag=true;for(var v=0;v0){flag=false;break}}return flag}function refreshRem(){var width=docEl.getBoundingClientRect().width;if(IsPC()&&width<2047){width=540}var rem=width/3.75;docEl.style.fontSize=rem+"px";flexible.rem=win.rem=rem}win.addEventListener("resize",function(){clearTimeout(tid);tid=setTimeout(refreshRem,300)},false);win.addEventListener("pageshow",function(e){if(e.persisted){clearTimeout(tid);tid=setTimeout(refreshRem,300)}},false);if(doc.readyState==="complete"){doc.body.style.fontSize=14*dpr+"px"}else{doc.addEventListener("DOMContentLoaded",function(e){doc.body.style.fontSize=14*dpr+"px"},false)}refreshRem();flexible.dpr=win.dpr=dpr;flexible.refreshRem=refreshRem;flexible.rem2px=function(d){var val=parseFloat(d)*this.rem;if(typeof d==="string"&&d.match(/rem$/)){val+="px"}return val};flexible.px2rem=function(d){var val=parseFloat(d)/this.rem;if(typeof d==="string"&&d.match(/px$/)){val+="rem"}return val}})(window,window["lib"]||(window["lib"]={})); 2 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | 8 | /** 9 | * Get the baseUrl of a compilerOptions object. 10 | * 11 | * @param {Object} options 12 | */ 13 | function getAdditionalModulePaths(options = {}) { 14 | const baseUrl = options.baseUrl; 15 | 16 | // We need to explicitly check for null and undefined (and not a falsy value) because 17 | // TypeScript treats an empty string as `.`. 18 | if (baseUrl == null) { 19 | // If there's no baseUrl set we respect NODE_PATH 20 | // Note that NODE_PATH is deprecated and will be removed 21 | // in the next major release of create-react-app. 22 | 23 | const nodePath = process.env.NODE_PATH || ''; 24 | return nodePath.split(path.delimiter).filter(Boolean); 25 | } 26 | 27 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 28 | 29 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 30 | // the default behavior. 31 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 32 | return null; 33 | } 34 | 35 | // Allow the user set the `baseUrl` to `appSrc`. 36 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 37 | return [paths.appSrc]; 38 | } 39 | 40 | // Otherwise, throw an error. 41 | throw new Error( 42 | chalk.red.bold( 43 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 44 | ' Create React App does not support other values at this time.' 45 | ) 46 | ); 47 | } 48 | 49 | function getModules() { 50 | // Check if TypeScript is setup 51 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 52 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 53 | 54 | if (hasTsConfig && hasJsConfig) { 55 | throw new Error( 56 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 57 | ); 58 | } 59 | 60 | let config; 61 | 62 | // If there's a tsconfig.json we assume it's a 63 | // TypeScript project and set up the config 64 | // based on tsconfig.json 65 | if (hasTsConfig) { 66 | config = require(paths.appTsConfig); 67 | // Otherwise we'll check if there is jsconfig.json 68 | // for non TS projects. 69 | } else if (hasJsConfig) { 70 | config = require(paths.appJsConfig); 71 | } 72 | 73 | config = config || {}; 74 | const options = config.compilerOptions || {}; 75 | 76 | const additionalModulePaths = getAdditionalModulePaths(options); 77 | 78 | return { 79 | additionalModulePaths: additionalModulePaths, 80 | hasTsConfig, 81 | }; 82 | } 83 | 84 | module.exports = getModules(); 85 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | 28 | 37 | 41 | React App 42 | 43 | 44 | 45 |
46 | 47 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-boilerplate", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "This is a boilerplate based on create-react-app and m2 library.", 6 | "homepage": ".", 7 | "scripts": { 8 | "start": "node scripts/local.js", 9 | "build": "node scripts/prod.js", 10 | "dev": "node scripts/dev.js", 11 | "st": "node scripts/st.js", 12 | "uat": "node scripts/uat.js" 13 | }, 14 | "dependencies": { 15 | "m2-core": "1.3.0", 16 | "m2-react": "1.1.6", 17 | "m2-redux": "1.1.8", 18 | "prop-types": "^15.7.2", 19 | "react": "^16.8.0", 20 | "react-app-polyfill": "^1.0.1", 21 | "react-dom": "^16.8.0", 22 | "react-router-dom": "4.3.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "7.4.3", 26 | "@babel/plugin-proposal-class-properties": "^7.4.4", 27 | "@babel/plugin-proposal-decorators": "^7.4.4", 28 | "@babel/plugin-syntax-jsx": "^7.2.0", 29 | "@svgr/webpack": "4.1.0", 30 | "@typescript-eslint/eslint-plugin": "1.6.0", 31 | "@typescript-eslint/parser": "1.6.0", 32 | "babel-eslint": "10.0.1", 33 | "babel-jest": "^24.8.0", 34 | "babel-loader": "8.0.5", 35 | "babel-plugin-import": "^1.12.0", 36 | "babel-plugin-named-asset-import": "^0.3.2", 37 | "babel-preset-react-app": "^9.0.0", 38 | "browserslist": "4.6.3", 39 | "camelcase": "^5.2.0", 40 | "case-sensitive-paths-webpack-plugin": "2.2.0", 41 | "css-loader": "2.1.1", 42 | "dotenv": "6.2.0", 43 | "dotenv-expand": "4.2.0", 44 | "eslint": "^5.16.0", 45 | "eslint-config-react-app": "^4.0.1", 46 | "eslint-loader": "2.1.2", 47 | "eslint-plugin-flowtype": "2.50.1", 48 | "eslint-plugin-import": "2.16.0", 49 | "eslint-plugin-jsx-a11y": "6.2.1", 50 | "eslint-plugin-react": "7.12.4", 51 | "eslint-plugin-react-hooks": "^1.5.0", 52 | "file-loader": "3.0.1", 53 | "fs-extra": "7.0.1", 54 | "html-webpack-plugin": "4.0.0-beta.5", 55 | "http-proxy-middleware": "^0.19.1", 56 | "identity-obj-proxy": "3.0.0", 57 | "is-wsl": "^1.1.0", 58 | "less": "2.7.3", 59 | "less-loader": "4.1.0", 60 | "mini-css-extract-plugin": "0.5.0", 61 | "optimize-css-assets-webpack-plugin": "5.0.1", 62 | "pnp-webpack-plugin": "1.2.1", 63 | "postcss-flexbugs-fixes": "4.1.0", 64 | "postcss-loader": "3.0.0", 65 | "postcss-normalize": "7.0.1", 66 | "postcss-preset-env": "6.6.0", 67 | "postcss-safe-parser": "4.0.1", 68 | "react-dev-utils": "^9.0.1", 69 | "resolve": "1.10.0", 70 | "sass-loader": "7.1.0", 71 | "semver": "6.0.0", 72 | "style-loader": "0.23.1", 73 | "style-resources-loader": "^1.2.1", 74 | "terser-webpack-plugin": "1.2.3", 75 | "ts-pnp": "1.1.2", 76 | "url-loader": "1.1.2", 77 | "webpack": "4.29.6", 78 | "webpack-dev-server": "3.2.1", 79 | "webpack-manifest-plugin": "2.0.4", 80 | "workbox-webpack-plugin": "4.2.0" 81 | }, 82 | "license": "MIT", 83 | "repository": { 84 | "type": "git", 85 | "url": "https://github.com/hmiinyu/react-app-boilerplate.git" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /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/facebook/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(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 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