├── .gitattributes ├── .gitignore ├── .npmignore ├── README-zh.md ├── README.md ├── _config.yml ├── bin └── meet.js ├── package-lock.json ├── package.json ├── packages ├── commands │ ├── analysis.js │ ├── build.js │ ├── generate.js │ ├── generate │ │ ├── generate_native.js │ │ └── generate_vue_ssr.js │ ├── git.js │ ├── initial.js │ ├── start.js │ └── upload.js └── libs │ ├── filetype.js │ ├── meetyou.png │ ├── shellHelper.js │ ├── spinner.js │ └── utils.js ├── templates_no_framework ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── build │ ├── commands │ │ ├── build.js │ │ ├── ocstart.js │ │ └── upload.js │ ├── libs │ │ ├── aliupload.js │ │ ├── doupload.js │ │ ├── ip.js │ │ ├── qupload.js │ │ └── spinner.js │ ├── modules.js │ └── webpack │ │ ├── hot-reload-plugin.js │ │ └── webpack.config.js ├── client │ ├── common │ │ ├── css │ │ │ └── base.css │ │ └── js │ │ │ ├── flexible.js │ │ │ └── hot-reload.js │ └── demo │ │ ├── images │ │ └── 1.jpg │ │ ├── js │ │ ├── index.js │ │ └── index │ │ │ ├── business.js │ │ │ ├── service.js │ │ │ └── utils.js │ │ ├── styles │ │ └── index.css │ │ └── views │ │ └── index.html ├── mg.config.js ├── package.json ├── postcss.config.js ├── process.json ├── server │ ├── routes │ │ ├── demo.js │ │ └── index.js │ └── views │ │ ├── dev │ │ ├── demo │ │ │ └── index.html │ │ └── head.html │ │ ├── error.ejs │ │ ├── index.html │ │ └── prod │ │ └── head.html └── static │ └── readme.md └── templates_vue_ssr ├── module ├── app │ ├── app.js │ ├── components │ │ ├── App.vue │ │ ├── Bar.vue │ │ └── Foo.vue │ ├── index.css │ ├── index.html │ ├── service.js │ └── store.js ├── entry-client.js └── entry-server.js └── project ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── build ├── commands │ ├── build.js │ ├── ocstart.js │ └── upload.js ├── libs │ ├── aliupload.js │ ├── doupload.js │ ├── ip.js │ ├── qupload.js │ ├── spinner.js │ └── utils.js ├── modules-client.js ├── modules-server.js └── webpack │ ├── webpack.client.config.js │ └── webpack.server.config.js ├── client ├── common │ ├── css │ │ └── base.css │ └── js │ │ ├── commonUtils.js │ │ ├── flexible.js │ │ └── hot-reload.js └── demo │ └── index │ ├── app │ ├── app.js │ ├── components │ │ ├── App.vue │ │ ├── Bar.vue │ │ └── Foo.vue │ ├── index.css │ ├── index.html │ ├── service.js │ └── store.js │ ├── entry-client.js │ └── entry-server.js ├── mg.config.js ├── package.json ├── postcss.config.js ├── process.json ├── server ├── routes │ ├── demo.js │ └── index.js ├── ssr_code │ └── demo │ │ └── index-server.js └── views │ ├── dev │ ├── demo │ │ └── index.html │ └── head.html │ ├── error.ejs │ ├── index.html │ └── prod │ ├── demo │ └── index.html │ └── head.html └── static └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=Javascript; 2 | *.css linguist-language=Javascript; 3 | *.html linguist-language=Javascript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea 61 | 62 | .idea/*.xml 63 | 64 | dist/ 65 | dest/ 66 | public/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea 61 | 62 | .idea/*.xml 63 | 64 | dist/ 65 | dest/ 66 | public/ 67 | .aliossaccess -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 中文 2 | 3 | multipages-generator [![NPM version](https://badge.fury.io/js/multipages-generator.png)](http://badge.fury.io/js/multipages-generator) 4 | ====== 5 | 6 | [![NPM](https://nodei.co/npm/multipages-generator.png?downloads=true&stars=true)](https://nodei.co/npm/multipages-generator) 7 | 8 | multipages-generator (MG) 🤡是一个像express-generator一样快速生成网站开发脚手架的npm模块,完整的移动端h5解决方案,快速、高效、良好兼容性、高性能。 9 | 10 | ## 适合场景 11 | 如美柚,淘宝,今日头条,微信内分享的等独立的,小的h5,可以是广告,营销,活动,展示页,秀肌肉,好玩的h5,如[这些](http://www.ih5.cn/not-logged-in/template)。 12 | 还有我们的例子: 13 | [美柚吃鸡游戏](https://uedkit.meiyou.com/annualmeeting/game/) 14 | 15 | ## 特点 16 | 17 | 1. 使用Node.js,是一个JavasScript的全栈的H5解决方案,工程可直接部署 18 | 2. 高效率开发,支持一键创建模块(业务模块、一键编译发布、上传、生产代码分析等快捷命令 19 | 3. 工程结构良好划分,结构清晰,可维护。 20 | 4. 🔥 (新) 支持Vue SSR 与无框架的模板 21 | 5. 支持development,producton环境区分 22 | 6. 支持sass、less、postcss 23 | 7. 开发环境CSS、JS热编译 24 | 8. 文件上传支持阿里OSS,七牛云等 25 | 9. 加入[手淘flexible布局方案](https://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html),适配不同尺寸和DPI的屏幕 26 | 10. 支持pm2集群启动 27 | 28 | 29 | ## Document 30 | * [全局安装](#全局安装) 31 | * [创建一个工程](#创建一个工程) 32 | * [指令介绍](#指令介绍) 33 | * [新建一个模块](#新建一个模块) 34 | * [指定模块启动](#指定模块启动) 35 | * [指定模块编译](#指定模块编译) 36 | * [上传](#上传) 37 | * [七牛云CDN](#七牛云cdn) 38 | * [阿里云OSS](#阿里云oss) 39 | * [配置](#配置) 40 | * [TodoList](#TodoList) 41 | 42 | ## 全局安装 ⚙️ 43 | 44 | ### 环境要求 45 | 46 | node环境:node.js 6.11.0 47 | 48 | 操作系统:支持 mac,windows,centos 49 | 50 | ### 全局安装 51 | 52 | ```bash 53 | npm install multipages-generator -g //目前最新版本为1.5.x 54 | ``` 55 | 56 | ## 创建一个工程 📽 57 | 58 | 初始化工程 59 | ```bash 60 | meet init 61 | ``` 62 | 63 | 选择模板: 64 | - No JavaScript framework 为无框架的模板,可以自行选择需要的开发框架,jQuery,zepto,vue,react等 65 | - Vue width SSR 为选择Vue框架的版本,默认带了SSR 66 | ```bash 67 | ? Select your JavaScript framework (Use arrow keys) 68 | ❯ No JavaScript framework 69 | Vue width SSR 70 | ``` 71 | 72 | 完成了项目创建,提示运行 73 | ```bash 74 | C:\xxx\workspace>meet init 75 | ? Project name: h5-project 76 | __ __ _ ____ _ ___ 77 | | \/ | ___ ___| |_ / ___| | |_ _| 78 | | |\/| |/ _ \/ _ \ __| | | | | | | 79 | | | | | __/ __/ |_ | |___| |___ | | 80 | |_| |_|\___|\___|\__| \____|_____|___| 81 | 82 | [Success] Project h5-project init finished, be pleasure to use 😊! 83 | 84 | Install dependencies: 85 | cd h5-project && npm install 86 | 87 | Run the app: 88 | meet start demo 89 | Or: 90 | pm2 start process.json 91 | 92 | ``` 93 | 94 | ## 指令介绍 95 | 查看指令帮助 meet -help 96 | ```bash 97 | C:\xxx\workspace>meet -help 98 | 99 | Usage: meet [command] 100 | 101 | Options: 102 | 103 | -v, --version output the version number 104 | -h, --help output usage information 105 | 106 | Commands: 107 | 108 | init initialize your project 109 | new [module]/[module]-[page] generate_native a new module 110 | start [module] start application in development mode 111 | build [module] build a module using webpack 112 | upload upload dist files to CDN 113 | analyse analysis dist files size and percent 114 | git auto git commit and push 115 | 116 | ``` 117 | 注意,创建模块使用meet new [module],如果是在该模块下创建其他页面,则使用meet new [module]-[page]。举例:在demo下创建一个详情页面detail.html 使用 meet new demo-detail 118 | 119 | ## 新建一个模块 120 | 121 | meet new [module]/[module]-[page] 122 | 123 | ```bash 124 | meet new game // 创建游戏模块(默认index页面) 125 | ``` 126 | 由于是多页面的,所以你可能还会创建一个详情页面 127 | ``` 128 | meet new game-detail // 创建游戏模块下的详情页面 129 | ``` 130 | 131 | 得到一个文件结构 132 | ```bash 133 | game 134 | ├─images // 由于没有文件,可能不会创建,开发者自行创建images 135 | ├─js 136 | | ├─index 137 | | | ├─business.js // 具体业务层(可根据业务复杂度再细分) 138 | | | ├─service.js // 数据处理层 139 | | | └─util.js // 工具类函数层 140 | | └─index.js // 主逻辑层 141 | ├─styles 142 | | └─index.css // css 143 | └─views 144 | └─index.html // html源文件 145 | ``` 146 | 147 | ## 指定模块启动 148 | ### meet start [module] 149 | 150 | ``` 151 | meet start demo 152 | ``` 153 | 启动后会出现如下显示,可点击相应的地址访问 154 | ``` 155 | √ Build done 156 | 157 | [Tips] visit: http://localhost:8080/demo/ 158 | : http://192.168.50.194:8080/demo/ 159 | 160 | ``` 161 | 162 | 注意: Vue CSR: http://localhost:8080/demo/?csr=true 163 | 164 | ### 热编译 165 | 166 | JS、CSS支持热编译,HTML需要刷新 167 | 168 | ![image](http://cnd.yintage.com/HRM.gif) 169 | 170 | 生成的html文件中有如下两处标记,用来热编译用。无需担心,编译阶段会删除。 171 | 172 | ```html 173 | 174 | 175 | 176 | <% include ../head.html %> 177 | demo 178 | 179 | 180 | 181 | 182 |
内容...
183 | 184 | 185 | 186 | 187 | ``` 188 | 189 | ## 指定模块编译 190 | 191 | ### meet build [demo] 192 | 193 | ```bash 194 | meet build demo 195 | ``` 196 | 197 | ```bash 198 | C:xxx\workspace\h5>meet build demo 199 | 200 | > mg-template@1.0.0 build C:\meetyou\workspace\test\mg-workspace\h5 201 | > cross-env NODE_ENV=production node build/commands/build.js "demo" 202 | 203 | Delete dist directory! 204 | ⣾ Building... 205 | ⣽ lasted 1 seconds. HTML去除开发环境hotReload代码: ..\server\views\prod\demo\index.html 206 | Hash: 2a217fb45f03fb354254 207 | Version: webpack 4.17.2 208 | Time: 1687ms 209 | Built at: 2018-09-06 19:50:40 210 | Asset Size Chunks Chunk Names 211 | index.12969e6e.css 4.71 KiB 0 [emitted] index 212 | index.080a1e3d.js 1.01 KiB 0 [emitted] index 213 | ..\server\views\prod\demo\index.html 3.74 KiB [emitted] 214 | Entrypoint index = index.12969e6e.css index.080a1e3d.js 215 | 216 | Upload dist files to Qiniu CDN: 217 | Webpack Bundle Analyzer is started at http://127.0.0.1:8888 218 | Use Ctrl+C to close it 219 | [Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.080a1e3d.js 220 | [Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.12969e6e.css 221 | [Success]: 上传完毕 😊! 222 | Use Ctrl+C to close it 223 | 224 | ``` 225 | 编译后分析会调用webpack插件显示每个js,css的依赖情况 226 | 227 | ![image](http://cnd.yintage.com/build.png) 228 | 229 | ### meet analyse 230 | 通过meet analyse 查看占比 231 | 232 | ![image](http://cnd.yintage.com/chart.png) 233 | 234 | ## 上传 235 | 236 | ### meet upload 237 | 上传的是dist文件夹中的文件,配置阿里云,七牛云请看mg.config.js 238 | ```bash 239 | meet upload 240 | ``` 241 | 242 | ## 配置 243 | ### mg.config.js 244 | MG目前支持发布时自动,支持阿里OSS、七牛云 245 | 246 | mg.config.js 中有如下配置 247 | 248 | ``` 249 | module.exports = { 250 | 251 | // 启动的客户端服务器端口 252 | clientPort: '8080', 253 | 254 | // 服务端服务器端口 255 | server: { 256 | port: '8090', 257 | }, 258 | 259 | // 上传相关配置 260 | upload: { 261 | cdn: '//oflt40zxf.bkt.clouddn.com/', 262 | projectPrefix: 'nodejs-common', 263 | 264 | // 如果是阿里云,则aliconfig配置一个空对象,目前采用.aliossacess 文件配置的方式 265 | // aliconfig: { 266 | // 267 | // }, 268 | // 七牛云 269 | 270 | qconfig: { 271 | ACCESS_KEY: 'ei1uOdGpVLliA7kb50sLcV9i4wfYLPwt5v0shU10', 272 | SECRET_KEY: '-pFFIY-ew35Exyfcd67Sbaw40k15ah3UfZTFWFKF', 273 | bucket:'hotshots-image', 274 | origin:'http://cnd.yintage.com' 275 | }, 276 | 277 | // 是否编译后自动上传 278 | autoUpload: true 279 | 280 | } 281 | }; 282 | 283 | ``` 284 | 285 | ## Todo List 286 | 1. 更好的支持Vue SSR,类似nuxt 287 | 2. 支持react, react-ssr 288 | 289 | ## Contribution 290 | 291 | [吴俊川](https://github.com/wujunchuan) 292 | 293 | 感谢俊川提供的热更新方案的建议,以及对项目某些细节的改进 294 | 295 | ## 配套部署方案请参考 296 | [30分钟快速部署到云服务器上](http://medium.yintage.com/?p=248) 297 | 298 | 299 | ## License 300 | 301 | The MIT License 请自由享受开源。 302 | 303 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [中文](./README-zh.md) 2 | 3 | multipages-generator [![NPM version](https://badge.fury.io/js/multipages-generator.png)](http://badge.fury.io/js/multipages-generator) 4 | ====== 5 | 6 | [![NPM](https://nodei.co/npm/multipages-generator.png?downloads=true&stars=true)](https://nodei.co/npm/multipages-generator) 7 | 8 | multipages-generator is a multiple pages application generator (or CLI) for mobile. It has the whole DevOps which includes development, build, publish and the deployment. It is One-stop solution for mobile H5. 9 | 10 | ## Scene 11 | Multipages-generator suite for multipages website whatever is mobile website or PC website, H5 in hybird app. For example: [this](http://www.ih5.cn/not-logged-in/template), [chiji game](https://uedkit.meiyou.com/annualmeeting/game/). 12 | 13 | ## Feature 14 | 1. One-stop mobile MPA solution with modern web technologys like Nodejs, webpack4, babel, Vue with server side rendering. 15 | 2. Efficient commands like new, develop,build,upload,analysis,deploy. 16 | 3. Best practices for architechure and organization. 17 | 4. 🔥 (new) Support Vue SSR and no framework or any other framework you like. 18 | 5. Support development,producton ENV. 19 | 6. Support sass、less、postcss 20 | 7. Hot code reload for CSS and JS 21 | 8. Support upload to Ali OSS and Qiniu OSS 22 | 9. Support mobile adaptation with [taobao flexible layout solution](https://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html),fit different screen size and DPI. 23 | 10. Support pm2 deployment 24 | 25 | 26 | ## Document 27 | * [Global install](#global-install) 28 | * [Create a project](#create-a-project) 29 | * [Commands](#commands) 30 | * [Create a new module](#create-a-new-module) 31 | * [Develop a module](#develop-a-module) 32 | * [Build a module](#build-a-module) 33 | * [Upload](#upload) 34 | * [Qiniu OSS](#qiniu-oss) 35 | * [Ali OSS](#ali-oss) 36 | * [Config](#config) 37 | * [TodoList](#TodoList) 38 | 39 | ## Global install ⚙️ 40 | 41 | ### Envirment requirement 42 | 43 | NodeJS: >= 6.11.0 44 | 45 | OS: MacOS,windows,centos 46 | 47 | ### install 48 | 49 | ```bash 50 | npm install multipages-generator -g //now the latest is 1.6.x 51 | ``` 52 | 53 | ## Create a project 📽 54 | 55 | ### init a project 56 | ```bash 57 | meet init 58 | ``` 59 | 60 | ### Choose a template: 61 | - No JavaScript framework (You can add your framework like jQuery,zepto,vue,react and so on.) 62 | - Vue width SSR (It's add SSR default for now) 63 | ```bash 64 | ? Select your JavaScript framework (Use arrow keys) 65 | ❯ No JavaScript framework 66 | Vue width SSR 67 | ``` 68 | 69 | ### start 70 | When initialized, install the dependencis and start the demo 71 | ```bash 72 | C:\xxx\workspace>meet init 73 | ? Project name: h5-project 74 | __ __ _ ____ _ ___ 75 | | \/ | ___ ___| |_ / ___| | |_ _| 76 | | |\/| |/ _ \/ _ \ __| | | | | | | 77 | | | | | __/ __/ |_ | |___| |___ | | 78 | |_| |_|\___|\___|\__| \____|_____|___| 79 | 80 | [Success] Project h5-project init finished, be pleasure to use 😊! 81 | 82 | Install dependencies: 83 | cd h5-project && npm install 84 | 85 | Run the app: 86 | meet start demo 87 | Or: 88 | pm2 start process.json 89 | 90 | ``` 91 | 92 | ## Commands 93 | Use meet -help to show all the commands. 94 | 95 | ```bash 96 | C:\xxx\workspace>meet -help 97 | 98 | Usage: meet [command] 99 | 100 | Options: 101 | 102 | -v, --version output the version number 103 | -h, --help output usage information 104 | 105 | Commands: 106 | 107 | init initialize your project 108 | new [module]/[module]-[page] generate_native a new module 109 | start [module] start application in development mode 110 | build [module] build a module using webpack 111 | upload upload dist files to CDN 112 | analyse analysis dist files size and percent 113 | git auto git commit and push 114 | 115 | ``` 116 | 117 | ## Create a new module 118 | 119 | meet new [module]/[module]-[page] 120 | 121 | ### Description 122 | Attention, create a new module use like this 123 | ``` 124 | meet new [module] 125 | ``` 126 | When you need to create a new page in the existed module, use this command: 127 | ``` 128 | meet new [module]-[page] 129 | ``` 130 | 131 | ### For a example, create a game H5(module) 132 | 133 | ```bash 134 | meet new game // create a game with default page index.html 135 | ``` 136 | Because it's so called multiple pages generator, so create another page use this: 137 | ``` 138 | meet new game-detail // create the game detail.html in the game module 139 | ``` 140 | 141 | And you got a list files like this: 142 | ```bash 143 | game 144 | ├─images // this is no images, just a dictory 145 | ├─js 146 | | ├─index 147 | | | ├─business.js // the business js(Expand as you wish) 148 | | | ├─service.js // http service code 149 | | | └─util.js // utils code 150 | | └─index.js // the main js file 151 | ├─styles 152 | | └─index.css // css code 153 | └─views 154 | └─index.html // html code 155 | ``` 156 | 157 | ## Develop a module 158 | ### meet start [module] 159 | 160 | ``` 161 | meet start demo 162 | ``` 163 | 164 | It started with this followed, you can choose a link to open in browser. 165 | ``` 166 | √ Build done 167 | 168 | [Tips] visit: http://localhost:8080/demo/ 169 | : http://192.168.50.194:8080/demo/ 170 | 171 | ``` 172 | 173 | Attention: 174 | Vue CSR: http://localhost:8080/demo/?csr=true 175 | Vue SSR: http://localhost:8080/demo/ 176 | 177 | ### Hot reload 178 | 179 | JS、CSS support hot code reload,HTML changes need man to refresh the browser. 180 | 181 | ![image](http://cnd.yintage.com/HRM.gif) 182 | 183 | Html generated contain two marker, you don't need to worry about this. It's for better development and will removed when in build. 184 | 185 | ```html 186 | 187 | 188 | 189 | <% include ../head.html %> 190 | demo 191 | 192 | 193 | 194 | 195 |
you content...
196 | 197 | 198 | 199 | 200 | ``` 201 | 202 | ## Build a module 203 | 204 | ### meet build [demo] 205 | 206 | ```bash 207 | meet build demo 208 | ``` 209 | 210 | ```bash 211 | C:xxx\workspace\h5>meet build demo 212 | 213 | > mg-template@1.0.0 build C:\meetyou\workspace\test\mg-workspace\h5 214 | > cross-env NODE_ENV=production node build/commands/build.js "demo" 215 | 216 | Delete dist directory! 217 | ⣾ Building... 218 | ⣽ lasted 1 seconds. HTML去除开发环境hotReload代码: ..\server\views\prod\demo\index.html 219 | Hash: 2a217fb45f03fb354254 220 | Version: webpack 4.17.2 221 | Time: 1687ms 222 | Built at: 2018-09-06 19:50:40 223 | Asset Size Chunks Chunk Names 224 | index.12969e6e.css 4.71 KiB 0 [emitted] index 225 | index.080a1e3d.js 1.01 KiB 0 [emitted] index 226 | ..\server\views\prod\demo\index.html 3.74 KiB [emitted] 227 | Entrypoint index = index.12969e6e.css index.080a1e3d.js 228 | 229 | Upload dist files to Qiniu CDN: 230 | Webpack Bundle Analyzer is started at http://127.0.0.1:8888 231 | Use Ctrl+C to close it 232 | [Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.080a1e3d.js 233 | [Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.12969e6e.css 234 | [Success]: 上传完毕 😊! 235 | Use Ctrl+C to close it 236 | 237 | ``` 238 | After analysis powerd by webpack plugin, the page will show the code proportion. 239 | 240 | ![image](http://cnd.yintage.com/build.png) 241 | 242 | ### meet analyse 243 | 244 | Use this command after builded. 245 | 246 | ``` 247 | meet analyse 248 | ``` 249 | 250 | ![image](http://cnd.yintage.com/chart.png) 251 | 252 | ## Upload 253 | 254 | ### meet upload 255 | Upload the files which in the dist dictory to OSS server. Config the Ali OSS or Qiniu OSS configs in mg.config.js. 256 | ```bash 257 | meet upload 258 | ``` 259 | 260 | ## Config 261 | ### mg.config.js 262 | 263 | mg.config.js is look like: 264 | 265 | ``` 266 | module.exports = { 267 | 268 | // the client server (use for hot reload ) port 269 | clientPort: '8080', 270 | 271 | // the server(for deployment) port 272 | server: { 273 | port: '8090', 274 | }, 275 | 276 | // upload config 277 | upload: { 278 | cdn: '//oflt40zxf.bkt.clouddn.com/', 279 | projectPrefix: 'nodejs-common', 280 | 281 | // if use Ali OSS,set aliconfig a empty object, now it support Ali CLI for upload, 282 | // aliconfig: { 283 | // 284 | // }, 285 | 286 | // Qiniu OSS 287 | qconfig: { 288 | ACCESS_KEY: 'ei1uOdGpVLliA7kb50sLcV9i4wfYLPwt5v0shU10', 289 | SECRET_KEY: '-pFFIY-ew35Exyfcd67Sbaw40k15ah3UfZTFWFKF', 290 | bucket:'hotshots-image', 291 | origin:'http://cnd.yintage.com' 292 | }, 293 | 294 | // is auto upload after build 295 | autoUpload: true 296 | 297 | } 298 | }; 299 | 300 | ``` 301 | 302 | [Ali OSS upload](https://www.npmjs.com/package/meetyou-ali-oss) 303 | 304 | ## Todo List 305 | 1. Better Vue SSR solution 306 | 2. Support react, react-ssr 307 | 308 | ## deployment 309 | [deploy to server in 30 minutes](http://medium.yintage.com/?p=248) 310 | 311 | 312 | ## License 313 | 314 | The MIT License 315 | 316 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bin/meet.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const program = require('commander'); 5 | const initial = require('../packages/commands/initial'); 6 | const start = require('../packages/commands/start'); 7 | const build = require('../packages/commands/build'); 8 | const upload = require('../packages/commands/upload'); 9 | const generate = require('../packages/commands/generate'); 10 | const analyse = require('../packages/commands/analysis'); 11 | const git = require('../packages/commands/git'); 12 | 13 | var config = {}; 14 | 15 | // 配置文件如果存在则读取 16 | if(fs.existsSync(path.resolve('mg.config.js'))){ 17 | config = require(path.resolve('mg.config.js')); 18 | } 19 | 20 | // 创建工程 21 | program 22 | .version('1.5.3','-v, --version') 23 | .usage('[command]') 24 | .command('init') 25 | .description('initialize your project') 26 | .action(initial); 27 | 28 | // 新建模块 29 | program 30 | .command('new [module]/[module]-[page]') 31 | .description('generate a new module') 32 | .action(generate); 33 | 34 | // 启动工程 35 | program 36 | .command('start [module] ') 37 | .description('start application in development mode') 38 | .action(start); 39 | 40 | // 编译 41 | program 42 | .command('build [module]') 43 | .description('build a module using webpack') 44 | .action(build); 45 | 46 | // 上传 47 | program 48 | .command('upload') 49 | .description('upload dist files to CDN') 50 | .action(upload); 51 | 52 | // 分析生成文件占比 53 | program 54 | .command('analyse') 55 | .description('analysis dist files size and percent') 56 | .action(function(){ 57 | analyse(path.resolve('dist')); 58 | }); 59 | 60 | // 分析生成文件占比 61 | program 62 | .command('git') 63 | .description('auto git commit and push') 64 | .action(git); 65 | 66 | program.parse(process.argv); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multipages-generator", 3 | "version": "1.6.1", 4 | "description": "generator for multipage webpack application template ,use express ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/linweiwei123/multipage-generator.git" 12 | }, 13 | "keywords": [ 14 | "multipage", 15 | "webpack", 16 | "express", 17 | "node.js", 18 | "template" 19 | ], 20 | "bin": { 21 | "meet": "./bin/meet.js" 22 | }, 23 | "author": "linweiwei", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/linweiwei123/multipage-generator/issues" 27 | }, 28 | "homepage": "https://github.com/linweiwei123/multipage-generator#readme", 29 | "dependencies": { 30 | "blessed": "^0.1.81", 31 | "blessed-contrib": "^4.8.5", 32 | "chalk": "^2.4.1", 33 | "clui": "^0.3.6", 34 | "commander": "^2.15.1", 35 | "figlet": "^1.2.0", 36 | "inquirer": "^6.0.0", 37 | "meetyou-ali-oss": "^1.0.7", 38 | "qn": "^1.3.0", 39 | "shelljs": "^0.8.2", 40 | "webpack": "^4.17.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/commands/analysis.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const filetype = require('../libs/filetype'); 4 | const chalk = require('chalk'); 5 | 6 | module.exports = function(dir){ 7 | let data = analysisAssets(dir); 8 | renderChart(data); 9 | }; 10 | 11 | function renderChart(assetsData){ 12 | var blessed = require('blessed') 13 | , contrib = require('blessed-contrib') 14 | , screen = blessed.screen(); 15 | 16 | let titles=[],data=[],total = 0; 17 | for(let item in assetsData){ 18 | titles.push(item); 19 | data.push(assetsData[item].toFixed(1)); 20 | total += assetsData[item]; 21 | } 22 | 23 | var bar = contrib.bar( 24 | { label: chalk.blue('total assets size is : ') + chalk.yellow(`${total.toFixed(2)} kb`) + chalk.blue('. The details are as follows (kb): \n') 25 | , barWidth: 8 26 | , barSpacing: 10 27 | , xOffset: 5 28 | , maxHeight: 7 29 | , barBgColor: 'green' 30 | , barFgColor: 'red' 31 | }); 32 | 33 | screen.append(bar); //must append before setting data 34 | 35 | bar.setData({ titles, data }); 36 | screen.render(); 37 | } 38 | 39 | function analysisAssets(dir){ 40 | let assets = { 41 | html: 0, 42 | js: 0, 43 | css: 0, 44 | image: 0, 45 | media: 0, 46 | other: 0 47 | }; 48 | 49 | function readAndCountFile(dirPath){ 50 | let files = fs.readdirSync(dirPath); 51 | 52 | files.forEach((file)=>{ 53 | let curPath = `${dirPath}/${file}`; 54 | let stat = fs.statSync(curPath); 55 | if(stat.isDirectory()){ 56 | readAndCountFile(`${dirPath}/${file}`); 57 | } 58 | else{ 59 | let ext = path.extname(file); 60 | assets[filetype(ext)] += stat.size/1000; 61 | } 62 | }); 63 | } 64 | 65 | readAndCountFile(dir); 66 | 67 | return assets; 68 | } -------------------------------------------------------------------------------- /packages/commands/build.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const shell = require('shelljs'); 3 | const path = require('path'); 4 | const analyse = require('./analysis'); 5 | 6 | module.exports = function(){ 7 | 8 | const argvStr = process.argv[3] || ''; 9 | 10 | // 如果未指定编译模块,则提示返回 11 | if(argvStr === ''){ 12 | console.log(chalk.yellow('[warning]: You have not specify a module to build, you can run "meet build [module]"')); 13 | return; 14 | } 15 | 16 | shell.exec(`npm run build ${argvStr}`, function(err){ 17 | if(err){ 18 | console.log(chalk.red(err)); 19 | process.exit(0); 20 | } 21 | }); 22 | 23 | }; -------------------------------------------------------------------------------- /packages/commands/generate.js: -------------------------------------------------------------------------------- 1 | const utils = require('../libs/utils'); 2 | const gr_native = require('./generate/generate_native'); 3 | const gr_vue_ssr = require('./generate/generate_vue_ssr'); 4 | 5 | function generate(){ 6 | if(utils.isVueTemplate()){ 7 | gr_vue_ssr(); 8 | } 9 | else { 10 | gr_native(); 11 | } 12 | } 13 | 14 | module.exports = generate; -------------------------------------------------------------------------------- /packages/commands/generate/generate_native.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const chalk = require('chalk'); 4 | 5 | // 获取命令携带的参数 6 | const modulePage = process.argv[3] || ''; 7 | 8 | const CSS = `@import url('../../common/css/base.css'); 9 | 10 | html, body{ 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | .container{ 16 | text-align: center; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | right: 0; 21 | bottom: 0; 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | font-size: 64px; 26 | color: #4fc08d; 27 | font-weight: bold; 28 | } 29 | 30 | `; 31 | 32 | var HTML = ''; 33 | var JS = ''; 34 | 35 | function generate_native(){ 36 | if(modulePage === ''){ 37 | console.log(chalk.yellow('[warning]: You have not specify a module to start, you can run "meet start [module]"')); 38 | return; 39 | } 40 | 41 | let nameArr = modulePage.split('-'); 42 | let page = nameArr.length == 2?nameArr[1]:'index'; 43 | let moduleDir = nameArr[0]; 44 | let targetPath = path.resolve(`client/${moduleDir}`); 45 | let options = { 46 | module: moduleDir, 47 | page, 48 | targetPath 49 | }; 50 | 51 | HTML = ` 52 | 53 | 54 | <% include ../head.html %> 55 | ${moduleDir} 56 | 57 | 58 | 59 | 60 | 61 |
<%= data %>
62 | 63 | 64 | 65 | 66 | `; 67 | 68 | JS = `import '../styles/${page}.css'`; 69 | 70 | let filePath = path.join(targetPath,'js',`${page}.js`); 71 | if(fs.existsSync(filePath)){ 72 | console.log(chalk.red('该页面已存在,如果需要覆盖,请先删除')); 73 | return; 74 | } 75 | 76 | // 生成网页JavaScript、stylesheets目录,模板 77 | generateAssets(options); 78 | 79 | // 生成router文件,配置router 80 | generateRouter(options); 81 | 82 | // router/index.js 增加配置 83 | generateRouteIndex(options); 84 | 85 | // 添加webpack entrys 86 | generateEntry(options); 87 | 88 | } 89 | 90 | function generateAssets(options){ 91 | let ImgPath = path.resolve(options.targetPath,'images'); 92 | let JSPath = path.resolve(options.targetPath,'js'); 93 | let CSSPath = path.resolve(options.targetPath,'styles'); 94 | let viewPath = path.resolve('client',options.module,'views'); 95 | 96 | HTML = HTML.replace('模板页面',`模板页面${options.page}`); 97 | 98 | let pathArr = [ 99 | { 100 | path: ImgPath, 101 | type: 'img', 102 | content: null 103 | }, 104 | { 105 | path: JSPath, 106 | type: 'js', 107 | content: JS 108 | }, 109 | { 110 | path: CSSPath, 111 | type: 'css', 112 | content: CSS 113 | }, 114 | { 115 | path: viewPath, 116 | type: 'html', 117 | content: HTML 118 | } 119 | ]; 120 | 121 | if(!fs.existsSync(options.targetPath)){ 122 | fs.mkdirSync(options.targetPath); 123 | } 124 | 125 | // generate_native dir and file 126 | pathArr.some((item)=>{ 127 | if(!fs.existsSync(item.path)){ 128 | fs.mkdirSync(item.path); 129 | } 130 | if(item.content !== null){ 131 | let filePath = path.join(item.path,`${options.page}.${item.type}`); 132 | fs.writeFileSync(filePath,item.content,'utf8'); 133 | console.log(chalk.green(`创建了文件:`+ filePath)); 134 | } 135 | }); 136 | 137 | // 生成js 相关文件 138 | let partialDir = path.resolve('client',options.module,'js',options.page); 139 | if(!fs.existsSync(partialDir)){ 140 | fs.mkdirSync(partialDir); 141 | } 142 | ['business','service','utils'].forEach((item)=>{ 143 | let jsPartial = path.join(partialDir,item + ".js"); 144 | fs.writeFileSync(jsPartial,'','utf8') 145 | }) 146 | } 147 | 148 | function generateRouter(options){ 149 | const routerPath = path.resolve('server/routes',options.module+'.js'); 150 | const ROUTER = ` 151 | module.exports.index = function(req, res){ 152 | res.render('${options.module}/index',{ data: 'index'}) 153 | }; 154 | `; 155 | if(!fs.existsSync(routerPath)){ 156 | fs.writeFileSync(routerPath,ROUTER,'utf8'); 157 | console.log(chalk.green(`创建了router文件:`+ routerPath)); 158 | } 159 | else { 160 | let content = fs.readFileSync(routerPath,'utf8'); 161 | content +=` 162 | module.exports.${options.page} = function(req, res){ 163 | res.render('${options.module}/${options.page}', { data: '${options.page}'}) 164 | }; 165 | `; 166 | fs.writeFileSync(routerPath,content,'utf8'); 167 | console.log(chalk.green(`创建了router文件:`+ routerPath)); 168 | } 169 | 170 | } 171 | 172 | function generateRouteIndex(options) { 173 | let routeIndex = path.resolve('server/routes/index.js'); 174 | let content = fs.readFileSync(routeIndex,'utf8'); 175 | let content1= content; 176 | if(content.indexOf(`const ${options.module} = require('./${options.module}'); 177 | //<@add page@>`)==-1){ 178 | content1 = content.replace(`//<@add page@>`,`const ${options.module} = require('./${options.module}'); 179 | //<@add page@>`); 180 | } 181 | 182 | let content2 = content1.replace('//<@add page router@>', `app.get('/${options.module}${options.page==='index'? '': '/' + options.page}', ${options.module}.${options.page}); 183 | //<@add page router@>`); 184 | fs.writeFileSync(routeIndex,content2,'utf8'); 185 | console.log(chalk.green(`server/router/index.js增加了router配置:`)); 186 | } 187 | 188 | function generateEntry(options){ 189 | const entryPath = path.resolve('build/modules.js'); 190 | let content = fs.readFileSync(entryPath,'utf8'); 191 | let addedPage = `'${options.module}/js/${options.page}.js'`; 192 | let final = content.replace(` 193 | }`,` 194 | ,'${options.module + '-' + options.page}': ${addedPage} 195 | }`); 196 | 197 | fs.writeFileSync(entryPath,final,'utf8'); 198 | console.log(chalk.green(`build/modules.js增加了:`+ addedPage)); 199 | 200 | } 201 | 202 | 203 | module.exports = generate_native; -------------------------------------------------------------------------------- /packages/commands/generate/generate_vue_ssr.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const chalk = require('chalk'); 4 | 5 | // 获取命令携带的参数 6 | const modulePage = process.argv[3] || ''; 7 | const CSS = `@import url('../../common/css/base.css'); 8 | `; 9 | 10 | var HTML = ''; 11 | var JS = ''; 12 | 13 | function generate_native(){ 14 | if(modulePage === ''){ 15 | console.log(chalk.yellow('[warning]: You have not specify a module to start, you can run "meet start [module]"')); 16 | return; 17 | } 18 | 19 | let nameArr = modulePage.split('-'); 20 | let page = nameArr.length == 2?nameArr[1]:'index'; 21 | let moduleDir = nameArr[0]; 22 | let targetPath = path.resolve(`client/${moduleDir}`); 23 | let options = { 24 | module: moduleDir, 25 | page, 26 | targetPath 27 | }; 28 | 29 | HTML = ` 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 模板页面 41 | 42 | 43 | 44 | 45 | `; 46 | 47 | JS = `import '../styles/${page}.css'`; 48 | 49 | let filePath = path.join(targetPath, page,`entry-client.js`); 50 | if(fs.existsSync(filePath)){ 51 | console.log(chalk.red('该页面已存在,如果需要覆盖,请先删除')); 52 | return; 53 | } 54 | 55 | copyModule(options); 56 | 57 | // 生成网页JavaScript、stylesheets目录,模板 58 | generateAssets(options); 59 | // 60 | // 生成router文件,配置router 61 | generateRouter(options); 62 | // 63 | // router/index.js 增加配置 64 | generateRouteIndex(options); 65 | // 66 | // 添加webpack entrys 67 | generateClientEntry(options); 68 | 69 | // 添加 webpack server entrys 70 | generateServerEntry(options); 71 | 72 | } 73 | 74 | function generateAssets(options){ 75 | 76 | HTML = HTML.replace('模板页面',`模板页面${options.page}`); 77 | 78 | if(!fs.existsSync(options.targetPath)){ 79 | fs.mkdirSync(options.targetPath); 80 | } 81 | 82 | let viewPath = path.resolve('client',options.module, options.page, 'app', `${options.page}.html`); 83 | 84 | fs.writeFileSync(viewPath, HTML, 'utf8'); 85 | console.log(chalk.green(`覆盖了文件:`+ viewPath)); 86 | } 87 | 88 | function generateRouter(options){ 89 | const routerPath = path.resolve('server/routes', `${options.module}-${options.page}` +'.js'); 90 | const ROUTER = ` 91 | const fs = require('fs'); 92 | const path = require('path'); 93 | const { createBundleRenderer } = require('vue-server-renderer'); 94 | const env = process.env.ENV !== 'prod' ? 'dev' : 'prod'; 95 | const bundle = fs.readFileSync(path.resolve('server/ssr_code/${options.module}/${options.page}-server.js'), 'utf8'); 96 | const renderer = createBundleRenderer(bundle, { 97 | template: fs.readFileSync(path.resolve('server/views/' + env + '/${options.module}/${options.page}.html'), 'utf8') 98 | }); 99 | 100 | module.exports.index = function(req, res){ 101 | 102 | if(req.query.ssr === 'true'){ 103 | const context = { url: req.url } 104 | console.log('render start', new Date().getTime()) 105 | renderer.renderToString(context, (err, html) => { 106 | if(err){ 107 | console.log(err); 108 | } 109 | console.log('render End', new Date().getTime()) 110 | res.end(html) 111 | }) 112 | } 113 | else{ 114 | res.render('${options.module}/${options.page}') 115 | } 116 | 117 | }; 118 | `; 119 | if(!fs.existsSync(routerPath)){ 120 | fs.writeFileSync(routerPath,ROUTER,'utf8'); 121 | console.log(chalk.green(`创建了router文件:`+ routerPath)); 122 | } 123 | 124 | } 125 | 126 | function generateRouteIndex(options) { 127 | let routeIndex = path.resolve('server/routes/index.js'); 128 | let content = fs.readFileSync(routeIndex,'utf8'); 129 | let content1= content; 130 | let fileName = `${options.module}_${options.page}`; 131 | let depName = `${options.module}-${options.page}`; 132 | if(content.indexOf(`const ${fileName} = require('./${depName}'); 133 | //<@add page@>`) === -1){ 134 | content1 = content.replace(`//<@add page@>`,`const ${fileName} = require('./${depName}'); 135 | //<@add page@>`); 136 | } 137 | 138 | let content2 = content1.replace('//<@add page router@>', `app.get('/${options.module}${options.page==='index'? '': '/' + options.page}', ${fileName}.index); 139 | //<@add page router@>`); 140 | fs.writeFileSync(routeIndex,content2,'utf8'); 141 | console.log(chalk.green(`server/router/index.js增加了router配置:`)); 142 | } 143 | 144 | function generateClientEntry(options){ 145 | const entryPath = path.resolve('build/modules-client.js'); 146 | let content = fs.readFileSync(entryPath,'utf8'); 147 | let addedPage = `'client/${options.module}/${options.page}/entry-client.js'`; 148 | let final = content.replace(` 149 | }`,` 150 | ,'${options.module + '-' + options.page}': ${addedPage} 151 | }`); 152 | 153 | fs.writeFileSync(entryPath,final,'utf8'); 154 | console.log(chalk.green(`build/modules-client.js增加了:`+ addedPage)); 155 | } 156 | 157 | 158 | function generateServerEntry(options){ 159 | const entryPath = path.resolve('build/modules-server.js'); 160 | let content = fs.readFileSync(entryPath,'utf8'); 161 | let addedPage = `'client/${options.module}/${options.page}/entry-server.js'`; 162 | let final = content.replace(` 163 | }`,` 164 | ,'${options.module + '-' + options.page}': ${addedPage} 165 | }`); 166 | 167 | fs.writeFileSync(entryPath,final,'utf8'); 168 | console.log(chalk.green(`build/modules-server.js增加了:`+ addedPage)); 169 | } 170 | 171 | function copyModule(options){ 172 | // module文件夹检测是否存在,不存在则创建 173 | if(!fs.existsSync(options.targetPath)){ 174 | fs.mkdirSync(options.targetPath); 175 | } 176 | // page文件夹检测是否存在,不存在则创建 177 | let targetPath = path.resolve(options.targetPath, options.page); 178 | if(!fs.existsSync(targetPath)){ 179 | fs.mkdirSync(targetPath); 180 | } 181 | const templatePath = path.join(__dirname,'../../../', 'templates_vue_ssr/module'); 182 | createDirectoryContents(templatePath, targetPath); 183 | } 184 | 185 | function createDirectoryContents (templatePath, newProjectPath) { 186 | const filesToCreate = fs.readdirSync(templatePath); 187 | 188 | filesToCreate.forEach(file => { 189 | const origFilePath = `${templatePath}/${file}`; 190 | 191 | // get stats about the current file 192 | const stats = fs.statSync(origFilePath); 193 | 194 | if (stats.isFile()) { 195 | const contents = fs.readFileSync(origFilePath, 'utf8'); 196 | const writePath = `${newProjectPath}/${file}`; 197 | 198 | console.log(chalk.green(`创建了文件:`+ writePath)); 199 | fs.writeFileSync(writePath, contents, 'utf8'); 200 | } 201 | else if (stats.isDirectory()) { 202 | fs.mkdirSync(`${newProjectPath}/${file}`); 203 | 204 | // recursive call 205 | createDirectoryContents(`${templatePath}/${file}`, `${newProjectPath}/${file}`); 206 | } 207 | }); 208 | 209 | } 210 | 211 | 212 | module.exports = generate_native; -------------------------------------------------------------------------------- /packages/commands/git.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const inquirer = require('inquirer'); 3 | const shellHelper = require('../libs/shellHelper'); 4 | 5 | 6 | function gitCommit(){ 7 | // 发布,提示输入commit 信息 8 | inquirer.prompt([ 9 | { 10 | name:'message', 11 | type:'input', 12 | message:`Enter your publish message \n ` 13 | } 14 | ]) 15 | .then(answers=>{ 16 | let message = answers.message; 17 | shellHelper.series([ 18 | 'git pull', 19 | 'git add .', 20 | `git commit -m "${message}"`, 21 | 'git push', 22 | ], function(err){ 23 | if(err){ 24 | console.log(chalk.red(err)); 25 | process.exit(0); 26 | } 27 | console.log(chalk.green('Git push finished!')); 28 | process.exit(0); 29 | }); 30 | }) 31 | .catch(err=>{ 32 | console.log(chalk.red(err)); 33 | }); 34 | } 35 | 36 | module.exports = gitCommit; -------------------------------------------------------------------------------- /packages/commands/initial.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | const figlet = require('figlet'); 6 | const CURR_DIR = process.cwd(); 7 | 8 | const QUESTIONS = [ 9 | { 10 | name: 'project-name', 11 | type: 'input', 12 | message: 'Project name:', 13 | validate: function (input) { 14 | if (/^([A-Za-z\-\_\d])+$/.test(input)) return true; 15 | else return 'Project name may only include letters, numbers, underscores and hashes.'; 16 | } 17 | }, 18 | { 19 | name: 'project-type', 20 | type: 'list', 21 | message: 'Select your JavaScript framework', 22 | choices:[ 23 | 'No JavaScript framework', 24 | 'Vue width SSR' 25 | ] 26 | } 27 | ]; 28 | 29 | var projectName = ''; 30 | 31 | function createDirectoryContents (templatePath, newProjectPath) { 32 | const filesToCreate = fs.readdirSync(templatePath); 33 | 34 | filesToCreate.forEach(file => { 35 | const origFilePath = `${templatePath}/${file}`; 36 | 37 | // get stats about the current file 38 | const stats = fs.statSync(origFilePath); 39 | 40 | if (stats.isFile()) { 41 | const contents = fs.readFileSync(origFilePath, 'utf8'); 42 | // Rename 43 | if (file === '.npmignore') file = '.gitignore'; 44 | const writePath = `${CURR_DIR}/${newProjectPath}/${file}`; 45 | fs.writeFileSync(writePath, contents, 'utf8'); 46 | } 47 | else if (stats.isDirectory()) { 48 | fs.mkdirSync(`${CURR_DIR}/${newProjectPath}/${file}`); 49 | 50 | // recursive call 51 | createDirectoryContents(`${templatePath}/${file}`, `${newProjectPath}/${file}`); 52 | } 53 | }); 54 | 55 | } 56 | 57 | function complete () { 58 | figlet('Meet CLI', function(err, data) { 59 | if(err){ 60 | console.log(chalk.red('Some thing about figlet is wrong!')); 61 | } 62 | 63 | console.log(chalk.yellow(data)); 64 | console.log(chalk.green(` [Success] Project ${projectName} init finished, be pleasure to use 😊!`)); 65 | console.log(); 66 | console.log(' Install dependencies:'); 67 | console.log(chalk.magenta(` cd ${projectName} && npm install`)); 68 | console.log(); 69 | console.log(' Run the app:'); 70 | console.log(chalk.magenta(' meet start demo')); 71 | console.log(' Or:'); 72 | console.log(chalk.magenta(' pm2 start process.json')); 73 | 74 | }); 75 | 76 | } 77 | 78 | module.exports = function(){ 79 | inquirer.prompt(QUESTIONS) 80 | .then(answers => { 81 | projectName = answers['project-name']; 82 | let templateDir = getTemplateDir(answers['project-type']); 83 | const templatePath = path.join(__dirname,'../../',templateDir); 84 | 85 | fs.mkdirSync(`${CURR_DIR}/${projectName}`); 86 | 87 | setTimeout(()=>complete(),1000); 88 | createDirectoryContents(templatePath, projectName); 89 | }); 90 | }; 91 | 92 | function getTemplateDir(templateType) { 93 | let templateDir = ''; 94 | switch (templateType) { 95 | case 'No JavaScript framework': templateDir = 'templates_no_framework'; break; 96 | case 'Vue width SSR': templateDir = 'templates_vue_ssr/project'; break; 97 | default : templateDir = 'templates_no_framework'; break 98 | } 99 | return templateDir; 100 | } -------------------------------------------------------------------------------- /packages/commands/start.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | const chalk = require('chalk'); 3 | 4 | module.exports = function start(){ 5 | 6 | // 获取命令携带的参数 7 | const argvStr = process.argv[3] || ''; 8 | 9 | if(argvStr === ''){ 10 | console.log(chalk.yellow('[warning]: You have not specify a module to start, you can run "meet start [module]"')); 11 | return; 12 | } 13 | 14 | shell.exec(`npm run start ${argvStr}`, function(err){ 15 | if(err){ 16 | console.log(chalk.red(err)); 17 | process.exit(0); 18 | } 19 | }); 20 | 21 | } -------------------------------------------------------------------------------- /packages/commands/upload.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const shell = require('shelljs'); 3 | 4 | module.exports = function(){ 5 | 6 | shell.exec(`npm run upload`, function(err){ 7 | if(err){ 8 | console.log(chalk.red(err)); 9 | process.exit(0); 10 | } 11 | }); 12 | 13 | }; -------------------------------------------------------------------------------- /packages/libs/filetype.js: -------------------------------------------------------------------------------- 1 | const extension = { 2 | js: ['.js'], 3 | html: ['.html'], 4 | css: ['.css'], 5 | image: ['.png','.jpg','.webp','.gif','.svg'], 6 | media: ['.mp3','.mpa','.wav','.mp4','.wmv','.swf','.mov','.avi'] 7 | }; 8 | 9 | function detectExtension(ext){ 10 | for(let key in extension){ 11 | let typeArr = extension[key]; 12 | if(typeArr.indexOf(ext)>-1){ 13 | return key; 14 | } 15 | } 16 | return 'other'; 17 | } 18 | 19 | module.exports = detectExtension; -------------------------------------------------------------------------------- /packages/libs/meetyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linweiwei123/multipages-generator/a3ed5b93ca64b9f5a181d5416820207777b5f1ba/packages/libs/meetyou.png -------------------------------------------------------------------------------- /packages/libs/shellHelper.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const shell = require('shelljs'); 3 | 4 | // execute a single shell command where "cmd" is a string 5 | exports.exec = function(cmd, cb){ 6 | // this would be way easier on a shell/bash script :P 7 | var child_process = require('child_process'); 8 | var parts = cmd.split(/\s+/g); 9 | var p = child_process.spawn(parts[0], parts.slice(1), {stdio: 'inherit'}); 10 | p.on('exit', function(code){ 11 | var err = null; 12 | if (code) { 13 | err = new Error('command "'+ cmd +'" exited with wrong status code "'+ code +'"'); 14 | err.code = code; 15 | err.cmd = cmd; 16 | } 17 | if (cb) cb(err); 18 | }); 19 | }; 20 | 21 | 22 | // execute multiple commands in series 23 | // this could be replaced by any flow control lib 24 | exports.series = function(cmds, cb){ 25 | var execNext = function(){ 26 | let cmd = cmds.shift(); 27 | console.log(chalk.blue('run command: ') + chalk.magenta(cmd)); 28 | shell.exec(cmd, function(err){ 29 | if (err) { 30 | cb(err); 31 | } else { 32 | if (cmds.length) execNext(); 33 | else cb(null); 34 | } 35 | }); 36 | }; 37 | execNext(); 38 | }; -------------------------------------------------------------------------------- /packages/libs/spinner.js: -------------------------------------------------------------------------------- 1 | const CLI = require('clui'); 2 | const Spinner = CLI.Spinner; 3 | 4 | module.exports = function(message){ 5 | var time = 0; 6 | var countdown = new Spinner(message, ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷']); 7 | 8 | countdown.start(); 9 | 10 | setInterval(function () { 11 | time++; 12 | countdown.message(`${message}, lasted ${time} seconds. `); 13 | }, 1000); 14 | 15 | this.stop = function(){ 16 | countdown.stop(); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/libs/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | function isVueTemplate(){ 4 | console.log('pakcage.json的路径',path.resolve('package.json')); 5 | let template = require(path.resolve('package.json')).template; 6 | return template === 'vue_ssr' 7 | } 8 | 9 | module.exports = { 10 | isVueTemplate 11 | } -------------------------------------------------------------------------------- /templates_no_framework/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea 61 | 62 | .idea/*.xml 63 | 64 | dist/ 65 | dest/ 66 | public/ 67 | package-lock.json -------------------------------------------------------------------------------- /templates_no_framework/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /templates_no_framework/README.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | # 安装 3 | npm install 4 | 5 | # 启动 6 | npm run start demo 7 | 8 | ## 发布 9 | npm run build demo 10 | 11 | # pm2启动 12 | pm2 start process.json 13 | -------------------------------------------------------------------------------- /templates_no_framework/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var debug = require('debug')('server:server'); 4 | var http = require('http'); 5 | var express = require('express'); 6 | var path = require('path'); 7 | var favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | var cookieParser = require('cookie-parser'); 10 | var bodyParser = require('body-parser'); 11 | var router = require('./server/routes/index'); 12 | var app = express(); 13 | var server = http.createServer(app); 14 | 15 | var serverPort = require('./mg.config').server.port; 16 | var port = normalizePort(process.env.PORT || serverPort || '8090'); 17 | app.set('port', port); 18 | 19 | 20 | // views目标设置为ejs与html 21 | app.engine('html', require('ejs').renderFile); 22 | app.set('views',path.join(__dirname,'server/views',process.env.ENV !== 'prod' ? 'dev' : 'prod')); 23 | app.set('view engine', 'html'); 24 | 25 | // uncomment after placing your favicon in /public 26 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 27 | app.use(logger('dev')); 28 | app.use(bodyParser.json()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(cookieParser()); 31 | app.use(express.static(path.join(__dirname, 'static'))); 32 | if(process.env.ENV !== 'prod'){ 33 | app.use(express.static(path.join(__dirname, 'client'))); 34 | } 35 | 36 | app.use(function(req, res, next) { 37 | res.header("Access-Control-Allow-Origin", "*"); 38 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 39 | next(); 40 | }); 41 | 42 | router.init(app); 43 | 44 | // catch 404 and forward to error handler 45 | app.use(function(req, res, next) { 46 | var err = new Error('Not Found'); 47 | err.status = 404; 48 | next(err); 49 | }); 50 | 51 | // error handler 52 | app.use(function(err, req, res, next) { 53 | // set locals, only providing error in development 54 | res.locals.message = err.message; 55 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 56 | 57 | // render the error page 58 | res.status(err.status || 500); 59 | console.log(err); 60 | res.render('error'); 61 | }); 62 | 63 | 64 | /** 65 | * Listen on provided port, on all network interfaces. 66 | */ 67 | 68 | server.listen(port); 69 | server.on('error', onError); 70 | server.on('listening', onListening); 71 | 72 | /** 73 | * Normalize a port into a number, string, or false. 74 | */ 75 | 76 | function normalizePort(val) { 77 | var port = parseInt(val, 10); 78 | 79 | if (isNaN(port)) { 80 | // named pipe 81 | return val; 82 | } 83 | 84 | if (port >= 0) { 85 | // port number 86 | return port; 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /** 93 | * Event listener for HTTP server "error" event. 94 | */ 95 | 96 | function onError(error) { 97 | if (error.syscall !== 'listen') { 98 | throw error; 99 | } 100 | 101 | var bind = typeof port === 'string' 102 | ? 'Pipe ' + port 103 | : 'Port ' + port; 104 | 105 | // handle specific listen errors with friendly messages 106 | switch (error.code) { 107 | case 'EACCES': 108 | console.error(bind + ' requires elevated privileges'); 109 | process.exit(1); 110 | break; 111 | case 'EADDRINUSE': 112 | console.error(bind + ' is already in use'); 113 | process.exit(1); 114 | break; 115 | default: 116 | throw error; 117 | } 118 | } 119 | 120 | /** 121 | * Event listener for HTTP server "listening" event. 122 | */ 123 | 124 | function onListening() { 125 | var addr = server.address(); 126 | var bind = typeof addr === 'string' 127 | ? 'pipe ' + addr 128 | : 'port ' + addr.port; 129 | debug('Listening on ' + bind); 130 | } 131 | -------------------------------------------------------------------------------- /templates_no_framework/build/commands/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const chalk = require('chalk'); 4 | const del = require('delete'); 5 | const doUpload = require('../libs/doupload'); 6 | const Spinner = require('../libs/spinner'); 7 | const argvStr = process.argv.slice(-1)[0]; 8 | const mgConfig = require('../../mg.config.js'); 9 | 10 | // 如果未指定编译模块,则提示返回 11 | if(argvStr === ''){ 12 | console.log(chalk.yellow('[warning]: You have not specify a module to build, you can run "meet build [module]"')); 13 | return; 14 | } 15 | 16 | // 删除dist下的所有文件 17 | console.log(chalk.blue('Delete dist directory!')); 18 | del.sync([`./dist/**`]); 19 | 20 | // 获取命令携带的参数 21 | const webpackConfig = require(path.resolve('build/webpack/webpack.config.js')); 22 | const spinner = new Spinner('Building...\n'); 23 | 24 | webpack(webpackConfig, (err, stats) => { 25 | spinner.stop(); 26 | 27 | if (err) throw err; 28 | 29 | process.stdout.write(stats.toString({ 30 | colors: true, 31 | modules: false, 32 | children: false, 33 | chunks: false, 34 | chunkModules: false 35 | }) + '\n'); 36 | 37 | // 如果配置了自动上传则立即上传 38 | if(mgConfig.upload.autoUpload === true){ 39 | doUpload(); 40 | } 41 | 42 | }); -------------------------------------------------------------------------------- /templates_no_framework/build/commands/ocstart.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { exec } = require('child_process'); 4 | const express = require('express'); 5 | const webpack = require('webpack'); 6 | const webpackDevMiddleware = require('webpack-dev-middleware'); 7 | const webpackHotMiddleware = require('webpack-hot-middleware'); 8 | const proxy = require('http-proxy-middleware'); 9 | const chalk = require('chalk'); 10 | const Spinner = require('../libs/spinner'); 11 | const mgConfig = require(path.resolve('mg.config.js')); 12 | const projectName = process.argv[2] || ''; 13 | const port = mgConfig.clientPort; 14 | 15 | // 提示未创建项目 16 | const projectPath = path.resolve('client', projectName); 17 | if (!fs.existsSync(projectPath)) { 18 | console.log(chalk.yellow(`[Warn] ${projectPath} is not exist, you can run "npm run new ${projectName}" to create.`)); 19 | process.exit(); 20 | } 21 | 22 | const webpackConfig = require('../webpack/webpack.config'); 23 | const ip = require('../libs/ip'); 24 | const app = express(); 25 | 26 | // 启动代理服务端 27 | const processServer = exec(`npm run server`); 28 | 29 | processServer.stdout.on('data', stats => { 30 | process.stdout.write(stats.toString({ 31 | colors: true, 32 | modules: false, 33 | children: false, 34 | chunks: false, 35 | chunkModules: false 36 | }) + '\n'); 37 | }); 38 | 39 | // 编译 + 启动开发服务端 40 | const spinner = new Spinner('Building...\n'); 41 | const compiler = webpack(webpackConfig); 42 | 43 | compiler.plugin('done', function(){ 44 | setTimeout(() => { 45 | spinner.stop(); 46 | console.log(chalk.bgGreen('\n √ Build done ') + '\n'); 47 | console.log(chalk.magenta(`[Tips] visit: http://localhost:${port}/${projectName}/`)); 48 | console.log(chalk.magenta(` : http://${ip()}:${port}/${projectName}/`) + '\n'); 49 | }, 0); 50 | }); 51 | 52 | const middleware = webpackDevMiddleware(compiler, { 53 | publicPath: webpackConfig.output.publicPath, 54 | // html only 55 | writeToDisk: filePath => /\.html$/.test(filePath), 56 | }); 57 | 58 | app.use(middleware); 59 | app.use(webpackHotMiddleware(compiler)); 60 | app.use(express.static(path.resolve(__dirname, '../../public/assets'))); 61 | app.use(proxy(`http://localhost:${mgConfig.server.port}`)); 62 | app.listen(8080); 63 | -------------------------------------------------------------------------------- /templates_no_framework/build/commands/upload.js: -------------------------------------------------------------------------------- 1 | const doUpload = require('../libs/doupload'); 2 | 3 | doUpload(); -------------------------------------------------------------------------------- /templates_no_framework/build/libs/aliupload.js: -------------------------------------------------------------------------------- 1 | const oss = require('ali-oss'); 2 | const fs = require('fs'); 3 | const co = require('co'); 4 | 5 | function upload (config, file, filename) { 6 | 7 | let defaultConfig = { 8 | }; 9 | 10 | if (fs.existsSync(aliossaccess)) { 11 | const access = JSON.parse(fs.readFileSync(aliossaccess, 'utf-8')); 12 | 13 | defaultConfig = Object.assign(defaultConfig, access); 14 | } 15 | 16 | const ossPath = `${config.prefix}/${filename}`; 17 | const store = oss(Object.assign(defaultConfig, config)); 18 | 19 | return new Promise((resovle, reject) => { 20 | co(function* () { 21 | return yield store.list({ 22 | prefix: ossPath, 23 | }); 24 | }).then(data => { 25 | if (config.deduplication !== true || (config.deduplication === true && typeof data.objects === 'undefined')) { 26 | co(function* () { 27 | return yield store.put(ossPath, file); 28 | }) 29 | .then(res => { 30 | resovle(res); 31 | }) 32 | .catch(err => { 33 | reject(err); 34 | }); 35 | } else { 36 | reject(new Error('文件已存在')); 37 | } 38 | }); 39 | }); 40 | 41 | } 42 | 43 | module.exports = upload; 44 | -------------------------------------------------------------------------------- /templates_no_framework/build/libs/doupload.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const chalk = require('chalk'); 3 | const qupload = require('./qupload'); 4 | const aliupload = require('meetyou-ali-oss'); 5 | const mgConfig = require('../../mg.config.js'); 6 | 7 | function doUpload(){ 8 | 9 | var aliconfig = mgConfig.upload.aliconfig; 10 | var qconfig = mgConfig.upload.qconfig; 11 | var distPath = path.resolve('dist'); 12 | 13 | if(typeof aliconfig === 'object'){ 14 | aliupload({ 15 | "srcDir": path.resolve('dist'), 16 | "ignoreDir": false, 17 | "deduplication": true, 18 | "prefix": mgConfig.upload.projectPrefix 19 | }); 20 | return; 21 | } 22 | 23 | if(typeof qconfig === 'object'){ 24 | console.log(chalk.greenBright('\n' + 'Upload dist files to Qiniu CDN:' )); 25 | qupload(distPath); 26 | return; 27 | } 28 | } 29 | 30 | module.exports = doUpload; -------------------------------------------------------------------------------- /templates_no_framework/build/libs/ip.js: -------------------------------------------------------------------------------- 1 | const OS = require('os'); 2 | 3 | module.exports = function getIP() { 4 | const interfaces = OS.networkInterfaces(); 5 | for (let devName in interfaces) { 6 | const iface = interfaces[devName]; 7 | for (let i = 0; i < iface.length; i++) { 8 | const alias = iface[i]; 9 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 10 | return alias.address; 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /templates_no_framework/build/libs/qupload.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const qn = require('qn'); 3 | const fs = require('fs'); 4 | const chalk = require('chalk'); 5 | const { qconfig } = require(path.resolve('mg.config.js')).upload; 6 | 7 | var fileTotal = 0; 8 | var fileUploadCount = 0; 9 | 10 | var client = qn.create({ 11 | accessKey: qconfig.ACCESS_KEY, 12 | secretKey: qconfig.SECRET_KEY, 13 | bucket: qconfig.bucket, 14 | origin: qconfig.origin, 15 | }); 16 | 17 | function qnUpload(filePath,filename){ 18 | client.uploadFile(filePath, {key: filename}, function (err, result) { 19 | if(err){ 20 | console.log(chalk.red(`[Error]: 上传文件失败:${err.toString()}`)); 21 | return; 22 | } 23 | console.log(chalk.greenBright(`[Success]: 上传文件至七牛云CDN成功!文件地址:${result.url}`)); 24 | 25 | fileUploadCount++; 26 | // 上传完毕则退出 27 | if(fileTotal === fileUploadCount){ 28 | console.log(chalk.greenBright('[Success]: 上传完毕 😊!')); 29 | console.log('Use Ctrl+C to close it\n'); 30 | //process.exit(0); 31 | } 32 | }); 33 | } 34 | 35 | function readDicUpload(filePath,tempPath = ''){ 36 | let filesArr = []; 37 | try{ 38 | filesArr = fs.readdirSync(filePath) 39 | } 40 | catch (e) { 41 | console.log(chalk.red('[Error]: 要上传的文件路径srcDir不存在')); 42 | return; 43 | } 44 | filesArr.forEach(file => { 45 | const origFilePath = `${filePath}/${file}`; 46 | const stats = fs.statSync(origFilePath); 47 | if(stats.isFile()){ 48 | fileTotal++; 49 | qnUpload(origFilePath,`${tempPath}/${file}`); 50 | } 51 | else if(stats.isDirectory()){ 52 | readDicUpload(origFilePath,`${tempPath}/${file}`); 53 | } 54 | }) 55 | } 56 | 57 | module.exports = readDicUpload; -------------------------------------------------------------------------------- /templates_no_framework/build/libs/spinner.js: -------------------------------------------------------------------------------- 1 | const CLI = require('clui'); 2 | const Spinner = CLI.Spinner; 3 | 4 | module.exports = function(message){ 5 | var time = 0; 6 | var countdown = new Spinner(message, ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷']); 7 | 8 | countdown.start(); 9 | 10 | setInterval(function () { 11 | time++; 12 | countdown.message(`lasted ${time} seconds. `); 13 | }, 1000); 14 | 15 | this.stop = function(){ 16 | countdown.stop(); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /templates_no_framework/build/modules.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'demo-index': 'demo/js/index.js' 3 | }; -------------------------------------------------------------------------------- /templates_no_framework/build/webpack/hot-reload-plugin.js: -------------------------------------------------------------------------------- 1 | class HotReloadPlugin { 2 | constructor(options) { 3 | this.options = options; 4 | } 5 | 6 | apply(compiler) { 7 | 8 | compiler.plugin("emit", function(compilation,callback) { 9 | 10 | for(var file in compilation.assets){ 11 | if(file.endsWith('.html')){ 12 | 13 | const html = compilation.assets[file].source(); 14 | const JSReg = /<[^<>]+data-hr="hot-reload[^<>]+>[^<>]*<\/script>/g; 15 | const CSSReg = /<[^<>]+data-hr="hot-reload"[^<>]+>/g; 16 | 17 | var html0 = html.replace(JSReg,''); 18 | var html1 = html0.replace(CSSReg,''); 19 | 20 | console.log('HTML去除开发环境hotReload代码: ' + file); 21 | 22 | compilation.assets[file] = { 23 | source() { 24 | return html1; 25 | }, 26 | size() { 27 | return html1.length; 28 | } 29 | }; 30 | } 31 | } 32 | 33 | callback(); 34 | 35 | }); 36 | } 37 | } 38 | 39 | module.exports = HotReloadPlugin; -------------------------------------------------------------------------------- /templates_no_framework/build/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const webpack = require('webpack'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 8 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | const HotReloadPlugin = require('../webpack/hot-reload-plugin'); 10 | const mgConfig = require(path.resolve('mg.config.js')); 11 | 12 | // 编译的参数 13 | const argvStr = process.argv.slice(-1)[0]; 14 | const isDev = process.env.NODE_ENV === 'development'; 15 | const options = getOptions(argvStr); 16 | const clientPath = path.resolve('client'); 17 | const viewsPath = path.resolve('server/views',isDev ? 'dev' : 'prod'); 18 | const modulePath = path.resolve(clientPath, options.module); 19 | const outputPath = path.resolve('dist'); 20 | const { entry, htmlPlugins } = readFiles(); 21 | const publicPath = isDev? '/' : mgConfig.upload.cdn; 22 | 23 | 24 | module.exports = { 25 | mode: isDev ? 'development' : 'production', 26 | devtool: isDev ? 'cheap-module-eval-source-map' : 'none', 27 | entry: entry, 28 | output: { 29 | path: outputPath, 30 | publicPath: `${publicPath}`, 31 | filename: isDev ? '[name].[hash].js' : '[name].[chunkhash:8].js', 32 | }, 33 | module: { 34 | rules: [ 35 | { 36 | test: /\.js$/, 37 | use: 'babel-loader', 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.html$/, 42 | use: { 43 | loader: 'html-loader', 44 | options: { 45 | minimize: !isDev, 46 | removeAttributeQuotes: false, 47 | } 48 | } 49 | }, 50 | { 51 | test: /\.(le|c)ss$/, 52 | use: [ 53 | isDev ? 'style-loader' : MiniCssExtractPlugin.loader, 54 | 'css-loader', 55 | 'postcss-loader', 56 | 'less-loader' 57 | ] 58 | }, 59 | { 60 | test: /\.scss$/, 61 | use: [ 62 | isDev ? 'style-loader' : MiniCssExtractPlugin.loader, 63 | 'css-loader', 64 | 'postcss-loader', 65 | 'sass-loader' 66 | ] 67 | }, 68 | { 69 | test: /\.(jpe?g|png|gif|svg)$/, 70 | use: { 71 | loader: 'url-loader', 72 | options: { 73 | limit: 10000, 74 | name: '[hash:8].[ext]' 75 | } 76 | } 77 | }, 78 | { 79 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 80 | loader: 'url-loader', 81 | options: { 82 | limit: 10000, 83 | name: '[hash:8].[ext]' 84 | } 85 | }, 86 | { 87 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 88 | loader: 'url-loader', 89 | options: { 90 | limit: 10000, 91 | name: '[hash:8].[ext]' 92 | } 93 | } 94 | ] 95 | }, 96 | 97 | plugins: [ 98 | ...htmlPlugins, 99 | 100 | // 针对 moment.js 打包体积过大问题 101 | new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/) 102 | ], 103 | optimization: { 104 | splitChunks: { 105 | cacheGroups: { 106 | vendor: { 107 | name: 'vendor', // 与 output.filename 一致, 即为 'vendor.[chunckhash:8].js' 108 | chunks: 'all', 109 | test: /node_modules/, 110 | enforce: true 111 | } 112 | } 113 | }, 114 | }, 115 | resolve: { 116 | extensions: ['.js', '.vue', '.less', 'scss', '.json'], 117 | alias: { 118 | 'vue$': 'vue/dist/vue.js', 119 | '@': modulePath, 120 | } 121 | }, 122 | performance: { 123 | assetFilter: function(assetFilename) { 124 | return assetFilename.endsWith('.js'); 125 | }, 126 | hints: isDev ? false : 'warning' // 当打包的文件大于 244 KiB 是会在提出警告 127 | } 128 | }; 129 | 130 | if (isDev) { 131 | // 开发模式 132 | module.exports.plugins.push( 133 | new webpack.HotModuleReplacementPlugin(), 134 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 135 | new webpack.NoEmitOnErrorsPlugin() 136 | ); 137 | } else { 138 | // 预发/生产模式 139 | module.exports.optimization = { 140 | splitChunks: { 141 | cacheGroups: { 142 | vendor: { 143 | name: 'vendor', // 与 output.filename 一致, 即为 'vendor.[chunckhash:8].js' 144 | chunks: 'initial', 145 | test: /node_modules/, 146 | enforce: true 147 | } 148 | } 149 | }, 150 | minimize: true, 151 | minimizer: [ 152 | new OptimizeCSSAssetsPlugin(),// 压缩 css 文件 153 | new UglifyJsPlugin({ 154 | test: /\.js$/, 155 | exclude: /\/node_modules/, 156 | // cache: resolve(outputPath, '..', '.cache'), 157 | parallel: true,// 并行打包 158 | uglifyOptions: { 159 | compress: { 160 | drop_console: true // 去掉所有console.* 161 | } 162 | } 163 | }), 164 | ] 165 | }; 166 | 167 | module.exports.plugins.push( 168 | new MiniCssExtractPlugin({ 169 | filename: '[name].[contenthash:8].css', 170 | chunkFilename: '[id].[contenthash:8].css' 171 | }), 172 | new HotReloadPlugin({ 173 | setting: true 174 | }), 175 | new BundleAnalyzerPlugin({ 176 | analyzerMode: 'static' 177 | }) 178 | ); 179 | } 180 | 181 | // 获取人口文件和对应的html 182 | function readFiles() { 183 | const jsDir = resolve(modulePath, 'js'); 184 | const htmlDir = resolve(modulePath, 'views'); 185 | const entry = {}; 186 | const htmlPlugins = []; 187 | 188 | fs.readdirSync(htmlDir).forEach(file => { 189 | const filename = file.match(/(.+)\.html$/)[1]; 190 | const htmlname = `${filename}.html`; 191 | const htmlTemplate = resolve(htmlDir, htmlname); 192 | const cssFile = path.resolve(modulePath,'styles',options.page + '.css') 193 | 194 | entry[filename] = [ 195 | resolve(jsDir, filename + '.js'), 196 | ...(isDev ? [htmlTemplate,'webpack-hot-middleware/client?reload=true'] : []) 197 | ]; 198 | 199 | 200 | htmlPlugins.push( 201 | new HtmlWebpackPlugin({ 202 | template: htmlTemplate, 203 | filename: resolve(`${viewsPath}/${options.module}/${htmlname}`), 204 | chunks: ['vendor', filename], 205 | }) 206 | ); 207 | }); 208 | 209 | return { 210 | entry, 211 | htmlPlugins 212 | }; 213 | } 214 | 215 | 216 | function getOptions(argvStr){ 217 | let obj = {}; 218 | let nameArr = argvStr.split('-'); 219 | obj.module = nameArr[0]; 220 | obj.page = nameArr.length === 2? nameArr[1] : 'index'; 221 | return obj; 222 | } 223 | 224 | function resolve(...dir) { 225 | return path.resolve(__dirname, ...dir); 226 | } -------------------------------------------------------------------------------- /templates_no_framework/client/common/css/base.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | box-sizing: inherit; 7 | } 8 | 9 | html { 10 | font-size: 10px; 11 | height: 100%; 12 | min-height: 100%; 13 | max-width: 750px; 14 | margin: 0 auto; 15 | box-sizing: border-box; 16 | user-select: none; 17 | text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | body { 22 | position: relative; 23 | background-color: #fff; 24 | color: #323232; 25 | font-family: STHeiti,Microsoft YaHei,Helvetica,Arial,sans-serif; 26 | margin: 0; 27 | padding: 0; 28 | -webkit-touch-callout: none; 29 | -webkit-font-smoothing: antialiased; 30 | -webkit-overflow-scrolling: touch; 31 | overflow-scrolling: touch; 32 | word-break: break-word; 33 | } 34 | 35 | a:hover, 36 | a:active { 37 | outline: none; 38 | } 39 | 40 | input:focus, 41 | input:active, 42 | textarea:focus, 43 | textarea:active { 44 | outline: none; 45 | -moz-outline-style: none; 46 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 47 | } 48 | 49 | a { 50 | text-decoration: none; 51 | } 52 | 53 | /* remove the up and down arrow for input:number; */ 54 | input::-webkit-outer-spin-button, 55 | input::-webkit-inner-spin-button { 56 | -webkit-appearance: none !important; 57 | margin: 0; 58 | } 59 | 60 | input[type="number"] { 61 | -moz-appearance: textfield; 62 | } 63 | 64 | table { 65 | border-collapse: collapse; 66 | border-spacing: 0; 67 | } 68 | 69 | li, 70 | ul, 71 | ol { 72 | padding: 0; 73 | margin: 0; 74 | list-style: none; 75 | display: list-item; 76 | text-align: -webkit-match-parent; 77 | } 78 | 79 | /* Remove inner padding and border in Firefox */ 80 | ::-moz-focus-inner { 81 | padding: 0; 82 | border: 0; 83 | } 84 | 85 | ::-webkit-scrollbar { 86 | display: none; 87 | } 88 | 89 | img { 90 | max-width: 100%; 91 | height: auto; 92 | border: 0; 93 | -webkit-touch-callout: none; 94 | appearance: none; 95 | } 96 | 97 | h1, 98 | h2, 99 | h3, 100 | h4, 101 | h5, 102 | h6, 103 | p { 104 | margin: 0; 105 | font-weight: normal; 106 | } 107 | 108 | p { 109 | line-height: 18px; 110 | border: 0; 111 | outline: 0; 112 | max-height: 100%; 113 | } 114 | 115 | input, 116 | select { 117 | vertical-align: middle; 118 | } 119 | 120 | input, 121 | select, 122 | textarea { 123 | -webkit-appearance: none; 124 | } 125 | 126 | ::-moz-focus-inner { 127 | padding: 0; 128 | border: 0; 129 | } 130 | 131 | article, 132 | aside, 133 | details, 134 | figcaption, 135 | figure, 136 | footer, 137 | header, 138 | hgroup, 139 | main, 140 | menu, 141 | nav, 142 | section { 143 | display: block; 144 | } 145 | 146 | blockquote, 147 | q { 148 | quotes: none; 149 | } 150 | 151 | blockquote::before, 152 | blockquote::after, 153 | q::before, 154 | q::after { 155 | content: ""; 156 | content: none; 157 | } 158 | 159 | /* 160 | * 1. Address `overflow` set to `hidden` in IE 10/11. 161 | * 2. Improve consistency of cursor style 162 | */ 163 | button { 164 | overflow: visible; 165 | cursor: pointer; 166 | } 167 | 168 | .hidden { 169 | display: none !important; 170 | opacity: 0 !important; 171 | } 172 | 173 | .clearfix::after { 174 | display: block; 175 | content: ""; 176 | visibility: hidden; 177 | height: 0; 178 | clear: both; 179 | } 180 | 181 | .clearfix { 182 | zoom: 1; 183 | } 184 | 185 | .clear::before, 186 | .clear::after { 187 | *zoom: 1; 188 | display: table; 189 | content: ""; 190 | line-height: 0; 191 | } 192 | 193 | .clear::after { 194 | clear: both; 195 | } 196 | 197 | /* 0.5px线条 */ 198 | .line-bottom::after, 199 | .line-top::before { 200 | content: ""; 201 | position: absolute; 202 | background-color: #ddd; 203 | left: 0; 204 | height: 1px; 205 | width: 100%; 206 | -webkit-transform: scaleY(0.5); 207 | transform: scaleY(0.5); 208 | } 209 | 210 | .line-left::before, 211 | .line-right::after { 212 | content: ""; 213 | position: absolute; 214 | background-color: #ddd; 215 | top: 0; 216 | width: 1px; 217 | height: 100%; 218 | -webkit-transform: scaleX(0.5); 219 | transform: scaleX(0.5); 220 | } 221 | 222 | .line-bottom::after { 223 | bottom: 0; 224 | } 225 | 226 | .line-top::before { 227 | top: 0; 228 | } 229 | 230 | .line-left::before { 231 | left: 0; 232 | } 233 | 234 | .line-right::after { 235 | right: 0; 236 | } 237 | 238 | -------------------------------------------------------------------------------- /templates_no_framework/client/common/js/flexible.js: -------------------------------------------------------------------------------- 1 | !function(){var a="@charset \"utf-8\";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}html,body{font-family:sans-serif}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:before,q:after{content:''}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}ins,a{text-decoration:none}",b=document.createElement("style");if(document.getElementsByTagName("head")[0].appendChild(b),b.styleSheet)b.styleSheet.disabled||(b.styleSheet.cssText=a);else try{b.innerHTML=a}catch(c){b.innerText=a}}();!function(a,b){function c(){var b=f.getBoundingClientRect().width;b/i>540&&(b=540*i);var c=b/10;f.style.fontSize=c+"px",k.rem=a.rem=c}var d,e=a.document,f=e.documentElement,g=e.querySelector('meta[name="demo"]'),h=e.querySelector('meta[name="flexible"]'),i=0,j=0,k=b.flexible||(b.flexible={});if(g){console.warn("灏嗘牴鎹凡鏈夌殑meta鏍囩鏉ヨ缃缉鏀炬瘮渚�");var l=g.getAttribute("content").match(/initial\-scale=([\d\.]+)/);l&&(j=parseFloat(l[1]),i=parseInt(1/j))}else if(h){var m=h.getAttribute("content");if(m){var n=m.match(/initial\-dpr=([\d\.]+)/),o=m.match(/maximum\-dpr=([\d\.]+)/);n&&(i=parseFloat(n[1]),j=parseFloat((1/i).toFixed(2))),o&&(i=parseFloat(o[1]),j=parseFloat((1/i).toFixed(2)))}}if(!i&&!j){var p=(a.navigator.appVersion.match(/android/gi),a.navigator.appVersion.match(/iphone/gi)),q=a.devicePixelRatio;i=p?q>=3&&(!i||i>=3)?3:q>=2&&(!i||i>=2)?2:1:1,j=1/i}if(f.setAttribute("data-dpr",i),!g)if(g=e.createElement("meta"),g.setAttribute("name","viewport"),g.setAttribute("content","initial-scale="+j+", maximum-scale="+j+", minimum-scale="+j+", user-scalable=no"),f.firstElementChild)f.firstElementChild.appendChild(g);else{var r=e.createElement("div");r.appendChild(g),e.write(r.innerHTML)}a.addEventListener("resize",function(){clearTimeout(d),d=setTimeout(c,300)},!1),a.addEventListener("pageshow",function(a){a.persisted&&(clearTimeout(d),d=setTimeout(c,300))},!1),"complete"===e.readyState?e.body.style.fontSize=12*i+"px":e.addEventListener("DOMContentLoaded",function(){e.body.style.fontSize=12*i+"px"},!1),c(),k.dpr=a.dpr=i,k.refreshRem=c,k.rem2px=function(a){var b=parseFloat(a)*this.rem;return"string"==typeof a&&a.match(/rem$/)&&(b+="px"),b},k.px2rem=function(a){var b=parseFloat(a)/this.rem;return"string"==typeof a&&a.match(/px$/)&&(b+="rem"),b}}(window,window.lib||(window.lib={})); -------------------------------------------------------------------------------- /templates_no_framework/client/common/js/hot-reload.js: -------------------------------------------------------------------------------- 1 | (function(doc){ 2 | setTimeout(function(){ 3 | doc.head.querySelector('link[data-hr="hot-reload"]').remove(); 4 | doc.body.querySelector('script[data-hr="hot-reload"]').remove(); 5 | },300); 6 | })(document); -------------------------------------------------------------------------------- /templates_no_framework/client/demo/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linweiwei123/multipages-generator/a3ed5b93ca64b9f5a181d5416820207777b5f1ba/templates_no_framework/client/demo/images/1.jpg -------------------------------------------------------------------------------- /templates_no_framework/client/demo/js/index.js: -------------------------------------------------------------------------------- 1 | import '../styles/index.css' -------------------------------------------------------------------------------- /templates_no_framework/client/demo/js/index/business.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linweiwei123/multipages-generator/a3ed5b93ca64b9f5a181d5416820207777b5f1ba/templates_no_framework/client/demo/js/index/business.js -------------------------------------------------------------------------------- /templates_no_framework/client/demo/js/index/service.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linweiwei123/multipages-generator/a3ed5b93ca64b9f5a181d5416820207777b5f1ba/templates_no_framework/client/demo/js/index/service.js -------------------------------------------------------------------------------- /templates_no_framework/client/demo/js/index/utils.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linweiwei123/multipages-generator/a3ed5b93ca64b9f5a181d5416820207777b5f1ba/templates_no_framework/client/demo/js/index/utils.js -------------------------------------------------------------------------------- /templates_no_framework/client/demo/styles/index.css: -------------------------------------------------------------------------------- 1 | @import url('../../common/css/base.css'); 2 | 3 | 4 | html,body{ 5 | padding: 0; 6 | height: 100%; 7 | width: 100%; 8 | background-color: #dddddd!important; 9 | } 10 | 11 | 12 | @font-face { 13 | font-family: 'iconfont'; /* project id 608106 */ 14 | src: url('//at.alicdn.com/t/font_608106_93932aqg8hicz0k9.eot'); 15 | src: url('//at.alicdn.com/t/font_608106_93932aqg8hicz0k9.eot?#iefix') format('embedded-opentype'), 16 | url('//at.alicdn.com/t/font_608106_93932aqg8hicz0k9.woff') format('woff'), 17 | url('//at.alicdn.com/t/font_608106_93932aqg8hicz0k9.ttf') format('truetype'), 18 | url('//at.alicdn.com/t/font_608106_93932aqg8hicz0k9.svg#iconfont') format('svg'); 19 | } 20 | 21 | .mg-icon{ 22 | font-family: 'iconfont'; 23 | font-size: 36px;/*px*/ 24 | font-style: normal; 25 | font-weight: 300; 26 | } 27 | 28 | .header{ 29 | text-align: center; 30 | font-size: 48px; 31 | padding: 10px; 32 | color: #fff; 33 | background-color: #FF9800; 34 | margin-bottom: 2px; 35 | } 36 | 37 | .banner{ 38 | width: 100%; 39 | height: 350px; 40 | } 41 | 42 | .banner-img{ 43 | width: 100%; 44 | height: 350px; 45 | } 46 | 47 | .swiper-container { 48 | width: 100%; 49 | height: 100%; 50 | } 51 | 52 | .swiper-pagination-bullet { 53 | width: 0.206667rem; 54 | height: 0.206667rem; 55 | background: #fff; 56 | opacity: 0.6; 57 | } 58 | 59 | .swiper-pagination-bullet-active { 60 | width: 0.206667rem; 61 | height: 0.206667rem; 62 | background: #fff; 63 | opacity: 1; 64 | } 65 | 66 | .category{ 67 | width: 100%; 68 | height: 160px; 69 | padding: 10px 15px; 70 | box-sizing: border-box; 71 | display: flex; 72 | background-color: #ffffff; 73 | } 74 | 75 | .category:after{ 76 | display: block; 77 | content: ''; 78 | clear: both; 79 | } 80 | 81 | .category .item{ 82 | display: block; 83 | padding: 12px; 84 | box-sizing: border-box; 85 | width: 144px; 86 | } 87 | 88 | .category .item img{ 89 | width: 120px; 90 | height: 120px; 91 | border-radius: 100%; 92 | border: 1px solid #ddd; 93 | } 94 | 95 | .list-section{ 96 | width: 100%; 97 | margin-top: 10px; 98 | background-color: #fff; 99 | } 100 | 101 | .box{ 102 | width: 100%; 103 | height: 210px; 104 | display: flex; 105 | padding: 30px 0; 106 | } 107 | 108 | .box-image{ 109 | width: 300px; 110 | height: 100%; 111 | padding: 10px 20px; 112 | box-sizing: border-box; 113 | } 114 | 115 | .box-image img{ 116 | width: 100%; 117 | height: 100%; 118 | } 119 | 120 | .box-message{ 121 | width: 450px; 122 | height: 100%; 123 | box-sizing: border-box; 124 | padding: 0px 15px; 125 | position: relative; 126 | } 127 | 128 | .pd-title{ 129 | font-size: 32px;/*px*/ 130 | } 131 | .pd-sub-title{ 132 | background-color: rgb(253,251,236); 133 | font-size: 24px;/*px*/ 134 | color: rgb(201, 154, 123); 135 | margin-top: 20px; 136 | } 137 | 138 | .pd-option{ 139 | position: absolute; 140 | top: 120px; 141 | font-size: 24px;/*px*/ 142 | font-weight: 600; 143 | width: 420px; 144 | height: 30px; 145 | } 146 | 147 | .pd-option .pd-card{ 148 | position: absolute; 149 | right: 40px; 150 | } -------------------------------------------------------------------------------- /templates_no_framework/client/demo/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../head.html %> 5 | demo 6 | 7 | 8 | 9 | 10 | 11 |
MG最佳实践DEMO页面
12 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
进口深海鳕鱼500g(精选)
64 |
“还是个小孩就这么贪吃?”
65 |
66 | ¥18.8/份 67 | 68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |
进口深海鳕鱼500g(精选)
77 |
“还是个小孩就这么贪吃?”
78 |
79 | ¥18.8/份 80 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
进口深海鳕鱼500g(精选)
90 |
“还是个小孩就这么贪吃?”
91 |
92 | ¥18.8/份 93 | 94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
进口深海鳕鱼500g(精选)
103 |
“还是个小孩就这么贪吃?”
104 |
105 | ¥18.8/份 106 | 107 |
108 |
109 |
110 |
111 |
112 | 113 |
114 |
115 |
进口深海鳕鱼500g(精选)
116 |
“还是个小孩就这么贪吃?”
117 |
118 | ¥18.8/份 119 | 120 |
121 |
122 |
123 |
124 |
125 | 126 |
127 |
128 |
进口深海鳕鱼500g(精选)
129 |
“还是个小孩就这么贪吃?”
130 |
131 | ¥18.8/份 132 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /templates_no_framework/mg.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // 启动的客户端服务器端口 4 | clientPort: '8080', 5 | 6 | // 服务端服务器端口 7 | server: { 8 | port: '8090', 9 | }, 10 | 11 | // 上传相关配置 12 | upload: { 13 | cdn: '//cnd.yintage.com/', 14 | projectPrefix: 'nodejs-common', 15 | 16 | // 如果是阿里云,则aliconfig配置一个空对象,目前采用.aliossacess 文件配置的方式 17 | // aliconfig: { 18 | // 19 | // }, 20 | // 七牛云 21 | 22 | qconfig: { 23 | ACCESS_KEY: 'ei1uOdGpVLliA7kb50sLcV9i4wfYLPwt5v0shU10', 24 | SECRET_KEY: '-pFFIY-ew35Exyfcd67Sbaw40k15ah3UfZTFWFKF', 25 | bucket:'hotshots-image', 26 | origin:'http://cnd.yintage.com' 27 | }, 28 | 29 | // 是否编译后自动上传 30 | autoUpload: true 31 | 32 | } 33 | }; -------------------------------------------------------------------------------- /templates_no_framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mg-template", 3 | "version": "1.0.0", 4 | "description": "A excellent develop project template for mobile H5 !", 5 | "main": "app.js", 6 | "scripts": { 7 | "server": "cross-env ENV=development nodemon app.js", 8 | "start": "cross-env NODE_ENV=development node build/commands/ocstart.js", 9 | "build": "cross-env NODE_ENV=production node build/commands/build.js", 10 | "upload": "node build/commands/upload.js" 11 | }, 12 | "dependencies": { 13 | "ali-oss": "^4.12.2", 14 | "body-parser": "~1.18.2", 15 | "browser-sync": "^2.23.6", 16 | "cookie-parser": "~1.4.3", 17 | "cross-env": "^5.1.4", 18 | "debug": "~2.6.9", 19 | "ejs": "~2.5.7", 20 | "express": "~4.15.5", 21 | "http-proxy-middleware": "^0.19.0", 22 | "morgan": "~1.9.0", 23 | "nodemon": "^1.17.1", 24 | "postcss-px2rem": "^0.3.0", 25 | "serve-favicon": "~2.4.5" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.0.0-beta.40", 29 | "@babel/preset-env": "^7.0.0-beta.40", 30 | "autoprefixer": "^8.2.0", 31 | "babel-loader": "^8.0.0-beta.0", 32 | "chalk": "^2.4.1", 33 | "clui": "^0.3.6", 34 | "css-loader": "^0.28.10", 35 | "delete": "^1.1.0", 36 | "extract-text-webpack-plugin": "^3.0.2", 37 | "file-loader": "^1.1.11", 38 | "html-loader": "^0.5.5", 39 | "html-webpack-plugin": "^3.2.0", 40 | "inquirer": "^5.2.0", 41 | "jquery": "^3.3.1", 42 | "less": "^3.7.0", 43 | "less-loader": "^4.1.0", 44 | "meetyou-ali-oss": "^1.0.7", 45 | "mini-css-extract-plugin": "^0.4.2", 46 | "node-sass": "^4.9.3", 47 | "optimize-css-assets-webpack-plugin": "^5.0.1", 48 | "ora": "^3.0.0", 49 | "postcss-loader": "^2.1.3", 50 | "postcss-safe-parser": "^3.0.1", 51 | "progress-bar-webpack-plugin": "^1.11.0", 52 | "qn": "^1.3.0", 53 | "raw-loader": "^0.5.1", 54 | "rimraf": "^2.6.2", 55 | "sass-loader": "^7.1.0", 56 | "style-loader": "^0.20.3", 57 | "uglifyjs-webpack-plugin": "^1.3.0", 58 | "url-loader": "^1.1.1", 59 | "webpack": "^4.17.1", 60 | "webpack-bundle-analyzer": "^2.13.1", 61 | "webpack-dev-middleware": "^3.2.0", 62 | "webpack-hot-middleware": "^2.22.3" 63 | }, 64 | "keywords": [], 65 | "author": "", 66 | "license": "ISC", 67 | "nodemonConfig": { 68 | "ignore": [ 69 | "client/**", 70 | "dist/**", 71 | "build/**" 72 | ], 73 | "delay": "2500" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /templates_no_framework/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yitala on 2018/3/27. 3 | */ 4 | module.exports = { 5 | parser: 'postcss-safe-parser', 6 | plugins: { 7 | "postcss-px2rem":{ 8 | remUnit: 75 9 | }, 10 | autoprefixer: { 11 | browsers: [ 12 | 'last 2 versions', 13 | 'iOS >= 8' 14 | ] 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /templates_no_framework/process.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps":[{ 3 | "script" : "app.js", 4 | "instances": "4", 5 | "exec_mode": "cluster", 6 | "watch" : true, 7 | "env" :{ 8 | "ENV": "prod" 9 | }, 10 | "env_production":{ 11 | "ENV": "prod" 12 | } 13 | }] 14 | } -------------------------------------------------------------------------------- /templates_no_framework/server/routes/demo.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.index = function(req, res){ 3 | res.render('demo/index',{ data: 'index'}) 4 | }; 5 | -------------------------------------------------------------------------------- /templates_no_framework/server/routes/index.js: -------------------------------------------------------------------------------- 1 | const demo = require('./demo'); 2 | //<@add page@> 3 | 4 | module.exports.init = app =>{ 5 | 6 | app.get('/demo', demo.index); 7 | //<@add page router@> 8 | 9 | return app; 10 | }; 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates_no_framework/server/views/dev/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../head.html %> 5 | demo 6 | 7 | 8 | 9 | 10 | 11 |
MG最佳实践DEMO页面
12 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
进口深海鳕鱼500g(精选)
64 |
“还是个小孩就这么贪吃?”
65 |
66 | ¥18.8/份 67 | 68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |
进口深海鳕鱼500g(精选)
77 |
“还是个小孩就这么贪吃?”
78 |
79 | ¥18.8/份 80 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
进口深海鳕鱼500g(精选)
90 |
“还是个小孩就这么贪吃?”
91 |
92 | ¥18.8/份 93 | 94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
进口深海鳕鱼500g(精选)
103 |
“还是个小孩就这么贪吃?”
104 |
105 | ¥18.8/份 106 | 107 |
108 |
109 |
110 |
111 |
112 | 113 |
114 |
115 |
进口深海鳕鱼500g(精选)
116 |
“还是个小孩就这么贪吃?”
117 |
118 | ¥18.8/份 119 | 120 |
121 |
122 |
123 |
124 |
125 | 126 |
127 |
128 |
进口深海鳕鱼500g(精选)
129 |
“还是个小孩就这么贪吃?”
130 |
131 | ¥18.8/份 132 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /templates_no_framework/server/views/dev/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /templates_no_framework/server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /templates_no_framework/server/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | 12 | -------------------------------------------------------------------------------- /templates_no_framework/server/views/prod/head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates_no_framework/static/readme.md: -------------------------------------------------------------------------------- 1 | 存放静态资源,如静态html等 -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/app.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | import Vue from 'vue'; 3 | import App from './components/App'; 4 | import {isBrowser} from "../../../common/js/commonUtils"; 5 | 6 | export function createApp(store) { 7 | 8 | let app = new Vue({ 9 | data: store, 10 | render: h => h(App) 11 | }); 12 | 13 | if (isBrowser()) { 14 | // 特定平台api的逻辑 15 | } 16 | 17 | return { store, app, App} 18 | } -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/components/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | 42 | -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/components/Bar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/components/Foo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | } 4 | 5 | h1,h2{ 6 | margin: 0; 7 | } 8 | 9 | .container{ 10 | background-color: #FF9800; 11 | } -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 模板页面index 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export function getHeros(params){ 4 | return Promise.resolve({ 5 | data: ['雷神', '美队', '黑寡妇', '钢铁侠'] 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /templates_vue_ssr/module/app/store.js: -------------------------------------------------------------------------------- 1 | import * as service from './service' 2 | 3 | var store = { 4 | state: { 5 | message: 'hello', 6 | heros: [], 7 | }, 8 | setHerosAction() { 9 | return service.getHeros() 10 | .then( resp => { 11 | this.state.heros = resp.data; 12 | }) 13 | }, 14 | addHerosAction(newValue){ 15 | this.state.heros = newValue; 16 | }, 17 | clearHerosAction(){ 18 | this.state.heros = []; 19 | } 20 | } 21 | 22 | 23 | export function createStore() { 24 | return store; 25 | } -------------------------------------------------------------------------------- /templates_vue_ssr/module/entry-client.js: -------------------------------------------------------------------------------- 1 | import {createStore} from "./app/store"; 2 | import { deepExtend } from "../../common/js/commonUtils"; 3 | import {createApp} from "./app/app"; 4 | 5 | let store = createStore(); 6 | store.state = deepExtend(store.state, window.__INITIAL_STATE__); 7 | let { app, App } = createApp(store); 8 | 9 | // 降级方案,判断是否要做CSR 10 | if(isCSR()){ 11 | createAppEl(); 12 | App.asyncData(store) 13 | .then( resp => { 14 | app.$mount('#app', true); 15 | }) 16 | } 17 | else { 18 | app.$mount('#app', true); 19 | } 20 | 21 | // 检查有没有appid,没有则表示是CSR,创建一个div,设置id为app 22 | function isCSR(){ 23 | let appEl = document.getElementById('app'); 24 | return !appEl; 25 | } 26 | 27 | function createAppEl(){ 28 | let appEl = document.createElement('div'); 29 | appEl.setAttribute('id', 'app') 30 | document.querySelector('body').insertBefore(appEl, document.querySelector('body').firstChild); 31 | } -------------------------------------------------------------------------------- /templates_vue_ssr/module/entry-server.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "./app/app"; 2 | import { createStore } from "./app/store"; 3 | 4 | export default context => { 5 | const store = createStore(); 6 | const { app, App } = createApp(store); 7 | 8 | console.log('请求开始', new Date().getTime()) 9 | return App.asyncData(store) 10 | .then( resp => { 11 | console.log('请求返回', new Date().getTime()) 12 | context.state = store.state; 13 | return app; 14 | }) 15 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea 61 | 62 | .idea/*.xml 63 | 64 | dist/ 65 | dest/ 66 | public/ 67 | package-lock.json -------------------------------------------------------------------------------- /templates_vue_ssr/project/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/README.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | ## 安装 3 | npm install 4 | 5 | ## 启动 6 | npm run start demo 7 | 8 | 客户端访问 9 | CSR: localhost:8080/[模块]/?ssr=true 10 | SSR: localhost:8080/[模块] 11 | 12 | 13 | ## 发布 14 | npm run build demo 15 | 16 | # pm2启动 17 | pm2 start process.json 18 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var debug = require('debug')('server:server'); 4 | var http = require('http'); 5 | var express = require('express'); 6 | var path = require('path'); 7 | var favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | var cookieParser = require('cookie-parser'); 10 | var bodyParser = require('body-parser'); 11 | var proxy = require('http-proxy-middleware'); 12 | var router = require('./server/routes/index'); 13 | var app = express(); 14 | var server = http.createServer(app); 15 | 16 | var serverPort = require('./mg.config').server.port; 17 | var port = normalizePort(process.env.PORT || serverPort || '8090'); 18 | app.set('port', port); 19 | 20 | console.log('env', process.env.ENV); 21 | 22 | // views目标设置为ejs与html 23 | app.engine('html', require('ejs').renderFile); 24 | app.set('views',path.join(__dirname,'server/views',process.env.ENV !== 'prod' ? 'dev' : 'prod')); 25 | app.set('view engine', 'html'); 26 | app.set('view cache', false); 27 | 28 | // uncomment after placing your favicon in /public 29 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 30 | app.use(logger('dev')); 31 | app.use(bodyParser.json()); 32 | app.use(bodyParser.urlencoded({ extended: false })); 33 | app.use(cookieParser()); 34 | app.use(express.static(path.join(__dirname, 'static'))); 35 | if(process.env.ENV !== 'prod'){ 36 | app.use(express.static(path.join(__dirname, 'client'))); 37 | } 38 | 39 | app.use(function(req, res, next) { 40 | res.header("Access-Control-Allow-Origin", "*"); 41 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 42 | next(); 43 | }); 44 | 45 | router.init(app); 46 | 47 | app.use('/gravidity-api', proxy({ target: 'http://test-ybbview.seeyouyima.com', changeOrigin: true })) 48 | 49 | // catch 404 and forward to error handler 50 | app.use(function(req, res, next) { 51 | var err = new Error('Not Found'); 52 | err.status = 404; 53 | next(err); 54 | }); 55 | 56 | // error handler 57 | app.use(function(err, req, res, next) { 58 | // set locals, only providing error in development 59 | res.locals.message = err.message; 60 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 61 | 62 | // render the error page 63 | res.status(err.status || 500); 64 | console.log(err); 65 | res.render('error'); 66 | }); 67 | 68 | 69 | 70 | 71 | /** 72 | * Listen on provided port, on all network interfaces. 73 | */ 74 | 75 | server.listen(port); 76 | server.on('error', onError); 77 | server.on('listening', onListening); 78 | 79 | /** 80 | * Normalize a port into a number, string, or false. 81 | */ 82 | 83 | function normalizePort(val) { 84 | var port = parseInt(val, 10); 85 | 86 | if (isNaN(port)) { 87 | // named pipe 88 | return val; 89 | } 90 | 91 | if (port >= 0) { 92 | // port number 93 | return port; 94 | } 95 | 96 | return false; 97 | } 98 | 99 | /** 100 | * Event listener for HTTP server "error" event. 101 | */ 102 | 103 | function onError(error) { 104 | if (error.syscall !== 'listen') { 105 | throw error; 106 | } 107 | 108 | var bind = typeof port === 'string' 109 | ? 'Pipe ' + port 110 | : 'Port ' + port; 111 | 112 | // handle specific listen errors with friendly messages 113 | switch (error.code) { 114 | case 'EACCES': 115 | console.error(bind + ' requires elevated privileges'); 116 | process.exit(1); 117 | break; 118 | case 'EADDRINUSE': 119 | console.error(bind + ' is already in use'); 120 | process.exit(1); 121 | break; 122 | default: 123 | throw error; 124 | } 125 | } 126 | 127 | /** 128 | * Event listener for HTTP server "listening" event. 129 | */ 130 | 131 | function onListening() { 132 | var addr = server.address(); 133 | var bind = typeof addr === 'string' 134 | ? 'pipe ' + addr 135 | : 'port ' + addr.port; 136 | debug('Listening on ' + bind); 137 | } 138 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/commands/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const chalk = require('chalk'); 4 | const del = require('delete'); 5 | const fs = require('fs'); 6 | const doUpload = require('../libs/doupload'); 7 | const Spinner = require('../libs/spinner'); 8 | const argvStr = process.argv.slice(-1)[0]; 9 | const options = getOptions(argvStr); 10 | const moduleSp = options.module; 11 | const mgConfig = require('../../mg.config.js'); 12 | 13 | // 如果未指定编译模块,则提示返回 14 | if(argvStr === ''){ 15 | console.log(chalk.yellow('[warning]: You have not specify a module to build, you can run "meet build [module]"')); 16 | return; 17 | } 18 | 19 | // 删除dist下的所有文件 20 | console.log(chalk.blue('Delete dist directory!')); 21 | del.sync([`./dist/**`]); 22 | 23 | // 获取命令携带的参数 24 | const webpackClient = require(path.resolve('build/webpack/webpack.client.config.js')); 25 | const webpackServer = require(path.resolve('build/webpack/webpack.server.config.js')); 26 | const spinner = new Spinner('Building...\n'); 27 | 28 | const clientPack = function(){ 29 | return new Promise((resolve, reject) => { 30 | webpack(webpackClient, (err, stats) => { 31 | spinner.stop(); 32 | 33 | if (err) throw err; 34 | 35 | process.stdout.write(stats.toString({ 36 | colors: true, 37 | modules: false, 38 | children: false, 39 | chunks: false, 40 | chunkModules: false 41 | }) + '\n'); 42 | 43 | resolve(true); 44 | }); 45 | }) 46 | } 47 | 48 | const serverPack = function(){ 49 | return new Promise((resolve, reject) => { 50 | webpack(webpackServer(), (err, stats) => { 51 | spinner.stop(); 52 | 53 | if (err) throw err; 54 | 55 | process.stdout.write(stats.toString({ 56 | colors: true, 57 | modules: false, 58 | children: false, 59 | chunks: false, 60 | chunkModules: false 61 | }) + '\n'); 62 | 63 | resolve(true); 64 | }); 65 | }) 66 | } 67 | 68 | serverPack() 69 | .then( () => clientPack()) 70 | .then( () => { 71 | replaceCssContent(); 72 | if(mgConfig.upload.autoUpload === true){ 73 | doUpload(); 74 | } 75 | }) 76 | .catch(err => { 77 | console.log('build 出错', err); 78 | }); 79 | 80 | /** 81 | * client 与 server 构建的CSS module id 不同导致同构的css代码不同,目前使用server构建的css来临时解决;即: 82 | * 用server构建的的css覆盖client构建的css 83 | */ 84 | function replaceCssContent(){ 85 | let files = fs.readdirSync(path.resolve('dist')); 86 | let serverPackedCSS = []; 87 | // 遍历找出所有server pack的css文件 88 | files.forEach( file => { 89 | if(file.endsWith('.css') && file.indexOf('-server.') !== -1){ 90 | serverPackedCSS.push(file); 91 | } 92 | }) 93 | 94 | // 根据文件命名规则 page-server.xxxxxx.css,找出相应的css文件名,做覆盖 95 | serverPackedCSS.forEach( serverCssFile => { 96 | let page = serverCssFile.split('-server')[0]; 97 | files.forEach( item => { 98 | if(item.endsWith('.css') && item.indexOf( page + '.') !== -1){ 99 | fs.writeFileSync(path.resolve('dist', item), fs.readFileSync(path.resolve('dist', serverCssFile), 'utf8')) 100 | } 101 | }) 102 | }) 103 | } 104 | 105 | function getOptions(argvStr){ 106 | let obj = {}; 107 | let nameArr = argvStr.split('-'); 108 | obj.module = nameArr[0]; 109 | obj.page = nameArr.length === 2? nameArr[1] : 'index'; 110 | return obj; 111 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/commands/ocstart.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { exec } = require('child_process'); 4 | const express = require('express'); 5 | const webpack = require('webpack'); 6 | const webpackDevMiddleware = require('webpack-dev-middleware'); 7 | const webpackHotMiddleware = require('webpack-hot-middleware'); 8 | const proxy = require('http-proxy-middleware'); 9 | const chalk = require('chalk'); 10 | const Spinner = require('../libs/spinner'); 11 | const mgConfig = require(path.resolve('mg.config.js')); 12 | 13 | const app = express(); 14 | const projectName = process.argv[2] || ''; 15 | const port = mgConfig.clientPort; 16 | // 提示未创建项目 17 | const projectPath = path.resolve('client', projectName); 18 | if(projectName === ''){ 19 | console.log(chalk.yellow(`[Warn] module is not exist, you can run "npm run start [module]"`)); 20 | process.exit(); 21 | } 22 | if (!fs.existsSync(projectPath)) { 23 | console.log(chalk.yellow(`[Warn] ${projectPath} is not exist, you can run "npm run new ${projectName}" to create.`)); 24 | process.exit(); 25 | } 26 | 27 | const webpackClient = require('../webpack/webpack.client.config'); 28 | const webpackSever = require('../webpack/webpack.server.config'); 29 | const ip = require('../libs/ip'); 30 | 31 | // 编译 + 启动开发服务端 32 | const spinner = new Spinner('Building...\n'); 33 | 34 | // 步骤一、热编译client端项目 35 | const compiler = webpack(webpackClient); 36 | 37 | compiler.plugin('done', function(){ 38 | setTimeout(() => { 39 | spinner.stop(); 40 | console.log(chalk.bgGreen('\n √ Build done ') + '\n'); 41 | console.log(chalk.magenta(`[Tips] visit: http://localhost:${port}/${projectName}/`)); 42 | console.log(chalk.magenta(` : http://${ip()}:${port}/${projectName}/`) + '\n'); 43 | }, 0); 44 | }); 45 | 46 | const middleware = webpackDevMiddleware(compiler, { 47 | publicPath: webpackClient.output.publicPath, 48 | // html only 49 | writeToDisk: filePath => /\.html$/.test(filePath), 50 | }); 51 | 52 | app.use(middleware); 53 | app.use(webpackHotMiddleware(compiler)); 54 | app.use(express.static(path.resolve(__dirname, '../../public/assets'))); 55 | app.use(proxy(`http://localhost:${mgConfig.server.port}`)); 56 | app.listen(8080); 57 | 58 | // 步骤二、服务端监听构建Vue 59 | const serverCompiler = webpack(webpackSever()); 60 | 61 | const watching = serverCompiler.watch({ 62 | // watchOptions 示例 63 | aggregateTimeout: 300, 64 | poll: undefined 65 | }, (err, stats) => { 66 | // 在这里打印 watch/build 结果... 67 | console.log(chalk.bgGreen('\n √ Server compile done ') + '\n'); 68 | 69 | if (err) { 70 | console.error(err); 71 | return; 72 | } 73 | 74 | if(stats.hasErrors()){ 75 | console.log(chalk.red('\n ✖️ Server compile Error ') + '\n'); 76 | 77 | console.log(stats.toString({ 78 | chunks: false, // 使构建过程更静默无输出 79 | colors: true // 在控制台展示颜色 80 | })); 81 | } 82 | 83 | }); 84 | 85 | // 步骤三、4秒后启动服务 86 | setTimeout(function () { 87 | // 启动代理服务端 88 | const processServer = exec(`npm run server`); 89 | 90 | processServer.stdout.on('data', stats => { 91 | process.stdout.write(stats.toString({ 92 | colors: true, 93 | modules: false, 94 | children: false, 95 | chunks: false, 96 | chunkModules: false 97 | }) + '\n'); 98 | }); 99 | }, 6000) -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/commands/upload.js: -------------------------------------------------------------------------------- 1 | const doUpload = require('../libs/doupload'); 2 | 3 | doUpload(); -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/aliupload.js: -------------------------------------------------------------------------------- 1 | const oss = require('ali-oss'); 2 | const fs = require('fs'); 3 | const co = require('co'); 4 | 5 | function upload (config, file, filename) { 6 | 7 | let defaultConfig = { 8 | }; 9 | 10 | if (fs.existsSync(aliossaccess)) { 11 | const access = JSON.parse(fs.readFileSync(aliossaccess, 'utf-8')); 12 | 13 | defaultConfig = Object.assign(defaultConfig, access); 14 | } 15 | 16 | const ossPath = `${config.prefix}/${filename}`; 17 | const store = oss(Object.assign(defaultConfig, config)); 18 | 19 | return new Promise((resovle, reject) => { 20 | co(function* () { 21 | return yield store.list({ 22 | prefix: ossPath, 23 | }); 24 | }).then(data => { 25 | if (config.deduplication !== true || (config.deduplication === true && typeof data.objects === 'undefined')) { 26 | co(function* () { 27 | return yield store.put(ossPath, file); 28 | }) 29 | .then(res => { 30 | resovle(res); 31 | }) 32 | .catch(err => { 33 | reject(err); 34 | }); 35 | } else { 36 | reject(new Error('文件已存在')); 37 | } 38 | }); 39 | }); 40 | 41 | } 42 | 43 | module.exports = upload; 44 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/doupload.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const chalk = require('chalk'); 3 | const qupload = require('./qupload'); 4 | const aliupload = require('meetyou-ali-oss'); 5 | const mgConfig = require('../../mg.config.js'); 6 | 7 | function doUpload(){ 8 | 9 | var aliconfig = mgConfig.upload.aliconfig; 10 | var qconfig = mgConfig.upload.qconfig; 11 | var distPath = path.resolve('dist'); 12 | 13 | if(typeof aliconfig === 'object'){ 14 | aliupload({ 15 | "srcDir": path.resolve('dist'), 16 | "ignoreDir": false, 17 | "deduplication": true, 18 | "prefix": mgConfig.upload.projectPrefix 19 | }); 20 | return; 21 | } 22 | 23 | if(typeof qconfig === 'object'){ 24 | console.log(chalk.greenBright('\n' + 'Upload dist files to Qiniu CDN:' )); 25 | qupload(distPath,mgConfig.upload.projectPrefix); 26 | return; 27 | } 28 | } 29 | 30 | module.exports = doUpload; -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/ip.js: -------------------------------------------------------------------------------- 1 | const OS = require('os'); 2 | 3 | module.exports = function getIP() { 4 | const interfaces = OS.networkInterfaces(); 5 | for (let devName in interfaces) { 6 | const iface = interfaces[devName]; 7 | for (let i = 0; i < iface.length; i++) { 8 | const alias = iface[i]; 9 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 10 | return alias.address; 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/qupload.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const qn = require('qn'); 3 | const fs = require('fs'); 4 | const chalk = require('chalk'); 5 | const { qconfig } = require(path.resolve('mg.config.js')).upload; 6 | 7 | var fileTotal = 0; 8 | var fileUploadCount = 0; 9 | 10 | var client = qn.create({ 11 | accessKey: qconfig.ACCESS_KEY, 12 | secretKey: qconfig.SECRET_KEY, 13 | bucket: qconfig.bucket, 14 | origin: qconfig.origin, 15 | }); 16 | 17 | function qnUpload(filePath,filename){ 18 | client.uploadFile(filePath, {key: filename}, function (err, result) { 19 | if(err){ 20 | console.log(chalk.red(`[Error]: 上传文件失败:${err.toString()}`)); 21 | return; 22 | } 23 | console.log(chalk.greenBright(`[Success]: 上传文件至七牛云CDN成功!文件地址:${result.url}`)); 24 | 25 | fileUploadCount++; 26 | // 上传完毕则退出 27 | if(fileTotal === fileUploadCount){ 28 | console.log(chalk.greenBright('[Success]: 上传完毕 😊!')); 29 | console.log('Use Ctrl+C to close it\n'); 30 | //process.exit(0); 31 | } 32 | }); 33 | } 34 | 35 | function readDicUpload(filePath,tempPath = ''){ 36 | let filesArr = []; 37 | try{ 38 | filesArr = fs.readdirSync(filePath) 39 | } 40 | catch (e) { 41 | console.log(chalk.red('[Error]: 要上传的文件路径srcDir不存在')); 42 | return; 43 | } 44 | filesArr.forEach(file => { 45 | const origFilePath = `${filePath}/${file}`; 46 | const stats = fs.statSync(origFilePath); 47 | if(stats.isFile()){ 48 | fileTotal++; 49 | qnUpload(origFilePath,`${tempPath}/${file}`); 50 | } 51 | else if(stats.isDirectory()){ 52 | readDicUpload(origFilePath,`${tempPath}/${file}`); 53 | } 54 | }) 55 | } 56 | 57 | module.exports = readDicUpload; -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/spinner.js: -------------------------------------------------------------------------------- 1 | const CLI = require('clui'); 2 | const Spinner = CLI.Spinner; 3 | 4 | module.exports = function(message){ 5 | var time = 0; 6 | var countdown = new Spinner(message, ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷']); 7 | 8 | countdown.start(); 9 | 10 | setInterval(function () { 11 | time++; 12 | countdown.message(`lasted ${time} seconds. `); 13 | }, 1000); 14 | 15 | this.stop = function(){ 16 | countdown.stop(); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/libs/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const clientEntryModules = require('../modules-client'); 3 | const serverEntryModules = require('../modules-server'); 4 | 5 | function readClientEntrys(moduleBS, pageBS){ 6 | var entrys = {}; 7 | for(var key in clientEntryModules){ 8 | if(key.indexOf(`${moduleBS}-`) !== -1){ 9 | entrys[key] = clientEntryModules[key] 10 | } 11 | } 12 | return entrys; 13 | } 14 | 15 | function readServerEntrys(moduleBS, pageBS){ 16 | var entrys = {}; 17 | for(var key in serverEntryModules){ 18 | if(key.indexOf(`${moduleBS}-`) !== -1){ 19 | let page = key.split('-')[1]; 20 | entrys[`${page}-server`] = path.resolve(serverEntryModules[key]); 21 | } 22 | } 23 | return entrys; 24 | } 25 | 26 | module.exports = { 27 | readClientEntrys, 28 | readServerEntrys 29 | }; -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/modules-client.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'demo-index': 'client/demo/index/entry-client.js' 3 | }; 4 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/modules-server.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'demo-index': 'client/demo/index/entry-server.js' 3 | }; -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/webpack/webpack.client.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const webpack = require('webpack'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 8 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | const ErudaWebapckPlugin = require('eruda-webpack-plugin') 10 | const { VueLoaderPlugin } = require('vue-loader'); 11 | const mgConfig = require(path.resolve('mg.config.js')); 12 | const { readClientEntrys } = require('../libs/utils'); 13 | 14 | // 编译的参数 15 | const argvStr = process.argv.slice(-1)[0]; 16 | const isDev = process.env.NODE_ENV === 'development'; 17 | const options = getOptions(argvStr); 18 | const moduleBS = options.module; 19 | const pageBS = options.page; 20 | const clientPath = path.resolve('client'); 21 | const viewsPath = path.resolve('server/views',isDev ? 'dev' : 'prod'); 22 | const modulePath = path.resolve(clientPath, options.module); 23 | const outputPath = path.resolve('dist'); 24 | const { entry, htmlPlugins } = readFiles(); 25 | const publicPath = isDev? '/' : mgConfig.upload.cdn; 26 | 27 | module.exports = { 28 | mode: isDev ? 'development' : 'production', 29 | devtool: isDev ? 'cheap-module-eval-source-map' : 'none', 30 | entry: entry, 31 | output: { 32 | path: outputPath, 33 | publicPath: `${publicPath}`, 34 | filename: isDev ? '[name].[hash].js' : '[name].[chunkhash:8].js', 35 | }, 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.vue$/, 40 | use: 'vue-loader', 41 | exclude: /node_modules/ 42 | }, 43 | { 44 | test: /\.js$/, 45 | use: 'babel-loader', 46 | exclude: /node_modules/ 47 | }, 48 | { 49 | test: /\.html$/, 50 | use: { 51 | loader: 'html-loader', 52 | options: { 53 | minimize: !isDev, 54 | removeAttributeQuotes: false, 55 | removeComments: false 56 | } 57 | } 58 | }, 59 | { 60 | test: /\.(le|c)ss$/, 61 | use: [ 62 | isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader, 63 | 'css-loader', 64 | 'postcss-loader', 65 | 'less-loader' 66 | ] 67 | }, 68 | { 69 | test: /\.scss$/, 70 | use: [ 71 | isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader, 72 | 'css-loader', 73 | 'postcss-loader', 74 | 'sass-loader' 75 | ] 76 | }, 77 | { 78 | test: /\.(jpe?g|png|gif|svg)$/, 79 | use: { 80 | loader: 'url-loader', 81 | options: { 82 | limit: 10000, 83 | name: '[hash:8].[ext]' 84 | } 85 | } 86 | }, 87 | { 88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | name: '[hash:8].[ext]' 93 | } 94 | }, 95 | { 96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 97 | loader: 'url-loader', 98 | options: { 99 | limit: 10000, 100 | name: '[hash:8].[ext]' 101 | } 102 | } 103 | ] 104 | }, 105 | 106 | plugins: [ 107 | ...htmlPlugins, 108 | new VueLoaderPlugin(), 109 | new MiniCssExtractPlugin({ 110 | filename: '[name].[contenthash:8].css', 111 | chunkFilename: '[id].[contenthash:8].css' 112 | }), 113 | // 针对 moment.js 打包体积过大问题 114 | new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/) 115 | ], 116 | optimization: { 117 | splitChunks: { 118 | cacheGroups: { 119 | vendor: { 120 | name: 'vendor', // 与 output.filename 一致, 即为 'vendor.[chunckhash:8].js' 121 | chunks: 'all', 122 | test: /node_modules/, 123 | enforce: true 124 | } 125 | } 126 | }, 127 | }, 128 | resolve: { 129 | extensions: ['.js', '.vue', '.less', 'scss', '.json'], 130 | alias: { 131 | '@': modulePath, 132 | } 133 | }, 134 | performance: { 135 | assetFilter: function(assetFilename) { 136 | return assetFilename.endsWith('.js'); 137 | }, 138 | hints: isDev ? false : 'warning' // 当打包的文件大于 244 KiB 是会在提出警告 139 | } 140 | }; 141 | 142 | if (isDev) { 143 | // 开发模式 144 | module.exports.plugins.push( 145 | new webpack.HotModuleReplacementPlugin(), 146 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 147 | new webpack.NoEmitOnErrorsPlugin(), 148 | new ErudaWebapckPlugin() 149 | ); 150 | } else { 151 | // 预发/生产模式 152 | module.exports.optimization = { 153 | splitChunks: { 154 | cacheGroups: { 155 | vendor: { 156 | name: 'vendor', // 与 output.filename 一致, 即为 'vendor.[chunckhash:8].js' 157 | chunks: 'initial', 158 | test: /node_modules/, 159 | enforce: true 160 | } 161 | } 162 | }, 163 | minimize: true, 164 | minimizer: [ 165 | new OptimizeCSSAssetsPlugin(),// 压缩 css 文件 166 | new UglifyJsPlugin({ 167 | test: /\.js$/, 168 | exclude: /\/node_modules/, 169 | // cache: resolve(outputPath, '..', '.cache'), 170 | parallel: true,// 并行打包 171 | uglifyOptions: { 172 | compress: { 173 | drop_console: true // 去掉所有console.* 174 | } 175 | } 176 | }), 177 | ] 178 | }; 179 | 180 | module.exports.plugins.push( 181 | new BundleAnalyzerPlugin({ 182 | analyzerMode: 'static' 183 | }) 184 | ); 185 | } 186 | 187 | // 获取人口文件和对应的html 188 | function readFiles() { 189 | let entrys = {}, htmlPlugins = []; 190 | let entrysTemp = readClientEntrys(moduleBS, pageBS); 191 | for(var key in entrysTemp){ 192 | let page = key.split('-')[1]; 193 | let htmlTemplate = resolve(modulePath, page, `app/${page}.html`) 194 | entrys[page] = [resolve(modulePath, page, 'entry-client.js'), ...(isDev ? [htmlTemplate,'webpack-hot-middleware/client?reload=true'] : [])] 195 | htmlPlugins.push(new HtmlWebpackPlugin({ 196 | template: htmlTemplate, 197 | filename: resolve(`${viewsPath}/${options.module}/${page}.html`), 198 | chunks: ['vendor', page], 199 | minify: false 200 | })) 201 | } 202 | 203 | return { 204 | entry: entrys, 205 | htmlPlugins 206 | }; 207 | 208 | } 209 | 210 | function getOptions(argvStr){ 211 | let obj = {}; 212 | let nameArr = argvStr.split('-'); 213 | obj.module = nameArr[0]; 214 | obj.page = nameArr.length === 2? nameArr[1] : 'index'; 215 | return obj; 216 | } 217 | 218 | function resolve(...dir) { 219 | return path.resolve(__dirname, ...dir); 220 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/build/webpack/webpack.server.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const { VueLoaderPlugin } = require('vue-loader'); 4 | const del = require('delete'); 5 | const mgConfig = require(resolve('mg.config.js')); 6 | const { readServerEntrys } = require('../libs/utils'); 7 | 8 | // 编译的参数 9 | const argvStr = process.argv.slice(-1)[0]; 10 | const options = getOptions(argvStr); 11 | const isDev = process.env.NODE_ENV === 'development'; 12 | const publicPath = isDev? '/' : mgConfig.upload.cdn; 13 | const clientPath = resolve('client'); 14 | const modulePath = resolve(clientPath, options.module); 15 | const moduleBS = options.module; 16 | const pageBS = options.page; 17 | 18 | del.sync([`./server/ssr_code/${moduleBS}/**`]); 19 | 20 | module.exports = (opts = {}) => { 21 | var config = { 22 | mode: isDev ? 'development' : 'production', 23 | entry: readServerEntrys(moduleBS, pageBS), 24 | target: 'node', 25 | output: { 26 | path: resolve(`server/ssr_code/${moduleBS}`), 27 | publicPath: publicPath, 28 | filename: '[name].js', 29 | libraryTarget: 'commonjs2' 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.vue$/, 35 | use: 'vue-loader', 36 | exclude: /node_modules/ 37 | }, 38 | { 39 | test: /\.js$/, 40 | use: 'babel-loader', 41 | exclude: /node_modules/ 42 | }, 43 | { 44 | test: /\.(le|c)ss$/, 45 | use: [ 46 | MiniCssExtractPlugin.loader, 47 | 'css-loader', 48 | 'postcss-loader', 49 | 'less-loader' 50 | ] 51 | }, 52 | { 53 | test: /\.scss$/, 54 | use: [ 55 | MiniCssExtractPlugin.loader, 56 | 'css-loader', 57 | 'postcss-loader', 58 | 'sass-loader' 59 | ] 60 | }, 61 | { 62 | test: /\.(jpe?g|png|gif|svg)$/, 63 | use: { 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: '[hash:8].[ext]' 68 | } 69 | } 70 | }, 71 | { 72 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: '[hash:8].[ext]' 77 | } 78 | }, 79 | { 80 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 81 | loader: 'url-loader', 82 | options: { 83 | limit: 10000, 84 | name: '[hash:8].[ext]' 85 | } 86 | } 87 | ] 88 | }, 89 | plugins: [ 90 | new VueLoaderPlugin(), 91 | new MiniCssExtractPlugin({ 92 | filename: '../../../dist/[name].[contenthash:8].css', 93 | chunkFilename: '[id].[contenthash:8].css' 94 | }), 95 | // new BundleAnalyzerPlugin({ 96 | // analyzerMode: 'static' 97 | // }) 98 | ], 99 | resolve: { 100 | extensions: ['.js', '.vue', '.less', 'scss', '.json'], 101 | alias: { 102 | '@': modulePath, 103 | } 104 | }, 105 | performance: { 106 | assetFilter: function(assetFilename) { 107 | return assetFilename.endsWith('.js'); 108 | }, 109 | hints: isDev ? false : 'warning' // 当打包的文件大于 244 KiB 是会在提出警告 110 | } 111 | } 112 | 113 | return config; 114 | }; 115 | 116 | function getOptions(argvStr){ 117 | let obj = {}; 118 | let nameArr = argvStr.split('-'); 119 | obj.module = nameArr[0]; 120 | obj.page = nameArr.length === 2? nameArr[1] : 'index'; 121 | return obj; 122 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/common/css/base.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | box-sizing: inherit; 7 | } 8 | 9 | html { 10 | font-size: 10px; 11 | height: 100%; 12 | min-height: 100%; 13 | max-width: 750px; 14 | margin: 0 auto; 15 | box-sizing: border-box; 16 | user-select: none; 17 | text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | body { 22 | position: relative; 23 | background-color: #fff; 24 | color: #323232; 25 | font-family: STHeiti,Microsoft YaHei,Helvetica,Arial,sans-serif; 26 | margin: 0; 27 | padding: 0; 28 | -webkit-touch-callout: none; 29 | -webkit-font-smoothing: antialiased; 30 | -webkit-overflow-scrolling: touch; 31 | overflow-scrolling: touch; 32 | word-break: break-word; 33 | } 34 | 35 | a:hover, 36 | a:active { 37 | outline: none; 38 | } 39 | 40 | input:focus, 41 | input:active, 42 | textarea:focus, 43 | textarea:active { 44 | outline: none; 45 | -moz-outline-style: none; 46 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 47 | } 48 | 49 | a { 50 | text-decoration: none; 51 | } 52 | 53 | /* remove the up and down arrow for input:number; */ 54 | input::-webkit-outer-spin-button, 55 | input::-webkit-inner-spin-button { 56 | -webkit-appearance: none !important; 57 | margin: 0; 58 | } 59 | 60 | input[type="number"] { 61 | -moz-appearance: textfield; 62 | } 63 | 64 | table { 65 | border-collapse: collapse; 66 | border-spacing: 0; 67 | } 68 | 69 | li, 70 | ul, 71 | ol { 72 | padding: 0; 73 | margin: 0; 74 | list-style: none; 75 | display: list-item; 76 | text-align: -webkit-match-parent; 77 | } 78 | 79 | /* Remove inner padding and border in Firefox */ 80 | ::-moz-focus-inner { 81 | padding: 0; 82 | border: 0; 83 | } 84 | 85 | ::-webkit-scrollbar { 86 | display: none; 87 | } 88 | 89 | img { 90 | max-width: 100%; 91 | height: auto; 92 | border: 0; 93 | -webkit-touch-callout: none; 94 | appearance: none; 95 | } 96 | 97 | h1, 98 | h2, 99 | h3, 100 | h4, 101 | h5, 102 | h6, 103 | p { 104 | margin: 0; 105 | font-weight: normal; 106 | } 107 | 108 | p { 109 | line-height: 18px; 110 | border: 0; 111 | outline: 0; 112 | max-height: 100%; 113 | } 114 | 115 | input, 116 | select { 117 | vertical-align: middle; 118 | } 119 | 120 | input, 121 | select, 122 | textarea { 123 | -webkit-appearance: none; 124 | } 125 | 126 | ::-moz-focus-inner { 127 | padding: 0; 128 | border: 0; 129 | } 130 | 131 | article, 132 | aside, 133 | details, 134 | figcaption, 135 | figure, 136 | footer, 137 | header, 138 | hgroup, 139 | main, 140 | menu, 141 | nav, 142 | section { 143 | display: block; 144 | } 145 | 146 | blockquote, 147 | q { 148 | quotes: none; 149 | } 150 | 151 | blockquote::before, 152 | blockquote::after, 153 | q::before, 154 | q::after { 155 | content: ""; 156 | content: none; 157 | } 158 | 159 | /* 160 | * 1. Address `overflow` set to `hidden` in IE 10/11. 161 | * 2. Improve consistency of cursor style 162 | */ 163 | button { 164 | overflow: visible; 165 | cursor: pointer; 166 | } 167 | 168 | .hidden { 169 | display: none !important; 170 | opacity: 0 !important; 171 | } 172 | 173 | .clearfix::after { 174 | display: block; 175 | content: ""; 176 | visibility: hidden; 177 | height: 0; 178 | clear: both; 179 | } 180 | 181 | .clearfix { 182 | zoom: 1; 183 | } 184 | 185 | .clear::before, 186 | .clear::after { 187 | *zoom: 1; 188 | display: table; 189 | content: ""; 190 | line-height: 0; 191 | } 192 | 193 | .clear::after { 194 | clear: both; 195 | } 196 | 197 | /* 0.5px线条 */ 198 | .line-bottom::after, 199 | .line-top::before { 200 | content: ""; 201 | position: absolute; 202 | background-color: #ddd; 203 | left: 0; 204 | height: 1px; 205 | width: 100%; 206 | -webkit-transform: scaleY(0.5); 207 | transform: scaleY(0.5); 208 | } 209 | 210 | .line-left::before, 211 | .line-right::after { 212 | content: ""; 213 | position: absolute; 214 | background-color: #ddd; 215 | top: 0; 216 | width: 1px; 217 | height: 100%; 218 | -webkit-transform: scaleX(0.5); 219 | transform: scaleX(0.5); 220 | } 221 | 222 | .line-bottom::after { 223 | bottom: 0; 224 | } 225 | 226 | .line-top::before { 227 | top: 0; 228 | } 229 | 230 | .line-left::before { 231 | left: 0; 232 | } 233 | 234 | .line-right::after { 235 | right: 0; 236 | } 237 | 238 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/common/js/commonUtils.js: -------------------------------------------------------------------------------- 1 | export function deepExtend(out) { 2 | out = out || {} 3 | 4 | for (var i = 1, len = arguments.length; i < len; ++i) { 5 | var obj = arguments[i] 6 | 7 | if (!obj) { 8 | continue 9 | } 10 | 11 | for (var key in obj) { 12 | if (!obj.hasOwnProperty(key)) { 13 | continue 14 | } 15 | 16 | // based on https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ 17 | if (Object.prototype.toString.call(obj[key]) === '[object Object]') { 18 | out[key] = deepExtend(out[key], obj[key]) 19 | continue 20 | } 21 | 22 | out[key] = obj[key] 23 | } 24 | } 25 | 26 | return out 27 | } 28 | 29 | /** 30 | * 深拷贝代码 31 | */ 32 | export function merge(/* obj1, obj2, obj3, ... */) { 33 | var result = {} 34 | function assignValue(val, key) { 35 | if (typeof result[key] === 'object' && typeof val === 'object') { 36 | result[key] = merge(result[key], val) 37 | } else { 38 | result[key] = val 39 | } 40 | } 41 | 42 | for (var i = 0, l = arguments.length; i < l; i++) { 43 | forEach(arguments[i], assignValue) 44 | } 45 | return result 46 | } 47 | 48 | export function forEach(obj, fn) { 49 | // Don't bother if no value provided 50 | if (obj === null || typeof obj === 'undefined') { 51 | return 52 | } 53 | 54 | // Force an array if not already something iterable 55 | if (typeof obj !== 'object') { 56 | /*eslint no-param-reassign:0*/ 57 | obj = [obj] 58 | } 59 | 60 | if (isArray(obj)) { 61 | // Iterate over array values 62 | for (var i = 0, l = obj.length; i < l; i++) { 63 | fn.call(null, obj[i], i, obj) 64 | } 65 | } else { 66 | // Iterate over object keys 67 | for (var key in obj) { 68 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 69 | fn.call(null, obj[key], key, obj) 70 | } 71 | } 72 | } 73 | } 74 | 75 | function isArray(val) { 76 | return toString.call(val) === '[object Array]' 77 | } 78 | 79 | export function obj2KV(obj){ 80 | let ret = []; 81 | for(let item in obj){ 82 | ret.push({ 83 | key: item, 84 | value: obj[item] 85 | }) 86 | } 87 | return ret; 88 | } 89 | export function arrToObj(arr) { 90 | if (!isType(arr, 'array')) { 91 | console.log('不是数组') 92 | return {} 93 | } 94 | var obj = {}; 95 | arr.forEach(item => { 96 | if (!obj.hasOwnProperty(item.id)){ 97 | obj[item.id] = item.content; 98 | } 99 | }) 100 | return obj; 101 | } 102 | 103 | export function isType(obj, type) { 104 | return Object.prototype.toString.call(obj).toLowerCase() === `[object ${type}]`; 105 | } 106 | 107 | 108 | // 判断输入框是否为空 109 | export function isEmpty(val) { 110 | return val.split(" ").join("").length == 0 ? true : false 111 | } 112 | 113 | // 去掉字符串前后空格 114 | export function trim(str) 115 | { 116 | return str.replace(/(^\s*)|(\s*$)/g, ""); 117 | } 118 | 119 | // 下载导出 120 | export function downloadFile(url) { 121 | try{ 122 | var elemIF = document.createElement("iframe"); 123 | elemIF.src = url; 124 | elemIF.style.display = "none"; 125 | document.body.appendChild(elemIF); 126 | }catch(e){ } 127 | } 128 | 129 | export function getFileType(filePath) { 130 | var startIndex = filePath.lastIndexOf("."); 131 | if(startIndex != -1) 132 | return filePath.substring(startIndex+1, filePath.length).toLowerCase(); 133 | else return ""; 134 | } 135 | 136 | export function isImageFile(filename){ 137 | const types = ['jpg', 'png']; 138 | const filetype = getFileType(filename); 139 | if(!filetype){ 140 | return false; 141 | } 142 | return types.indexOf(filetype) !== -1; 143 | } 144 | 145 | export function isBrowser(){ 146 | console.log('global.toString()',global.toString()); 147 | return global.toString() === '[object Window]' || global.toString() === '[object DOMWindow]'; 148 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/common/js/flexible.js: -------------------------------------------------------------------------------- 1 | !function(){var a="@charset \"utf-8\";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}html,body{font-family:sans-serif}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:before,q:after{content:''}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}ins,a{text-decoration:none}",b=document.createElement("style");if(document.getElementsByTagName("head")[0].appendChild(b),b.styleSheet)b.styleSheet.disabled||(b.styleSheet.cssText=a);else try{b.innerHTML=a}catch(c){b.innerText=a}}();!function(a,b){function c(){var b=f.getBoundingClientRect().width;b/i>540&&(b=540*i);var c=b/10;f.style.fontSize=c+"px",k.rem=a.rem=c}var d,e=a.document,f=e.documentElement,g=e.querySelector('meta[name="demo"]'),h=e.querySelector('meta[name="flexible"]'),i=0,j=0,k=b.flexible||(b.flexible={});if(g){console.warn("灏嗘牴鎹凡鏈夌殑meta鏍囩鏉ヨ缃缉鏀炬瘮渚�");var l=g.getAttribute("content").match(/initial\-scale=([\d\.]+)/);l&&(j=parseFloat(l[1]),i=parseInt(1/j))}else if(h){var m=h.getAttribute("content");if(m){var n=m.match(/initial\-dpr=([\d\.]+)/),o=m.match(/maximum\-dpr=([\d\.]+)/);n&&(i=parseFloat(n[1]),j=parseFloat((1/i).toFixed(2))),o&&(i=parseFloat(o[1]),j=parseFloat((1/i).toFixed(2)))}}if(!i&&!j){var p=(a.navigator.appVersion.match(/android/gi),a.navigator.appVersion.match(/iphone/gi)),q=a.devicePixelRatio;i=p?q>=3&&(!i||i>=3)?3:q>=2&&(!i||i>=2)?2:1:1,j=1/i}if(f.setAttribute("data-dpr",i),!g)if(g=e.createElement("meta"),g.setAttribute("name","viewport"),g.setAttribute("content","initial-scale="+j+", maximum-scale="+j+", minimum-scale="+j+", user-scalable=no"),f.firstElementChild)f.firstElementChild.appendChild(g);else{var r=e.createElement("div");r.appendChild(g),e.write(r.innerHTML)}a.addEventListener("resize",function(){clearTimeout(d),d=setTimeout(c,300)},!1),a.addEventListener("pageshow",function(a){a.persisted&&(clearTimeout(d),d=setTimeout(c,300))},!1),"complete"===e.readyState?e.body.style.fontSize=12*i+"px":e.addEventListener("DOMContentLoaded",function(){e.body.style.fontSize=12*i+"px"},!1),c(),k.dpr=a.dpr=i,k.refreshRem=c,k.rem2px=function(a){var b=parseFloat(a)*this.rem;return"string"==typeof a&&a.match(/rem$/)&&(b+="px"),b},k.px2rem=function(a){var b=parseFloat(a)/this.rem;return"string"==typeof a&&a.match(/px$/)&&(b+="rem"),b}}(window,window.lib||(window.lib={})); -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/common/js/hot-reload.js: -------------------------------------------------------------------------------- 1 | (function(doc){ 2 | setTimeout(function(){ 3 | doc.head.querySelector('link[data-hr="hot-reload"]').remove(); 4 | doc.body.querySelector('script[data-hr="hot-reload"]').remove(); 5 | },300); 6 | })(document); -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/app.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | import 'swiper/dist/css/swiper.css' 3 | import Vue from 'vue'; 4 | import App from './components/App'; 5 | import {isBrowser} from "../../../common/js/commonUtils"; 6 | 7 | export function createApp(store) { 8 | 9 | let app = new Vue({ 10 | data: store, 11 | render: h => h(App) 12 | }); 13 | 14 | if (isBrowser()) { 15 | // const eruda = require('eruda'); 16 | // eruda.init(); 17 | const VueAwesomeSwiper = require('vue-awesome-swiper/dist/ssr') 18 | Vue.use(VueAwesomeSwiper) 19 | } 20 | 21 | return { store, app, App} 22 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/components/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 63 | 64 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/components/Bar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | 37 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/components/Foo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | background-color: aliceblue; 4 | } 5 | 6 | h1,h2{ 7 | margin: 0; 8 | } 9 | 10 | .container{ 11 | background-color: #FF9800; 12 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hello 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {isBrowser} from "../../../common/js/commonUtils"; 3 | 4 | export function getHeros(params){ 5 | // return axios.get('http://127.0.0.1:8080/heros'); 6 | // return axios.get('https://api.github.com/users/linweiwei123/repos'); 7 | let host = isBrowser() ? "" : "http://test-ybbview.seeyouyima.com"; 8 | return axios.get(host + '/gravidity-api/v2/knowledge_column_list_v2', { params }); 9 | } 10 | 11 | export function clientGetHeros(params){ 12 | // return axios.get('http://127.0.0.1:8080/heros'); 13 | // return axios.get('https://api.github.com/users/linweiwei123/repos'); 14 | return axios.get('/gravidity-api/v2/knowledge_column_list_v2', { params }); 15 | } 16 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/app/store.js: -------------------------------------------------------------------------------- 1 | import * as service from './service' 2 | 3 | var store = { 4 | state: { 5 | message: 'hello', 6 | heros: [], 7 | }, 8 | setHerosAction() { 9 | return service.getHeros() 10 | .then( resp => { 11 | // resp需要对上data 12 | //console.log('=====返回的heros', resp.data.data.column_list); 13 | this.state.heros = resp.data.data.column_list; 14 | }) 15 | }, 16 | addHerosAction(newValue){ 17 | this.state.heros = newValue; 18 | }, 19 | clearHerosAction(){ 20 | this.state.heros = []; 21 | } 22 | } 23 | 24 | 25 | export function createStore() { 26 | return store; 27 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/entry-client.js: -------------------------------------------------------------------------------- 1 | import {createStore} from "./app/store"; 2 | import { deepExtend } from "../../common/js/commonUtils"; 3 | import {createApp} from "./app/app"; 4 | 5 | let store = createStore(); 6 | store.state = deepExtend(store.state, window.__INITIAL_STATE__); 7 | let { app, App } = createApp(store); 8 | 9 | // 降级方案,判断是否要做CSR 10 | if(isCSR()){ 11 | createAppEl(); 12 | App.asyncData(store) 13 | .then( resp => { 14 | app.$mount('#app', true); 15 | }) 16 | } 17 | else { 18 | app.$mount('#app', true); 19 | } 20 | 21 | // 检查有没有appid,没有则表示是CSR,创建一个div,设置id为app 22 | function isCSR(){ 23 | let appEl = document.getElementById('app'); 24 | return !appEl; 25 | } 26 | 27 | function createAppEl(){ 28 | let appEl = document.createElement('div'); 29 | appEl.setAttribute('id', 'app') 30 | document.querySelector('body').insertBefore(appEl, document.querySelector('body').firstChild); 31 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/client/demo/index/entry-server.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "./app/app"; 2 | import { createStore } from "./app/store"; 3 | 4 | export default context => { 5 | const store = createStore(); 6 | const { app, App } = createApp(store); 7 | 8 | console.log('请求开始', new Date().getTime()) 9 | return App.asyncData(store) 10 | .then( resp => { 11 | console.log('请求返回', new Date().getTime()) 12 | context.state = store.state; 13 | return app; 14 | }) 15 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/mg.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // 启动的客户端服务器端口 4 | clientPort: '8080', 5 | 6 | // 服务端服务器端口 7 | server: { 8 | port: '8090', 9 | }, 10 | 11 | // 上传相关配置 12 | upload: { 13 | cdn: '//cnd.yintage.com/h5/', 14 | projectPrefix: 'h5', 15 | 16 | // 如果是阿里云,则aliconfig配置一个空对象,目前采用.aliossacess 文件配置的方式 17 | // aliconfig: { 18 | // 19 | // }, 20 | // 七牛云 21 | 22 | qconfig: { 23 | ACCESS_KEY: 'ei1uOdGpVLliA7kb50sLcV9i4wfYLPwt5v0shU10', 24 | SECRET_KEY: '-pFFIY-ew35Exyfcd67Sbaw40k15ah3UfZTFWFKF', 25 | bucket:'hotshots-image', 26 | origin:'http://cnd.yintage.com' 27 | }, 28 | 29 | // 是否编译后自动上传 30 | autoUpload: true 31 | 32 | } 33 | }; -------------------------------------------------------------------------------- /templates_vue_ssr/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mg-template", 3 | "version": "1.0.0", 4 | "description": "A excellent develop project template for mobile H5 !", 5 | "main": "app.js", 6 | "template": "vue_ssr", 7 | "scripts": { 8 | "server": "cross-env ENV=development nodemon app.js", 9 | "server:prod": "cross-env ENV=prod nodemon app.js", 10 | "start": "cross-env NODE_ENV=development node build/commands/ocstart.js", 11 | "build": "cross-env NODE_ENV=production node build/commands/build.js", 12 | "upload": "node build/commands/upload.js" 13 | }, 14 | "dependencies": { 15 | "ali-oss": "^4.12.2", 16 | "axios": "^0.18.0", 17 | "body-parser": "~1.18.2", 18 | "browser-sync": "^2.23.6", 19 | "cookie-parser": "~1.4.3", 20 | "cross-env": "^5.1.4", 21 | "debug": "~2.6.9", 22 | "ejs": "~2.5.7", 23 | "express": "~4.15.5", 24 | "g": "^2.0.1", 25 | "http-proxy-middleware": "^0.19.0", 26 | "morgan": "~1.9.0", 27 | "nodemon": "^1.17.1", 28 | "postcss-px2rem": "^0.3.0", 29 | "serve-favicon": "~2.4.5", 30 | "vue-server-renderer": "^2.6.10" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.0.0-beta.40", 34 | "@babel/preset-env": "^7.0.0-beta.40", 35 | "autoprefixer": "^8.2.0", 36 | "babel-loader": "^8.0.0-beta.0", 37 | "chalk": "^2.4.1", 38 | "clui": "^0.3.6", 39 | "css-loader": "^0.28.10", 40 | "delete": "^1.1.0", 41 | "eruda": "^1.5.6", 42 | "eruda-webpack-plugin": "^1.2.0", 43 | "extract-text-webpack-plugin": "^3.0.2", 44 | "file-loader": "^1.1.11", 45 | "html-loader": "^0.5.5", 46 | "html-webpack-plugin": "^3.2.0", 47 | "inquirer": "^5.2.0", 48 | "jquery": "^3.3.1", 49 | "less": "^3.7.0", 50 | "less-loader": "^4.1.0", 51 | "meetyou-ali-oss": "^1.0.7", 52 | "mini-css-extract-plugin": "^0.4.2", 53 | "node-sass": "^4.9.3", 54 | "optimize-css-assets-webpack-plugin": "^5.0.1", 55 | "ora": "^3.0.0", 56 | "postcss-loader": "^2.1.3", 57 | "postcss-safe-parser": "^3.0.1", 58 | "progress-bar-webpack-plugin": "^1.11.0", 59 | "qn": "^1.3.0", 60 | "raw-loader": "^0.5.1", 61 | "rimraf": "^2.6.2", 62 | "run-p": "0.0.0", 63 | "sass-loader": "^7.1.0", 64 | "style-loader": "^0.20.3", 65 | "uglifyjs-webpack-plugin": "^1.3.0", 66 | "url-loader": "^1.1.1", 67 | "vue": "^2.6.10", 68 | "vue-awesome-swiper": "^3.1.3", 69 | "vue-loader": "^15.7.0", 70 | "vue-style-loader": "^4.1.2", 71 | "vue-template-compiler": "^2.6.10", 72 | "webpack": "^4.17.1", 73 | "webpack-bundle-analyzer": "^2.13.1", 74 | "webpack-cli": "^3.3.0", 75 | "webpack-dev-middleware": "^3.2.0", 76 | "webpack-hot-middleware": "^2.22.3" 77 | }, 78 | "keywords": [], 79 | "author": "", 80 | "license": "ISC", 81 | "nodemonConfig": { 82 | "ignore": [ 83 | "client/**", 84 | "dist/**", 85 | "build/**" 86 | ], 87 | "delay": "2500" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yitala on 2018/3/27. 3 | */ 4 | module.exports = { 5 | parser: 'postcss-safe-parser', 6 | plugins: { 7 | // "postcss-px2rem":{ 8 | // remUnit: 37.5 9 | // }, 10 | autoprefixer: { 11 | browsers: [ 12 | 'last 2 versions', 13 | 'iOS >= 8' 14 | ] 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/process.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps":[{ 3 | "script" : "app.js", 4 | "instances": "4", 5 | "exec_mode": "cluster", 6 | "watch" : true, 7 | "env" :{ 8 | "ENV": "prod" 9 | }, 10 | "env_production":{ 11 | "ENV": "prod" 12 | } 13 | }] 14 | } -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/routes/demo.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { createBundleRenderer } = require('vue-server-renderer'); 4 | const env = process.env.ENV !== 'prod' ? 'dev' : 'prod'; 5 | const bundle = fs.readFileSync(path.resolve('server/ssr_code/demo/index-server.js'), 'utf8'); 6 | const renderer = createBundleRenderer(bundle, { 7 | template: fs.readFileSync(path.resolve('server/views/' + env + '/demo/index.html'), 'utf8') 8 | }); 9 | 10 | module.exports.index = function(req, res){ 11 | 12 | if(req.query.csr === 'true'){ 13 | res.render('demo/index') 14 | } 15 | else{ 16 | const context = { url: req.url } 17 | // 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。 18 | // 现在我们的服务器与应用程序已经解耦! 19 | console.log('render start', new Date().getTime()) 20 | renderer.renderToString(context, (err, html) => { 21 | if(err){ 22 | console.log(err); 23 | } 24 | console.log('render End', new Date().getTime()) 25 | res.end(html) 26 | }) 27 | } 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/routes/index.js: -------------------------------------------------------------------------------- 1 | const demo = require('./demo'); 2 | //<@add page@> 3 | 4 | module.exports.init = app =>{ 5 | 6 | app.get('/demo', demo.index); 7 | //<@add page router@> 8 | 9 | return app; 10 | }; 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/dev/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hello 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/dev/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | 12 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/prod/demo/index.html: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /templates_vue_ssr/project/server/views/prod/head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates_vue_ssr/project/static/readme.md: -------------------------------------------------------------------------------- 1 | 存放静态资源,如静态html等 --------------------------------------------------------------------------------