基于前端工程化的考虑,我们之前基于webpack工具链封装了前端工程化工具uba,并在此基础上整合成最佳实践的前端脚手架工程,我们将其命名为 tinper-react,并且广泛应用于三一重工、贵冶智能工厂、华新丽华、绿城集团、中兴通讯等大型企业应用和用友云系列产品矩阵当中。

ucf-web 和 tinper-react 有什么联系

ucf-web 是从 tinper-react 吸取经验、总结最佳实践、解决开发者提出的问题,并在 UCF 中台能力框架的指导下演进而来,它在技术选型上保证了和之前的 tinper-react 框架的延续性,并在易用性、规范化、标准化上得到了更好的实践。

1、升级开发体验,增强工程化能力

1.1、简化项目依赖包数量,由原来的60个左右减少到10个左右

之前:

  1. "devDependencies": {
  2. "autoprefixer": "^9.4.3",
  3. "babel-core": "^6.26.3",
  4. "babel-loader": "^7.1.5",
  5. "babel-plugin-transform-runtime": "^6.23.0",
  6. "babel-polyfill": "^6.26.0",
  7. "babel-preset-env": "^1.7.0",
  8. "babel-preset-react": "^6.24.1",
  9. "babel-preset-stage-2": "^6.24.1",
  10. "clean-webpack-plugin": "^1.0.0",
  11. "copy-webpack-plugin": "^4.6.0",
  12. "cross-env": "^5.2.0",
  13. "css-loader": "^1.0.1",
  14. "cssnano": "^4.1.7",
  15. "extract-text-webpack-plugin": "^2.1.2",
  16. "file-loader": "^2.0.0",
  17. "glob": "^7.1.3",
  18. "gulp": "^3.9.1",
  19. "gulp-fez-sftp": "^1.0.1",
  20. "gulp-ftp": "^1.1.0",
  21. "gulp-zip": "^4.1.0",
  22. "html-webpack-plugin": "^3.2.0",
  23. "less": "^3.9.0",
  24. "less-loader": "^4.1.0",
  25. "open-browser-webpack-plugin": "^0.0.5",
  26. "postcss-flexbugs-fixes": "^4.1.0",
  27. "postcss-loader": "^3.0.0",
  28. "redux-logger": "^3.0.6",
  29. "style-loader": "^0.23.1",
  30. "uba": "^2.3.11",
  31. "url-loader": "^1.1.2",
  32. "webpack-bundle-analyzer": "^3.0.3",
  33. "webpack-hot-middleware": "^2.24.3"
  34. },
  35. "dependencies": {
  36. "ac-attachment": "^0.2.2",
  37. "async-validator": "^1.10.0",
  38. "axios": "^0.18.0",
  39. "bee-autocomplete": "^1.0.4",
  40. "bee-checkbox": "^1.2.6",
  41. "bee-complex-grid": "^1.0.8",
  42. "bee-datepicker": "^1.3.8",
  43. "bee-dropdown": "^1.0.3",
  44. "bee-form": "^2.0.6",
  45. "bee-input-number": "^1.2.1",
  46. "bee-menus": "^1.0.8",
  47. "bee-pagination": "^1.1.14",
  48. "bee-search-panel": "^0.1.4",
  49. "bee-select": "^1.1.5",
  50. "bee-table": "^1.6.16",
  51. "bee-tooltip": "^1.0.13",
  52. "bee-upload": "^1.0.0",
  53. "classnames": "^2.2.5",
  54. "core-js": "^2.6.0",
  55. "mirrorx": "^0.2.12",
  56. "moment": "^2.23.0",
  57. "prop-types": "^15.6.2",
  58. "query-string": "^5.1.1",
  59. "react": "^16.6.3",
  60. "react-dom": "^16.6.3",
  61. "react-transition-group": "^2.4.0",
  62. "ref-combobox": "0.0.2",
  63. "ref-multiple-table": "^0.1.12",
  64. "ref-tree": "^0.1.16",
  65. "tinper-bee": "1.6.5",
  66. "yyuap-bpm": "^0.3.26",
  67. "yyuap-ref": "^1.1.55"
  68. }

