├── dev-public
├── favicon.ico
└── index.html
├── src
├── containers
│ ├── Test
│ │ ├── test.less
│ │ ├── store
│ │ │ ├── index.ts
│ │ │ ├── Test.service.ts
│ │ │ ├── Test.reducer.ts
│ │ │ └── Test.action.ts
│ │ └── test.tsx
│ ├── Home
│ │ ├── style.less
│ │ ├── store
│ │ │ ├── index.ts
│ │ │ ├── Home.service.ts
│ │ │ ├── Home.reducer.ts
│ │ │ └── Home.action.ts
│ │ └── index.tsx
│ ├── app.less
│ └── app.tsx
├── static
│ ├── img
│ │ └── timg.jpg
│ └── fonts
│ │ ├── icomoon.eot
│ │ ├── icomoon.ttf
│ │ ├── icomoon.woff
│ │ └── icomoon.svg
├── components
│ └── Header
│ │ ├── header.less
│ │ └── index.tsx
├── service
│ └── file.ts
├── saga
│ └── index.ts
├── reducer
│ └── index.ts
├── routes.ts
├── store.js
├── server
│ ├── render.js
│ └── index.js
├── client
│ └── index.tsx
├── common
│ └── icon-font.css
└── routes.tsx
├── .gitignore
├── .babelrc
├── .editorconfig
├── tslint.json
├── configs
├── webpack.config.js
├── webpack.config.server.js
├── webpack.config.bak.js
└── webpack.config.client.js
├── package.json
└── tsconfig.json
/dev-public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neroneroffy/react-music/HEAD/dev-public/favicon.ico
--------------------------------------------------------------------------------
/src/containers/Test/test.less:
--------------------------------------------------------------------------------
1 | .a {
2 | width: 200px;
3 | height: 200px;
4 | background: red;
5 | }
--------------------------------------------------------------------------------
/src/static/img/timg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neroneroffy/react-music/HEAD/src/static/img/timg.jpg
--------------------------------------------------------------------------------
/src/containers/Home/style.less:
--------------------------------------------------------------------------------
1 | .home {
2 | color: #d24747;
3 | span {
4 | color: green;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Header/header.less:
--------------------------------------------------------------------------------
1 | .header {
2 | color: #fff;
3 | }
4 | .headerInner {
5 | color: aqua;
6 | }
7 |
--------------------------------------------------------------------------------
/src/static/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neroneroffy/react-music/HEAD/src/static/fonts/icomoon.eot
--------------------------------------------------------------------------------
/src/static/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neroneroffy/react-music/HEAD/src/static/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/src/static/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neroneroffy/react-music/HEAD/src/static/fonts/icomoon.woff
--------------------------------------------------------------------------------
/src/service/file.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios/index';
2 | export function getFile() {
3 | return axios.get('/data.json')
4 | }
5 |
--------------------------------------------------------------------------------
/src/containers/app.less:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0
4 | }
5 | .app {
6 | & > span {
7 | color: #d24747;
8 | }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/containers/Test/store/index.ts:
--------------------------------------------------------------------------------
1 | import { takeLatest } from 'redux-saga/effects'
2 | import { readFile } from './Test.service'
3 | import { READ_FILE } from './Test.action';
4 |
5 | export default function* testSaga() {
6 | yield [
7 | takeLatest(READ_FILE, readFile),
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/saga/index.ts:
--------------------------------------------------------------------------------
1 | import { fork } from 'redux-saga/effects'
2 | import testSaga from '../containers/Test/store/index'
3 | import homeSaga from '../containers/Home/store/index'
4 |
5 | export default function* rootSaga() {
6 | yield [
7 | testSaga(),
8 | homeSaga(),
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/reducer/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import fileContent from '../containers/Test/store/Test.reducer'
3 | import home from '../containers/Home/store/Home.reducer'
4 |
5 | const rootReducer = combineReducers({
6 | fileContent,
7 | home,
8 | })
9 | export default rootReducer
10 |
--------------------------------------------------------------------------------
/src/containers/Home/store/index.ts:
--------------------------------------------------------------------------------
1 | import { takeLatest } from 'redux-saga/effects'
2 | import { fetchHomeData } from './Home.service'
3 | import { GET_HOME_DATA } from './Home.action';
4 |
5 | export default function* homeSaga() {
6 | yield [
7 | takeLatest(GET_HOME_DATA, fetchHomeData),
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/dev-public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/routes.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Home from './containers/Home/index'
3 | import Test from './containers/Test/test'
4 |
5 | import { Redirect } from 'react-router-dom'
6 |
7 | export default [
8 | {
9 | path: '/',
10 | component: Home,
11 | exact: true,
12 | },
13 | {
14 | path: '/test',
15 | component: Test,
16 | exact: true,
17 | },
18 | ]
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 | .idea
3 | /dev-public
4 |
5 | # dependencies
6 | /node_modules
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /public
14 |
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | package-lock.json
27 |
--------------------------------------------------------------------------------
/src/containers/Test/store/Test.service.ts:
--------------------------------------------------------------------------------
1 | import { select, put, call } from 'redux-saga/effects'
2 | import { getFile } from '../../../service/file'
3 | import { readFileFailure, readFileSuccess, readFileComplete } from './Test.action'
4 |
5 | export function* readFile() {
6 | try {
7 | const file = yield call(getFile)
8 | yield put(readFileSuccess(file.data))
9 | } catch {
10 | yield put(readFileFailure())
11 | } finally {
12 | yield put(readFileComplete())
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [ "es2015", {
4 | "loose": true
5 | }],
6 | "stage-1",
7 | "react"
8 | ],
9 | "plugins": [
10 | "transform-decorators-legacy",
11 | "react-hot-loader/babel",
12 | [
13 | "transform-runtime",
14 | {
15 | "helpers": false,
16 | "polyfill": false,
17 | "regenerator": true,
18 | "moduleName": "babel-runtime"
19 | }
20 | ],
21 | ["import", { "libraryName": "antd-mobile", "style": true }]
22 | ]
23 | }
--------------------------------------------------------------------------------
/src/containers/Home/store/Home.service.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios/index';
2 | import { select, put, call } from 'redux-saga/effects'
3 | import { getHomeSuccess, getHomeFailure } from './Home.action'
4 | const getHomeData = () => axios.get('https://randomuser.me/api/')
5 |
6 | export function* fetchHomeData() {
7 | try {
8 | const data = yield call(getHomeData)
9 | yield put(getHomeSuccess(data.data))
10 | } catch {
11 | yield put(getHomeFailure())
12 | } finally {
13 | //
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/containers/Test/store/Test.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as Actions from './Test.action'
2 | const initialState = {
3 | name: '',
4 | }
5 | export default function fileContent(state = initialState, action: any) {
6 | switch (action.type) {
7 | case Actions.READ_FILE:
8 | return {
9 | ...state,
10 | ...action.payload,
11 | }
12 | case Actions.READ_FILE_SUCCESS:
13 | return {
14 | ...state,
15 | ...action.payload,
16 | }
17 | default:
18 | return state
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import styles from './header.less'
4 |
5 | interface HeaderProps {
6 | menus: any
7 | }
8 | class Header extends React.Component {
9 | componentDidMount() {
10 | // do
11 | }
12 |
13 | render() {
14 | return (
15 |
16 | 首页
17 | 测sdadas
18 |
19 |
)
20 | }
21 | }
22 |
23 | export default Header
24 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset=utf-8
3 | end_of_line=lf
4 | insert_final_newline=true
5 | indent_style=space
6 | indent_size=4
7 |
8 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
9 | indent_style=space
10 | indent_size=2
11 |
12 | [*.js]
13 | indent_style=space
14 | indent_size=2
15 |
16 | [{tsconfig.app.json,tsconfig.e2e.json,tsconfig.json,tsconfig.spec.json}]
17 | indent_style=space
18 | indent_size=2
19 |
20 | [*.less]
21 | indent_style=space
22 | indent_size=2
23 |
24 | [{.analysis_options,*.yml,*.yaml}]
25 | indent_style=space
26 | indent_size=2
27 |
28 |
--------------------------------------------------------------------------------
/src/containers/Test/store/Test.action.ts:
--------------------------------------------------------------------------------
1 | export const READ_FILE = 'READ_FILE'
2 | export const READ_FILE_SUCCESS = 'READ_FILE_SUCCESS'
3 | export const READ_FILE_FAIL = 'READ_FILE_FAIL'
4 | export const READ_FILE_COMPLETE = 'READ_FILE_COMPLETE'
5 |
6 | export const readFileSuccess = (data: object) => {
7 | return {
8 | type: READ_FILE_SUCCESS,
9 | payload: data,
10 | }
11 | }
12 | export const readFileFailure = () => {
13 | return {
14 | type: READ_FILE_FAIL,
15 | }
16 | }
17 | export const readFileComplete = () => {
18 | return {
19 | type: READ_FILE_COMPLETE,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "no-empty": true,
9 | "semicolon": false,
10 | "indent":[true, "spaces", 2],
11 | "linebreak-style":[true,"CR/LF"],
12 | "max-classes-per-file":[true,1],
13 | "max-line-length":[true,100],
14 | "quotemark": [true, "single"],
15 | "ordered-imports": false,
16 | "interface-name": false,
17 | "member-access": false,
18 | "object-literal-sort-keys": false
19 | },
20 | "rulesDirectory": []
21 | }
--------------------------------------------------------------------------------
/src/containers/Home/store/Home.reducer.ts:
--------------------------------------------------------------------------------
1 | import * as Actions from './Home.action'
2 | const initialState = {
3 | homeData: '',
4 | }
5 | export default function home(state = initialState, action: any) {
6 | switch (action.type) {
7 | case Actions.GET_HOME_DATA_SUCCESS:
8 | return {
9 | ...state,
10 | ...action.payload,
11 | }
12 | case Actions.GET_HOME_DATA_FAILURE:
13 | return {
14 | ...state,
15 | homeData: 'failure',
16 | }
17 | default:
18 | return state
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/containers/app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { renderRoutes } from 'react-router-config'
3 | import Header from '../components/Header/index'
4 | import styles from './app.less'
5 | import Routes from '../routes.tsx'
6 | import routes from '../routes'
7 | import { Button } from 'antd-mobile';
8 | import '../common/icon-font.css'
9 |
10 | class App extends React.Component {
11 | render() {
12 | return
13 |
14 |
15 | {renderRoutes(routes)}
16 |
17 | }
18 | }
19 |
20 | export default App
21 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 | import createSagaMiddleware from 'redux-saga'
3 | import rootReducers from './reducer/index'
4 | import rootSaga from './saga/index'
5 | const sagaMiddleware = createSagaMiddleware()
6 | const enhancer = typeof window === 'object' ? window.devToolsExtension() : f=>f
7 | const middlewares = compose(
8 | applyMiddleware(sagaMiddleware),
9 | enhancer
10 | )
11 | const defaultStore = window.context.state
12 | const clientStore = createStore(rootReducers, defaultStore, middlewares)
13 | sagaMiddleware.run(rootSaga)
14 | export const createClientStore = () => clientStore
15 | // export default store
16 |
--------------------------------------------------------------------------------
/src/containers/Home/store/Home.action.ts:
--------------------------------------------------------------------------------
1 | export const GET_HOME_DATA = 'GET_HOME_DATA'
2 | export const GET_HOME_DATA_SUCCESS = 'GET_HOME_DATA_SUCCESS'
3 | export const GET_HOME_DATA_FAILURE = 'GET_HOME_DATA_FAILURE'
4 | export const GET_HOME_DATA_COMPLETE = 'GET_HOME_DATA_COMPLETE'
5 |
6 | export const getHomeSuccess = (data: object) => {
7 | return {
8 | type: GET_HOME_DATA_SUCCESS,
9 | payload: data,
10 | }
11 | }
12 | export const getHomeFailure = () => {
13 | return {
14 | type: GET_HOME_DATA_FAILURE,
15 | }
16 | }
17 |
18 | export const getHomeDataAction = (dispatch: any) => {
19 | console.log('服务端渲染调用')
20 | return {
21 | type: GET_HOME_DATA,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/server/render.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { renderToString } from 'react-dom/server'
3 | import { StaticRouter } from 'react-router-dom'
4 | import { Provider } from 'react-redux'
5 | import App from '../containers/app'
6 | import fs from 'fs'
7 |
8 | const serverRender = (req, store) => {
9 | const initialState = ``
14 | const content = renderToString((
15 |
16 |
17 |
18 |
19 |
20 | ))
21 | const template = fs.readFileSync(process.cwd() + '/public/static/index.html', 'utf8')
22 | return template.replace('', content).replace('', initialState)
23 | }
24 |
25 | export default serverRender
26 |
--------------------------------------------------------------------------------
/src/containers/Test/test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { connect } from 'react-redux'
3 | import { Button } from 'antd-mobile'
4 | import style from './test.less'
5 |
6 | interface TestProps {
7 | readFileAsync: any
8 | }
9 | class Test extends React.Component {
10 | componentDidMount() {
11 | // do
12 | }
13 | file = () => {
14 | this.props.readFileAsync()
15 | }
16 | render() {
17 | return
25 | }
26 | }
27 |
28 | function mapStateToProps(state: { fileContent: {} }) {
29 | const { fileContent } = state || { fileContent: {} }
30 | return {
31 | fileContent,
32 | }
33 | }
34 | function mapDispatchToProps(dispatch: any) {
35 | return {
36 | readFileAsync: () => dispatch({
37 | type: 'READ_FILE',
38 | }),
39 | }
40 | }
41 |
42 | export default connect(mapStateToProps, mapDispatchToProps)(Test)
43 |
--------------------------------------------------------------------------------
/src/client/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import * as ReactDOM from 'react-dom'
3 | const root = document.getElementById('root')
4 | import {AppContainer} from 'react-hot-loader'
5 | import { Provider } from 'react-redux'
6 | import { BrowserRouter } from 'react-router-dom'
7 | import { createClientStore } from '../store';
8 | import App from '../containers/app'
9 |
10 | if (module.hot) {
11 | module.hot.accept('../containers/app.tsx', () => {
12 | const NextApp = require('../containers/app.tsx').default
13 | ReactDOM.render(
14 |
15 |
16 |
17 |
18 |
19 |
20 | , root)
21 | })
22 | }
23 |
24 | ReactDOM.render(
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | , root)
33 |
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { createStore, applyMiddleware, compose } from 'redux'
3 | import { matchRoutes } from "react-router-config"
4 | import createSagaMiddleware from 'redux-saga'
5 | import rootReducers from '../reducer/index'
6 | import { fetchHomeData } from '../containers/Home/store/Home.service'
7 | import routes from '../routes'
8 | import serverRender from './render'
9 |
10 | // 服务端渲染的store
11 | const sagaMiddleware = createSagaMiddleware()
12 | const middlewares = compose(
13 | applyMiddleware(sagaMiddleware)
14 | )
15 | const store = createStore(rootReducers, middlewares);
16 |
17 | const app = express()
18 | app.use(express.static('public'))
19 | app.get("*", (req, res) => {
20 | const matchedRoutes = matchRoutes(routes, req.path)
21 | // @Todo 会自动请求robots.txt文件,导致matchedRoutes 为undefined 暂时判断处理,日后补充
22 | if (matchedRoutes[0]) {
23 | sagaMiddleware.run(fetchHomeData).done.then(() => {
24 | res.send(serverRender(req, store))
25 | })
26 |
27 | }
28 | })
29 |
30 | app.listen(3000, () => {
31 | console.log('server listening on port 3000')
32 | })
33 |
--------------------------------------------------------------------------------
/src/common/icon-font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src: url('../static/fonts/icomoon.eot?om4pos');
4 | src: url('../static/fonts/icomoon.eot?om4pos#iefix') format('embedded-opentype'),
5 | url('../static/fonts/icomoon.ttf?om4pos') format('truetype'),
6 | url('../static/fonts/icomoon.woff?om4pos') format('woff'),
7 | url('../static/fonts/icomoon.svg?om4pos#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | [class^="icon-"], [class*=" icon-"] {
13 | /* use !important to prevent issues with browser extensions that change fonts */
14 | font-family: 'icomoon' !important;
15 | speak: none;
16 | font-style: normal;
17 | font-weight: normal;
18 | font-variant: normal;
19 | text-transform: none;
20 | line-height: 1;
21 |
22 | /* Better Font Rendering =========== */
23 | -webkit-font-smoothing: antialiased;
24 | -moz-osx-font-smoothing: grayscale;
25 | }
26 |
27 | .icon-home2:before {
28 | content: "\e901";
29 | }
30 | .icon-office:before {
31 | content: "\e903";
32 | }
33 | .icon-newspaper:before {
34 | content: "\e904";
35 | }
36 |
--------------------------------------------------------------------------------
/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Home from './containers/Home/index'
3 | import Test from './containers/Test/test'
4 | import App from './containers/app'
5 | import { Route, Redirect } from 'react-router-dom'
6 |
7 | interface RoutesProps {
8 | routes: any
9 | }
10 | class Routes extends React.Component {
11 | state = {
12 | routesList: [
13 | {
14 | path: '/',
15 | component: () => ,
16 | name: 'App',
17 | },
18 | {
19 | path: '/home',
20 | component: Home,
21 | name: 'Home',
22 | },
23 | {
24 | path: '/test',
25 | component: Test,
26 | name: 'Test',
27 | },
28 | ],
29 | }
30 | render() {
31 | return
32 | {
33 | this.state.routesList.map((v: any) => {
34 | return
35 | }
36 | )
37 | }
38 |
39 | }
40 | }
41 |
42 | export default Routes
43 |
--------------------------------------------------------------------------------
/src/containers/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Button } from 'antd-mobile'
3 | import { connect } from 'react-redux'
4 | import { GET_HOME_DATA, getHomeDataAction } from './store/Home.action'
5 | import pic from '../../static/img/timg.jpg'
6 | import styles from './style.less'
7 | interface HomeProps {
8 | getHomeDataAction: any,
9 | }
10 | class Home extends React.Component {
11 | componentDidMount() {
12 | // const { getHomeDataAction } = this.props
13 | // getHomeDataAction()
14 | // do
15 | }
16 |
17 | render() {
18 | const { results } = this.props.home
19 | return
20 |
21 |
22 | {
23 | results && results.map(v =>
24 | { v.name.title + v.name.first + v.name.last }
25 | )
26 | }
27 |
28 |

29 |
30 | }
31 | }
32 |
33 | const mapStateToProps = (state: object) => {
34 | return {
35 | home: state.home,
36 | }
37 | }
38 |
39 | export default connect(mapStateToProps, {
40 | getHomeDataAction,
41 | })(Home)
42 |
--------------------------------------------------------------------------------
/src/static/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/configs/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const tsImportPluginFactory = require('ts-import-plugin')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const theme = require('../package.json').theme
6 |
7 | const config = {
8 | resolve: {
9 | extensions: [ '.ts', '.tsx', '.js' ]
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js$/,
15 | loader: 'babel-loader',
16 | exclude: [
17 | path.join(__dirname, '../node_modules'),
18 | ],
19 |
20 | },
21 | {
22 | test: /\.(tsx|ts)$/,
23 | use: [
24 | { loader: 'babel-loader' },
25 | {
26 | loader: 'ts-loader',
27 | options: {
28 | transpileOnly: true,
29 | getCustomTransformers: () => ({
30 | before: [ tsImportPluginFactory({
31 | libraryDirectory: 'es',
32 | libraryName: 'antd-mobile',
33 | style: true,
34 | }) ]
35 | }),
36 | compilerOptions: {
37 | module: 'es2015'
38 | }
39 | },
40 | }
41 | ],
42 | exclude: [ path.join(__dirname, '../node_modules') ]
43 | },
44 | {
45 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
46 | loader: require.resolve('url-loader'),
47 | options: {
48 | limit: 8000,
49 | name: 'static/img/[name].[hash:8].[ext]',
50 | },
51 | },
52 | {
53 | test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: 'static/fonts/[name].[hash:8].[ext]'
58 | }
59 | }
60 | ]
61 | },
62 |
63 | }
64 |
65 | module.exports = config
66 |
--------------------------------------------------------------------------------
/configs/webpack.config.server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const merge = require('webpack-merge')
3 | const baseConfig = require('./webpack.config')
4 | const CleanWebpackPlugin = require("clean-webpack-plugin");
5 | const theme = require('../package.json').theme
6 | const serverConfig = {
7 | entry: path.join(__dirname, '../src/server/index.js'),
8 | target: 'node',
9 | mode: 'production',
10 | output: {
11 | path: path.join(__dirname, '../build'),
12 | filename: 'server.js',
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.less$/,
18 | include: [ path.join(__dirname, '../node_modules/antd-mobile') ],
19 | use: [
20 | { loader: 'isomorphic-style-loader' },
21 | { loader: 'css-loader' },
22 | {
23 | loader: 'less-loader',
24 | options: {
25 | modifyVars: theme
26 | }
27 | }
28 | ]
29 | },
30 | {
31 | test: /\.less$/,
32 | exclude: [ path.join(__dirname, '../node_modules/antd-mobile') ],
33 | use: [
34 | { loader: 'isomorphic-style-loader' },
35 | {
36 | loader: 'css-loader',
37 | options: {
38 | importLoaders: 1,
39 | modules: true,
40 | localIdentName: '[name]_[local]_[hash:base64:5]'
41 | }
42 | },
43 | {
44 | loader: 'less-loader',
45 | options: {
46 | modifyVars: theme
47 | }
48 | }
49 | ]
50 | },
51 | {
52 | test: /\.css$/,
53 | use: [
54 | 'isomorphic-style-loader',
55 | 'css-loader',
56 | ],
57 | }
58 | ]
59 | },
60 | plugins: [
61 | new CleanWebpackPlugin([path.join(__dirname, '../build')]),
62 | ]
63 | }
64 | module.exports = merge(serverConfig, baseConfig)
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "1.0.0",
4 | "description": "A react music webapp",
5 | "main": "index.js",
6 | "scripts": {
7 | "dist": "cross-env NODE_ENV=production webpack --config configs/webpack.config.js --mode production",
8 | "build": "npm run dist",
9 | "dev-temp": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.js --mode development",
10 | "dev": "npm-run-all --parallel dev:**",
11 | "start:server": "nodemon --watch build --exec node \"./build/server.js\"",
12 | "build:server": "webpack --config configs/webpack.config.server.js",
13 | "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.client.js --watch",
14 | "build:client": "cross-env NODE_ENV=production webpack --config configs/webpack.config.client.js",
15 | "test": "echo \"Error: no test specified\" && exit 1",
16 | "tslint": "tslint --project . src/**/*.ts src/**/*.tsx"
17 | },
18 | "author": "",
19 | "license": "ISC",
20 | "devDependencies": {
21 | "@types/react": "^16.4.6",
22 | "@types/react-dom": "^16.0.6",
23 | "@types/react-redux": "^6.0.4",
24 | "@types/react-router": "^4.0.28",
25 | "autoprefixer": "^8.6.5",
26 | "babel-core": "^6.26.3",
27 | "babel-loader": "^7.1.4",
28 | "babel-plugin-import": "^1.8.0",
29 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
30 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
31 | "babel-plugin-transform-runtime": "^6.23.0",
32 | "babel-preset-es2015": "^6.24.1",
33 | "babel-preset-es2015-loose": "^8.0.0",
34 | "babel-preset-react": "^6.24.1",
35 | "babel-preset-stage-1": "^6.24.1",
36 | "clean-webpack-plugin": "^0.1.19",
37 | "cross-env": "^5.2.0",
38 | "css-loader": "^1.0.0",
39 | "file-loader": "^2.0.0",
40 | "html-webpack-plugin": "^3.2.0",
41 | "isomorphic-style-loader": "^4.0.0",
42 | "less": "^3.5.3",
43 | "less-loader": "^4.1.0",
44 | "mini-css-extract-plugin": "^0.4.4",
45 | "nodemon": "^1.18.4",
46 | "postcss-flexbugs-fixes": "^3.3.1",
47 | "postcss-loader": "^2.1.6",
48 | "react-hot-loader": "^4.3.3",
49 | "react-loadable": "^5.5.0",
50 | "rimraf": "^2.6.2",
51 | "style-loader": "^0.21.0",
52 | "ts-import-plugin": "^1.5.4",
53 | "ts-loader": "^4.4.2",
54 | "tslint": "^5.10.0",
55 | "typescript": "^2.9.2",
56 | "url-loader": "^1.1.2",
57 | "webpack": "^4.12.1",
58 | "webpack-cli": "^3.0.8",
59 | "webpack-dev-server": "^3.1.4",
60 | "webpack-hot-middleware": "^2.24.3",
61 | "webpack-merge": "^4.1.4"
62 | },
63 | "dependencies": {
64 | "@types/react-router-config": "^1.0.9",
65 | "@types/react-router-dom": "^4.3.1",
66 | "antd-mobile": "^2.2.1",
67 | "axios": "^0.18.0",
68 | "express": "^4.16.4",
69 | "react": "^16.4.1",
70 | "react-dom": "^16.4.1",
71 | "react-redux": "^5.0.7",
72 | "react-router": "^4.3.1",
73 | "react-router-config": "^4.4.0-beta.1",
74 | "react-router-dom": "^4.3.1",
75 | "redux": "^4.0.0",
76 | "redux-saga": "^0.16.0"
77 | },
78 | "theme": {
79 | "brand-primary": "#f68b0e",
80 | "color-text-base": "#fff",
81 | "brand-primary-tap": "#f7a03a"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/configs/webpack.config.bak.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const tsImportPluginFactory = require('ts-import-plugin')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const theme = require('../package.json').theme
6 | const isDev = process.env.NODE_ENV === 'development'
7 | const config = {
8 | entry: path.join(__dirname, '../src/client/index.tsx'),
9 | output: {
10 | path: path.join(__dirname, '../build'),
11 | filename: '[hash:8]-[name].js',
12 | },
13 | resolve: {
14 | extensions: [ '.ts', '.tsx', '.js' ]
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.js$/,
20 | loader: 'babel-loader',
21 | exclude: [
22 | path.join(__dirname, '../node_modules'),
23 | ],
24 |
25 | },
26 | {
27 | test: /\.(tsx|ts)$/,
28 | use: [
29 | { loader: 'babel-loader' },
30 | {
31 | loader: 'ts-loader',
32 | options: {
33 | transpileOnly: true,
34 | getCustomTransformers: () => ({
35 | before: [ tsImportPluginFactory({
36 | libraryDirectory: 'es',
37 | libraryName: 'antd-mobile',
38 | style: true,
39 | }) ]
40 | }),
41 | compilerOptions: {
42 | module: 'es2015'
43 | }
44 | },
45 | }
46 | ],
47 | exclude: [ path.join(__dirname, '../node_modules') ]
48 | },
49 | {
50 | test: /\.less$/,
51 | include: [ path.join(__dirname, '../node_modules/antd-mobile') ],
52 | use: [
53 | { loader: 'style-loader' },
54 | { loader: 'css-loader' },
55 | {
56 | loader: 'less-loader',
57 | options: {
58 | modifyVars: theme
59 | }
60 | }
61 | ]
62 | },
63 | {
64 | test: /\.less$/,
65 | exclude: [ path.join(__dirname, '../node_modules/antd-mobile') ],
66 | use: [
67 | { loader: 'style-loader' },
68 | { loader: 'css-loader?modules&localIdentName=[name]-[hash:base64:5]' },
69 | {
70 | loader: 'less-loader',
71 | options: {
72 | modifyVars: theme
73 | }
74 | }
75 | ]
76 | },
77 | {
78 | test: /\.css$/,
79 | use: [
80 | 'style-loader',
81 | 'css-loader',
82 | ],
83 | },
84 |
85 | ]
86 | },
87 | plugins: [
88 | new HtmlWebpackPlugin({
89 | title: 'music',
90 | filename: path.join(__dirname, '../build/index.html'),
91 | template: path.join(__dirname, '../public/index.html'),
92 | inject: true,
93 | }),
94 | ]
95 | }
96 | if (isDev) {
97 | config.devServer = {
98 | host: '0.0.0.0',
99 | port: '8080',
100 | contentBase: path.join(__dirname, '../public'),
101 | overlay: {
102 | error: true,
103 | },
104 | historyApiFallback: {
105 | index: path.join(__dirname, '../public/index.html'),
106 | },
107 | hot: true,
108 | }
109 | config.entry = {
110 | app :[
111 | "react-hot-loader/patch",
112 | path.join(__dirname, '../src/client/index.tsx'),
113 | ]
114 | }
115 | config.plugins.push(
116 | new webpack.HotModuleReplacementPlugin()
117 | )
118 | }
119 | module.exports = config
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | // "lib": [], /* Specify library files to be included in the compilation. */
7 | "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | // "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | // "outDir": "./", /* Redirect output structure to the directory. */
15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "removeComments": true, /* Do not emit comments to output. */
18 | // "noEmit": true, /* Do not emit outputs. */
19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
22 |
23 | /* Strict Type-Checking Options */
24 | "strict": true, /* Enable all strict type-checking options. */
25 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
26 | // "strictNullChecks": true, /* Enable strict null checks. */
27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
31 |
32 | /* Additional Checks */
33 | // "noUnusedLocals": true, /* Report errors on unused locals. */
34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
37 |
38 | /* Module Resolution Options */
39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
43 | // "typeRoots": [], /* List of folders to include type definitions from. */
44 | // "types": [], /* Type declaration files to be included in compilation. */
45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
48 |
49 | /* Source Map Options */
50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
54 |
55 | /* Experimental Options */
56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
58 | }
59 | }
--------------------------------------------------------------------------------
/configs/webpack.config.client.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const merge = require('webpack-merge')
4 | const baseConfig = require('./webpack.config')
5 | const theme = require('../package.json').theme
6 | const HtmlWebpackPlugin = require('html-webpack-plugin')
7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
8 | const CleanWebpackPlugin = require("clean-webpack-plugin");
9 | const isDev = process.env.NODE_ENV === 'development'
10 | const isProd = process.env.NODE_ENV === 'production'
11 | const clientConfig = {
12 | entry: path.join(__dirname, '../src/client/index.tsx'),
13 | output: {
14 | path: path.join(__dirname, '../public'),
15 | filename: 'static/js/[name].client.js',
16 | chunkFilename: "static/js/[name].js",
17 | },
18 | resolve: {
19 | extensions: [ '.ts', '.tsx', '.js', '.json' ]
20 | },
21 | module: {
22 | rules: [
23 | ]
24 | },
25 | devtool: "#eval-source-map",
26 | plugins: []
27 | }
28 | if (isDev) {
29 | const devRules = [
30 | {
31 | test: /\.less$/,
32 | include: [ path.join(__dirname, '../node_modules/antd-mobile') ],
33 | use: [
34 | { loader: 'style-loader' },
35 | { loader: 'css-loader' },
36 | {
37 | loader: 'less-loader',
38 | options: {
39 | modifyVars: theme
40 | }
41 | }
42 | ]
43 | },
44 | {
45 | test: /\.less$/,
46 | exclude: [ path.join(__dirname, '../node_modules/antd-mobile') ],
47 | use: [
48 | { loader: 'style-loader' },
49 | {
50 | loader: 'css-loader',
51 | options: {
52 | importLoaders: 1,
53 | modules: true,
54 | localIdentName: '[name]_[local]_[hash:base64:5]'
55 | }
56 | },
57 | {
58 | loader: 'less-loader',
59 | options: {
60 | modifyVars: theme
61 | }
62 | }
63 | ]
64 | },
65 | {
66 | test: /\.css$/,
67 | use: [
68 | {loader: 'style-loader'},
69 | 'css-loader',
70 | ],
71 | },
72 | ]
73 | clientConfig.entry = [
74 | "react-hot-loader/patch",
75 | path.join(__dirname, '../src/client/index.tsx'),
76 | ]
77 | clientConfig.output = {
78 | path: path.join(__dirname, '../dev-public'),
79 | filename: '[name].client.js',
80 | chunkFilename: "[name].js",
81 | },
82 |
83 | clientConfig.mode = 'development'
84 | clientConfig.module.rules = clientConfig.module.rules.concat(devRules)
85 | clientConfig.devServer = {
86 | host: '0.0.0.0',
87 | port: '8080',
88 | contentBase: path.join(__dirname, '../dev-public'),
89 | inline: true,
90 | overlay: {
91 | error: true,
92 | },
93 | historyApiFallback: true,
94 | hot: true,
95 | }
96 | clientConfig.plugins = clientConfig.plugins.concat(
97 | new webpack.HotModuleReplacementPlugin(),
98 | new HtmlWebpackPlugin({
99 | title: 'music',
100 | filename: path.join(__dirname, '../dev-public/index.html'),
101 | template: path.join(__dirname, '../dev-public/index.html'),
102 | inject: true,
103 | }),
104 | )
105 | }
106 | if (isProd) {
107 | const prodRules = [
108 | {
109 | test: /\.less$/,
110 | include: /node_modules/,
111 | use: [
112 | {loader: MiniCssExtractPlugin.loader},
113 | // { loader: 'style-loader' },
114 | { loader: 'css-loader' },
115 | {
116 | loader: 'less-loader',
117 | options: {
118 | modifyVars: theme
119 | }
120 | }
121 | ]
122 | },
123 | {
124 | test: /\.less$/,
125 | exclude: /node_modules/,
126 | use: [
127 | {loader: MiniCssExtractPlugin.loader},
128 | // { loader: 'style-loader' },
129 | {
130 | loader: 'css-loader',
131 | options: {
132 | importLoaders: 1,
133 | modules: true,
134 | localIdentName: '[name]_[local]_[hash:base64:5]'
135 | }
136 | },
137 | {
138 | loader: 'less-loader',
139 | options: {
140 | modifyVars: theme
141 | }
142 | }
143 | ]
144 | },
145 | {
146 | test: /\.css$/,
147 | use: [
148 | {loader: MiniCssExtractPlugin.loader},
149 | 'css-loader',
150 | ],
151 | },
152 | ]
153 | clientConfig.mode = 'production'
154 | clientConfig.module.rules = clientConfig.module.rules.concat(prodRules)
155 | clientConfig.optimization = {
156 | runtimeChunk: {
157 | name: "manifest"
158 | },
159 | splitChunks: {
160 | cacheGroups: {
161 | vendor: {
162 | test: /[\\/]node_modules[\\/]/,
163 | name: "vendors",
164 | priority: -20,
165 | chunks: "all"
166 | }
167 | }
168 | }
169 | }
170 | clientConfig.plugins.push(
171 | new CleanWebpackPlugin(['../public']),
172 | new MiniCssExtractPlugin({
173 | filename: "static/css/[name].css",
174 | // chunkFilename: "static/css/style.[id].css",
175 | // allChunks: true
176 | }),
177 | new HtmlWebpackPlugin({
178 | title: 'music',
179 | filename: path.join(__dirname, '../public/static/index.html'),
180 | template: path.join(__dirname, '../dev-public/index.html'),
181 | inject: true,
182 | }),
183 |
184 | )
185 | }
186 | module.exports = merge(clientConfig, baseConfig)
187 |
--------------------------------------------------------------------------------