diff --git a/.gitignore b/.gitignore index e29155f..87e2d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .idea .DS_Store tmp -spm_modules node_modules examples/**/dist boilerplate/dist diff --git a/.npmignore b/.npmignore index 8869ccb..fb23fce 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,5 @@ .idea .DS_Store tmp -spm_modules node_modules examples diff --git a/bin/antd-init b/bin/antd-init index 792231d..8c52b11 100755 --- a/bin/antd-init +++ b/bin/antd-init @@ -4,6 +4,7 @@ var vfs = require('vinyl-fs'); var fs = require('fs'); var through = require('through2'); var path = require('path'); +var inquirer = require('inquirer'); var join = path.join; var basename = path.basename; @@ -13,17 +14,38 @@ if (process.argv.length === 3 && return; } -var cwd = join(__dirname, '../boilerplate'); -var dest = process.cwd(); - -vfs.src('**/*', {cwd: cwd, cwdbase: true, dot: true}) - .pipe(template(dest)) - .pipe(vfs.dest(dest)) - .on('end', function() { - fs.renameSync(path.join(dest,'gitignore'),path.join(dest,'.gitignore')); - require('../lib/install'); +inquirer.prompt({ + name: 'type', + type: 'list', + message: 'Please select boilerplate type', + choices: [ + { + name: 'plain react - for simple project', + value: 'plain-react', + }, + { + name: 'redux - for complex project', + value: 'redux', + }, + ], }) - .resume(); + .then(function(answers) { + init(answers.type); + }); + +function init(type) { + var cwd = join(__dirname, '../boilerplates', type); + var dest = process.cwd(); + + vfs.src('**/*', {cwd: cwd, cwdbase: true, dot: true}) + .pipe(template(dest)) + .pipe(vfs.dest(dest)) + .on('end', function() { + fs.renameSync(path.join(dest,'gitignore'),path.join(dest,'.gitignore')); + require('../lib/install'); + }) + .resume(); +} function template(dest) { return through.obj(function (file, enc, cb) { diff --git a/boilerplate/.editorconfig b/boilerplates/plain-react/.editorconfig similarity index 100% rename from boilerplate/.editorconfig rename to boilerplates/plain-react/.editorconfig diff --git a/boilerplate/.eslintrc b/boilerplates/plain-react/.eslintrc similarity index 100% rename from boilerplate/.eslintrc rename to boilerplates/plain-react/.eslintrc diff --git a/boilerplate/README.md b/boilerplates/plain-react/README.md similarity index 100% rename from boilerplate/README.md rename to boilerplates/plain-react/README.md diff --git a/boilerplate/gitignore b/boilerplates/plain-react/gitignore similarity index 100% rename from boilerplate/gitignore rename to boilerplates/plain-react/gitignore diff --git a/boilerplate/index.html b/boilerplates/plain-react/index.html similarity index 100% rename from boilerplate/index.html rename to boilerplates/plain-react/index.html diff --git a/boilerplate/package.json b/boilerplates/plain-react/package.json similarity index 100% rename from boilerplate/package.json rename to boilerplates/plain-react/package.json diff --git a/boilerplate/proxy.config.js b/boilerplates/plain-react/proxy.config.js similarity index 100% rename from boilerplate/proxy.config.js rename to boilerplates/plain-react/proxy.config.js diff --git a/boilerplate/src/common/lib.js b/boilerplates/plain-react/src/common/lib.js similarity index 100% rename from boilerplate/src/common/lib.js rename to boilerplates/plain-react/src/common/lib.js diff --git a/boilerplate/src/component/App.jsx b/boilerplates/plain-react/src/component/App.jsx similarity index 100% rename from boilerplate/src/component/App.jsx rename to boilerplates/plain-react/src/component/App.jsx diff --git a/boilerplate/src/component/App.less b/boilerplates/plain-react/src/component/App.less similarity index 100% rename from boilerplate/src/component/App.less rename to boilerplates/plain-react/src/component/App.less diff --git a/boilerplate/src/entry/index.jsx b/boilerplates/plain-react/src/entry/index.jsx similarity index 100% rename from boilerplate/src/entry/index.jsx rename to boilerplates/plain-react/src/entry/index.jsx diff --git a/boilerplate/webpack.config.js b/boilerplates/plain-react/webpack.config.js similarity index 100% rename from boilerplate/webpack.config.js rename to boilerplates/plain-react/webpack.config.js diff --git a/boilerplates/redux/.eslintrc b/boilerplates/redux/.eslintrc new file mode 100644 index 0000000..4f1e575 --- /dev/null +++ b/boilerplates/redux/.eslintrc @@ -0,0 +1,20 @@ +{ + "parser": "babel-eslint", + "extends": "eslint-config-airbnb", + "rules": { + "spaced-comment": [0], + "no-unused-vars": [0], + "no-empty": [0], + "react/wrap-multilines": [0], + "react/no-multi-comp": [0], + "no-constant-condition": [0], + "react/jsx-no-bind": [0], + "react/prop-types": [0], + "arrow-body-style": [0], + "react/prefer-stateless-function": [0], + "semi": [0] + }, + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } +} diff --git a/boilerplates/redux/README.md b/boilerplates/redux/README.md new file mode 100644 index 0000000..f01e872 --- /dev/null +++ b/boilerplates/redux/README.md @@ -0,0 +1,109 @@ +# react-redux-boilerplate + +A boilerplate with react, redux, redux-saga, react-router, webpack, babel, css-modules ... + +## 环境准备 + +先安装依赖 + +```bash +$ npm install +``` + +想要更好的开发体验,还需安装两个 Chrome 插件:[Redux DevTools](https://chrome.google.com/webstore/detail/lmhkpmbekcpmknklioeibfkpmmfibljd) 和 [LiveReload](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei) 。 + +## 启动调试 + +```bash +$ npm start +$ open http://localhost:8989/ +``` + +## 构建代码 + +```bash +$ npm run build + +// 构建但不压缩 +$ npm run build -- --no-compress +``` + +## 目录结构 + +``` +. +├── /dist/ # 构建输出的文件会在这里 +├── /node_modules/ # 第三方类库和工具 +├── /src/ # 应用源码 +│ ├── /components/ # React components +│ ├── /constants/ # 常量 (比如 action types 等) +│ ├── /containers/ # React containers +│ ├── /entries/ # 应用入口 +│ ├── /reducers/ # reducers +│ ├── /routes/ # 路由信息 +│ ├── /sagas/ # redux-sagas +│ └── /services/ # 处理和服务器的交互 +├── proxy.config.js # 配置 dora-plugin-proxy,用于 mock 和在线调试 +├── webpack.config.js # 扩展 webpack 配置 +└── package.json # 配置入口文件、依赖和 scripts +``` + +## 系统组织 + +![](https://camo.githubusercontent.com/068c4ff126977b861cff3338428bdde6927f7dad/68747470733a2f2f6f732e616c697061796f626a656374732e636f6d2f726d73706f7274616c2f43684d775a42755a6c614c725377652e706e67) + +详见:[React + Redux 最佳实践](https://github.com/sorrycc/blog/issues/1) + +## 内置类库 + +- [react](https://github.com/facebook/react) +- [redux](https://github.com/reactjs/redux) +- [redux-saga](https://github.com/yelouafi/redux-saga) +- [redux-actions](https://github.com/acdlite/redux-actions) +- [react-router](https://github.com/reactjs/react-router) +- [reselect](https://github.com/reactjs/reselect) +- [classnames](https://github.com/JedWatson/classnames) +- [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) +- [react-router](https://github.com/reactjs/react-router) +- [react-router-redux](https://github.com/reactjs/react-router-redux) + +## 工具特性 + +热替换和 LiveReload + +> 基于 [Webpack Vanilla HMR](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html),支持 `components`, `containers`, `reducers`, `routers` 目录的模块热替换,其余目录的修改则会自动刷新页面。 + +> CSS 的自动刷新需通过 [LiveReload](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei) Chrome 插件配合使用。 + +> - [Why Vanilla HMR](https://github.com/reactjs/redux/pull/1455) + +支持 css-modules + +> 后缀为 `.module.less` 或 `.module.css` 的会被解析为 css-modules + +内置支持 jsx-control-statements + +> jsx 里可以通过 If, Else 和 For 标签来实现控制语句 + +运行错误和语法错误的提醒 + +> 通过 [redbox-react](https://github.com/KeywordBrain/redbox-react) 和 webpack hmr overlay 提示运行错误和语法错误 + +自动引入 `reducer` 和 `saga` + +> 通过 webpack 的 `require.context` 黑魔法批量引入 `reducer` 和 `saga`,新增、删除和重命名时会更方便 + +自动安装 npm 依赖 + +> ![](https://camo.githubusercontent.com/898e02d6539900efe65fadbfd15e2a1d7ce4dccf/68747470733a2f2f6f732e616c697061796f626a656374732e636f6d2f726d73706f7274616c2f4b6541474f776a70746a6152684d6d2e676966) + +数据 mock 和线上调试 + +> 通过 dora-plugin-proxy 实现,详见:https://github.com/dora-js/dora-plugin-proxy#规则定义 + +... + +## License + +MIT + diff --git a/boilerplates/redux/gitignore b/boilerplates/redux/gitignore new file mode 100644 index 0000000..a7cf257 --- /dev/null +++ b/boilerplates/redux/gitignore @@ -0,0 +1,4 @@ +dist +node_modules +.DS_Store +npm-debug.log diff --git a/boilerplates/redux/package.json b/boilerplates/redux/package.json new file mode 100644 index 0000000..4e19524 --- /dev/null +++ b/boilerplates/redux/package.json @@ -0,0 +1,45 @@ +{ + "private": true, + "entry": { + "index": "./src/entries/index.js" + }, + "dependencies": { + "atool-build": "0.6.x", + "babel-polyfill": "^6.3.14", + "classnames": "^2.2.3", + "history": "^2.0.1", + "isomorphic-fetch": "^2.2.1", + "jsx-control-statements": "^3.1.0", + "react": "0.14.x", + "react-dom": "0.14.x", + "react-redux": "4.4.x", + "react-router": "^2.0.1", + "react-router-redux": "^4.0.1", + "redux": "3.3.x", + "redux-actions": "0.9.x", + "redux-saga": "^0.9.5", + "reselect": "^2.0.3" + }, + "devDependencies": { + "babel-eslint": "^6.0.2", + "dora": "0.3.x", + "dora-plugin-browser-history": "^0.1.1", + "dora-plugin-livereload": "^0.3.0", + "dora-plugin-proxy": "^0.6.1", + "dora-plugin-webpack": "0.6.x", + "dora-plugin-webpack-hmr": "^0.1.0", + "eslint": "^2.7.0", + "eslint-config-airbnb": "^6.2.0", + "eslint-plugin-react": "^4.2.3", + "pre-commit": "1.x", + "redbox-react": "^1.2.2" + }, + "pre-commit": [ + "lint" + ], + "scripts": { + "start": "dora --plugins \"proxy,webpack,webpack-hmr,livereload?enableJs=false,browser-history?index=/src/entries/index.html\"", + "build": "atool-build", + "lint": "eslint --ext .js,.jsx src/" + } +} \ No newline at end of file diff --git a/boilerplates/redux/proxy.config.js b/boilerplates/redux/proxy.config.js new file mode 100644 index 0000000..eeeb1c5 --- /dev/null +++ b/boilerplates/redux/proxy.config.js @@ -0,0 +1,5 @@ +// More Examples: https://github.com/dora-js/dora-plugin-proxy#规则定义 + +module.exports = { + '/webapi/*': 'http://1.1.1.1', +}; diff --git a/boilerplates/redux/src/components/Count/Count.js b/boilerplates/redux/src/components/Count/Count.js new file mode 100644 index 0000000..b5b48f5 --- /dev/null +++ b/boilerplates/redux/src/components/Count/Count.js @@ -0,0 +1,27 @@ +import styles from './Count.module.less'; +import React, { Component, PropTypes } from 'react'; +import classnames from 'classnames'; +import { COUNT_DECREASE, COUNT_DECREASE_ASYNC, COUNT_REDUCE } from '../../constants/count'; + +const Count = ({ dispatch, count }) => +
+
{count}
+ + + +

+ + even + + odd + +
+ +Count.propTypes = { + count: PropTypes.any, +}; + +export default Count; diff --git a/boilerplates/redux/src/components/Count/Count.module.less b/boilerplates/redux/src/components/Count/Count.module.less new file mode 100644 index 0000000..46c2da1 --- /dev/null +++ b/boilerplates/redux/src/components/Count/Count.module.less @@ -0,0 +1,7 @@ + +.normal { + background: #fafafa; +} +.odd { + background: #ccc; +} diff --git a/boilerplates/redux/src/constants/count.js b/boilerplates/redux/src/constants/count.js new file mode 100644 index 0000000..6069849 --- /dev/null +++ b/boilerplates/redux/src/constants/count.js @@ -0,0 +1,4 @@ + +export const COUNT_DECREASE = 'count/decrease'; +export const COUNT_REDUCE = 'count/reduce'; +export const COUNT_DECREASE_ASYNC = 'count/decrease/async'; diff --git a/boilerplates/redux/src/containers/App.js b/boilerplates/redux/src/containers/App.js new file mode 100644 index 0000000..882ce94 --- /dev/null +++ b/boilerplates/redux/src/containers/App.js @@ -0,0 +1,20 @@ +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import Count from '../components/Count/Count'; + +const App = (props) => +
+ +
+ +App.propTypes = { +}; + +const selectors = createSelector([ + state => state.count, +], (count) => { + return { count }; +}); + +export default connect(selectors)(App); diff --git a/boilerplates/redux/src/entries/index.html b/boilerplates/redux/src/entries/index.html new file mode 100644 index 0000000..f65f403 --- /dev/null +++ b/boilerplates/redux/src/entries/index.html @@ -0,0 +1,16 @@ + + + + + Demo + + + + +
+ + + + + + diff --git a/boilerplates/redux/src/entries/index.js b/boilerplates/redux/src/entries/index.js new file mode 100644 index 0000000..3fe5703 --- /dev/null +++ b/boilerplates/redux/src/entries/index.js @@ -0,0 +1,66 @@ +import './index.html'; +import 'babel-polyfill'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; +import createSagaMiddleware from 'redux-saga'; +import { browserHistory } from 'react-router'; +import { syncHistoryWithStore, routerReducer as routing } from 'react-router-redux'; +import reducers from '../reducers/index'; +import sagas from '../sagas/index'; + +////////////////////// +// Store + +const initialState = {}; +const enhancer = compose( + applyMiddleware(createSagaMiddleware(sagas)), + window.devToolsExtension ? window.devToolsExtension() : f => f +); +const store = createStore(combineReducers({ + ...reducers, routing, +}), initialState, enhancer); + +if (module.hot) { + module.hot.accept('../reducers', () => { + const reducers = require('../reducers'); + const combinedReducers = combineReducers({...reducers, routing}); + store.replaceReducer(combinedReducers); + }); +} + +////////////////////// +// Render + +const history = syncHistoryWithStore(browserHistory, store); + +let render = () => { + const Routes = require('../routes/index'); + ReactDOM.render( + + + + , document.getElementById('root')); +}; + +if (module.hot) { + const renderNormally = render; + const renderException = (error) => { + const RedBox = require('redbox-react'); + ReactDOM.render(, document.getElementById('root')); + }; + render = () => { + try { + renderNormally(); + } catch (error) { + console.error('error', error); + renderException(error); + } + }; + module.hot.accept('../routes/index', () => { + render() + }); +} + +render(); diff --git a/boilerplates/redux/src/reducers/count.js b/boilerplates/redux/src/reducers/count.js new file mode 100644 index 0000000..99177b1 --- /dev/null +++ b/boilerplates/redux/src/reducers/count.js @@ -0,0 +1,12 @@ +import { handleActions } from 'redux-actions'; + +import { COUNT_DECREASE, COUNT_REDUCE } from '../constants/count'; + +export default handleActions({ + [COUNT_DECREASE](state) { + return state + 1; + }, + [COUNT_REDUCE](state) { + return state - 1; + }, +}, 0); diff --git a/boilerplates/redux/src/reducers/index.js b/boilerplates/redux/src/reducers/index.js new file mode 100644 index 0000000..9a4e66f --- /dev/null +++ b/boilerplates/redux/src/reducers/index.js @@ -0,0 +1,11 @@ +// Use require.context to require reducers automatically +// Ref: https://webpack.github.io/docs/context.html +const context = require.context('./', false, /\.js$/); +const keys = context.keys().filter(item => item !== './index.js'); + +const reducers = keys.reduce((memo, key) => { + memo[key.match(/([^\/]+)\.js$/)[1]] = context(key); + return memo; +}, {}); + +export default reducers; diff --git a/boilerplates/redux/src/routes/index.js b/boilerplates/redux/src/routes/index.js new file mode 100644 index 0000000..261e388 --- /dev/null +++ b/boilerplates/redux/src/routes/index.js @@ -0,0 +1,31 @@ +import React, { PropTypes } from 'react'; +import { Router, Route, IndexRoute, Link } from 'react-router'; +import App from '../containers/App'; + +const Layout = ({ children }) => +
+

Demo

+ {children} +
+ Go To /
+ Go To /foo
+
+ +const Foo = (props) => +
+ Foo +
+ +const Routes = ({ history }) => + + + + + + + +Routes.propTypes = { + history: PropTypes.any, +}; + +export default Routes; diff --git a/boilerplates/redux/src/sagas/count.js b/boilerplates/redux/src/sagas/count.js new file mode 100644 index 0000000..efdca3b --- /dev/null +++ b/boilerplates/redux/src/sagas/count.js @@ -0,0 +1,25 @@ +import { isCancelError } from 'redux-saga'; +import { take, call, put, fork, cancel } from 'redux-saga/effects'; +import { COUNT_DECREASE_ASYNC, COUNT_DECREASE } from '../constants/count'; + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function* decreaseCountWithDelay() { + try { + yield call(delay, 300); + yield put({ type: COUNT_DECREASE }); + } catch (e) { + if (isCancelError(e)) {} + } +} + +export default function* countSaga() { + let task; + while (true) { + yield take(COUNT_DECREASE_ASYNC); + if (task) yield cancel(task); + task = yield fork(decreaseCountWithDelay); + } +} diff --git a/boilerplates/redux/src/sagas/index.js b/boilerplates/redux/src/sagas/index.js new file mode 100644 index 0000000..430afbc --- /dev/null +++ b/boilerplates/redux/src/sagas/index.js @@ -0,0 +1,12 @@ +import { fork } from 'redux-saga/effects'; + +// Use require.context to require sagas automatically +// Ref: https://webpack.github.io/docs/context.html +const context = require.context('./', false, /\.js$/); +const keys = context.keys().filter(item => item !== './index.js'); + +export default function* root() { + for (let i = 0; i < keys.length; i ++) { + yield fork(context(keys[i])); + } +} diff --git a/boilerplates/redux/src/services/api.js b/boilerplates/redux/src/services/api.js new file mode 100644 index 0000000..593b978 --- /dev/null +++ b/boilerplates/redux/src/services/api.js @@ -0,0 +1,4 @@ +import fetch from 'isomorphic-fetch'; + +export async function getPlatforms() { +} diff --git a/boilerplates/redux/webpack.config.js b/boilerplates/redux/webpack.config.js new file mode 100644 index 0000000..92cf266 --- /dev/null +++ b/boilerplates/redux/webpack.config.js @@ -0,0 +1,8 @@ +// More Info: https://github.com/ant-tool/atool-build#配置扩展 + +const webpack = require('atool-build/lib/webpack'); + +module.exports = function(webpackConfig) { + webpackConfig.babel.plugins.push('jsx-control-statements'); + return webpackConfig; +}; diff --git a/package.json b/package.json index 7e817c5..b1f3ddf 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test": "mocha --no-timeouts" }, "dependencies": { + "inquirer": "^1.0.0", "through2": "2.0.x", "vinyl-fs": "^2.4.3", "which": "1.2.x"