├── .babelrc ├── .editorconfig ├── .eslintrc ├── LICENSE ├── karma.conf.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── readme.md ├── src ├── actions │ ├── InsideAction.js │ └── LoanIndexActions.js ├── components │ └── common │ │ ├── Header.js │ │ ├── Header.scss │ │ ├── Loading.js │ │ └── Loading.scss ├── containers │ ├── Inside.scss │ ├── InsideContainer.js │ ├── LoanIndex.scss │ └── LoanIndexContainer.js ├── images │ └── logo_my.png ├── index.html ├── index.js ├── reducers │ ├── LoanIndexReducers.js │ ├── globalReducers.js │ └── indexReducers.js ├── router │ └── router.js ├── stores │ ├── store-dev.js │ └── store.js ├── styles │ ├── _iconfont.scss │ ├── _mixin.scss │ ├── _reset.scss │ ├── _variable.scss │ ├── common.scss │ ├── index.scss │ └── layer.css └── untils │ ├── Untils.js │ ├── const.js │ └── respon.js ├── test ├── components │ └── MainTest.js ├── helpers │ └── shallowRenderHelper.js └── loadtests.js ├── webpack.analy.js ├── webpack.config.js ├── webpack.dist.js ├── webpack.server.js └── webpack.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env","@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true 11 | } 12 | }, 13 | "env": { 14 | "browser": true, 15 | "amd": true, 16 | "es6": true, 17 | "node": true, 18 | "mocha": true 19 | }, 20 | "rules": { 21 | "global-strict": 0, 22 | "no-underscore-dangle": 0, 23 | "no-console": 0, 24 | "no-trailing-spaces": [1, { "skipBlankLines": true }], //不允许在语句后存在多余的空格 25 | "no-alert": 0, 26 | "react/jsx-uses-react": 1, 27 | "react/jsx-uses-vars": 1, 28 | 29 | // allow async-await 30 | "generator-star-spacing": "off", 31 | // allow debugger during development 32 | // "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 33 | 34 | "space-before-function-paren": 0, // 强制在 function的左括号之前使用一致的空格 35 | 36 | 37 | 38 | //警告 39 | "quotes": ["warn", "single"], //建议使用单引号 40 | // "no-inner-declarations": [1, "both"], //不建议在{}代码块内部声明变量或函数 41 | "no-extra-boolean-cast": 1, //多余的感叹号转布尔型 42 | "no-extra-semi": 1, //多余的分号 43 | "semi": ["off", "always"], // js语句结尾必须使用分号 44 | "no-extra-parens": 1, //多余的括号 45 | "no-empty": 1, //空代码块 46 | "no-use-before-define": [1, "nofunc"], //使用前未定义 47 | "complexity": [1, 25], //圈复杂度大于25 警告 48 | 49 | 50 | 51 | //常见错误 52 | "comma-dangle": [0, "never"], //定义数组或对象最后多余的逗号 53 | // "no-debugger": 2, //debugger 调试代码未删除 54 | "no-constant-condition": 2, //常量作为条件 55 | "no-dupe-args": 2, //参数重复 56 | "no-dupe-keys": 2, //对象属性重复 57 | "no-duplicate-case": 2, //case重复 58 | "no-empty-character-class": 2, //正则无法匹配任何值 59 | "no-invalid-regexp": 2, //无效的正则 60 | "no-func-assign": 2, //函数被赋值 61 | "valid-typeof": 2, //无效的类型判断 62 | "no-unreachable": 2, //不可能执行到的代码 63 | "no-unexpected-multiline": 1, //行尾缺少分号可能导致一些意外情况 64 | "no-sparse-arrays": 2, //数组中多出逗号 65 | "no-shadow-restricted-names": 2, //关键词与命名冲突 66 | "no-undef": 2, //变量未定义 67 | "no-unused-vars": 0, //变量定义后未使用 68 | "no-cond-assign": 2, //条件语句中禁止赋值操作 69 | "no-native-reassign": 2, //禁止覆盖原生对象 70 | 71 | //代码风格优化 72 | "no-else-return": 1, //在else代码块中return,else是多余的 73 | "no-multi-spaces": 1, //不允许多个空格 74 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}],//object直接量建议写法 : 后一个空格签名不留空格 75 | "block-scoped-var": 1, //变量定义后未使用 76 | "consistent-return": 1, //函数返回值可能是不同类型 77 | "accessor-pairs": 2, //object getter/setter方法需要成对出现 78 | "dot-location": [2, "property"], //换行调用对象方法 点操作符应写在行首 79 | "no-lone-blocks": 0, //多余的{}嵌套 80 | "no-empty-label": 0, //无用的标记 81 | "no-extend-native": 2, //禁止扩展原生对象 82 | "no-floating-decimal": 2, //浮点型需要写全 禁止.1 或 2.写法 83 | "no-loop-func": 2, //禁止在循环体中定义函数 84 | "no-new-func": 2, //禁止new Function(...) 写法 85 | "no-self-compare": 2, //不允与自己比较作为条件 86 | "no-sequences": 2, //禁止可能导致结果不明确的逗号操作符 87 | "no-throw-literal": 2, //禁止抛出一个直接量 应是Error对象 88 | "no-return-assign": [2, "always"], //不允return时有赋值操作 89 | "no-redeclare": [2, {"builtinGlobals": true}],//不允许重复声明 90 | "no-unused-expressions": [2, {"allowShortCircuit": true, "allowTernary": true}],//未使用的表达式 91 | "no-useless-call": 2, //无意义的函数call或apply 92 | "no-useless-concat": 2, //无意义的string concat 93 | "no-void": 2, //禁用void 94 | "no-with": 2, //禁用with 95 | "no-warning-comments": [1, { "terms": ["fixme", "any other term"], "location": "anywhere" }],//标记未写注释 96 | "curly": 2, //if、else、while、for代码块用{}包围 97 | "one-var": 0, 98 | "no-multiple-empty-lines": [1, {"max": 5, "maxEOF": 7}], //空行 最多三行 99 | 100 | //工程化配置 101 | // "prettier.singleQuote": 1, // 防止格式化代码后单引号变双引号 102 | // "update.channel": 1, // 配置是否从更新通道接收自动更新。更改后需要重启。 103 | // "files.autoSaveDelay": 1500, // 保存延时 intellij webstrom 默认制动保存 104 | // "files.autoSave": "afterDelay" // 文件自动保存 105 | 106 | // "eslint.autoFixOnSave": false, // ESLint 自动修复 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zhoulujun.cn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackCfg; 2 | console.log('process.env.NODE_ENV', process.env.NODE_ENV); 3 | // Set node environment to testing 4 | if (process.env.NODE_ENV === 'development') { 5 | webpackCfg = require('./webpack.server'); 6 | } else if (process.env.NODE_ENV === 'production') { 7 | webpackCfg = require('./webpack.dist'); 8 | } else { 9 | webpackCfg = require('./webpack.test'); 10 | webpackCfg.mode = 'production'; 11 | } 12 | 13 | process.env.NODE_ENV = 'test'; 14 | 15 | 16 | module.exports = function (config) { 17 | config.set({ 18 | basePath: '', 19 | browsers: ['PhantomJS'], // 这里使用的是PhantomJS作为浏览器测试环境,这个插件支持DOM, CSS, JSON, Canvas, and SVG.的解析 20 | // frameworks: [ 'mocha', 'chai' ],// 下面的测试框架是用来测试js 21 | frameworks: ['jasmine'],// 下面的测试框架是用来测试js 22 | files: [//关于loadtests.js其实就是把需要测试的文件都require进来,然后一股脑的在上面的browsers里面跑,使用frameworks测试js,通过reporters输出报告 23 | 'test/loadtests.js' 24 | ], 25 | // exclude: [/node_modules/], 26 | port: 18218, 27 | captureTimeout: 60000, 28 | client: { 29 | mocha: {} 30 | }, 31 | singleRun: true, 32 | preprocessors: { 33 | 'test/loadtests.js': ['webpack', 'sourcemap'] 34 | }, 35 | //coverage是代码测试覆盖率的一个reporter,也就是说告诉你项目的代码有多少测试了 36 | reporters: ['mocha', 'coverage'],//代码覆盖率 _下面的是用来出报告的 37 | coverageReporter: { 38 | dir: 'coverage/', 39 | reporters: [ 40 | {type: 'html'}, 41 | {type: 'text'} 42 | ] 43 | }, 44 | // 下面给webpack指定相关的配置文件 45 | webpack: webpackCfg, 46 | webpackServer: { 47 | noInfo: true 48 | }, 49 | 50 | // plugins: [ 51 | // // Karma will require() these plugins 52 | // 'karma-jasmine', 53 | // // 'karma-chrome-launcher', 54 | // 55 | // // inlined plugins 56 | // // {'framework:xyz': ['factory', factoryFn]}, 57 | // // require('./plugin-required-from-config') 58 | // ] 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wepack4-react-project-template", 3 | "version": "2.0.1", 4 | "description": "webpack4 react sass标准模板工程", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development", 8 | "build": "rm -r dist & webpack --mode production", 9 | "dist": "rm -r dist & webpack --config webpack.dist.js", 10 | "analy": "webpack --config webpack.analy.js", 11 | "start": "node webpack.server.js", 12 | "test": "karma start", 13 | "clean": "rm -r dist/*" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/zhoulujun/wepack4-react-project-template.git" 18 | }, 19 | "keywords": [ 20 | "webpack4", 21 | "sass", 22 | "react", 23 | "react-router", 24 | "redux", 25 | "boilerplate" 26 | ], 27 | "author": "zhoulujun.cn", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/zhoulujun/wepack4-react-project-template/issues" 31 | }, 32 | "homepage": "https://github.com/zhoulujun/wepack4-react-project-template.git", 33 | "devDependencies": { 34 | "@babel/core": "^7.2.2", 35 | "@babel/preset-env": "^7.2.3", 36 | "@babel/preset-react": "^7.0.0", 37 | "autoprefixer": "^9.4.4", 38 | "babel-eslint": "^10.0.1", 39 | "babel-loader": "^8.0.4", 40 | "chai": "^4.2.0", 41 | "css-loader": "^2.1.0", 42 | "es6-promise": "^4.2.5", 43 | "eslint": "^5.12.0", 44 | "eslint-loader": "^2.1.1", 45 | "eslint-plugin-react": "^7.12.3", 46 | "eslint-plugin-vue": "^5.1.0", 47 | "file-loader": "^3.0.1", 48 | "glob": "^7.1.3", 49 | "happypack": "^5.0.1", 50 | "history": "^3.2.1", 51 | "html-loader": "^0.5.5", 52 | "html-webpack-plugin": "^3.2.0", 53 | "image-webpack-loader": "^4.6.0", 54 | "immutable": "^4.0.0-rc.12", 55 | "isomorphic-fetch": "^2.2.1", 56 | "isparta-instrumenter-loader": "^1.0.1", 57 | "jasmine": "^3.3.1", 58 | "jasmine-core": "^3.3.0", 59 | "karma": "^3.1.4", 60 | "karma-coverage": "^1.1.2", 61 | "karma-jasmine": "^2.0.1", 62 | "karma-mocha": "^1.3.0", 63 | "karma-mocha-reporter": "^2.2.5", 64 | "karma-phantomjs-launcher": "^1.0.4", 65 | "karma-sourcemap-loader": "^0.3.7", 66 | "karma-webpack": "^3.0.5", 67 | "mini-css-extract-plugin": "^0.5.0", 68 | "minimatch": "^3.0.4", 69 | "mocha": "^5.2.0", 70 | "open": "0.0.5", 71 | "phantomjs-prebuilt": "^2.1.16", 72 | "postcss": "^7.0.7", 73 | "postcss-loader": "^3.0.0", 74 | "react": "^16.7.0", 75 | "react-addons-test-utils": "^15.6.2", 76 | "react-dom": "^16.7.0", 77 | "react-hot-loader": "^4.6.3", 78 | "react-loading": "^2.0.3", 79 | "react-redux": "^6.0.0", 80 | "react-router": "^3.2.1", 81 | "redux": "^4.0.1", 82 | "redux-thunk": "^2.3.0", 83 | "sass.js": "^0.11.0", 84 | "sassjs-loader": "^2.0.0", 85 | "style-loader": "^0.23.1", 86 | "url-loader": "^1.1.2", 87 | "webpack": "^4.28.3", 88 | "webpack-assets-manifest": "^3.1.1", 89 | "webpack-bundle-analyzer": "^3.0.3", 90 | "webpack-cli": "^3.2.0", 91 | "webpack-dev-server": "^3.1.14", 92 | "webpack-manifest-plugin": "^2.0.4", 93 | "webpack-sftp-client": "^1.2.1", 94 | "webpack-subresource-integrity": "^1.3.1" 95 | }, 96 | "dependencies": {} 97 | } 98 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/7/192:59 PM 3 | *@version 1.0.0 4 | */ 5 | module.exports = { 6 | plugins: [ 7 | require('autoprefixer')({ 8 | browsers: [ 9 | "> 1%", 10 | "last 105 versions", 11 | "not ie <= 8", 12 | "ios >= 8", 13 | "android >= 4.0" 14 | ] 15 | }) 16 | ] 17 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | webpack4-react-redux boilerplate 2 | Migrating to webpack4 sass3 babel7 react16 react-router3 Redux 3 | > webpack 一直以来最饱受诟病的就是其配置门槛极高,配置内容极其复杂和繁琐,容易让人从入门到放弃,而它的后起之秀如 rollup、parcel 等均在配置流程上做了极大的优化,做到开箱即用,所以webpack 4 也从中借鉴了不少经验来提升自身的配置效率。愿世间再也不需要 webpack 配置工程师。 4 | 5 | 但是,webpack4还是需要n多优化部分,配置下来,实为不易。而实际开发也不需要浪费这个时间——了解即可 6 | 其实,https://github.com/react-boilerplate 够好了 7 | 但是还是觉得自己定制的更加亲切~^~,第二个,也分享下经验。 8 | 9 | 目前node-sass sass-loader 替换为 sass.js 与sassjs-loader 免除node-sass 安装困难的烦恼 10 | 11 | 启动: 12 | ```bash 13 | npm run start 14 | ``` 15 | 打包: 16 | ```bash 17 | npm run build 18 | ``` 19 | 20 | ##### 重要包提示 21 | + 生成manifest.json 离线比对 webpack-manifest-plugin 22 | + 自动上传服务器发包 webpack-sftp-client 23 | + 生成html5 integrity webpack-subresource-integrity 24 | + 多线程处理 happypack 25 | + 图片压缩 image-webpack-loader 26 | + js语法检查 eslint 27 | + sass预处理 node-ass 28 | + css兼容补全 autoprefixer 29 | + 测试框架 karma mocha jasmine 30 | 31 | 32 | 33 | 34 | # 目录结构 35 | + src //工程目录 36 | - actions 37 | - components 38 | - containers 39 | - images 40 | - reducers 41 | - router 42 | - stores 43 | - styles 44 | - untils 45 | + test //测试目录 46 | + dist //打包目录 47 | + coverage //测试报告目录 48 | 49 | 50 | # 团队规范 51 | 遵从平台发布前端规范标准,节选以下要点: 52 | 53 | ## 命名规范 54 | 遵从Camel命名 55 | 56 | ### 变量命名规范: 57 | 58 | ### js规范,请遵从eslint 59 | + 常量全部大写,单词间下划线分隔 60 | + 类采用Pascal命名 61 | ### scss 规范 62 | + css 按照工程结构 嵌套书写,嵌套层级不超过三层——采用 @at-root 63 | + 非页面引用scss文件,加前缀 _ 如:_fun.scss _mixin.scss 64 | 65 | # 构建过程 节选关键步骤 66 | ### 构建目录初始化 67 | ```bash 68 | mkdir yourFileName 69 | cd yourFileName 70 | ``` 71 | 根据工程目录结构,构建相关文件 72 | …… 73 | ___ 74 | ```bash 75 | npm init 76 | npm install webpack webpack-cli --save-dev 77 | ``` 78 | ##### 注:--save-dev和--save的区别: 79 | development很明显就是我们开发所需要的依赖包,而打包好上线的话是不需要这些包的,一来各种包加起来太大,二来它只是我们开发提高效率的工具而已; 80 | 由于本工程只在本地跑,最终还是sftp自动dist 到服务器,所以暂略 81 | 82 | 修改package.json ,npm run dev 检查打包结果 83 | ```json 84 | { 85 | "scripts": { 86 | "dev": "webpack --mode development", 87 | "build": "webpack --mode production" 88 | } 89 | } 90 | ``` 91 | ##### 注:webpack4只需要一个--mode选项 指定 production||development 92 | 参考http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html 93 | +如果是并行执行(即同时的平行执行),可以使用&符号。 94 | +如果是继发执行(即只有前一个任务成功,才执行下一个任务),可以使用&&符号。 95 | npm run script1.js & npm run script2.js 96 | npm run script1.js && npm run script2.js 97 | 98 | ___ 99 | #### 配置webpack配置文件 webpack.config.js 100 | ##### rule对象参数说明 101 | + test: A condition that must be met 必须满足的条件 102 | + exclude: A condition that must not be met 不能满足的条件 103 | + include: A condition that must be met 必须满足的条件 104 | + loader: A string of “!” separated loaders 用 “!”分割loaders 105 | + loaders: An array of loaders as string loaders的字符串数组 106 | 107 | #### 基础loader 108 | 109 | ```bash 110 | npm install css-loader style-loader html-loader url-loader file-loader --save-dev 111 | ``` 112 | 113 | ```javascript 114 | [ 115 | { 116 | test: /\.html$/, 117 | use: 'html-loader' 118 | }, 119 | { 120 | test: /\.css$/, 121 | use: [ 122 | { 123 | loader: 'style-loader', 124 | options:{ 125 | // singleton:true //处理为单个style标签 126 | } 127 | }, 128 | { 129 | loader: 'css-loader', 130 | options:{ 131 | // minimize:true //压缩css 132 | } 133 | } 134 | ] 135 | }, 136 | { 137 | test:/\.(png|jpg|jpeg|gif)$/,//图片处理 138 | use:[ 139 | { 140 | loader: 'url-loader', 141 | options:{ 142 | limit:2048, 143 | name:'[name][hash].[ext]' 144 | } 145 | }, 146 | { 147 | loader: 'file-loader', 148 | publicPath:publicPath, 149 | outputPath: 'dist/', 150 | useRelativePath: true 151 | } 152 | ] 153 | }, 154 | { 155 | test: /\.(woff|woff2|eot|ttf|otf)$/,//字体处理 156 | use: ['url-loader'] 157 | }, 158 | 159 | ] 160 | ``` 161 | #### 配置babel 编译js 162 | ```bash 163 | npm install --save-dev babel-loader @babel/core @babel/preset-env 164 | npm install --save-dev eslint-loader 165 | ``` 166 | 167 | ```javascript 168 | [ 169 | { 170 | test: /\.js$/, 171 | loader: 'babel-loader', 172 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 173 | } 174 | ] 175 | ``` 176 | 177 | 参考:https://segmentfault.com/a/1190000010468759 178 | 179 | babel7.0后,需要@ @babel/core vs babel-core babel插件和版本需要对应上,不然掉坑 180 | 参考https://www.w3ctech.com/topic/2150 181 | babel-preset-es2015 babel-plugin-transform-runtime babel-plugin-add-module-exports babel-plugin-transform-runtime babel-plugin-transform-class-properties 182 | 183 | 184 | ##### .babelrc配置文件 185 | ````json 186 | { 187 | "presets": ["@babel/preset-env","@babel/preset-react"] 188 | } 189 | ```` 190 | 191 | 192 | #### 配置eslint 检查 193 | 194 | ```bash 195 | npm install --save-dev eslint eslint-loader babel-eslint eslint-plugin-react 196 | ``` 197 | 198 | ```javascript 199 | [ 200 | {//eslint 检查 201 | test: /\.(js|jsx)$/, 202 | enforce: 'pre', 203 | loader: ['eslint-loader'], 204 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 205 | }, 206 | ] 207 | ``` 208 | 增加.eslintrc配置 209 | 210 | 具体查看 https://www.zhoulujun.cn/html/tools/grunt/2016_0519_7832.html 211 | ##### intellij 会自动检车eslint 212 | 213 | 214 | #### 处理html 215 | npm install html-webpack-plugin 216 | ```javascript 217 | new HtmlWebpackPlugin({ 218 | filename: './index.html',//输出文件 219 | template: 'src/index.html',//模板文件 220 | inject: 'body',//插入位置 221 | chunks: ['index'], 222 | hash: true, 223 | minify: { 224 | caseSensitive:false, 225 | removeComment:true,//移除注释 226 | collapseWhitespace:false//移除多余空格 227 | } 228 | }) 229 | ``` 230 | ##### chunks: ['index','vendor','manifest'], 一定要记得 各处的chunk ,特别是optimization.runtimeChunk 231 | #### 处理图片 - 压缩图片 232 | 参考:http://shirmy.me/2018/05/15/webpack-图片、文件处理/ 233 | ```bash 234 | npm install image-webpack-loader --save-dev 235 | ``` 236 | 237 | ```javascript 238 | [ 239 | { 240 | test: /\.(png|jpg|jpeg|gif)$/i,//图片处理 241 | use: [ 242 | { 243 | loader: 'url-loader', 244 | options: { 245 | limit: 0,//图片不转base64,增加css的阻塞时间,开启http2,所以也不用雪碧图 246 | name: '[name].[hash:5].[ext]', 247 | } 248 | }, 249 | ] 250 | }, 251 | {//压缩图片 252 | loader: 'image-webpack-loader', 253 | options: { 254 | bypassOnDebug: true, 255 | } 256 | }, 257 | ] 258 | ``` 259 | 260 | 261 | 262 | #### 配置webapck server 263 | ```bash 264 | npm install webpack-dev-server open --save-dev 265 | ``` 266 | 参看 webpack.server.js 注释 267 | 268 | ```json 269 | { 270 | "start": "node webpack.server.js" 271 | } 272 | ``` 273 | npm start 启动项目 274 | 275 | ### 配置css优化设置 276 | 277 | ```bash 278 | npm install --save-dev postcss-loader autoprefixer postcss autoprefixer mini-css-extract-plugin 279 | ``` 280 | ##### 注: 281 | + webpack4已经废弃 extract-text-webpack-plugin 这个插件了,现在使用的是 mini-css-extract-plugin 282 | + 在项目根目录新建postcss.config.js文件,并对postcss进行配置: 283 | ```javascript 284 | module.exports = { 285 | plugins: { 286 | 'autoprefixer': { 287 | browsers: [ 288 | "> 1%", 289 | "last 5 versions", 290 | "not ie <= 9", 291 | "ios >= 8", 292 | "android >= 4.0" 293 | ] 294 | } 295 | } 296 | }; 297 | ``` 298 | 不然会报出:Error: No PostCSS Config found 299 | 300 | #### 自动消除冗余的css代码 301 | ```bash 302 | npm install --save-dev optimize-css-assets-webpack-plugin 303 | ``` 304 | ##### css压缩优化空间不大,nginx开启gzip的情况,很有限,有点画蛇添足。但是,聊胜于无吧^_^ 305 | #### 配置sass 306 | ```bash 307 | npm install --save-dev node-sass sass-loader 308 | 309 | ``` 310 | 但是node-sass 是个坑货 311 | 所以最好换位 312 | ```bash 313 | 314 | npm i sass.js sassjs-loader 315 | 316 | ``` 317 | https://www.npmjs.com/package/sass 318 | 319 | https://www.npmjs.com/package/sassjs-loader 320 | 321 | 322 | 323 | ## webpack构建优化 324 | 325 | #### 多线程 happypack 326 | 327 | ```bash 328 | npm install --save-dev happypack 329 | 330 | ``` 331 | 332 | 配置第三方包,比如jquery 333 | ```bash 334 | npm install imports-loader --save-dev 335 | ``` 336 | ```javascript 337 | [ 338 | { 339 | loader: 'imports-loader', 340 | options: { 341 | // 模块为 value,同样webpack也会解析它,如果没有则从alias中解析它 342 | $: 'jquery' 343 | } 344 | } 345 | ] 346 | ``` 347 | #### 增加manifest.json 配置,缓存校对下载, 增加js integrity 安全校验 348 | ```bash 349 | npm install --save-dev webpack-subresource-integrity webpack-assets-manifest 350 | ``` 351 | 两个插件准备写成一个,看来不到春节没有时间 352 | 353 | 354 | #### 增加webpack 模块分析 355 | 配置参看 webpack.analy 356 | 参考文章:https://www.cnblogs.com/ssh-007/p/7944491.html 357 | ```bash 358 | npm install --save-dev webpack-bundle-analyzer 359 | ``` 360 | 361 | 362 | #### webpack压缩js、css文件 363 | ```bash 364 | npm install --save-dev webpack-parallel-uglify-plugin optimize-css-assets-webpack-plugin cssnano 365 | ``` 366 | ```javascript 367 | 368 | const UglifyJsPlugin=require('webpack-parallel-uglify-plugin'); 369 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 370 | const cssnano = require('cssnano'); 371 | 372 | 373 | config.optimization = { 374 | minimizer:[ 375 | new UglifyJsPlugin({ 376 | cache: true, // node_modules/.cache/uglifyjs-webpack-plugin 377 | parallel: os.cpus().length, // 并行 default:true os.cpus().length - 1 378 | uglifyOptions: { 379 | ecma: 5, 380 | mangle: true, 381 | }, 382 | sourceMap: false, 383 | }), 384 | new OptimizeCSSAssetsPlugin({ 385 | assetNameRegExp: /\.css$/g, 386 | cssProcessor: cssnano, // 默认使用 cssnano 处理 css 387 | cssProcessorOptions: { 388 | reduceIdents: false, // 禁止将 keyframes 自动更名 389 | mergeIdents: false, // 禁止自动合并 keyframes 390 | discardUnused: false, // 禁止移除掉未使用的 keyframes 391 | autoprefixer: false, // 禁止默认删除掉一些前缀,以减少兼容性的问题 392 | zindex: false, // 禁止自动转换 z-index 393 | map: false, 394 | }, 395 | }), 396 | ], 397 | ... 398 | 399 | } 400 | ``` 401 | 参考:https://jdc.jd.com/archives/212580 402 | > Webpack v4 以前使用内置的 webpack.optimize.UglifyJsPlugin 插件,在 Webpack 4 以后,开始使用 ^1.0.0 独立的版本。 403 | 404 | 405 | #### 增加上传至服务器 406 | ```bash 407 | npm install --save-dev webpack-sftp-client 408 | ``` 409 | ```javascript 410 | new WebpackSftpClient({ 411 | port: '20020', 412 | host: '10.111.111.38', 413 | username: 'nginx', 414 | password: 'zlj@123', 415 | path: './dist/',//本地上传目录 416 | remotePath: '/usr/local/nginx/html/demo',//服务器目标目录 417 | verbose: true 418 | }) 419 | ``` 420 | 421 | #### 配置react 422 | ```bash 423 | npm install --save-dev react react-dom @babel/preset-react babel-preset-react eslint-plugin-react 424 | 425 | ``` 426 | 427 | 428 | 429 | #### 配置react router 430 | 431 | ```bash 432 | npm install --save-dev react-router@3.2.1 history redux react-redux redux-thunk 433 | 434 | ``` 435 | react-router v4 官方教程 436 | 第一个是:react-router-dom,配置方面的 437 | 第二是code-splitting:https://reacttraining.com/react-router/web/guides/code-splitting 438 | React-router4简约教程 https://www.jianshu.com/p/bf6b45ce5bcc 439 | react-router@2.8.1 2.x 不兼容 440 | ##### react-router4升级踩坑 https://www.jianshu.com/p/56dce67b8b13 441 | 推荐 react-router@3.2.1 442 | ```bash 443 | npm install --save-dev react-router-dom history redux react-redux redux-thunk react-router-redux 444 | 445 | ``` 446 | 447 | 448 | 449 | ```bash 450 | npm install --save-dev react-loading react-hot-loader 451 | 452 | ``` 453 | 454 | ```bash 455 | npm install --save-dev es6-promise isomorphic-fetch immutable 456 | 457 | ``` 458 | 459 | # 测试 460 | Karma文档 http://karma-runner.github.io/3.0/config/configuration-file.html 461 | + 测试管理工具 karma 462 | + 测试框架 jasmine ||mocha&&断言库 chai||expect 463 | + 测试覆盖率统计工具 Karma-Coverage 464 | + 测试浏览器 PhantomJs||chrome 465 | ##### 之前一直是Mocha做测试,后面更喜欢 jasmine,因为之前有个童鞋就叫这个名字 466 | 推荐阅读:https://www.jianshu.com/p/6726c0410650 467 | 468 | ```bash 469 | npm install --save-dev karma karma-coverage karma-mocha karma-mocha-reporter karma-phantomjs-launcher karma-sourcemap-loader karma-webpack 470 | 471 | ``` 472 | 473 | ```bash 474 | npm install --save-dev karma-jasmine jasmine-core 475 | ``` 476 | 477 | ```bash 478 | npm install --save-dev chai isparta-instrumenter-loader mocha phantomjs-prebuilt react-addons-test-utils 479 | 480 | ``` 481 | 482 | 483 | 484 | ```bash 485 | npm install --save-dev glob minimatch 486 | 487 | ``` 488 | node的glob模块允许你使用 *等符号, 来写一个glob规则,像在shell里一样,获取匹配对应规则的文件. 489 | 这个glob工具基于javascript.它使用了 minimatch 库来进行匹配 490 | https://www.cnblogs.com/xinxingyu/p/5736244.html 491 | 492 | 493 | 494 | react-composition //中文输入问题 495 | 496 | webpack 相关优化,可参看:https://www.zhoulujun.cn/html/tools/webpack/2016_0218_7492.html 497 | 498 | 499 | #npm 包简要说明————待优化 500 | ``` 501 | { 502 | "devDependencies": { 503 | "@babel/core": "^7.2.2", 504 | "@babel/preset-env": "^7.2.3", 505 | "@babel/preset-react": "^7.0.0", 506 | "autoprefixer": "^9.4.4",//css不全兼容代码 507 | "babel-eslint": "^10.0.1", 508 | "babel-loader": "^8.0.4", 509 | "chai": "^4.2.0",//断言库 510 | "css-loader": "^2.1.0", 511 | "es6-promise": "^4.2.5", 512 | "eslint": "^5.12.0", 513 | "eslint-loader": "^2.1.1", 514 | "eslint-plugin-react": "^7.12.3", 515 | "eslint-plugin-vue": "^5.1.0", 516 | "file-loader": "^3.0.1", 517 | "glob": "^7.1.3", 518 | "happypack": "^5.0.1",//多线程 处理 *-loader 519 | "history": "^3.2.1", //router history 处理 520 | "html-loader": "^0.5.5", 521 | "html-webpack-plugin": "^3.2.0",//入口 html 合成 522 | "image-webpack-loader": "^4.6.0",//图片压缩 523 | "immutable": "^4.0.0-rc.12", 524 | "isomorphic-fetch": "^2.2.1", 525 | "isparta-instrumenter-loader": "^1.0.1", 526 | "jasmine": "^3.3.1",//BDD, framework independent, 测试框架 || https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript 527 | "jasmine-core": "^3.3.0", 528 | "karma": "^3.1.4",//测试管理工具 ||Selenium、WebDriver/Selenium 2、Mocha[1]、JsTestDriver、HTML Runners和Karma,我这里选择使用Karma 529 | "karma-coverage": "^1.1.2",//测试覆盖报告 530 | "karma-jasmine": "^2.0.1", 531 | "karma-mocha": "^1.3.0", 532 | "karma-mocha-reporter": "^2.2.5", 533 | "karma-phantomjs-launcher": "^1.0.4", 534 | "karma-sourcemap-loader": "^0.3.7", 535 | "karma-webpack": "^3.0.5", 536 | "mini-css-extract-plugin": "^0.5.0", 537 | "minimatch": "^3.0.4", 538 | "mocha": "^5.2.0",//测试框架 || https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript 539 | "node-sass": "^4.11.0", 540 | "open": "0.0.5",//打开浏览器 chrome-launch 541 | "phantomjs-prebuilt": "^2.1.16", 542 | "postcss": "^7.0.7", 543 | "postcss-loader": "^3.0.0", 544 | "react": "^16.7.0", 545 | "react-addons-test-utils": "^15.6.2",//react官方的测试插件 546 | "react-dom": "^16.7.0", 547 | "react-hot-loader": "^4.6.3", 548 | "react-loading": "^2.0.3",//loading 动画 549 | "react-redux": "^6.0.0", 550 | "react-router": "^3.2.1",//待升级4.x 551 | "redux": "^4.0.1", 552 | "redux-thunk": "^2.3.0",//异步套件 ||http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html 553 | "sass-loader": "^7.1.0", 554 | "style-loader": "^0.23.1", 555 | "url-loader": "^1.1.2",//处理url 文件打包 556 | "webpack": "^4.28.3", 557 | "webpack-assets-manifest": "^3.1.1", 558 | "webpack-bundle-analyzer": "^3.0.3", 559 | "webpack-cli": "^3.2.0", 560 | "webpack-dev-server": "^3.1.14", 561 | "webpack-manifest-plugin": "^2.0.4",//生成manifest 562 | "webpack-sftp-client": "^1.2.1",//上传服务器 563 | "webpack-subresource-integrity": "^1.3.1"//生成html5 integrity 564 | } 565 | } 566 | ``` 567 | 568 | 后记:从grunt glup webpack1.x -3.x ,每次搭建都耗费一番功夫。但是webpack4 跟 react redux router 里面 各个版本不兼容的坑蛮多。比如用用之前最顺手的项目环境 套webpack4,搞错是一丢丢, 569 | 干脆重新从0开始搭建,估计半年后,又忘记了!——我始终觉得这个东西,没有必要太在上面嗑。对里面稍微了解就好。 570 | 571 | -------------------------------------------------------------------------------- /src/actions/InsideAction.js: -------------------------------------------------------------------------------- 1 | require('es6-promise').polyfill(); 2 | require('isomorphic-fetch'); 3 | 4 | /** 5 | * 数据字典 6 | * **/ 7 | 8 | //获取产品信息 9 | export function queryPublicDataDic() { 10 | return (dispatch) => { 11 | 12 | dispatch({type: 'MASK'}); 13 | 14 | /** 15 | *REPAYMENTSTATUS 近期应还 16 | *LOANSTATUS 我的贷款 17 | * LOANDETAILSTATUS 贷款详情 18 | * REPAYTYPE 还款历史 19 | * SIGNSTATUS 还款卡管理 20 | * RATETYPE 我的贷款 年月日 21 | * */ 22 | 23 | 24 | fetch('/cfs-api/api/common/queryPublicDataDic', { 25 | credentials: 'include' 26 | }).then(res => { 27 | console.log(res); 28 | return res.json(); 29 | }).then(data => { 30 | if (data.code === '200') { 31 | let myData = data.data; 32 | dispatch({type: 'RELOAD_PUBLIC_DATA_DIC', publicDataDic: myData}); 33 | } else { 34 | console.log('cc'); 35 | 36 | } 37 | dispatch({type: 'UNMASK'}) 38 | }).catch(e => { 39 | console.log(e); 40 | dispatch({type: 'UNMASK'}) 41 | }) 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/actions/LoanIndexActions.js: -------------------------------------------------------------------------------- 1 | require('es6-promise').polyfill(); 2 | require('isomorphic-fetch'); 3 | //系统常量 4 | import CONST from '../untils/const'; 5 | 6 | export function undateLoanIndexLoanDatas(loanDatas) { 7 | return (dispatch) => { 8 | dispatch({ 9 | type: 'UPDATE_LOANINDEX_LOANDATAS', 10 | loanDatas 11 | }) 12 | } 13 | } 14 | 15 | //可贷额度查询 16 | export function getIndexLoanDatas() { 17 | return (dispatch) => { 18 | dispatch({type: 'MASK'}); 19 | 20 | fetch('/cfs-api/api/loans/index', { 21 | credentials: 'include' 22 | }).then((res) => { 23 | return res.json(); 24 | }).then((datas) => { 25 | if (datas.code === '200') { 26 | dispatch(undateLoanIndexLoanDatas(datas.data)); 27 | } else { 28 | console.log(datas); 29 | } 30 | dispatch({type: 'UNMASK'}) 31 | }).catch((e) => { 32 | console.log(e); 33 | dispatch({type: 'UNMASK'}) 34 | }) 35 | }; 36 | } 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/common/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Header.scss'; 3 | 4 | import {Router, Route, Link, hashHistory} from 'react-router'; 5 | 6 | 7 | class Header extends React.Component { 8 | 9 | render () { 10 | const {title, rightName, rightFn, showBack, isIcon, claName, backBsId} = this.props; 11 | 12 | return ( 13 |
14 |
15 | 回退 16 |
17 |
{title}
18 |
19 | {rightName} 20 |
21 |
22 | 23 |
24 |
25 | ); 26 | } 27 | goBack(backBsId){ 28 | console.log(backBsId); 29 | } 30 | 31 | } 32 | 33 | 34 | export default Header; 35 | -------------------------------------------------------------------------------- /src/components/common/Header.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variable"; 2 | 3 | .header { 4 | position: relative; 5 | display: flex; 6 | height: 50px; 7 | align-items: center; 8 | justify-content: center; 9 | background-color: $c-head; 10 | color: #FFF; 11 | font-size: .36rem; 12 | box-sizing: content-box; 13 | > .aside { 14 | width: 24%; 15 | } 16 | > .aside:last-child { 17 | text-align: right; 18 | } 19 | > .title { 20 | width: 52%; 21 | text-align: center; 22 | font-size: 18px; 23 | } 24 | .back { 25 | padding-left: 15px; 26 | display: flex; 27 | i { 28 | font-size: 28px; 29 | } 30 | } 31 | .right { 32 | font-size: 12px; 33 | padding-right: 15px; 34 | text-align: right; 35 | } 36 | .iconfont { 37 | font-size: 18px; 38 | } 39 | 40 | } 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/common/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Loading.scss'; 3 | 4 | import Loading from 'react-loading'; 5 | 6 | class Load extends React.Component { 7 | 8 | componentDidMount () { 9 | var load = document.querySelector('.loading-ct'); 10 | var loadText = document.querySelector('.loading-text'); 11 | loadText.innerHTML = '加载中...'; 12 | } 13 | 14 | render () { 15 | const {show} = this.props; 16 | return
17 |
18 |
19 | 20 |
21 |

