├── .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 [](http://badge.fury.io/js/multipages-generator)
4 | ======
5 |
6 | [](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 | 
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 | 
228 |
229 | ### meet analyse
230 | 通过meet analyse 查看占比
231 |
232 | 
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 [](http://badge.fury.io/js/multipages-generator)
4 | ======
5 |
6 | [](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 | 
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 | 
241 |
242 | ### meet analyse
243 |
244 | Use this command after builded.
245 |
246 | ```
247 | meet analyse
248 | ```
249 |
250 | 
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 |
12 |
13 |
14 |
15 |
16 |

17 |
18 |
19 |
20 |
21 |
22 |
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 |
12 |
13 |
14 |
15 |
16 |

17 |
18 |
19 |
20 |
21 |
22 |
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 |
2 |
3 |
Vue SSR
4 |
5 |
6 |
7 |
8 |
9 |
41 |
42 |
--------------------------------------------------------------------------------
/templates_vue_ssr/module/app/components/Bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Bar
4 |
content...
5 |
6 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/templates_vue_ssr/module/app/components/Foo.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
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 |
2 |
3 |
Vue SSR
4 |
5 |
6 |
7 |
8 |
9 |
63 |
64 |
--------------------------------------------------------------------------------
/templates_vue_ssr/project/client/demo/index/app/components/Bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
36 |
37 |
--------------------------------------------------------------------------------
/templates_vue_ssr/project/client/demo/index/app/components/Foo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![]()
5 |
6 |
7 |
{{mdata.title}}
8 |
{{mdata.author_title}}
9 |
10 |
11 |
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等
--------------------------------------------------------------------------------