├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── lerna.json ├── package.json └── packages ├── ucf-cli ├── README.md ├── bin │ └── ucf-cli ├── package.json └── src │ ├── getAutoUpdate.js │ ├── getDownloadUcf.js │ ├── getI18nMPA.js │ ├── getI18nSPA.js │ ├── getNewModule.js │ ├── getOnlineUcf.js │ ├── getUcfPkg.js │ ├── i18n.js │ ├── index.js │ ├── processBar.js │ ├── templates │ ├── MPA │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ │ ├── app.js │ │ │ ├── app.less │ │ │ ├── components │ │ │ └── App │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ │ ├── index.html │ │ │ ├── model.js │ │ │ └── service.js │ └── SPA │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ ├── app.js │ │ ├── app.less │ │ ├── index.html │ │ └── routes │ │ ├── contact │ │ ├── components │ │ │ └── IndexView │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ ├── model.js │ │ └── service.js │ │ ├── home │ │ ├── components │ │ │ └── IndexView │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ ├── model.js │ │ └── service.js │ │ └── index.js │ ├── ucf-cli-version.json │ └── utils.js ├── ucf-mdf └── README.md ├── ucf-request ├── .babelrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.MD ├── LICENSE ├── README.md ├── dist │ ├── ucf-request.js │ └── ucf-request.min.js ├── lib │ └── index.js ├── package.json ├── src │ └── index.js └── webpack.config.js └── ucf-scripts ├── README.md ├── bin └── ucf-scripts ├── package.json ├── src ├── base.config.js ├── build.config.js ├── build.js ├── index.js ├── start.config.js ├── start.js ├── util.js └── webpack-hot-middleware │ ├── LICENSE │ ├── README.md │ ├── client-overlay.js │ ├── client.js │ ├── helpers.js │ ├── middleware.js │ ├── package.json │ └── process-update.js └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #编辑器文件 2 | .idea 3 | .vscode 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # next.js build output 65 | .next 66 | package-lock.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "UCF-scripts", 11 | "program": "${workspaceFolder}/packages/ucf-scripts/bin/ucf-scripts", 12 | "args": ["start"], 13 | "cwd": "/Users/kvkens/code/yonyou/ucf-webapp" 14 | },{ 15 | "type": "node", 16 | "request": "launch", 17 | "name": "UCF-CLI", 18 | "program": "${workspaceFolder}/packages/ucf-cli/bin/ucf-cli", 19 | "args": ["init"], 20 | "cwd": "/Users/kvkens/code/test/" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 用友网络大前端技术团队 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ucf-web 一个专注于性能与效率的前端微应用开发框架 2 | 3 | > **ucf-web 是整个框架的代号,相关资源都在本仓库内维护,形成统一框架体系。** 4 | 5 | ## 关键特性 6 | 7 | - 中台前端技术收敛化、标准化 8 | - 三种微应用开发模式,打破铁桶一块的巨石应用 9 | - UI 体验一致性,基于统一设计语言之上构建标准组件体系 10 | - HTTP 通讯协议规范化,统一异常处理 11 | - 可选择的模型驱动开发模式,基于元数据之上的统一快速开发 12 | 13 | ## 快速上手使用 14 | 15 | ``` 16 | $ npm i ucf-cli -g 17 | $ ucf-cli init 18 | $ cd xx && npm install 19 | $ npm run dev 20 | ``` 21 | 22 | 23 | 快速理解生成的标准工程: 24 | 25 | ``` 26 | xx-webapp 27 | ├── package.json 28 | ├── ucf-apps # 按应用模块划分的子应用模块 29 | ├── ucf-common # 项目级公共资源:图标字体、公共业务组件、工具方法、配置文件、常量等 30 | ├── ucf-public # 构建出的最终静态资源,可对接集成部署 31 | └── ucf.config.js # 项目配置文件,默认好用,无需配置 32 | ``` 33 | 34 | ucf-apps 下的三类微应用: 35 | - 单体页面,简单直接; 36 | - 单页应用SPA,完成功能级的相关页面; 37 | - 模型驱动的应用mdfApp,动态扩展。 38 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "^3.4.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/ucf-cli/README.md: -------------------------------------------------------------------------------- 1 | # ucf-cli 2 | 3 | [![npm version](https://img.shields.io/npm/v/ucf-cli.svg)](https://www.npmjs.com/package/ucf-cli) 4 | [![NPM downloads](http://img.shields.io/npm/dt/ucf-cli.svg?style=flat)](https://npmjs.org/package/ucf-cli) 5 | 6 | --- 7 | 8 | [![NPM](https://nodei.co/npm/ucf-cli.png)](https://nodei.co/npm/ucf-cli/) 9 | 10 | --- 11 | 12 | ## 介绍 13 | 14 | 通过该工具可以快速下载初始化UCF微服务前端工程所有资源到本机开发,并且可以快速创建指定的页面、带路由页面等,功能强大、操作简单易上手。 15 | 16 | ![image](http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/uba/gui/img/ucf-cli.gif) 17 | 18 | ![image](http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/uba/gui/img/ucf-cli-err.png) 19 | 20 | 21 | ## 安装 22 | 23 | 24 | ```bash 25 | # 全局安装 26 | $ npm install ucf-cli -g 27 | ``` 28 | 29 | ## 使用 30 | 31 | 安装完成全局后使用下面命令: 32 | 33 | ```bash 34 | 35 | # 指定名称 `ucf-custom`,将会在ucf-custom里面创建资源 36 | $ ucf init ucf-custom 37 | 38 | # 快速下载工程到本地,并且不会创建文件夹直接在当前运行根目录进行平铺,适合初始化git仓库使用 39 | $ ucf init 40 | 41 | 42 | # 快速创建基础页面包含大致UCF微服务工程结构 43 | $ ucf new app 44 | 45 | # 查看现有微服务工程名 46 | $ ucf list 47 | 48 | ``` 49 | 50 | ## 说明 51 | 52 | - 查看帮助 `ucf -h` 53 | - 查看版本 `ucf -v` 54 | - 下载工程 `ucf init myweb` 55 | - 创建模块 `ucf new app` 56 | - 查看模块 `ucf list` 57 | 58 | 59 | 60 | ## 版本 61 | - `1.4.0` `ucf new app`模板去掉`container.js`文件,将路由与`container.js`合并 62 | - `1.3.1` 去除多语模板、调整为统一兼容模板 63 | - `1.3.0` 更换远程脚手架工程托管平台,下载速度更快 64 | - `1.2.3` 解决人机问答第三包包异常的问题,锁定版本号 65 | - `1.2.2` 调整模板CDN设置、更新代码 66 | - `1.2.1` 调整`ucf init`初始化工程必须输入名字,而不是直接平铺到根目录,也可以`ucf init webapp`直接指定工程名 67 | - `1.2.0` 调整`i18n`多语的机制问题,`Intl`多语文件夹调整到`ucf-common`下 68 | - `1.1.1` 修复没有选择多语微应用出现了`Intl`文件夹 69 | - `1.1.0` 增加微应用命令`ucf new app`多语选项的支持 70 | - `1.0.7` 调整微应用显示名称为 `MPA` , `SPA` 71 | - `1.0.6` 调整微应用显示名称为 `singleApp` , `spaApp` 72 | - `1.0.5` 在线更新最新微应用模板(仅测试) 73 | - `1.0.4` 模板App入口修改为`IndexView` 74 | - `1.0.3` 升级模板 75 | - `1.0.2` 增加版本在线检测,有新版本会给出提示升级 76 | - `1.0.1` 更新模板路由页面、`ucf init` 表示在当前根目录下平铺工程文件,适合初始化`git`仓库的时候 77 | - `1.0.0` 支持远端UCF工程初始化到本地、创建微服务模块工程、模块查看、内置两种模板 78 | - `0.0.x` 初步测试版本发布 -------------------------------------------------------------------------------- /packages/ucf-cli/bin/ucf-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | var commands = argv._; 5 | 6 | 7 | var opts = { 8 | cmd: commands, 9 | argv: argv, 10 | name: "ucf-cli" 11 | }; 12 | 13 | 14 | require("../src").plugin(opts); -------------------------------------------------------------------------------- /packages/ucf-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ucf-cli", 3 | "preferGlobal": true, 4 | "version": "1.4.0", 5 | "description": "A simple init for UCF scaffolding projects.", 6 | "bin": { 7 | "ucf-cli": "./bin/ucf-cli", 8 | "ucf": "./bin/ucf-cli" 9 | }, 10 | "scripts": {}, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/iuap-design/ucf-web.git" 14 | }, 15 | "keywords": [ 16 | "ucf", 17 | "ucf-cli", 18 | "tool", 19 | "yonyou", 20 | "fe" 21 | ], 22 | "author": "Kvkens (yueming@yonyou.com)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/iuap-design/ucf-web/issues" 26 | }, 27 | "homepage": "https://github.com/iuap-design/ucf-web/tree/master/packages/ucf-cli#readme", 28 | "dependencies": { 29 | "chalk": "2.4.2", 30 | "cli-color": "1.4.0", 31 | "download-git-repo": "1.1.0", 32 | "ejs": "2.6.1", 33 | "fs-extra": "7.0.1", 34 | "glob": "7.1.3", 35 | "inquirer": "6.2.1", 36 | "minimist": "1.2.0", 37 | "path-exists": "3.0.0", 38 | "request": "2.88.0", 39 | "request-promise": "^4.2.4", 40 | "single-line-log": "1.1.2", 41 | "str-format": "1.0.1", 42 | "unzipper": "^0.10.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/getAutoUpdate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 检测当前是否有新版本,给出提示升级ucf-cli 3 | * @url http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/uba/ucf-cli-version.json 4 | */ 5 | 6 | const request = require('request'); 7 | const chalk = require('chalk'); 8 | const path = require('path'); 9 | 10 | module.exports = () => { 11 | request({ url: 'http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/uba/ucf-cli-version.json' }, (error, response, body) => { 12 | let result = JSON.parse(body); 13 | let version = require('../package.json').version; 14 | if(result['ucf-cli'] != version){ 15 | console.log(chalk.yellow.bold(`New version ${version} -> ${result['ucf-cli']}`)); 16 | console.log(chalk.yellow.bold(`npm install ucf-cli@${result['ucf-cli']} -g`)); 17 | } 18 | }); 19 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/getDownloadUcf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UCF 下载最新仓库内的UCF完整工程 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-01-21 11:14:35 5 | */ 6 | 7 | const chalk = require('chalk'); 8 | const path = require('path'); 9 | const pathExists = require('path-exists'); 10 | // const download = require('download-git-repo'); 11 | const fse = require('fs-extra'); 12 | const inquirer = require('inquirer'); 13 | const utils = require('./utils'); 14 | const unzipper = require('unzipper'); 15 | 16 | module.exports = async (folderName = '.') => { 17 | if (folderName == '.') { 18 | let inquirerProjectName = await inquirer.prompt([{ 19 | type: 'input', 20 | name: 'name', 21 | message: 'Project Name:', 22 | default: function () { 23 | return 'ucf-web'; 24 | } 25 | }]); 26 | folderName = inquirerProjectName.name; 27 | } 28 | 29 | console.log(chalk.green(`\t\t⏳ UCF cloud transfer to local machine ⏳`)); 30 | console.log(); 31 | // console.log(chalk.green(`⏳🔊📢⚠️🇺🇿🌍☁️`)); 32 | console.log(chalk.cyan.bold(`[Info] : 🚀 Start downloading UCF project to the current directory 🎁`)); 33 | console.log(chalk.cyan.bold(`Path:${path.resolve('.', folderName)} 🏠`)); 34 | console.log(); 35 | 36 | var ProgressBar = require('./processBar'); 37 | var pb = new ProgressBar('Download', 72); 38 | var num = 0, total = 100; 39 | 40 | function downloading() { 41 | if (num < total) { 42 | pb.render({ completed: num, total: total, status: 'Downloading...' }); 43 | num++; 44 | setTimeout(function () { 45 | downloading(); 46 | }, 10); 47 | } else { 48 | //pb.render({ completed: num, total: total, status: "Completed." }); 49 | //process.exit(0); 50 | } 51 | } 52 | 53 | if (!pathExists.sync(folderName) || folderName == 'ucf-web') { 54 | downloading(); 55 | // download('iuap-design/ucf-webapp', folderName, function (err) { 56 | // if (!err) { 57 | // pb.render({ completed: num, total: total, status: "Completed." }); 58 | // console.log(); 59 | // console.log(); 60 | // console.log(chalk.cyan(`🚀 Next, install NPM package dependencies 🎁 `)); 61 | // console.log(chalk.cyan(`[Tips] : 🏆 cd ${folderName} && npm install && npm start`)); 62 | // } else { 63 | 64 | // } 65 | // }); 66 | // utils.download({ 67 | // url: "http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/ucf/templates/latest/ucf-webapp-master.zip" 68 | // }, () => { 69 | // pb.render({ completed: num, total: total, status: "Completed." }); 70 | // console.log(); 71 | // console.log(); 72 | // console.log(chalk.cyan(`🚀 Next, install NPM package dependencies 🎁 `)); 73 | // console.log(chalk.cyan(`[Tips] : 🏆 cd ${folderName} && npm install && npm start`)); 74 | // }, `${folderName}.zip`); 75 | let result = await utils.getRemoteZip({ 76 | filename: folderName 77 | }); 78 | let filepath = path.resolve('.'); 79 | if (result.success) { 80 | fse.createReadStream(`${filepath}/ucf-webapp-master.tmp`).pipe(unzipper.Extract({ path: filepath })).on('close', () => { 81 | // 删除压缩包 82 | fse.remove(`${filepath}/ucf-webapp-master.tmp`); 83 | fse.renameSync(`${filepath}/ucf-webapp-master`,`${filepath}/${folderName}`); 84 | }); 85 | pb.render({ completed: num, total: total, status: "Completed." }); 86 | console.log(); 87 | console.log(); 88 | console.log(chalk.cyan(`🚀 Next, install NPM package dependencies 🎁 `)); 89 | console.log(chalk.cyan(`[Tips] : 🏆 cd ${folderName} && npm install && npm start`)); 90 | } 91 | } else { 92 | console.log(chalk.red.bold(`[Error] : ⚠️ directory ${folderName} already exists. 😫`)); 93 | console.log(chalk.yellow(`[Tips] : 🤔 Try renaming the project name 🤗 `)); 94 | process.exit(0); 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/getI18nMPA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成MPA微应用 3 | */ 4 | 5 | const path = require('path'); 6 | const fse = require('fs-extra'); 7 | const ejs = require('ejs'); 8 | 9 | module.exports = async (ucfApps, ucfParam) => { 10 | // let IntlDirPath = path.resolve(ucfApps, ucfParam.name, 'src', 'components', 'Intl'); 11 | // #package.json 12 | let pkg_path = path.resolve(ucfApps, ucfParam.name, 'package.json'); 13 | let pkg_json = await ejs.renderFile(pkg_path, { name: ucfParam.name }); 14 | await fse.outputFile(pkg_path, pkg_json); 15 | 16 | // #app.js 17 | let app_path = path.resolve(ucfApps, ucfParam.name, 'src', 'app.js'); 18 | let app_json = await ejs.renderFile(app_path, { isI18n: ucfParam.i18n }); 19 | await fse.outputFile(app_path, app_json); 20 | 21 | // #container.js 22 | // let container_path = path.resolve(ucfApps, ucfParam.name, 'src', 'container.js'); 23 | // let container_json = await ejs.renderFile(container_path, { isI18n: ucfParam.i18n }); 24 | // await fse.outputFile(container_path, container_json); 25 | 26 | // #App/index.js 27 | let app_index_path = path.resolve(ucfApps, ucfParam.name, 'src', 'components', 'app', 'index.js'); 28 | let app_index_json = await ejs.renderFile(app_index_path, { isI18n: ucfParam.i18n }); 29 | await fse.outputFile(app_index_path, app_index_json); 30 | // 删除不属于多语的文件夹 31 | // if (!ucfParam.i18n) { 32 | // await fse.remove(IntlDirPath) 33 | // } 34 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/getI18nSPA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成SPA微应用 3 | */ 4 | 5 | const path = require('path'); 6 | const fse = require('fs-extra'); 7 | const ejs = require('ejs'); 8 | 9 | module.exports = async (ucfApps, ucfParam) => { 10 | // let IntlDirPath = path.resolve(ucfApps, ucfParam.name, 'src', 'Intl'); 11 | // #package.json 12 | let sps_pkg_path = path.resolve(ucfApps, ucfParam.name, 'package.json'); 13 | let sps_pkg_json = await ejs.renderFile(sps_pkg_path, { name: ucfParam.name }); 14 | await fse.outputFile(sps_pkg_path, sps_pkg_json); 15 | 16 | // #app.js 17 | let spa_app_path = path.resolve(ucfApps, ucfParam.name, 'src', 'app.js'); 18 | let spa_app_json = await ejs.renderFile(spa_app_path, { isI18n: ucfParam.i18n }); 19 | await fse.outputFile(spa_app_path, spa_app_json); 20 | 21 | // #home/container.js 22 | // let home_container_path = path.resolve(ucfApps, ucfParam.name, 'src', 'routes', 'home', 'container.js'); 23 | // let home_container_json = await ejs.renderFile(home_container_path, { isI18n: ucfParam.i18n }); 24 | // await fse.outputFile(home_container_path, home_container_json); 25 | 26 | // #home/components/IndexView/index.js 27 | let indexview_path = path.resolve(ucfApps, ucfParam.name, 'src', 'routes', 'home', 'components', 'IndexView', 'index.js'); 28 | let indexview_json = await ejs.renderFile(indexview_path, { isI18n: ucfParam.i18n }); 29 | await fse.outputFile(indexview_path, indexview_json); 30 | // 删除不属于多语的文件夹 31 | // if (!ucfParam.i18n) { 32 | // await fse.remove(IntlDirPath) 33 | // } 34 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/getNewModule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UCF 微服务前端工程最佳实践页面生成 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-05-17 10:44:06 5 | */ 6 | 7 | const chalk = require('chalk'); 8 | const path = require('path'); 9 | const fse = require('fs-extra'); 10 | const inquirer = require('inquirer'); 11 | const ejs = require('ejs'); 12 | const getI18nMPA = require('./getI18nMPA'); 13 | const getI18nSPA = require('./getI18nSPA'); 14 | 15 | 16 | module.exports = async (app = 'app') => { 17 | // 连接配置文件 18 | let ucfFilePath = path.resolve('.', 'ucf.config.js'); 19 | // 目标路径 20 | let ucfApps = path.resolve('.', 'ucf-apps'); 21 | // 模板路径 22 | let ucfPathTmp = path.resolve(__dirname, './templates'); 23 | // 人机交互选择的模板名称 24 | let ucfSelectTempArr = ['MPA', 'SPA']; 25 | // 生成模块参数 26 | let ucfParam = { 27 | name: '',// 微应用名字 28 | mode: '',// 选择哪种模板 29 | i18n: false,// 是否需要多语 30 | }; 31 | // TO DO : 1. 检测ucf.config.js是否存在,来判断当前目录是否正确 32 | let hasUcfFile = await fse.pathExists(ucfFilePath); 33 | if (!hasUcfFile) { 34 | //TO DO : 2.2 找不到配置文件,说明运行目录不正确给出提示 35 | console.log(chalk.red.bold('😫 Error failed to find ucf.config configuration file')); 36 | process.exit(1); 37 | } 38 | //TO DO : 2.1 确定正确目录下,开始执行下一步模块选择操作 39 | 40 | // 所有new操作主逻辑 41 | switch (app) { 42 | case 'app': 43 | console.log(chalk.cyan('🎁 Create App module startup...')); 44 | //TO DO : 3 展示人机交互,输入工程模块名,选择模板 45 | 46 | // 输入模块名 47 | let inquirerTempName = await inquirer.prompt([{ 48 | type: 'input', 49 | name: 'inputName', 50 | message: 'Page Name:', 51 | default: function () { 52 | return 'ucf-app-demo'; 53 | } 54 | }]); 55 | 56 | ucfParam.name = inquirerTempName.inputName; 57 | 58 | // 选择哪种方式的页面 59 | let inquirerTempModule = await inquirer.prompt([{ 60 | type: 'list', 61 | name: 'selectTemplates', 62 | message: 'UCF Templates Please Select:', 63 | choices: ucfSelectTempArr 64 | }]); 65 | 66 | ucfParam.mode = inquirerTempModule.selectTemplates; 67 | 68 | // 是否使用多语 69 | // let inquirerTempI18n = await inquirer.prompt([{ 70 | // type: 'confirm', 71 | // name: 'selectI18n', 72 | // message: 'Do you need i18n', 73 | // }]); 74 | 75 | // ucfParam.i18n = inquirerTempI18n.selectI18n; 76 | ucfParam.i18n = false; 77 | // console.log(ucfParam); 78 | // process.exit(0); 79 | 80 | // 复制微应用模板到客户指定位置 81 | await fse.copy(path.resolve(ucfPathTmp, ucfParam.mode), path.resolve(ucfApps, ucfParam.name)); 82 | // 判断模板类型 mode 83 | switch (ucfParam.mode) { 84 | case 'MPA': 85 | getI18nMPA(ucfApps, ucfParam); 86 | break; 87 | case 'SPA': 88 | getI18nSPA(ucfApps, ucfParam); 89 | break; 90 | default: 91 | break; 92 | } 93 | console.log(chalk.green(`🤗 Module Creation Successfully to \n💪 ${path.resolve(ucfApps, ucfParam.name)}`)); 94 | break; 95 | default: 96 | break; 97 | } 98 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/getOnlineUcf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获得在线最新UCF工程 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-01-21 11:14:35 5 | */ -------------------------------------------------------------------------------- /packages/ucf-cli/src/getUcfPkg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获得当前UCF下所有模块 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-01-24 19:10:34 5 | */ 6 | 7 | const path = require('path'); 8 | const glob = require('glob'); 9 | const chalk = require('chalk'); 10 | 11 | let appsPath = path.resolve('.', 'ucf-apps/*/'); 12 | 13 | module.exports = () => { 14 | glob.sync(appsPath).forEach(_path => { 15 | console.log(chalk.yellow.bold(`module : ${path.basename(_path)}`)) 16 | }); 17 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/i18n.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuap-design/ucf-web/888b525810e0c775dcb3eff7ab193b08faa8a637/packages/ucf-cli/src/i18n.js -------------------------------------------------------------------------------- /packages/ucf-cli/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UCF 微服务前端工程最佳实践脚手架生成工具 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-01-21 11:14:35 5 | */ 6 | 7 | const chalk = require('chalk'); 8 | const getDownloadUcf = require('./getDownloadUcf'); 9 | const getNewModule = require('./getNewModule'); 10 | const getUcfPkg = require('./getUcfPkg'); 11 | const getAutoUpdate = require('./getAutoUpdate'); 12 | 13 | //第一时间检测是否有最新版本给出提升自行升级或者是热更新模板 14 | 15 | getAutoUpdate(); 16 | 17 | 18 | function getHelp() { 19 | console.log(chalk.green.bold(" Usage : ")); 20 | console.log(); 21 | console.log(chalk.green(" ucf init \t 🚀 Create a standard microservice front-end project")); 22 | console.log(); 23 | console.log(chalk.green(" ucf new app \t ☁️ Create a module page \n \t\t ⚠️ There are two types of pages: separate pages and separate pages containing routing.")); 24 | console.log(); 25 | // process.exit(0); 26 | } 27 | 28 | function getVersion() { 29 | console.log(chalk.green('👉 ' + require("../package.json").version)); 30 | // process.exit(0); 31 | } 32 | 33 | module.exports = { 34 | plugin: function (options) { 35 | commands = options.cmd; 36 | pluginname = options.name; 37 | if (options.argv.h || options.argv.help) { 38 | getHelp(); 39 | } 40 | if (options.argv.v || options.argv.version) { 41 | getVersion(); 42 | } 43 | // if (options.argv._.length == 0) { 44 | // getHelp(); 45 | // } 46 | let action = options.argv._[0], 47 | param = options.argv._[1]; 48 | switch (action) { 49 | case 'init': 50 | getDownloadUcf(param); 51 | break; 52 | case 'new': 53 | getNewModule(param); 54 | break; 55 | case 'list': 56 | getUcfPkg(); 57 | break; 58 | default: 59 | // getHelp(); 60 | break; 61 | } 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/processBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UCF 进度条实现 3 | * @author Kvkens(yueming@yonyou.com) 4 | * @date 2019-01-21 11:14:35 5 | */ 6 | 7 | const log = require('single-line-log').stdout; 8 | const strformat = require('str-format').format; 9 | const clicolor = require('cli-color'); 10 | 11 | /** 12 | * 封装一个进度条工具。 13 | */ 14 | function ProgressBar(description, bar_length) { 15 | this.description = description || "PROGRESS"; 16 | this.length = bar_length.length || 28; 17 | 18 | // 刷新进度条图案,文字的方法 19 | this.render = function (opts) { 20 | var percentage = (opts.completed / opts.total).toFixed(4); 21 | var cell_num = Math.floor(percentage * this.length); 22 | // 拼接黑色条 23 | var cell = ''; 24 | for (var i = 0; i < cell_num; i++) { 25 | cell += '█'; 26 | } 27 | // 拼接灰色条 28 | var empty = ''; 29 | for (var i = 0; i < this.length - cell_num; i++) { 30 | empty += '░'; 31 | } 32 | 33 | var percent = (100 * percentage).toFixed(2); 34 | /** 35 | * 使用cli-color进行包装美化。 36 | */ 37 | this.description = clicolor.blue.bold(this.description); 38 | cell = clicolor.green.bgBlack.bold(cell); 39 | opts.completed = clicolor.yellow.bold(opts.completed); 40 | opts.total = clicolor.blue.bold(opts.total); 41 | opts.status = percent == 100.00 ? clicolor.green.bold(opts.status) : clicolor.red.bold(opts.status); 42 | 43 | 44 | // 拼接最终文本 45 | var cmdtext = strformat("<{}:{}%> {}{} [ {}/{} `{}`]", [this.description, percent, 46 | cell, empty, opts.completed, opts.total, opts.status]); 47 | log(cmdtext); 48 | }; 49 | } 50 | 51 | /** 52 | * 模块导出。 53 | */ 54 | module.exports = ProgressBar; -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/README.md: -------------------------------------------------------------------------------- 1 | # 单独页面不带路由 2 | 3 | 适合单独开发某一个模块,不涉及到路由层面可以使用本模板 -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%=name %>", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "MPA APP", 6 | "author": "yonyou" 7 | } 8 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 入口、导入组件样式、渲染 3 | */ 4 | 5 | import React from 'react'; 6 | import { render } from 'mirrorx'; 7 | import mirror, { connect } from 'mirrorx'; 8 | import IndexView from './components/App'; 9 | 10 | import model from './model' 11 | 12 | import './app.less'; 13 | 14 | // 数据和组件UI关联、绑定 15 | mirror.model(model); 16 | 17 | const App = connect(state => state.app)(IndexView); 18 | 19 | 20 | render(, document.querySelector("#app")); -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/app.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuap-design/ucf-web/888b525810e0c775dcb3eff7ab193b08faa8a637/packages/ucf-cli/src/templates/MPA/src/app.less -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/components/App/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App模块 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { Table, Button } from 'tinper-bee'; 7 | 8 | import './index.less'; 9 | 10 | class App extends Component { 11 | constructor(props) { 12 | super(props); 13 | } 14 | columns = [ 15 | { 16 | title: '用户名', 17 | dataIndex: "username", 18 | key: "username", 19 | width: 300 20 | }, 21 | { 22 | title: '性别', 23 | dataIndex: "sex", 24 | key: "sex", 25 | width: 500 26 | }, 27 | { 28 | title: '年龄', 29 | dataIndex: "age", 30 | key: "age", 31 | width: 200 32 | } 33 | ]; 34 | data = [ 35 | { username: "令狐冲", sex: "男", age: 41, d: "操作", key: "1" }, 36 | { username: "杨过", sex: "男", age: 67, d: "操作", key: "2" }, 37 | { username: "郭靖", sex: "男", age: 25, d: "操作", key: "3" } 38 | ]; 39 | render() { 40 | return ( 41 |
42 | { 46 | console.log(record, index); 47 | }} 48 | footer={() => 49 | 52 | } 53 | /> 54 | 55 | ); 56 | } 57 | } 58 | 59 | App.displayName = "App"; 60 | export default App; 61 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/components/App/index.less: -------------------------------------------------------------------------------- 1 | .app-wrap { 2 | font-weight: bold; 3 | font-size: 18px; 4 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | MPA 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据模型类 3 | */ 4 | 5 | import { actions } from "mirrorx"; 6 | import * as api from "./service"; 7 | 8 | export default { 9 | // 确定 Store 中的数据模型作用域 10 | name: "app", 11 | // 设置当前 Model 所需的初始化 state 12 | initialState: { 13 | order: '', 14 | }, 15 | reducers: { 16 | /** 17 | * 纯函数,相当于 Redux 中的 Reducer,只负责对数据的更新。 18 | * @param {*} state 19 | * @param {*} data 20 | */ 21 | updateState(state, data) { //更新state 22 | return { 23 | ...state, 24 | ...data 25 | }; 26 | } 27 | }, 28 | effects: { 29 | /** 30 | * 按钮测试数据 31 | * @param {*} param 32 | * @param {*} getState 33 | */ 34 | async loadData(params, getState) { 35 | let result = await api.getList(params); 36 | return result; 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/MPA/src/service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务请求类 3 | */ 4 | import request from "axios"; 5 | //定义接口地址 6 | const URL = { 7 | "GET_LIST": `${GROBAL_HTTP_CTX}/order/list` 8 | } 9 | 10 | /** 11 | * 获取主列表 12 | * @param {*} params 13 | */ 14 | export const getList = (params) => { 15 | return request(URL.GET_LIST, { 16 | method: "get", 17 | params 18 | }); 19 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/README.md: -------------------------------------------------------------------------------- 1 | # 单页面带路由 2 | 3 | 使用单页面SPA包含路由使用可以使用本模板 -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%=name %>", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "templates", 6 | "author": "yonyou" 7 | } 8 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 入口、路由、导入组件样式、渲染页面 3 | */ 4 | 5 | import React from 'react'; 6 | import mirror, { render, Router } from 'mirrorx'; 7 | import Routes from './routes'; 8 | 9 | // 全局样式 10 | import './app.less'; 11 | 12 | // 设置mirrorx 路由加载方式 13 | mirror.defaults({ 14 | historyMode: "hash" 15 | }); 16 | 17 | render( 18 | 19 | , document.querySelector("#app")); -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/app.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuap-design/ucf-web/888b525810e0c775dcb3eff7ab193b08faa8a637/packages/ucf-cli/src/templates/SPA/src/app.less -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | SPA 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/contact/components/IndexView/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { actions } from 'mirrorx'; 3 | import { Button } from 'tinper-bee'; 4 | import './index.less'; 5 | 6 | class IndexView extends Component { 7 | render() { 8 | return ( 9 |
10 | Hello,World Contact 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default IndexView; 17 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/contact/components/IndexView/index.less: -------------------------------------------------------------------------------- 1 | .contact-wrap { 2 | font-size: 18px; 3 | font-weight: bold; 4 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/contact/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据模型类 3 | */ 4 | 5 | import { actions } from "mirrorx"; 6 | import * as api from "./service"; 7 | 8 | export default { 9 | // 确定 Store 中的数据模型作用域 10 | name: "contact", 11 | // 设置当前 Model 所需的初始化 state 12 | initialState: { 13 | text: '', 14 | }, 15 | reducers: { 16 | /** 17 | * 纯函数,相当于 Redux 中的 Reducer,只负责对数据的更新。 18 | * @param {*} state 19 | * @param {*} data 20 | */ 21 | updateState(state, data) { //更新state 22 | return { 23 | ...state, 24 | ...data 25 | }; 26 | } 27 | }, 28 | effects: { 29 | /** 30 | * 按钮测试数据 31 | * @param {*} param 32 | * @param {*} getState 33 | */ 34 | async loadData(params, getState) { 35 | let result = await api.getList(params); 36 | return result; 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/contact/service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务请求类 3 | */ 4 | import request from "axios"; 5 | //定义接口地址 6 | const URL = { 7 | "GET_LIST": `${GROBAL_HTTP_CTX}/sales/list` 8 | } 9 | 10 | /** 11 | * 获取主列表 12 | * @param {*} params 13 | */ 14 | export const getList = (params) => { 15 | return request(URL.GET_LIST, { 16 | method: "get", 17 | params 18 | }); 19 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/home/components/IndexView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App模块 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { Table, Button } from 'tinper-bee'; 7 | 8 | import './index.less'; 9 | 10 | class IndexView extends Component { 11 | constructor(props) { 12 | super(props); 13 | } 14 | columns = [ 15 | { 16 | title: '用户名', 17 | dataIndex: "username", 18 | key: "username", 19 | width: 300 20 | }, 21 | { 22 | title: '性别', 23 | dataIndex: "sex", 24 | key: "sex", 25 | width: 500 26 | }, 27 | { 28 | title: '年龄', 29 | dataIndex: "age", 30 | key: "age", 31 | width: 200 32 | } 33 | ]; 34 | data = [ 35 | { username: "令狐冲", sex: "男", age: 41, d: "操作", key: "1" }, 36 | { username: "杨过", sex: "男", age: 67, d: "操作", key: "2" }, 37 | { username: "郭靖", sex: "男", age: 25, d: "操作", key: "3" } 38 | ]; 39 | render() { 40 | return ( 41 |
42 |
{ 46 | console.log(record, index); 47 | }} 48 | footer={() => 49 | 52 | } 53 | /> 54 | 55 | ); 56 | } 57 | } 58 | 59 | IndexView.displayName = "IndexView"; 60 | export default IndexView; 61 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/home/components/IndexView/index.less: -------------------------------------------------------------------------------- 1 | .home-wrap { 2 | font-size: 18px; 3 | font-weight: bold; 4 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/home/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据模型类 3 | */ 4 | 5 | import { actions } from "mirrorx"; 6 | import * as api from "./service"; 7 | 8 | export default { 9 | // 确定 Store 中的数据模型作用域 10 | name: "home", 11 | // 设置当前 Model 所需的初始化 state 12 | initialState: { 13 | text: '', 14 | }, 15 | reducers: { 16 | /** 17 | * 纯函数,相当于 Redux 中的 Reducer,只负责对数据的更新。 18 | * @param {*} state 19 | * @param {*} data 20 | */ 21 | updateState(state, data) { //更新state 22 | return { 23 | ...state, 24 | ...data 25 | }; 26 | } 27 | }, 28 | effects: { 29 | /** 30 | * 按钮测试数据 31 | * @param {*} param 32 | * @param {*} getState 33 | */ 34 | async loadData(params, getState) { 35 | let result = await api.getList(params); 36 | return result; 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/home/service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务请求类 3 | */ 4 | import request from "axios"; 5 | //定义接口地址 6 | const URL = { 7 | "GET_LIST": `${GROBAL_HTTP_CTX}/sales/list` 8 | } 9 | 10 | /** 11 | * 获取主列表 12 | * @param {*} params 13 | */ 14 | export const getList = (params) => { 15 | return request(URL.GET_LIST, { 16 | method: "get", 17 | params 18 | }); 19 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/templates/SPA/src/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 前端路由说明: 3 | * 基于浏览器 History 的前端 Hash 路由 4 | */ 5 | 6 | import React from "react"; 7 | import { Route } from "mirrorx"; 8 | 9 | // 引用mirrorx作为connect 10 | import mirror, { connect } from 'mirrorx'; 11 | 12 | // 默认页面组件 13 | import Home from './home/components/IndexView'; 14 | import Contact from './contact/components/IndexView'; 15 | //引用模型 16 | import homeModel from './home/model'; 17 | import contactModel from './contact/model'; 18 | 19 | // 数据和组件UI关联、绑定 20 | mirror.model(homeModel); 21 | 22 | // 数据和组件UI关联、绑定 23 | mirror.model(contactModel); 24 | 25 | 26 | const ConnectedContact = connect(state => state.contact)(Contact); 27 | 28 | const ConnectedHome = connect(state => state.home)(Home); 29 | 30 | 31 | export default () => ( 32 |
33 | 34 | 35 | 36 |
37 | ); -------------------------------------------------------------------------------- /packages/ucf-cli/src/ucf-cli-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "ucf-cli": "1.4.0", 3 | "ucf-templates": "1.0.0" 4 | } -------------------------------------------------------------------------------- /packages/ucf-cli/src/utils.js: -------------------------------------------------------------------------------- 1 | const rp = require('request-promise'); 2 | const path = require('path'); 3 | const fse = require('fs-extra'); 4 | 5 | const download = async function (options, filename, cb) { 6 | let opts = { 7 | method: 'get', 8 | headers: { 9 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 10 | 'Accept-Encoding': 'gzip, deflate', 11 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7', 12 | 'Connection': 'keep-alive', 13 | 'Upgrade-Insecure-Requests': 1 14 | } 15 | } 16 | opts = { ...opts, ...options }; 17 | // 获得文件夹路径 18 | let fileFolder = path.dirname(filename); 19 | // 创建文件夹 20 | fse.ensureDirSync(fileFolder); 21 | // 开始下载无需返回 22 | rp(opts).pipe(fse.createWriteStream(filename)).on('close', cb); 23 | } 24 | 25 | /** 26 | * 下载zip压缩包包含路径文件名 27 | */ 28 | const getRemoteZip = ({ filename, filepath }, cb) => { 29 | let url = `http://iuap-design-cdn.oss-cn-beijing.aliyuncs.com/static/ucf/templates/1.4.x/ucf-webapp-master.zip` 30 | return new Promise((resolve, reject) => { 31 | download({ url }, `ucf-webapp-master.tmp`, () => { 32 | resolve({ success: true }); 33 | }); 34 | }); 35 | } 36 | 37 | 38 | exports.download = download; 39 | exports.getRemoteZip = getRemoteZip; -------------------------------------------------------------------------------- /packages/ucf-mdf/README.md: -------------------------------------------------------------------------------- 1 | ucf2 层要实现的工作,模型驱动的sdk和典型模板维护,例如 2 | 3 | ``` 4 | import { createMDFApp } from 'ucf-mdf/lib/core' 5 | 6 | class myMDFApp extends createMDFApp{ 7 | 8 | } 9 | 10 | export default myMDFApp 11 | ``` -------------------------------------------------------------------------------- /packages/ucf-request/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } -------------------------------------------------------------------------------- /packages/ucf-request/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .git 4 | .vscode 5 | .DS_Store 6 | package-lock.json -------------------------------------------------------------------------------- /packages/ucf-request/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | 7 | before_script: 8 | - npm run build 9 | 10 | after_success: 11 | - npm run build 12 | -------------------------------------------------------------------------------- /packages/ucf-request/CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - 解决操作错误无法返回的问题 4 | 5 | ## 0.0.6 6 | 7 | - 迁移仓库 8 | 9 | ## 0.0.5 10 | 11 | - 发布第一版 -------------------------------------------------------------------------------- /packages/ucf-request/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 用友网络大前端技术团队 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 | -------------------------------------------------------------------------------- /packages/ucf-request/README.md: -------------------------------------------------------------------------------- 1 | # ucf-request 2 | 3 | 网络请求库,基于 axios 封装, 旨在为开发者提供一个统一的api调用方式. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![build status][travis-image]][travis-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/ucf-request.svg?style=flat-square 9 | [npm-url]: https://npmjs.org/package/ucf-request 10 | [travis-image]: https://img.shields.io/travis/iuap-design/ucf-request.svg?style=flat-square 11 | [travis-url]: https://travis-ci.org/iuap-design/ucf-request.svg?branch=master 12 | 13 | -------------------- 14 | 15 | ## 支持的功能 16 | - url 参数自动序列化 17 | - post 数据提交方式简化 18 | - response 返回处理简化 19 | - api 超时支持 20 | - axios 的 request 和 response 拦截器(interceptors)支持 21 | - 统一的错误处理方式 22 | 23 | 24 | ## 安装 25 | `npm install ucf-request --save` 26 | 27 | 28 | ## 使用 29 | ```javascript 30 | 31 | import request from 'ucf-request'; 32 | 33 | 34 | // 请求一个api, 没有method参数默认为get 35 | request('/api/v1/some/api').then(res => { 36 | console.log(res); 37 | }).catch(err => { 38 | console.log(err); 39 | }); 40 | 41 | // url参数序列化 42 | request('/api/v1/some/api', { params: {foo: 'bar'} }); 43 | 44 | // post 数据提交简化 45 | // 当data为object时, 默认requestType: 'json'可不写, header会自动带上 application/json 46 | request('/api/v1/some/api', { method:'post', data: {foo: 'bar'} }); 47 | 48 | // requestType: 'form', header会自动带上 application/x-www-form-urlencoded 49 | request('/api/v1/some/api', { method:'post', requestType: 'form', data: {foo: 'bar'} }); 50 | 51 | // reponseType: 'blob', 如何处理返回的数据, 默认情况下 text 和 json 都不用加. 如blob 或 formData 之类需要加 52 | request('/api/v1/some/api', { reponseType: 'blob' }); 53 | 54 | // 提交其他数据, 如文本, 上传文件等, requestType不填, 手动添加对应header. 55 | request('/api/v1/some/api', { method:'post', data: 'some data', headers: { 'Content-Type': 'multipart/form-data'} }); 56 | 57 | 58 | // 超时 单位毫秒, 但是超时后客户端虽然返回超时, 但api请求不会断开, 写操作慎用. 59 | request('/api/v1/some/api', { timeout: 3000 }); 60 | 61 | // 使用缓存, 只有get时有效. 单位毫秒, 不加ttl默认60s, ttl=0不过期. cache key为url+params组合 62 | request('/api/v1/some/api', { params: { hello: 'world' }, useCache: true, ttl: 10000 }); 63 | 64 | // 当服务端返回的是gbk时可用这个参数, 避免得到乱码 65 | request('/api/v1/some/api', { charset: 'gbk' }); 66 | 67 | ``` -------------------------------------------------------------------------------- /packages/ucf-request/dist/ucf-request.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("axios")):"function"==typeof define&&define.amd?define(["axios"],t):"object"==typeof exports?exports["Ucf-request"]=t(require("axios")):e["Ucf-request"]=t(e.axios)}(window,function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(t,n){t.exports=e},function(e,t,n){"use strict";n.r(t);var r=n(0),o=n.n(r),u="",a=Math.random();t.default=function(e,t){t.start&&t.start();var n=Object.assign({},t.params,"get"==t.method.toLowerCase()?{r:Math.random()}:{});return o()({timeout:8e3,method:t.method,url:e,data:t.data,headers:{"X-Requested-With":"XMLHttpRequest","random-num":a,"x-xsrf-token":u},params:n}).then(function(e){t.end&&t.end();var n=e.headers["x-xsrf-token"];return n&&(u=n),new Promise(function(t,n){t(e.data)})}).catch(function(e){t.end&&t.end(),console.log(e);var n=e.response;if(n){var r=n.status,o=n.data.msg;switch(r){case 401:return console.log("RBAC鉴权失败!"+o),Promise.resolve(n);case 306:window.top.location.href="/wbalone/pages/login/login.html"}}return new Promise(function(e,t){t({code:-1,data:[],message:"request发生服务器 http ".concat(status," 请求错误!")})})})}}])}); -------------------------------------------------------------------------------- /packages/ucf-request/dist/ucf-request.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("axios")):"function"==typeof define&&define.amd?define(["axios"],t):"object"==typeof exports?exports["Ucf-request"]=t(require("axios")):e["Ucf-request"]=t(e.axios)}(window,function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(t,n){t.exports=e},function(e,t,n){"use strict";n.r(t);var r=n(0),o=n.n(r),u="",a=Math.random();t.default=function(e,t){t.start&&t.start();var n=Object.assign({},t.params,"get"==t.method.toLowerCase()?{r:Math.random()}:{});return o()({timeout:8e3,method:t.method,url:e,data:t.data,headers:{"X-Requested-With":"XMLHttpRequest","random-num":a,"x-xsrf-token":u},params:n}).then(function(e){t.end&&t.end();var n=e.headers["x-xsrf-token"];return n&&(u=n),new Promise(function(t,n){t(e.data)})}).catch(function(e){t.end&&t.end(),console.log(e);var n=e.response;if(n){var r=n.status,o=n.data.msg;switch(r){case 401:return console.log("RBAC鉴权失败!"+o),Promise.resolve(n);case 306:window.top.location.href="/wbalone/pages/login/login.html"}}return new Promise(function(e,t){t({code:-1,data:[],message:"request发生服务器 http ".concat(status," 请求错误!")})})})}}])}); -------------------------------------------------------------------------------- /packages/ucf-request/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _axios = _interopRequireDefault(require("axios")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | var x_xsrf_token = '', 13 | random_num = Math.random(); 14 | 15 | var noop = function noop() {}; 16 | 17 | var _default = function _default(url, options) { 18 | var startFunc = options['start'] || noop; 19 | var endFunc = options['end'] || noop; 20 | startFunc(); 21 | var params = Object.assign({}, options.params, options.method.toLowerCase() == 'get' ? { 22 | r: Math.random() 23 | } : {}); 24 | var defaultOptions = { 25 | timeout: 8000, 26 | method: options.method, 27 | url: url, 28 | data: options.data, 29 | headers: { 30 | 'X-Requested-With': 'XMLHttpRequest', 31 | 'random-num': random_num, 32 | 'x-xsrf-token': x_xsrf_token 33 | }, 34 | params: params 35 | }; 36 | return (0, _axios["default"])(Object.assign(defaultOptions, options)).then(function (res) { 37 | endFunc(); 38 | var inner_x_xsrf_token = res.headers['x-xsrf-token']; 39 | 40 | if (inner_x_xsrf_token) { 41 | x_xsrf_token = inner_x_xsrf_token; 42 | } 43 | 44 | return new Promise(function (resolve, reject) { 45 | resolve(res.data); 46 | }); 47 | })["catch"](function (err) { 48 | endFunc(); 49 | console.log(err); 50 | return new Promise(function (resolve, reject) { 51 | var errMsg = "request Error!"; 52 | reject({ 53 | code: -1, 54 | data: [], 55 | message: errMsg 56 | }); 57 | }); 58 | }); 59 | }; 60 | 61 | exports["default"] = _default; -------------------------------------------------------------------------------- /packages/ucf-request/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ucf-request", 3 | "version": "1.0.3", 4 | "description": "UCF网络请求库 - 基于axios封装, 旨在为开发者提供一个统一的api调用方式, 简化使用,错误处理等常用功能", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:iuap-design/ucf-web.git" 9 | }, 10 | "bugs": "https://github.com/iuap-design/ucf-web/issues", 11 | "scripts": { 12 | "prepublishOnly": "npm run build", 13 | "build": "rm -rf lib && babel src -d lib --no-comments", 14 | "pub": "npm publish", 15 | "pub-beta": "npm publish --tag beta", 16 | "bundle": "webpack && webpack -p --output-filename ucf-request.min.js" 17 | }, 18 | "author": { 19 | "name": "kvkens", 20 | "email": "kvkens@qq.com" 21 | }, 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@babel/cli": "^7.0.0", 25 | "@babel/core": "^7.0.0", 26 | "@babel/preset-env": "^7.0.0", 27 | "@babel/preset-react": "^7.0.0", 28 | "babel-core": "^7.0.0-0", 29 | "babel-loader": "^8.0.0", 30 | "prop-types": "^15.6.2", 31 | "webpack": "^4.17.2", 32 | "webpack-cli": "^3.1.0" 33 | }, 34 | "dependencies": { 35 | "axios": "^0.18.0", 36 | "mirrorx": "^0.2.12" 37 | }, 38 | "files": [ 39 | "lib/", 40 | "dist/", 41 | "README.md", 42 | "LICENSE" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /packages/ucf-request/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * request服务请求处理 3 | */ 4 | 5 | import axios from "axios"; 6 | 7 | //CSRF 8 | let x_xsrf_token = '', 9 | random_num = Math.random(); 10 | 11 | const noop = ()=>{} 12 | /** 13 | * 参数:url 请求的服务地址 14 | * 参数:options 请求参数选项,包括 method 方法类型、params GET参数、data POST参数 15 | */ 16 | export default (url, options) => { 17 | let startFunc = options['start']||noop; 18 | let endFunc = options['end']||noop; 19 | startFunc() 20 | let params = Object.assign({}, options.params, options.method.toLowerCase() == 'get' ? { 21 | r: Math.random() 22 | } : {}); 23 | let defaultOptions = { 24 | timeout: 8000, 25 | method: options.method, 26 | url: url, 27 | data: options.data, 28 | headers: { 29 | 'X-Requested-With': 'XMLHttpRequest', 30 | 'random-num': random_num, 31 | 'x-xsrf-token': x_xsrf_token, 32 | }, 33 | params, 34 | } 35 | 36 | return axios(Object.assign(defaultOptions,options)).then(function (res) { 37 | endFunc() 38 | let inner_x_xsrf_token = res.headers['x-xsrf-token'];//added by yany 39 | if (inner_x_xsrf_token) { 40 | x_xsrf_token = inner_x_xsrf_token; 41 | } 42 | return new Promise((resolve, reject) => { 43 | resolve(res.data); 44 | }); 45 | }).catch(function (err) { 46 | endFunc() 47 | console.log(err); 48 | // let res = err.response; 49 | // if (res) { 50 | // let { status, data: { msg } } = res; 51 | // switch (status) { 52 | // case 401: 53 | // console.log("RBAC鉴权失败!" + msg); 54 | // return Promise.resolve(res); 55 | // case 306: 56 | // window.top.location.href = '/wbalone/pages/login/login.html'; 57 | // break; 58 | // default: 59 | // } 60 | // } 61 | return new Promise((resolve, reject) => { 62 | let errMsg = `request Error!`; 63 | reject({ code: -1, data: [], message: errMsg }); 64 | }); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /packages/ucf-request/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | // mode: 'production', 5 | entry: './src/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | filename: 'ucf-request.js', 9 | library: 'Ucf-request', 10 | libraryTarget: 'umd' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | include: [ 17 | path.resolve(__dirname, 'src') 18 | ], 19 | loader: 'babel-loader' 20 | } 21 | ] 22 | }, 23 | externals: { 24 | 'react': 'React', 25 | 'react-dom': 'ReactDOM', 26 | 'prop-types': 'PropTypes', 27 | 'axios': 'axios', 28 | 'mirrorx': 'Mirror' 29 | } 30 | } -------------------------------------------------------------------------------- /packages/ucf-scripts/README.md: -------------------------------------------------------------------------------- 1 | # ucf-scripts 2 | 3 | [![npm version](https://img.shields.io/npm/v/ucf-scripts.svg)](https://www.npmjs.com/package/ucf-scripts) 4 | [![NPM downloads](http://img.shields.io/npm/dt/ucf-scripts.svg?style=flat)](https://npmjs.org/package/ucf-scripts) 5 | 6 | --- 7 | 8 | [![NPM](https://nodei.co/npm/ucf-scripts.png)](https://nodei.co/npm/ucf-scripts/) 9 | 10 | --- 11 | 12 | ## 介绍 13 | 14 | 集成了最新的技术栈包括`babel7,autoprefixer,less,postcss,webpack4`、高度封装、简化配置、无多余依赖、具有服务启动、开发调试、代理访问、数据模拟、构建资源、自动刷新功能。快速开发UCF微服务工程底层配套工具支撑,[详情文档](https://www.yuque.com/ucf-web/book/zfy8x1) 15 | 16 | 17 | 18 | ## 安装 19 | 20 | 21 | 工具可以依赖UCF项目工程通过`scripts`运行 22 | 23 | ## 使用 24 | 25 | 1. 通过`npm scripts`启动 26 | 27 | ```bash 28 | 29 | # 开发启动 30 | $ npm start 31 | 32 | # 开发构建 33 | $ npm run build 34 | ``` 35 | 内置已经集成ucf-scripts的启动 36 | 37 | ## 启动方式对比优劣 38 | 39 | 全局启动和项目内脚本启动区别: 40 | 41 | 启动方式 | 优点 | 缺点 42 | ---|---|--- 43 | 全局启动 | 无需根据项目一次次安装重复依赖npm包节省磁盘空间速度 | 不受项目内工具版本控制,会导致每个开发者环境不统一,出现未知版本错误等 44 | 脚本启动 | 无需管理全局环境变量、不污染全局变量、随时根据项目内版本更新、可控每一次版本 | 多次项目使用需要反复安装、占用磁盘空间大 45 | 46 | 47 | ## 项目配置文件说明 48 | 49 | UCF微服务前端工程核心配置文件只有一个`ucf.config.js`下面对配置文件说明: 50 | 51 | ```js 52 | module.exports = () => { 53 | return { 54 | // 启动所有模块,默认这个配置,速度慢的时候使用另外的配置 55 | // bootList: true, 56 | 57 | // 启动这两个模块,不启动调试,关闭构建 58 | bootList: [ 59 | "demo-app-org", 60 | //"demo-app-staff" 61 | ], 62 | // 代理的配置 63 | proxy: [ 64 | { 65 | enable: true, 66 | headers: { 67 | // 与下方url一致 68 | "Referer": "http://iuap-meger-demo.test.app.yyuap.com" 69 | }, 70 | //要代理访问的对方路由 71 | router: [ 72 | '/iuap' 73 | ], 74 | // pathRewrite: { 75 | // '^/api/old-path': '/api/new-path', // rewrite path 76 | // '^/api/remove/path': '/path' // remove base path 77 | // }, 78 | url: 'http://iuap-meger-demo.test.app.yyuap.com' 79 | } 80 | ], 81 | // 静态托管服务 82 | static: 'ucf-common/src/static', 83 | // 是否展开静态引用资源 84 | res_extra: true, 85 | // 构建资源是否产出SourceMap 86 | open_source_map: true, 87 | // 全局环境变量 88 | global_env: { 89 | GROBAL_HTTP_CTX: JSON.stringify("/iuap_demo"), 90 | }, 91 | // 别名配置 92 | alias: { 93 | //'ucf-apps': path.resolve(__dirname, 'ucf-apps/') 94 | }, 95 | // 构建排除指定包 96 | externals: { 97 | //'tinper-bee': 'TinperBee' 98 | }, 99 | // 调试服务需要运行的插件 100 | devPlugins: [], 101 | // 构建服务需要运行的插件 102 | buildPlugins: [] 103 | } 104 | } 105 | ``` 106 | 107 | ## 功能配置节点说明 108 | 109 | 配置项 | 说明 | 默认值 | 可选值 | 备注 110 | ---|---|---|---|--- 111 | bootList | 启动、构建入口配置,true表示所有模块全部启用,数组参数按需模块使用 | true | `true`,`['app-name','app-demo']` | 一般默认开启所有模块的调试和构建,低配置机器或者只需要开发一块模块的话可以选择性的去配置单独启动 112 | proxy | 开发调试阶段的代理服务配置 | [] | `enable:true` 是否有效代理,false表示关闭. `headers:{}` 设置代理请求的消息头. `router:['/iuap','wbalone']`. `url:'proxy.example.com'`. 本地请求代理对方服务器地址. `pathRewrite:{}`URL重写服务. `opts:{}` 如内置配置无法满足需求,需要单独设置原生配置 [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware#options). | 数组节点可以配置多条代理服务,通过`enable`来控制启用哪个,针对一些服务器校验头信息例如:`Referer`等就需要设置,其他常规的设置工具已经内置,代理路由`router`表示设置的几个路由访问后会代理到对方服务器上,`url`就是对方服务器地址 113 | global_env | 程序内公共变量 | null | 同webpack4 { key : value } | 接收K、V格式如:{GROBAL_HTTP_CTX: JSON.stringify("/iuap_demo")} 114 | alias | 别名 | null | 同webpack4 {key : value} | 接收K、V格式如:{'ucf-apps': path.resolve(__dirname, 'ucf-apps/')} 115 | externals | 排除指定的包用外部变量代理提升打包性能 | null | 同webpack4 { key : value } | 接收K、V格式如:{'tinper-bee': 'TinperBee'} 116 | loader | 内置加载器无法处理需要单独去设置处理 | [] | 同webpack4 loader | 117 | devPlugins | 开发环境加载的插件 | [] | 同webpack4 plugin | 开发阶段使用的插件 118 | buildPlugins | 生产环境加载的插件 | [] | 同webpack4 plugin | 生产阶段使用的插件 119 | open_source_map | 构建资源生产环境的时候产出sourceMap | true | true,false | - 120 | css | css loader的options | undefined | - | 具体参考https://www.npmjs.com/package/css-loader 121 | less | less loader的options | undefined | - | 具体参考https://www.npmjs.com/package/less-loader 122 | res_extra | 是否展开静态引用资源,用于打包处理字体、图片等资源产出,或者不使用展开资源会打包到css方便管理 | true | true,false | - 123 | static | 静态托管服务,不按需打包 | undefined | - | 脚手架内的任意文件夹即可,如:static : 'ucf-common/src/static' 124 | babel_presets | `babel`使用的 presets | undefined | - | 如:require.resolve('@babel/preset-react') 125 | babel_plugins | `babel`的插件 | undefined | 如:require.resolve("babel-plugin-import-bee") 126 | scan_root | 自定义文件夹作为扫描微应用入口,原则上是按照./自定义目录/*/src/app.js扫描 | undefined | - 127 | dist_root | 输出自定义文件夹 | undefined | - 128 | host | 自定义IP、域名启动 | undefined | 支持IPV4、IPV6、域名 129 | postcss_plugins | `PostCss` Plugin | undefined | - 130 | 131 | ## 自动开启浏览器 132 | 133 | 通过配置npm启动命令: 134 | 135 | ```js 136 | "scripts": { 137 | "start": "ucf-scripts start --homepage=demo-app-org", 138 | "build": "ucf-scripts build" 139 | } 140 | ``` 141 | 142 | ## 版本 143 | 144 | - `1.2.8` 增加limit参数,用于设置静态资源大于多少后进行base64转码 145 | - `1.2.7` 修复启动端口冲突的问题 146 | - `1.2.6` 增加less配置参数 147 | - `1.2.5` 增加publicPath={boolen}字段,用于解决需要进行按需加载优化的项目,开启该配置后 项目中资源依赖路径将使用 context 字段作为资源依赖公共路径,并且路径依赖由原来的相对路径变为 /开头的绝对路径 148 | - `1.2.4` 支持Less-Loader的JavaScript 149 | - `1.2.3` 增加 `postcss_plugins` 参数用于支持扩展PostCss Loader的插件机制 150 | - `1.2.2` 解决在使用了`context`后,静态资源地址不正确的问题以及静态资源根据开发环境不同加载对应的脚本 151 | - `1.2.1` 支持自定义域名、IP绑定参数`host`、对应文档更新 152 | - `1.2.0` 实现splitChunks参数暴露、版本号锁定 153 | - `1.1.9` 解决启动`autoprefixer`红色警告的问题 154 | - `1.1.8` 显示文件名和构建时间&新特性实现多级构建源码目录 155 | - `1.1.7` 修复启动或构建执行三次配置文件的问题,修复第一次传递错误环境参数值 156 | - `1.1.6` 增加自定义入口扫描`scan_root`、自定义输出目录`dist_root` 157 | - `1.1.5` 支持`babel`的`presets`、`plugin`参数,更新对应使用文档,`ucf.config`增加`babel_presets`、`babel_plugins` 158 | - `1.1.4` 升级`clean-webpack-plugin`、`get-port`,去除压缩插件`uglifyjs-webpack-plugin`使用内置压缩插件`terser-webpack-plugin` 159 | - `1.1.3` 解决`res_extra`参数的差异化,现在不管是`development`还是`production`都是一致的路径 160 | - `1.1.2` 完善`res_extra:true`性能优化,只针对构建上线打包的时候生效,拆分公共vendor、js、css 161 | - `1.1.1` 修复静态服务没有设置的错误 162 | - `1.1.0` 增加调试和打包的静态依赖资源展开参数`res_extra`,增加静态服务功能参数`static` 163 | - `1.0.9` 修复context不设置构建的时候没有html文件的问题 164 | - `1.0.8` 增加context参数 165 | - `1.0.7` 调整参数变化、输出目录调整ucf-publish、自动清理构建目录 166 | - `1.0.6` 增加portal平台开发环境支持 167 | - `1.0.5` 增加对CSS Loader配置支持 168 | - `1.0.4` 增加CSS Modules支持、自动打开浏览器命令行`--homepage` 169 | - `1.0.3` 增加注解支持 170 | - `1.0.2` 增加SourceMap参数支持 171 | - `1.0.1` 切换正式环境 172 | - `1.0.0` 完善开发服务、精简配置、容错处理 173 | - `0.0.x` 初步完成开发调试、构建、代理访问 -------------------------------------------------------------------------------- /packages/ucf-scripts/bin/ucf-scripts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("../src"); 4 | -------------------------------------------------------------------------------- /packages/ucf-scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ucf-scripts", 3 | "version": "1.2.16-beta.3", 4 | "description": "development and build services of UCF project", 5 | "main": "index.js", 6 | "bin": { 7 | "ucf-scripts": "bin/ucf-scripts", 8 | "us": "bin/ucf-scripts" 9 | }, 10 | "engines": { 11 | "node": ">= 8.9.0" 12 | }, 13 | "scripts": {}, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+ssh://git@github.com/iuap-design/ucf-web.git" 17 | }, 18 | "keywords": [ 19 | "server", 20 | "build", 21 | "webpack", 22 | "tool", 23 | "ucf", 24 | "yonyou", 25 | "fe" 26 | ], 27 | "author": "Kvkens (yueming@yonyou.com)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/iuap-design/ucf-web/issues" 31 | }, 32 | "homepage": "https://github.com/iuap-design/ucf-web/tree/master/packages/ucf-scripts#readme", 33 | "dependencies": { 34 | "@babel/core": "7.5.5", 35 | "@babel/plugin-proposal-class-properties": "7.5.5", 36 | "@babel/plugin-proposal-decorators": "7.4.4", 37 | "@babel/plugin-transform-runtime": "7.5.5", 38 | "@babel/preset-env": "7.5.5", 39 | "@babel/preset-react": "7.0.0", 40 | "autoprefixer": "9.6.0", 41 | "babel-loader": "8.0.6", 42 | "babel-plugin-dynamic-import-webpack": "1.1.0", 43 | "chalk": "2.4.2", 44 | "clean-webpack-plugin": "3.0.0", 45 | "css-loader": "3.5.2", 46 | "cssnano": "4.1.10", 47 | "express": "4.17.1", 48 | "file-loader": "4.2.0", 49 | "fs-extra": "8.0.1", 50 | "get-port": "5.0.0", 51 | "glob": "7.1.4", 52 | "html-webpack-plugin": "3.2.0", 53 | "http-proxy-middleware": "0.19.1", 54 | "ip": "1.1.5", 55 | "less": "3.9.0", 56 | "less-loader": "5.0.0", 57 | "mini-css-extract-plugin": "0.8.0", 58 | "minimist": "1.2.5", 59 | "open-browser-webpack-plugin": "0.0.5", 60 | "optimize-css-assets-webpack-plugin": "5.0.3", 61 | "postcss-flexbugs-fixes": "4.1.0", 62 | "postcss-loader": "3.0.0", 63 | "style-loader": "0.23.1", 64 | "terser-webpack-plugin": "1.4.1", 65 | "url-loader": "2.1.0", 66 | "webpack": "4.39.2", 67 | "webpack-dev-middleware": "3.7.0", 68 | "webpack-hot-middleware": "2.25.0", 69 | "webpack-merge": "4.2.1" 70 | }, 71 | "devDependencies": { 72 | "@babel/plugin-proposal-object-rest-spread": "^7.12.1" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/base.config.js: -------------------------------------------------------------------------------- 1 | /* Base Webpack4 config 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-21 13:02:27 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-10-17 18:57:29 6 | */ 7 | 8 | const path = require('path'); 9 | const webpack = require('webpack'); 10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 11 | const argv = require("minimist")(process.argv.slice(2)); 12 | const commands = argv; 13 | const util = require('./util'); 14 | const cfg = util.getUcfConfig(commands._); 15 | 16 | let _context = ""; 17 | // 处理资源展开 18 | let limit = cfg.res_extra ? (cfg.limit ? cfg.limit : 8196) : 81960000; 19 | // 处理上下文路径 20 | if (cfg.context) { 21 | _context = `${cfg.context}/`; 22 | } 23 | // 处理babel插件兼容 24 | cfg.babel_plugins == undefined ? cfg.babel_plugins = [] : cfg.babel_plugins; 25 | // 处理babel preset兼容 26 | cfg.babel_include == undefined ? cfg.babel_include = [] : cfg.babel_include; 27 | cfg.babel_presets == undefined ? cfg.babel_presets = [] : cfg.babel_presets; 28 | // 接收postcss插件 29 | cfg.postcss_plugins == undefined ? cfg.postcss_plugins = [] : cfg.postcss_plugins; 30 | // 扫描微应用入口规则 31 | const scan_root = cfg.scan_root ? cfg.scan_root : 'ucf-apps'; 32 | // 输出资源的文件夹 33 | const dist_root = cfg.dist_root ? cfg.dist_root : 'ucf-publish'; 34 | 35 | const config = { 36 | output: { 37 | path: path.resolve('.', dist_root, _context), 38 | filename: '[name].js', 39 | chunkFilename: '[name].js', 40 | //TODO ucf.config 中增加publicPath参数,当publicPath为true时, 使用 context 参数作为生成 publicPath的值, 并且start.js中引入 41 | //TODO 项目中的产出资源依赖会由原来的 ../../${_context}${chunkName} 路径会变成 /${_context}${chunkName} 42 | publicPath: cfg.publicPath ? '/' + _context : undefined 43 | }, 44 | module: { 45 | rules: [{ 46 | test: /\.js[x]?$/, 47 | // exclude: /(node_modules)/, 48 | include: [ 49 | path.resolve('.', 'ucf-apps'), 50 | path.resolve('.', 'ucf-common'), 51 | path.resolve('.', scan_root), 52 | // path.resolve('.', 'node_modules/@babel/register'), 53 | ...cfg.babel_include 54 | ], 55 | use: [{ 56 | loader: require.resolve('babel-loader'), 57 | options: { 58 | babelrc: false, 59 | presets: [ 60 | require.resolve('@babel/preset-env'), 61 | require.resolve('@babel/preset-react'), 62 | ...cfg.babel_presets 63 | ], 64 | plugins: [ 65 | [require.resolve('@babel/plugin-transform-runtime'), { 66 | 'corejs': false, 67 | 'helpers': true, 68 | 'regenerator': true, 69 | 'useESModules': false 70 | }], 71 | [require.resolve('babel-plugin-dynamic-import-webpack'), { 72 | 'helpers': false, 73 | 'polyfill': true, 74 | 'regenerator': true 75 | }], 76 | [require.resolve('@babel/plugin-proposal-decorators'), { 77 | "legacy": true 78 | }], 79 | [require.resolve('@babel/plugin-proposal-class-properties'), { 80 | "loose": true 81 | }], 82 | require.resolve('@babel/plugin-proposal-object-rest-spread'), 83 | ...cfg.babel_plugins 84 | ] 85 | } 86 | }] 87 | }, { 88 | test: /\.(le|c)ss$/, 89 | use: [{ 90 | loader: MiniCssExtractPlugin.loader, 91 | options: { 92 | publicPath: './' 93 | } 94 | }, { 95 | loader: require.resolve('css-loader'), 96 | options: cfg.css 97 | }, { 98 | loader: require.resolve('postcss-loader'), 99 | options: { 100 | ident: 'postcss', 101 | plugins: (loader) => [require('autoprefixer')({ 102 | overrideBrowserslist: ['last 2 Chrome versions', 'last 2 Firefox versions', 'Safari >= 7', 'ie > 10'] 103 | }), 104 | require('postcss-flexbugs-fixes'), 105 | ...cfg.postcss_plugins 106 | ] 107 | } 108 | }, { 109 | loader: require.resolve('less-loader'), 110 | options: { 111 | javascriptEnabled: true, 112 | ...cfg.less 113 | } 114 | }] 115 | }, { 116 | test: /\.(png|jpg|jpeg|gif)(\?.+)?$/, 117 | use: [{ 118 | loader: require.resolve('url-loader'), 119 | options: { 120 | limit, 121 | name: '[name].[hash:8].[ext]', 122 | //TODO 当publicPath=true 因为start.js webpack-dev-middleware 中配置了publicPath, 所以此处产出路径不在包含 context 123 | outputPath: commands._[0] === 'start' && !cfg.publicPath ? `${_context}assets/images/` : 'assets/images/', 124 | publicPath: `/${_context}assets/images` 125 | } 126 | }] 127 | }, { 128 | test: /\.(woff|woff2|eot|ttf|svg|svgz|otf)$/, 129 | use: [{ 130 | loader: require.resolve('url-loader'), 131 | options: { 132 | limit, 133 | name: '[name].[hash:8].[ext]', 134 | //TODO 当publicPath=true 因为start.js webpack-dev-middleware 中配置了publicPath, 所以此处产出路径不在包含 context 135 | outputPath: commands._[0] === 'start' && !cfg.publicPath ? `${_context}assets/fonts/` : 'assets/fonts/', 136 | publicPath: `/${_context}assets/fonts` 137 | } 138 | }] 139 | }] 140 | }, 141 | resolve: { 142 | extensions: [ 143 | ".jsx", ".js", ".less", ".css", ".json" 144 | ], 145 | alias: { 146 | 'ucf-apps': path.resolve('.', 'ucf-apps/'), 147 | 'ucf-common': path.resolve('.', 'ucf-common/src/'), 148 | components: path.resolve('.', 'ucf-common/src/components/'), 149 | static: path.resolve('.', 'ucf-common/src/static/'), 150 | utils: path.resolve('.', 'ucf-common/src/utils/') 151 | } 152 | }, 153 | plugins: [ 154 | new webpack.BannerPlugin({ 155 | banner: `File:[file] Date:${new Date()}` 156 | }), 157 | new MiniCssExtractPlugin({ 158 | filename: '[name].css', 159 | chunkFilename: '[name].css' 160 | }), 161 | new webpack.ProgressPlugin() 162 | ] 163 | }; 164 | 165 | module.exports = config; -------------------------------------------------------------------------------- /packages/ucf-scripts/src/build.config.js: -------------------------------------------------------------------------------- 1 | /* Build Webpack4 config 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-22 14:57:43 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-06-21 16:39:15 6 | */ 7 | 8 | const glob = require('glob'); 9 | const path = require('path'); 10 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 11 | const webpack = require('webpack'); 12 | const TerserPlugin = require('terser-webpack-plugin'); 13 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 14 | const merge = require('webpack-merge'); 15 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 16 | const argv = require("minimist")(process.argv.slice(2)); 17 | const commands = argv; 18 | const util = require('./util'); 19 | const base = require('./base.config'); 20 | const cfg = util.getUcfConfig(commands._); 21 | 22 | 23 | //当前应用模式 24 | //入口集合 25 | const entries = {}; 26 | //HTML插件 27 | const HtmlPlugin = []; 28 | //启动器控制 29 | const _bootList = new Set(); 30 | // 启动器 31 | const bootList = cfg.bootList ? cfg.bootList : true; 32 | // 扫描微应用入口规则 33 | const scan_root = cfg.scan_root ? cfg.scan_root : 'ucf-apps'; 34 | 35 | const HtmlPluginConf = cfg.HtmlPluginConf || {}; 36 | 37 | 38 | const output = cfg.hasHash ? { 39 | ...base.output, 40 | filename: '[name].[hash].js', 41 | chunkFilename: '[name].[hash].js', 42 | } : base.output; 43 | 44 | //构造模块加载入口以及html出口 45 | glob.sync(`./${scan_root}/**/src/app.js`).forEach(_path => { 46 | //模块名 47 | const module = `${_path.split(`./${scan_root}/`)[1].split('/src/app.js')[0]}`; 48 | const chunk = `${module}/index`; 49 | 50 | const targetDir = _path.split('/app.js')[0] 51 | // 兼容其他类型的模版配置,如:ejs 52 | const templateType = cfg.templateType ? cfg.templateType : 'html' 53 | const htmlConf = Object.assign({ 54 | filename: `${chunk}.html`, 55 | template: `${targetDir}/index.${templateType}`, 56 | inject: 'body', 57 | chunks: ['vendor', chunk], 58 | hash: true, 59 | static_url: cfg.static_url ? cfg.static_url : '' 60 | },HtmlPluginConf) ; 61 | 62 | //处理启动器逻辑 63 | if (bootList && typeof bootList == 'boolean') { 64 | entries[chunk] = _path; 65 | HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); 66 | } else if (Array.isArray(bootList) && bootList.length > 0) { 67 | bootList.forEach(item => { 68 | _bootList.add(item); 69 | }); 70 | if (_bootList.has(module)) { 71 | entries[chunk] = _path; 72 | HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); 73 | } 74 | } 75 | }); 76 | let splitChunks = { 77 | cacheGroups: { 78 | default: { 79 | minChunks: 2, 80 | priority: -20, 81 | reuseExistingChunk: true, 82 | }, 83 | //打包重复出现的代码 84 | vendor: { 85 | chunks: 'initial', 86 | minChunks: 2, 87 | maxInitialRequests: 5, // The default limit is too small to showcase the effect 88 | minSize: 0, // This is example is too small to create commons chunks 89 | name: 'vendor' 90 | }, 91 | //打包第三方类库 92 | commons: { 93 | name: 'vendor', 94 | chunks: 'initial', 95 | minChunks: Infinity 96 | } 97 | } 98 | } 99 | //默认的配置用于merge操作 100 | const config = { 101 | output, 102 | mode: 'production', 103 | devtool: 'cheap-module-source-map', 104 | externals: cfg.externals, 105 | resolve: { 106 | alias: cfg.alias 107 | }, 108 | module: { 109 | rules: cfg.loader 110 | }, 111 | optimization: { 112 | // 不使用内部压缩参数关闭 113 | minimize: false 114 | }, 115 | plugins: [ 116 | new CleanWebpackPlugin(), 117 | new OptimizeCSSAssetsPlugin({ 118 | cssProcessorOptions: { 119 | safe: true, 120 | mergeLonghand: false, 121 | discardComments: { removeAll: true } 122 | }, 123 | canPrint: true 124 | }), 125 | new TerserPlugin({ 126 | // test: /\.js(\?.*)?$/i, 127 | cache: true, 128 | parallel: true, 129 | sourceMap: cfg.open_source_map == undefined ? true : cfg.open_source_map 130 | // include:'vendor', 131 | // exclude:'vendor', 132 | }), 133 | ...HtmlPlugin 134 | ] 135 | } 136 | // 入口 137 | config.entry = entries; 138 | 139 | // 环境变量注入 140 | cfg.global_env && (config.plugins = config.plugins.concat(new webpack.DefinePlugin(cfg.global_env))); 141 | // 传入插件设置 142 | cfg.buildPlugins && (config.plugins = config.plugins.concat(cfg.buildPlugins)); 143 | // 是否启用优化资源单独生成文件 144 | cfg.res_extra && (cfg.splitChunks ? config.optimization['splitChunks'] = cfg.splitChunks : config.optimization['splitChunks'] = splitChunks); 145 | module.exports = merge(base, config); -------------------------------------------------------------------------------- /packages/ucf-scripts/src/build.js: -------------------------------------------------------------------------------- 1 | /* UCF Build Services 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-22 09:43:39 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-01-22 09:43:42 6 | */ 7 | 8 | const chalk = require('chalk'); 9 | const webpack = require('webpack'); 10 | const util = require('./util'); 11 | const webpackConfig = require('./build.config'); 12 | const compiler = webpack(webpackConfig); 13 | 14 | 15 | /** 16 | * build 主程序 17 | */ 18 | build = () => { 19 | console.log(); 20 | console.log(chalk.green(`--------------------------------------------`)); 21 | console.log(chalk.yellow(`\t 🚀 UCF Build Server`)); 22 | console.log(chalk.green(`\t [Build Version] : 🏅 ${util.getPkg().version}`)); 23 | console.log(); 24 | console.log(chalk.green(`\t 💪 Good Luck Please Wait ☃️`)); 25 | console.log(chalk.green(`--------------------------------------------`)); 26 | console.log(); 27 | compiler.run((err, stats) => { 28 | if (!err) { 29 | console.log('\n' + stats.toString({ 30 | hash: false, 31 | chunks: false, 32 | children: false, 33 | colors: true 34 | })); 35 | } else { 36 | console.log(chalk.red(err)); 37 | } 38 | }); 39 | } 40 | //启动构建 41 | module.exports = { 42 | plugin: () => { 43 | build(); 44 | } 45 | } -------------------------------------------------------------------------------- /packages/ucf-scripts/src/index.js: -------------------------------------------------------------------------------- 1 | /* ucf-scripts 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-21 13:02:27 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-05-25 14:28:58 6 | * e.g: 1. ucf-scripts start 7 | * 2. ucf-scripts build 8 | * 3. install "ucf-scripts" : "latest" 9 | */ 10 | 11 | const argv = require("minimist")(process.argv.slice(2)); 12 | const commands = argv._; 13 | 14 | try { 15 | require(`./${commands[0]}`).plugin(); 16 | } catch (error) { 17 | console.error(error); 18 | console.log('bad command \neg : ucf-scripts [start][build]') 19 | } 20 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/start.config.js: -------------------------------------------------------------------------------- 1 | /* Start Webpack4 config 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-21 13:02:27 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-05-10 10:45:56 6 | */ 7 | 8 | const glob = require('glob'); 9 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 10 | const webpack = require('webpack'); 11 | const merge = require('webpack-merge'); 12 | const argv = require("minimist")(process.argv.slice(2)); 13 | const commands = argv; 14 | const util = require('./util'); 15 | const base = require('./base.config'); 16 | const cfg = util.getUcfConfig(commands._); 17 | 18 | 19 | //当前应用模式 20 | //入口集合 21 | const entries = {}; 22 | //HTML插件 23 | const HtmlPlugin = []; 24 | //启动器控制 25 | const _bootList = new Set(); 26 | // 启动器 27 | const bootList = cfg.bootList ? cfg.bootList : true; 28 | // 扫描微应用入口规则 29 | const scan_root = cfg.scan_root ? cfg.scan_root : 'ucf-apps'; 30 | 31 | const HtmlPluginConf = cfg.HtmlPluginConf || {}; 32 | 33 | //加载本地开发环境的Portal 34 | glob.sync('./ucf-common/src/portal/src/app.js').forEach(_path => { 35 | entries['index'] = './ucf-common/src/portal/src/app.js'; 36 | const htmlConf = { 37 | filename: `index.html`, 38 | template: './ucf-common/src/portal/src/index.html', 39 | inject: 'body', 40 | chunks: ['index'], 41 | hash: true 42 | }; 43 | HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); 44 | }); 45 | 46 | 47 | //构造模块加载入口以及html出口 48 | glob.sync(`./${scan_root}/**/src/app.js`).forEach(_path => { 49 | let _context = ""; 50 | //TODO 当publicPath=true 因为start.js webpack-dev-middleware 中配置了publicPath, 所以此处产出路径不在包含 context 51 | if (!cfg.publicPath && cfg.context) { 52 | _context = `${cfg.context}/`; 53 | } 54 | //模块名 55 | const module = `${_path.split(`./${scan_root}/`)[1].split('/src/app.js')[0]}`; 56 | const chunk = `${_context}${module}/index`; 57 | 58 | const targetDir = _path.split('/app.js')[0] 59 | // 兼容其他类型的模版配置,如:ejs 60 | const templateType = cfg.templateType ? cfg.templateType : 'html' 61 | const htmlConf = Object.assign({ 62 | filename: `${chunk}.html`, 63 | template: `${targetDir}/index.${templateType}`, 64 | inject: 'body', 65 | chunks: [chunk], 66 | hash: true, 67 | static_url: cfg.static_url ? cfg.static_url : '' 68 | },HtmlPluginConf); 69 | //处理启动器逻辑 70 | if (bootList && typeof bootList == 'boolean') { 71 | entries[chunk] = [_path, require.resolve('./webpack-hot-middleware/client')]; 72 | HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); 73 | } else if (Array.isArray(bootList) && bootList.length > 0) { 74 | bootList.forEach(item => { 75 | _bootList.add(item); 76 | }); 77 | if (_bootList.has(module)) { 78 | entries[chunk] = [_path, require.resolve('./webpack-hot-middleware/client')]; 79 | HtmlPlugin.push(new HtmlWebPackPlugin(htmlConf)); 80 | } 81 | } 82 | }); 83 | 84 | //默认的配置用于merge操作 85 | const config = { 86 | devtool: 'source-map', 87 | mode: 'development', 88 | externals: cfg.externals, 89 | resolve: { 90 | alias: cfg.alias 91 | }, 92 | module: { 93 | rules: cfg.loader 94 | }, 95 | plugins: [ 96 | new webpack.HotModuleReplacementPlugin(), 97 | ...HtmlPlugin 98 | ] 99 | } 100 | //入口 101 | config.entry = entries; 102 | 103 | //环境变量注入 104 | cfg.global_env && (config.plugins = config.plugins.concat(new webpack.DefinePlugin(cfg.global_env))); 105 | //传入插件设置 106 | cfg.devPlugins && (config.plugins = config.plugins.concat(cfg.devPlugins)); 107 | 108 | 109 | module.exports = merge(base, config); -------------------------------------------------------------------------------- /packages/ucf-scripts/src/start.js: -------------------------------------------------------------------------------- 1 | /* UCF Start Services 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-21 13:02:27 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-05-10 10:46:02 6 | */ 7 | 8 | const path = require('path'); 9 | const chalk = require('chalk'); 10 | const argv = require("minimist")(process.argv.slice(2)); 11 | const commands = argv; 12 | const express = require('express'); 13 | const app = new express(); 14 | const webpack = require('webpack'); 15 | const proxy = require('http-proxy-middleware'); 16 | const OpenBrowserPlugin = require("open-browser-webpack-plugin"); 17 | const devMiddleware = require('webpack-dev-middleware'); 18 | const hotMiddleware = require('webpack-hot-middleware'); 19 | const ip = require('ip'); 20 | const getPort = require('get-port'); 21 | const util = require('./util'); 22 | const webpackConfig = require('./start.config'); 23 | const cfg = util.getUcfConfig(commands._); 24 | const compiler = webpack(webpackConfig); 25 | 26 | let host = cfg.host ? cfg.host : null; 27 | let browserHost = cfg.host ? cfg.host : '127.0.0.1'; 28 | 29 | /** 30 | * server 主程序 31 | */ 32 | server = opt => { 33 | // 判断是否加载默认页面打开浏览器 34 | if (commands.homepage) { 35 | compiler.apply(new OpenBrowserPlugin({ 36 | url: `http://${browserHost}:${opt.port}/${commands.homepage || ''}` 37 | })); 38 | } 39 | // 静态编译 40 | const instance = devMiddleware(compiler, { 41 | //TODO 与webpackConfig中保持一致 42 | publicPath: webpackConfig.output.publicPath, 43 | logTime: true, 44 | logLevel: commands.logLevel || "info", 45 | headers: { 46 | 'Access-Control-Allow-Origin': '*', 47 | 'Ucf-Server': util.getPkg().version 48 | }, 49 | stats: { 50 | colors: true, 51 | hash: false, 52 | children: false, 53 | chunks: false 54 | } 55 | }); 56 | // 静态资源托管 57 | cfg.static && app.use((cfg.context == '' || cfg.context == undefined) ? '' : `/${cfg.context}`, express.static(path.resolve(".", cfg.static))); 58 | // 加载实例 59 | app.use(instance); 60 | // 热更新 61 | app.use(hotMiddleware(compiler)); 62 | 63 | // 加载代理插件 64 | // 处理proxy数组情况 65 | cfg.proxy && cfg.proxy.forEach(function (element) { 66 | if (element.enable) {// 代理开启 67 | // 默认配置项 68 | let proxyOpt = { 69 | target: element.url, 70 | logLevel: "debug", 71 | changeOrigin: true, 72 | pathRewrite: Object.assign({}, element.pathRewrite), 73 | headers: (typeof element.headers !== 'undefined' ? element.headers : {}), 74 | onProxyRes: function (proxyRes) { 75 | proxyRes.headers["Ucf-Proxy"] = "success"; 76 | } 77 | } 78 | app.use(element.router, proxy(element.opts || proxyOpt)); 79 | console.log(chalk.green(`[proxy] : ${element.router} to ${element.url}`)); 80 | } 81 | }); 82 | // 运行调试服务 83 | app.listen(opt.port, host, () => { 84 | console.log(); 85 | console.log(chalk.green(`----------------------------------------------------`)); 86 | console.log(chalk.yellow(`\t 🚀 UCF Develop Server`)); 87 | console.log(chalk.green(`\t [Server Version]: 🏅 ${util.getPkg().version}`)); 88 | console.log(chalk.green(`\t [Local] : 🏠 http://${browserHost}:${opt.port}`)); 89 | console.log(chalk.green(`\t [Lan] : 📡 http://${opt.ip}:${opt.port}`)); 90 | console.log(chalk.green(`----------------------------------------------------`)); 91 | console.log(); 92 | }); 93 | } 94 | 95 | // 插件启动 96 | module.exports = { 97 | // 主程序ucf调用插件Context 98 | plugin: () => { 99 | // 设置默认端口 100 | // 检测是否被占用,更换端口,启动调试服务 101 | getPort({ 102 | port: commands.port || 3000 103 | }).then(port => { 104 | // 启动服务 105 | server({ 106 | port, 107 | ip: ip.address() 108 | }); 109 | }); 110 | } 111 | } -------------------------------------------------------------------------------- /packages/ucf-scripts/src/util.js: -------------------------------------------------------------------------------- 1 | /* util 2 | * @Author: Kvkens(yueming@yonyou.com) 3 | * @Date: 2019-01-21 12:59:30 4 | * @Last Modified by: Kvkens 5 | * @Last Modified time: 2019-05-10 10:16:46 6 | */ 7 | 8 | const path = require("path"); 9 | const chalk = require("chalk"); 10 | const fse = require('fs-extra'); 11 | let config = null; 12 | 13 | /** 14 | * 获得当前运行路径 15 | * @param {string} file 获得文件路径 16 | */ 17 | exports.getRunPath = (file) => { 18 | return path.resolve(".", file); 19 | } 20 | /** 21 | * 获得uba.config 22 | */ 23 | exports.getUcfConfig = (cmd) => { 24 | try { 25 | let isUcfConfig = fse.pathExistsSync(this.getRunPath("ucf.config.js")); 26 | // 缓存配置文件,防止二次修改无效 27 | if (config == null) { 28 | // 兼容mtl.config 29 | config = require(this.getRunPath(isUcfConfig ? 'ucf.config.js' : 'mtl.config.js'))(cmd[0] == 'start' ? 'development' : 'production', cmd); 30 | } 31 | return config; 32 | } catch (error) { 33 | this.errorLog(error, 'The "ucf.config.js" configuration file was not found', true); 34 | process.exit(0); 35 | } 36 | } 37 | 38 | /** 39 | * 获取package.json 40 | */ 41 | exports.getPkg = () => { 42 | return require("../package.json"); 43 | } 44 | 45 | /** 46 | * 错误处理 47 | * @param {*} err 错误对象 48 | * @param {*} msg 描述文字 49 | * @param {*} statck 是否显示错误栈 50 | */ 51 | exports.errorLog = (err, msg, statck) => { 52 | console.log(chalk.bold.red(`>> Error Message:`)); 53 | console.log(chalk.cyan(err.message)); 54 | msg && console.log(chalk.yellow(`[message] ${msg}`)); 55 | console.log(chalk.bold.red(`>> End`)); 56 | statck && console.log() 57 | statck && console.log(chalk.bold.red(`>> Stack Message:`)); 58 | statck && console.log(chalk.cyan(err.stack)); 59 | statck && console.log(chalk.bold.red(`>> End`)); 60 | } -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/README.md: -------------------------------------------------------------------------------- 1 | # Webpack Hot Middleware 2 | 3 | Webpack hot reloading using only [webpack-dev-middleware](https://webpack.js.org/guides/development/#webpack-dev-middleware). This allows you to add hot reloading into an existing server without [webpack-dev-server](https://webpack.js.org/configuration/dev-server/). 4 | 5 | This module is **only** concerned with the mechanisms to connect a browser client to a webpack server & receive updates. It will subscribe to changes from the server and execute those changes using [webpack's HMR API](https://webpack.js.org/concepts/hot-module-replacement/). Actually making your application capable of using hot reloading to make seamless changes is out of scope, and usually handled by another library. 6 | 7 | If you're using React then some common options are [react-transform-hmr](https://github.com/gaearon/react-transform-hmr/) and [react-hot-loader](https://github.com/gaearon/react-hot-loader). 8 | 9 | [![npm version](https://img.shields.io/npm/v/webpack-hot-middleware.svg)](https://www.npmjs.com/package/webpack-hot-middleware) [![CircleCI](https://circleci.com/gh/webpack-contrib/webpack-hot-middleware/tree/master.svg?style=svg)](https://circleci.com/gh/webpack-contrib/webpack-hot-middleware/tree/master)[![codecov](https://codecov.io/gh/webpack-contrib/webpack-hot-middleware/branch/master/graph/badge.svg)](https://codecov.io/gh/webpack-contrib/webpack-hot-middleware)![MIT Licensed](https://img.shields.io/npm/l/webpack-hot-middleware.svg) 10 | 11 | ## Installation & Usage 12 | 13 | See [example/](./example/) for an example of usage. 14 | 15 | First, install the npm module. 16 | 17 | ```sh 18 | npm install --save-dev webpack-hot-middleware 19 | ``` 20 | 21 | Next, enable hot reloading in your webpack config: 22 | 23 | 1. Add the following plugins to the `plugins` array: 24 | ```js 25 | plugins: [ 26 | // OccurrenceOrderPlugin is needed for webpack 1.x only 27 | new webpack.optimize.OccurrenceOrderPlugin(), 28 | new webpack.HotModuleReplacementPlugin(), 29 | // Use NoErrorsPlugin for webpack 1.x 30 | new webpack.NoEmitOnErrorsPlugin() 31 | ] 32 | ``` 33 | 34 | Occurence ensures consistent build hashes, hot module replacement is 35 | somewhat self-explanatory, no errors is used to handle errors more cleanly. 36 | 37 | 3. Add `'webpack-hot-middleware/client'` into the `entry` array. 38 | This connects to the server to receive notifications when the bundle 39 | rebuilds and then updates your client bundle accordingly. 40 | 41 | Now add the middleware into your server: 42 | 43 | 1. Add `webpack-dev-middleware` the usual way 44 | ```js 45 | var webpack = require('webpack'); 46 | var webpackConfig = require('./webpack.config'); 47 | var compiler = webpack(webpackConfig); 48 | 49 | app.use(require("webpack-dev-middleware")(compiler, { 50 | noInfo: true, publicPath: webpackConfig.output.publicPath 51 | })); 52 | ``` 53 | 54 | 2. Add `webpack-hot-middleware` attached to the same compiler instance 55 | ```js 56 | app.use(require("webpack-hot-middleware")(compiler)); 57 | ``` 58 | 59 | And you're all set! 60 | 61 | ## Changelog 62 | 63 | ### 2.0.0 64 | 65 | **Breaking Change** 66 | 67 | As of version 2.0.0, all client functionality has been rolled into this module. This means that you should remove any reference to `webpack/hot/dev-server` or `webpack/hot/only-dev-server` from your webpack config. Instead, use the `reload` config option to control this behaviour. 68 | 69 | This was done to allow full control over the client receiving updates, which is now able to output full module names in the console when applying changes. 70 | 71 | ## Documentation 72 | 73 | More to come soon, you'll have to mostly rely on the example for now. 74 | 75 | ### Config 76 | 77 | #### Client 78 | 79 | Configuration options can be passed to the client by adding querystring parameters to the path in the webpack config. 80 | 81 | ```js 82 | 'webpack-hot-middleware/client?path=/__what&timeout=2000&overlay=false' 83 | ``` 84 | 85 | * **path** - The path which the middleware is serving the event stream on 86 | * **name** - Bundle name, specifically for multi-compiler mode 87 | * **timeout** - The time to wait after a disconnection before attempting to reconnect 88 | * **overlay** - Set to `false` to disable the DOM-based client-side overlay. 89 | * **reload** - Set to `true` to auto-reload the page when webpack gets stuck. 90 | * **noInfo** - Set to `true` to disable informational console logging. 91 | * **quiet** - Set to `true` to disable all console logging. 92 | * **dynamicPublicPath** - Set to `true` to use webpack `publicPath` as prefix of `path`. (We can set `__webpack_public_path__` dynamically at runtime in the entry point, see note of [output.publicPath](https://webpack.js.org/configuration/output/#output-publicpath)) 93 | * **autoConnect** - Set to `false` to use to prevent a connection being automatically opened from the client to the webpack back-end - ideal if you need to modify the options using the `setOptionsAndConnect` function 94 | * **ansiColors** - An object to customize the client overlay colors as mentioned in the [ansi-html](https://github.com/Tjatse/ansi-html/blob/99ec49e431c70af6275b3c4e00c7be34be51753c/README.md#set-colors) package. 95 | * **overlayStyles** - An object to let you override or add new inline styles to the client overlay div. 96 | * **overlayWarnings** - Set to `true` to enable client overlay on warnings in addition to errors. 97 | 98 | > Note: 99 | > Since the `ansiColors` and `overlayStyles` options are passed via query string, you'll need to uri encode your stringified options like below: 100 | 101 | ```js 102 | var ansiColors = { 103 | red: '00FF00' // note the lack of "#" 104 | }; 105 | var overlayStyles = { 106 | color: '#FF0000' // note the inclusion of "#" (these options would be the equivalent of div.style[option] = value) 107 | }; 108 | var hotMiddlewareScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true&ansiColors=' + encodeURIComponent(JSON.stringify(ansiColors)) + '&overlayStyles=' + encodeURIComponent(JSON.stringify(overlayStyles)); 109 | ``` 110 | 111 | #### Middleware 112 | 113 | Configuration options can be passed to the middleware by passing a second argument. 114 | 115 | ```js 116 | app.use(require("webpack-hot-middleware")(compiler, { 117 | log: false, 118 | path: "/__what", 119 | heartbeat: 2000 120 | })); 121 | ``` 122 | 123 | * **log** - A function used to log lines, pass `false` to disable. Defaults to `console.log` 124 | * **path** - The path which the middleware will serve the event stream on, must match the client setting 125 | * **heartbeat** - How often to send heartbeat updates to the client to keep the connection alive. Should be less than the client's `timeout` setting - usually set to half its value. 126 | 127 | ## How it Works 128 | 129 | The middleware installs itself as a webpack plugin, and listens for compiler events. 130 | 131 | Each connected client gets a [Server Sent Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/) connection, the server will publish notifications to connected clients on compiler events. 132 | 133 | When the client receives a message, it will check to see if the local code is up to date. If it isn't up to date, it will trigger webpack hot module reloading. 134 | 135 | ### Multi-compiler mode 136 | 137 | If you're using multi-compiler mode (exporting an array of config in `webpack.config.js`), set `name` parameters to make sure bundles don't process each other's updates. For example: 138 | 139 | ``` 140 | // webpack.config.js 141 | module.exports = [ 142 | { 143 | name: 'mobile', 144 | entry: { 145 | vendor: 'vendor.js', 146 | main: ['webpack-hot-middleware/client?name=mobile', 'mobile.js'] 147 | } 148 | }, 149 | { 150 | name: 'desktop', 151 | entry: { 152 | vendor: 'vendor.js', 153 | main: ['webpack-hot-middleware/client?name=desktop', 'desktop.js'] 154 | } 155 | } 156 | ] 157 | ``` 158 | 159 | ## Other Frameworks 160 | 161 | ### Hapi 162 | 163 | Use the [hapi-webpack-plugin](https://www.npmjs.com/package/hapi-webpack-plugin). 164 | 165 | ### Koa 166 | 167 | [koa-webpack-middleware](https://www.npmjs.com/package/koa-webpack-middleware) 168 | wraps this module for use with Koa 1.x 169 | 170 | [koa-webpack](https://www.npmjs.com/package/koa-webpack) 171 | can be used for Koa 2.x 172 | 173 | ## Troubleshooting 174 | 175 | ### Use on browsers without EventSource 176 | 177 | If you want to use this module with browsers that don't support eventsource, you'll need to use a [polyfill](https://libraries.io/search?platforms=NPM&q=eventsource+polyfill). See [issue #11](https://github.com/webpack-contrib/webpack-hot-middleware/issues/11) 178 | 179 | ### Not receiving updates in client when using Gzip 180 | 181 | This is because gzip generally buffers the response, but the Server Sent Events event-stream expects to be able to send data to the client immediately. You should make sure gzipping isn't being applied to the event-stream. See [issue #10](https://github.com/webpack-contrib/webpack-hot-middleware/issues/10). 182 | 183 | ### Use with auto-restarting servers 184 | 185 | This module expects to remain running while you make changes to your webpack bundle, if you use a process manager like nodemon then you will likely see very slow changes on the client side. If you want to reload the server component, either use a separate process, or find a way to reload your server routes without restarting the whole process. See https://github.com/glenjamin/ultimate-hot-reloading-example for an example of one way to do this. 186 | 187 | ### Use with multiple entry points in webpack 188 | 189 | If you want to use [multiple entry points in your webpack config](https://webpack.js.org/concepts/output/#multiple-entry-points) you need to include the hot middleware client in each entry point. This ensures that each entry point file knows how to handle hot updates. See the [examples folder README](example/README.md) for an example. 190 | 191 | ```js 192 | entry: { 193 | vendor: ['jquery', 'webpack-hot-middleware/client'], 194 | index: ['./src/index', 'webpack-hot-middleware/client'] 195 | } 196 | ``` 197 | 198 | ## License 199 | 200 | See [LICENSE file](LICENSE). 201 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/client-overlay.js: -------------------------------------------------------------------------------- 1 | /*eslint-env browser*/ 2 | 3 | var clientOverlay = document.createElement('div'); 4 | clientOverlay.id = 'webpack-hot-middleware-clientOverlay'; 5 | var styles = { 6 | background: 'rgba(0,0,0,0.85)', 7 | color: '#E8E8E8', 8 | lineHeight: '1.2', 9 | whiteSpace: 'pre', 10 | fontFamily: 'Menlo, Consolas, monospace', 11 | fontSize: '13px', 12 | position: 'fixed', 13 | zIndex: 9999, 14 | padding: '10px', 15 | left: 0, 16 | right: 0, 17 | top: 0, 18 | bottom: 0, 19 | overflow: 'auto', 20 | dir: 'ltr', 21 | textAlign: 'left' 22 | }; 23 | 24 | var ansiHTML = require('ansi-html'); 25 | var colors = { 26 | reset: ['transparent', 'transparent'], 27 | black: '181818', 28 | red: 'E36049', 29 | green: 'B3CB74', 30 | yellow: 'FFD080', 31 | blue: '7CAFC2', 32 | magenta: '7FACCA', 33 | cyan: 'C3C2EF', 34 | lightgrey: 'EBE7E3', 35 | darkgrey: '6D7891' 36 | }; 37 | 38 | var Entities = require('html-entities').AllHtmlEntities; 39 | var entities = new Entities(); 40 | 41 | function showProblems(type, lines) { 42 | clientOverlay.innerHTML = ''; 43 | lines.forEach(function(msg) { 44 | msg = ansiHTML(entities.encode(msg)); 45 | var div = document.createElement('div'); 46 | div.style.marginBottom = '26px'; 47 | div.innerHTML = problemType(type) + ' in ' + msg; 48 | clientOverlay.appendChild(div); 49 | }); 50 | if (document.body) { 51 | document.body.appendChild(clientOverlay); 52 | } 53 | } 54 | 55 | function clear() { 56 | if (document.body && clientOverlay.parentNode) { 57 | document.body.removeChild(clientOverlay); 58 | } 59 | } 60 | 61 | function problemType (type) { 62 | var problemColors = { 63 | errors: colors.red, 64 | warnings: colors.yellow 65 | }; 66 | var color = problemColors[type] || colors.red; 67 | return ( 68 | '' + 69 | type.slice(0, -1).toUpperCase() + 70 | '' 71 | ); 72 | } 73 | 74 | module.exports = function(options) { 75 | for (var color in options.overlayColors) { 76 | if (color in colors) { 77 | colors[color] = options.overlayColors[color]; 78 | } 79 | ansiHTML.setColors(colors); 80 | } 81 | 82 | for (var style in options.overlayStyles) { 83 | styles[style] = options.overlayStyles[style]; 84 | } 85 | 86 | for (var key in styles) { 87 | clientOverlay.style[key] = styles[key]; 88 | } 89 | 90 | return { 91 | showProblems: showProblems, 92 | clear: clear 93 | } 94 | }; 95 | 96 | module.exports.clear = clear; 97 | module.exports.showProblems = showProblems; 98 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/client.js: -------------------------------------------------------------------------------- 1 | /*eslint-env browser*/ 2 | /*global __resourceQuery __webpack_public_path__*/ 3 | 4 | var options = { 5 | path: "/__webpack_hmr", 6 | timeout: 20 * 1000, 7 | overlay: true, 8 | reload: true, 9 | log: true, 10 | warn: true, 11 | name: '', 12 | autoConnect: true, 13 | overlayStyles: {}, 14 | overlayWarnings: false, 15 | ansiColors: {} 16 | }; 17 | if (__resourceQuery) { 18 | var querystring = require('querystring'); 19 | var overrides = querystring.parse(__resourceQuery.slice(1)); 20 | setOverrides(overrides); 21 | } 22 | 23 | if (typeof window === 'undefined') { 24 | // do nothing 25 | } else if (typeof window.EventSource === 'undefined') { 26 | console.warn( 27 | "webpack-hot-middleware's client requires EventSource to work. " + 28 | "You should include a polyfill if you want to support this browser: " + 29 | "https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools" 30 | ); 31 | } else { 32 | if (options.autoConnect) { 33 | connect(); 34 | } 35 | } 36 | 37 | /* istanbul ignore next */ 38 | function setOptionsAndConnect(overrides) { 39 | setOverrides(overrides); 40 | connect(); 41 | } 42 | 43 | function setOverrides(overrides) { 44 | if (overrides.autoConnect) options.autoConnect = overrides.autoConnect == 'true'; 45 | if (overrides.path) options.path = overrides.path; 46 | if (overrides.timeout) options.timeout = overrides.timeout; 47 | if (overrides.overlay) options.overlay = overrides.overlay !== 'false'; 48 | if (overrides.reload) options.reload = overrides.reload !== 'false'; 49 | if (overrides.noInfo && overrides.noInfo !== 'false') { 50 | options.log = false; 51 | } 52 | if (overrides.name) { 53 | options.name = overrides.name; 54 | } 55 | if (overrides.quiet && overrides.quiet !== 'false') { 56 | options.log = false; 57 | options.warn = false; 58 | } 59 | 60 | if (overrides.dynamicPublicPath) { 61 | options.path = __webpack_public_path__ + options.path; 62 | } 63 | 64 | if (overrides.ansiColors) options.ansiColors = JSON.parse(overrides.ansiColors); 65 | if (overrides.overlayStyles) options.overlayStyles = JSON.parse(overrides.overlayStyles); 66 | 67 | if (overrides.overlayWarnings) { 68 | options.overlayWarnings = overrides.overlayWarnings == 'true'; 69 | } 70 | } 71 | 72 | function EventSourceWrapper() { 73 | var source; 74 | var lastActivity = new Date(); 75 | var listeners = []; 76 | 77 | init(); 78 | var timer = setInterval(function() { 79 | if ((new Date() - lastActivity) > options.timeout) { 80 | handleDisconnect(); 81 | } 82 | }, options.timeout / 2); 83 | 84 | function init() { 85 | source = new window.EventSource(options.path); 86 | source.onopen = handleOnline; 87 | source.onerror = handleDisconnect; 88 | source.onmessage = handleMessage; 89 | } 90 | 91 | function handleOnline() { 92 | if (options.log) console.log("[HMR] connected"); 93 | lastActivity = new Date(); 94 | } 95 | 96 | function handleMessage(event) { 97 | lastActivity = new Date(); 98 | for (var i = 0; i < listeners.length; i++) { 99 | listeners[i](event); 100 | } 101 | } 102 | 103 | function handleDisconnect() { 104 | clearInterval(timer); 105 | source.close(); 106 | setTimeout(init, options.timeout); 107 | } 108 | 109 | return { 110 | addMessageListener: function(fn) { 111 | listeners.push(fn); 112 | } 113 | }; 114 | } 115 | 116 | function getEventSourceWrapper() { 117 | if (!window.__whmEventSourceWrapper) { 118 | window.__whmEventSourceWrapper = {}; 119 | } 120 | if (!window.__whmEventSourceWrapper[options.path]) { 121 | // cache the wrapper for other entries loaded on 122 | // the same page with the same options.path 123 | window.__whmEventSourceWrapper[options.path] = EventSourceWrapper(); 124 | } 125 | return window.__whmEventSourceWrapper[options.path]; 126 | } 127 | 128 | function connect() { 129 | getEventSourceWrapper().addMessageListener(handleMessage); 130 | 131 | function handleMessage(event) { 132 | if (event.data == "\uD83D\uDC93") { 133 | return; 134 | } 135 | try { 136 | processMessage(JSON.parse(event.data)); 137 | } catch (ex) { 138 | if (options.warn) { 139 | console.warn("Invalid HMR message: " + event.data + "\n" + ex); 140 | } 141 | } 142 | } 143 | } 144 | 145 | // the reporter needs to be a singleton on the page 146 | // in case the client is being used by multiple bundles 147 | // we only want to report once. 148 | // all the errors will go to all clients 149 | var singletonKey = '__webpack_hot_middleware_reporter__'; 150 | var reporter; 151 | if (typeof window !== 'undefined') { 152 | if (!window[singletonKey]) { 153 | window[singletonKey] = createReporter(); 154 | } 155 | reporter = window[singletonKey]; 156 | } 157 | 158 | function createReporter() { 159 | var strip = require('strip-ansi'); 160 | 161 | var overlay; 162 | if (typeof document !== 'undefined' && options.overlay) { 163 | overlay = require('./client-overlay')({ 164 | ansiColors: options.ansiColors, 165 | overlayStyles: options.overlayStyles 166 | }); 167 | } 168 | 169 | var styles = { 170 | errors: "color: #ff0000;", 171 | warnings: "color: #999933;" 172 | }; 173 | var previousProblems = null; 174 | function log(type, obj) { 175 | var newProblems = obj[type].map(function(msg) { return strip(msg); }).join('\n'); 176 | if (previousProblems == newProblems) { 177 | return; 178 | } else { 179 | previousProblems = newProblems; 180 | } 181 | 182 | var style = styles[type]; 183 | var name = obj.name ? "'" + obj.name + "' " : ""; 184 | var title = "[HMR] bundle " + name + "has " + obj[type].length + " " + type; 185 | // NOTE: console.warn or console.error will print the stack trace 186 | // which isn't helpful here, so using console.log to escape it. 187 | if (console.group && console.groupEnd) { 188 | console.group("%c" + title, style); 189 | console.log("%c" + newProblems, style); 190 | console.groupEnd(); 191 | } else { 192 | console.log( 193 | "%c" + title + "\n\t%c" + newProblems.replace(/\n/g, "\n\t"), 194 | style + "font-weight: bold;", 195 | style + "font-weight: normal;" 196 | ); 197 | } 198 | } 199 | 200 | return { 201 | cleanProblemsCache: function () { 202 | previousProblems = null; 203 | }, 204 | problems: function(type, obj) { 205 | if (options.warn) { 206 | log(type, obj); 207 | } 208 | if (overlay) { 209 | if (options.overlayWarnings || type === 'errors') { 210 | overlay.showProblems(type, obj[type]); 211 | return false; 212 | } 213 | overlay.clear(); 214 | } 215 | return true; 216 | }, 217 | success: function() { 218 | if (overlay) overlay.clear(); 219 | }, 220 | useCustomOverlay: function(customOverlay) { 221 | overlay = customOverlay; 222 | } 223 | }; 224 | } 225 | 226 | var processUpdate = require('./process-update'); 227 | 228 | var customHandler; 229 | var subscribeAllHandler; 230 | function processMessage(obj) { 231 | switch(obj.action) { 232 | case "building": 233 | if (options.log) { 234 | console.log( 235 | "[HMR] bundle " + (obj.name ? "'" + obj.name + "' " : "") + 236 | "rebuilding" 237 | ); 238 | } 239 | break; 240 | case "built": 241 | if (options.log) { 242 | console.log( 243 | "[HMR] bundle " + (obj.name ? "'" + obj.name + "' " : "") + 244 | "rebuilt in " + obj.time + "ms" 245 | ); 246 | } 247 | // fall through 248 | case "sync": 249 | if (obj.name && options.name && obj.name !== options.name) { 250 | return; 251 | } 252 | var applyUpdate = true; 253 | if (obj.errors.length > 0) { 254 | if (reporter) reporter.problems('errors', obj); 255 | applyUpdate = false; 256 | } else if (obj.warnings.length > 0) { 257 | if (reporter) { 258 | var overlayShown = reporter.problems('warnings', obj); 259 | applyUpdate = overlayShown; 260 | } 261 | } else { 262 | if (reporter) { 263 | reporter.cleanProblemsCache(); 264 | reporter.success(); 265 | } 266 | } 267 | if (applyUpdate) { 268 | processUpdate(obj.hash, obj.modules, options); 269 | } 270 | break; 271 | default: 272 | if (customHandler) { 273 | customHandler(obj); 274 | } 275 | } 276 | 277 | if (subscribeAllHandler) { 278 | subscribeAllHandler(obj); 279 | } 280 | } 281 | 282 | if (module) { 283 | module.exports = { 284 | subscribeAll: function subscribeAll(handler) { 285 | subscribeAllHandler = handler; 286 | }, 287 | subscribe: function subscribe(handler) { 288 | customHandler = handler; 289 | }, 290 | useCustomOverlay: function useCustomOverlay(customOverlay) { 291 | if (reporter) reporter.useCustomOverlay(customOverlay); 292 | }, 293 | setOptionsAndConnect: setOptionsAndConnect 294 | }; 295 | } 296 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/helpers.js: -------------------------------------------------------------------------------- 1 | var parse = require('url').parse; 2 | 3 | exports.pathMatch = function(url, path) { 4 | try { 5 | return parse(url).pathname === path; 6 | } catch (e) { 7 | return false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/middleware.js: -------------------------------------------------------------------------------- 1 | module.exports = webpackHotMiddleware; 2 | 3 | var helpers = require('./helpers'); 4 | var pathMatch = helpers.pathMatch; 5 | 6 | function webpackHotMiddleware(compiler, opts) { 7 | opts = opts || {}; 8 | opts.log = typeof opts.log == 'undefined' ? console.log.bind(console) : opts.log; 9 | opts.path = opts.path || '/__webpack_hmr'; 10 | opts.heartbeat = opts.heartbeat || 10 * 1000; 11 | 12 | var eventStream = createEventStream(opts.heartbeat); 13 | var latestStats = null; 14 | var closed = false; 15 | 16 | if (compiler.hooks) { 17 | compiler.hooks.invalid.tap("webpack-hot-middleware", onInvalid); 18 | compiler.hooks.done.tap("webpack-hot-middleware", onDone); 19 | } else { 20 | compiler.plugin("invalid", onInvalid); 21 | compiler.plugin("done", onDone); 22 | } 23 | function onInvalid() { 24 | if (closed) return; 25 | latestStats = null; 26 | if (opts.log) opts.log("webpack building..."); 27 | eventStream.publish({action: "building"}); 28 | } 29 | function onDone(statsResult) { 30 | if (closed) return; 31 | // Keep hold of latest stats so they can be propagated to new clients 32 | latestStats = statsResult; 33 | publishStats("built", latestStats, eventStream, opts.log); 34 | } 35 | var middleware = function(req, res, next) { 36 | if (closed) return next(); 37 | if (!pathMatch(req.url, opts.path)) return next(); 38 | eventStream.handler(req, res); 39 | if (latestStats) { 40 | // Explicitly not passing in `log` fn as we don't want to log again on 41 | // the server 42 | publishStats("sync", latestStats, eventStream); 43 | } 44 | }; 45 | middleware.publish = function(payload) { 46 | if (closed) return; 47 | eventStream.publish(payload); 48 | }; 49 | middleware.close = function() { 50 | if (closed) return; 51 | // Can't remove compiler plugins, so we just set a flag and noop if closed 52 | // https://github.com/webpack/tapable/issues/32#issuecomment-350644466 53 | closed = true; 54 | eventStream.close(); 55 | eventStream = null; 56 | }; 57 | return middleware; 58 | } 59 | 60 | function createEventStream(heartbeat) { 61 | var clientId = 0; 62 | var clients = {}; 63 | function everyClient(fn) { 64 | Object.keys(clients).forEach(function(id) { 65 | fn(clients[id]); 66 | }); 67 | } 68 | var interval = setInterval(function heartbeatTick() { 69 | everyClient(function(client) { 70 | client.write("data: \uD83D\uDC93\n\n"); 71 | }); 72 | }, heartbeat).unref(); 73 | return { 74 | close: function() { 75 | clearInterval(interval); 76 | everyClient(function(client) { 77 | if (!client.finished) client.end(); 78 | }); 79 | clients = {}; 80 | }, 81 | handler: function(req, res) { 82 | var headers = { 83 | 'Access-Control-Allow-Origin': '*', 84 | 'Content-Type': 'text/event-stream;charset=utf-8', 85 | 'Cache-Control': 'no-cache, no-transform', 86 | // While behind nginx, event stream should not be buffered: 87 | // http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering 88 | 'X-Accel-Buffering': 'no' 89 | }; 90 | 91 | var isHttp1 = !(parseInt(req.httpVersion) >= 2); 92 | if (isHttp1) { 93 | req.socket.setKeepAlive(true); 94 | Object.assign(headers, { 95 | 'Connection': 'keep-alive', 96 | }); 97 | } 98 | 99 | res.writeHead(200, headers); 100 | res.write('\n'); 101 | var id = clientId++; 102 | clients[id] = res; 103 | req.on("close", function(){ 104 | if (!res.finished) res.end(); 105 | delete clients[id]; 106 | }); 107 | }, 108 | publish: function(payload) { 109 | everyClient(function(client) { 110 | client.write("data: " + JSON.stringify(payload) + "\n\n"); 111 | }); 112 | } 113 | }; 114 | } 115 | 116 | function publishStats(action, statsResult, eventStream, log) { 117 | var stats = statsResult.toJson({ 118 | all: false, 119 | cached: true, 120 | children: true, 121 | modules: true, 122 | timings: true, 123 | hash: true 124 | }); 125 | // For multi-compiler, stats will be an object with a 'children' array of stats 126 | var bundles = extractBundles(stats); 127 | bundles.forEach(function(stats) { 128 | var name = stats.name || ""; 129 | 130 | // Fallback to compilation name in case of 1 bundle (if it exists) 131 | if (bundles.length === 1 && !name && statsResult.compilation) { 132 | name = statsResult.compilation.name || ""; 133 | } 134 | 135 | if (log) { 136 | log("webpack built " + (name ? name + " " : "") + 137 | stats.hash + " in " + stats.time + "ms"); 138 | } 139 | eventStream.publish({ 140 | name: name, 141 | action: action, 142 | time: stats.time, 143 | hash: stats.hash, 144 | warnings: stats.warnings || [], 145 | errors: stats.errors || [], 146 | modules: buildModuleMap(stats.modules) 147 | }); 148 | }); 149 | } 150 | 151 | function extractBundles(stats) { 152 | // Stats has modules, single bundle 153 | if (stats.modules) return [stats]; 154 | 155 | // Stats has children, multiple bundles 156 | if (stats.children && stats.children.length) return stats.children; 157 | 158 | // Not sure, assume single 159 | return [stats]; 160 | } 161 | 162 | function buildModuleMap(modules) { 163 | var map = {}; 164 | modules.forEach(function(module) { 165 | map[module.id] = module.name; 166 | }); 167 | return map; 168 | } 169 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "webpack-hot-middleware@^2.24.3", 3 | "_id": "webpack-hot-middleware@2.24.3", 4 | "_inBundle": false, 5 | "_integrity": "sha512-pPlmcdoR2Fn6UhYjAhp1g/IJy1Yc9hD+T6O9mjRcWV2pFbBjIFoJXhP0CoD0xPOhWJuWXuZXGBga9ybbOdzXpg==", 6 | "_location": "/webpack-hot-middleware", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "webpack-hot-middleware@^2.24.3", 12 | "name": "webpack-hot-middleware", 13 | "escapedName": "webpack-hot-middleware", 14 | "rawSpec": "^2.24.3", 15 | "saveSpec": null, 16 | "fetchSpec": "^2.24.3" 17 | }, 18 | "_requiredBy": [ 19 | "/" 20 | ], 21 | "_resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.24.3.tgz", 22 | "_shasum": "5bb76259a8fc0d97463ab517640ba91d3382d4a6", 23 | "_spec": "webpack-hot-middleware@^2.24.3", 24 | "_where": "/Users/kvkens/code/yonyou/ucf-web/packages/ucf-scripts", 25 | "author": { 26 | "name": "Glen Mailer", 27 | "email": "glen@stainlessed.co.uk" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/webpack-contrib/webpack-hot-middleware/issues" 31 | }, 32 | "bundleDependencies": false, 33 | "dependencies": { 34 | "ansi-html": "0.0.7", 35 | "html-entities": "^1.2.0", 36 | "querystring": "^0.2.0", 37 | "strip-ansi": "^3.0.0" 38 | }, 39 | "deprecated": false, 40 | "description": "Webpack hot reloading you can attach to your own server", 41 | "devDependencies": { 42 | "eslint": "^4.6.1", 43 | "express": "^4.13.3", 44 | "istanbul": "^0.4.2", 45 | "mocha": "^5.2.0", 46 | "sinon": "^1.12.2", 47 | "supertest": "^3.1.0" 48 | }, 49 | "homepage": "https://github.com/webpack-contrib/webpack-hot-middleware#readme", 50 | "keywords": [ 51 | "webpack", 52 | "hmr", 53 | "hot", 54 | "module", 55 | "reloading", 56 | "hot-reloading", 57 | "middleware", 58 | "express" 59 | ], 60 | "license": "MIT", 61 | "main": "middleware.js", 62 | "name": "webpack-hot-middleware", 63 | "repository": { 64 | "type": "git", 65 | "url": "git+https://github.com/webpack-contrib/webpack-hot-middleware.git" 66 | }, 67 | "scripts": { 68 | "ci:coverage": "npm run test:coverage", 69 | "ci:lint": "npm run lint && npm run security", 70 | "ci:test": "npm run test", 71 | "lint": "eslint . --max-warnings 0", 72 | "security": "npm audit", 73 | "test": "mocha", 74 | "test:coverage": "istanbul cover _mocha --" 75 | }, 76 | "version": "2.24.3" 77 | } 78 | -------------------------------------------------------------------------------- /packages/ucf-scripts/src/webpack-hot-middleware/process-update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based heavily on https://github.com/webpack/webpack/blob/ 3 | * c0afdf9c6abc1dd70707c594e473802a566f7b6e/hot/only-dev-server.js 4 | * Original copyright Tobias Koppers @sokra (MIT license) 5 | */ 6 | 7 | /* global window __webpack_hash__ */ 8 | 9 | if (!module.hot) { 10 | throw new Error("[HMR] Hot Module Replacement is disabled."); 11 | } 12 | 13 | var hmrDocsUrl = "https://webpack.js.org/concepts/hot-module-replacement/"; // eslint-disable-line max-len 14 | 15 | var lastHash; 16 | var failureStatuses = { abort: 1, fail: 1 }; 17 | var applyOptions = { 18 | ignoreUnaccepted: true, 19 | ignoreDeclined: true, 20 | ignoreErrored: true, 21 | onUnaccepted: function(data) { 22 | console.warn("Ignored an update to unaccepted module " + data.chain.join(" -> ")); 23 | }, 24 | onDeclined: function(data) { 25 | console.warn("Ignored an update to declined module " + data.chain.join(" -> ")); 26 | }, 27 | onErrored: function(data) { 28 | console.error(data.error); 29 | console.warn("Ignored an error while updating module " + data.moduleId + " (" + data.type + ")"); 30 | } 31 | } 32 | 33 | function upToDate(hash) { 34 | if (hash) lastHash = hash; 35 | return lastHash == __webpack_hash__; 36 | } 37 | 38 | module.exports = function(hash, moduleMap, options) { 39 | var reload = options.reload; 40 | if (!upToDate(hash) && module.hot.status() == "idle") { 41 | if (options.log) console.log("[HMR] Checking for updates on the server..."); 42 | check(); 43 | } 44 | 45 | function check() { 46 | var cb = function(err, updatedModules) { 47 | if (err) return handleError(err); 48 | 49 | if(!updatedModules) { 50 | if (options.warn) { 51 | console.warn("[HMR] Cannot find update (Full reload needed)"); 52 | console.warn("[HMR] (Probably because of restarting the server)"); 53 | } 54 | performReload(); 55 | return null; 56 | } 57 | 58 | var applyCallback = function(applyErr, renewedModules) { 59 | if (applyErr) return handleError(applyErr); 60 | 61 | if (!upToDate()) check(); 62 | 63 | logUpdates(updatedModules, renewedModules); 64 | }; 65 | 66 | var applyResult = module.hot.apply(applyOptions, applyCallback); 67 | // webpack 2 promise 68 | if (applyResult && applyResult.then) { 69 | // HotModuleReplacement.runtime.js refers to the result as `outdatedModules` 70 | applyResult.then(function(outdatedModules) { 71 | applyCallback(null, outdatedModules); 72 | }); 73 | applyResult.catch(applyCallback); 74 | } 75 | 76 | }; 77 | 78 | var result = module.hot.check(false, cb); 79 | // webpack 2 promise 80 | if (result && result.then) { 81 | result.then(function(updatedModules) { 82 | cb(null, updatedModules); 83 | }); 84 | result.catch(cb); 85 | } 86 | } 87 | 88 | function logUpdates(updatedModules, renewedModules) { 89 | var unacceptedModules = updatedModules.filter(function(moduleId) { 90 | return renewedModules && renewedModules.indexOf(moduleId) < 0; 91 | }); 92 | 93 | if(unacceptedModules.length > 0) { 94 | if (options.warn) { 95 | console.warn( 96 | "[HMR] The following modules couldn't be hot updated: " + 97 | "(Full reload needed)\n" + 98 | "This is usually because the modules which have changed " + 99 | "(and their parents) do not know how to hot reload themselves. " + 100 | "See " + hmrDocsUrl + " for more details." 101 | ); 102 | unacceptedModules.forEach(function(moduleId) { 103 | console.warn("[HMR] - " + (moduleMap[moduleId] || moduleId)); 104 | }); 105 | } 106 | performReload(); 107 | return; 108 | } 109 | 110 | if (options.log) { 111 | if(!renewedModules || renewedModules.length === 0) { 112 | console.log("[HMR] Nothing hot updated."); 113 | } else { 114 | console.log("[HMR] Updated modules:"); 115 | renewedModules.forEach(function(moduleId) { 116 | console.log("[HMR] - " + (moduleMap[moduleId] || moduleId)); 117 | }); 118 | } 119 | 120 | if (upToDate()) { 121 | console.log("[HMR] App is up to date."); 122 | } 123 | } 124 | } 125 | 126 | function handleError(err) { 127 | if (module.hot.status() in failureStatuses) { 128 | if (options.warn) { 129 | console.warn("[HMR] Cannot check for update (Full reload needed)"); 130 | console.warn("[HMR] " + (err.stack || err.message)); 131 | } 132 | performReload(); 133 | return; 134 | } 135 | if (options.warn) { 136 | console.warn("[HMR] Update check failed: " + (err.stack || err.message)); 137 | } 138 | } 139 | 140 | function performReload() { 141 | if (reload) { 142 | if (options.warn) console.warn("[HMR] Reloading page"); 143 | window.location.reload(); 144 | } 145 | } 146 | }; 147 | --------------------------------------------------------------------------------