Skip to content
This repository has been archived by the owner on Nov 4, 2018. It is now read-only.

Add redux boilerplate #25

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.idea
.DS_Store
tmp
spm_modules
node_modules
examples/**/dist
boilerplate/dist
*.log
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.idea
.DS_Store
tmp
spm_modules
node_modules
examples
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Generate boilerplate.
```bash
$ mkdir foo && cd foo
$ antd-init
// or
$ antd-init --type plain-react
$ antd-init --type redux
```

Start development server.
Expand Down
45 changes: 36 additions & 9 deletions bin/antd-init
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -13,17 +14,43 @@ if (process.argv.length === 3 &&
return;
}

var cwd = join(__dirname, '../boilerplate');
var dest = process.cwd();
if (process.argv.length === 4 && process.argv[2] === '--type') {
init(process.argv[3]);
return;
}

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) {
Expand Down
20 changes: 20 additions & 0 deletions boilerplates/redux/.eslintrc
Original file line number Diff line number Diff line change
@@ -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
}
}
103 changes: 103 additions & 0 deletions boilerplates/redux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 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
│ ├── /entries/ # 应用入口
│ ├── /layouts/ # 布局信息
│ ├── /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)
- [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`, `reducers`, `routers`, `sagas`, `layouts` 目录的模块热替换,其余目录的修改则会自动刷新页面。

> CSS 的自动刷新需通过 [LiveReload](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei) Chrome 插件配合使用。

> - [Why Vanilla HMR](https://github.com/reactjs/redux/pull/1455)

支持 css-modules

> 所有 less 文件会被解析为 css-modules

运行错误和语法错误的提醒

> 通过 [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

4 changes: 4 additions & 0 deletions boilerplates/redux/gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
.DS_Store
npm-debug.log
51 changes: 51 additions & 0 deletions boilerplates/redux/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"private": true,
"entry": {},
"dependencies": {
"antd": "^1.0.1",
"atool-build": "^0.7.1",
"babel-plugin-antd": "^0.4.0",
"babel-plugin-transform-runtime": "^6.8.0",
"babel-runtime": "^6.6.1",
"classnames": "^2.2.3",
"history": "^2.0.1",
"isomorphic-fetch": "^2.2.1",
"js-cookie": "^2.1.1",
"react": "^15.0.2",
"react-dom": "^15.0.2",
"react-redux": "4.4.x",
"react-router": "^2.0.1",
"react-router-redux": "^4.0.1",
"redux": "^3.5.2",
"redux-actions": "0.9.x",
"redux-saga": "^0.10.4"
},
"devDependencies": {
"atool-test-mocha": "^0.1.4",
"babel-eslint": "^6.0.2",
"dora": "0.3.x",
"dora-plugin-browser-history": "^0.1.1",
"dora-plugin-livereload": "^0.4.0",
"dora-plugin-proxy": "^0.6.1",
"dora-plugin-webpack": "0.6.x",
"dora-plugin-webpack-hmr": "^0.1.0",
"eslint": "^2.10.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.8.0",
"eslint-plugin-jsx-a11y": "^1.2.0",
"eslint-plugin-react": "^5.1.1",
"expect": "^1.20.1",
"glob": "^7.0.3",
"pre-commit": "^1.1.3",
"redbox-react": "^1.2.2"
},
"pre-commit": [
"lint"
],
"scripts": {
"start": "dora --plugins 'proxy,webpack,webpack-hmr,livereload?enableJs=false&injectHost=localhost,browser-history?index=/src/entries/index.html'",
"build": "atool-build",
"lint": "eslint --ext .js,.jsx src/",
"test": "atool-test-mocha ./src/**/__tests__/*-test.js"
}
}
27 changes: 27 additions & 0 deletions boilerplates/redux/proxy.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Learn more on how to config.
// - https://github.com/dora-js/dora-plugin-proxy#规则定义

module.exports = {
'/api/todos': function(req, res) {
setTimeout(function() {
res.json({
success: true,
data: [
{
id: 1,
text: 'Learn antd',
isComplete: true,
},
{
id: 2,
text: 'Learn ant-tool',
},
{
id: 3,
text: 'Learn dora',
},
],
});
}, 500);
},
};
17 changes: 17 additions & 0 deletions boilerplates/redux/src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Todos from './Todos/Todos';
import MainLayout from '../layouts/MainLayout/MainLayout';

const App = ({ location }) => {
return (
<MainLayout>
<Todos location={location} />
</MainLayout>
);
};

App.propTypes = {
};

export default connect()(App);
17 changes: 17 additions & 0 deletions boilerplates/redux/src/components/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { Button } from 'antd';
import styles from './NotFound.less';

const NotFound = () => {
return (
<div className={styles.normal}>
<div className={styles.container}>
<h1 className={styles.title}>404</h1>
<p className={styles.desc}>未找到该页面</p>
<a href="/"><Button type="primary" style={{ marginTop: 5 }}>返回首页</Button></a>
</div>
</div>
);
};

export default NotFound;
25 changes: 25 additions & 0 deletions boilerplates/redux/src/components/NotFound.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.normal {
width: 100%;
height: 100%;
min-height: 100vh;
padding-top: 120px;
}

.container {
padding: 0;
margin: 0 auto;
width: 620px;
height: 300px;
text-align: center;
}

.title {
font-size: 80px;
color: #666;
margin-top: 20px;
margin-bottom: 10px;
}

.desc {
font-size: 14px;
}
35 changes: 35 additions & 0 deletions boilerplates/redux/src/components/Todos/Todo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import styles from './Todo.less';

const Todo = ({ data, onToggleComplete }) => {
const { text, isComplete } = data;
const todoCls = classnames({
[styles.normal]: true,
[styles.isComplete]: isComplete,
});

return (
<div className={todoCls}>
<div className={styles.checkbox}>
<input
type="checkbox"
value=""
checked={isComplete}
onChange={onToggleComplete.bind(this)}
/>
</div>
<div className={styles.text}>
{text}
</div>
</div>
);
};

Todo.propTypes = {
data: PropTypes.object.isRequired,
onToggleComplete: PropTypes.func.isRequired,
};

export default Todo;

Loading