├── .gitignore
├── .lintstagedrc
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .steamer
├── .mobxTemplate
│ ├── app.tsx
│ ├── containers
│ │ ├── shared
│ │ │ └── App
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.scss
│ │ └── views
│ │ │ ├── Home
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ │ │ └── NotFound
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ ├── store
│ │ ├── globalStore
│ │ │ ├── index.ts
│ │ │ └── type.d.ts
│ │ └── index.ts
│ ├── typings
│ │ └── router.d.ts
│ └── util
│ │ └── constants.ts
├── .svgModified
│ ├── src
│ │ └── app.scss
│ ├── tools
│ │ └── rules
│ │ │ └── fileRules.js
│ └── typings
│ │ └── svg.d.ts
├── replace-redux-files-to-mobx.js
├── steamer-plugin-kit.js
├── steamer-react-ts.js
└── support-svg-component.js
├── .stylelintignore
├── .stylelintrc.js
├── README.md
├── config
├── project.js
└── steamer.config.js
├── docs
├── css.md
├── index.md
├── js.md
├── store.md
├── svg.md
├── test.md
└── tree.md
├── jest.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── qshell.prod.json
├── qshell.qa.json
├── src
├── app.scss
├── app.tsx
├── assets
│ └── images
│ │ └── logo404.png
├── components
│ ├── ReducerInjector
│ │ └── index.tsx
│ └── SagaInjector
│ │ └── index.tsx
├── configureStore.ts
├── containers
│ ├── shared
│ │ └── App
│ │ │ ├── actions.ts
│ │ │ ├── constants.ts
│ │ │ ├── index.tsx
│ │ │ ├── reducer.ts
│ │ │ ├── selectors.ts
│ │ │ └── style.scss
│ └── views
│ │ ├── Home
│ │ ├── actions.ts
│ │ ├── constants.ts
│ │ ├── index.tsx
│ │ ├── reducer.ts
│ │ ├── saga.ts
│ │ ├── selectors.ts
│ │ └── style.scss
│ │ └── NotFound
│ │ ├── index.tsx
│ │ └── style.scss
├── env
│ ├── dev.ts
│ ├── prod.ts
│ └── qa.ts
├── libs
│ ├── preact.js
│ ├── react-dom.js
│ └── react.js
├── main.html
├── reducers.ts
├── services
│ ├── api.ts
│ └── contants.ts
├── styles
│ ├── _base.scss
│ ├── _functions.scss
│ ├── _mixins.scss
│ └── _var.scss
└── util
│ ├── constants.ts
│ ├── http.ts
│ ├── reducerInjectors.ts
│ └── sagaInjectors.ts
├── test
├── mocks
│ └── .gitkeep
└── spec
│ └── simple.spec.tsx
├── tools
├── feature
│ └── feature.js
├── optimization
│ └── index.js
├── plugins
│ ├── basePlugins.js
│ ├── cachePlugins.js
│ ├── resourcePlugins.js
│ └── spritePlugins.js
├── rules
│ ├── fileRules.js
│ ├── htmlRules.js
│ ├── jsRules.js
│ └── styleRules.js
├── script.js
├── server.js
├── template
│ ├── dependency.js
│ ├── mobxViewTemplate
│ │ ├── index.tsx
│ │ └── style.scss
│ └── viewTemplate
│ │ ├── actions.ts
│ │ ├── constants.ts
│ │ ├── index.tsx
│ │ ├── reducer.ts
│ │ ├── saga.ts
│ │ ├── selectors.ts
│ │ └── style.scss
└── webpack.base.js
├── tsconfig.json
├── tsconfig.webpack.json
├── tslint.json
└── typings
├── antd.shim.d.ts
├── echarts.d.ts
├── http.d.ts
├── index.d.ts
├── redux-store.shim.d.ts
├── redux.shim.d.ts
└── typed-css-modules.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | /dev
4 | dist/*
5 | src/**/*.scss.d.ts
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "src/**/*.{ts,tsx}": ["tslint -c tslint.json", "prettier --write", "git add"],
3 | "src/**/*.scss": ["stylelint --fix", "prettier --write", "git add"]
4 | }
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npm.taobao.org
2 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /**/libs/**
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslintIntegration": true,
3 | "stylelintIntegration": true,
4 | "tabWidth": 4,
5 | "singleQuote": true,
6 | "semi": false
7 | }
8 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/app.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * app.js
3 | *
4 | * 程序的入口
5 | */
6 |
7 | import * as React from 'react'
8 | import * as ReactDOM from 'react-dom'
9 | import { Provider } from 'mobx-react'
10 | import { configure } from 'mobx'
11 | import createHashHistory from 'history/createHashHistory'
12 | import { syncHistoryWithStore } from 'mobx-react-router'
13 | import { Router } from 'react-router-dom'
14 |
15 | // 如果依赖第三方UI库的样式,建议去掉sanitize.css的依赖
16 | import 'sanitize.css/sanitize.css'
17 |
18 | /**
19 | * 这里开始你的第三方包依赖,包括css
20 | * Example
21 | *
22 | * import "antd/dist/antd.css"
23 | */
24 |
25 | // app global style
26 | import './app.scss'
27 |
28 | // Import root app
29 | import App from 'containers/shared/App'
30 |
31 | import * as store from './store'
32 |
33 | // 开启mobx严格模式
34 | configure({ enforceActions: true })
35 |
36 | const hashHistory = createHashHistory()
37 | const history = syncHistoryWithStore(hashHistory, store.routerStore)
38 |
39 | const MOUNT_NODE = document.getElementById('root')
40 |
41 | const render = Component => {
42 | ReactDOM.render(
43 |
44 |
45 |
46 |
47 | ,
48 | MOUNT_NODE
49 | )
50 | }
51 |
52 | render(App)
53 |
54 | if (module.hot) {
55 | // 热更新React Components
56 | // module.hot.accept不支持动态的依赖
57 | // 必须是编译时定义的常量
58 | module.hot.accept(['containers/shared/App'], () => {
59 | ReactDOM.unmountComponentAtNode(MOUNT_NODE)
60 | render(require('containers/shared/App').default)
61 | })
62 | }
63 |
64 | // TODO: 待验证
65 | // Install ServiceWorker and AppCache in the end since
66 | // it's not most important operation and if main code fails,
67 | // we do not want it installed
68 | if (process.env.NODE_ENV === 'production') {
69 | // tslint:disable-next-line:no-var-requires
70 | require('offline-plugin/runtime').install() // eslint-disable-line global-require
71 | }
72 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/shared/App/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App
4 | *
5 | * 这个组件是每个页面的最外层骨架,所以这里的代码只能存放所有页面公有的, 比如(导航)或者路由
6 | * 切勿在其他组件引用
7 | */
8 |
9 | import * as React from "react";
10 | import { HashRouter as Router, Switch, Route } from "react-router-dom";
11 |
12 | import HomePage from "containers/views/Home";
13 | import NotFoundPage from "containers/views/NotFound";
14 |
15 |
16 | import * as styles from "./style.scss";
17 |
18 | const AppWrapper = props => (
19 |
{props.children}
20 | );
21 |
22 | export default function App(props) {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | {/*
30 | */}
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/shared/App/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .appWrapper {
4 | display: flex;
5 | width: 100%;
6 | height: 100%;
7 | overflow: auto;
8 | padding: 16px;
9 | margin: 0 auto;
10 | min-height: 100%;
11 | flex-direction: column;
12 | }
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/views/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { observer, inject } from 'mobx-react'
3 | import { computed } from 'mobx'
4 |
5 | import * as styles from './style.scss'
6 |
7 | interface IP {
8 | globalStore: IGlobalStore.GlobalStore
9 | routerStore: RouterStore
10 | }
11 |
12 | @inject('globalStore', 'routerStore')
13 | @observer
14 | class Home extends React.Component {
15 | @computed
16 | get hello() {
17 | const { test } = this.props.globalStore
18 | return test ? test.hello : ''
19 | }
20 |
21 | routerTest = () => {
22 | this.props.routerStore.push('/error')
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
29 | Hello World!
30 |
31 | {this.hello}
32 |
33 | )
34 | }
35 | }
36 |
37 | export default Home
38 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/views/Home/style.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/_base';
2 |
3 | .routerTest {
4 | cursor: pointer;
5 | color: purple;
6 | }
7 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/views/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import * as styles from './style.scss'
4 |
5 | import * as Logo from 'assets/images/logo404.png'
6 |
7 | class NotFound extends React.Component {
8 | public render() {
9 | return (
10 |
11 |

12 |
13 |
404
14 |
抱歉,你访问的页面不存在
15 |
16 |
17 | )
18 | }
19 | }
20 |
21 | export default NotFound
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/containers/views/NotFound/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .pageContainer {
4 | display: flex;
5 | width: 100%;
6 | height: 100%;
7 | align-items: center;
8 | justify-content: center;
9 | background: #f0f2f5;
10 | }
11 |
12 | .leftWrapper {
13 | margin-right: 10%;
14 | }
15 |
16 | .pageTitle {
17 | color: #434e59;
18 | font-size: 70px;
19 | }
20 |
21 | .pageDescription {
22 | color: rgba(#000, 0.45);
23 | font-size: 20px;
24 | }
25 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/store/globalStore/index.ts:
--------------------------------------------------------------------------------
1 | import { observable, action } from 'mobx'
2 |
3 | export class GlobalStore {
4 | @observable test: IGlobalStore.Test = {
5 | hello: 'hello mobx!'
6 | }
7 |
8 | @action
9 | changeTest = (test: IGlobalStore.Test) => {
10 | this.test = test
11 | }
12 | }
13 |
14 | export default new GlobalStore()
15 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/store/globalStore/type.d.ts:
--------------------------------------------------------------------------------
1 | import { GlobalStore as GlobalStoreModel } from './index'
2 |
3 | export as namespace IGlobalStore
4 |
5 | export interface GlobalStore extends GlobalStoreModel {}
6 |
7 | export interface Test {
8 | hello: string
9 | }
10 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/store/index.ts:
--------------------------------------------------------------------------------
1 | import { RouterStore } from 'mobx-react-router'
2 |
3 | export const routerStore = new RouterStore()
4 |
5 | export { default as globalStore } from './globalStore'
6 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/typings/router.d.ts:
--------------------------------------------------------------------------------
1 | import { RouterStore as _RouterStore } from 'mobx-react-router'
2 |
3 | declare global {
4 | interface RouterStore extends _RouterStore {}
5 | }
6 |
--------------------------------------------------------------------------------
/.steamer/.mobxTemplate/util/constants.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YDJ-FE/steamer-react-ts/cb18e32841f2000e218a74476e29c3faebff1306/.steamer/.mobxTemplate/util/constants.ts
--------------------------------------------------------------------------------
/.steamer/.svgModified/src/app.scss:
--------------------------------------------------------------------------------
1 | @import 'styles/_base.scss';
2 |
3 | :global {
4 | html,
5 | body {
6 | width: 100%;
7 | height: 100%;
8 | }
9 |
10 | #root {
11 | width: 100%;
12 | height: 100%;
13 | background: $body-background;
14 | }
15 |
16 | svg,
17 | svg path {
18 | fill: currentcolor;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.steamer/.svgModified/tools/rules/fileRules.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | let configWebpack = config.webpack
3 | let isProduction = config.env === 'production'
4 |
5 | let rules = [
6 | {
7 | test: /\.ico$/,
8 | loader: 'url-loader',
9 | options: {
10 | name: '[name].[ext]'
11 | }
12 | },
13 | {
14 | test: /\.(jpe?g|png|gif)$/i,
15 | use: [
16 | {
17 | loader: 'url-loader',
18 | options: {
19 | publicPath: isProduction
20 | ? configWebpack.imgCdn
21 | : configWebpack.webserver,
22 | limit: 10,
23 | name: `img/[path]${configWebpack.hashName}.[ext]`
24 | }
25 | }
26 | ]
27 | },
28 | {
29 | test: /\.(woff|woff2|eot|ttf)\??.*$/,
30 | use: [
31 | {
32 | loader: 'url-loader',
33 | options: {
34 | limit: 10,
35 | name: `font/[path]${configWebpack.hashName}.[ext]`
36 | }
37 | }
38 | ]
39 | },
40 | {
41 | test: /\.svg$/,
42 | loader: '@svgr/webpack'
43 | }
44 | ]
45 |
46 | return rules
47 | }
48 |
--------------------------------------------------------------------------------
/.steamer/.svgModified/typings/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare interface SvgrComponent extends React.StatelessComponent> {}
2 |
3 | declare module '*.svg' {
4 | const content: SvgrComponent
5 | export default content
6 | }
7 |
--------------------------------------------------------------------------------
/.steamer/replace-redux-files-to-mobx.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const mobxTemplatePath = path.join(__dirname, '.mobxTemplate')
4 |
5 | module.exports = function(folderPath) {
6 | // 删除/修改入口文件中redux相关的模块
7 | this.fs.removeSync(path.join(folderPath, 'src/configureStore.ts'))
8 | this.fs.removeSync(path.join(folderPath, 'src/reducers.ts'))
9 | this.fs.copySync(
10 | path.join(mobxTemplatePath, 'app.tsx'),
11 | path.join(folderPath, 'src/app.tsx')
12 | )
13 |
14 | // 添加store
15 | this.fs.copySync(
16 | path.join(mobxTemplatePath, 'store'),
17 | path.join(folderPath, 'src/store')
18 | )
19 |
20 | // 删除/修改util中redux相关的模块
21 | const utilPath = path.join(folderPath, 'src/util')
22 | this.fs.removeSync(path.join(utilPath, 'reducerInjectors.ts'))
23 | this.fs.removeSync(path.join(utilPath, 'sagaInjectors.ts'))
24 | this.fs.removeSync(path.join(utilPath, 'constants.ts'))
25 |
26 | // 删除typings中redux相关的模块
27 | const typingsPath = path.join(folderPath, 'typings')
28 | this.fs.removeSync(path.join(typingsPath, 'redux-store.shim.d.ts'))
29 | this.fs.removeSync(path.join(typingsPath, 'redux.shim.d.ts'))
30 | // 添加router.d.ts到typings
31 | this.fs.copySync(
32 | path.join(mobxTemplatePath, 'typings/router.d.ts'),
33 | path.join(typingsPath, 'router.d.ts')
34 | )
35 |
36 | // 删除/修改components中redux相关的组件
37 | const componentsPath = path.join(folderPath, 'src/components')
38 | this.fs.removeSync(path.join(componentsPath, 'ReducerInjector/'))
39 | this.fs.removeSync(path.join(componentsPath, 'SagaInjector'))
40 |
41 | // 删除/修改containers中redux相关的组件
42 | const containersPath = path.join(folderPath, 'src/containers')
43 | this.fs.emptyDirSync(containersPath)
44 | this.fs.copySync(path.join(mobxTemplatePath, 'containers'), containersPath)
45 | }
46 |
--------------------------------------------------------------------------------
/.steamer/steamer-plugin-kit.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "plugin": "steamer-plugin-kit",
3 | "config": {
4 | "kit": "steamer-react-redux-ts",
5 | "version": "1.0.0",
6 | "template": {
7 | "src": "./tools/template",
8 | "dist": "./src/containers/views",
9 | "npm": "yarn"
10 | }
11 | }
12 | };
--------------------------------------------------------------------------------
/.steamer/steamer-react-ts.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const replaceReduxFilesToMobx = require('./replace-redux-files-to-mobx')
4 | const supportSvgComponent = require('./support-svg-component')
5 |
6 | const onInstall = function(folderPath, config) {
7 | const pkg = this.getPkgJson(folderPath)
8 | let pkgHasChanged = false
9 | if (!!config.mobx) {
10 | pkgHasChanged = true
11 | pkg.dependencies = Object.assign({}, pkg.dependencies, {
12 | mobx: '^5.0.0',
13 | 'mobx-react': '^5.2.2',
14 | 'mobx-react-router': '^4.0.4'
15 | })
16 | const rmMods = {
17 | dependencies: [
18 | 'react-redux',
19 | 'react-router-redux',
20 | 'redux',
21 | 'redux-immutable',
22 | 'redux-saga',
23 | 'reselect'
24 | ],
25 | devDependencies: ['@types/react-redux', '@types/react-router-redux']
26 | }
27 | for (const dep of Object.keys(rmMods)) {
28 | for (const mod of rmMods[dep]) {
29 | delete pkg[dep][mod]
30 | }
31 | }
32 | replaceReduxFilesToMobx.bind(this)(folderPath)
33 | }
34 | if (!!config.svg) {
35 | pkgHasChanged = true
36 | pkg.devDependencies = Object.assign({}, pkg.devDependencies, {
37 | '@svgr/webpack': '^2.1.1'
38 | })
39 | supportSvgComponent.bind(this)(folderPath)
40 | }
41 | if (!!config.jest) {
42 | pkgHasChanged = true
43 | pkg.dependencies = Object.assign({}, pkg.dependencies, {
44 | enzyme: '^3.3.0',
45 | jest: '^22.4.4'
46 | })
47 | pkg.devDependencies = Object.assign({}, pkg.devDependencies, {
48 | '@types/enzyme': '^3.1.10',
49 | '@types/jest': '^22.2.3',
50 | 'enzyme-adapter-react-16': '^1.1.1'
51 | })
52 | pkg.scripts = Object.assign({}, pkg.scripts, {
53 | test: 'jest',
54 | coverage: 'jest --coverage'
55 | })
56 | }
57 | if (pkgHasChanged) {
58 | fs.writeFileSync(
59 | path.join(folderPath, 'package.json'),
60 | JSON.stringify(pkg, null, 4),
61 | 'utf-8'
62 | )
63 | }
64 | this.walkAndReplace(
65 | folderPath,
66 | ['.js', '.jsx', '.ts', '.tsx', '.html', '.json'],
67 | {
68 | ProjectName: config.projectName.replace(/^[a-z]/, l =>
69 | l.toUpperCase()
70 | ),
71 | projectName: config.projectName.replace(/^[A-Z]/, L =>
72 | L.toLowerCase()
73 | )
74 | }
75 | )
76 | }
77 |
78 | module.exports = {
79 | files: [
80 | 'config',
81 | 'src',
82 | 'tools',
83 | 'typings',
84 | '.gitignore',
85 | '.lintstagedrc',
86 | '.npmrc',
87 | '.prettierrc',
88 | '.stylelintrc.js',
89 | 'package.json',
90 | 'postcss.config.js',
91 | 'qshell.qa.json',
92 | 'qshell.prod.json',
93 | 'README.md',
94 | 'tsconfig.json',
95 | 'tsconfig.webpack.json',
96 | 'tslint.json'
97 | ],
98 | beforeInstallCopy: function(answers, folderPath, files) {
99 | console.log('------------- beforeInstallCopy -------------')
100 | if (!!answers.jest) {
101 | files.push('test', 'jest.config.js')
102 | }
103 | },
104 | beforeInstallDep: function(answers, folderPath) {
105 | console.log('------------- beforeInstallDep -------------')
106 | onInstall.bind(this)(folderPath, answers)
107 | },
108 | beforeUpdateDep: function() {
109 | console.log('------------- beforeUpdateDep -------------')
110 | const folderPath = process.cwd()
111 | const answersConfig = this.readKitConfig(
112 | path.join(folderPath, 'config/steamer.config.js')
113 | )
114 | onInstall.bind(this)(folderPath, answersConfig)
115 | },
116 | options: [
117 | {
118 | type: 'input',
119 | name: 'webserver',
120 | message: 'dev server domain(//localhost)',
121 | default: '//localhost'
122 | },
123 | {
124 | type: 'input',
125 | name: 'cdn',
126 | message: 'common cdn domain(//ss.yidejia.com)',
127 | default: '//ss.yidejia.com'
128 | },
129 | {
130 | type: 'input',
131 | name: 'port',
132 | message: 'dev server port(9000)',
133 | default: '9000'
134 | },
135 | {
136 | type: 'confirm',
137 | name: 'jest',
138 | message: 'wanna use jest to unit test?(N)',
139 | default: false
140 | },
141 | {
142 | type: 'confirm',
143 | name: 'mobx',
144 | message: 'wanna use mobx instead of redux?(N)',
145 | default: false
146 | },
147 | {
148 | type: 'confirm',
149 | name: 'svg',
150 | message: 'support svg component?(N)',
151 | default: false
152 | }
153 | ]
154 | }
155 |
--------------------------------------------------------------------------------
/.steamer/support-svg-component.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const svgModifiedPath = path.join(__dirname, '.svgModified')
4 |
5 | module.exports = function(folderPath) {
6 | // 修改fileRules(添加svg loader)
7 | const fileRulesPath = path.join(folderPath, 'tools/rules/fileRules.js')
8 | this.fs.copySync(
9 | path.join(svgModifiedPath, 'tools/rules/fileRules.js'),
10 | fileRulesPath
11 | )
12 |
13 | // 修改入口scss
14 | const scssPath = path.join(folderPath, 'src/app.scss')
15 | this.fs.copySync(path.join(svgModifiedPath, 'src/app.scss'), scssPath)
16 |
17 | // 添加svg typing
18 | const svgTypingPath = path.join(folderPath, 'typings/svg.d.ts')
19 | this.fs.copySync(
20 | path.join(svgModifiedPath, 'typings/svg.d.ts'),
21 | svgTypingPath
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | **/css/sprites/**
2 | **/dist/**/*.css
3 | **/*.{ts, tsx}
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['stylelint-config-ydj/scss', 'stylelint-config-ydj/prettier', 'stylelint-config-css-modules'],
3 | ignoreFiles: [
4 | '**/*.md',
5 | '**/*.ts',
6 | '**/*.tsx',
7 | '**/*.js'
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # steamer-react-ts
2 |
3 | 伊的家前端出品: react + typescript 项目脚手架
4 |
5 | ## 说明
6 |
7 | 本脚手架基于AlloyTeam出品的steamer体系制作,详情[请点击前往](https://steamerjs.github.io/)
8 |
9 | ## 使用文档
10 |
11 | [点击前往](docs/index.md)
12 |
13 |
--------------------------------------------------------------------------------
/config/project.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | let utils = require('steamer-webpack-utils');
3 | let steamerConfig = require('./steamer.config');
4 | let __basename = path.dirname(__dirname);
5 | let __env = process.env.NODE_ENV || 'development';
6 | let __app_env = process.env.APP_ENV || 'dev';
7 |
8 | let isProduction = __env === 'production';
9 |
10 | let srcPath = path.resolve(__basename, 'src');
11 | let devPath = path.resolve(__basename, 'dev');
12 | let distPath = path.resolve(__basename, 'dist');
13 | let spritePath = path.resolve(__basename, 'src/img/sprites');
14 |
15 | let hash = '[hash:6]';
16 | let chunkhash = '[chunkhash:6]';
17 | let contenthash = '[contenthash:6]';
18 |
19 | // ========================= webpack快捷配置 =========================
20 | // 基本情况下,你只需要关注这里的配置
21 | let config = {
22 | // ========================= webpack环境配置 =========================
23 | env: __env,
24 |
25 | appEnv: __app_env,
26 |
27 | // 默认使用的npm命令行
28 | npm: 'npm',
29 |
30 | webpack: {
31 |
32 | useCdn: true, // 是否使用webserver, cdn 分离 html 与其它静态资源
33 |
34 | // ========================= webpack路径与url =========================
35 | // 项目路径
36 | path: {
37 | src: srcPath,
38 | dev: devPath,
39 | dist: distPath,
40 | sprite: spritePath,
41 | distCdn: 'cdn', // 生成cdn的目录,dist/cdn
42 | distWebserver: 'webserver' // 生成webserver的目录, dist/webserver
43 | },
44 |
45 | // ========================= webpack服务器及路由配置 =========================
46 | // 开发服务器配置
47 | webserver: `${steamerConfig.webserver}:${steamerConfig.port}/`,
48 | cdn: `${steamerConfig.cdn}/<% projectName %>/dist/${__app_env}/`,
49 | cssCdn: `${steamerConfig.cssCdn || steamerConfig.cdn}/<% projectName %>/dist/${__app_env}/`,
50 | imgCdn: `${steamerConfig.imgCdn || steamerConfig.cdn}/<% projectName %>/dist/${__app_env}/`,
51 | port: steamerConfig.port, // port for local server
52 | route: [], // proxy route, 例如: /news/
53 |
54 | 'api-port': steamerConfig['api-port'] || 7000, // 后台转发端口,默认配合 steamer-plugin-mock 使用
55 | 'api-route': steamerConfig['api-route'] || [], // 后台转发路径
56 |
57 | // ========================= webpack自定义配置 =========================
58 | // 是否显示开发环境下的生成文件
59 | showSource: true,
60 |
61 | // 是否在生产环境下生成manifest文件
62 | manifest: false,
63 |
64 | // 是否清理生成文件夹
65 | clean: true,
66 |
67 | // sourcemap, 请写具体的sourcemap名称,而不是写true
68 | // 参考文章: https://segmentfault.com/a/1190000004280859
69 | sourceMap: {
70 | development: 'cheap-module-eval-source-map',
71 | production: false,
72 | },
73 |
74 | cssSourceMap: false,
75 |
76 | // 预编译器,默认支持css 和 less. sass, scss 和 stylus,会自动安装
77 | style: [
78 | 'css', 'scss'
79 | ],
80 |
81 | // 生产环境是否提取css
82 | extractCss: true,
83 |
84 | // 是否启用css模块化
85 | cssModule: true,
86 |
87 | // 合图,normal (仅1倍图) , retinaonly (仅2倍图), retina (包括1倍及2倍图), none (不使用合图)
88 | spriteMode: 'retinaonly',
89 | // 默认支持less. sass, scss 和 stylus,会自动安装
90 | spriteStyle: 'scss',
91 |
92 | // html 模板. 默认支持html 和 ejs, handlebars 和 pug(原jade),art(art-template) 会自动安装
93 | template: [
94 | 'html'
95 | ],
96 |
97 | // 生产环境下资源(js, css, html)是否压缩
98 | compress: true,
99 |
100 | // 不经webpack打包的资源
101 | static: [
102 | {
103 | src: 'libs/',
104 | hash: true
105 | }
106 | ],
107 |
108 | // 利用DefinePlugin给应用注入变量
109 | injectVar: {
110 | 'process.env': {
111 | NODE_ENV: JSON.stringify(__env),
112 | APP_ENV: JSON.stringify(__app_env)
113 | }
114 | },
115 |
116 | // webpack resolve.alias 包别名
117 | alias: {
118 | 'env': path.join(srcPath, 'env', `${__app_env}.ts`)
119 | },
120 |
121 | // 文件名与哈希, hash, chunkhash, contenthash 与webpack的哈希配置对应
122 | hash: hash,
123 | chunkhash: chunkhash,
124 | contenthash: contenthash,
125 | hashName: isProduction ? ('[name]-' + hash) : '[name]',
126 | chunkhashName: isProduction ? ('[name]-' + chunkhash) : '[name]',
127 | contenthashName: isProduction ? ('[name]-' + contenthash) : '[name]',
128 |
129 | // ========================= webpack entry配置 =========================
130 | // 多页模式
131 | // 根据约定,自动扫描js entry,约定是src/page/xxx/main.js 或 src/page/xxx/main.jsx
132 | /**
133 | 获取结果示例
134 | {
135 | 'index': [path.join(configWebpack.path.src, "/page/index/main.js")],
136 | 'spa': [path.join(configWebpack.path.src, "/page/spa/main.js")],
137 | 'pindex': [path.join(configWebpack.path.src, "/page/pindex/main.jsx")],
138 | }
139 | */
140 | // entry: utils.filterJsFileByCmd(utils.getJsEntry({
141 | // srcPath: path.join(srcPath, 'page'),
142 | // fileName: 'main',
143 | // extensions: ['js', 'jsx', 'ts', 'tsx'],
144 | // level: 1
145 | // })),
146 |
147 | // 单页模式
148 | entry: {
149 | app: [path.join(srcPath, 'app.tsx')]
150 | },
151 |
152 | // 多页模式
153 | // 自动扫描html,配合html-res-webpack-plugin
154 | /**
155 | 获取结果示例
156 | [
157 | {
158 | key: 'index',
159 | path: 'path/src/page/index/index.html'
160 | },
161 | {
162 | key: 'spa',
163 | path: 'path/src/page/spa/index.html'
164 | },
165 | {
166 | key: 'pindex',
167 | path: 'path/src/page/pindex/index.html'
168 | }
169 | ]
170 | */
171 | // html: utils.filterHtmlFileByCmd(utils.getHtmlEntry({
172 | // srcPath: path.join(srcPath, 'page'),
173 | // level: 1
174 | // })),
175 |
176 | // 单页模式
177 | html: [{
178 | key: 'index',
179 | path: path.join(srcPath, 'main.html')
180 | }],
181 |
182 | // 自动扫描合图,配合webpack-spritesmith插件
183 | /**
184 | 获取结果示例
185 | [
186 | {
187 | key: 'btn',
188 | path: 'path/src/img/sprites/btn'
189 | },
190 | {
191 | key: 'list',
192 | path: 'path/src/img/sprites/list'
193 | }
194 | ]
195 | */
196 | sprites: utils.getSpriteEntry({
197 | srcPath: spritePath
198 | })
199 |
200 | }
201 | };
202 |
203 | // ========================= webpack深度配置 =========================
204 | // 使用了webpack-merge与webpack.base.js进行配置合并
205 | // 如果上面的配置仍未能满足你,你可以在此处对webpack直接进行配置,这里的配置与webpack的配置项目一一对应
206 | config.custom = {
207 | // webpack output
208 | getOutput: function() {
209 | return {};
210 | },
211 |
212 | // webpack module
213 | getModule: function() {
214 | let module = {
215 | rules: []
216 | };
217 |
218 | return module;
219 | },
220 |
221 | // webpack resolve
222 | getResolve: function() {
223 | return {
224 | alias: config.webpack.alias
225 | };
226 | },
227 |
228 | // webpack plugins
229 | getPlugins: function() {
230 | let plugins = [];
231 |
232 | return plugins;
233 | },
234 |
235 | // webpack externals
236 | getExternals: function() {
237 | if (isProduction) {
238 | return {
239 | 'react': 'React',
240 | 'react-dom': 'ReactDOM',
241 | 'preact': 'preact'
242 | };
243 | }
244 |
245 | return {};
246 | },
247 |
248 | // 其它 webpack 配置
249 | getOtherOptions: function() {
250 | return {
251 | optimization: {}
252 | };
253 | }
254 | };
255 |
256 | // ========================= webpack merge 的策略 =========================
257 | config.webpackMerge = {
258 | // webpack-merge smartStrategy 配置
259 | smartStrategyOption: {
260 | 'module.rules': 'append',
261 | 'plugins': 'append'
262 | },
263 |
264 | // 在smartStrategy merge 之前,用户可以先行对 webpack.base.js 的配置进行处理
265 | mergeProcess: function(webpackBaseConfig) {
266 | return webpackBaseConfig;
267 | }
268 | };
269 |
270 | module.exports = config;
271 |
--------------------------------------------------------------------------------
/config/steamer.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | "webserver": "//localhost",
4 | "cdn": "//ss.yidejia.com", // 注意这里结尾不要加`/`
5 | "port": "9000",
6 | "jest": false,
7 | "mobx": false
8 | }
--------------------------------------------------------------------------------
/docs/css.md:
--------------------------------------------------------------------------------
1 | # CSS
2 |
3 | 以下所有支持方案皆带postcss能力,具体配置可根据需要修改项目根目录下的`postcss.config.js`
4 |
5 | ## scss + css-module
6 |
7 | 脚手架默认使用的样式方案。
8 |
9 | 采用scss作为预编译语言,搭配css-module的模块化方案。
10 |
11 | `src/styles`目录下的所有scss文件皆是公用的scss能力,不推荐这里编写实际的样式,避免产生大量重复的css样式输出。
12 |
13 | 全局基础样式可在`src/app.scss`下面编写。
14 |
15 | 在css-module方案下,所有组件的样式的class命名都可以用简单易懂的单词进行描述。关于cssModule的介绍,可[点击前往](https://github.com/css-modules/css-modules)
16 |
17 | 如果需要复写第三方className, 可在嵌套的指定位置声明
18 |
19 | ```scss
20 | //...
21 | :global {
22 | .exitsClassName {
23 | // ...
24 | }
25 | }
26 |
27 | //...
28 | ```
29 |
30 |
31 | ## styled-component
32 |
33 | TODO: 待完善
34 |
35 |
36 | ## less
37 |
38 | TODO: 待完善
39 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ## 环境准备
2 | - 本脚手架依赖`steamerjs`和`steamer-plugin-kit`两个包
3 |
4 | ```shell
5 | $ npm i -g steamerjs@latest steamer-plugin-kit@latest
6 | ```
7 |
8 | 注意: 这个脚手架依赖的steamerjs>=3.0.0
9 |
10 | ## 使用
11 |
12 | - 确保环境依赖正确
13 |
14 | ```shell
15 | $ str --verion
16 | # 检查版本是否大于3.0.0
17 |
18 | $ str kit --version
19 | # 检查版本是否大于3.0.0
20 | ```
21 |
22 | 如果没有正确打印版本号,请[前往安装依赖](#环境准备)
23 |
24 | - 安装脚手架
25 |
26 | ```shell
27 | $ str kit --add https://github.com/YDJ-FE/steamer-react-ts
28 | ```
29 |
30 | - 创建项目
31 |
32 | ```shell
33 | $ str kit
34 | # 选择脚手架, 版本,按引导一步一步输入项目信息
35 | ```
36 |
37 | - 更新脚手架
38 |
39 | ```shell
40 | $ str kit -u --global
41 | ```
42 |
43 | - 更新脚手架到项目
44 | ```shell
45 | # cd 到你项目根目录
46 | $ str kit -u
47 | ```
48 |
49 | - 添加模板代码到项目
50 |
51 | ```shell
52 | $ str template
53 | # 按提示操作
54 | ```
55 |
56 | ## 文件目录
57 |
58 | [点击跳转](./tree.md)
59 |
60 | ## css
61 |
62 | 默认使用*css-module* 和 _scss_
63 |
64 | 你也可以使用你喜欢的预处理语言和模块化方式, e.g. `less`, `stylus`, 又或者`styled-components`, `scss-bem`
65 |
66 | [详情点击](./css.md)
67 |
68 | ## js
69 |
70 | 目前仅支持 typescript,如果需要 js,推荐[steamer-react](https://github.com/steamerjs/steamer-react)官方脚手架
71 |
72 | [详情点击](./js.md)
73 |
74 | ## 状态管理
75 |
76 | 默认使用 redux
77 |
78 | 支持选择 mobx
79 |
80 | [详情点击](./store.md)
81 |
82 | ## 可选的 svg 支持
83 |
84 | 使用[@svgr/webpack](https://github.com/smooth-code/svgr/tree/master/packages/webpack)
85 |
86 | [使用示例](./svg.md)
87 |
--------------------------------------------------------------------------------
/docs/js.md:
--------------------------------------------------------------------------------
1 | # js
2 |
3 | Only Typescript!
4 |
5 | TODO: 待完善
--------------------------------------------------------------------------------
/docs/store.md:
--------------------------------------------------------------------------------
1 | # state manager
2 |
3 | ## Redux
4 |
5 | 脚手架默认使用的状态管理工具
6 |
7 | 默认搭配: redux + typesafe-action + reselect + redux-saga
8 |
9 |
10 | ## Mobx
11 | TODO: 待完善
--------------------------------------------------------------------------------
/docs/svg.md:
--------------------------------------------------------------------------------
1 | ## 例
2 |
3 | ```ts
4 | import IconReact from 'assets/svg/react.svg'
5 | ```
6 |
7 | ```jsx
8 | ...
9 | render() {
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 | ...
17 | ```
18 |
--------------------------------------------------------------------------------
/docs/test.md:
--------------------------------------------------------------------------------
1 | # 测试
2 |
3 | ``` shell
4 | $ steamer kit -d [aliasName]
5 | $ cd
6 | $ steamer kit
7 | ```
8 |
9 | 通过`steamer kit -d [aliasName]`会生成一个软链接在`~/.steamer/starterkits/`, 并已当前的版本号命令产生一个分支
10 |
11 | 然后可以一边开发, 一边在新的folder里面init项目
--------------------------------------------------------------------------------
/docs/tree.md:
--------------------------------------------------------------------------------
1 | # File Tree
2 | ``` javascript
3 | .
4 | ├── .steamer
5 | │ └── steamer-plugin-kit.js // 脚本配置
6 | ├── .stylelintignore // stylelint配置
7 | ├── .stylelintrc.js // stylelint配置
8 | ├── README.md // 项目说明
9 | ├── config // 项目配置
10 | │ ├── project.js // 构建相关配置
11 | │ └── steamer.config.js // steamer相关配置
12 | ├── package.json
13 | ├── postcss.config.js // postcss配置
14 | ├── qshell.qa.json // 测试环境qshell配置,创建项目后需要修改
15 | ├── qshell.prod.json // 生产环境配置, 创建项目后需要修改
16 | ├── src
17 | │ ├── app.scss // 公共样式
18 | │ ├── app.tsx // 入口
19 | │ ├── assets // 多媒体资源
20 | │ │ ├── images/
21 | │ │ └── svgs/
22 | │ ├── components/ // 展示组件
23 | │ ├── configureStore.ts // redux store 配置
24 | │ ├── containers // 容器组件
25 | │ │ ├── shared // 可共享容器组件
26 | │ │ └── views // 页面
27 | │ ├── env // 环境配置
28 | │ │ ├── dev.ts
29 | │ │ ├── prod.ts
30 | │ │ └── qa.ts
31 | │ ├── libs // 不编译的第三方库
32 | │ │ ├── preact.js
33 | │ │ ├── react-dom.js
34 | │ │ └── react.js
35 | │ ├── main.html // 页面模板
36 | │ ├── reducers.ts // reducer入口
37 | │ ├── services // api服务
38 | │ │ ├── api.ts
39 | │ │ └── contants.ts
40 | │ ├── styles // scss能力库
41 | │ │ ├── _base.scss
42 | │ │ ├── _functions.scss
43 | │ │ ├── _mixins.scss
44 | │ │ └── _var.scss
45 | │ └── util // 工具
46 | │ ├── constants.ts
47 | │ ├── http.ts
48 | │ ├── reducerInjectors.ts
49 | │ └── sagaInjectors.ts
50 | ├── tools // 构建相关工具和配置
51 | │ ├── feature // TODO
52 | │ ├── optimization // 优化相关
53 | │ ├── plugins // 插件相关
54 | │ │ ├── basePlugins.js
55 | │ │ ├── cachePlugins.js
56 | │ │ ├── resourcePlugins.js
57 | │ │ └── spritePlugins.js
58 | │ ├── rules // loader相关
59 | │ │ ├── fileRules.js
60 | │ │ ├── htmlRules.js
61 | │ │ ├── jsRules.js
62 | │ │ └── styleRules.js
63 | │ ├── script.js // 构建启动脚本
64 | │ ├── server.js // 开发服务器
65 | │ ├── template // 脚手架代码模板
66 | │ │ ├── dependency.js
67 | │ │ └── viewTemplate
68 | │ └── webpack.base.js
69 | ├── tsconfig.json // tsconfig
70 | ├── tslint.json // tslint配置
71 | └── typings // ts类型声明
72 | ```
73 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ["js", "ts", "tsx"],
3 | moduleNameMapper: {
4 | "\\.(css|scss)$": "identity-obj-proxy"
5 | },
6 | transform: {
7 | "^.+\\.tsx?$": "ts-jest"
8 | },
9 | testMatch: [
10 | "/test/spec/**/?(*.)(spec|test).ts?(x)"
11 | ],
12 | verbose: true
13 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "steamer-react-ts",
3 | "version": "1.4.9",
4 | "description": "伊的家 react+ts脚手架",
5 | "license": "MIT",
6 | "scripts": {
7 | "lint": "lint-staged",
8 | "dev": "cross-env NODE_ENV=development node ./tools/script.js",
9 | "build:qa": "cross-env NODE_ENV=production APP_ENV=qa node ./tools/script.js",
10 | "deploy:qa": "npm run build:qa && qshell qupload ./qshell.qa.json",
11 | "build:prod": "cross-env NODE_ENV=production APP_ENV=prod node ./tools/script.js",
12 | "deploy:prod": "npm run build:prod && qshell qupload ./qshell.prod.json"
13 | },
14 | "main": "./.steamer/steamer-react-redux-ts.js",
15 | "keywords": [
16 | "steamer starterkit"
17 | ],
18 | "pre-commit": [
19 | "lint"
20 | ],
21 | "dependencies": {
22 | "antd": "^3.5.0",
23 | "axios": "^0.18.0",
24 | "blueimp-md5": "^2.10.0",
25 | "bourbon": "^5.0.0",
26 | "lodash": "^4.17.10",
27 | "moment": "^2.22.1",
28 | "offline-plugin": "^5.0.3",
29 | "prop-types": "^15.6.1",
30 | "qs": "^6.5.2",
31 | "react": "16.2.0",
32 | "react-dom": "16.2.0",
33 | "react-loadable": "^5.3.1",
34 | "react-redux": "^5.0.7",
35 | "react-router": "^4.2.0",
36 | "react-router-dom": "^4.2.2",
37 | "react-router-redux": "^5.0.0-alpha.9",
38 | "redux": "^3.7.2",
39 | "redux-immutable": "^4.0.0",
40 | "redux-saga": "^0.16.0",
41 | "reselect": "^3.0.1",
42 | "styled-components": "^3.2.6"
43 | },
44 | "devDependencies": {
45 | "@types/lodash": "^4.14.109",
46 | "@types/node": "^10.0.4",
47 | "@types/react": "^16.3.13",
48 | "@types/react-dom": "^16.0.5",
49 | "@types/react-hot-loader": "^4.1.0",
50 | "@types/react-redux": "^6.0.0",
51 | "@types/react-router-dom": "^4.3.0",
52 | "@types/react-router-redux": "^5.0.13",
53 | "@types/webpack-env": "^1.13.6",
54 | "autoprefixer": "^8.3.0",
55 | "awesome-typescript-loader": "^5.0.0",
56 | "browserslist": "^3.2.4",
57 | "cache-loader": "^1.2.2",
58 | "caniuse-db": "^1.0.30000830",
59 | "clean-webpack-plugin": "^0.1.19",
60 | "copy-webpack-plugin-hash": "^6.0.0",
61 | "cross-env": "^5.1.4",
62 | "css-loader": "^0.28.11",
63 | "exports-loader": "^0.7.0",
64 | "expose-loader": "^0.7.5",
65 | "express": "^4.16.3",
66 | "file-loader": "^1.1.11",
67 | "file-webpack-plugin": "^2.0.0",
68 | "fs-extra": "^6.0.1",
69 | "hoist-non-react-statics": "^2.5.5",
70 | "html-loader": "^0.5.5",
71 | "html-res-webpack-plugin": "^4.0.0",
72 | "http-proxy-middleware": "^0.18.0",
73 | "identity-obj-proxy": "^3.0.0",
74 | "imports-loader": "^0.8.0",
75 | "invariant": "^2.2.4",
76 | "lint-staged": "^7.2.0",
77 | "mini-css-extract-plugin": "^0.4.0",
78 | "node-sass": "^4.9.0",
79 | "optimize-css-assets-webpack-plugin": "^4.0.0",
80 | "postcss-assets": "^5.0.0",
81 | "postcss-import": "^11.1.0",
82 | "postcss-loader": "^2.1.4",
83 | "pre-commit": "^1.2.2",
84 | "react-hot-loader": "^4.1.3",
85 | "sanitize.css": "^5.0.0",
86 | "sass-loader": "^7.0.1",
87 | "spritesheet-templates-steamer": "^1.0.2",
88 | "steamer-webpack-utils": "^1.1.0",
89 | "style-loader": "^0.21.0",
90 | "stylelint": "^9.2.0",
91 | "stylelint-config-css-modules": "^1.2.0",
92 | "stylelint-config-ydj": "^0.1.1",
93 | "ts-jest": "^22.4.6",
94 | "tslint": "^5.10.0",
95 | "tslint-config-prettier": "^1.12.0",
96 | "typescript": "^2.8.3",
97 | "typings-for-css-modules-loader": "^1.7.0",
98 | "uglifyjs-webpack-plugin": "^1.2.5",
99 | "url-loader": "^1.0.1",
100 | "webpack": "^4.6.0",
101 | "webpack-dev-middleware": "^3.1.2",
102 | "webpack-hot-middleware": "^2.22.1",
103 | "webpack-manifest-plugin": "^2.0.1",
104 | "webpack-merge": "^4.1.2",
105 | "webpack-spritesmith": "^0.4.1",
106 | "write-file-webpack-plugin": "^4.2.0"
107 | },
108 | "peerDependencies": {},
109 | "engines": {
110 | "node": ">=8.9.4"
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | let config = require('./config/project');
2 | let PostcssImport = require('postcss-import');
3 | let Autoprefixer = require('autoprefixer');
4 | let PostcssAsset = require('postcss-assets');
5 |
6 | module.exports = {
7 | plugins: [
8 | PostcssImport({
9 | path: [config.webpack.path.src]
10 | }),
11 | Autoprefixer({
12 | browsers: ['iOS 7', '> 0.1%', 'android 2.1']
13 | }),
14 | PostcssAsset({
15 | loadPaths: [config.webpack.path.src]
16 | })
17 | ]
18 | };
19 |
--------------------------------------------------------------------------------
/qshell.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "src_dir": "./dist/cdn",
3 | "bucket": "ydj-statics",
4 | "key_prefix": "<% projectName %>/dist/prod/",
5 | "ignore_dir": false,
6 | "overwrite": false,
7 | "check_exists": false,
8 | "check_hash": false,
9 | "check_size": false,
10 | "rescan_local": true,
11 | "log_stdout": true
12 | }
13 |
--------------------------------------------------------------------------------
/qshell.qa.json:
--------------------------------------------------------------------------------
1 | {
2 | "src_dir": "./dist/cdn",
3 | "bucket": "ydj-statics",
4 | "key_prefix": "<% projectName %>/dist/qa/",
5 | "ignore_dir": false,
6 | "overwrite": false,
7 | "check_exists": false,
8 | "check_hash": false,
9 | "check_size": false,
10 | "rescan_local": true,
11 | "log_stdout": true
12 | }
13 |
--------------------------------------------------------------------------------
/src/app.scss:
--------------------------------------------------------------------------------
1 |
2 | @import "styles/_base.scss";
3 |
4 | :global {
5 | html, body {
6 | width: 100%;
7 | height: 100%;
8 | }
9 |
10 | #root {
11 | width: 100%;
12 | height: 100%;
13 | background: $body-background;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * app.js
3 | *
4 | * 程序的入口
5 | */
6 |
7 | import * as React from "react";
8 | import * as ReactDOM from "react-dom";
9 | import { Provider } from "react-redux";
10 | import { ConnectedRouter } from "react-router-redux";
11 | import configureStore from "./configureStore";
12 |
13 |
14 | // 如果依赖第三方UI库的样式,建议去掉sanitize.css的依赖
15 | import "sanitize.css/sanitize.css";
16 |
17 | /**
18 | * 这里开始你的第三方包依赖,包括css
19 | * Example
20 | *
21 | * import "antd/dist/antd.css"
22 | */
23 |
24 |
25 | // app global style
26 | import './app.scss'
27 |
28 | // Import root app
29 | import App from "containers/shared/App";
30 |
31 | // 如果用brwoserHistory方式的话服务器需要配合设置
32 | // import createHistory from 'history/createBrowserHistory'
33 | import createHistory from "history/createHashHistory";
34 |
35 |
36 |
37 |
38 | const initialState = {};
39 | const history = createHistory();
40 | const store = configureStore(initialState, history);
41 | const MOUNT_NODE = document.getElementById("root");
42 |
43 | const render = (Component) => {
44 | ReactDOM.render(
45 |
46 |
47 |
48 |
49 | ,
50 | MOUNT_NODE
51 | );
52 | };
53 |
54 | render(App)
55 |
56 | if (module.hot) {
57 | // 热更新React Components
58 | // module.hot.accept不支持动态的依赖
59 | // 必须是编译时定义的常量
60 | module.hot.accept(["containers/shared/App"], () => {
61 | ReactDOM.unmountComponentAtNode(MOUNT_NODE);
62 | render(require('containers/shared/App').default);
63 | });
64 | }
65 |
66 | // TODO: 待验证
67 | // Install ServiceWorker and AppCache in the end since
68 | // it's not most important operation and if main code fails,
69 | // we do not want it installed
70 | if (process.env.NODE_ENV === "production") {
71 | // tslint:disable-next-line:no-var-requires
72 | require("offline-plugin/runtime").install(); // eslint-disable-line global-require
73 | }
74 |
--------------------------------------------------------------------------------
/src/assets/images/logo404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YDJ-FE/steamer-react-ts/cb18e32841f2000e218a74476e29c3faebff1306/src/assets/images/logo404.png
--------------------------------------------------------------------------------
/src/components/ReducerInjector/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 高阶组件, 注入reducer
3 | */
4 |
5 | import * as React from "react";
6 | import { ReactPropTypes } from "react";
7 | // import hoisNonReactStatics from "hoist-non-react-statics";
8 | import getInjectors, { RESET_SUB_STATE } from "util/reducerInjectors";
9 | import { Reducer } from "redux";
10 | import * as PropTypes from "prop-types";
11 | import hoisNonReactStatics from 'hoist-non-react-statics'
12 |
13 | interface IP {
14 | store: IStore;
15 | [key: string]: any;
16 | }
17 |
18 | interface IInjectReducerParams {
19 | /**
20 | * reducer key
21 | *
22 | * @type {string}
23 | * @memberof IInjectReducerParams
24 | */
25 | key: string;
26 | /**
27 | * reducer instance
28 | *
29 | * @type {Reducer}
30 | * @memberof IInjectReducerParams
31 | */
32 | reducer: Reducer;
33 | /**
34 | * 如果为true, 则页面ummount之后仍保持state, 否则重置初始值
35 | * 默认false
36 | *
37 | * @type {boolean}
38 | * @memberof IInjectReducerParams
39 | */
40 | keepStateAlive?: boolean;
41 | }
42 |
43 | export default ({ key, reducer, keepStateAlive }: IInjectReducerParams) => (
44 | WrapperComponent: React.ComponentType
45 | ) => {
46 | class ReducerInjector extends React.Component {
47 | public static WrapperComponent = WrapperComponent;
48 |
49 | public static displayName = `withReducer(${WrapperComponent.displayName ||
50 | WrapperComponent.name ||
51 | "Component"})`;
52 |
53 | static contextTypes = {
54 | store: PropTypes.object.isRequired
55 | };
56 |
57 | injectors = getInjectors(this.context.store);
58 |
59 | componentWillMount() {
60 | const { injectReducer } = this.injectors;
61 | injectReducer(key, reducer);
62 | }
63 |
64 | componentWillUnmount() {
65 | if (!keepStateAlive) {
66 | this.context.store.dispatch({ type: RESET_SUB_STATE, key });
67 | }
68 | }
69 |
70 | render() {
71 | return ;
72 | }
73 | }
74 |
75 | return hoisNonReactStatics(ReducerInjector, WrapperComponent);
76 | };
77 |
--------------------------------------------------------------------------------
/src/components/SagaInjector/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 高阶组件 注入saga
3 | *
4 | */
5 | import * as React from 'react'
6 | import * as PropTypes from 'prop-types'
7 | import hoisNonReactStatics from 'hoist-non-react-statics'
8 | import getInjectors from 'util/sagaInjectors'
9 | import { SagaIterator } from 'redux-saga'
10 |
11 | interface IP {
12 | store: IStore
13 | [key: string]: any
14 | }
15 |
16 | interface IInjectSagaParams {
17 | /**
18 | * saga的名字(key)
19 | *
20 | * @type {string}
21 | * @memberof IInjectSagaParams
22 | */
23 | key: string
24 | /**
25 | * saga对象
26 | *
27 | * @type {SagaIterator}
28 | * @memberof IInjectSagaParams
29 | */
30 | saga: SagaIterator
31 | mode?: string
32 | }
33 |
34 | /**
35 | * Dynamically injects a saga, passes component's props as saga arguments
36 | *
37 | * @param {string} key A key of the saga
38 | * @param {Saga} saga A root saga that will be injected
39 | * @param {string} [mode] By default (constants.RESTART_ON_REMOUNT) the saga will be started on component mount and
40 | * cancelled with `task.cancel()` on component un-mount for improved performance. Another two options:
41 | * - constants.DAEMON—starts the saga on component mount and never cancels it or starts again,
42 | * - constants.ONCE_TILL_UNMOUNT—behaves like 'RESTART_ON_REMOUNT' but never runs it again.
43 | *
44 | */
45 | export default ({ key, saga, mode }: IInjectSagaParams) => (
46 | WrappedComponent: React.ComponentType
47 | ) => {
48 | class InjectSaga extends React.Component {
49 | static WrappedComponent = WrappedComponent
50 | static displayName = `withSaga(${WrappedComponent.displayName ||
51 | WrappedComponent.name ||
52 | 'Component'})`
53 |
54 | static contextTypes = {
55 | store: PropTypes.object.isRequired
56 | }
57 |
58 | componentWillMount() {
59 | const { injectSaga } = this.injectors
60 |
61 | injectSaga(key, { saga, mode }, this.props)
62 | }
63 |
64 | componentWillUnmount() {
65 | const { ejectSaga } = this.injectors
66 |
67 | ejectSaga(key)
68 | }
69 |
70 | // tslint:disable-next-line:member-ordering
71 | injectors = getInjectors(this.context.store)
72 |
73 | render() {
74 | return
75 | }
76 | }
77 | return hoisNonReactStatics(InjectSaga, WrappedComponent)
78 | }
79 |
--------------------------------------------------------------------------------
/src/configureStore.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Create the store with dynamic reducers
3 | */
4 |
5 | import Redux, { createStore, applyMiddleware, compose } from "redux";
6 | import { fromJS } from "immutable";
7 | import { routerMiddleware } from "react-router-redux";
8 | import createSagaMiddleware from "redux-saga";
9 | import createReducer from "./reducers";
10 |
11 | import { Task, SagaIterator } from "redux-saga";
12 |
13 | const sagaMiddleware = createSagaMiddleware();
14 |
15 | export default function configureStore(initialState = {}, history) {
16 | // Create the store with two middlewares
17 | // 1. sagaMiddleware: Makes redux-sagas work
18 | // 2. routerMiddleware: Syncs the location/URL path to the state
19 | const middlewares = [sagaMiddleware, routerMiddleware(history)];
20 |
21 | const enhancers = [applyMiddleware(...middlewares)];
22 |
23 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
24 | /* eslint-disable no-underscore-dangle */
25 | const composeEnhancers =
26 | process.env.NODE_ENV !== "production" &&
27 | typeof window === "object" &&
28 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
29 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
30 | // TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
31 | // Prevent recomputing reducers for `replaceReducer`
32 | shouldHotReload: false
33 | })
34 | : compose;
35 | /* eslint-enable */
36 |
37 | const store: IStore = createStore(
38 | createReducer(),
39 | fromJS(initialState),
40 | composeEnhancers(...enhancers)
41 | );
42 |
43 | // Extensions
44 | store.runSaga = sagaMiddleware.run;
45 | store.injectedReducers = {}; // Reducer registry
46 | store.injectedSagas = {}; // Saga registry
47 |
48 | // Make reducers hot reloadable, see http://mxs.is/googmo
49 | /* istanbul ignore next */
50 | if (module.hot) {
51 | module.hot.accept("./reducers", () => {
52 | store.replaceReducer(createReducer(store.injectedReducers));
53 | });
54 | }
55 |
56 | return store;
57 | }
58 |
--------------------------------------------------------------------------------
/src/containers/shared/App/actions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * App Actions
3 | *
4 | * * To add a new Action:
5 | * 1) Import your constant
6 | * 2) Add a function like this:
7 | * export function yourAction(var) {
8 | * return { type: YOUR_ACTION_CONSTANT, var: var }
9 | * }
10 | */
11 |
12 |
--------------------------------------------------------------------------------
/src/containers/shared/App/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * AppConstants
3 | * 每个action都有相应的type, 好让reducer知道如何处理
4 | * 避免在reducer和action之间使用了错别字, 我们都从这里获取相同的constants
5 | * 我们推荐加前缀比如 ‘yourproject/yourComponent’避免reducer搞错action,类似命名空间
6 | *
7 | * Follow this format:
8 | * export const YOUR_ACTION_CONSTANT = 'pageName/containerName/YOUR_ACTION_CONSTANT';
9 | */
10 |
11 | const PAGENAME = 'finance/App'
12 |
13 | export const DEMO_TYPE = `${PAGENAME}/DEMO_TYPE`
14 |
--------------------------------------------------------------------------------
/src/containers/shared/App/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App
4 | *
5 | * 这个组件是每个页面的最外层骨架,所以这里的代码只能存放所有页面公有的, 比如(导航)或者路由
6 | * 切勿在其他组件引用
7 | */
8 |
9 | import * as React from "react";
10 | import { Switch, Route } from "react-router-dom";
11 |
12 | import HomePage from "containers/views/Home";
13 | import NotFoundPage from "containers/views/NotFound";
14 |
15 |
16 | import * as styles from "./style.scss";
17 |
18 | const AppWrapper = props => (
19 | {props.children}
20 | );
21 |
22 | export default function App(props) {
23 | return (
24 |
25 |
26 |
27 |
28 | {/*
29 | */}
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/containers/shared/App/reducer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * AppReducer
3 | *
4 | * 全局的reducer
5 | *
6 | * Example
7 | * case YOUR_ACTION_CONSTANT:
8 | * return state.set('yourStateVarivle', yourActionPayload)
9 | */
10 |
11 | import { fromJS } from "immutable";
12 | import { AnyAction } from "redux";
13 |
14 | import * as types from './constants'
15 |
16 | // The initial state of the App
17 |
18 | const initialState = fromJS({
19 | loading: false,
20 | });
21 |
22 | function appReducer(state = initialState, action: AnyAction) {
23 | switch (action.type) {
24 | case types.DEMO_TYPE:
25 | return state
26 | .set("loading", true)
27 |
28 | default:
29 | return state;
30 | }
31 | }
32 |
33 | export default appReducer;
34 |
--------------------------------------------------------------------------------
/src/containers/shared/App/selectors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The global state selectors
3 | *
4 | * 添加一个selector(functoin),提取state的数据并计算
5 | *
6 | * Example
7 | * const yourSelector = state => state.get('rootStateField')
8 | *
9 | * or
10 | * const yourSelect = () => createSelector(parentSelector, selectorState => selectorState.get('stateField') )
11 | *
12 | */
13 |
14 | import { createSelector } from "reselect";
15 |
16 | export const selectGlobal = state => state.get("global");
17 |
18 | export const selectRoute = state => state.get("route");
19 |
20 |
21 | export const makeSelectLoading = () =>
22 | createSelector(selectGlobal, globalState => globalState.get("loading"));
23 |
--------------------------------------------------------------------------------
/src/containers/shared/App/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .appWrapper {
4 | display: flex;
5 | width: 100%;
6 | height: 100%;
7 | overflow: auto;
8 | padding: 16px;
9 | margin: 0 auto;
10 | min-height: 100%;
11 | flex-direction: column;
12 | }
--------------------------------------------------------------------------------
/src/containers/views/Home/actions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Home Actions
3 | *
4 | * * To add a new Action:
5 | * 1) Import your constant
6 | * 2) Add a function like this:
7 | * export function yourAction(var) {
8 | * return { type: YOUR_ACTION_CONSTANT, var: var }
9 | * }
10 | */
11 |
12 | import {
13 | DEMO_TYPE
14 | } from "./constants";
15 |
16 | export function demoAction() {
17 | return {
18 | type: DEMO_TYPE
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/containers/views/Home/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Home Constants
3 | *
4 | * 每个action都有相应的type, 好让reducer知道如何处理
5 | * 避免在reducer和action之间使用了错别字, 我们都从这里获取相同的constants
6 | * 我们推荐加前缀比如 ‘yourproject/yourComponent’避免reducer搞错action,类似命名空间
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'pageName/containerName/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | const PAGENAME = 'finanace/Home'
13 |
14 | export const DEMO_TYPE = `${PAGENAME}/DEMO_TYPE`
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/containers/views/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { compose } from "redux";
3 | import { connect } from "react-redux";
4 | import injectReducer from "components/ReducerInjector";
5 | import injectSaga from "components/SagaInjector";
6 | import { createStructuredSelector } from "reselect";
7 |
8 | import reducer from "./reducer";
9 | import saga from "./saga";
10 | import * as selectors from "./selectors";
11 | import * as actions from "./actions";
12 |
13 | import * as styles from "./style.scss";
14 |
15 | interface IP {
16 | demoProps: any
17 | }
18 |
19 | class Home extends React.PureComponent {
20 | render() {
21 | return
22 |
Hello World!
23 | ;
24 | }
25 | }
26 |
27 | const mapDispatchToProps = dispatch => ({
28 | });
29 |
30 | const mapStateToProps = createStructuredSelector({
31 | // TODO: add Selector
32 | });
33 |
34 | const withReducer = injectReducer({ key: "home", reducer });
35 | const withSaga = injectSaga({ key: "home", saga });
36 | const withConnect = connect(mapStateToProps, mapDispatchToProps);
37 |
38 | export default compose(withReducer, withSaga, withConnect)(Home);
39 |
--------------------------------------------------------------------------------
/src/containers/views/Home/reducer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Home Reducer
3 | *
4 | * The reducer takes care of our data. Using actions, we can change our
5 | * application state.
6 | * To add a new action, add it to the switch statement in the reducer function
7 | *
8 | * Example
9 | * case YOUR_ACTION_CONSTANT:
10 | * return state.set('yourStateVarivle', yourActionPayload)
11 | */
12 |
13 | import { fromJS } from "immutable";
14 | import { AnyAction } from "redux";
15 | import { DEMO_TYPE } from "./constants";
16 |
17 | const initialState = fromJS({
18 | });
19 |
20 | export default (state = initialState, action: AnyAction) => {
21 | switch (action.type) {
22 | default:
23 | return state;
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/containers/views/Home/saga.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * home Saga
3 | *
4 | */
5 |
6 |
7 | import { takeEvery, takeLatest, put, call, select } from "redux-saga/effects";
8 |
9 | import * as api from "services/api";
10 | // import { makePageIndexSelector, makePageSizeSelector } from "./selectors";
11 |
12 | // 入口
13 | export default function* mainSaga() {
14 | // yield takeLatest(QUERY_LIST, queryListSaga);
15 | // yield takeLatest(CHANGE_PAGE_QUERY, changePageQuery);
16 | }
17 |
--------------------------------------------------------------------------------
/src/containers/views/Home/selectors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Selector
3 | */
4 |
5 | import { createSelector } from "reselect";
6 |
7 | export const homeSelector = state =>
8 | state.get("home");
9 |
--------------------------------------------------------------------------------
/src/containers/views/Home/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .pageContainer {
4 | // ..
5 | width: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/src/containers/views/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { compose } from "redux";
3 | import { connect } from "react-redux";
4 |
5 | import * as styles from './style.scss'
6 |
7 | import * as Logo from 'assets/images/logo404.png'
8 |
9 | class NotFound extends React.Component {
10 | public render() {
11 | return (
12 |
13 |

14 |
15 |
404
16 |
抱歉,你访问的页面不存在
17 |
18 |
19 | )
20 | }
21 | }
22 |
23 | export function mapDispatchToProps(dispatch) {
24 | return {};
25 | }
26 |
27 | export function mapStateToProps(state) {
28 | return {}
29 | }
30 |
31 | const withConnect = connect(mapStateToProps, mapDispatchToProps);
32 |
33 | export default compose(withConnect)(NotFound);
34 |
--------------------------------------------------------------------------------
/src/containers/views/NotFound/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .pageContainer {
4 | display: flex;
5 | width: 100%;
6 | height: 100%;
7 | align-items: center;
8 | justify-content: center;
9 | background: #f0f2f5;
10 | }
11 |
12 | .leftWrapper {
13 | margin-right: 10%;
14 | }
15 |
16 | .pageTitle {
17 | color: #434e59;
18 | font-size: 70px;
19 | }
20 |
21 | .pageDescription {
22 | color: rgba(#000, 0.45);
23 | font-size: 20px;
24 | }
25 |
--------------------------------------------------------------------------------
/src/env/dev.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | env: 'dev',
3 | apiUrlPrefix: '/' // api server baseURL
4 | };
--------------------------------------------------------------------------------
/src/env/prod.ts:
--------------------------------------------------------------------------------
1 |
2 | import env from './dev'
3 |
4 | env.env = 'prod'
5 |
6 | export default env
--------------------------------------------------------------------------------
/src/env/qa.ts:
--------------------------------------------------------------------------------
1 |
2 | import env from './dev';
3 | import { eventNames } from 'cluster';
4 |
5 | env.env = 'qa';
6 |
7 | export default env;
--------------------------------------------------------------------------------
/src/libs/preact.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";function e(){}function t(t,n){var o,r,i,l,a=E;for(l=arguments.length;l-- >2;)W.push(arguments[l]);n&&null!=n.children&&(W.length||W.push(n.children),delete n.children);while(W.length)if((r=W.pop())&&void 0!==r.pop)for(l=r.length;l--;)W.push(r[l]);else"boolean"==typeof r&&(r=null),(i="function"!=typeof t)&&(null==r?r="":"number"==typeof r?r+="":"string"!=typeof r&&(i=!1)),i&&o?a[a.length-1]+=r:a===E?a=[r]:a.push(r),o=i;var u=new e;return u.nodeName=t,u.children=a,u.attributes=null==n?void 0:n,u.key=null==n?void 0:n.key,void 0!==S.vnode&&S.vnode(u),u}function n(e,t){for(var n in t)e[n]=t[n];return e}function o(e,o){return t(e.nodeName,n(n({},e.attributes),o),arguments.length>2?[].slice.call(arguments,2):e.children)}function r(e){!e.__d&&(e.__d=!0)&&1==A.push(e)&&(S.debounceRendering||P)(i)}function i(){var e,t=A;A=[];while(e=t.pop())e.__d&&k(e)}function l(e,t,n){return"string"==typeof t||"number"==typeof t?void 0!==e.splitText:"string"==typeof t.nodeName?!e._componentConstructor&&a(e,t.nodeName):n||e._componentConstructor===t.nodeName}function a(e,t){return e.__n===t||e.nodeName.toLowerCase()===t.toLowerCase()}function u(e){var t=n({},e.attributes);t.children=e.children;var o=e.nodeName.defaultProps;if(void 0!==o)for(var r in o)void 0===t[r]&&(t[r]=o[r]);return t}function _(e,t){var n=t?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e);return n.__n=e,n}function p(e){var t=e.parentNode;t&&t.removeChild(e)}function c(e,t,n,o,r){if("className"===t&&(t="class"),"key"===t);else if("ref"===t)n&&n(null),o&&o(e);else if("class"!==t||r)if("style"===t){if(o&&"string"!=typeof o&&"string"!=typeof n||(e.style.cssText=o||""),o&&"object"==typeof o){if("string"!=typeof n)for(var i in n)i in o||(e.style[i]="");for(var i in o)e.style[i]="number"==typeof o[i]&&!1===V.test(i)?o[i]+"px":o[i]}}else if("dangerouslySetInnerHTML"===t)o&&(e.innerHTML=o.__html||"");else if("o"==t[0]&&"n"==t[1]){var l=t!==(t=t.replace(/Capture$/,""));t=t.toLowerCase().substring(2),o?n||e.addEventListener(t,f,l):e.removeEventListener(t,f,l),(e.__l||(e.__l={}))[t]=o}else if("list"!==t&&"type"!==t&&!r&&t in e)s(e,t,null==o?"":o),null!=o&&!1!==o||e.removeAttribute(t);else{var a=r&&t!==(t=t.replace(/^xlink\:?/,""));null==o||!1===o?a?e.removeAttributeNS("http://www.w3.org/1999/xlink",t.toLowerCase()):e.removeAttribute(t):"function"!=typeof o&&(a?e.setAttributeNS("http://www.w3.org/1999/xlink",t.toLowerCase(),o):e.setAttribute(t,o))}else e.className=o||""}function s(e,t,n){try{e[t]=n}catch(e){}}function f(e){return this.__l[e.type](S.event&&S.event(e)||e)}function d(){var e;while(e=D.pop())S.afterMount&&S.afterMount(e),e.componentDidMount&&e.componentDidMount()}function h(e,t,n,o,r,i){H++||(R=null!=r&&void 0!==r.ownerSVGElement,j=null!=e&&!("__preactattr_"in e));var l=m(e,t,n,o,i);return r&&l.parentNode!==r&&r.appendChild(l),--H||(j=!1,i||d()),l}function m(e,t,n,o,r){var i=e,l=R;if(null!=t&&"boolean"!=typeof t||(t=""),"string"==typeof t||"number"==typeof t)return e&&void 0!==e.splitText&&e.parentNode&&(!e._component||r)?e.nodeValue!=t&&(e.nodeValue=t):(i=document.createTextNode(t),e&&(e.parentNode&&e.parentNode.replaceChild(i,e),b(e,!0))),i.__preactattr_=!0,i;var u=t.nodeName;if("function"==typeof u)return U(e,t,n,o);if(R="svg"===u||"foreignObject"!==u&&R,u+="",(!e||!a(e,u))&&(i=_(u,R),e)){while(e.firstChild)i.appendChild(e.firstChild);e.parentNode&&e.parentNode.replaceChild(i,e),b(e,!0)}var p=i.firstChild,c=i.__preactattr_,s=t.children;if(null==c){c=i.__preactattr_={};for(var f=i.attributes,d=f.length;d--;)c[f[d].name]=f[d].value}return!j&&s&&1===s.length&&"string"==typeof s[0]&&null!=p&&void 0!==p.splitText&&null==p.nextSibling?p.nodeValue!=s[0]&&(p.nodeValue=s[0]):(s&&s.length||null!=p)&&v(i,s,n,o,j||null!=c.dangerouslySetInnerHTML),g(i,t.attributes,c),R=l,i}function v(e,t,n,o,r){var i,a,u,_,c,s=e.childNodes,f=[],d={},h=0,v=0,y=s.length,g=0,w=t?t.length:0;if(0!==y)for(var C=0;Cu.length&&u.push(a)}function t(a,b,c,d){var f=typeof a;if("undefined"===f||"boolean"===f)a=null;var l=!1;if(null===a)l=!0;else switch(f){case "string":case "number":l=!0;break;case "object":switch(a.$$typeof){case r:case P:case Q:case R:l=!0}}if(l)return c(d,a,""===b?"."+D(a,0):b),1;l=0;b=""===b?".":b+":";if(Array.isArray(a))for(var e=
13 | 0;ea;a++)b["_"+String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?!1:!0}catch(d){return!1}}()?Object.assign:
16 | function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");var c=Object(a);for(var d,f=1;f
2 |
3 |
4 |
5 |
6 |
7 | <% projectName %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/reducers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | */
4 |
5 | import { fromJS } from "immutable";
6 | import { combineReducers } from "redux-immutable";
7 | import { routerReducer } from "react-router-redux";
8 | import * as Redux from "redux";
9 |
10 | import globalReducer from "containers/shared/App/reducer";
11 |
12 | // import testReducer from 'containers/views/CertificateList/reducer'
13 |
14 | export const RESET_SUB_STATE = "ROOT/RESET_SUB_STATE";
15 |
16 | /**
17 | * Creates the main reducer with the dynamically injected ones
18 | */
19 | export default function createReducer(injectedReducers?) {
20 | const appReducer = combineReducers({
21 | // route: routeReducer, // TODO: check route state in redux
22 | router: routerReducer,
23 | global: globalReducer,
24 | ...injectedReducers
25 | });
26 | const rootReducers = (state, action) => {
27 | let s;
28 | switch (action.type) {
29 | case RESET_SUB_STATE:
30 | s = state.delete(action.key);
31 | break;
32 | default:
33 | s = state;
34 | }
35 | return appReducer(s, action);
36 | };
37 | return rootReducers;
38 | }
39 |
--------------------------------------------------------------------------------
/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import { stringify } from "qs";
2 | import http from "util/http";
3 | import { message } from "antd";
4 |
5 | import { DEFAULT_HTTP_OPTION } from "./contants";
6 |
7 | export async function demoRequest(id: string) {
8 | return http.get('yourPath', {
9 | id
10 | }, DEFAULT_HTTP_OPTION)
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/contants.ts:
--------------------------------------------------------------------------------
1 | import { message } from 'antd'
2 |
3 | /**
4 | * 默认的http请求配置, 默认显示错误信息提示
5 | */
6 | export const DEFAULT_HTTP_OPTION: HttpRequestOptions = {
7 | onError: (err: IHttpError) => {
8 | message.error(err.msg || '未知错误')
9 | }
10 | }
--------------------------------------------------------------------------------
/src/styles/_base.scss:
--------------------------------------------------------------------------------
1 | // 所有sass文件依赖基础入口
2 | // 注意: 因为所有scss文件都依赖,
3 | // 此文件包括此文件依赖的所有模块都不能有实际的css代码输出,
4 | // 避免太多重复的css代码输出
5 |
6 | @import "_var";
7 |
8 | // -------- bourbon --------
9 | @import "~bourbon/core/_bourbon.scss";
10 | // -------- bourbon:galobal-setting --------
11 | $bourbon: (
12 | "contrast-switch-dark-color" : #000,
13 | "contrast-switch-light-color": #fff,
14 | "global-font-file-formats" : ("ttf", "woff2", "woff"),
15 | "modular-scale-base" : 1em,
16 | "modular-scale-ratio" : $major-third,
17 | "rails-asset-pipeline" : false,
18 | );
19 |
20 | // -------- sass-bem --------
21 | // 如果使用bem, 可以通过 yarn add -D sass-bem 方式安装
22 | // @import "~sass-bem/_bem";
23 | // 如果使用sass-bem, 安装成功后取消下面注释可修改默认配置
24 | // $bem: (
25 | // debug: false,
26 | // separator: (element: "__", modifier: "--", "state": "-"),
27 | // namespace:
28 | // (
29 | // component: "ykc",
30 | // hack: "_",
31 | // object: "yk",
32 | // scope: "s",
33 | // test: "qa",
34 | // theme: "t",
35 | // utility: "yku"
36 | // ),
37 | // shortcodes: (ab: ("before", "after"))
38 | // ) !default;
39 |
40 | @import "_functions";
41 | @import "_mixins";
--------------------------------------------------------------------------------
/src/styles/_functions.scss:
--------------------------------------------------------------------------------
1 | // @function rem($px1, $px2: n, $px3: n, $px4: n) {
2 | // $all: $px1 / $rem + rem;
3 |
4 | // @each $value in $px2, $px3, $px4 {
5 | // @if $value != n {
6 | // $all: $all + " " + $value / $rem + rem;
7 | // }
8 | // }
9 |
10 | // @return $all;
11 | // }
12 |
--------------------------------------------------------------------------------
/src/styles/_mixins.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YDJ-FE/steamer-react-ts/cb18e32841f2000e218a74476e29c3faebff1306/src/styles/_mixins.scss
--------------------------------------------------------------------------------
/src/styles/_var.scss:
--------------------------------------------------------------------------------
1 | // global variables
2 | // -------- Colors -----------
3 | $primary-color : #1890ff;
4 | $info-color : #1890ff;
5 | $success-color : #52c41a;
6 | $processing-color : $primary-color;
7 | $error-color : #f5222d;
8 | $highlight-color : #f5222d;
9 | $warning-color : #faad14;
10 | $normal-color : #d9d9d9;
11 | $black-color : #000;
12 | $body-color : rgba(#000, 0.65);
13 | $heading-color : rgba(#000, 0.85);
14 | $text-color : rgba(#000, 0.65);
15 | $text-color-secondary : rgba(#000, 0.45);
16 | $heading-color-dark : rgba(#fff, 1);
17 | $text-color-dark : rgba(#fff, 0.85);
18 | $text-color-secondary-dark: rgba(#fff, 0.65);
19 | // Background color for ``
20 | $body-background : #F0F2F5;
21 | // Base background color for most components
22 | $component-background : #fff;
23 |
24 |
25 |
26 | // -------- Font --------
27 | $font-family-no-number: "Chinese Quote", -apple-system, BlinkMacSystemFont,
28 | "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
29 | "Helvetica Neue", Helvetica, Arial, sans-serif;
30 | $font-family : "Monospaced Number", $font-family-no-number;
31 | $font-weight-light : 300 !default;
32 | $font-weight-normal : 400 !default;
33 | $font-weight-bold : 700 !default;
34 | $font-weight-base : $font-weight-normal !default;
35 | $font-size-base : 14px;
36 | $font-size-lg : $font-size-base + 2px;
37 | $font-size-sm : 12px;
38 | $code-family : Consolas, Menlo, Courier, monospace;
39 |
40 |
41 | // -------- box & border --------
42 | $line-height-base : 1.5;
43 | $border-radius-base: 4px;
44 | $border-radius-sm : 2px;
45 |
46 | // -------- Function --------
47 | $rem: 40;
48 | $flexs: (
49 | 1,
50 | 2,
51 | 3,
52 | 4,
53 | 5,
54 | 6,
55 | 7,
56 | 8,
57 | 9,
58 | 10,
59 | 11,
60 | 12
61 | );
62 |
63 |
64 |
65 |
66 | // -------- Media queries breakpoints --------
67 | // Extra small screen / phone
68 | $screen-xs : 480px;
69 | $screen-xs-min : $screen-xs;
70 |
71 | // Small screen / tablet
72 | $screen-sm : 576px;
73 | $screen-sm-min : $screen-sm;
74 |
75 | // Medium screen / desktop
76 | $screen-md : 768px;
77 | $screen-md-min : $screen-md;
78 |
79 | // Large screen / wide desktop
80 | $screen-lg : 992px;
81 | $screen-lg-min : $screen-lg;
82 |
83 | // Extra large screen / full hd
84 | $screen-xl : 1200px;
85 | $screen-xl-min : $screen-xl;
86 |
87 | // Extra extra large screen / large descktop
88 | $screen-xxl : 1600px;
89 | $screen-xxl-min: $screen-xxl;
90 |
91 | // provide a maximum
92 | $screen-xs-max : ($screen-sm-min - 1px);
93 | $screen-sm-max : ($screen-md-min - 1px);
94 | $screen-md-max : ($screen-lg-min - 1px);
95 | $screen-lg-max : ($screen-xl-min - 1px);
96 | $screen-xl-max : ($screen-xxl-min - 1px);
97 |
98 | // Border color
99 | $border-color-base : hsv(0, 0, 85%); // base border outline a component
100 | $border-color-split: hsv(0, 0, 91%); // split border inside a component
101 | $border-width-base : 1px; // width of the border for a component
102 | $border-style-base : solid; // style of a components border
103 |
--------------------------------------------------------------------------------
/src/util/constants.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * react-saga 模式
4 | */
5 | export enum ENUM_ALLOW_MODES {
6 | RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount',
7 | DAEMON = '@@saga-injector/daemon',
8 | ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount',
9 | }
--------------------------------------------------------------------------------
/src/util/http.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * axios封装的http请求
3 | */
4 |
5 | import axios, { AxiosResponse } from 'axios'
6 | import * as md5 from 'blueimp-md5'
7 | import * as qs from 'qs'
8 | import ENVCONFIG from 'env'
9 |
10 | enum HTTPERROR {
11 | LOGICERROR,
12 | TIMEOUTERROR,
13 | NETWORKERROR
14 | }
15 |
16 |
17 | const isSuccess = res =>
18 | res.code !== undefined && res.code !== null && Number(res.code) === 1
19 |
20 | const resFormat = res => res.response || res.data || {}
21 |
22 | const http: HttpResquest = {}
23 |
24 | const methods = ['get', 'post', 'put', 'delete']
25 |
26 | methods.forEach(method => {
27 | http[method] = (
28 | url: string,
29 | data: PlainObject,
30 | options?: HttpRequestOptions
31 | ): Promise => {
32 | const opts: HttpRequestOptions = Object.assign(
33 | {
34 | baseUrl: ENVCONFIG.apiUrlPrefix, // 默认apiUrl前缀使用环境配置,特殊接口可以通过httpRequestOptions覆盖
35 | formatResponse: true,
36 | },
37 | options || {}
38 | )
39 |
40 | const axiosConfig: AxiosRequestConfig = {
41 | method,
42 | url,
43 | baseURL: opts.baseUrl,
44 | withCredentials: true, // 如果不需要带cookie请求, 请注释
45 | }
46 |
47 | const requestInstance = axios.create()
48 |
49 | // 创建axios实例,配置全局追加请求参数
50 | requestInstance.interceptors.request.use(
51 | (cfg: AxiosRequestConfig) => {
52 | const ts = Date.now() / 1000
53 | const queryData = {
54 | ts
55 | }
56 | cfg.params = Object.assign({}, cfg.params || {}, queryData)
57 | return cfg
58 | },
59 | error => Promise.reject(error)
60 | )
61 |
62 | // 全局请求错误拦截
63 | requestInstance.interceptors.response.use(
64 | response => response,
65 | error => {
66 | const errorDetail: IHttpError = {
67 | msg: error.message || '网络故障',
68 | type: /^timeout of/.test(error.message)
69 | ? HTTPERROR[HTTPERROR.TIMEOUTERROR]
70 | : HTTPERROR[HTTPERROR.NETWORKERROR],
71 | config: error.config
72 | }
73 | // cbNetworkError && cbNetworkError.call(null, _err);
74 | return Promise.reject(errorDetail)
75 | }
76 | )
77 |
78 | // 参数传递方式
79 | if (method === 'get') {
80 | axiosConfig.params = data
81 | } else if (data instanceof FormData) {
82 | axiosConfig.data = data
83 | } else {
84 | axiosConfig.data = qs.stringify(data)
85 | }
86 |
87 | return requestInstance
88 | .request(axiosConfig)
89 | .then(response => {
90 | let rdata: IHttpResponse
91 | if (
92 | typeof response.data === 'object' &&
93 | Array.isArray(response.data)
94 | ) {
95 | rdata = response.data[0]
96 | } else {
97 | rdata = response.data
98 | }
99 | if (!opts.formatResponse) {
100 | return rdata
101 | }
102 | if (!isSuccess(rdata)) {
103 | const errorDetail: IHttpError = {
104 | msg: rdata.msg,
105 | code: rdata.code,
106 | type: HTTPERROR[HTTPERROR.LOGICERROR],
107 | config: response.config
108 | }
109 | return Promise.reject(errorDetail)
110 | }
111 | return resFormat(rdata)
112 | })
113 | .catch((err: IHttpError) => {
114 | if(opts.onError) {
115 | options.onError(err)
116 | }
117 | return Promise.reject(err)
118 | })
119 | }
120 | })
121 |
122 | export default http
123 |
--------------------------------------------------------------------------------
/src/util/reducerInjectors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Reducer注入器
3 | */
4 |
5 | import * as invariant from "invariant";
6 | import { isEmpty, isFunction, isString } from "lodash";
7 | import { Reducer } from "redux";
8 |
9 | import createReducer, { RESET_SUB_STATE } from "../reducers";
10 |
11 | export { RESET_SUB_STATE };
12 |
13 | export function injectReducerFactory(store: IStore) {
14 | return function injectReducer(key: string, reducer: Reducer) {
15 | // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
16 | if (
17 | Reflect.has(store.injectedReducers, key) &&
18 | store.injectedReducers[key] === reducer
19 | ) {
20 | return;
21 | }
22 |
23 | store.injectedReducers[key] = reducer;
24 | store.replaceReducer(createReducer(store.injectedReducers));
25 | };
26 | }
27 |
28 | export default function getInjectors(store: IStore) {
29 | return {
30 | injectReducer: injectReducerFactory(store)
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/src/util/sagaInjectors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Saga注入器
3 | */
4 |
5 | import isEmpty from "lodash/isEmpty";
6 | import isFunction from "lodash/isFunction";
7 | import isString from "lodash/isString";
8 | import invariant from "invariant";
9 | import conformsTo from "lodash/conformsTo";
10 |
11 | import { SagaIterator } from 'redux-saga'
12 |
13 |
14 | import { ENUM_ALLOW_MODES } from "./constants";
15 |
16 | interface ISagaDescriptor {
17 | saga: SagaIterator,
18 | mode?: string
19 | }
20 |
21 | type InjectSagaFunc = (key: string, sagaDescriptor:ISagaDescriptor, args) => void
22 | type EjectSagaFunc = (key: string) => void
23 |
24 | /**
25 | * 工厂-生成注入Saga函数
26 | *
27 | * @export
28 | * @param {IStore} store
29 | * @returns
30 | */
31 | export function injectSagaFactory(store: IStore): InjectSagaFunc {
32 | return function injectSaga(key: string, descriptor: ISagaDescriptor, args: PlainObject) {
33 |
34 | const newDescriptor = {
35 | ...descriptor,
36 | mode: descriptor.mode || ENUM_ALLOW_MODES.RESTART_ON_REMOUNT
37 | };
38 | const { saga, mode } = newDescriptor;
39 |
40 |
41 | let hasSaga = Reflect.has(store.injectedSagas, key);
42 |
43 | // webpack配置注入‘process.env.NODE_ENV’变量
44 | if (process.env.NODE_ENV !== "production") {
45 | const oldDescriptor = store.injectedSagas[key];
46 | // enable hot reloading of daemon and once-till-unmount sagas
47 | if (hasSaga && oldDescriptor.saga !== saga) {
48 | oldDescriptor.task.cancel();
49 | hasSaga = false;
50 | }
51 | }
52 |
53 | if (
54 | !hasSaga ||
55 | (hasSaga && mode !== ENUM_ALLOW_MODES.DAEMON && mode !== ENUM_ALLOW_MODES.ONCE_TILL_UNMOUNT)
56 | ) {
57 | store.injectedSagas[key] = {
58 | ...newDescriptor,
59 | task: store.runSaga(saga, args)
60 | };
61 | }
62 | };
63 | }
64 |
65 | /**
66 | * 工厂-生成ejectSaga函数
67 | *
68 | * @export
69 | * @param {IStore} store
70 | * @returns
71 | */
72 | export function ejectSagaFactory(store: IStore): EjectSagaFunc {
73 | return function ejectSaga(key) {
74 |
75 | if (Reflect.has(store.injectedSagas, key)) {
76 | const descriptor = store.injectedSagas[key];
77 | if (descriptor.mode !== ENUM_ALLOW_MODES.DAEMON) {
78 | descriptor.task.cancel();
79 | // Clean up in production; in development we need `descriptor.saga` for hot reloading
80 | if (process.env.NODE_ENV === "production") {
81 | // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga`
82 | store.injectedSagas[key] = null;
83 | }
84 | }
85 | }
86 | };
87 | }
88 |
89 | export default function getInjectors(store: IStore) {
90 | return {
91 | injectSaga: injectSagaFactory(store),
92 | ejectSaga: ejectSagaFactory(store)
93 | };
94 | }
95 |
--------------------------------------------------------------------------------
/test/mocks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YDJ-FE/steamer-react-ts/cb18e32841f2000e218a74476e29c3faebff1306/test/mocks/.gitkeep
--------------------------------------------------------------------------------
/test/spec/simple.spec.tsx:
--------------------------------------------------------------------------------
1 | import { shallow, configure } from 'enzyme'
2 | import Adapter from 'enzyme-adapter-react-16'
3 | import React from 'react'
4 |
5 | configure({ adapter: new Adapter() })
6 |
7 | test('simple test', () => {
8 | expect(shallow().type()).toBe('div')
9 | })
10 |
--------------------------------------------------------------------------------
/tools/feature/feature.js:
--------------------------------------------------------------------------------
1 | // // used for install dependencies and files to support certain kinds of features
2 |
3 | // let path = require('path');
4 | // let project = require('../../config/project');
5 | // let pkgJson = require('../../package.json');
6 | // let merge = require('lodash').merge;
7 | // let spawnSync = require('child_process').spawnSync;
8 | // let utils = require('steamer-webpack-utils');
9 |
10 | // let dependency = {
11 | // template: {
12 | // html: {
13 | // 'html-loader': '^0.5.5'
14 | // },
15 | // handlebars: {
16 | // 'handlebars-loader': '^1.7.0',
17 | // 'handlebars': '^4.0.11'
18 | // },
19 | // pug: {
20 | // 'pug-loader': '^2.4.0',
21 | // 'pug': '^2.0.3'
22 | // },
23 | // ejs: {
24 | // 'ejs-loader': '^1.1.0',
25 | // 'ejs': '^2.5.9'
26 | // },
27 | // art: {
28 | // 'art-template': '^4.12.2',
29 | // 'art-template-loader': '^1.4.3'
30 | // }
31 | // },
32 | // style: {
33 | // css: {
34 | // 'style-loader': '^0.21.0',
35 | // 'css-loader': '^0.28.11'
36 | // },
37 | // less: {
38 | // 'style-loader': '^0.21.0',
39 | // 'css-loader': '^0.28.11',
40 | // 'less': '^3.0.1',
41 | // 'less-loader': '^4.1.0'
42 | // },
43 | // sass: {
44 | // 'style-loader': '^0.21.0',
45 | // 'css-loader': '^0.28.11',
46 | // 'node-sass': '^4.8.3',
47 | // 'sass-loader': '^7.0.1'
48 | // },
49 | // scss: {
50 | // 'style-loader': '^0.21.0',
51 | // 'css-loader': '^0.28.11',
52 | // 'node-sass': '^4.8.3',
53 | // 'sass-loader': '^7.0.1'
54 | // },
55 | // stylus: {
56 | // 'style-loader': '^0.21.0',
57 | // 'css-loader': '^0.28.11',
58 | // 'stylus': '^0.54.5',
59 | // 'stylus-loader': '^3.0.2'
60 | // }
61 | // },
62 | // js: {
63 | // ts: {
64 | // 'awesome-typescript-loader': '^5.0.0',
65 | // 'typescript': '^2.8.3',
66 | // '@types/react': '^15.6.15',
67 | // '@types/react-dom': '^15.5.7'
68 | // }
69 | // }
70 | // };
71 |
72 | // let files = {
73 | // template: {},
74 | // style: {},
75 | // js: {
76 | // ts: [
77 | // {
78 | // src: path.join(__dirname, './tsconfig.json'),
79 | // dist: path.resolve('tsconfig.json')
80 | // }
81 | // ]
82 | // }
83 | // };
84 |
85 | // module.exports = {
86 | // installDependency: function() {
87 | // console.log();
88 |
89 | // let dependencies = merge({}, pkgJson.dependencies, pkgJson.devDependencies);
90 |
91 | // let installDep = {};
92 | // let installFile = {
93 | // template: {},
94 | // style: {},
95 | // js: {}
96 | // };
97 | // let cmd = '';
98 |
99 | // project.webpack.template.forEach((item1) => {
100 | // let dep = dependency['template'][item1] || {};
101 |
102 | // Object.keys(dep).forEach((item2) => {
103 | // if (!dependencies[item2]) {
104 | // installDep[item2] = dependency['template'][item1][item2];
105 | // installFile.template[item1] = true;
106 | // }
107 | // });
108 | // });
109 |
110 | // project.webpack.style.forEach((item1) => {
111 | // let dep = dependency['style'][item1] || {};
112 |
113 | // Object.keys(dep).forEach((item2) => {
114 | // if (!dependencies[item2]) {
115 | // installDep[item2] = dependency['style'][item1][item2];
116 | // installFile.style[item1] = true;
117 | // }
118 | // });
119 | // });
120 |
121 | // project.webpack.js.forEach((item1) => {
122 | // let dep = dependency['js'][item1] || {};
123 |
124 | // Object.keys(dep).forEach((item2) => {
125 | // if (!dependencies[item2]) {
126 | // installDep[item2] = dependency['js'][item1][item2];
127 | // installFile.js[item1] = true;
128 | // }
129 | // });
130 | // });
131 |
132 | // Object.keys(installDep).forEach((item) => {
133 | // cmd += (item + '@' + installDep[item] + ' ');
134 | // });
135 |
136 | // if (cmd) {
137 | // utils.info('Start installing missing dependencies. Please wait......');
138 | // this.copyFile(installFile);
139 | // spawnSync(project.npm, ['install', '--save-dev', cmd], { stdio: 'inherit', shell: true });
140 | // utils.info('Dependencies installation complete. Please run your command again.');
141 | // return true;
142 | // }
143 | // else {
144 | // return false;
145 | // }
146 | // },
147 | // copyFile: function(installFile) {
148 | // Object.keys(installFile.template).forEach((item1) => {
149 | // let fileArr = files.template[item1] || [];
150 | // fileArr.forEach((item2) => {
151 | // utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
152 | // utils.fs.copySync(item2.src, item2.dist);
153 | // });
154 | // });
155 |
156 | // Object.keys(installFile.style).forEach((item1) => {
157 | // let fileArr = files.style[item1] || [];
158 | // fileArr.forEach((item2) => {
159 | // utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
160 | // utils.fs.copySync(item2.src, item2.dist);
161 | // });
162 | // });
163 | // Object.keys(installFile.js).forEach((item1) => {
164 | // let fileArr = files.js[item1] || [];
165 | // fileArr.forEach((item2) => {
166 | // utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
167 | // utils.fs.copySync(item2.src, item2.dist);
168 | // });
169 | // });
170 | // }
171 | // };
172 |
--------------------------------------------------------------------------------
/tools/optimization/index.js:
--------------------------------------------------------------------------------
1 | let UglifyJsPlugin = require('uglifyjs-webpack-plugin');
2 | let OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
3 |
4 | module.exports = function(config, webpack) {
5 | let configWebpack = config.webpack;
6 | let optimization = {
7 | minimizer: []
8 | };
9 |
10 | if (configWebpack.compress) {
11 | optimization.minimizer.push(new UglifyJsPlugin({
12 | cache: true,
13 | parallel: true,
14 | sourceMap: true // set to true if you want JS source maps
15 | }));
16 | optimization.minimizer.push(
17 | new OptimizeCSSAssetsPlugin({
18 | cssProcessor: require('cssnano'),
19 | cssProcessorOptions: {
20 | reduceIdents: false,
21 | autoprefixer: false,
22 | },
23 | })
24 | );
25 | }
26 |
27 | return optimization;
28 | };
29 |
--------------------------------------------------------------------------------
/tools/plugins/basePlugins.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 |
3 | let ManifestPlugin = require('webpack-manifest-plugin');
4 |
5 | module.exports = function(config, webpack) {
6 |
7 | let configWebpack = config.webpack;
8 | let isProduction = config.env === 'production';
9 |
10 | let plugins = [
11 | new webpack.DefinePlugin(configWebpack.injectVar)
12 | ];
13 |
14 | if (isProduction) {
15 | if (configWebpack.manifest) {
16 | plugins.push(new ManifestPlugin());
17 | }
18 | }
19 | else {
20 | plugins.push(new webpack.NoEmitOnErrorsPlugin());
21 | plugins.push(new webpack.HotModuleReplacementPlugin());
22 | }
23 |
24 | return plugins;
25 | };
26 |
--------------------------------------------------------------------------------
/tools/plugins/cachePlugins.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config, webpack) {
2 |
3 | let isProduction = config.env === 'production';
4 | let plugins = [];
5 |
6 | if (isProduction) {
7 | plugins = [
8 | new webpack.HashedModuleIdsPlugin({
9 | hashFunction: 'sha256',
10 | hashDigest: 'hex',
11 | hashDigestLength: 10
12 | })
13 | ];
14 | }
15 |
16 | return plugins;
17 | };
18 |
--------------------------------------------------------------------------------
/tools/plugins/resourcePlugins.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | let Clean = require('clean-webpack-plugin');
4 | let CopyWebpackPlugin = require('copy-webpack-plugin-hash');
5 | let WriteFilePlugin = require('write-file-webpack-plugin');
6 | let FileWebpackPlugin = require('file-webpack-plugin');
7 | let HtmlResWebpackPlugin = require('html-res-webpack-plugin');
8 | let MiniCssExtractPlugin = require('mini-css-extract-plugin');
9 |
10 | module.exports = function(config, webpack) {
11 |
12 | let configWebpack = config.webpack;
13 | let isProduction = config.env === 'production';
14 |
15 | let plugins = [
16 | new MiniCssExtractPlugin({
17 | filename: `css/${config.webpack.contenthashName}.css`,
18 | chunkFilename: 'css/[name]-[id]-[hash].css'
19 | }),
20 |
21 | ];
22 |
23 | if (isProduction) {
24 | let useCdn = configWebpack.useCdn || true;
25 |
26 | if (useCdn) {
27 | plugins.push(new FileWebpackPlugin({
28 | 'after-emit': [
29 | {
30 | from: path.join(configWebpack.path.dist, '**/*'),
31 | to: path.join(configWebpack.path.dist, configWebpack.path.distCdn),
32 | action: 'move',
33 | options: {
34 | cwd: configWebpack.path.dist,
35 | absolute: true,
36 | ignore: [
37 | '*.html',
38 | '**/*.ico',
39 | '**/*.html'
40 | ]
41 | }
42 | },
43 | {
44 | from: path.join(configWebpack.path.dist, '*.ico'),
45 | to: path.join(configWebpack.path.dist, configWebpack.path.distWebserver),
46 | action: 'move',
47 | options: {
48 | cwd: configWebpack.path.dist,
49 | absolute: true,
50 | }
51 | },
52 | {
53 | from: path.join(configWebpack.path.dist, '*.html'),
54 | to: path.join(configWebpack.path.dist, configWebpack.path.distWebserver),
55 | action: 'move',
56 | options: {
57 | cwd: configWebpack.path.dist,
58 | absolute: true,
59 | }
60 | }
61 | ]
62 | }));
63 | }
64 | }
65 | else {
66 | if (configWebpack.showSource) {
67 | plugins.push(new WriteFilePlugin());
68 | }
69 | }
70 |
71 | if (configWebpack.clean) {
72 | plugins.push(new Clean([isProduction ? configWebpack.path.dist : configWebpack.path.dev], { root: path.resolve() }));
73 | }
74 |
75 | if (config.webpack.promise) {
76 | plugins.push(new webpack.ProvidePlugin({
77 | Promise: 'imports-loader?this=>global!exports-loader?global.Promise!es6-promise'
78 | }));
79 | }
80 |
81 | configWebpack.static.forEach((item) => {
82 | plugins.push(new CopyWebpackPlugin([{
83 | from: item.src,
84 | to: (item.dist || item.src) + (item.hash ? '[path]' + configWebpack.hashName : '[path][name]') + '.[ext]',
85 | }]));
86 | });
87 |
88 | config.webpack.html.forEach(function(page, key) {
89 | plugins.push(new HtmlResWebpackPlugin({
90 | // removeUnMatchedAssets: true,
91 | env: isProduction ? 'production' : 'development',
92 | mode: 'html',
93 | filename: page.key + '.html',
94 | template: page.path,
95 | // favicon: 'src/assets/favicon.ico',
96 | htmlMinify: null,
97 | entryLog: !key,
98 | cssPublicPath: isProduction ? config.webpack.cssCdn : config.webpack.webserver,
99 | templateContent: function(tpl) {
100 | return tpl;
101 | }
102 | }));
103 | });
104 |
105 | return plugins;
106 | };
107 |
--------------------------------------------------------------------------------
/tools/plugins/spritePlugins.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | let SpritesmithPlugin = require('webpack-spritesmith');
4 |
5 | module.exports = function(config, webpack) {
6 |
7 | let configWebpack = config.webpack;
8 |
9 | let plugins = [];
10 |
11 | configWebpack.sprites = (configWebpack.spriteMode === 'none') ? [] : configWebpack.sprites;
12 |
13 | configWebpack.sprites.forEach(function(sprites) {
14 | let style = configWebpack.spriteStyle,
15 | extMap = {
16 | css: 'css',
17 | stylus: 'styl',
18 | less: 'less',
19 | sass: 'sass',
20 | scss: 'scss'
21 | },
22 | spriteMode = (~sprites.key.indexOf('_retina')) ? 'retinaonly' : configWebpack.spriteMode,
23 | retinaTplMap = {
24 | retinaonly: '_retinaonly',
25 | 'normal': '',
26 | 'retina': '_retina'
27 | },
28 | retinaTpl = retinaTplMap[spriteMode] || '';
29 |
30 | let spritesConfig = {
31 | src: {
32 | cwd: sprites.path,
33 | glob: '*.png'
34 | },
35 | target: {
36 | image: path.join(configWebpack.path.src, 'css/sprites/' + sprites.key + '.png'),
37 | css: [
38 | [
39 | path.join(configWebpack.path.src, 'css/sprites/' + sprites.key + '.' + extMap[style]),
40 | {
41 | format: `${sprites.key}${retinaTpl}`
42 | }
43 | ],
44 | [
45 | path.join(configWebpack.path.src, 'css/sprites/' + sprites.key + '.full.css'),
46 | {
47 | format: `${sprites.key}${retinaTpl}-full`
48 | }
49 | ],
50 | ]
51 | },
52 | spritesmithOptions: {
53 | padding: 2
54 | },
55 | apiOptions: {
56 | cssImageRef: '~' + sprites.key + '.png',
57 | handlebarsHelpers: {
58 | 'half': function(val) {
59 | return (val.replace('px', '') / 2) + 'px';
60 | }
61 | }
62 | }
63 | };
64 |
65 | let templatePath = require.resolve('spritesheet-templates-steamer/lib/templates/' + style + retinaTpl + '.template.handlebars');
66 | // 引入所有的合图样式模板
67 | let templateFullPath = require.resolve(`spritesheet-templates-steamer/lib/templates/full${retinaTpl}.template.handlebars`);
68 |
69 | spritesConfig.customTemplates = {
70 | [`${sprites.key}${retinaTpl}`]: templatePath,
71 | [`${sprites.key}${retinaTpl}-full`]: templateFullPath
72 | };
73 |
74 | if (spriteMode === 'retina') {
75 | spritesConfig.retina = '@2x';
76 | spritesConfig.target.css[0].push({
77 | format: `${sprites.key}`
78 | });
79 | }
80 | else {
81 | spritesConfig.target.css[0].push({
82 | format: `${sprites.key}${retinaTpl}`
83 | });
84 | }
85 |
86 | plugins.push(new SpritesmithPlugin(spritesConfig));
87 | });
88 |
89 | return plugins;
90 | };
91 |
--------------------------------------------------------------------------------
/tools/rules/fileRules.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 |
3 | let configWebpack = config.webpack;
4 | let isProduction = config.env === 'production';
5 |
6 | let rules = [
7 | {
8 | test: /\.ico$/,
9 | loader: 'url-loader',
10 | options: {
11 | name: '[name].[ext]'
12 | }
13 | },
14 | {
15 | test: /\.(jpe?g|png|gif|svg)$/i,
16 | use: [
17 | {
18 | loader: 'url-loader',
19 | options: {
20 | publicPath: isProduction ? configWebpack.imgCdn : configWebpack.webserver,
21 | limit: 10,
22 | name: `img/[path]${configWebpack.hashName}.[ext]`
23 | }
24 | }
25 | ]
26 | },
27 | {
28 | test: /\.(woff|woff2|eot|ttf)\??.*$/,
29 | use: [
30 | {
31 | loader: 'url-loader',
32 | options: {
33 | limit: 10,
34 | name: `font/[path]${configWebpack.hashName}.[ext]`
35 | }
36 | }
37 | ]
38 | }
39 | ];
40 |
41 | return rules;
42 | };
43 |
--------------------------------------------------------------------------------
/tools/rules/htmlRules.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 |
3 | let configWebpack = config.webpack;
4 |
5 | // 模板loader
6 | const templateRules = {
7 | html: {
8 | test: /\.html$/,
9 | loader: 'html-loader'
10 | },
11 | pug: {
12 | test: /\.pug$/,
13 | loader: 'pug-loader'
14 | },
15 | handlebars: {
16 | test: /\.handlebars$/,
17 | loader: 'handlebars-loader'
18 | },
19 | ejs: {
20 | test: /\.ejs$/,
21 | loader: 'ejs-loader',
22 | query: {
23 | 'htmlmin': true, // or enable here
24 | 'htmlminOptions': {
25 | removeComments: true
26 | }
27 | }
28 | },
29 | art: {
30 | test: /\.art$/,
31 | loader: 'art-template-loader',
32 | }
33 | };
34 |
35 | let rules = [];
36 |
37 | configWebpack.template.forEach((tpl) => {
38 | let rule = templateRules[tpl] || '';
39 | rule && rules.push(rule);
40 | });
41 |
42 | return rules;
43 | };
44 |
--------------------------------------------------------------------------------
/tools/rules/jsRules.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = function(config) {
4 |
5 | let configWebpack = config.webpack;
6 |
7 | let rules = [
8 | {
9 | test: /\.(tsx?|js)$/,
10 | loader: 'awesome-typescript-loader',
11 | exclude: /node_modules/,
12 | options: {
13 | configFileName: path.join(__dirname, '../../tsconfig.webpack.json')
14 | }
15 | }
16 | ];
17 |
18 | return rules;
19 | };
20 |
--------------------------------------------------------------------------------
/tools/rules/styleRules.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const merge = require('lodash').merge;
3 |
4 | let MiniCssExtractPlugin = require("mini-css-extract-plugin");
5 |
6 | module.exports = function(config) {
7 | let configWebpack = config.webpack;
8 | let isProduction = config.env === "production";
9 |
10 | let includePaths = [
11 | path.resolve("node_modules"),
12 | config.webpack.path.src,
13 | path.join(configWebpack.path.src, "css/sprites")
14 | ];
15 |
16 | // 样式loader
17 | let commonLoaders = [
18 | {
19 | loader: "cache-loader",
20 | options: {
21 | // provide a cache directory where cache items should be stored
22 | cacheDirectory: path.resolve(".cache")
23 | }
24 | },
25 | {
26 | loader: "css-loader",
27 | },
28 | {
29 | loader: "postcss-loader"
30 | }
31 | ];
32 |
33 | if (isProduction) {
34 | commonLoaders.splice(0, 0, { loader: MiniCssExtractPlugin.loader });
35 | } else {
36 | commonLoaders.splice(0, 0, { loader: "style-loader" });
37 | }
38 |
39 | const withTypingCssModule = loaders => {
40 | let newLoaders = [...loaders];
41 | if (config.webpack.cssModule) {
42 | // replace css-loader, remove cache-loader
43 | newLoaders.splice(1, 2, {
44 | loader: "typings-for-css-modules-loader",
45 | options: {
46 | localIdentName: "[name]-[local]-[hash:base64:5]",
47 | module: true,
48 | namedExport: true,
49 | camelCase: true,
50 | sourceMap: configWebpack.cssSourceMap,
51 | importLoaders: 2,
52 | sass: true
53 | }
54 | });
55 | }
56 | return newLoaders;
57 | };
58 |
59 | const styleRules = {
60 | css: {
61 | test: /\.css$/,
62 | use: commonLoaders,
63 | include: includePaths
64 | },
65 | less: {
66 | test: /\.less$/,
67 | use: merge([], withTypingCssModule(commonLoaders)).concat([
68 | {
69 | loader: "less-loader",
70 | options: {
71 | sourceMap: configWebpack.cssSourceMap
72 | // paths: includePaths
73 | }
74 | }
75 | ]),
76 | include: includePaths
77 | },
78 | stylus: {
79 | test: /\.styl$/,
80 | use: merge([], withTypingCssModule(commonLoaders)).concat([
81 | {
82 | loader: "stylus-loader"
83 | }
84 | ]),
85 | include: includePaths
86 | },
87 | sass: {
88 | test: /\.s(a|c)ss$/,
89 | use: merge([], withTypingCssModule(commonLoaders)).concat([
90 | {
91 | loader: "sass-loader",
92 | options: {
93 | sourceMap: configWebpack.cssSourceMap
94 | }
95 | }
96 | ]),
97 | include: includePaths
98 | }
99 | };
100 |
101 | let rules = [];
102 |
103 | configWebpack.style.forEach(styleParam => {
104 | let style = styleParam === "scss" ? "sass" : styleParam;
105 | let rule = styleRules[style] || "";
106 | rule && rules.push(rule);
107 | });
108 |
109 | // console.log(JSON.stringify(rules, null, 4))
110 | return rules;
111 | };
112 |
--------------------------------------------------------------------------------
/tools/script.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | let isProduction = process.env.NODE_ENV === 'production';
4 |
5 | // const feature = require('./feature/feature');
6 |
7 | // if (feature.installDependency()) {
8 | // return;
9 | // }
10 |
11 | if (!isProduction) {
12 | require('./server');
13 | }
14 | else if (isProduction) {
15 |
16 | compilerRun(require('./webpack.base'));
17 | }
18 |
19 | function compilerRun(config) {
20 | let compiler = webpack(config);
21 |
22 | compiler.run(function(err, stats) {
23 | if (!err) {
24 | console.log(stats.toString({
25 | assets: true,
26 | cached: false,
27 | colors: true,
28 | children: false,
29 | errors: true,
30 | warnings: true,
31 | version: true,
32 | modules: false,
33 | publicPath: true,
34 | }));
35 | }
36 | else {
37 | console.log(err);
38 | }
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/tools/server.js:
--------------------------------------------------------------------------------
1 | const url = require('url');
2 | const express = require('express');
3 | const app = express();
4 | const webpack = require('webpack');
5 | const webpackDevMiddleware = require('webpack-dev-middleware');
6 | const webpackHotMiddleware = require('webpack-hot-middleware');
7 | const proxy = require('http-proxy-middleware');
8 |
9 | let webpackConfig = require('./webpack.base.js');
10 | let config = require('../config/project');
11 | let configWebpack = config.webpack;
12 | let port = configWebpack.port;
13 | let route = Array.isArray(configWebpack.route) ? configWebpack.route : [configWebpack.route];
14 | let apiPort = configWebpack['api-port'];
15 | let apiRoute = configWebpack['api-route'];
16 |
17 | function addProtocal(urlString) {
18 | if (!!~urlString.indexOf('http:') || !!~urlString.indexOf('https:')) {
19 | return urlString;
20 | }
21 |
22 | return 'http:' + urlString;
23 | }
24 |
25 | let urlObject = url.parse(addProtocal(configWebpack.webserver));
26 |
27 | for (let key in webpackConfig.entry) {
28 | if (webpackConfig.entry.hasOwnProperty(key)) {
29 | webpackConfig.entry[key].unshift(`webpack-hot-middleware/client?reload=true&dynamicPublicPath=true&path=__webpack_hmr`);
30 | webpackConfig.entry[key].unshift('react-hot-loader/patch');
31 | }
32 | }
33 |
34 | let compiler = webpack(webpackConfig);
35 | app.use(webpackDevMiddleware(compiler, {
36 | noInfo: true,
37 | stats: {
38 | colors: true
39 | },
40 | publicPath: configWebpack.webserver
41 | }));
42 |
43 | app.use(webpackHotMiddleware(compiler, {
44 | // 这里和上面的client配合,可以修正 webpack_hmr 的路径为项目路径的子路径,而不是直接成为 host 子路径(从publicPath开始,而不是根开始)
45 | // https://github.com/glenjamin/webpack-hot-middleware/issues/24
46 | path: `${urlObject.path}__webpack_hmr`
47 | }));
48 |
49 | // 静态资源转发
50 | route.forEach((rt) => {
51 | app.use(rt, proxy({ target: `http://127.0.0.1:${port}`, pathRewrite: { [`^${rt}`]: '/' }}));
52 | });
53 |
54 | // 后台转发
55 | apiRoute.forEach((rt) => {
56 | app.use(rt, proxy({ target: `http://127.0.0.1:${apiPort}` }));
57 | });
58 |
59 | app.listen(port, function(err) {
60 | if (err) {
61 | console.error(err);
62 | }
63 | else {
64 | console.info('Listening on port %s. Open up http://localhost:%s/ in your browser.', port, port);
65 | }
66 | });
67 |
--------------------------------------------------------------------------------
/tools/template/dependency.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // 'preact-list': {
3 | // 'preact': '^8.2.7',
4 | // 'preact-redux': '^2.0.3',
5 | // 'react-list-scroll': '^1.0.6',
6 | // 'react-touch-component': '^1.1.4',
7 | // 'react-spin-component': '0.0.1',
8 | // },
9 | // 'react-simple-mobx': {
10 | // 'mobx': '^3.2.2',
11 | // 'mobx-react': '^4.2.2',
12 | // 'mobx-react-devtools': '^4.2.15'
13 | // },
14 | // 'react-simple-redux': {
15 | // 'redux': '^4.0.0',
16 | // 'redux-devtools': '^3.4.1',
17 | // 'redux-devtools-dock-monitor': '^1.1.3',
18 | // 'redux-devtools-log-monitor': '^1.4.0',
19 | // 'redux-thunk': '^2.2.0'
20 | // },
21 | // 'react-spa-mobx': {
22 | // 'react-list-scroll': '^1.0.6',
23 | // 'react-loadable': '^5.3.1',
24 | // 'react-touch-component': '^1.1.4',
25 | // 'react-spin-component': '0.0.1',
26 | // 'react-router': '^4.2.0',
27 | // 'react-router-dom': '^4.2.2',
28 | // 'mobx-react-router': '^4.0.2',
29 | // 'history': '^4.7.2',
30 | // 'mobx-react-devtools': '^4.2.15'
31 | // },
32 | // 'react-spa-redux': {
33 | // 'react-list-scroll': '^1.0.6',
34 | // 'react-loadable': '^5.3.1',
35 | // 'react-touch-component': '^1.1.4',
36 | // 'react-spin-component': '0.0.1',
37 | // 'react-router': '^4.2.0',
38 | // 'react-router-dom': '^4.2.2',
39 | // 'react-router-redux': '^5.0.0-alpha.9',
40 | // 'history': '^4.7.2',
41 | // 'redux-devtools': '^3.4.1',
42 | // 'redux-devtools-dock-monitor': '^1.1.3',
43 | // 'redux-devtools-log-monitor': '^1.4.0',
44 | // 'redux-thunk': '^2.2.0'
45 | // },
46 | // 'react-typescript': {
47 | // '@types/react': '^15.0.35',
48 | // '@types/react-dom': '^15.5.1'
49 | // }
50 | // 'list': {
51 | // 'react-list-scroll': '^1.0.6',
52 | // 'react-touch-component': '^1.1.4',
53 | // 'react-spin-component': '0.0.1',
54 | // },
55 | // 'list-immutable': {
56 | // 'react-list-scroll': '^1.0.6',
57 | // 'react-touch-component': '^1.1.4',
58 | // 'immutable': '^3.8.2',
59 | // 'pure-render-immutable-decorator': '^1.0.3',
60 | // 'redux-immutable': '^4.0.0',
61 | // },
62 | };
63 |
--------------------------------------------------------------------------------
/tools/template/mobxViewTemplate/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { observer } from 'mobx-react'
3 |
4 | import * as styles from "./style.scss";
5 |
6 | interface IP {
7 | demoProps: any
8 | }
9 |
10 | @observer
11 | class <% Title %> extends React.Component {
12 | render() {
13 | return
14 |
Hello World!
15 | ;
16 | }
17 | }
18 |
19 | export default <% Title %>;
20 |
--------------------------------------------------------------------------------
/tools/template/mobxViewTemplate/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .pageContainer {
4 | // ..
5 | width: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/actions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * <% Title %> Actions
3 | *
4 | * * To add a new Action:
5 | * 1) Import your constant
6 | * 2) Add a function like this:
7 | * export function yourAction(var) {
8 | * return { type: YOUR_ACTION_CONSTANT, var: var }
9 | * }
10 | */
11 |
12 | import {
13 | DEMO_TYPE
14 | } from "./constants";
15 |
16 | export function demoAction() {
17 | return {
18 | type: DEMO_TYPE
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * <% Title %> Constants
3 | *
4 | * 每个action都有相应的type, 好让reducer知道如何处理
5 | * 避免在reducer和action之间使用了错别字, 我们都从这里获取相同的constants
6 | * 我们推荐加前缀比如 ‘yourproject/yourComponent’避免reducer搞错action,类似命名空间
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'pageName/containerName/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | const PAGENAME = 'finanace/<% Title %>'
13 |
14 | export const DEMO_TYPE = `${PAGENAME}/DEMO_TYPE`
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { compose } from "redux";
3 | import { connect } from "react-redux";
4 | import injectReducer from "components/ReducerInjector";
5 | import injectSaga from "components/SagaInjector";
6 | import { createStructuredSelector } from "reselect";
7 |
8 | import reducer from "./reducer";
9 | import saga from "./saga";
10 | import * as selectors from "./selectors";
11 | import * as actions from "./actions";
12 |
13 | import * as styles from "./style.scss";
14 |
15 | interface IP {
16 | demoProps: any
17 | }
18 |
19 | class <% Title %> extends React.PureComponent {
20 | render() {
21 | return
22 |
Hello World!
23 | ;
24 | }
25 | }
26 |
27 | const mapDispatchToProps = dispatch => ({
28 | });
29 |
30 | const mapStateToProps = createStructuredSelector({
31 | // TODO: add Selector
32 | });
33 |
34 | const withReducer = injectReducer({ key: "<% title %>", reducer });
35 | const withSaga = injectSaga({ key: "<% title %>", saga });
36 | const withConnect = connect(mapStateToProps, mapDispatchToProps);
37 |
38 | export default compose(withReducer, withSaga, withConnect)(<% Title %>);
39 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/reducer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * <% Title %> Reducer
3 | *
4 | * The reducer takes care of our data. Using actions, we can change our
5 | * application state.
6 | * To add a new action, add it to the switch statement in the reducer function
7 | *
8 | * Example
9 | * case YOUR_ACTION_CONSTANT:
10 | * return state.set('yourStateVarivle', yourActionPayload)
11 | */
12 |
13 | import { fromJS } from "immutable";
14 | import { AnyAction } from "redux";
15 | import { DEMO_TYPE } from "./constants";
16 |
17 | const initialState = fromJS({
18 | });
19 |
20 | export default (state = initialState, action: AnyAction) => {
21 | switch (action.type) {
22 | default:
23 | return state;
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/saga.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * <% title %> Saga
3 | *
4 | */
5 |
6 |
7 | import { takeEvery, takeLatest, put, call, select } from "redux-saga/effects";
8 |
9 | import * as api from "services/api";
10 | // import { makePageIndexSelector, makePageSizeSelector } from "./selectors";
11 |
12 | // 入口
13 | export default function* mainSaga() {
14 | // yield takeLatest(QUERY_LIST, queryListSaga);
15 | // yield takeLatest(CHANGE_PAGE_QUERY, changePageQuery);
16 | }
17 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/selectors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Selector
3 | */
4 |
5 | import { createSelector } from "reselect";
6 |
7 | export const <% title %>Selector = state =>
8 | state.get("<% title %>");
9 |
--------------------------------------------------------------------------------
/tools/template/viewTemplate/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/_base";
2 |
3 | .pageContainer {
4 | // ..
5 | width: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/tools/webpack.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const webpack = require('webpack');
4 | const webpackMerge = require('webpack-merge');
5 |
6 | let config = require('../config/project');
7 | let configWebpack = config.webpack;
8 | let configWebpackMerge = config.webpackMerge;
9 | let configCustom = config.custom;
10 | let isProduction = process.env.NODE_ENV === 'production';
11 |
12 |
13 | let baseConfig = {
14 | mode: isProduction ? 'production' : 'development',
15 | context: configWebpack.path.src,
16 | entry: configWebpack.entry,
17 | output: {
18 | publicPath: isProduction ? configWebpack.cdn : configWebpack.webserver,
19 | path: isProduction ? configWebpack.path.dist : configWebpack.path.dev,
20 | filename: `js/${configWebpack.chunkhashName}.js`,
21 | chunkFilename: 'chunk/' + configWebpack.chunkhashName + '.js'
22 | },
23 | module: {
24 | rules: []
25 | },
26 | resolve: {
27 | modules: [
28 | configWebpack.path.src,
29 | 'node_modules',
30 | path.join(configWebpack.path.src, 'css/sprites')
31 | ],
32 | extensions: [
33 | '.ts', '.tsx', '.js', '.jsx', '.css', '.scss', 'sass', '.less', '.styl',
34 | '.png', '.jpg', '.jpeg', '.ico', '.ejs', '.pug', '.art', '.handlebars', 'swf', 'svg',
35 | ],
36 | // alias: {}
37 | },
38 | plugins: [],
39 | optimization: {},
40 | watch: !isProduction,
41 | devtool: isProduction ? configWebpack.sourceMap.production : configWebpack.sourceMap.development,
42 | performance: {
43 | hints: isProduction ? 'warning' : false,
44 | assetFilter: function(assetFilename) {
45 | return assetFilename.endsWith('.js') || assetFilename.endsWith('.css');
46 | }
47 | }
48 | };
49 |
50 | /** *********** 处理脚手架基础rules & plugins *************/
51 | let rules = fs.readdirSync(path.join(__dirname, 'rules'));
52 | let plugins = fs.readdirSync(path.join(__dirname, 'plugins'));
53 |
54 | let baseConfigRules = [];
55 | let baseConfigPlugins = [];
56 |
57 | rules.forEach((rule) => {
58 | baseConfigRules = baseConfigRules.concat(require(`./rules/${rule}`)(config));
59 | });
60 |
61 | plugins.forEach((plugin) => {
62 | baseConfigPlugins = baseConfigPlugins.concat(require(`./plugins/${plugin}`)(config, webpack));
63 | });
64 |
65 | baseConfig.module.rules = baseConfigRules;
66 | baseConfig.plugins = baseConfigPlugins;
67 | baseConfig.optimization = require('./optimization')(config, webpack);
68 |
69 | // console.log(rules, plugins);
70 |
71 | /** *********** base 与 user config 合并 *************/
72 | let userConfig = {
73 | output: configCustom.getOutput(),
74 | module: configCustom.getModule(),
75 | resolve: configCustom.getResolve(),
76 | externals: configCustom.getExternals(),
77 | plugins: configCustom.getPlugins(),
78 | };
79 |
80 | let otherConfig = configCustom.getOtherOptions();
81 |
82 | for (let key in otherConfig) {
83 | if (otherConfig.hasOwnProperty(key)) {
84 | userConfig[key] = otherConfig[key];
85 | }
86 | }
87 |
88 | baseConfig = configWebpackMerge.mergeProcess(baseConfig);
89 |
90 | let webpackConfig = webpackMerge.smartStrategy(
91 | configWebpackMerge.smartStrategyOption
92 | )(baseConfig, userConfig);
93 |
94 | // console.log(JSON.stringify(webpackConfig, null, 4));
95 |
96 | module.exports = webpackConfig;
97 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "sourceMap": true,
8 | "baseUrl": "src",
9 | "alwaysStrict": true,
10 | "allowJs": true,
11 | "importHelpers": true,
12 | "experimentalDecorators": true,
13 | "jsx": "react",
14 | "skipLibCheck": true,
15 | "typeRoots": ["node_modules/@types", "./typings"],
16 | "lib": [
17 | "es5",
18 | "es2015.core",
19 | "es2015.reflect",
20 | "es2015.promise",
21 | "es2015.generator",
22 | "es2016.array.include",
23 | "dom"
24 | ]
25 | },
26 |
27 | "include": [
28 | "src/**/*",
29 | "typings"
30 | ],
31 |
32 | "awesomeTypescriptLoaderOptions": {
33 | "reportFiles": ["src/**/*.{ts,tsx}"]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tsconfig.webpack.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": [
4 | "src/**/*.d.ts",
5 | "typings"
6 | ],
7 | "files": [
8 | "src/app.tsx"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-config-prettier"],
3 | "rules": {
4 | "ordered-imports": false,
5 | "object-literal-sort-keys": false,
6 | "member-access": false,
7 | "member-ordering": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/typings/antd.shim.d.ts:
--------------------------------------------------------------------------------
1 | import { Cascader } from "antd";
2 |
3 | declare module 'antd/lib/Cascader' {
4 | export interface CascaderProps {
5 | autoFocus?: boolean,
6 | onKeyDown?: (evt) => void
7 | }
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/typings/echarts.d.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // export as namespace echarts
4 |
5 | // export interface EChartOption {
6 | // title?: echarts.EChartOption | echarts.EChartOption[]
7 | // }
8 |
9 | ///
10 | type Diff =
11 | ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
12 | type Overwrite = Pick> & U;
13 |
14 | declare namespace echarts {
15 |
16 | interface TitleExtension {
17 | title?: echarts.EChartTitleOption | echarts.EChartTitleOption[]
18 | }
19 |
20 | interface EChartOptionNew extends Overwrite {
21 | axisPointer?: object
22 | }
23 |
24 | interface ECharts {
25 | setOption(option: EChartOption | EChartOptionNew, notMerge?: boolean, notRefreshImmediately?: boolean): void
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/typings/http.d.ts:
--------------------------------------------------------------------------------
1 | import * as axios from 'axios'
2 |
3 | declare global {
4 | interface AxiosRequestConfig extends axios.AxiosRequestConfig {
5 | startTime?: Date
6 | }
7 |
8 | interface IHttpError {
9 | msg: string
10 | type: string
11 | config: AxiosRequestConfig
12 | code?: number
13 | }
14 |
15 | interface IHttpResponse {
16 | code: number
17 | response: PlainObject | string | number
18 | msg: string
19 | }
20 |
21 | interface HttpResquest {
22 | /**
23 | * get请求
24 | *
25 | * @param {string} url 接口地址(不带域名)
26 | * @param {PlainObject} data 接口参数
27 | * @param {HttpRequestOptions} [options] http模块的请求选项
28 | * @returns {Promise}
29 | * @memberof HttpResquest
30 | */
31 | get?(url: string, data: PlainObject, options?: HttpRequestOptions): Promise
32 |
33 | /**
34 | * post请求
35 | *
36 | * @param {string} url 接口地址(不带域名)
37 | * @param {PlainObject} data 接口参数
38 | * @param {HttpRequestOptions} [options] http模块的请求选项
39 | * @returns {Promise}
40 | * @memberof HttpResquest
41 | */
42 | post?(url: string, data: PlainObject, options?: HttpRequestOptions): Promise
43 | /**
44 | * delete请求
45 | *
46 | * @param {string} url 接口地址(不带域名)
47 | * @param {PlainObject} data 接口参数
48 | * @param {HttpRequestOptions} [options] http模块的请求选项
49 | * @returns {Promise}
50 | * @memberof HttpResquest
51 | */
52 | delete?(url: string, data: PlainObject, options?: HttpRequestOptions): Promise
53 | /**
54 | * put请求
55 | *
56 | * @param {string} url 接口地址(不带域名)
57 | * @param {PlainObject} data 接口参数
58 | * @param {HttpRequestOptions} [options] http模块的请求选项
59 | * @returns {Promise}
60 | * @memberof HttpResquest
61 | */
62 | put?(url: string, data: PlainObject, options?: HttpRequestOptions): Promise
63 | }
64 |
65 | interface HttpRequestOptions {
66 | /**
67 | * 请求接口的path前缀,默认使用环境设置的apiUrlPrefix
68 | *
69 | * @type {string}
70 | * @memberof HttpRequestOptions
71 | */
72 | baseUrl?: string
73 | /**
74 | * 是否格式化话输出,默认为true, 如果格式化输入,则成功时候只返回response字段
75 | *
76 | * @type {boolean}
77 | * @memberof HttpRequestOptions
78 | */
79 | formatResponse?: boolean
80 | /**
81 | * 错误回调, 默认不做任何处理
82 | *
83 | * @memberof HttpRequestOptions
84 | */
85 | onError?: (error: IHttpError) => any
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | interface PlainObject {
2 | [key: string]: any;
3 | }
4 |
5 | interface Console {
6 | dev?: (param: any) => any;
7 | }
8 |
9 | declare module "*.png";
10 |
11 | /**
12 | * 环境设置模块
13 | */
14 | declare module 'env' {
15 | import env from 'env/dev'
16 | export default env
17 | }
18 |
--------------------------------------------------------------------------------
/typings/redux-store.shim.d.ts:
--------------------------------------------------------------------------------
1 | import Redux from "redux";
2 | import { Task, SagaIterator } from "redux-saga";
3 |
4 | interface InjectedReducers {
5 | [key: string]: Redux.Reducer;
6 | }
7 |
8 | interface InjectedSagas {
9 | [key: string]: {
10 | mode: string;
11 | saga: SagaIterator;
12 | task: Task
13 | };
14 | }
15 |
16 | declare global {
17 | // 扩展Store接口
18 | interface IStore extends Redux.Store {
19 | /**
20 | * 运行一个saga
21 | *
22 | * @memberof IStore
23 | */
24 | runSaga?: any; // TODO: cleanup
25 | asyncReducers?: Redux.ReducersMapObject;
26 |
27 | /**
28 | * 注入的Reducers
29 | *
30 | * @type {InjectedReducers}
31 | * @memberof IStore
32 | */
33 | injectedReducers?: InjectedReducers;
34 |
35 | /**
36 | * 注入的sagas
37 | *
38 | * @type {InjectedSagas}
39 | * @memberof IStore
40 | */
41 | injectedSagas?: InjectedSagas;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typings/redux.shim.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
3 | }
4 |
--------------------------------------------------------------------------------
/typings/typed-css-modules.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.scss' {
2 | const content: any;
3 | export = content;
4 | }
--------------------------------------------------------------------------------