├── src ├── api │ ├── home.js │ ├── base.js │ ├── index.js │ └── fetchAPI.js ├── routes │ ├── index.jsx │ └── Home │ │ ├── models │ │ ├── index.js │ │ └── PostModel.js │ │ ├── components │ │ ├── Assets │ │ │ ├── mobx.png │ │ │ ├── react.png │ │ │ ├── webpack.png │ │ │ ├── reactrouter.png │ │ │ ├── GitHub-Mark-32px.png │ │ │ ├── GitHub-Mark-Light-32px.png │ │ │ └── react_logo.svg │ │ ├── ArticlePage │ │ │ ├── style.less │ │ │ └── index.jsx │ │ ├── Protected │ │ │ └── index.jsx │ │ ├── PostsPage │ │ │ ├── index.jsx │ │ │ └── style.less │ │ └── WelcomePage │ │ │ ├── index.jsx │ │ │ └── style.less │ │ ├── service │ │ └── index.js │ │ ├── index.jsx │ │ ├── container │ │ ├── index.jsx │ │ ├── Posts.jsx │ │ └── Article.jsx │ │ └── modules │ │ └── HomeStore.js ├── static │ ├── img │ │ ├── 404.png │ │ └── cloud_404.png │ └── css │ │ ├── common.less │ │ └── normalize.less ├── constants │ └── env.js ├── utils │ └── index.js ├── components │ ├── TopNav │ │ ├── style.less │ │ ├── ActiveLink.jsx │ │ └── index.jsx │ ├── TopBar │ │ ├── style.less │ │ ├── Button.jsx │ │ └── index.jsx │ ├── FootBar │ │ └── index.jsx │ ├── LazyLoader │ │ └── index.jsx │ └── NotFound │ │ ├── index.jsx │ │ └── style.less ├── store │ ├── index.js │ └── RouterStore.js ├── index.html ├── coreLayout │ └── index.jsx └── index.jsx ├── .gitignore ├── public ├── favicon.ico └── lib │ ├── min │ └── manifest.json │ └── debug │ └── manifest.json ├── .prettierrc ├── postcss.config.js ├── .babelrc ├── webpack.dll.lib.js ├── .eslintrc ├── package.json ├── webpack.config.production.js ├── webpack.config.js └── README.md /src/api/home.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/index.jsx: -------------------------------------------------------------------------------- 1 | import baseRoute from './Home'; 2 | 3 | export default [...baseRoute]; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .env 4 | .DS_Store 5 | npm-debug.log 6 | .idea/ 7 | output/ 8 | -------------------------------------------------------------------------------- /src/routes/Home/models/index.js: -------------------------------------------------------------------------------- 1 | import PostModel from './PostModel'; 2 | 3 | export { PostModel }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/api/base.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getPostList: { 3 | method: 'GET', 4 | url: '/posts/' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/static/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/static/img/404.png -------------------------------------------------------------------------------- /src/static/img/cloud_404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/static/img/cloud_404.png -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/mobx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/mobx.png -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/react.png -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/webpack.png -------------------------------------------------------------------------------- /src/constants/env.js: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === 'production'; 2 | const isDevelopment = !isProduction; 3 | 4 | export { isProduction, isDevelopment }; 5 | -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/reactrouter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/reactrouter.png -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/GitHub-Mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/GitHub-Mark-32px.png -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/GitHub-Mark-Light-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojurie/react-mobx-react-router4-boilerplate/HEAD/src/routes/Home/components/Assets/GitHub-Mark-Light-32px.png -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | const utils = { 2 | sleep: (timeoutMS) => { 3 | return new Promise((resolve) => { 4 | setTimeout(resolve, timeoutMS); 5 | }); 6 | } 7 | }; 8 | 9 | export default utils; 10 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { assign } from 'lodash'; 2 | import baseAPIs from './base'; 3 | 4 | // 后续随 API 增加,可以考虑根据功能拆分成单独文件维护 5 | // 拆分 API 在此处 assign 进来,如 6 | // assign({}, baseAPIs, { moreAPIs }) 7 | 8 | export default assign({}, baseAPIs, {}); 9 | -------------------------------------------------------------------------------- /src/routes/Home/service/index.js: -------------------------------------------------------------------------------- 1 | import api from 'api'; 2 | import fetchAPI from 'api/fetchAPI'; 3 | 4 | const Posts = { 5 | getPostList: (params) => fetchAPI.get(api.getPostList.url, { params }), 6 | 7 | getPostItem: ({ id }) => fetchAPI.get(`${api.getPostList.url}/${id}`) 8 | }; 9 | 10 | export { Posts }; 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "arrowParens": "always", 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "jsxSingleQuote": true, 8 | "jsxBracketSameLine": false, 9 | "semi": true, 10 | "trailingComma": "none", 11 | "bracketSpacing": true, 12 | "parser": "babylon" 13 | } 14 | -------------------------------------------------------------------------------- /src/components/TopNav/style.less: -------------------------------------------------------------------------------- 1 | .topNav { 2 | display: flex; 3 | flex: 1; 4 | 5 | a { 6 | margin-right: 24px; 7 | color: rgba(#000, .5); 8 | font-weight: 500; 9 | 10 | &:hover { 11 | color: rgba(#000, 1); 12 | } 13 | } 14 | 15 | a.active { 16 | color: rgba(#000, 1); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import HomeStore from 'routes/Home/modules/HomeStore'; 2 | import RouterStore from './RouterStore'; 3 | 4 | export default class RootStore { 5 | static create() { 6 | return new RootStore(); 7 | } 8 | 9 | constructor() { 10 | this.routerStore = new RouterStore(); 11 | this.homeStore = new HomeStore(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/TopBar/style.less: -------------------------------------------------------------------------------- 1 | .topbar { 2 | display: flex; 3 | flex: 0 1 auto; 4 | align-items: center; 5 | padding: 0 24px; 6 | width: 100%; 7 | height: 60px; 8 | background: #F6E27F; 9 | 10 | .button { 11 | color: rgba(#000, .5); 12 | font-weight: 600; 13 | 14 | &:hover { 15 | color: rgba(#000, 1); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/TopNav/ActiveLink.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Link } from 'react-router-dom'; 3 | 4 | const ActiveLink = ({ to, activeOnlyWhenExact, ...rest }) => ( 5 | 6 | {({ match }) => } 7 | 8 | ); 9 | 10 | export default ActiveLink; 11 | -------------------------------------------------------------------------------- /src/components/TopBar/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import propTypes from 'prop-types'; 3 | import style from './style.less'; 4 | 5 | const Button = ({ onClick, title }) => { 6 | return ( 7 | 8 | {title} 9 | 10 | ); 11 | }; 12 | 13 | Button.propTypes = { 14 | title: propTypes.string, 15 | onClick: propTypes.func 16 | }; 17 | 18 | export default Button; 19 | -------------------------------------------------------------------------------- /src/store/RouterStore.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | import { RouterStore as BaseRouterStore, syncHistoryWithStore } from 'mobx-react-router'; 3 | 4 | const history = createBrowserHistory(); 5 | 6 | class RouterStore extends BaseRouterStore { 7 | constructor() { 8 | super(); 9 | if (history) { 10 | this.history = syncHistoryWithStore(history, this); 11 | } 12 | } 13 | } 14 | 15 | export default RouterStore; 16 | -------------------------------------------------------------------------------- /src/routes/Home/models/PostModel.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx'; 2 | 3 | export default class PostModel { 4 | @observable id; 5 | @observable userId; 6 | @observable title; 7 | @observable body; 8 | 9 | constructor({ id, userId, title, body }) { 10 | this.id = id; 11 | this.userId = userId; 12 | this.title = title; 13 | this.body = body; 14 | } 15 | 16 | static fromJS(data) { 17 | return new PostModel(data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/fetchAPI.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // https://www.jianshu.com/p/7a9fbcbb1114 3 | 4 | const fetchAPI = axios.create({ 5 | // baseURL: isDevelopment ? 'https://www.easy-mock.com/mock/593611b991470c0ac101d474' : '/', 6 | baseURL: __DEV__ ? '/api' : 'http://jsonplaceholder.typicode.com', 7 | timeout: 10000, // 设置超时时间 8 | headers: { 9 | Accept: 'application/json', 10 | 'X-Requested-With': 'XMLHttpRequest' 11 | } 12 | }); 13 | 14 | export default fetchAPI; 15 | -------------------------------------------------------------------------------- /src/routes/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import LazyLoader from 'components/LazyLoader'; 2 | import HomePage from './container'; 3 | 4 | export default [ 5 | { 6 | path: '/', 7 | exact: true, 8 | component: HomePage 9 | }, 10 | { 11 | path: '/posts/', 12 | exact: true, 13 | component: LazyLoader(() => import('./container/Posts')) 14 | }, 15 | { 16 | path: '/posts/:id?', 17 | exact: true, 18 | component: LazyLoader(() => import('./container/Article')) 19 | } 20 | ]; 21 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-preset-env')({ 4 | autoprefixer: false 5 | }), 6 | 7 | require('css-declaration-sorter')({ 8 | order: 'concentric-css' 9 | }), 10 | 11 | require('cssnano')({ 12 | preset: 'advanced', 13 | autoprefixer: false, 14 | 'postcss-zindex': false 15 | }), 16 | 17 | require('autoprefixer')({ 18 | browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'], 19 | remove: true 20 | }), 21 | 22 | require('css-mqpacker')() 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /src/routes/Home/components/ArticlePage/style.less: -------------------------------------------------------------------------------- 1 | .post { 2 | min-width: 960px; 3 | max-width: 960px; 4 | padding: 48px 24px; 5 | margin: 0 auto; 6 | 7 | article { 8 | margin-top: 72px; 9 | } 10 | 11 | h1 { 12 | color: #000; 13 | margin-bottom: 24px; 14 | font-weight: 700; 15 | &::first-letter { 16 | text-transform: uppercase; 17 | } 18 | } 19 | 20 | p { 21 | &::first-letter { 22 | text-transform: uppercase; 23 | } 24 | } 25 | @media screen and (max-width: 960px) { 26 | min-width: 100%; 27 | max-width: 100%; 28 | padding: 48px 24px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Boilerplate 6 | 7 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/routes/Home/container/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { observer } from 'mobx-react'; 4 | import TopBar from 'components/TopBar'; 5 | import FootBar from 'components/FootBar'; 6 | import WelcomePage from '../components/WelcomePage'; 7 | 8 | @observer 9 | class HomeContainer extends Component { 10 | constructor(props) { 11 | super(props); 12 | } 13 | 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | HomeContainer.propTypes = { 26 | homeStore: propTypes.object 27 | }; 28 | 29 | export default HomeContainer; 30 | -------------------------------------------------------------------------------- /src/components/FootBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | 4 | class FootBar extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | return ( 11 | 20 | ); 21 | } 22 | } 23 | 24 | FootBar.propTypes = { 25 | location: propTypes.object, 26 | routerStore: propTypes.object, 27 | homeStore: propTypes.object 28 | }; 29 | 30 | export default FootBar; 31 | -------------------------------------------------------------------------------- /src/coreLayout/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import NotFound from 'components/NotFound'; 4 | import routes from 'routes'; 5 | 6 | import '../static/css/normalize.less'; 7 | import '../static/css/common.less'; 8 | 9 | export default class CoreLayout extends Component { 10 | componentDidMount() { 11 | document.querySelector('#root').style.display = 'block'; 12 | } 13 | 14 | render() { 15 | return ( 16 | 17 | {routes.map((route, i) => { 18 | /* eslint-disable react/no-array-index-key */ 19 | return ; 20 | })} 21 | 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/LazyLoader/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const asyncComponent = (loadComponent) => 4 | class AsyncComponent extends React.Component { 5 | state = { 6 | Component: null 7 | }; 8 | 9 | componentDidMount() { 10 | if (this.hasLoadedComponent()) { 11 | return; 12 | } 13 | 14 | loadComponent() 15 | .then((module) => module.default) 16 | .then((Component) => { 17 | this.setState({ Component }); 18 | }) 19 | .catch((err) => { 20 | console.error('Cannot load component in '); 21 | throw err; 22 | }); 23 | } 24 | 25 | hasLoadedComponent() { 26 | const { Component } = this.state; 27 | return Component !== null; 28 | } 29 | 30 | render() { 31 | const { Component } = this.state; 32 | return Component ? : null; 33 | } 34 | }; 35 | 36 | export default asyncComponent; 37 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "loose": true, 7 | "modules": false, 8 | "useBuiltIns": "usage", 9 | "targets": { 10 | "browsers": [ 11 | "> 1%", 12 | "last 2 versions", 13 | "Android >= 4" 14 | ] 15 | } 16 | } 17 | ], 18 | "@babel/preset-react" 19 | ], 20 | "plugins": [ 21 | [ 22 | "@babel/plugin-proposal-decorators", 23 | { 24 | "legacy": true 25 | } 26 | ], 27 | "@babel/plugin-syntax-jsx", 28 | "@babel/plugin-syntax-dynamic-import", 29 | [ 30 | "@babel/plugin-proposal-class-properties", 31 | { 32 | "loose": true 33 | } 34 | ], 35 | [ 36 | "@babel/plugin-transform-runtime", 37 | { 38 | "corejs": 2, 39 | "helpers": true, 40 | "regenerator": true, 41 | "useESModules": false 42 | } 43 | ], 44 | [ 45 | "lodash" 46 | ] 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /src/components/TopNav/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { inject, observer } from 'mobx-react'; 4 | import ActiveLink from './ActiveLink'; 5 | 6 | import style from './style.less'; 7 | 8 | @inject('homeStore') 9 | @observer 10 | class Index extends Component { 11 | constructor(props) { 12 | super(props); 13 | } 14 | 15 | authenticate(e) { 16 | e.preventDefault(); 17 | const { homeStore } = this.props; 18 | homeStore.authenticate(); 19 | } 20 | 21 | render() { 22 | const { homeStore } = this.props; 23 | const { authenticated } = homeStore; 24 | return ( 25 | 31 | ); 32 | } 33 | } 34 | 35 | Index.propTypes = { 36 | location: propTypes.object, 37 | routerStore: propTypes.object, 38 | homeStore: propTypes.object 39 | }; 40 | 41 | export default Index; 42 | -------------------------------------------------------------------------------- /src/routes/Home/components/ArticlePage/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { observer } from 'mobx-react'; 4 | import { Link } from 'react-router-dom'; 5 | import Protected from '../Protected'; 6 | 7 | import style from './style.less'; 8 | 9 | @Protected 10 | @observer 11 | class PostsPage extends Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | const { item, loading } = this.props; 18 | return ( 19 |
20 | ← Back to Posts 21 | {loading ? ( 22 |
23 |

{item.title}

24 |

{item.body}

25 |
26 | ) : ( 27 |
28 | Loading... 29 |
30 | )} 31 |
32 | ); 33 | } 34 | } 35 | 36 | PostsPage.propTypes = { 37 | item: propTypes.object.isRequired, 38 | loading: propTypes.bool.isRequired 39 | }; 40 | 41 | export default PostsPage; 42 | -------------------------------------------------------------------------------- /src/routes/Home/components/Protected/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { inject, observer } from 'mobx-react'; 3 | import { Redirect } from 'react-router-dom'; 4 | 5 | export default function Protected(Children) { 6 | @inject('homeStore') 7 | @observer 8 | class AuthenticatedComponent extends Component { 9 | constructor(props) { 10 | super(props); 11 | const { homeStore } = this.props; 12 | this.homeStore = homeStore; 13 | } 14 | 15 | render() { 16 | const { authenticated, authenticating } = this.homeStore; 17 | const { location } = this.props; 18 | return ( 19 |
20 | {authenticated ? ( 21 | 22 | ) : !authenticating && !authenticated ? ( 23 | 29 | ) : null} 30 |
31 | ); 32 | } 33 | } 34 | 35 | return AuthenticatedComponent; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/TopBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { inject, observer } from 'mobx-react'; 4 | import TopNav from '../TopNav'; 5 | import Button from './Button'; 6 | 7 | import style from './style.less'; 8 | 9 | @inject('routerStore') 10 | @inject('homeStore') 11 | @observer 12 | class TopBar extends Component { 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | authenticate = (e) => { 18 | e.preventDefault(); 19 | const { homeStore } = this.props; 20 | homeStore.authenticate(); 21 | }; 22 | 23 | render() { 24 | const { 25 | location, 26 | homeStore: { authenticated } 27 | } = this.props; 28 | return ( 29 |
30 | 31 |
33 | ); 34 | } 35 | } 36 | 37 | TopBar.propTypes = { 38 | location: propTypes.object, 39 | routerStore: propTypes.object, 40 | homeStore: propTypes.object 41 | }; 42 | 43 | export default TopBar; 44 | -------------------------------------------------------------------------------- /src/routes/Home/container/Posts.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { inject, observer } from 'mobx-react'; 4 | import TopBar from 'components/TopBar'; 5 | import FootBar from 'components/FootBar'; 6 | import PostsPage from '../components/PostsPage'; 7 | 8 | @inject('routerStore') 9 | @inject('homeStore') 10 | @observer 11 | class HomeContainer extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.homeStore = props.homeStore; 15 | } 16 | 17 | componentDidMount() { 18 | this.homeStore.getPostList(); 19 | } 20 | 21 | componentWillUnmount() { 22 | this.homeStore.clearItems(); 23 | } 24 | 25 | render() { 26 | const { 27 | match: { url } 28 | } = this.props; 29 | 30 | const { items, loading } = this.homeStore; 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | HomeContainer.propTypes = { 42 | homeStore: propTypes.object, 43 | match: propTypes.object 44 | }; 45 | 46 | export default HomeContainer; 47 | -------------------------------------------------------------------------------- /src/routes/Home/container/Article.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import propTypes from 'prop-types'; 3 | import { inject, observer } from 'mobx-react'; 4 | import TopBar from 'components/TopBar'; 5 | import FootBar from 'components/FootBar'; 6 | import ArticlePage from '../components/ArticlePage'; 7 | 8 | @inject('routerStore') 9 | @inject('homeStore') 10 | @observer 11 | class HomeContainer extends Component { 12 | constructor(props) { 13 | super(props); 14 | const { homeStore } = this.props; 15 | this.homeStore = homeStore; 16 | } 17 | 18 | componentDidMount() { 19 | const { 20 | match: { 21 | params: { id = null } 22 | } 23 | } = this.props; 24 | this.homeStore.getPostItem({ id }); 25 | } 26 | 27 | componentWillUnmount() { 28 | this.homeStore.clearItems(); 29 | } 30 | 31 | render() { 32 | const { item, loadingItem } = this.homeStore; 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | HomeContainer.propTypes = { 44 | homeStore: propTypes.object, 45 | match: propTypes.object 46 | }; 47 | 48 | export default HomeContainer; 49 | -------------------------------------------------------------------------------- /src/components/NotFound/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import img404 from '../../static/img/404.png'; 3 | import cloud404 from '../../static/img/cloud_404.png'; 4 | 5 | import style from './style.less'; 6 | 7 | const goBack = (e) => { 8 | e.preventDefault(); 9 | return history.back(); 10 | }; 11 | 12 | export const NotFound = () => ( 13 |
14 |
15 |
16 | 404 17 | 404 18 | 404 19 | 404 20 |
21 |
22 |
当前页面无法访问!
23 |
404 Not Found
24 |
请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告
25 | 26 | 返回首页 27 | 28 |
29 |
30 |
31 | ); 32 | 33 | export default NotFound; 34 | -------------------------------------------------------------------------------- /src/routes/Home/components/PostsPage/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import propTypes from 'prop-types'; 4 | import { observer } from 'mobx-react'; 5 | import Protected from '../Protected'; 6 | 7 | import style from './style.less'; 8 | 9 | @Protected 10 | @observer 11 | class PostsPage extends Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | const { items, url, loading } = this.props; 18 | return ( 19 |
20 |

Posts

21 |

Posts are fetched from jsonplaceholder.typicode.com

22 |
23 |
    24 | {items && loading 25 | ? items.slice(6, 12).map((post) => { 26 | return ( 27 |
  • 28 | 29 |

    {post.title}

    30 | 31 |

    {post.body.substring(0, 120)}

    32 |
  • 33 | ); 34 | }) 35 | : 'Loading...'} 36 |
37 |
38 | ); 39 | } 40 | } 41 | 42 | PostsPage.propTypes = { 43 | match: propTypes.object, 44 | loading: propTypes.bool, 45 | items: propTypes.array.isRequired, 46 | url: propTypes.string.isRequired 47 | }; 48 | 49 | export default PostsPage; 50 | -------------------------------------------------------------------------------- /src/routes/Home/components/PostsPage/style.less: -------------------------------------------------------------------------------- 1 | .posts { 2 | min-width: 960px; 3 | max-width: 960px; 4 | padding: 48px 24px; 5 | margin: 0 auto; 6 | h1 { 7 | color: #000; 8 | font-weight: 700; 9 | margin-bottom: 9px; 10 | } 11 | .subheader { 12 | font-size: 1.414em; 13 | font-weight: 300; 14 | } 15 | hr { 16 | border: none; 17 | height: 1px; 18 | color:red; 19 | background-color: #ddd; 20 | margin-top: 48px; 21 | margin-bottom: 48px; 22 | width: 100%; 23 | } 24 | ul { 25 | display: flex; 26 | flex-flow: row wrap; 27 | li { 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: space-between; 31 | flex: 0 1 auto; 32 | margin-right: 36px; 33 | width: calc(50% - 36px); 34 | margin-bottom: 36px; 35 | a { 36 | display: block; 37 | font-weight: 700; 38 | margin-bottom: 12px; 39 | h1 { 40 | font-size: 1.414em; 41 | line-height: 1.2; 42 | &::first-letter { 43 | text-transform: uppercase; 44 | } 45 | } 46 | } 47 | p { 48 | font-weight: 400; 49 | &::first-letter { 50 | text-transform: uppercase; 51 | } 52 | } 53 | } 54 | } 55 | @media screen and (max-width: 960px) { 56 | min-width: 100%; 57 | max-width: 100%; 58 | padding: 48px 24px; 59 | ul li { 60 | flex: 0 1 auto; 61 | width: 100%; 62 | margin-right: 0; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webpack.dll.lib.js: -------------------------------------------------------------------------------- 1 | /* webpack --config webpack.dll.config.js --progress */ 2 | 3 | const path = require('path'); 4 | const webpack = require('webpack'); 5 | 6 | const isDebug = process.env.NODE_ENV === 'development'; 7 | const publicPath = './public/lib'; 8 | 9 | const outputPath = isDebug ? path.join(__dirname, `${publicPath}/debug`) : path.join(__dirname, `${publicPath}/min`); 10 | 11 | const lib = [ 12 | 'react', 13 | 'react-dom', 14 | 'react-router', 15 | 'react-router-dom', 16 | 'history', 17 | 'mobx', 18 | 'mobx-react', 19 | 'mobx-react-router', 20 | 'axios', 21 | 'classnames' 22 | ]; 23 | 24 | const plugin = [ 25 | new webpack.DllPlugin({ 26 | /** 27 | * path 28 | * 定义 manifest 文件生成的位置 29 | * [name]的部分由entry的名字替换 30 | */ 31 | path: path.join(outputPath, 'manifest.json'), 32 | /** 33 | * name 34 | * dll bundle 输出到那个全局变量上 35 | * 和 output.library 一样即可。 36 | */ 37 | name: '[name]', 38 | context: __dirname 39 | }) 40 | ]; 41 | 42 | module.exports = { 43 | devtool: '#source-map', 44 | entry: { 45 | lib: lib 46 | }, 47 | module: { 48 | rules: [ 49 | { 50 | test: /\.(js|jsx)$/, 51 | exclude: /node_modules/, 52 | loader: 'babel-loader', 53 | query: { 54 | compact: true, 55 | cacheDirectory: true 56 | } 57 | } 58 | ] 59 | }, 60 | mode: isDebug ? 'development' : 'production', 61 | output: { 62 | path: outputPath, 63 | filename: isDebug ? '[name].js' : '[name].[hash:9].js', 64 | /** 65 | * output.library 66 | * 将会定义为 window.${output.library} 67 | * 在这次的例子中,将会定义为`window.vendor_library` 68 | */ 69 | library: '[name]' 70 | }, 71 | plugins: plugin 72 | }; 73 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { configure } from 'mobx'; 4 | import RedBox from 'redbox-react'; 5 | import { Provider } from 'mobx-react'; 6 | import { Router } from 'react-router-dom'; 7 | import { AppContainer } from 'react-hot-loader'; 8 | import store from './store'; 9 | import App from './coreLayout'; 10 | 11 | // ======================================================== 12 | // Store Instantiation 13 | // ======================================================== 14 | 15 | const stores = store.create(); 16 | const { 17 | routerStore: { history } 18 | } = stores; 19 | 20 | global._____APP_STATE_____ = stores; 21 | 22 | // changing observed observable values only in actions 23 | configure({ enforceActions: 'always' }); 24 | 25 | // ======================================================== 26 | // Render Setup 27 | // ======================================================== 28 | const MOUNT_NODE = document.getElementById('root'); 29 | let render = () => { 30 | ReactDOM.render( 31 | 32 | 33 | 34 | 35 | 36 | 37 | , 38 | MOUNT_NODE 39 | ); 40 | }; 41 | 42 | // This code is excluded from production bundle 43 | if (__DEV__) { 44 | if (module.hot) { 45 | // Development render functions 46 | const renderApp = render; 47 | const renderError = (error) => { 48 | ReactDOM.render(, MOUNT_NODE); 49 | }; 50 | 51 | // Wrap render in try/catch 52 | render = () => { 53 | try { 54 | renderApp(); 55 | } catch (error) { 56 | console.error(error); 57 | renderError(error); 58 | } 59 | }; 60 | 61 | // hot reload 62 | if (module.hot) { 63 | module.hot.accept('./coreLayout', () => { 64 | try { 65 | renderApp(); 66 | } catch (e) { 67 | render(, MOUNT_NODE); 68 | } 69 | }); 70 | } 71 | } 72 | } 73 | 74 | // ======================================================== 75 | // Go! 76 | // ======================================================== 77 | render(); 78 | -------------------------------------------------------------------------------- /src/routes/Home/components/Assets/react_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React.js_logo 5 | Created with Sketch. 6 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "prettier/react" 7 | ], 8 | "env": { 9 | "browser": true, 10 | "node": true, 11 | "jasmine": true, 12 | "jest": true, 13 | "es6": true 14 | }, 15 | "parser": "babel-eslint", 16 | "parserOptions": { 17 | "ecmaVersion": 6, 18 | "sourceType": "module", 19 | "ecmaFeatures": { 20 | "jsx": true, 21 | "experimentalObjectRestSpread": true 22 | } 23 | }, 24 | "plugins": [ 25 | "react", 26 | "jsx-a11y", 27 | "import", 28 | "prettier", 29 | "promise" 30 | ], 31 | "globals": { 32 | "__VERSION__": true, 33 | "__DEV__": false, 34 | "__TEST__": false, 35 | "__PROD__": false, 36 | "__COVERAGE__": false, 37 | "React": true, 38 | "TestUtils": true 39 | }, 40 | "rules": { 41 | "semi": [ 42 | "error", 43 | "always" 44 | ], 45 | "no-octal-escape": 0, 46 | "no-console": 0, 47 | "import/no-unresolved": 0, 48 | "import/prefer-default-export": 0, 49 | "import/no-extraneous-dependencies": 0, 50 | "no-mixed-operators": 0, 51 | "prefer-promise-reject-errors": 0, 52 | "key-spacing": 0, 53 | "quotes": ["error", "single"], 54 | "jsx-quotes": [ 55 | 2, 56 | "prefer-single" 57 | ], 58 | "max-len": [ 59 | 2, 60 | 160, 61 | 2 62 | ], 63 | "object-curly-spacing": [ 64 | 2, 65 | "always" 66 | ], 67 | "prettier/prettier": [ 68 | "error", 69 | { 70 | "jsxSingleQuote": true 71 | } 72 | ], 73 | "no-useless-constructor": 0, 74 | "jsx-a11y/no-noninteractive-element-interactions": 0, 75 | "react/no-unused-prop-types": 0, 76 | "react/jsx-one-expression-per-line": 0, 77 | "jsx-a11y/click-events-have-key-events": 0, 78 | "jsx-a11y/no-static-element-interactions": 0, 79 | "jsx-a11y/anchor-is-valid": 0, 80 | "react/require-default-props": 0, 81 | "react/forbid-prop-types": 0, 82 | "react/no-danger": 0, 83 | "react/no-array-index-key": 0, 84 | "react/prefer-stateless-function": 0 85 | }, 86 | "settings": { 87 | "react": { 88 | "version": "16" 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/routes/Home/modules/HomeStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action, computed } from 'mobx'; 2 | import { isEmpty } from 'lodash'; 3 | import { PostModel } from '../models'; 4 | import { Posts } from '../service'; 5 | 6 | class HomeStore { 7 | @observable authenticated; 8 | @observable authenticating; 9 | @observable items; 10 | @observable item; 11 | @observable testval; 12 | 13 | constructor() { 14 | this.authenticated = false; 15 | this.authenticating = false; 16 | this.items = []; 17 | this.item = {}; 18 | } 19 | 20 | getPostList(params = {}) { 21 | Posts.getPostList(params) 22 | .then( 23 | action('success', ({ status, data }) => { 24 | if (status === 200) { 25 | this.items = data.map((item) => PostModel.fromJS(item)); 26 | } else { 27 | Promise.reject(status); 28 | } 29 | }) 30 | ) 31 | .catch( 32 | action('error', (error) => { 33 | console.log(error); 34 | }) 35 | ); 36 | } 37 | 38 | getPostItem(params = {}) { 39 | Posts.getPostItem(params) 40 | .then( 41 | action('success', ({ status, data }) => { 42 | if (status === 200) { 43 | this.item = PostModel.fromJS(data); 44 | } else { 45 | Promise.reject(status); 46 | } 47 | }) 48 | ) 49 | .catch( 50 | action('error', (error) => { 51 | console.log(error); 52 | }) 53 | ); 54 | } 55 | 56 | @action clearItems() { 57 | this.items = []; 58 | this.item = {}; 59 | } 60 | 61 | @action authenticate() { 62 | return new Promise((resolve) => { 63 | setTimeout( 64 | action('success', () => { 65 | this.authenticated = !this.authenticated; 66 | this.authenticating = false; 67 | resolve(this.authenticated); 68 | }), 69 | 200 70 | ); 71 | }); 72 | } 73 | 74 | @computed get loading() { 75 | return this.items.length > 0; 76 | } 77 | 78 | @computed get loadingItem() { 79 | return !isEmpty(this.item); 80 | } 81 | 82 | @computed get getItem() { 83 | return this.items.filter((todo) => todo.id === 1); 84 | } 85 | } 86 | 87 | export default HomeStore; 88 | -------------------------------------------------------------------------------- /src/static/css/common.less: -------------------------------------------------------------------------------- 1 | :global { 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td, 11 | article, aside, canvas, details, embed, 12 | figure, figcaption, footer, header, hgroup, 13 | menu, nav, output, ruby, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | 23 | /* HTML5 display-role reset for older browsers */ 24 | 25 | article, aside, details, figcaption, figure, 26 | footer, header, hgroup, menu, nav, section { 27 | display: block; 28 | } 29 | 30 | body { 31 | line-height: 1; 32 | } 33 | 34 | ol, ul { 35 | list-style: none; 36 | } 37 | 38 | blockquote, q { 39 | quotes: none; 40 | } 41 | 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { 44 | content: ''; 45 | content: none; 46 | } 47 | 48 | table { 49 | border-collapse: collapse; 50 | border-spacing: 0; 51 | } 52 | 53 | *, *:before, *:after { 54 | box-sizing: inherit; 55 | } 56 | 57 | html { 58 | font-family: 'Roboto', Helvetica, sans-serif; 59 | font-size: 100%; 60 | font-style: normal; 61 | font-weight: normal; 62 | text-rendering: optimizeLegibility; 63 | -webkit-font-smoothing: antialiased; 64 | line-height: 1.5; 65 | box-sizing: border-box; 66 | } 67 | 68 | body { 69 | color: #666; 70 | display: flex; 71 | line-height: 1.5; 72 | flex-direction: column; 73 | flex: 1; 74 | padding: 0; 75 | margin: 0; 76 | overflow-y: scroll; 77 | } 78 | 79 | .wrapper { 80 | min-height: 100vh; 81 | } 82 | 83 | #root, .wrapper, .page, .authComponent { 84 | display: flex; 85 | flex-direction: column; 86 | flex: 1; 87 | } 88 | 89 | footer { 90 | display: flex; 91 | flex: 0 1 auto; 92 | align-items: center; 93 | justify-content: center; 94 | width: 100%; 95 | height: 60px; 96 | padding: 18px 0; 97 | border-top: 1px solid #eee; 98 | font-size: 13px; 99 | color: #666; 100 | 101 | a { 102 | margin-left: .5ch; 103 | 104 | &:first-child { 105 | margin-right: .5ch; 106 | } 107 | } 108 | } 109 | 110 | h1 { 111 | margin-top: 0; 112 | font-size: 3.998em; 113 | } 114 | 115 | h1, h2 { 116 | font-size: 2.827em; 117 | } 118 | 119 | h3 { 120 | font-size: 1.999em; 121 | } 122 | 123 | h4 { 124 | font-size: 1.414em; 125 | } 126 | 127 | small, .font_small { 128 | font-size: 0.707em; 129 | } 130 | 131 | a { 132 | text-decoration: none; 133 | cursor: pointer; 134 | color: rgba(#000, .9); 135 | 136 | &:hover { 137 | color: rgba(#000, 1); 138 | } 139 | 140 | &.active { 141 | color: rgba(#000, 1); 142 | } 143 | } 144 | 145 | #preloader { 146 | display: none; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/static/css/normalize.less: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Correct the line height in all browsers. 6 | * 3. Prevent adjustments of font size after orientation changes in 7 | * IE on Windows Phone and in iOS. 8 | */ 9 | 10 | /* Document 11 | ========================================================================== */ 12 | 13 | :global { 14 | 15 | html, 16 | body { 17 | margin: 0; 18 | padding: 0; 19 | -webkit-tap-highlight-color: transparent; 20 | font-size: 14px; 21 | font-family: 'STHeiti', 'Microsoft YaHei', 'Helvetica', 'Arial', sans-serif; 22 | } 23 | 24 | *, 25 | *:before, 26 | *:after { 27 | box-sizing: inherit; 28 | -webkit-overflow-scrolling: touch; 29 | } 30 | 31 | .text-left { 32 | text-align: left; 33 | } 34 | 35 | .text-right { 36 | text-align: right; 37 | } 38 | 39 | .fr { 40 | float: right; 41 | } 42 | 43 | .fl { 44 | float: left; 45 | } 46 | 47 | // reset 48 | div, span, applet, object, iframe, 49 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 50 | a, abbr, acronym, address, big, cite, code, 51 | del, dfn, em, img, ins, kbd, q, s, samp, 52 | small, strike, strong, sub, sup, tt, var, 53 | b, u, i, center, 54 | dl, dt, dd, ol, ul, li, 55 | fieldset, form, label, legend, 56 | table, caption, tbody, tfoot, thead, tr, th, td, 57 | article, aside, canvas, details, embed, 58 | figure, figcaption, footer, header, hgroup, 59 | menu, nav, output, ruby, section, summary, 60 | time, mark, audio, video { 61 | margin: 0; 62 | padding: 0; 63 | border: 0; 64 | font-size: 100%; 65 | font: inherit; 66 | vertical-align: baseline; 67 | font-family: PingFangSC-Regular, sans-serif; 68 | } 69 | 70 | article, aside, details, figcaption, figure, 71 | footer, header, hgroup, menu, nav, section { 72 | display: block; 73 | } 74 | 75 | body { 76 | line-height: 1.5; 77 | -webkit-font-smoothing: antialiased; 78 | } 79 | 80 | ol, ul { 81 | list-style: none; 82 | } 83 | 84 | blockquote, q { 85 | quotes: none; 86 | } 87 | 88 | blockquote:before, blockquote:after, 89 | q:before, q:after { 90 | content: ''; 91 | content: none; 92 | } 93 | 94 | table { 95 | border-collapse: collapse; 96 | border-spacing: 0; 97 | } 98 | 99 | b, strong { 100 | font-weight: bolder; 101 | } 102 | 103 | [hidden] { 104 | display: none; 105 | } 106 | 107 | .no-select { 108 | -ms-user-select: none; 109 | -moz-user-select: none; 110 | -webkit-user-select: none; 111 | user-select: none; 112 | } 113 | 114 | .no-appear { 115 | -webkit-appearance: none; 116 | -moz-appearance: none; 117 | -ms-appearance: none; 118 | -o-appearance: none; 119 | appearance: none; 120 | } 121 | 122 | .no-wrap { 123 | overflow: hidden; 124 | white-space: nowrap; 125 | text-overflow: ellipsis; 126 | } 127 | 128 | .clearfix:before, .clearfix:after { 129 | content: " "; 130 | display: table; 131 | } 132 | 133 | .clearfix:after { 134 | clear: both; 135 | } 136 | 137 | /* for ie 6/7 */ 138 | 139 | .clearfix { 140 | zoom: 1; 141 | } 142 | 143 | *, *:after, *:before { 144 | -webkit-box-sizing: border-box; 145 | -moz-box-sizing: border-box; 146 | box-sizing: border-box; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/routes/Home/components/WelcomePage/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import style from './style.less'; 4 | 5 | export default class WelcomePage extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |
14 |
15 |
16 |

React MobX React-Router 4 Boilerplate

17 |
18 |
19 |

A simple starting point for React with routing, data-fetching and state management!

20 |
21 | 30 |
31 |
32 |
33 |

Included libraries

34 |
35 |
36 |
37 |
38 |
39 | 40 |

React 16

41 |
42 | UI Library 43 |

44 | React makes it painless to create
45 | interactive UIs. 46 |

47 |
48 |
49 |
50 |
51 |
52 | 53 |

MobX 5

54 |
55 | Reactive State Management 56 |

MobX is a battle tested library that makes state management simple and scalable.

57 |
58 |
59 |
60 |
61 |
62 | 63 |

React Router 4

64 |
65 | Routing Library 66 |

67 | React Router is a declarative way to render, at any location, any UI that you and your team can think 68 | up. 69 |

70 |
71 |
72 |
73 |
74 |
75 | 76 |

Webpack 4

77 |
78 | Module Bundler 79 |

Webpack takes modules with dependencies and generates static assets representing those modules.

80 |
81 |
82 |
83 |

Extras

84 |
85 |
    86 |
  • ✓ React-hot-loader
  • 87 |
  • ✓ Mobx-react-router
  • 88 |
  • ✓ CSS Modules
  • 89 |
  • ✓ Pushstate Server Preview
  • 90 |
91 |
    92 |
  • ✓ Prettier & ESlint
  • 93 |
  • ✓ Babel-preset-env
  • 94 |
  • ✓ Code Splitting
  • 95 |
  • ✓ Webpack Dll Plugin
  • 96 |
97 |
98 |
99 |
100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MartinsReactBoilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "lint": "eslint ./src --ext .jsx,.js", 8 | "lint:fix": "npm run lint -- --fix", 9 | "format": "prettier-eslint --write './src/**/*.{ts,js,json,jsx}'", 10 | "start": "cross-env NODE_ENV=development webpack-dev-server --inline --progress --config webpack.config.js", 11 | "build": "rimraf ./output && cross-env NODE_ENV=production webpack --config webpack.config.production.js", 12 | "preview": "pushstate-server ./output 3003", 13 | "dll": "NODE_ENV=production webpack --config webpack.dll.lib.js --progress", 14 | "dll:dev": "NODE_ENV=development webpack --config webpack.dll.lib.js --progress" 15 | }, 16 | "keywords": [ 17 | "React", 18 | "React-Router", 19 | "MobX", 20 | "Webpack", 21 | "ES6", 22 | "ES7" 23 | ], 24 | "author": "Martin Haagensli", 25 | "license": "ISC", 26 | "devDependencies": { 27 | "@babel/core": "^7.1.2", 28 | "@babel/plugin-proposal-class-properties": "^7.1.0", 29 | "@babel/plugin-proposal-decorators": "^7.1.2", 30 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 31 | "@babel/plugin-syntax-jsx": "^7.0.0", 32 | "@babel/plugin-transform-runtime": "^7.1.0", 33 | "@babel/polyfill": "^7.0.0", 34 | "@babel/preset-env": "^7.1.0", 35 | "@babel/preset-react": "^7.0.0", 36 | "@babel/runtime": "^7.1.2", 37 | "@babel/runtime-corejs2": "^7.1.2", 38 | "add-asset-html-webpack-plugin": "^3.1.2", 39 | "autoprefixer": "^9.4.2", 40 | "babel-eslint": "^9.0.0", 41 | "babel-loader": "^8.0.4", 42 | "babel-plugin-add-module-exports": "^0.3.3", 43 | "babel-plugin-import": "^1.6.6", 44 | "babel-plugin-lodash": "^3.3.4", 45 | "babel-plugin-react-css-modules": "^3.4.2", 46 | "cross-env": "^5.1.4", 47 | "css-loader": "^0.28.11", 48 | "eslint": "^5.10.0", 49 | "eslint-config-airbnb": "^17.0.0", 50 | "eslint-config-prettier": "^3.3.0", 51 | "eslint-loader": "^2.1.1", 52 | "eslint-module-utils": "^2.0.0", 53 | "eslint-plugin-import": "^2.2.0", 54 | "eslint-plugin-jasmine": "^2.2.0", 55 | "eslint-plugin-jsx-a11y": "^6.1.0", 56 | "eslint-plugin-prettier": "^3.0.0", 57 | "eslint-plugin-promise": "^3.8.0", 58 | "eslint-plugin-react": "^7.11.1", 59 | "file-loader": "^1.1.11", 60 | "html-webpack-plugin": "^3.2.0", 61 | "image-webpack-loader": "^4.2.0", 62 | "less": "^3.8.1", 63 | "less-loader": "^4.1.0", 64 | "lint-staged": "^8.1.0", 65 | "lodash-webpack-plugin": "^0.11.5", 66 | "mini-css-extract-plugin": "^0.4.5", 67 | "optimize-css-assets-webpack-plugin": "^4.0.0", 68 | "postcss-loader": "^2.1.3", 69 | "prerender-spa-plugin": "^3.4.0", 70 | "prettier": "^1.15.3", 71 | "prettier-eslint": "^8.8.2", 72 | "prettier-eslint-cli": "^4.7.1", 73 | "pushstate-server": "^3.0.1", 74 | "react-hot-loader": "^4.6.0", 75 | "redbox-react": "^1.6.0", 76 | "rimraf": "^2.5.4", 77 | "style-loader": "^0.20.3", 78 | "uglifyjs-webpack-plugin": "^2.0.1", 79 | "url-loader": "^1.0.1", 80 | "webpack": "^4.20.2", 81 | "webpack-bundle-analyzer": "^3.0.3", 82 | "webpack-cli": "^3.1.2", 83 | "webpack-dev-server": "^3.1.10", 84 | "webpack-merge": "^4.1.5" 85 | }, 86 | "dependencies": { 87 | "axios": "^0.18.0", 88 | "classnames": "^2.2.6", 89 | "css-declaration-sorter": "^2.0.0", 90 | "css-mqpacker": "^6.0.1", 91 | "faker": "^4.1.0", 92 | "history": "^4.7.2", 93 | "husky": "^1.2.0", 94 | "immer": "^1.9.0", 95 | "lazy-route": "^1.0.7", 96 | "lodash": "^4.17.11", 97 | "mobx": "^5.7.0", 98 | "mobx-react": "^5.4.3", 99 | "mobx-react-router": "^4.0.5", 100 | "moment": "^2.15.1", 101 | "package": "^1.0.1", 102 | "postcss-preset-env": "^4.1.0", 103 | "prop-types": "^15.5.10", 104 | "puppeteer": "^1.11.0", 105 | "querystring": "^0.2.0", 106 | "react": "^16.8.2", 107 | "react-dom": "^16.8.2", 108 | "react-router": "^4.3.1", 109 | "react-router-dom": "^4.3.1" 110 | }, 111 | "lint-staged": { 112 | "src/**/*.{js,jsx}": [ 113 | "npm run format", 114 | "npm run lint:fix", 115 | "git add" 116 | ] 117 | }, 118 | "husky": { 119 | "hooks": { 120 | "pre-commit": "lint-staged", 121 | "pre-push": "" 122 | } 123 | }, 124 | "repository": { 125 | "url": "git@github.com:mhaagens/react-mobx-react-router4-boilerplate.git", 126 | "type": "git" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/components/NotFound/style.less: -------------------------------------------------------------------------------- 1 | .notFound { 2 | background: #fff; 3 | marginTop: -20px; 4 | 5 | .wscnHttp404 { 6 | position: relative; 7 | width: 1200px; 8 | margin: 0 auto; 9 | padding: 0 100px; 10 | overflow: hidden; 11 | 12 | .pic404 { 13 | position: relative; 14 | float: left; 15 | width: 600px; 16 | padding: 150px 0; 17 | overflow: hidden; 18 | 19 | &__parent { 20 | width: 100%; 21 | } 22 | 23 | &__child { 24 | position: absolute; 25 | 26 | &.left { 27 | width: 80px; 28 | top: 17px; 29 | left: 220px; 30 | opacity: 0; 31 | animation-name: cloudLeft; 32 | animation-duration: 2s; 33 | animation-timing-function: linear; 34 | animation-fill-mode: forwards; 35 | animation-delay: 1s; 36 | } 37 | 38 | &.mid { 39 | width: 46px; 40 | top: 10px; 41 | left: 420px; 42 | opacity: 0; 43 | animation-name: cloudMid; 44 | animation-duration: 2s; 45 | animation-timing-function: linear; 46 | animation-fill-mode: forwards; 47 | animation-delay: 1.2s; 48 | } 49 | 50 | &.right { 51 | width: 62px; 52 | top: 100px; 53 | left: 500px; 54 | opacity: 0; 55 | animation-name: cloudRight; 56 | animation-duration: 2s; 57 | animation-timing-function: linear; 58 | animation-fill-mode: forwards; 59 | animation-delay: 1s; 60 | } 61 | 62 | @keyframes cloudLeft { 63 | 0% { 64 | top: 17px; 65 | left: 220px; 66 | opacity: 0; 67 | } 68 | 20% { 69 | top: 33px; 70 | left: 188px; 71 | opacity: 1; 72 | } 73 | 80% { 74 | top: 81px; 75 | left: 92px; 76 | opacity: 1; 77 | } 78 | 100% { 79 | top: 97px; 80 | left: 60px; 81 | opacity: 0; 82 | } 83 | } 84 | @keyframes cloudMid { 85 | 0% { 86 | top: 10px; 87 | left: 420px; 88 | opacity: 0; 89 | } 90 | 20% { 91 | top: 40px; 92 | left: 360px; 93 | opacity: 1; 94 | } 95 | 70% { 96 | top: 130px; 97 | left: 180px; 98 | opacity: 1; 99 | } 100 | 100% { 101 | top: 160px; 102 | left: 120px; 103 | opacity: 0; 104 | } 105 | } 106 | @keyframes cloudRight { 107 | 0% { 108 | top: 100px; 109 | left: 500px; 110 | opacity: 0; 111 | } 112 | 20% { 113 | top: 120px; 114 | left: 460px; 115 | opacity: 1; 116 | } 117 | 80% { 118 | top: 180px; 119 | left: 340px; 120 | opacity: 1; 121 | } 122 | 100% { 123 | top: 200px; 124 | left: 300px; 125 | opacity: 0; 126 | } 127 | } 128 | } 129 | } 130 | 131 | .bullshit { 132 | position: relative; 133 | float: left; 134 | width: 300px; 135 | padding: 150px 0; 136 | overflow: hidden; 137 | 138 | &__oops { 139 | font-size: 32px; 140 | font-weight: bold; 141 | line-height: 40px; 142 | color: #1482f0; 143 | opacity: 0; 144 | margin-bottom: 20px; 145 | animation-name: slideUp; 146 | animation-duration: 0.5s; 147 | animation-fill-mode: forwards; 148 | } 149 | 150 | &__headline { 151 | font-size: 20px; 152 | line-height: 24px; 153 | color: #1482f0; 154 | opacity: 0; 155 | margin-bottom: 10px; 156 | animation-name: slideUp; 157 | animation-duration: 0.5s; 158 | animation-delay: 0.1s; 159 | animation-fill-mode: forwards; 160 | } 161 | 162 | &__info { 163 | font-size: 13px; 164 | line-height: 21px; 165 | color: grey; 166 | opacity: 0; 167 | margin-bottom: 30px; 168 | animation-name: slideUp; 169 | animation-duration: 0.5s; 170 | animation-delay: 0.2s; 171 | animation-fill-mode: forwards; 172 | } 173 | 174 | &__return_home { 175 | display: block; 176 | float: left; 177 | width: 110px; 178 | height: 36px; 179 | background: #1482f0; 180 | border-radius: 100px; 181 | text-align: center; 182 | color: #ffffff; 183 | opacity: 0; 184 | font-size: 14px; 185 | line-height: 36px; 186 | cursor: pointer; 187 | animation-name: slideUp; 188 | animation-duration: 0.5s; 189 | animation-delay: 0.3s; 190 | animation-fill-mode: forwards; 191 | } 192 | 193 | @keyframes slideUp { 194 | 0% { 195 | transform: translateY(60px); 196 | opacity: 0; 197 | } 198 | 100% { 199 | transform: translateY(0); 200 | opacity: 1; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/routes/Home/components/WelcomePage/style.less: -------------------------------------------------------------------------------- 1 | .home { 2 | .hero-unit { 3 | display: flex; 4 | flex: 0 1 auto; 5 | flex-flow: row wrap; 6 | align-items: center; 7 | justify-content: center; 8 | width: 100%; 9 | } 10 | .react-logo { 11 | display: block; 12 | flex: none; 13 | overflow: hidden; 14 | clear: both; 15 | width: 100%; 16 | height: 96px; 17 | margin-bottom: 24px; 18 | background: url(../Assets/react_logo.svg); 19 | background-repeat: no-repeat; 20 | background-size: 96px 96px; 21 | background-position: center; 22 | } 23 | header { 24 | position: relative; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: center; 29 | min-height: calc(480px - 60px); 30 | background: #F6E27F; 31 | .hero-unit { 32 | margin-bottom: 24px; 33 | } 34 | .react-logo { 35 | } 36 | h1 { 37 | font-weight: 700; 38 | color: #000; 39 | } 40 | h4 { 41 | color: rgba(#000, .5); 42 | font-weight: 400; 43 | } 44 | @media screen and (max-width: 960px) { 45 | padding: 24px; 46 | text-align: center; 47 | h1 { 48 | font-size: 6vmax; 49 | } 50 | } 51 | } 52 | main { 53 | display: flex; 54 | flex-flow: row wrap; 55 | min-width: 960px; 56 | max-width: 960px; 57 | margin: 0 auto; 58 | padding: 96px 0; 59 | .section-header { 60 | flex: 0 1 auto; 61 | width: 100%; 62 | padding-bottom: 72px; 63 | text-align: center; 64 | color: #000; 65 | h3 { 66 | font-weight: 700; 67 | } 68 | &.extras { 69 | margin-top: 48px; 70 | h4 { 71 | font-weight: 700; 72 | color: #000; 73 | } 74 | ul { 75 | display: flex; 76 | flex: 1; 77 | flex-flow: row nowrap; 78 | width: 100%; 79 | max-width: 960px; 80 | margin: 36px auto 0 auto; 81 | li { 82 | display: flex; 83 | flex: 1; 84 | align-content: center; 85 | justify-content: center; 86 | } 87 | @media screen and (max-width: 960px) { 88 | flex-flow: column; 89 | li { 90 | flex: 0 1 auto; 91 | width: 100%; 92 | margin-bottom: 18px; 93 | } 94 | } 95 | } 96 | } 97 | hr { 98 | border: none; 99 | height: 1px; 100 | color: #ddd; 101 | background-color: #ddd; 102 | margin: 24px auto 0px; 103 | max-width: 96px; 104 | } 105 | } 106 | .boilerplate-item { 107 | display: flex; 108 | flex: 0 1 auto; 109 | flex-flow: row nowrap; 110 | width: calc(50% - 48px); 111 | margin-right: 48px; 112 | margin-bottom: 48px; 113 | .boilerplate-logo { 114 | display: flex; 115 | width: 80px; 116 | height: 80px; 117 | margin-right: 24px; 118 | background: url(../Assets/react.png); 119 | background-repeat: no-repeat; 120 | background-size: contain; 121 | &.mobx { 122 | background: url(../Assets/mobx.png); 123 | background-repeat: no-repeat; 124 | background-size: contain; 125 | } 126 | &.reactrouter { 127 | background: url(../Assets/reactrouter.png); 128 | background-repeat: no-repeat; 129 | background-size: contain; 130 | } 131 | &.webpack { 132 | background: url(../Assets/webpack.png); 133 | background-repeat: no-repeat; 134 | background-size: contain; 135 | } 136 | } 137 | .boilerplate-item-content { 138 | flex: 1; 139 | } 140 | h4 { 141 | font-weight: 500; 142 | color: #000; 143 | } 144 | small { 145 | text-transform: uppercase; 146 | font-weight: 500; 147 | letter-spacing: 1px; 148 | color: #999; 149 | } 150 | p { 151 | margin-top: 12px; 152 | } 153 | } 154 | @media screen and (max-width: 960px) { 155 | min-width: 100%; 156 | max-width: 100%; 157 | padding: 48px 24px; 158 | .boilerplate-item { 159 | flex: 0 1 auto; 160 | width: 100%; 161 | margin-right: 0; 162 | } 163 | } 164 | } 165 | } 166 | 167 | .github-buttons { 168 | display: flex; 169 | flex: 0 1 auto; 170 | width: 100%; 171 | margin: 36px 0; 172 | justify-content: center; 173 | align-items: center; 174 | } 175 | 176 | .github-buttons a { 177 | display: flex; 178 | align-items: center; 179 | padding: 18px 24px; 180 | border-radius: 2px; 181 | border: 1px solid #000; 182 | color: #000; 183 | font-weight: 600; 184 | font-size: 18px; 185 | &:before { 186 | content: ''; 187 | width: 24px; 188 | height: 24px; 189 | margin-right: 12px; 190 | background: url(../Assets/GitHub-Mark-32px.png); 191 | background-repeat: no-repeat; 192 | background-size: contain; 193 | } 194 | &:hover { 195 | background: #000; 196 | color: #fff; 197 | &:before { 198 | background: url(../Assets/GitHub-Mark-Light-32px.png); 199 | background-repeat: no-repeat; 200 | background-size: contain; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const cssnano = require('cssnano'); 4 | const merge = require('webpack-merge'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const BundlePlugin = require('webpack-bundle-analyzer'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 9 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 10 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); 11 | const PrerenderSPAPlugin = require('prerender-spa-plugin'); 12 | const webpackConfig = require('./webpack.config'); 13 | 14 | const Renderer = PrerenderSPAPlugin.PuppeteerRenderer; 15 | const publicPath = '/'; 16 | 17 | const mergeConfig = merge(webpackConfig, { 18 | mode: 'production', 19 | 20 | output: { 21 | path: path.join(__dirname, 'output'), 22 | publicPath: publicPath, 23 | filename: 'js/[name].[hash:12].js', 24 | chunkFilename: 'js/[id].[chunkhash:12].js' 25 | }, 26 | 27 | devtool: '#source-map', 28 | 29 | module: {}, 30 | 31 | optimization: { 32 | runtimeChunk: { 33 | name: 'manifest' 34 | }, 35 | minimizer: [ 36 | new UglifyJsPlugin({ 37 | cache: true, 38 | parallel: true, 39 | uglifyOptions: { 40 | warnings: false, 41 | mangle: { 42 | toplevel: true 43 | } 44 | } 45 | }), 46 | new OptimizeCSSAssetsPlugin({ 47 | assetNameRegExp: /\.css$/g, 48 | cssProcessor: cssnano, 49 | cssProcessorOptions: { 50 | autoprefixer: false, 51 | preset: [ 52 | 'default', 53 | { 54 | discardComments: { 55 | removeAll: true 56 | } 57 | } 58 | ] 59 | } 60 | }) 61 | ], 62 | splitChunks: { 63 | chunks: 'async', 64 | minSize: 30000, 65 | minChunks: 1, 66 | maxAsyncRequests: 5, 67 | maxInitialRequests: 3, 68 | name: false, 69 | cacheGroups: { 70 | vendor: { 71 | name: 'vendor', 72 | chunks: 'initial', 73 | priority: -10, 74 | reuseExistingChunk: false, 75 | test: /node_modules\/(.*)\.js/ 76 | }, 77 | styles: { 78 | name: 'styles', 79 | test: /\.(less|css)$/, 80 | chunks: 'all', 81 | minChunks: 1, 82 | reuseExistingChunk: true, 83 | enforce: true 84 | } 85 | } 86 | } 87 | }, 88 | 89 | performance: { 90 | hints: false 91 | }, 92 | 93 | plugins: [ 94 | new webpack.DefinePlugin({ 95 | 'process.env': { 96 | NODE_ENV: JSON.stringify('production'), 97 | __DEV__: process.env.NODE_ENV === 'production' 98 | } 99 | }), 100 | 101 | new webpack.NamedModulesPlugin(), 102 | 103 | new MiniCssExtractPlugin({ 104 | filename: 'css/app.[name].css', 105 | chunkFilename: 'css/app.[contenthash:12].css' 106 | }), 107 | 108 | new HtmlWebpackPlugin({ 109 | hash: false, 110 | template: './src/index.html', 111 | filename: 'index.html' 112 | }), 113 | 114 | new webpack.DllReferencePlugin({ 115 | context: __dirname, 116 | manifest: require('./public/lib/min/manifest.json') 117 | }), 118 | 119 | new AddAssetHtmlPlugin([ 120 | { 121 | filepath: path.resolve(__dirname, './public/lib/min/lib.aef84325a.js'), 122 | outputPath: 'lib/min', 123 | publicPath: `${publicPath}lib/min`, 124 | includeSourcemap: false 125 | } 126 | ]), 127 | 128 | new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /nb/), 129 | 130 | new BundlePlugin.BundleAnalyzerPlugin(), 131 | 132 | new PrerenderSPAPlugin({ 133 | // Index.html is in the root directory. 134 | staticDir: path.join(__dirname, 'output'), 135 | outputDir: path.join(__dirname, 'output'), 136 | indexPath: path.join(__dirname, 'output', 'index.html'), 137 | routes: ['/'], 138 | // Optional minification. 139 | minify: { 140 | collapseBooleanAttributes: true, 141 | collapseWhitespace: true, 142 | decodeEntities: true, 143 | keepClosingSlash: true, 144 | sortAttributes: true 145 | }, 146 | 147 | server: { 148 | // Normally a free port is autodetected, but feel free to set this if needed. 149 | port: 8001, 150 | proxy: { 151 | '/dist/': { 152 | target: 'http://127.0.0.1:8001', 153 | pathRewrite: { '^/dist': '/' } 154 | } 155 | } 156 | }, 157 | 158 | postProcess(context) { 159 | // Remove /index.html from the output path if the dir name ends with a .html file extension. 160 | // For example: /dist/dir/special.html/index.html -> /dist/dir/special.html 161 | // context.html = context.html.replace(/\/dist/gi, ''); 162 | return context; 163 | }, 164 | 165 | renderer: new Renderer({ 166 | renderAfterTime: 2000 167 | }) 168 | }) 169 | ] 170 | }); 171 | 172 | mergeConfig.module.rules 173 | .filter((rule) => { 174 | const bool = 175 | rule.use && 176 | Array.isArray(rule.use) && 177 | rule.use.find((name) => { 178 | if (Object.prototype.toString.call(name) === '[object Object]') { 179 | name = name.loader; 180 | } 181 | return /css-loader/.test(name.split('?')[0]); 182 | }); 183 | return bool; 184 | }) 185 | .forEach((rule) => { 186 | rule.use[0] = MiniCssExtractPlugin.loader; 187 | }); 188 | 189 | module.exports = mergeConfig; 190 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); 5 | const POSTCSS = require('./postcss.config'); 6 | 7 | const isDebug = process.env.NODE_ENV === 'development'; 8 | 9 | const baseConfig = { 10 | mode: 'development', 11 | entry: { 12 | vendor: ['classnames', 'immer'], 13 | app: isDebug 14 | ? [ 15 | 'react-hot-loader/patch', 16 | 'webpack-dev-server/client?http://0.0.0.0:3000', 17 | 'webpack/hot/only-dev-server', 18 | './src/index' 19 | ] 20 | : ['./src/index'] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx', '.json', '.web.js'], 24 | alias: { 25 | api: path.resolve(__dirname, 'src/api'), 26 | utils: path.resolve(__dirname, 'src/utils'), 27 | static: path.resolve(__dirname, 'src/static'), 28 | routes: path.resolve(__dirname, 'src/routes'), 29 | components: path.resolve(__dirname, 'src/components'), 30 | constants: path.resolve(__dirname, 'src/constants') 31 | } 32 | }, 33 | devServer: { 34 | hot: true, 35 | contentBase: [path.resolve(__dirname, 'output'), path.resolve(__dirname, 'public')], 36 | port: 3000, 37 | host: '0.0.0.0', 38 | publicPath: '/', 39 | stats: { 40 | color: true 41 | }, 42 | clientLogLevel: 'none', 43 | proxy: { 44 | '/api': { 45 | target: 'http://jsonplaceholder.typicode.com', 46 | pathRewrite: { '^/api': '' }, 47 | changeOrigin: true 48 | } 49 | }, 50 | historyApiFallback: true, 51 | disableHostCheck: true 52 | }, 53 | output: { 54 | path: path.join(__dirname, 'output'), 55 | publicPath: '/', 56 | filename: 'js/[name].js', 57 | chunkFilename: 'js/[id].js' 58 | }, 59 | devtool: '#source-map', 60 | module: { 61 | rules: [ 62 | { 63 | test: /\.(js|jsx)$/, 64 | exclude: /node_modules/, 65 | loader: 'babel-loader', 66 | query: { 67 | compact: true, 68 | cacheDirectory: true 69 | } 70 | }, 71 | { 72 | test: /\.less$/, 73 | exclude: /node_modules/, 74 | use: [ 75 | 'style-loader', 76 | { 77 | loader: 'css-loader', 78 | options: { 79 | modules: true, 80 | importLoaders: 1, 81 | minimize: false, 82 | localIdentName: '[name]__[local]___[hash:base64:5]' 83 | } 84 | }, 85 | { 86 | loader: 'postcss-loader', 87 | options: { 88 | plugins: POSTCSS.plugins, 89 | sourceMap: isDebug 90 | } 91 | }, 92 | { 93 | loader: 'less-loader', 94 | options: { 95 | javascriptEnabled: true 96 | } 97 | } 98 | ] 99 | }, 100 | { 101 | test: /\.less$/, 102 | include: /node_modules/, 103 | use: [ 104 | 'style-loader', 105 | { 106 | loader: 'css-loader', 107 | options: { 108 | importLoaders: 1 109 | } 110 | }, 111 | { 112 | loader: 'postcss-loader', 113 | options: { 114 | plugins: POSTCSS.plugins, 115 | sourceMap: isDebug 116 | } 117 | }, 118 | { 119 | loader: 'less-loader', 120 | options: { 121 | javascriptEnabled: true 122 | } 123 | } 124 | ] 125 | }, 126 | { 127 | test: /\.css$/, 128 | use: [ 129 | 'style-loader', 130 | { 131 | loader: 'css-loader', 132 | options: { 133 | importLoaders: 1 134 | } 135 | }, 136 | { 137 | loader: 'postcss-loader', 138 | options: { 139 | plugins: POSTCSS.plugins, 140 | sourceMap: isDebug 141 | } 142 | } 143 | ] 144 | }, 145 | { 146 | test: /\.(jpe?g|png|gif|svg)$/i, 147 | use: [ 148 | 'file-loader?hash=sha512&digest=hex&limit=8192&name=assets/[name].[hash:8].[ext]', 149 | { 150 | loader: 'image-webpack-loader', 151 | options: { 152 | optipng: { 153 | optimizationLevel: 7 154 | }, 155 | gifsicle: { 156 | interlaced: false 157 | }, 158 | pngquant: { 159 | quality: '65-90', 160 | speed: 4 161 | }, 162 | mozjpeg: { 163 | quality: 65, 164 | progressive: true 165 | } 166 | } 167 | } 168 | ] 169 | }, 170 | { 171 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 172 | use: 'url-loader??prefix=fonts/name=assets/[name].[hash:8].[ext]&limit=10000&mimetype=application/font-woff' 173 | }, 174 | { 175 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 176 | use: 'file-loader?prefix=fonts/&name=assets/[name].[hash:8].[ext]&limit=10000&mimetype=font/opentype' 177 | } 178 | ] 179 | }, 180 | optimization: { 181 | runtimeChunk: { 182 | name: 'manifest' 183 | }, 184 | splitChunks: { 185 | chunks: 'async', 186 | minSize: 30000, 187 | minChunks: 1, 188 | maxAsyncRequests: 5, 189 | maxInitialRequests: 3, 190 | name: false, 191 | cacheGroups: { 192 | vendor: { 193 | name: 'vendor', 194 | chunks: 'initial', 195 | priority: -10, 196 | reuseExistingChunk: false, 197 | test: /node_modules\/(.*)\.js/ 198 | } 199 | } 200 | } 201 | }, 202 | performance: { 203 | hints: false 204 | }, 205 | 206 | plugins: [ 207 | new webpack.DefinePlugin({ 208 | 'process.env': { 209 | NODE_ENV: JSON.stringify('development') 210 | }, 211 | __DEV__: process.env.NODE_ENV === 'development' 212 | }), 213 | 214 | new webpack.NamedModulesPlugin(), 215 | 216 | new webpack.HotModuleReplacementPlugin(), 217 | 218 | new HtmlWebpackPlugin({ 219 | hash: false, 220 | template: './src/index.html' 221 | }), 222 | 223 | new webpack.DllReferencePlugin({ 224 | context: __dirname, 225 | manifest: require('./public/lib/debug/manifest.json') 226 | }), 227 | 228 | new AddAssetHtmlPlugin([ 229 | { 230 | filepath: path.resolve(__dirname, './public/lib/debug/lib.js'), 231 | outputPath: 'lib/debug', 232 | publicPath: '/lib/debug', 233 | includeSourcemap: true 234 | } 235 | ]), 236 | 237 | new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /nb/) 238 | ] 239 | }; 240 | 241 | module.exports = baseConfig; 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webpack4升级完全指南 2 | 3 | > webpack4官方已经于近日升级到了V4.5的稳定版本,对应的一些必备插件(webpack-contrib)也陆续完成了更新支持,笔者在第一时间完成了项目由V3到V4的迁移,在此记录一下升级过程中遇到的种种问题和对应的解决手段,方便后续入坑者及时查阅,减少重复工作。 4 | 5 | ## 一、Node版本依赖重新调整 6 | 7 | 官方不再支持node4以下的版本,依赖node的环境版本>=6.11.5,当然考虑到最佳的es6特性实现,建议node版本可以升级到V8.9.4或以上版本,具体更新说明部分可以见:[webpack4更新日志](https://github.com/webpack/webpack/releases/tag/v4.0.0) 8 | 9 | ```javascript 10 | "engines": { 11 | "node": ">=6.11.5" // >=8.10.0 (recommendation version) 12 | } 13 | ``` 14 | 15 | 16 | ## 二、用更加快捷的mode模式来优化配置文件 17 | 18 | webpack4中提供的mode有两个值:development和production,默认值是 production。mode是我们为减小生产环境构建体积以及节约开发环境的构建时间提供的一种优化方案,提供对应的构建参数项的默认开启或关闭,降低配置成本。 19 | 20 | ### 开启方式 1:直接在启动命令后加入参数 21 | 22 | ```javascript 23 | "scripts": { 24 | "dev": "webpack --mode development", 25 | "build": "webpack --mode production" 26 | } 27 | ``` 28 | 29 | ### 开启方式 2:可以在配置文件中加入一个mode属性: 30 | 31 | ```javascript 32 | module.exports = { 33 | mode: 'production' // development 34 | }; 35 | ``` 36 | 37 | ### development模式下,将侧重于功能调试和优化开发体验,包含如下内容: 38 | > 1. 浏览器调试工具 39 | > 2. 注释、开发阶段的详细错误日志和提示 40 | > 3. 快速和优化的增量构建机制 41 | 42 | ### production模式下,将侧重于模块体积优化和线上部署,包含如下内容: 43 | > 1. 开启所有的优化代码 44 | > 2. 更小的bundle大小 45 | > 3. 去除掉只在开发阶段运行的代码 46 | > 4. Scope hoisting和Tree-shaking 47 | > 5. 自动启用uglifyjs对代码进行压缩 48 | 49 | webpack一直以来最饱受诟病的就是其配置门槛极高,配置内容复杂而繁琐,容易让人从入门到放弃,而它的后起之秀如rollup,parcel等均在配置流程上做了极大的优化,做到开箱即用,webpack在V4中应该也从中借鉴了不少经验来提升自身的配置效率,详见内容可以参考这篇文章[《webpack 4: mode and optimization》](https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a) 50 | 51 | 52 | ## 三、再见commonchunk,你好optimization 53 | 54 | 从webpack4开始官方移除了commonchunk插件,改用了optimization属性进行更加灵活的配置,这也应该是从V3升级到V4的代码修改过程中最为复杂的一部分,下面的代码即是optimize.splitChunks 中的一些配置参考, 55 | 56 | ```javascript 57 | module.exports = { 58 | optimization: { 59 | runtimeChunk: { 60 | name: 'manifest' 61 | }, 62 | minimizer: true, // [new UglifyJsPlugin({...})] 63 | splitChunks:{ 64 | chunks: 'async', 65 | minSize: 30000, 66 | minChunks: 1, 67 | maxAsyncRequests: 5, 68 | maxInitialRequests: 3, 69 | name: false, 70 | cacheGroups: { 71 | vendor: { 72 | name: 'vendor', 73 | chunks: 'initial', 74 | priority: -10, 75 | reuseExistingChunk: false, 76 | test: /node_modules\/(.*)\.js/ 77 | }, 78 | styles: { 79 | name: 'styles', 80 | test: /\.(scss|css)$/, 81 | chunks: 'all', 82 | minChunks: 1, 83 | reuseExistingChunk: true, 84 | enforce: true 85 | } 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### 从中我们不难发现,其主要变化有如下几个方面: 93 | 94 | > 1. commonchunk配置项被彻底去掉,之前需要通过配置两次new webpack.optimize.CommonsChunkPlugin来分别获取vendor和manifest的通用chunk方式已经做了整合,** 直接在optimization中配置runtimeChunk和splitChunks即可 ** ,提取功能也更为强大,具体配置见:[splitChunks](https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks) 95 | 96 | >1. runtimeChunk可以配置成true,single或者对象,用自动计算当前构建的一些基础chunk信息,类似之前版本中的manifest信息获取方式。 97 | 98 | >1. webpack.optimize.UglifyJsPlugin现在也不需要了,只需要使用optimization.minimize为true就行,production mode下面自动为true,当然如果想使用第三方的压缩插件也可以在optimization.minimizer的数组列表中进行配置 99 | 100 | ## 四、ExtractTextWebpackPlugin调整,建议选用新的CSS文件提取插件mini-css-extract-plugin 101 | 102 | 由于webpack4以后对css模块支持的逐步完善和commonchunk插件的移除,在处理css文件提取的计算方式上也做了些调整,之前我们首选使用的[extract-text-webpack-plugin](https://github.com/webpack-contrib/extract-text-webpack-plugin)也完成了其历史使命,将让位于[mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) 103 | 104 | ### 基本配置如下: 105 | 106 | ```javascript 107 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 108 | module.exports = { 109 | plugins: [ 110 | new MiniCssExtractPlugin({ 111 | // Options similar to the same options in webpackOptions.output 112 | // both options are optional 113 | filename: "[name].css", 114 | chunkFilename: "[id].css" 115 | }) 116 | ], 117 | module: { 118 | rules: [ 119 | { 120 | test: /\.css$/, 121 | use: [ 122 | MiniCssExtractPlugin.loader, // replace ExtractTextPlugin.extract({..}) 123 | "css-loader" 124 | ] 125 | } 126 | ] 127 | } 128 | } 129 | ``` 130 | 131 | ### 生产环境下的配置优化: 132 | 133 | ```javascript 134 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); 135 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 136 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 137 | module.exports = { 138 | optimization: { 139 | minimizer: [ 140 | new UglifyJsPlugin({ 141 | cache: true, 142 | parallel: true, 143 | sourceMap: true 144 | }), 145 | new OptimizeCSSAssetsPlugin({ 146 | assetNameRegExp: /\.css$/g, 147 | cssProcessor: require('cssnano'), 148 | cssProcessorOptions: { 149 | autoprefixer: false, 150 | preset: [ 151 | 'default', 152 | { 153 | discardComments: { 154 | removeAll: true 155 | } 156 | } 157 | ] 158 | } 159 | }) // use OptimizeCSSAssetsPlugin 160 | ] 161 | }, 162 | plugins: [ 163 | new MiniCssExtractPlugin({ 164 | filename: 'css/app.[name].css', 165 | chunkFilename: 'css/app.[contenthash:12].css' // use contenthash * 166 | }) 167 | ] 168 | } 169 | 170 | ``` 171 | 172 | ### 将多个css chunk合并成一个css文件 173 | 174 | ```javascript 175 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 176 | module.exports = { 177 | optimization: { 178 | splitChunks: { 179 | cacheGroups: { 180 | styles: { 181 | name: 'styles', 182 | test: /\.scss|css$/, 183 | chunks: 'all', // merge all the css chunk to one file 184 | enforce: true 185 | } 186 | } 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | 193 | ## 五、其他调整项备忘 194 | 195 | > 1. NoEmitOnErrorsPlugin- > optimization.noEmitOnErrors(默认情况下处于生产模式) 196 | > 2. ModuleConcatenationPlugin- > optimization.concatenateModules(默认情况下处于生产模式) 197 | > 3. NamedModulesPlugin- > optimization.namedModules(在开发模式下默认开启) 198 | > 4. CommonsChunkPlugin 被删除 - > optimization.splitChunks 199 | > 5. webpack命令优化 -> 发布了独立的 [webpack-cli](https://webpack.js.org/api/cli/) 命令行工具包 200 | > 6. webpack-dev-server -> 建议升级到最新版本 201 | > 7. html-webpack-plugin -> 建议升级到的最新版本 202 | > 8. file-loader -> 建议升级到最新版本 203 | > 9. url-loader -> 建议升级到最新版本 204 | 205 | ## 六、参考工程 206 | [webpack4配置工程实例](https://github.com/taikongfeizhu/react-mobx-react-router4-boilerplate) 207 | 208 | ## 七、参阅资料 209 | 210 | 1. [webpack4](https://blog.csdn.net/qq_20334295/article/details/79401231) 211 | 2. [webpack4发布概览](https://zhuanlan.zhihu.com/p/34028750) 212 | 3. [webpack 4: mode and optimization](https://medium.com/webpack/webpack-4-mode-and-optimization-5423a6bc597a) 213 | 4. [webpack4新特性介绍](https://segmentfault.com/a/1190000013970017) 214 | 5. [webpack4升级指北](https://segmentfault.com/a/1190000013420383) 215 | 6. [webpack4升级指南以及从webpack3.x迁移](https://blog.csdn.net/qq_26733915/article/details/79446460) 216 | -------------------------------------------------------------------------------- /public/lib/min/manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"lib","content":{"./node_modules/prop-types/index.js":{"id":0,"buildMeta":{"providedExports":true}},"./node_modules/react/index.js":{"id":1,"buildMeta":{"providedExports":true}},"./node_modules/history/es/PathUtils.js":{"id":2,"buildMeta":{"exportsType":"namespace","providedExports":["addLeadingSlash","stripLeadingSlash","hasBasename","stripBasename","stripTrailingSlash","parsePath","createPath"]}},"./node_modules/invariant/browser.js":{"id":3,"buildMeta":{"providedExports":true}},"./node_modules/history/es/index.js":{"id":4,"buildMeta":{"exportsType":"namespace","providedExports":["createBrowserHistory","createHashHistory","createMemoryHistory","createLocation","locationsAreEqual","parsePath","createPath"]}},"./node_modules/warning/browser.js":{"id":5,"buildMeta":{"providedExports":true}},"./node_modules/react-router/node_modules/warning/warning.js":{"id":6,"buildMeta":{"providedExports":true}},"./node_modules/history/es/LocationUtils.js":{"id":7,"buildMeta":{"exportsType":"namespace","providedExports":["createLocation","locationsAreEqual"]}},"./node_modules/mobx/lib/mobx.module.js":{"id":8,"buildMeta":{"exportsType":"namespace","providedExports":["Reaction","untracked","IDerivationState","createAtom","spy","comparer","isObservableObject","isBoxedObservable","isObservableArray","ObservableMap","isObservableMap","transaction","observable","computed","isObservable","isObservableProp","isComputed","isComputedProp","extendObservable","observe","intercept","autorun","reaction","when","action","isAction","runInAction","keys","values","entries","set","remove","has","get","decorate","configure","onBecomeObserved","onBecomeUnobserved","flow","toJS","trace","getDependencyTree","getObserverTree","_resetGlobalState","_getGlobalState","getDebugName","getAtom","_getAdministration","_allowStateChanges","_allowStateChangesInsideComputed","isArrayLike","$mobx","_isComputingDerivation","onReactionError","_interceptReads"]}},"./node_modules/history/es/DOMUtils.js":{"id":9,"buildMeta":{"exportsType":"namespace","providedExports":["canUseDOM","addEventListener","removeEventListener","getConfirmation","supportsHistory","supportsPopStateOnHashChange","supportsGoWithoutReloadUsingHash","isExtraneousPopstateEvent"]}},"./node_modules/axios/lib/utils.js":{"id":10,"buildMeta":{"providedExports":true}},"./node_modules/react-router/es/Router.js":{"id":11,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/matchPath.js":{"id":12,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/generatePath.js":{"id":13,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Route.js":{"id":14,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Router.js":{"id":15,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/createTransitionManager.js":{"id":16,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-dom/index.js":{"id":17,"buildMeta":{"providedExports":true}},"./node_modules/react-router-dom/es/Link.js":{"id":18,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/MemoryRouter.js":{"id":19,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Prompt.js":{"id":20,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Redirect.js":{"id":21,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/StaticRouter.js":{"id":22,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Switch.js":{"id":23,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/withRouter.js":{"id":24,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Route.js":{"id":25,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/path-to-regexp/index.js":{"id":26,"buildMeta":{"providedExports":true}},"./node_modules/react-router-dom/node_modules/warning/warning.js":{"id":27,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/defaults.js":{"id":28,"buildMeta":{"providedExports":true}},"./node_modules/object-assign/index.js":{"id":29,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/global.js":{"id":30,"buildMeta":{"providedExports":true}},"./node_modules/history/es/createBrowserHistory.js":{"id":31,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/createHashHistory.js":{"id":32,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/createMemoryHistory.js":{"id":33,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/BrowserRouter.js":{"id":34,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/HashRouter.js":{"id":35,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/MemoryRouter.js":{"id":36,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/NavLink.js":{"id":37,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Prompt.js":{"id":38,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Redirect.js":{"id":39,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/StaticRouter.js":{"id":40,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Switch.js":{"id":41,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/generatePath.js":{"id":42,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/matchPath.js":{"id":43,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/withRouter.js":{"id":44,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/node-libs-browser/node_modules/process/browser.js":{"id":45,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/bind.js":{"id":46,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/adapters/xhr.js":{"id":47,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/createError.js":{"id":48,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/isCancel.js":{"id":49,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/Cancel.js":{"id":50,"buildMeta":{"providedExports":true}},"./node_modules/resolve-pathname/index.js":{"id":51,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/value-equal/index.js":{"id":52,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js":{"id":53,"buildMeta":{"providedExports":true}},"./node_modules/react/cjs/react.production.min.js":{"id":55,"buildMeta":{"providedExports":true}},"./node_modules/react-dom/cjs/react-dom.production.min.js":{"id":56,"buildMeta":{"providedExports":true}},"./node_modules/scheduler/index.js":{"id":57,"buildMeta":{"providedExports":true}},"./node_modules/scheduler/cjs/scheduler.production.min.js":{"id":58,"buildMeta":{"providedExports":true}},"./node_modules/react-router/es/index.js":{"id":59,"buildMeta":{"exportsType":"namespace","providedExports":["MemoryRouter","Prompt","Redirect","Route","Router","StaticRouter","Switch","generatePath","matchPath","withRouter"]}},"./node_modules/prop-types/factoryWithThrowingShims.js":{"id":60,"buildMeta":{"providedExports":true}},"./node_modules/prop-types/lib/ReactPropTypesSecret.js":{"id":61,"buildMeta":{"providedExports":true}},"./node_modules/path-to-regexp/node_modules/isarray/index.js":{"id":62,"buildMeta":{"providedExports":true}},"./node_modules/react-router-dom/es/index.js":{"id":63,"buildMeta":{"exportsType":"namespace","providedExports":["BrowserRouter","HashRouter","Link","MemoryRouter","NavLink","Prompt","Redirect","Route","Router","StaticRouter","Switch","generatePath","matchPath","withRouter"]}},"./node_modules/mobx-react/index.module.js":{"id":64,"buildMeta":{"exportsType":"namespace","providedExports":["propTypes","PropTypes","onError","observer","Observer","renderReporter","componentByNodeRegistery","componentByNodeRegistry","trackComponents","useStaticRendering","Provider","inject","disposeOnUnmount"]}},"./node_modules/mobx-react-router/dist/mobx-react-router.js":{"id":65,"buildMeta":{"providedExports":true}},"./node_modules/axios/index.js":{"id":66,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/axios.js":{"id":67,"buildMeta":{"providedExports":true}},"./node_modules/is-buffer/index.js":{"id":68,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/Axios.js":{"id":69,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/normalizeHeaderName.js":{"id":70,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/settle.js":{"id":71,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/enhanceError.js":{"id":72,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/buildURL.js":{"id":73,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/parseHeaders.js":{"id":74,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isURLSameOrigin.js":{"id":75,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/btoa.js":{"id":76,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/cookies.js":{"id":77,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/InterceptorManager.js":{"id":78,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/dispatchRequest.js":{"id":79,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/transformData.js":{"id":80,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isAbsoluteURL.js":{"id":81,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/combineURLs.js":{"id":82,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/CancelToken.js":{"id":83,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/spread.js":{"id":84,"buildMeta":{"providedExports":true}},"./node_modules/classnames/index.js":{"id":85,"buildMeta":{"providedExports":true}}}} -------------------------------------------------------------------------------- /public/lib/debug/manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"lib","content":{"./node_modules/react-router-dom/node_modules/warning/warning.js":{"id":"./node_modules/react-router-dom/node_modules/warning/warning.js","buildMeta":{"providedExports":true}},"./node_modules/react/cjs/react.development.js":{"id":"./node_modules/react/cjs/react.development.js","buildMeta":{"providedExports":true}},"./node_modules/object-assign/index.js":{"id":"./node_modules/object-assign/index.js","buildMeta":{"providedExports":true}},"./node_modules/prop-types/checkPropTypes.js":{"id":"./node_modules/prop-types/checkPropTypes.js","buildMeta":{"providedExports":true}},"./node_modules/prop-types/lib/ReactPropTypesSecret.js":{"id":"./node_modules/prop-types/lib/ReactPropTypesSecret.js","buildMeta":{"providedExports":true}},"./node_modules/react-dom/index.js":{"id":"./node_modules/react-dom/index.js","buildMeta":{"providedExports":true}},"./node_modules/react-dom/cjs/react-dom.development.js":{"id":"./node_modules/react-dom/cjs/react-dom.development.js","buildMeta":{"providedExports":true}},"./node_modules/scheduler/index.js":{"id":"./node_modules/scheduler/index.js","buildMeta":{"providedExports":true}},"./node_modules/scheduler/cjs/scheduler.development.js":{"id":"./node_modules/scheduler/cjs/scheduler.development.js","buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/global.js":{"id":"./node_modules/webpack/buildin/global.js","buildMeta":{"providedExports":true}},"./node_modules/scheduler/tracing.js":{"id":"./node_modules/scheduler/tracing.js","buildMeta":{"providedExports":true}},"./node_modules/scheduler/cjs/scheduler-tracing.development.js":{"id":"./node_modules/scheduler/cjs/scheduler-tracing.development.js","buildMeta":{"providedExports":true}},"./node_modules/react-router/es/index.js":{"id":"./node_modules/react-router/es/index.js","buildMeta":{"exportsType":"namespace","providedExports":["MemoryRouter","Prompt","Redirect","Route","Router","StaticRouter","Switch","generatePath","matchPath","withRouter"]}},"./node_modules/react-router/es/MemoryRouter.js":{"id":"./node_modules/react-router/es/MemoryRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/node_modules/warning/warning.js":{"id":"./node_modules/react-router/node_modules/warning/warning.js","buildMeta":{"providedExports":true}},"./node_modules/prop-types/index.js":{"id":"./node_modules/prop-types/index.js","buildMeta":{"providedExports":true}},"./node_modules/prop-types/factoryWithTypeCheckers.js":{"id":"./node_modules/prop-types/factoryWithTypeCheckers.js","buildMeta":{"providedExports":true}},"./node_modules/history/es/index.js":{"id":"./node_modules/history/es/index.js","buildMeta":{"exportsType":"namespace","providedExports":["createBrowserHistory","createHashHistory","createMemoryHistory","createLocation","locationsAreEqual","parsePath","createPath"]}},"./node_modules/history/es/createBrowserHistory.js":{"id":"./node_modules/history/es/createBrowserHistory.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/warning/browser.js":{"id":"./node_modules/warning/browser.js","buildMeta":{"providedExports":true}},"./node_modules/invariant/browser.js":{"id":"./node_modules/invariant/browser.js","buildMeta":{"providedExports":true}},"./node_modules/history/es/LocationUtils.js":{"id":"./node_modules/history/es/LocationUtils.js","buildMeta":{"exportsType":"namespace","providedExports":["createLocation","locationsAreEqual"]}},"./node_modules/resolve-pathname/index.js":{"id":"./node_modules/resolve-pathname/index.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/value-equal/index.js":{"id":"./node_modules/value-equal/index.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/PathUtils.js":{"id":"./node_modules/history/es/PathUtils.js","buildMeta":{"exportsType":"namespace","providedExports":["addLeadingSlash","stripLeadingSlash","hasBasename","stripBasename","stripTrailingSlash","parsePath","createPath"]}},"./node_modules/history/es/createTransitionManager.js":{"id":"./node_modules/history/es/createTransitionManager.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/DOMUtils.js":{"id":"./node_modules/history/es/DOMUtils.js","buildMeta":{"exportsType":"namespace","providedExports":["canUseDOM","addEventListener","removeEventListener","getConfirmation","supportsHistory","supportsPopStateOnHashChange","supportsGoWithoutReloadUsingHash","isExtraneousPopstateEvent"]}},"./node_modules/history/es/createHashHistory.js":{"id":"./node_modules/history/es/createHashHistory.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/history/es/createMemoryHistory.js":{"id":"./node_modules/history/es/createMemoryHistory.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Router.js":{"id":"./node_modules/react-router/es/Router.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Prompt.js":{"id":"./node_modules/react-router/es/Prompt.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Redirect.js":{"id":"./node_modules/react-router/es/Redirect.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/generatePath.js":{"id":"./node_modules/react-router/es/generatePath.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/path-to-regexp/index.js":{"id":"./node_modules/path-to-regexp/index.js","buildMeta":{"providedExports":true}},"./node_modules/path-to-regexp/node_modules/isarray/index.js":{"id":"./node_modules/path-to-regexp/node_modules/isarray/index.js","buildMeta":{"providedExports":true}},"./node_modules/react-router/es/Route.js":{"id":"./node_modules/react-router/es/Route.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/matchPath.js":{"id":"./node_modules/react-router/es/matchPath.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/StaticRouter.js":{"id":"./node_modules/react-router/es/StaticRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/Switch.js":{"id":"./node_modules/react-router/es/Switch.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router/es/withRouter.js":{"id":"./node_modules/react-router/es/withRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js":{"id":"./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js","buildMeta":{"providedExports":true}},"./node_modules/react-router-dom/es/index.js":{"id":"./node_modules/react-router-dom/es/index.js","buildMeta":{"exportsType":"namespace","providedExports":["BrowserRouter","HashRouter","Link","MemoryRouter","NavLink","Prompt","Redirect","Route","Router","StaticRouter","Switch","generatePath","matchPath","withRouter"]}},"./node_modules/react-router-dom/es/BrowserRouter.js":{"id":"./node_modules/react-router-dom/es/BrowserRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react/index.js":{"id":"./node_modules/react/index.js","buildMeta":{"providedExports":true}},"./node_modules/react-router-dom/es/Router.js":{"id":"./node_modules/react-router-dom/es/Router.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/HashRouter.js":{"id":"./node_modules/react-router-dom/es/HashRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Link.js":{"id":"./node_modules/react-router-dom/es/Link.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/MemoryRouter.js":{"id":"./node_modules/react-router-dom/es/MemoryRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/NavLink.js":{"id":"./node_modules/react-router-dom/es/NavLink.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Route.js":{"id":"./node_modules/react-router-dom/es/Route.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Prompt.js":{"id":"./node_modules/react-router-dom/es/Prompt.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Redirect.js":{"id":"./node_modules/react-router-dom/es/Redirect.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/StaticRouter.js":{"id":"./node_modules/react-router-dom/es/StaticRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/Switch.js":{"id":"./node_modules/react-router-dom/es/Switch.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/generatePath.js":{"id":"./node_modules/react-router-dom/es/generatePath.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/matchPath.js":{"id":"./node_modules/react-router-dom/es/matchPath.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/react-router-dom/es/withRouter.js":{"id":"./node_modules/react-router-dom/es/withRouter.js","buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/mobx/lib/mobx.module.js":{"id":"./node_modules/mobx/lib/mobx.module.js","buildMeta":{"exportsType":"namespace","providedExports":["Reaction","untracked","IDerivationState","createAtom","spy","comparer","isObservableObject","isBoxedObservable","isObservableArray","ObservableMap","isObservableMap","transaction","observable","computed","isObservable","isObservableProp","isComputed","isComputedProp","extendObservable","observe","intercept","autorun","reaction","when","action","isAction","runInAction","keys","values","entries","set","remove","has","get","decorate","configure","onBecomeObserved","onBecomeUnobserved","flow","toJS","trace","getDependencyTree","getObserverTree","_resetGlobalState","_getGlobalState","getDebugName","getAtom","_getAdministration","_allowStateChanges","_allowStateChangesInsideComputed","isArrayLike","$mobx","_isComputingDerivation","onReactionError","_interceptReads"]}},"./node_modules/node-libs-browser/node_modules/process/browser.js":{"id":"./node_modules/node-libs-browser/node_modules/process/browser.js","buildMeta":{"providedExports":true}},"./node_modules/mobx-react/index.module.js":{"id":"./node_modules/mobx-react/index.module.js","buildMeta":{"exportsType":"namespace","providedExports":["propTypes","PropTypes","onError","observer","Observer","renderReporter","componentByNodeRegistery","componentByNodeRegistry","trackComponents","useStaticRendering","Provider","inject","disposeOnUnmount"]}},"./node_modules/mobx-react-router/dist/mobx-react-router.js":{"id":"./node_modules/mobx-react-router/dist/mobx-react-router.js","buildMeta":{"providedExports":true}},"./node_modules/axios/index.js":{"id":"./node_modules/axios/index.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/axios.js":{"id":"./node_modules/axios/lib/axios.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/utils.js":{"id":"./node_modules/axios/lib/utils.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/bind.js":{"id":"./node_modules/axios/lib/helpers/bind.js","buildMeta":{"providedExports":true}},"./node_modules/is-buffer/index.js":{"id":"./node_modules/is-buffer/index.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/Axios.js":{"id":"./node_modules/axios/lib/core/Axios.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/defaults.js":{"id":"./node_modules/axios/lib/defaults.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/normalizeHeaderName.js":{"id":"./node_modules/axios/lib/helpers/normalizeHeaderName.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/adapters/xhr.js":{"id":"./node_modules/axios/lib/adapters/xhr.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/settle.js":{"id":"./node_modules/axios/lib/core/settle.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/createError.js":{"id":"./node_modules/axios/lib/core/createError.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/enhanceError.js":{"id":"./node_modules/axios/lib/core/enhanceError.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/buildURL.js":{"id":"./node_modules/axios/lib/helpers/buildURL.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/parseHeaders.js":{"id":"./node_modules/axios/lib/helpers/parseHeaders.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isURLSameOrigin.js":{"id":"./node_modules/axios/lib/helpers/isURLSameOrigin.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/btoa.js":{"id":"./node_modules/axios/lib/helpers/btoa.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/cookies.js":{"id":"./node_modules/axios/lib/helpers/cookies.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/InterceptorManager.js":{"id":"./node_modules/axios/lib/core/InterceptorManager.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/dispatchRequest.js":{"id":"./node_modules/axios/lib/core/dispatchRequest.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/transformData.js":{"id":"./node_modules/axios/lib/core/transformData.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/isCancel.js":{"id":"./node_modules/axios/lib/cancel/isCancel.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isAbsoluteURL.js":{"id":"./node_modules/axios/lib/helpers/isAbsoluteURL.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/combineURLs.js":{"id":"./node_modules/axios/lib/helpers/combineURLs.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/Cancel.js":{"id":"./node_modules/axios/lib/cancel/Cancel.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/CancelToken.js":{"id":"./node_modules/axios/lib/cancel/CancelToken.js","buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/spread.js":{"id":"./node_modules/axios/lib/helpers/spread.js","buildMeta":{"providedExports":true}},"./node_modules/classnames/index.js":{"id":"./node_modules/classnames/index.js","buildMeta":{"providedExports":true}}}} --------------------------------------------------------------------------------