22 |
23 |
; 24 | } 25 | } 26 | 27 | 28 | 29 | export default Load; 30 | -------------------------------------------------------------------------------- /src/components/common/Loading.scss: -------------------------------------------------------------------------------- 1 | .loading-ct{ 2 | position: fixed; 3 | top:0; 4 | width: 100%; 5 | height: 100%; 6 | z-index: 9999; 7 | display: flex; 8 | align-items:center; 9 | justify-content:center; 10 | } 11 | 12 | .loading-wrapper{ 13 | width:150px; 14 | height: 115px; 15 | border-radius: 10px; 16 | background-color: rgba(0,0,0,.5); 17 | } 18 | 19 | .loadEffect{ 20 | margin-top:10px; 21 | text-align:center; 22 | } 23 | .loading-text{ 24 | margin-top:-15px; 25 | font-size:15px; 26 | text-align: center; 27 | color:#ffffff; 28 | } 29 | 30 | 31 | .loadEffect div{ 32 | display:inline-block; 33 | margin:0 auto; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/containers/Inside.scss: -------------------------------------------------------------------------------- 1 | 2 | .inside-wrap { 3 | box-sizing: border-box; 4 | > div { 5 | height: 100%; 6 | overflow-y: hidden; 7 | overflow-x: hidden; 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/containers/InsideContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import React from 'react'; 3 | import {bindActionCreators} from 'redux'; 4 | import {connect} from 'react-redux'; 5 | 6 | import * as InsideAction from '../actions/InsideAction'; 7 | 8 | import Loading from '../components/common/Loading'; 9 | import './Inside.scss'; 10 | 11 | function mapStateToProps (state) { 12 | return { 13 | loading: state.global.get('loading') 14 | }; 15 | } 16 | 17 | function mapDispatchToProps (dispatch) { 18 | return bindActionCreators(InsideAction, dispatch); 19 | } 20 | 21 | class app extends React.Component { 22 | 23 | 24 | componentWillMount () { 25 | /**更新数据**/ 26 | } 27 | 28 | render () { 29 | const {loading} = this.props; 30 | 31 | return
32 | 33 |
34 | demo 35 | {this.props.children} 36 |
37 |
; 38 | 39 | } 40 | componentDidMount(){ 41 | /**请求异步数据**/ 42 | // this.props.queryPublicDataDic(); 43 | } 44 | 45 | } 46 | 47 | const MainContainer = connect(mapStateToProps, mapDispatchToProps)(app); 48 | 49 | export default MainContainer; 50 | -------------------------------------------------------------------------------- /src/containers/LoanIndex.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/variable"; 2 | 3 | .main-container { 4 | overflow: hidden; 5 | background-color: #ffffff; 6 | } 7 | 8 | .loanIndexPage { 9 | font-size: 0.3rem; 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/LoanIndexContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import React from 'react'; 3 | import {bindActionCreators} from "redux"; 4 | import {connect} from 'react-redux'; 5 | import immutable from 'immutable'; 6 | require('es6-promise').polyfill(); 7 | 8 | 9 | import * as LoanIndexActions from '../actions/LoanIndexActions'; 10 | 11 | import Header from '../components/common/Header'; 12 | import './LoanIndex.scss'; 13 | 14 | 15 | function mapStateToProps(state) { 16 | return { 17 | loanIndex: state.loanIndexDatas.get('loanDatas'), 18 | 19 | } 20 | } 21 | 22 | function mapDispatchToProps(dispatch) { 23 | return bindActionCreators(LoanIndexActions, dispatch); 24 | } 25 | 26 | class LoanIndex extends React.Component { 27 | constructor(props) { 28 | super(props); 29 | this.props.getIndexLoanDatas(); 30 | } 31 | 32 | componentDidUpdate() { 33 | 34 | } 35 | 36 | 37 | render() { 38 | 39 | let {advertising,loanableCredit,showHistory} = this.props.loanIndex; 40 | 41 | return
42 |
43 |
44 | 45 | demo 46 |
47 |
48 | } 49 | 50 | 51 | 52 | } 53 | 54 | const LoanIndexContainer = connect(mapStateToProps, mapDispatchToProps)(LoanIndex); 55 | 56 | export default LoanIndexContainer; 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/images/logo_my.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoulujun/wepack4-react-project-template/47db752d4d8da889fa95b1987ab8fa14feb7adef/src/images/logo_my.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | webpack4-vue2-project-template 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | */ 5 | 6 | 7 | //页面样式 8 | import './styles/index.scss'; 9 | 10 | //公用方法 11 | import Untils from './untils/Untils'; 12 | 13 | import React from "react"; 14 | import {render} from "react-dom"; 15 | import { Router,useRouterHistory,hashHistory} from 'react-router'; 16 | import { createHistory,createHashHistory, useBeforeUnload } from 'history' 17 | import {Provider} from 'react-redux'; 18 | 19 | 20 | import configStore from './stores/store';//生产模式 21 | import rootRouter from './router/router'; 22 | 23 | window.appHistory = createHashHistory({ 24 | basename: '', // The base URL of the app (see below) 25 | hashType: 'slash', // The hash type to use (see below) 26 | // A function to use to confirm navigation with the user (see below) 27 | getUserConfirmation: (message, callback) => callback(window.confirm(message)) 28 | }); 29 | 30 | 31 | const store = configStore(); 32 | 33 | render(( 34 | 35 | 36 | 37 | ), 38 | document.getElementById('app') 39 | ); 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/reducers/LoanIndexReducers.js: -------------------------------------------------------------------------------- 1 | import { Map} from 'immutable'; 2 | 3 | let initLoanIndexDatas = Map({ 4 | loanDatas: { 5 | 'advertising': '', 6 | 'amt': '', 7 | 'loanPeriod': '', 8 | 'rate': '', 9 | 'rateType': '2', 10 | 'repaymentMethod': '', 11 | 'showHistory': 'Y' 12 | } 13 | }); 14 | 15 | 16 | let initLoanIndexRepayDatas = Map({ 17 | repayDatas: { 18 | 'queryTime': '', 19 | 'recentRepayment': null, 20 | 'totalCount': 0, 21 | 'totalAmt': 0, 22 | 'runOnly': false 23 | } 24 | }); 25 | 26 | 27 | export function loanIndexDatas (state = initLoanIndexDatas, action = {}) { 28 | switch (action.type) { 29 | case 'UPDATE_LOANINDEX_LOANDATAS': 30 | state = state.set('loanDatas', action.loanDatas); 31 | return state; 32 | default : 33 | return state; 34 | } 35 | } 36 | 37 | 38 | export function loanIndexRepayDatas (state = initLoanIndexRepayDatas, action = {}) { 39 | switch (action.type) { 40 | case 'UPDATE_LOANINDEX_REPAYDATAS': 41 | state = state.set('repayDatas', action.repayDatas); 42 | return state; 43 | default : 44 | return state; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/reducers/globalReducers.js: -------------------------------------------------------------------------------- 1 | import {Map} from 'immutable'; 2 | 3 | let initState = Map({ 4 | loading: false, 5 | tokenId: '', 6 | userInfo: { 7 | name: 'test' 8 | } 9 | }); 10 | 11 | 12 | export default function global (state = initState, action = {}) { 13 | switch (action.type) { 14 | case 'INIT_TOKEN' : 15 | return state.set('tokenId', action.tokenId); 16 | case 'MASK' : 17 | return state.set('loading', true); 18 | case 'UNMASK' : 19 | return state.set('loading', false); 20 | case 'UPDATE_USER_INFO': { 21 | return state.set('userInfo', action.user); 22 | } 23 | default : 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/reducers/indexReducers.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import global from './globalReducers'; 4 | import {loanIndexDatas} from './LoanIndexReducers'; 5 | 6 | 7 | export default combineReducers({ 8 | global, 9 | loanIndexDatas 10 | }); 11 | -------------------------------------------------------------------------------- /src/router/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import InsideContainer from '../containers/InsideContainer'; 3 | import LoanIndexContainer from '../containers/LoanIndexContainer'; 4 | 5 | //router 6 | const rootRouter = { 7 | childRoutes: [{ 8 | path: '/', 9 | component: InsideContainer, 10 | childRoutes: [ 11 | { 12 | path: 'home', 13 | component: LoanIndexContainer, 14 | } 15 | ] 16 | }] 17 | }; 18 | 19 | export default rootRouter; 20 | -------------------------------------------------------------------------------- /src/stores/store-dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware ,compose } from 'redux' 2 | import { persistState } from 'redux-devtools'; 3 | import DevTools from '../vendors/DevTools'; 4 | import thunkMiddleware from 'redux-thunk' 5 | import createLogger from 'redux-logger' 6 | import rootReducer from '../reducers/indexReducers' 7 | 8 | 9 | const enhancer = compose( 10 | applyMiddleware(thunkMiddleware, createLogger()), 11 | DevTools.instrument(), 12 | persistState( 13 | window.location.href.match( 14 | /[?&]debug_session=([^&#]+)\b/ 15 | ) 16 | ) 17 | ); 18 | 19 | export default function configureStore(preloadedState) { 20 | const store = createStore( 21 | rootReducer, 22 | preloadedState, 23 | enhancer 24 | ) 25 | 26 | if (module.hot) { 27 | // Enable Webpack hot module replacement for reducers 28 | module.hot.accept('../reducers/indexReducers.js', () => { 29 | const nextRootReducer = require('../reducers/indexReducers.js').default 30 | store.replaceReducer(nextRootReducer) 31 | }) 32 | } 33 | 34 | return store; 35 | } -------------------------------------------------------------------------------- /src/stores/store.js: -------------------------------------------------------------------------------- 1 | import {createStore, applyMiddleware, compose} from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import rootReducer from '../reducers/indexReducers'; 4 | 5 | 6 | const enhancer = compose( 7 | applyMiddleware(thunkMiddleware) 8 | ); 9 | 10 | export default function configureStore (preloadedState) { 11 | const store = createStore( 12 | rootReducer, 13 | preloadedState, 14 | enhancer 15 | ); 16 | 17 | if (module.hot) { 18 | module.hot.accept('../reducers/indexReducers.js', () => { 19 | const nextRootReducer = require('../reducers/indexReducers.js').default; 20 | store.replaceReducer(nextRootReducer); 21 | }); 22 | } 23 | 24 | return store; 25 | } 26 | -------------------------------------------------------------------------------- /src/styles/_iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; /* project id 700262 */ 3 | src: url('//at.alicdn.com/t/font_700262_br7sp0umbec.eot'); 4 | src: url('//at.alicdn.com/t/font_700262_br7sp0umbec.eot?#iefix') format('embedded-opentype'), 5 | url('//at.alicdn.com/t/font_700262_br7sp0umbec.woff') format('woff'), 6 | url('//at.alicdn.com/t/font_700262_br7sp0umbec.ttf') format('truetype'), 7 | url('//at.alicdn.com/t/font_700262_br7sp0umbec.svg#iconfont') format('svg'); 8 | } 9 | .icon-font{ 10 | font-family: 'iconfont'; 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/_mixin.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoulujun/wepack4-react-project-template/47db752d4d8da889fa95b1987ab8fa14feb7adef/src/styles/_mixin.scss -------------------------------------------------------------------------------- /src/styles/_reset.scss: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | html{ 7 | -webkit-text-size-adjust: none; 8 | -webkit-touch-callout: none; 9 | height: 100%; 10 | word-break: break-all; 11 | word-wrap: break-word; 12 | font: 400 14px/1.5 "Microsoft YaHei",Tahoma,"Hiragino Sans GB",SimHei,sans-serif; 13 | 14 | } 15 | 16 | @for $i from 1 through 10{ 17 | h#{$i}{ 18 | font-weight: 500; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/_variable.scss: -------------------------------------------------------------------------------- 1 | //全局颜色 2 | 3 | $c-head: #e71d32; 4 | 5 | $c-btn: #e71d32; 6 | 7 | $c-active: #e71d32; 8 | 9 | $c-white: #FFF; 10 | 11 | $c-list-m: #5e6c77; 12 | $c-list-p: #c2c8cc; 13 | $c-list-d: #919699; 14 | //处理中 15 | $c-list-clz: #268ed0; 16 | //使用中 17 | $c-list-syz: #383838; 18 | //已关闭 19 | $c-list-ygb: #383838; 20 | //预期 21 | $c-list-yyq: #e71d32; 22 | //正常 23 | $c-list-zc: #919699; 24 | $c-c4: #ffc7c4; 25 | $c-4a: #e71d32; 26 | $c-border: #ececec; 27 | $c-99: #919699; 28 | $c-6c: #5e6c77; 29 | $c-cc: #c2c8cc; 30 | $c-7cc: #c2c7cc; 31 | $c-64b: #e71d32; 32 | $c-585: #858585; 33 | $c-ed0: #268ed0; 34 | $c-8cd: #c1c8cd; 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | @import "variable"; 2 | @import "mixin"; 3 | @import "iconfont"; 4 | 5 | @import "reset"; 6 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | .demo{ 2 | width: 146px; 3 | height: 49px; 4 | background: url("../images/logo_my.png"); 5 | background-size: 100%; 6 | transform-origin: 100%; 7 | transform: scale(.5); 8 | display: -webkit-box; 9 | 10 | } 11 | 12 | .page { 13 | display: grid; 14 | grid-gap: 33px; 15 | grid-template: 16 | "head head head" 1fr 17 | "nav main main" minmax(100px, 1fr) 18 | "nav foot foot" 2fr / 19 | 1fr 100px 1fr; 20 | } 21 | .page__head { 22 | grid-area: head; 23 | } 24 | .page__nav { 25 | grid-area: nav; 26 | } 27 | .page__main { 28 | grid-area: main; 29 | } 30 | .page__footer { 31 | grid-area: foot; 32 | } -------------------------------------------------------------------------------- /src/styles/layer.css: -------------------------------------------------------------------------------- 1 | .layui-m-layer { 2 | position: relative; 3 | z-index: 19891014 4 | } 5 | 6 | .layui-m-layer * { 7 | -webkit-box-sizing: content-box; 8 | -moz-box-sizing: content-box; 9 | box-sizing: content-box 10 | } 11 | 12 | .layui-m-layermain, .layui-m-layershade { 13 | position: fixed; 14 | left: 0; 15 | top: 0; 16 | width: 100%; 17 | height: 100% 18 | } 19 | 20 | .layui-m-layershade { 21 | background-color: rgba(0, 0, 0, .7); 22 | pointer-events: auto 23 | } 24 | 25 | .layui-m-layermain { 26 | display: table; 27 | font-family: Helvetica, arial, sans-serif; 28 | pointer-events: none 29 | } 30 | 31 | .layui-m-layermain .layui-m-layersection { 32 | display: table-cell; 33 | vertical-align: middle; 34 | text-align: center 35 | } 36 | 37 | .layui-m-layerchild { 38 | position: relative; 39 | display: inline-block; 40 | text-align: left; 41 | background-color: #fff; 42 | font-size: 14px; 43 | border-radius: 5px; 44 | box-shadow: 0 0 8px rgba(0, 0, 0, .1); 45 | pointer-events: auto; 46 | -webkit-overflow-scrolling: touch; 47 | -webkit-animation-fill-mode: both; 48 | animation-fill-mode: both; 49 | -webkit-animation-duration: .2s; 50 | animation-duration: .2s 51 | } 52 | 53 | @-webkit-keyframes layui-m-anim-scale { 54 | 0% { 55 | opacity: 0; 56 | -webkit-transform: scale(.5); 57 | transform: scale(.5) 58 | } 59 | 100% { 60 | opacity: 1; 61 | -webkit-transform: scale(1); 62 | transform: scale(1) 63 | } 64 | } 65 | 66 | @keyframes layui-m-anim-scale { 67 | 0% { 68 | opacity: 0; 69 | -webkit-transform: scale(.5); 70 | transform: scale(.5) 71 | } 72 | 100% { 73 | opacity: 1; 74 | -webkit-transform: scale(1); 75 | transform: scale(1) 76 | } 77 | } 78 | 79 | .layui-m-anim-scale { 80 | animation-name: layui-m-anim-scale; 81 | -webkit-animation-name: layui-m-anim-scale 82 | } 83 | 84 | @-webkit-keyframes layui-m-anim-up { 85 | 0% { 86 | opacity: 0; 87 | -webkit-transform: translateY(800px); 88 | transform: translateY(800px) 89 | } 90 | 100% { 91 | opacity: 1; 92 | -webkit-transform: translateY(0); 93 | transform: translateY(0) 94 | } 95 | } 96 | 97 | @keyframes layui-m-anim-up { 98 | 0% { 99 | opacity: 0; 100 | -webkit-transform: translateY(800px); 101 | transform: translateY(800px) 102 | } 103 | 100% { 104 | opacity: 1; 105 | -webkit-transform: translateY(0); 106 | transform: translateY(0) 107 | } 108 | } 109 | 110 | .layui-m-anim-up { 111 | -webkit-animation-name: layui-m-anim-up; 112 | animation-name: layui-m-anim-up 113 | } 114 | 115 | .layui-m-layer0 .layui-m-layerchild { 116 | width: 90%; 117 | max-width: 640px 118 | } 119 | 120 | .layui-m-layer1 .layui-m-layerchild { 121 | border: none; 122 | border-radius: 0 123 | } 124 | 125 | .layui-m-layer2 .layui-m-layerchild { 126 | width: auto; 127 | max-width: 260px; 128 | min-width: 40px; 129 | border: none; 130 | background: 0 0; 131 | box-shadow: none; 132 | color: #fff 133 | } 134 | 135 | .layui-m-layerchild h3 { 136 | padding: 0 10px; 137 | height: 60px; 138 | line-height: 60px; 139 | font-size: 16px; 140 | font-weight: 400; 141 | border-radius: 5px 5px 0 0; 142 | text-align: center 143 | } 144 | 145 | .layui-m-layerbtn span, .layui-m-layerchild h3 { 146 | text-overflow: ellipsis; 147 | overflow: hidden; 148 | white-space: nowrap 149 | } 150 | 151 | .layui-m-layercont { 152 | padding: 50px 30px; 153 | line-height: 22px; 154 | text-align: left; 155 | } 156 | 157 | .layui-m-layer1 .layui-m-layercont { 158 | padding: 0; 159 | text-align: left 160 | } 161 | 162 | .layui-m-layer2 .layui-m-layercont { 163 | text-align: center; 164 | padding: 0; 165 | line-height: 0 166 | } 167 | 168 | .layui-m-layer2 .layui-m-layercont i { 169 | width: 25px; 170 | height: 25px; 171 | margin-left: 8px; 172 | display: inline-block; 173 | background-color: #fff; 174 | border-radius: 100%; 175 | -webkit-animation: layui-m-anim-loading 1.4s infinite ease-in-out; 176 | animation: layui-m-anim-loading 1.4s infinite ease-in-out; 177 | -webkit-animation-fill-mode: both; 178 | animation-fill-mode: both 179 | } 180 | 181 | .layui-m-layerbtn, .layui-m-layerbtn span { 182 | position: relative; 183 | text-align: center; 184 | border-radius: 0 0 5px 5px 185 | } 186 | 187 | .layui-m-layer2 .layui-m-layercont p { 188 | margin-top: 20px 189 | } 190 | 191 | @-webkit-keyframes layui-m-anim-loading { 192 | 0%, 100%, 80% { 193 | transform: scale(0); 194 | -webkit-transform: scale(0) 195 | } 196 | 40% { 197 | transform: scale(1); 198 | -webkit-transform: scale(1) 199 | } 200 | } 201 | 202 | @keyframes layui-m-anim-loading { 203 | 0%, 100%, 80% { 204 | transform: scale(0); 205 | -webkit-transform: scale(0) 206 | } 207 | 40% { 208 | transform: scale(1); 209 | -webkit-transform: scale(1) 210 | } 211 | } 212 | 213 | .layui-m-layer2 .layui-m-layercont i:first-child { 214 | margin-left: 0; 215 | -webkit-animation-delay: -.32s; 216 | animation-delay: -.32s 217 | } 218 | 219 | .layui-m-layer2 .layui-m-layercont i.layui-m-layerload { 220 | -webkit-animation-delay: -.16s; 221 | animation-delay: -.16s 222 | } 223 | 224 | .layui-m-layer2 .layui-m-layercont > div { 225 | line-height: 22px; 226 | padding-top: 7px; 227 | margin-bottom: 20px; 228 | font-size: 14px 229 | } 230 | 231 | .layui-m-layerbtn { 232 | display: flex; 233 | justify-content: center; 234 | width: 100%; 235 | height: 50px; 236 | line-height: 50px; 237 | font-size: 0; 238 | border-top: 1px solid #D0D0D0; 239 | background-color: #F2F2F2 240 | } 241 | 242 | .layui-m-layerbtn span { 243 | display: flex; 244 | flex: 1; 245 | height: 50px; 246 | font-size: 14px; 247 | cursor: pointer; 248 | justify-content: center; 249 | } 250 | 251 | .layui-m-layerbtn span[yes] { 252 | color: #40AFFE 253 | } 254 | 255 | .layui-m-layerbtn span[no] { 256 | border-right: 1px solid #D0D0D0; 257 | border-radius: 0 0 0 5px 258 | } 259 | 260 | .layui-m-layerbtn span:active { 261 | background-color: #F6F6F6 262 | } 263 | 264 | .layui-m-layerend { 265 | position: absolute; 266 | right: 7px; 267 | top: 10px; 268 | width: 30px; 269 | height: 30px; 270 | border: 0; 271 | font-weight: 400; 272 | background: 0 0; 273 | cursor: pointer; 274 | -webkit-appearance: none; 275 | font-size: 30px 276 | } 277 | 278 | .layui-m-layerend::after, .layui-m-layerend::before { 279 | position: absolute; 280 | left: 5px; 281 | top: 15px; 282 | content: ''; 283 | width: 18px; 284 | height: 1px; 285 | background-color: #999; 286 | transform: rotate(45deg); 287 | -webkit-transform: rotate(45deg); 288 | border-radius: 3px 289 | } 290 | 291 | .layui-m-layerend::after { 292 | transform: rotate(-45deg); 293 | -webkit-transform: rotate(-45deg) 294 | } 295 | 296 | body .layui-m-layer .layui-m-layer-footer { 297 | position: fixed; 298 | width: 95%; 299 | max-width: 100%; 300 | margin: 0 auto; 301 | left: 0; 302 | right: 0; 303 | bottom: 10px; 304 | background: 0 0 305 | } 306 | 307 | .layui-m-layer-footer .layui-m-layercont { 308 | padding: 20px; 309 | border-radius: 5px 5px 0 0; 310 | background-color: rgba(255, 255, 255, .8) 311 | } 312 | 313 | .layui-m-layer-footer .layui-m-layerbtn { 314 | display: block; 315 | height: auto; 316 | background: 0 0; 317 | border-top: none 318 | } 319 | 320 | .layui-m-layer-footer .layui-m-layerbtn span { 321 | background-color: rgba(255, 255, 255, .8) 322 | } 323 | 324 | .layui-m-layer-footer .layui-m-layerbtn span[no] { 325 | color: #FD482C; 326 | border-top: 1px solid #c2c2c2; 327 | border-radius: 0 0 5px 5px 328 | } 329 | 330 | .layui-m-layer-footer .layui-m-layerbtn span[yes] { 331 | margin-top: 10px; 332 | border-radius: 5px 333 | } 334 | 335 | body .layui-m-layer .layui-m-layer-msg { 336 | width: auto; 337 | max-width: 90%; 338 | margin: 0 auto; 339 | bottom: -150px; 340 | background-color: rgba(0, 0, 0, .7); 341 | color: #fff 342 | } 343 | 344 | .layui-m-layer-msg .layui-m-layercont { 345 | padding: 10px 20px 346 | } -------------------------------------------------------------------------------- /src/untils/Untils.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn 3 | *@version 1.0.0 4 | *@description 通用方法类 5 | */ 6 | 7 | 8 | class Untils { 9 | 10 | 11 | 12 | 13 | /** 14 | *debounce 将若干个函数调用合成 一次,并在给定时间过去之后仅被调用一次。 15 | * @param func {Function} 防止抖动函数 16 | * @param delay {number} 延迟时间,限制下次函数调用之前必须等待的时间间隔(只有当高频事件停止,最后一次事件触发的超时调用才能在delay时间后执行) 17 | * @param immediate {number} 延迟执行时间 18 | * @returns {Function} 绑定的函数实际上是debounce内部返回的函数 19 | */ 20 | static debouce (func, delay, immediate) { 21 | let timer = null; 22 | return function () { 23 | let context = this; 24 | let args = arguments; 25 | if (timer) { 26 | clearTimeout(timer); 27 | } 28 | if (immediate) { 29 | //根据距离上次触发操作的时间是否到达delay来决定是否要现在执行函数 30 | let doNow = !timer; 31 | //每一次都重新设置timer,就是要保证每一次执行的至少delay秒后才可以执行 32 | timer = setTimeout(function () { 33 | timer = null; 34 | }, delay); 35 | //立即执行 36 | if (doNow) { 37 | func.apply(context, args); 38 | } 39 | } else { 40 | timer = setTimeout(function () { 41 | func.apply(context, args); 42 | }, delay); 43 | } 44 | }; 45 | } 46 | 47 | /** 48 | *throttle 允许一个函数在规定的时间内只执行一次 49 | * @param func {Function} 50 | * @param delay {number} 间隔时间 51 | * @returns {Function} 52 | */ 53 | static throttle (func, delay) { 54 | let timer = null; 55 | let startTime = Date.now(); 56 | return function () { 57 | let curTime = Date.now(); 58 | let remaining = delay - (curTime - startTime); 59 | let context = this; 60 | let args = arguments; 61 | 62 | clearTimeout(timer); 63 | if (remaining <= 0) { 64 | func.apply(context, args); 65 | startTime = Date.now(); 66 | } else { 67 | timer = setTimeout(function () { 68 | func.apply(context, args); 69 | }, remaining); 70 | } 71 | }; 72 | 73 | 74 | } 75 | 76 | /** 77 | *帧动画节流 78 | * @param fn {Function} 79 | * @returns {Function} 80 | */ 81 | static raf_debounce (fn) { 82 | let ticking = false; 83 | return function () { 84 | let context = this; 85 | let args = arguments; 86 | if (!ticking) { 87 | ticking = true; 88 | requestAnimationFrame(function () { 89 | ticking = false; 90 | fn && fn.apply(context, args); 91 | }); 92 | } 93 | }; 94 | } 95 | 96 | /** 97 | * 获取URl参数 98 | * @param search {string} url参数 99 | * @return {object} 100 | */ 101 | static getUrlParams(search=location.search){ 102 | //获取URL参数 103 | let urlParams={}; 104 | 105 | try { 106 | search.replace(/([^&=?]+)=([^&=?]*)/,function (str,$1,$2) { 107 | urlParams[$1]=$2; 108 | return str; 109 | }); 110 | 111 | }catch (e) { 112 | console.log(e); 113 | } 114 | return urlParams; 115 | } 116 | 117 | } 118 | 119 | export default Untils; 120 | -------------------------------------------------------------------------------- /src/untils/const.js: -------------------------------------------------------------------------------- 1 | 'user strict'; 2 | 3 | 4 | export default { 5 | layerBtnText: '我知道了', 6 | layerContent: '系统繁忙,请稍后再试' 7 | }; 8 | -------------------------------------------------------------------------------- /src/untils/respon.js: -------------------------------------------------------------------------------- 1 | (function (doc, win) { 2 | var docEl = doc.documentElement, 3 | resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', 4 | recalc = function () { 5 | var clientWidth = docEl.clientWidth>=750?750:docEl.clientWidth; 6 | if (!clientWidth) return; 7 | docEl.style.fontSize = 100 * (clientWidth / 750) + 'px'; 8 | }; 9 | if (!doc.addEventListener) return; 10 | win.addEventListener(resizeEvt, recalc, false); 11 | doc.addEventListener('DOMContentLoaded', recalc, false); 12 | })(document, window); -------------------------------------------------------------------------------- /test/components/MainTest.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | /*global expect */ 3 | /*eslint no-console: 0*/ 4 | 'use strict'; 5 | 6 | 7 | // import createComponent from '../helpers/shallowRenderHelper'; 8 | 9 | // import Header from '../../src/components/common/Header'; 10 | 11 | // import InsideContainer from 'src/containers/InsideContainer'; 12 | 13 | /** 14 | Module not found: Error: Can't resolve 'react-dom/lib/ReactTestUtils' in '/www/wepack4-react-project-template/node_modules/react-addons-test-utils' 15 | react 16 错误 16 | module.exports = require('react-dom/test-utils'); 17 | module.exports = require('react-dom/lib/ReactTestUtils'); 18 | 19 | Can't resolve 'react-dom/lib/ReactTestUtils' in 'react-addons-test-utils' 20 | 21 | */ 22 | 23 | describe('Header', function () { 24 | // beforeEach(function () { 25 | // this.HeaderComponent = createComponent(Header); 26 | // }); 27 | // 28 | // it('should have its component name as default className', function () { 29 | // expect(this.HeaderComponent.props.className).to.equal('index'); 30 | // }); 31 | it('test 1+1=2', function () { 32 | expect(true).toBe(true); 33 | // return 1+1 === 2; 34 | }); 35 | 36 | }); 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/helpers/shallowRenderHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function to get the shallow output for a given component 3 | * As we are using phantom.js, we also need to include the fn.proto.bind shim! 4 | * 5 | * @see http://simonsmith.io/unit-testing-react-components-without-a-dom/ 6 | * @author somonsmith 7 | */ 8 | import React from 'react'; 9 | import TestUtils from 'react-addons-test-utils'; 10 | 11 | /** 12 | * Get the shallow rendered component 13 | * 14 | * @param {Object} component The component to return the output for 15 | * @param {Object} props [optional] The components properties 16 | * @param {Mixed} ...children [optional] List of children 17 | * @return {Object} Shallow rendered output 18 | */ 19 | export default function createComponent(component, props = {}, ...children) { 20 | const shallowRenderer = TestUtils.createRenderer(); 21 | shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0])); 22 | return shallowRenderer.getRenderOutput(); 23 | } 24 | -------------------------------------------------------------------------------- /test/loadtests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-polyfill'); 4 | require('core-js/fn/object/assign'); 5 | 6 | // Add support for all files in the test directory 7 | const testsContext = require.context('.', true, /(Test\.js$)|(Helper\.js$)/); 8 | testsContext.keys().forEach(testsContext); 9 | -------------------------------------------------------------------------------- /webpack.analy.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | *webpack 打包配置 5 | *打包速度方面优化无必要。可能你研究了半天,改了一堆参数发现其实也就提升了几秒,但维护成本上去了,得不偿失。 6 | */ 7 | 8 | 9 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 10 | 11 | const config = require('./webpack.config'); 12 | 13 | config.plugins.push( 14 | new BundleAnalyzerPlugin() 15 | 16 | ); 17 | 18 | module.exports = config; 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | * webpack 配置文件 5 | */ 6 | 'use strict'; 7 | const path = require('path'); 8 | const os = require('os'); 9 | const HappyPack = require('happypack'); 10 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 12 | const devMode = process.env.NODE_ENV === 'development'; 13 | const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); 14 | const publicPath = ''; 15 | const config = { 16 | // target: 'web',//告知 webpack 为目标(target)指定一个环境。默认web 17 | entry: {//配置页面入口 18 | index: './src/index.js' 19 | }, 20 | output: { //配置输出选项 21 | path: path.resolve(__dirname, 'dist'),//输出路径为,当前路径下 22 | filename: '[name].[hash:5].js'//输出后的文件名称 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.json'], //减少文件查找 26 | /*alias: { 27 | actions: `${defaultSettings.srcPath}/actions/`, 28 | components: `${defaultSettings.srcPath}/components/`, 29 | sources: `${defaultSettings.srcPath}/sources/`, 30 | stores: `${defaultSettings.srcPath}/stores/`, 31 | styles: `${defaultSettings.srcPath}/styles/`, 32 | }*/ 33 | }, 34 | 35 | module: { 36 | rules: [ 37 | { 38 | test: /\.html$/, 39 | use: 'html-loader' 40 | }, 41 | { 42 | test: /\.css$/, 43 | use: [//使用use可配置多个loader进行处理。顺序由最后一个至第一个。此处匹配到css文件后,先由postcss-loader处理,css-loader处理后再交由style-loader处理 44 | devMode ? { 45 | loader: 'style-loader', 46 | options: { 47 | // singleton:true //处理为单个style标签 48 | } 49 | } : 50 | MiniCssExtractPlugin.loader, 51 | {//css-loader 解释(interpret) @import 和 url() 52 | loader: 'css-loader', 53 | options: { 54 | // url:false, //false css中加载图片的路径将不会被解析 不会改变 55 | // minimize:true, //压缩css 56 | importLoaders: 1//importLoaders代表import进来的资源;2代表css-loader后还需要使用几个loader 57 | } 58 | }, 59 | {//需在css-loader/style-loader后面,在其他预处理前面 60 | loader: 'postcss-loader', 61 | options: { 62 | 63 | plugins: [ 64 | require('autoprefixer') 65 | ], 66 | browsers: [ 67 | '> 1%', 68 | 'last 5 versions', 69 | 'not ie <= 9', 70 | 'ios >= 8', 71 | 'android >= 4.0' 72 | ] 73 | } 74 | } 75 | ] 76 | }, 77 | { 78 | test: /\.(scss)$/, 79 | use: [//使用use可配置多个loader进行处理。顺序由最后一个至第一个 80 | devMode ? { 81 | loader: 'style-loader' 82 | /* options: { 83 | singleton:true //处理为单个style标签 84 | }*/ 85 | } : 86 | MiniCssExtractPlugin.loader, 87 | {//css-loader 解释(interpret) @import 和 url() 88 | loader: 'css-loader', 89 | options: { 90 | // url:false, //false css中加载图片的路径将不会被解析 不会改变 91 | // minimize:true, //压缩css 92 | importLoaders: 1, 93 | sourceMap: devMode//importLoaders代表import进来的资源;2代表css-loader后还需要使用几个loader 94 | } 95 | }, 96 | {//需在css-loader/style-loader后面,在其他预处理前面 97 | loader: 'postcss-loader', 98 | options: { 99 | 100 | plugins: [ 101 | require('autoprefixer') 102 | ], 103 | browsers: [ 104 | '> 1%', 105 | 'last 5 versions', 106 | 'not ie <= 9', 107 | 'ios >= 8', 108 | 'android >= 4.0' 109 | ], 110 | sourceMap: devMode 111 | } 112 | }, 113 | { 114 | loader: 'happypack/loader?id=scss' 115 | 116 | } 117 | ] 118 | }, 119 | 120 | { 121 | test: /\.(png|jpg|jpeg|gif)$/,//图片处理 122 | use: [ 123 | { 124 | loader: 'url-loader', 125 | options: { 126 | limit: 50,//图片不转base64,减少css的阻塞时间,开启http2,所以也不用雪碧图 127 | name: '[name].[hash:5].[ext]', 128 | url: false,//不处理css图片路径, 129 | outputPath: 'images' 130 | } 131 | } 132 | ] 133 | }, 134 | 135 | 136 | { 137 | test: /\.(woff|woff2|eot|ttf|otf)$/,//字体处理 138 | use: ['url-loader'] 139 | }, 140 | 141 | {//babel编译 142 | test: /\.(js|jsx)$/, 143 | loader: 'babel-loader', 144 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 145 | }, 146 | {//eslint 检查 147 | test: /\.(js|jsx)$/, 148 | enforce: 'pre', 149 | loader: ['eslint-loader'], 150 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 151 | } 152 | 153 | 154 | ] 155 | }, 156 | plugins: [ 157 | new HappyPack({ 158 | id: 'scss',//用id来标识 happypack处理那里类文件 159 | threadPool: happyThreadPool, //共享进程池 160 | loaders: [ 161 | { 162 | loader: 'sassjs-loader' 163 | } 164 | ] 165 | }), 166 | 167 | new MiniCssExtractPlugin({ 168 | filename: '[name].[hash:5].css', 169 | chunkFilename: '[id].[hash].css', 170 | disable: false, //是否禁用此插件 171 | allChunks: true 172 | }), 173 | 174 | new HtmlWebpackPlugin({ 175 | template: './src/index.html',//本地模板文件的位置,支持加载器(如handlebars、ejs、undersore、html等),如比如 handlebars!src/index.hbs; 176 | filename: './index.html',//输出文件的文件名称,默认为index.html,不配置就是该文件名;此外,还可以为输出文件指定目录位置(例如'html/index.html') 177 | chunks: ['index','vendor','commons','manifest'], 178 | inject: true,//1、true或者body:所有JavaScript资源插入到body元素的底部2、head: 所有JavaScript资源插入到head元素中3、false: 所有静态资源css和JavaScript都不会注入到模板文件中 179 | showErrors: true,//是否将错误信息输出到html页面中 180 | hash: false,//是否为所有注入的静态资源添加webpack每次编译产生的唯一hash值 181 | favicon: '',//添加特定的 favicon 路径到输出的 HTML 文件中。 182 | minify: { 183 | caseSensitive: false, 184 | removeComment: true,//移除注释 185 | collapseWhitespace: false//移除多余空格 186 | } 187 | }) 188 | ] 189 | }; 190 | 191 | module.exports = config; 192 | -------------------------------------------------------------------------------- /webpack.dist.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | *webpack 打包配置 5 | *打包速度方面优化无必要。可能你研究了半天,改了一堆参数发现其实也就提升了几秒,但维护成本上去了,得不偿失。 6 | */ 7 | 8 | 9 | // const ManifestPlugin = require('webpack-manifest-plugin'); 10 | const WebpackAssetsManifest = require('webpack-assets-manifest'); 11 | const WebpackSubresourceIntegrity = require('webpack-subresource-integrity'); 12 | 13 | const WebpackSftpClient = require('webpack-sftp-client'); 14 | 15 | 16 | const config = require('./webpack.config'); 17 | 18 | process.env.NODE_ENV = 'production'; 19 | config.mode = 'production'; 20 | // config.output.publicPath='http://zhoulujun.cn/demo/'; 21 | 22 | config.optimization = { 23 | splitChunks: { // 打包 node_modules里的代码 24 | chunks: 'async', //async异步模块 initial只对入口文件处理 25 | minSize: 30000, //超过30000才打包 26 | minChunks: 1, //最小引入次数1 27 | maxAsyncRequests: 5, //一次异步加载的最大模块请求数 28 | maxInitialRequests: 3, //入口文件最大的模块请求数 29 | automaticNameDelimiter: '~', //默认的文件名分隔符 30 | name: true, //根据chunk名或cahceGroups里的key生成文件名 31 | //默认值+++ 32 | cacheGroups: {//一个对象,对象里的每一个key-value都对应一个公共块。如第一个: 33 | commons: {//将引用到的node_modules目录下的模块打包为一个文件 34 | chunks: 'initial',//打包初始时依赖第三方 35 | name: 'commons', 36 | minChunks: 2,//最小共用次数 37 | maxInitialRequests: 5, 38 | minSize: 0 39 | }, 40 | vendor: { // split `node_modules`目录下被打包的代码到 `page/vendor.js && .css` 没找到可打包文件的话,则没有。 41 | test: /node_modules/,//将引用到到node_modules目录下的模块打包为一个文件 42 | chunks: 'all',//提取所有 chunks 可配置为initial:默认打包的chunk, async:异步加载的chunk,all:所有的chunk 43 | name: 'vendor', 44 | priority: 10, 45 | enforce: true 46 | } 47 | } 48 | }, 49 | runtimeChunk: {name:'manifest'}// 单独抽离 runtimeChunk 之后,每次打包都会生成一个runtimeChunk.xxx.js。name 命名后,才能 HtmlWebpackPlugin chunk 50 | }; 51 | 52 | config.plugins.push( 53 | new WebpackAssetsManifest({ 54 | // Options go here 55 | integrity: true, 56 | integrityHashes: ['sha256', 'sha384'] 57 | }), 58 | new WebpackSubresourceIntegrity({ 59 | hashFuncNames: ['sha256', 'sha384'] 60 | }), 61 | 62 | /*new webpack.optimize.UglifyJsPlugin(), 63 | new webpack.optimize.OccurenceOrderPlugin(), 64 | new webpack.optimize.AggressiveMergingPlugin(), 65 | new webpack.NoErrorsPlugin()*/ 66 | 67 | //上传到服务器发布 68 | /*new WebpackSftpClient({ 69 | port: '20020', 70 | host: '10.111.111.38', 71 | username: 'nginx', 72 | password: 'zlj@123', 73 | path: './dist/',//本地上传目录 74 | remotePath: '/usr/local/nginx/html/demo',//服务器目标目录 75 | verbose: true 76 | })*/ 77 | ); 78 | 79 | module.exports = config; 80 | -------------------------------------------------------------------------------- /webpack.server.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | * webpack server 配置 5 | */ 6 | const webpack = require('webpack'); 7 | const WebpackDevServer = require('webpack-dev-server'); 8 | const open = require('open'); 9 | 10 | const config = require('./webpack.config'); 11 | 12 | process.env.NODE_ENV='development'; 13 | config.mode = 'development'; 14 | config.devtool='cheap-module-eval-source-map'; 15 | 16 | config.plugins.push( 17 | new webpack.HotModuleReplacementPlugin(),//热替换 18 | new webpack.NoEmitOnErrorsPlugin(),//去除系统抛出的错误消息 19 | ); 20 | 21 | 22 | const addressObj = { 23 | ip: getLocalIPAdress(), 24 | port: 11037 25 | }; 26 | 27 | new WebpackDevServer(webpack(config), { 28 | 29 | historyApiFallback: true, 30 | hot: true,//热加载 31 | hotOnly: true, 32 | overlay: { 33 | errors: true//webpack编译出现的错误是否会出现在网页中 34 | }, 35 | compress: false, 36 | proxy: { 37 | '/api/*': { 38 | target: 'https://zhoulujun.cn/api',//代理地址 39 | secure: false 40 | } 41 | } 42 | }) 43 | .listen(addressObj.port,addressObj.ip,function (error) { 44 | error&&console.log(error); 45 | let address=`http://${addressObj.ip}:${addressObj.port}`; 46 | // let address=`http://localhost:13080`; 47 | open(address); 48 | console.log('listening at:'+address) 49 | }); 50 | 51 | function getLocalIPAdress () { 52 | var interfaces = require('os').networkInterfaces(); 53 | for (let devName in interfaces) { 54 | let iface = interfaces[devName]; 55 | for (let i = 0; i < iface.length; i++) { 56 | let alias = iface[i]; 57 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 58 | return alias.address; 59 | } 60 | } 61 | } 62 | return 'localhost'; 63 | } -------------------------------------------------------------------------------- /webpack.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | *@author Create by zhoulujun.cn on 1/4/1910:30 AM 3 | *@version 1.0.0 4 | * webpack 配置文件 5 | */ 6 | 'use strict'; 7 | const path = require('path'); 8 | const config = { 9 | // target: 'web',//告知 webpack 为目标(target)指定一个环境。默认web 10 | entry: {//配置页面入口 11 | index: './src/index.js' 12 | }, 13 | output: { //配置输出选项 14 | path: path.resolve(__dirname, 'dist'),//输出路径为,当前路径下 15 | filename: '[name].[hash:5].js'//输出后的文件名称 16 | }, 17 | resolve: { 18 | extensions: ['.js', '.json'] //减少文件查找 19 | }, 20 | 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.html$/, 25 | use: 'html-loader' 26 | }, 27 | { 28 | test: /\.css$/, 29 | use: [//使用use可配置多个loader进行处理。顺序由最后一个至第一个。此处匹配到css文件后,先由postcss-loader处理,css-loader处理后再交由style-loader处理 30 | { 31 | loader: 'style-loader', 32 | options: { 33 | // singleton:true //处理为单个style标签 34 | } 35 | }, 36 | {//css-loader 解释(interpret) @import 和 url() 37 | loader: 'css-loader', 38 | options: { 39 | // url:false, //false css中加载图片的路径将不会被解析 不会改变 40 | // minimize:true, //压缩css 41 | importLoaders: 1//importLoaders代表import进来的资源;2代表css-loader后还需要使用几个loader 42 | } 43 | }, 44 | {//需在css-loader/style-loader后面,在其他预处理前面 45 | loader: 'postcss-loader', 46 | options: { 47 | 48 | plugins: [ 49 | require('autoprefixer') 50 | ], 51 | browsers: [ 52 | '> 1%', 53 | 'last 5 versions', 54 | 'not ie <= 9', 55 | 'ios >= 8', 56 | 'android >= 4.0' 57 | ] 58 | } 59 | } 60 | ] 61 | }, 62 | { 63 | test: /\.(scss)$/, 64 | use: [//使用use可配置多个loader进行处理。顺序由最后一个至第一个 65 | { 66 | loader: 'style-loader' 67 | /* options: { 68 | singleton:true //处理为单个style标签 69 | }*/ 70 | }, 71 | {//css-loader 解释(interpret) @import 和 url() 72 | loader: 'css-loader', 73 | options: { 74 | // url:false, //false css中加载图片的路径将不会被解析 不会改变 75 | // minimize:true, //压缩css 76 | importLoaders: 1, 77 | sourceMap: true,//importLoaders代表import进来的资源;2代表css-loader后还需要使用几个loader 78 | } 79 | }, 80 | {//需在css-loader/style-loader后面,在其他预处理前面 81 | loader: 'postcss-loader', 82 | options: { 83 | 84 | plugins: [ 85 | require('autoprefixer') 86 | ], 87 | browsers: [ 88 | '> 1%', 89 | 'last 5 versions', 90 | 'not ie <= 9', 91 | 'ios >= 8', 92 | 'android >= 4.0' 93 | ], 94 | sourceMap: true 95 | } 96 | }, 97 | { 98 | loader: 'sass-loader' 99 | 100 | } 101 | ] 102 | }, 103 | 104 | { 105 | test: /\.(png|jpg|jpeg|gif)$/,//图片处理 106 | use: [ 107 | { 108 | loader: 'url-loader', 109 | options: { 110 | limit: 50,//图片不转base64,减少css的阻塞时间,开启http2,所以也不用雪碧图 111 | name: '[name].[hash:5].[ext]', 112 | url: false,//不处理css图片路径, 113 | outputPath: 'images' 114 | } 115 | } 116 | ] 117 | }, 118 | 119 | { 120 | test: /\.(woff|woff2|eot|ttf|otf)$/,//字体处理 121 | use: ['url-loader'] 122 | }, 123 | 124 | {//babel编译 125 | test: /\.(js|jsx)$/, 126 | loader: 'babel-loader', 127 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 128 | }, 129 | {//eslint 检查 130 | test: /\.(js|jsx)$/, 131 | enforce: 'pre', 132 | loader: ['eslint-loader'], 133 | exclude: /node_modules/ //设置node_modules里的js文件不用解析 134 | } 135 | 136 | 137 | ] 138 | }, 139 | plugins: [ 140 | 141 | ] 142 | }; 143 | 144 | module.exports = config; 145 | --------------------------------------------------------------------------------