├── 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 | location.hash='/'}>返回首页
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 |
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 | this.handleLogin()}>Login
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 |
this.handleLogout()}>Logout
45 |
React project has been ready, let's start!!!!
46 |
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 | You need to enable JavaScript to run this app.
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