From d7d3ece51d5c5d327ce349c281951324d16a1aab Mon Sep 17 00:00:00 2001 From: "xiangzhong.wxz" Date: Mon, 21 Mar 2016 13:44:14 +0800 Subject: [PATCH] init --- README.md | 121 +++++++++++++++++ buildConfig.js | 18 +++ gulp/build.js | 49 +++++++ gulp/custom-task.js | 9 ++ gulp/debug-hot.js | 13 ++ gulp/debug.js | 13 ++ gulp/index.js | 13 ++ gulp/utils/debug-common.js | 52 +++++++ gulp/utils/define-plugin.js | 9 ++ gulpfile.js | 4 + make-webpack.config.js | 193 ++++++++++++++++++++++++++ mock/mock-list.js | 225 +++++++++++++++++++++++++++++++ package.json | 75 +++++++++++ routes.js | 24 ++++ server-with-mock.js | 86 ++++++++++++ test/app.jsx | 15 +++ test/component-generated.jsx | 23 ++++ test/environment.js | 18 +++ test/index.js | 5 + test/page-test.jsx | 23 ++++ webpack-dev-mock.config.js | 4 + webpack-dev-server.config.js | 3 + webpack-hot-dev-server.config.js | 4 + webpack-production.config.js | 3 + 24 files changed, 1002 insertions(+) create mode 100644 README.md create mode 100644 buildConfig.js create mode 100644 gulp/build.js create mode 100644 gulp/custom-task.js create mode 100644 gulp/debug-hot.js create mode 100644 gulp/debug.js create mode 100644 gulp/index.js create mode 100644 gulp/utils/debug-common.js create mode 100644 gulp/utils/define-plugin.js create mode 100644 gulpfile.js create mode 100644 make-webpack.config.js create mode 100644 mock/mock-list.js create mode 100644 package.json create mode 100644 routes.js create mode 100644 server-with-mock.js create mode 100644 test/app.jsx create mode 100644 test/component-generated.jsx create mode 100644 test/environment.js create mode 100644 test/index.js create mode 100644 test/page-test.jsx create mode 100644 webpack-dev-mock.config.js create mode 100644 webpack-dev-server.config.js create mode 100644 webpack-hot-dev-server.config.js create mode 100644 webpack-production.config.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..333295b --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# react-dev + +基于webpack(打包)&gulp(工作流)&koa(数据mock)的本地开发环境 + +## 执行命令 + - `tnpm run start` + 启用带有`hmr`功能的本地服务开发环境 + - `tnpm run debug` + 启用不带`hrm`功能的本地服务开发环境 + - `tnpm run mock` + 启用带有`数据mock`功能的本地服务开发环境(不带hmr,因该功能还不稳定[Webpack用来做模块热替换](http://segmentfault.com/a/1190000003872635)) + - `tnpm run build` + 执行项目构建,构建至`.build`文件夹,用于线上发布 + - `tnpm run test` + 执行单元测试 + +## 数据mock配置说明 + - 目前数据mock功能只能支持mock一个接口(设定为`/api/list`,所以所有的mock请求都请求这一个接口),在配置中,需要将不同的请求区分配置放在req参数中,然后在配置对象中指定req对应值返回的数据即可。 + - 配置完数据后要想使得mock的数据生效,需要关闭当前本地服务,执行`tnpm run mock`重新启动服务,mock数据方可生效。 + +## 技术方案 + + - reflux + react + - webpack + gulp + koa(用于mock数据) + - tnpm + +## 目录结构 + + + ├── .build [项目发布后生成的目录或文件] + └─── sc-radar [前端开发代码目录] + ├── app.js [业务js目录] + ├── app.css [业务css目录] +   └── index.html [入口文件] + ├── app [前端开发静态资源] + ├── gulp [gulp任务目录] + ├── mock [ajax请求数据mock配置] + ├── node_modules [依赖] + ├── test [测试] + ├── .editorconfig [代码格式化小工具配置] + ├── .eslintrc [eslint代码检查配置文件] + ├── .gitignore [配置git操作会忽略的文件] + ├── buildConfig.js [构建配置文件] + ├── gulpfile.js [gulp任务执行入口文件] + ├── HISTORY.md [修改记录] + ├── index.html [spa入口] + ├── make-webpack.config.js [webpack-config统一配置文件] + ├── package.json [前端项目依赖配置] + ├── READMD.md [你在看的文件] + ├── routes.js [koa proxy路由] + ├── server-with-mock.js [koa利用中间件起本地服务] + ├── webpack-dev-mock.config.js [带mock功能的本地服务配置] + ├── webpack-dev-server.config.js [不带hmr功能的本地服务配置] + ├── webpack-hot-dev-server.config.js [带hmr功能的本地服务配置] + └── webpack-production.config.js [构建用于部署的静态资源文件配置] + +## 依赖说明 + lodash + "react-router": "1.0.0-beta3" 不稳定版本,后续要关注下更新 + + +## webpack 记录 + + - output 里的 publicPath: '/build/' 注释掉,此配置会导致生成build中的css引用的字体路径为/build/xxxx (publicPath:'/') + +## BUG LIST + +console 报错 Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref. + +解决方法:同时使用了两个版本的react,删除本地的react即可 + +### react-highcharts 用webpack打包,使用babel打包后报错 + +解决方法:react-highcharts自身已经用webpack打包,二次打包时添加 'use strict'; +手动删除 'use strict'; 报错消失,通过配置webpack config 的loader,修改{test: /\.(js)$/,loader: 'babel-loader?loose=all'}, 为 {test: /\.(js)$/,exclude: /node_modules/,loader: 'babel-loader?loose=all'}, 可以解决此问题。 + +### 重构代码组织目录,点击菜单报错 + +`type.toUpperCase() is not a function` +由于重构 sideItem ,sideNav 导致了循环引用。webpack并未报错,只是返回了 {} 。导致 React无法识别。 + +### Reflux问题记录 +模型: + Stroe : state [a:'',b:''] , + onGetA: Ajax , trigger({a:a}) + onGetB: Ajax , trigger({b:b}) + componentsDidMount : Action.getA , Action.getB + +问题:组件中的显示情况,当ajax获取到数据a后,组件中的a数据更新,当ajax获取到数据b后,组件中的b数据更新了,但是之前a数据更新的结果消失了。 +调试:render中用console.log查看组件的state,一共有三条记录: + this.state a:'',b:'' 第一次渲染 + this.state a;'value' ajax获取到了a的数据,但是state中没有b + this.state b:'value' ajax获取到了b的数据,但是state中没有a +解决方法: + 将store中的trigger({a:a}),trigger({b:b})都修改为trigger({a:a,b:b}) + +### jsPlumb 打包 + +imports-loader +exports-loader + +webpack.config.js : + + module.noParse : /min\.js|jsPlumb.*\.js/ ## 不解析依赖 + + +usage method 1: + + var jsPlumb = require('imports-loader?this=>window!exports-loader?jsPlumb!./dom.jsPlumb-1.7.7-min.js'); + +usage method 2: + + require('jsPlumb') ; + // webpack.config.js + loaders: {test: /\.jsPlumb.*\.js$/, loader: 'imports-loader?this=>window!exports-loader?jsPlumb'} + // or resolve.alias + +### moment-timezone + +webpack 加载 moment-timezone 问题处理: +http://stackoverflow.com/questions/29548386/how-should-i-use-moment-timezone-with-webpack diff --git a/buildConfig.js b/buildConfig.js new file mode 100644 index 0000000..c26c351 --- /dev/null +++ b/buildConfig.js @@ -0,0 +1,18 @@ +module.exports = { + webpackConfig: { + debug: require('./webpack-dev-server.config.js'), + debugHot: require('./webpack-hot-dev-server.config.js'), + production: require('./webpack-production.config.js') + }, + devServer: { + // webpack-dev-server的设置 + proxy: { + // 在这里放置你定制的代理路由, e.g.: + // '/api/': 'http://localhost:8081' + }, + headers: { + // put your custom headers here, e.g.: + // 'X-TEST': 1 + } + } +}; diff --git a/gulp/build.js b/gulp/build.js new file mode 100644 index 0000000..bedbd93 --- /dev/null +++ b/gulp/build.js @@ -0,0 +1,49 @@ +/** + * 执行构建的任务,会将构建出的文件放置于目标文件夹中 + */ + +import gutil from 'gulp-util'; +import webpack from 'webpack'; +import definePlugin from './utils/define-plugin.js'; + +// 优化插件 +const optimizations = [ + definePlugin, + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin({ + output: { + comments: false, + }, + compress: { + warnings: false, + } + }) +]; + +// 获取构建配置 +const buildConfig = require('../buildConfig'); +// 获取webpack config +const wpConfig = buildConfig.webpackConfig.production; + +// 导出用于初始化的模块 +export default (gulp) => { + gulp.task('build', function(callback) { + wpConfig.plugins = wpConfig.plugins ? wpConfig.plugins.concat( + optimizations) : optimizations; + // 运行 webpack + webpack(wpConfig, function(err, stats) { + if (err) { + throw new gutil.PluginError('webpack', err); + } + + // 出错时打印出错误 + gutil.log('[webpack]: ', stats.toString({ + chunks: false, + modules: false, + colors: true, + })); + + callback(); + }); + }); +}; diff --git a/gulp/custom-task.js b/gulp/custom-task.js new file mode 100644 index 0000000..a799865 --- /dev/null +++ b/gulp/custom-task.js @@ -0,0 +1,9 @@ +/** + * 可以在这里添加自己的定制任务 + */ + +module.exports = function(gulp) { + gulp.task('custom-task', function() { + console.log('custom task done!'); + }); +}; diff --git a/gulp/debug-hot.js b/gulp/debug-hot.js new file mode 100644 index 0000000..3ba9c23 --- /dev/null +++ b/gulp/debug-hot.js @@ -0,0 +1,13 @@ +/** + * 开发模式的构建,注意,此时构建出的所有文件都位于内存中 + */ + +/* eslint no-console: 0 */ +import debugCommon from './utils/debug-common'; + +const buildConfig = require('../buildConfig'); +const wpConfig = buildConfig.webpackConfig.debugHot; + +export default (gulp) => { + debugCommon(gulp, wpConfig, 'debugHot'); +}; diff --git a/gulp/debug.js b/gulp/debug.js new file mode 100644 index 0000000..18323a4 --- /dev/null +++ b/gulp/debug.js @@ -0,0 +1,13 @@ +/** + * 开发模式的构建,注意,此时构建出的所有文件都位于内存中 + */ + +/* eslint no-console: 0 */ +import debugCommon from './utils/debug-common'; + +const buildConfig = require('../buildConfig'); +const wpConfig = buildConfig.webpackConfig.debug; + +export default (gulp) => { + debugCommon(gulp, wpConfig); +}; diff --git a/gulp/index.js b/gulp/index.js new file mode 100644 index 0000000..da65170 --- /dev/null +++ b/gulp/index.js @@ -0,0 +1,13 @@ +import fs from 'fs'; +import gulp from 'gulp'; + +const blacklist = ['index.js', 'utils']; +// 获取定制任务的文件列表 +const files = fs.readdirSync('./gulp').filter(f => !blacklist.includes(f)); + +// 加载定制的任务 +files.forEach(function(file) { + require('./' + file)(gulp); +}); + +gulp.task('default', ['debugHot']); diff --git a/gulp/utils/debug-common.js b/gulp/utils/debug-common.js new file mode 100644 index 0000000..9c53ff5 --- /dev/null +++ b/gulp/utils/debug-common.js @@ -0,0 +1,52 @@ +/** + * 开发模式的构建,注意,此时构建出的所有文件都位于内存中 + */ + +/* eslint no-console: 0 */ +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import definePlugin from './define-plugin.js'; + +const optimizations = [definePlugin]; + +const buildConfig = require('../../buildConfig'); + +export default (gulp, wpConfig, taskName) => { + taskName = taskName || 'debug'; + + gulp.task(taskName, function() { + wpConfig.plugins = wpConfig.plugins ? wpConfig.plugins.concat( + optimizations) : optimizations; + + let proxy = {}; + if (buildConfig.devServer && buildConfig.devServer.proxy) { + proxy = buildConfig.devServer.proxy; + } + let headers = {}; + if (buildConfig.devServer && buildConfig.devServer.headers) { + headers = buildConfig.devServer.headers; + } + + const compiler = webpack(wpConfig); + const server = new WebpackDevServer(compiler, { + contentBase: wpConfig.context, + hot: taskName === 'debugHot', + quiet: false, + noInfo: false, + watchOptions: { + aggregateTimeout: 300, + poll: true + }, + headers, + stats: { + chunks: false, + colors: true + }, + historyApiFallback: true, + proxy + }); + server.listen(8080, 'localhost', function() { + console.log('Webpack-Dev-Server: started on port 8080'); + }); + }); +}; diff --git a/gulp/utils/define-plugin.js b/gulp/utils/define-plugin.js new file mode 100644 index 0000000..edd9726 --- /dev/null +++ b/gulp/utils/define-plugin.js @@ -0,0 +1,9 @@ +import webpack from 'webpack'; + +// definePlugin takes raw strings and inserts them, so you can put strings of JS if you want. +const definePlugin = new webpack.DefinePlugin({ + __WEBPACK__: true, // say we're the webpack + __DEV__: process.env.BUILD_DEV // dev environment indication +}); + +export default definePlugin; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..6a9f998 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,4 @@ +// enable babel +require('babel/register'); +// require gulp entry +require('./gulp'); diff --git a/make-webpack.config.js b/make-webpack.config.js new file mode 100644 index 0000000..66b3b8b --- /dev/null +++ b/make-webpack.config.js @@ -0,0 +1,193 @@ +/* + * @Author: xiangzhong.wxz + * @Date: 2015-12-05 + * @Last Modified by: xiangzohng.wxz + * @Last Modified time: 2015-12-05 + * @info: 因为是多版本,故不再搞hash路径生成:[contenthash:8] + */ + +'use strict'; + +var webpack = require('webpack'), + path = require('path'), + ExtractTextPlugin = require('extract-text-webpack-plugin'), + HtmlWebpackPlugin = require('html-webpack-plugin'), + pkg = require('./package.json'), + appDir = path.resolve(process.cwd(), 'app'), + CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin, + fs = require('fs'), + excludeFromStats = [ + /node_modules[\\\/]/ + ], + + config; + +module.exports = function(options) { + options = options || {}; + + var debug = options.debug || false, + hot = options.hot || false, + mock = options.mock || false, + baseEntry = [ + 'webpack-dev-server/client?http://0.0.0.0:8080', + 'webpack/hot/only-dev-server', + './app/app.js' + ], + + entry = (mock || !debug) ? { + app: baseEntry[2] + } : (hot ? baseEntry : (baseEntry.splice(1, 1), baseEntry)), + + devtool = debug ? 'inline-source-map' : null, + + providePlugin = [new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery", + React: "react", + Reflux: 'reflux' + })], + plugins = debug ? + + (hot ? providePlugin.concat(new webpack.HotModuleReplacementPlugin()) : providePlugin) : + + providePlugin.concat(new ExtractTextPlugin('app.min.css', { + allChunks: true + }), new HtmlWebpackPlugin({ + // template: path.resolve(__dirname, 'index.html'), + templateContent: function(templateParams, compilation) { + return fs.readFileSync(path.resolve(__dirname, 'index.html')).toString().replace(/]*>(\s|\n|\r)*<\/script>/ig, ''); + }, + filename: 'index.html', + inject: 'body', + minify: { + collapseWhitespace: true, + removeComments: true, + minifyJS: true, + minifyCSS: true + } + })), + + module = debug ? { + /*preLoaders: [{ + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'eslint' + }],*/ + loaders: [{ + test: /\.css$/, + loaders: ['style', 'css'] + }, { + test: /\.json$/, + loader: 'json' + }, { + test: /\.less$/, + loaders: ['style', 'css', 'less'] + }, { + test: /\.scss$/, + loaders: ['style', 'css', 'sass'] + }, { + test: /\.styl$/, + loaders: ['style', 'css', 'stylus'] + }, { + test: /\.jsx?$/, + exclude: /node_modules/, + loaders: ['react-hot', 'babel?loose=all'] + }, { + test: /\.woff\d?(\?.+)?$/, + loader: 'url?limit=10000&minetype=application/font-woff' + }, { + test: /\.ttf(\?.+)?$/, + loader: 'url?limit=10000&minetype=application/octet-stream' + }, { + test: /\.eot(\?.+)?$/, + loader: 'url?limit=10000' + }, { + test: /\.svg(\?.+)?$/, + loader: 'url?limit=10000&minetype=image/svg+xml' + }, { + test: /\.png$/, + loader: 'url?limit=10000&mimetype=image/png' + }, { + test: /\.gif$/, + loader: 'url?limit=10000&mimetype=image/gif' + }] + } : { + loaders: [{ + test: /\.(jsx?)$/, + exclude: /node_modules/, + loader: 'babel?loose=all' + }, { + test: /\.json$/, + loaders: ["json"] + }, { + test: /\.less$/, + loader: ExtractTextPlugin.extract('style', + 'css!less') + }, { + test: /\.css$/, + loader: ExtractTextPlugin.extract('style', + 'css?minimize') + }, { + test: /\.png$/, + loader: 'url?mimetype=image/png' + }, { + test: /\.gif$/, + loader: 'url?mimetype=image/gif' + }, { + test: /\.jpe?g$/, + loader: 'url?mimetype=image/jpeg' + }, { + test: /\.(woff(2)?|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file?name=fonts/[name].[ext]' + }] + }, + + resolve = debug ? { + root: path.resolve(__dirname), + extensions: ['', '.js', '.jsx'], + modulesDirectories: ['node_modules'] + } : { + root: path.resolve(__dirname), + packageMains: ['style', 'main'], + extensions: ['', '.js', '.json', '.jsx', '.css', '.less'], + modulesDirectories: ['node_modules', 'app'] + }; + + config = { + target: 'web', + devtool: devtool, + debug: debug, + context: path.resolve(__dirname), + entry: entry, + output: { + path: path.join(__dirname, '.build', pkg.name), + publicPath: '/' + pkg.name + '/', + filename: debug ? 'app.js' : 'app.min.js' + }, + resolve: resolve, + node: { + fs: 'empty', + }, + module: module, + plugins: plugins, + devServer: { + stats: { + cached: false, + exclude: excludeFromStats, + colors: true + } + } + }; + + if (!debug) { + config.frameworks = ['webpack']; + } + + if (debug) { + config.eslint = { + configFile: path.join(__dirname, '.eslintrc'), + }; + } + + return config; +}; \ No newline at end of file diff --git a/mock/mock-list.js b/mock/mock-list.js new file mode 100644 index 0000000..3a8e26b --- /dev/null +++ b/mock/mock-list.js @@ -0,0 +1,225 @@ +/** + * @Info: 数据mock配置文件,配置说明如下: + * 1、每一个配置对象对应一个接口; + * 2、请求url统一为"/api/list",不同接口需要在请求参数中配置req参数。下面export的对象中的属性即为不同req配置的返回数据; + */ + +'use strict'; + +module.exports = { + // 立即执行请求 + 'exec-immediate': { + code: 200, + data: { + score: 80, + detail: [{ + rule: 'rule1', + point: -2 + }, { + rule: 'rule2', + point: -6 + }, { + rule: 'rule3', + point: -5 + }, { + rule: 'rule4', + point: -3 + }, { + rule: 'rule5', + point: -1 + }] + } + }, + + // 执行管理页搜索输入框实时联想 + 'execute-link': (query) => { + if (query.search === '') { + return { + status: true, + returnData: [] + }; + } else { + return { + status: true, + returnData: [{ + id: "1", + name: 'link1' + }, { + id: "2", + name: 'link2' + }, { + id: "3", + name: 'link3' + }] + }; + } + }, + + // 执行管理搜索结果 + 'execute-search': (query) => { + query = query || { + curPage: 1 + }; + + if (query.search === '') { + return { + status: true, + data: [] + }; + } else { + return { + status: true, + totalPage: 2, + curPage: query.curPage, + data: [{ + time: '17:20', + rule1: '-10', + rule2: '20', + // TODO:这里的value需要根据返回数据修改 + operation: '' + }, { + time: '17:25', + rule1: '-30', + rule2: '10', + operation: '' + }] + }; + } + }, + + // 工单管理页搜索输入框实时联想 + 'worksheet-link': (query) => { + if (query.search === '') { + return { + status: true, + returnData: [] + }; + } else { + return { + status: true, + returnData: [{ + id: "1", + name: 'link1' + }, { + id: "2", + name: 'link2' + }, { + id: "3", + name: 'link3' + }] + }; + } + }, + + // 工单管理页搜索 + 'worksheets-search': (query) => { + query = query || { + curPage: 1 + }; + let returnData = { + 1: { + status: true, + totalPage: 2, + curPage: query.curPage, + data: [{ + worksheetName: '工单1', + worksheetId: '1', + result: [{ + name: '业务线名称', + value: 'ww' + }, { + name: '执行时间', + value: '2015-12-20' + }, { + name: '状态', + value: 'create' + }] + }, { + worksheetName: '工单2', + worksheetId: '2', + result: [{ + name: '业务线名称', + value: 'dd' + }, { + name: '执行时间', + value: '2015-12-21' + }, { + name: '状态', + value: 'processing' + }] + }] + }, + 2: { + status: true, + totalPage: 2, + curPage: query.curPage, + data: [{ + worksheetName: '工单3', + worksheetId: '3', + result: [{ + name: '业务线名称', + value: 'ww' + }, { + name: '执行时间', + value: '2015-12-20' + }, { + name: '状态', + value: 'create' + }] + }, { + worksheetName: '工单4', + worksheetId: '4', + result: [{ + name: '业务线名称', + value: 'dd' + }, { + name: '执行时间', + value: '2015-12-21' + }, { + name: '状态', + value: 'processing' + }] + }] + } + }; + + return returnData[query.curPage]; + }, + + // 接收单子 + 'accept-worksheet': (query) => { + + return { + worksheetName: '工单' + query.worksheetId, + worksheetId: query.worksheetId, + result: [{ + name: '业务线名称', + value: 'ww' + }, { + name: '执行时间', + value: '2015-12-20' + }, { + name: '状态', + value: 'complete' + }] + } + + }, + + 'complete-worksheet': (query) => { + return { + worksheetName: '工单' + query.worksheetId, + worksheetId: query.worksheetId, + result: [{ + name: '业务线名称', + value: 'ww' + }, { + name: '执行时间', + value: '2015-12-20' + }, { + name: '状态', + value: 'complete' + }] + }; + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a06b25a --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "react-dev", + "private": true, + "version": "0.0.1", + "description": "基于webpack(打包)&gulp(工作流)&koa(数据mock)的本地开发环境", + "scripts": { + "test": "tape ./test | faucet", + "start": "gulp", + "debug": "gulp debug", + "devbuild": "gulp build", + "build": "wokman", + "mock": "NODE_ENV=development node server-with-mock.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "moment": "~2.10.6", + "moment-timezone": "~0.4.0", + "react": "^0.14.0", + "react-bootstrap": "0.28.1", + "react-bootstrap-datetimepicker": "0.0.18", + "react-highcharts": "1.0.3", + "react-router": "1.0.0-beta3", + "react-tools": "0.13.2", + "reflux": "^0.3.0", + "react-bootstrap-table": "1.x", + "jquery": "2.1.4", + "animate.css": "3.4.0", + "bootstrap": "3.3.6", + "font-awesome": "4.5.0", + "html5-boilerplate-npm ": "4.1.0-B", + "mottle": "0.7.0", + "jsplumb": "2.0.3", + "lodash": "3.10.1", + "react-paginate": "0.4.3", + "react-bootstrap-validation": "0.1.10", + "react-select": "0.9.1" + }, + "devDependencies": { + "babel": "^5.8.19", + "babel-core": "^5.8.19", + "babel-eslint": "^4.0.5", + "babel-loader": "^5.3.2", + "css-loader": "^0.17.0", + "eslint": "^1.1.0", + "eslint-loader": "^1.0.0", + "eslint-plugin-react": "^3.1.0", + "extract-text-webpack-plugin": "^0.8.2", + "faucet": "0.0.1", + "file-loader": "^0.8.4", + "gulp": "^3.9.0", + "gulp-util": "^3.0.6", + "istanbul": "^0.3.17", + "jsdom": "^6.3.0", + "less-loader": "^2.2.0", + "localStorage": "^1.0.3", + "react-hot-loader": "^1.3.0", + "style-loader": "^0.12.3", + "tape": "^4.0.1", + "url-loader": "^0.5.6", + "webpack": "^1.10.5", + "webpack-dev-server": "^1.14.0", + "html-webpack-plugin": "~1.6.0", + "imports-loader": "^0.6.4", + "script-loader": "~0.6.1", + "json-loader": "~0.5.2", + "colors": "^1.1.2", + "koa": "^0.21.0", + "koa-proxy": "^0.3.0", + "koa-router": "^5.1.2", + "koa-static": "^1.4.9", + "koa-webpack-dev-middleware": "^1.0.1", + "kcors": "^1.0.1" + } +} diff --git a/routes.js b/routes.js new file mode 100644 index 0000000..2a5c522 --- /dev/null +++ b/routes.js @@ -0,0 +1,24 @@ +/* + * @Info: koa proxy路由配置 + * @Author: xiangzhong.wxz + * @Date: 2015-12-05 + * @Last Modified by: xiangzhong.wxz + * @Last Modified time: 2015-12-05 + */ + +'use strict'; + +var proxy = require('koa-proxy'), // 用于设置代理,暂时未用到 + list = require('./mock/mock-list'), + req, res; + +module.exports = function(router, app) { + router.get('/api/list', function*() { + req = this.query.req; + res = list[req]; + + if (res) { + this.body = (typeof res === 'function') ? res(this.query) : res; + } + }); +}; diff --git a/server-with-mock.js b/server-with-mock.js new file mode 100644 index 0000000..a42151e --- /dev/null +++ b/server-with-mock.js @@ -0,0 +1,86 @@ +'use strict'; + +var http = require('http'), + path = require('path'), + + koa = require('koa'), + router = require('koa-router')(), + serve = require('koa-static'), + colors = require('colors'), + + pkg = require('./package.json'), + env = process.env.NODE_ENV, + debug = !env || env === 'development', + viewDir = './', + routes = require('./routes'), + + // koa初始化 + app = koa(); + +colors.setTheme({ + silly: 'rainbow', + input: 'grey', + verbose: 'cyan', + prompt: 'grey', + info: 'green', + data: 'grey', + help: 'cyan', + warn: 'yellow', + debug: 'blue', + error: 'red' +}); + +// 基本设置 +app.keys = [pkg.name, pkg.description]; +app.proxy = true; + +// 全局事件监听 +app.on('error', function(err, ctx) { + err.url = err.url || ctx.request.url; + console.error(err, ctx); +}); + +// favicon.ico +app.use(function*(next) { + if (this.url.match(/favicon\.ico$/)) { + this.body = ''; + } + + yield next; +}); + +app.use(function*(next) { + console.log(this.method.info, this.url); + yield next; +}); + +// 使用routes +routes(router, app); +app.use(router.routes()); + +if (debug) { + var webpackDevMiddleware = require('koa-webpack-dev-middleware'), + webpack = require('webpack'), + webpackDevConf = require('./webpack-dev-mock.config'); + + app.use(webpackDevMiddleware(webpack(webpackDevConf), { + contentBase: webpackDevConf.output.path, + publicPath: webpackDevConf.output.publicPath, + hot: true, + stats: { + cached: false, + colors: true + } + })); +} + +// 处理静态资源文件 +app.use(serve(path.resolve(__dirname, viewDir), { + maxage: 0 +})); + +app = http.createServer(app.callback()); + +app.listen(3005, '0.0.0.0', function() { + console.log('app listen localhost:3005 success.'); +}); diff --git a/test/app.jsx b/test/app.jsx new file mode 100644 index 0000000..028f5ab --- /dev/null +++ b/test/app.jsx @@ -0,0 +1,15 @@ +// import helpers +import test from 'tape'; + +// import app +import App from '../app/app.js'; + +test('App suite', function(it) { + it.test('# should render', function(t) { + // render + App.start(); + // verify it exists + t.equal(1, document.getElementById('mainContainer').children.length); + t.end(); + }); +}); diff --git a/test/component-generated.jsx b/test/component-generated.jsx new file mode 100644 index 0000000..6dd0542 --- /dev/null +++ b/test/component-generated.jsx @@ -0,0 +1,23 @@ +/* + +*/ +/* global describe, it */ +// import helpers +import test from 'tape'; +import React from 'React/addons'; +const {TestUtils} = React.addons; + +// import page +import Component from '../src/components/generated/index.js'; + +test('Generated component suite', function(it) { + it.test('# should render', function(t) { + // render + const comp = TestUtils.renderIntoDocument(); + + // check if link and name are correct + const divs = TestUtils.scryRenderedDOMComponentsWithTag(comp, 'div'); + t.equal(1, divs.length); + t.end(); + }); +}); diff --git a/test/environment.js b/test/environment.js new file mode 100644 index 0000000..9d894a7 --- /dev/null +++ b/test/environment.js @@ -0,0 +1,18 @@ +import localStorage from 'localStorage'; +import {jsdom} from 'jsdom'; + +// say we're not in webpack environment +// this is required to skip including styles +global.__WEBPACK__ = false; // eslint-disable-line no-underscore-dangle + +// init jsdom +global.document = jsdom('
'); +global.window = global.document.defaultView; +global.navigator = global.window.navigator; + +// mock location +global.window.location.href = 'http://localhost/'; + +// local storage polyfill +global.window.localStorage = localStorage; +global.localStorage = localStorage; diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..6c63190 --- /dev/null +++ b/test/index.js @@ -0,0 +1,5 @@ +require('babel/register'); +require('./environment'); +require('./app.jsx'); +require('./page-test.jsx'); +require('./component-generated.jsx'); diff --git a/test/page-test.jsx b/test/page-test.jsx new file mode 100644 index 0000000..dedc610 --- /dev/null +++ b/test/page-test.jsx @@ -0,0 +1,23 @@ +/* + +*/ +/* global describe, it */ +// import helpers +import test from 'tape'; +import React from 'React/addons'; +const {TestUtils} = React.addons; + +// import page +import Page from '../src/pages/test/page.js'; + +test('Test page suite', function(it) { + it.test('# should render', function(t) { + // render + const comp = TestUtils.renderIntoDocument(); + + // check if link and name are correct + const divs = TestUtils.scryRenderedDOMComponentsWithTag(comp, 'h1'); + t.equal(1, divs.length); + t.end(); + }); +}); diff --git a/webpack-dev-mock.config.js b/webpack-dev-mock.config.js new file mode 100644 index 0000000..eb7c934 --- /dev/null +++ b/webpack-dev-mock.config.js @@ -0,0 +1,4 @@ +module.exports = require("./make-webpack.config")({ + debug: true, + mock: true +}); diff --git a/webpack-dev-server.config.js b/webpack-dev-server.config.js new file mode 100644 index 0000000..596b4fb --- /dev/null +++ b/webpack-dev-server.config.js @@ -0,0 +1,3 @@ +module.exports = require("./make-webpack.config")({ + debug: true +}); diff --git a/webpack-hot-dev-server.config.js b/webpack-hot-dev-server.config.js new file mode 100644 index 0000000..7d9afea --- /dev/null +++ b/webpack-hot-dev-server.config.js @@ -0,0 +1,4 @@ +module.exports = require("./make-webpack.config")({ + debug: true, + hot: true +}); diff --git a/webpack-production.config.js b/webpack-production.config.js new file mode 100644 index 0000000..236f133 --- /dev/null +++ b/webpack-production.config.js @@ -0,0 +1,3 @@ +module.exports = require("./make-webpack.config")({ + +});