├── .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
;
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 |
--------------------------------------------------------------------------------