现在(dependencies里面的包会随着项目逐渐完善而会增多):

  1. "devDependencies": {
  2. "ucf-scripts": "^1.0.2"
  3. },
  4. "dependencies": {
  5. "@babel/polyfill": "^7.2.5",
  6. "@babel/runtime": "^7.3.1",
  7. "bee-complex-grid": "^1.0.11",
  8. "bee-form": "^2.0.7",
  9. "bee-table": "^1.6.33",
  10. "classnames": "^2.2.6",
  11. "mirrorx": "^0.2.12",
  12. "prop-types": "^15.6.2",
  13. "react": "^16.7.0",
  14. "react-dom": "^16.7.0",
  15. "tinper-bee": "^1.6.9",
  16. "ucf-request": "^1.0.0"
  17. }

1.2、对配置文件做了精简,由原来的200多行精简到三四十行

原来的 uba.config.js:

  1. const path = require('path');
  2. const hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true';
  3. const webpack = require('webpack');
  4. const glob = require("glob");
  5. const HtmlWebpackPlugin = require('html-webpack-plugin');
  6. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  7. const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
  8. const CleanWebpackPlugin = require('clean-webpack-plugin');
  9. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  10. const pathUrl = ''; //http://127.0.0.1:8080 设置host,可选
  11. const context = '/iuap_walsin_fe';//工程节点名称
  12. const contentBase = './build' + context;//打包目录
  13. const staticConfig = {
  14. folder: "dll"
  15. };
  16. let entries = {};
  17. let chunks = [];
  18. let prodEntries = {};
  19. let prodChunks = [];
  20. let htmlEntrys = [];
  21. const svrConfig = {
  22. historyApiFallback: false
  23. };
  24. // 远程代理访问,可以配置多个代理服务:https://github.com/chimurai/http-proxy-middleware
  25. const proxyConfig = [
  26. {
  27. enable: true,
  28. headers: {
  29. // 与下方url一致
  30. "Referer": "http://172.20.52.215:8888"
  31. },
  32. //要代理访问的对方路由
  33. router: [
  34. '/iuap_walsin_demo', '/wbalone', '/iuap-saas-message-center/', '/iuap-saas-filesystem-service/', '/eiap-plus/', '/newref/', '/print_service/', '/iuap-print/'
  35. ],
  36. url: 'http://172.20.52.215:8888'
  37. }
  38. ];
  39. const globalEnvConfig = new webpack.DefinePlugin({
  40. __MODE__: JSON.stringify(process.env.NODE_ENV),
  41. GROBAL_HTTP_CTX: JSON.stringify("/iuap_walsin_demo"),
  42. 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  43. })
  44. const MINIMIZE_FLAG = (process.env.NODE_ENV == "production") ? true : false;
  45. //提取package里的包
  46. function getVendors() {
  47. let pkg = require("./package.json");
  48. let _vendors = [];
  49. for (const key in pkg.dependencies) {
  50. _vendors.push(key);
  51. }
  52. return _vendors;
  53. }
  54. //优化配置,对于使用CDN作为包资源的引用从外到内的配置
  55. const externals = {
  56. // 'axios': 'axios',
  57. // 'react': 'React',
  58. // 'react-dom': 'ReactDOM',
  59. //'tinper-bee': 'TinperBee'
  60. }
  61. //默认加载扩展名、相对JS路径模块的配置
  62. const resolve = {
  63. extensions: [
  64. '.jsx', '.js', '.less', '.css', '.json'
  65. ],
  66. alias: {
  67. components: path.resolve(__dirname, 'src/components/'),
  68. modules: path.resolve(__dirname, 'src/pages/'),
  69. routes: path.resolve(__dirname, 'src/routes/'),
  70. layout: path.resolve(__dirname, 'src/layout/'),
  71. utils: path.resolve(__dirname, 'src/utils/'),
  72. static: path.resolve(__dirname, 'src/static/'),
  73. src: path.resolve(__dirname, 'src/')
  74. }
  75. }
  76. //开发和生产需要的loader
  77. const rules = [{
  78. test: /\.js[x]?$/,
  79. exclude: /(node_modules)/,
  80. include: path.resolve('src'),
  81. use: [{
  82. loader: 'babel-loader'
  83. }]
  84. }, {
  85. test: /\.less$/,
  86. exclude: /(node_modules)/,
  87. use: ExtractTextPlugin.extract({
  88. use: ['css-loader', 'postcss-loader', 'less-loader'],
  89. fallback: 'style-loader'
  90. })
  91. }, {
  92. test: /\.css$/,
  93. use: ExtractTextPlugin.extract({
  94. use: [{
  95. loader: 'css-loader',
  96. }, 'postcss-loader'],
  97. fallback: 'style-loader'
  98. })
  99. }, {
  100. test: /\.(png|jpg|jpeg|gif)(\?.+)?$/,
  101. //exclude: /favicon\.png$/,
  102. use: [{
  103. loader: 'url-loader',
  104. options: {
  105. limit: 8196,
  106. name: 'images/[name].[hash:8].[ext]',
  107. publicPath: pathUrl + context
  108. }
  109. }]
  110. }, {
  111. test: /\.(eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
  112. use: [{
  113. loader: 'file-loader',
  114. options: {
  115. name: '[name].[hash:8].[ext]',
  116. outputPath: 'fonts',
  117. publicPath: pathUrl + context + '/fonts/'
  118. }
  119. }]
  120. }]
  121. entries.vendors = prodEntries.vendors = ['babel-polyfill'].concat(getVendors());
  122. glob.sync("./src/pages/**/app.js").forEach(path => {
  123. const chunk = path.split("./src/pages/")[1].split(".js")[0];
  124. entries[chunk] = [path, hotMiddlewareScript];
  125. chunks.push(chunk);
  126. });
  127. //开发环境的webpack配置
  128. const devConfig = {
  129. devtool: 'cheap-module-eval-source-map',
  130. entry: entries,
  131. output: {
  132. path: path.resolve(__dirname, contentBase),
  133. filename: "[name].js",
  134. chunkFilename: 'js/[name].[hash:8].bundle.js',
  135. publicPath: context
  136. },
  137. externals: externals,
  138. module: {
  139. rules: rules
  140. },
  141. plugins: [
  142. new CommonsChunkPlugin({
  143. name: "vendors",
  144. filename: "vendors/[name].js"
  145. }),
  146. new ExtractTextPlugin({
  147. filename: '[name].css',
  148. allChunks: true
  149. }),
  150. globalEnvConfig,
  151. new webpack.NamedModulesPlugin(),
  152. new webpack.HotModuleReplacementPlugin(),
  153. ],
  154. resolve: resolve
  155. }
  156. glob.sync("./src/pages/**/index.html").forEach(path => {
  157. const chunk = path.split("./src/pages/")[1].split("/index.html")[0];
  158. const filename = chunk + "/index.html"
  159. const key = chunk + "/index";
  160. const htmlConf = {
  161. filename: filename,
  162. template: path,
  163. inject: false,
  164. hash: true,
  165. key: key,
  166. chunks: ['vendors', chunk + '/app'],
  167. favicon: './src/static/images/favicon.png'
  168. };
  169. htmlEntrys.push(filename);
  170. devConfig.plugins.push(new HtmlWebpackPlugin(htmlConf));
  171. });
  172. glob.sync("./src/pages/**/app.js").forEach(path => {
  173. const chunk = path.split("./src/pages/")[1].split(".js")[0];
  174. prodEntries[chunk] = [path];
  175. prodChunks.push(chunk);
  176. });
  177. //生产环境的webpack配置
  178. const prodConfig = {
  179. // devtool: 'source-map',
  180. entry: prodEntries,
  181. output: {
  182. publicPath: pathUrl + context,
  183. path: path.resolve(__dirname, contentBase),
  184. chunkFilename: 'js/[name].bundle.js',
  185. },
  186. externals: externals,
  187. module: {
  188. rules: rules
  189. },
  190. plugins: [
  191. new CommonsChunkPlugin({
  192. name: "vendors",
  193. filename: "vendors/[name].js"
  194. }),
  195. new ExtractTextPlugin({
  196. filename: '[name].css',
  197. allChunks: true
  198. }),
  199. globalEnvConfig,
  200. new webpack.optimize.UglifyJsPlugin({
  201. sourceMap: false,
  202. compress: {
  203. warnings: false,
  204. drop_debugger: true,
  205. drop_console: true
  206. }
  207. }),
  208. new CleanWebpackPlugin(['build']),
  209. new BundleAnalyzerPlugin({
  210. analyzerMode: 'static'
  211. })
  212. ],
  213. resolve: resolve
  214. }
  215. glob.sync("./src/pages/**/index.html").forEach(path => {
  216. const chunk = path.split("./src/pages/")[1].split("/index.html")[0];
  217. const filename = chunk + "/index.html";
  218. const key = chunk + "/index";
  219. const realPath = prodConfig.output.publicPath + key + '.js';
  220. const realCssPath = prodConfig.output.publicPath + key + '.css';
  221. const htmlConf = {
  222. filename: filename,
  223. template: path,
  224. inject: false,
  225. hash: true,
  226. key: key,
  227. chunks: ['vendors', chunk + '/app'],
  228. favicon: './src/static/images/favicon.png',
  229. realPath: realPath,
  230. realCssPath: realCssPath,
  231. minify: {
  232. removeComments: true,
  233. collapseWhitespace: true,
  234. removeRedundantAttributes: true,
  235. useShortDoctype: true,
  236. removeEmptyAttributes: true,
  237. removeStyleLinkTypeAttributes: true,
  238. keepClosingSlash: true,
  239. minifyJS: true,
  240. minifyCSS: true,
  241. minifyURLs: true
  242. }
  243. };
  244. prodConfig.plugins.push(new HtmlWebpackPlugin(htmlConf));
  245. });
  246. //最终向uba导出配置文件
  247. module.exports = {
  248. devConfig,
  249. prodConfig,
  250. svrConfig,
  251. proxyConfig,
  252. staticConfig
  253. };

现在的 ucf.config.js:

这个只是为了展示有这个能力,可以直接返回一个对象{},并不是依赖所有功能节点

  1. require('@babel/polyfill');
  2. module.exports = (env, argv) => {
  3. return {
  4. // 启动所有模块,默认这个配置,速度慢的时候使用另外的配置
  5. // bootList: true,
  6. // 启动这两个模块,启动调试、构建
  7. bootList: [
  8. "demo-app-org",
  9. "demo-app-staff"
  10. ],
  11. // 代理的配置
  12. proxy: [
  13. {
  14. enable: true,
  15. headers: {
  16. "Referer": "http://iuap-meger-demo.test.app.yyuap.com"
  17. },
  18. //要代理访问的对方路由
  19. router: [
  20. '/iuap'
  21. ],
  22. url: 'http://iuap-meger-demo.test.app.yyuap.com'
  23. }
  24. ],
  25. // 构建资源的时候产出sourceMap,调试服务不会生效
  26. source_map: true,
  27. // 全局环境变量
  28. global_env: {
  29. GROBAL_HTTP_CTX: JSON.stringify("/iuap_demo"),
  30. },
  31. // 别名配置
  32. alias: {
  33. //'ucf-apps': path.resolve(__dirname, 'ucf-apps/')
  34. },
  35. // 构建排除指定包
  36. externals: {
  37. //'tinper-bee': 'TinperBee'
  38. },
  39. // 加载器Loader
  40. loader: [],
  41. // 调试服务需要运行的插件
  42. devPlugins: [],
  43. // 构建服务需要运行的插件
  44. buildPlugins: []
  45. }
  46. }

1.3、项目整体结构更加规范化,文件数量减少

之前的:

  1. ├── .babelrc
  2. ├── .gitignore
  3. ├── LICENSE
  4. ├── README.md
  5. ├── doc
  6. ├── gulpfile.js
  7. ├── mock
  8. ├── node_modules
  9. ├── package-lock.json
  10. ├── package.json
  11. ├── postcss.config.js
  12. ├── src
  13. ├── uba.config.js
  14. └── uba.mock.js
  15. 4 directories, 10 files

现在的:

  1. ├── .gitignore
  2. ├── LICENSE
  3. ├── README.md
  4. ├── docs
  5. ├── package.json
  6. ├── ucf-apps
  7. ├── ucf-common
  8. └── ucf.config.js
  9. 4 directories, 5 files

1.4、启动开发调试效率加快

原来的大型单页应用,整体页面有可能达到四五百个页面,甚至近千个,在前端开发和调试的速度较慢,目前支持以下优化:

  • 支持将整个应用按功能或是按业务模块拆分为多个独立的单页应用,并支持将部分页面拆分为独立的单体页面。
  • 支持按需启动指定的业务模块,按需打包指定的业务模块。

2、易用性提升

2.1、文档更加系统化

在此之前,前端基础文档、框架介绍资料、典型案例培训文档得现写、配套的开发调试和部署等方式未做说明,文档化内容比较散乱,这次ucf-web 将文档内容也系统化的进行了整理。

2.2、更加友好:支持快速创建标准的模板节点

之前是需要自己手动复制修改粘贴去新增节点,目前已经通过工具命令来快速创建模板,减少复杂繁琐操作,专注业务组件开发

2.3、调试更加方便

目前已经集成了开发平台门户系统,只用于本地开发环境,可以很好的模拟出最终上线后的视觉要求,浏览器兼容性等问题,不再是只开发一部分页面,有效的降低了错误率。还增加了打开后帮助开发者访问指定的页面功能。