├── .gitignore ├── LICENSE ├── README.md ├── meta.js ├── package.json ├── scenarios ├── README.md ├── full-karma-airbnb.json ├── full.json ├── index.js └── minimal.json ├── template ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ ├── webpack.prod.conf.js │ └── webpack.test.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── styles │ │ ├── global.css │ │ └── theme.styl │ └── views │ │ └── home.vue └── static │ ├── .gitkeep │ └── responsive.js └── utils └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | test/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mand-mobile-template 2 | 3 | > A full-featured Webpack setup with Mand Mobile and also with hot-reload, lint-on-save, unit testing, css extraction. 4 | 5 | ## Usage 6 | 7 | This is a [Mand Mobile](https://github.com/didi/mand-mobile) project template for [vue-cli](https://github.com/vuejs/vue-cli). **It is recommended to use npm 3+ for a more efficient dependency tree.** 8 | 9 | ``` bash 10 | $ npm install -g vue-cli 11 | $ vue init mand-mobile/mand-mobile-template my-project 12 | $ cd my-project 13 | $ npm install 14 | $ npm run dev 15 | ``` 16 | 17 | The development server will run on port 8080 by default. If that port is already in use on your machine, the next free port will be used. 18 | 19 | ## Scripts 20 | 21 | [For this template](http://vuejs-templates.github.io/webpack): common questions specific to this template are answered and each part is described in greater detail 22 | 23 | - `npm run dev`: first-in-class development experience. 24 | 25 | - `npm run build`: Production ready build. 26 | 27 | - `npm run unit`: Unit tests run in [JSDOM](https://github.com/tmpvar/jsdom) with [Jest](https://facebook.github.io/jest/), or in PhantomJS with Karma + Mocha + karma-webpack. 28 | 29 | - `npm run e2e`: End-to-end tests with [Nightwatch](http://nightwatchjs.org/). 30 | 31 | ## Custom Theme 32 | We have already integrated configuration of custom theme within the project. You can enable it in two steps: 33 | 34 | - At initialization,Answer `Y` to question `Need customize theme?` 35 | 36 | - Edit `src/styles/theme.styl` during development 37 | 38 | Detailed configuration of custom theme can refer to [Custom Theme](https://didi.github.io/mand-mobile/#/docs/theme) 39 | 40 | 41 | -------------------------------------------------------------------------------- /meta.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const { 5 | sortDependencies, 6 | installDependencies, 7 | runLintFix, 8 | printMessage, 9 | } = require('./utils') 10 | const pkg = require('./package.json') 11 | 12 | const templateVersion = pkg.version 13 | 14 | const { addTestAnswers } = require('./scenarios') 15 | 16 | module.exports = { 17 | metalsmith: { 18 | // When running tests for the template, this adds answers for the selected scenario 19 | before: addTestAnswers 20 | }, 21 | helpers: { 22 | if_or(v1, v2, options) { 23 | 24 | if (v1 || v2) { 25 | return options.fn(this) 26 | } 27 | 28 | return options.inverse(this) 29 | }, 30 | template_version() { 31 | return templateVersion 32 | }, 33 | }, 34 | 35 | prompts: { 36 | name: { 37 | when: 'isNotTest', 38 | type: 'string', 39 | required: true, 40 | message: 'Project name', 41 | }, 42 | description: { 43 | when: 'isNotTest', 44 | type: 'string', 45 | required: false, 46 | message: 'Project description', 47 | default: 'A Vue.js Project for Mand Mobile', 48 | }, 49 | author: { 50 | when: 'isNotTest', 51 | type: 'string', 52 | message: 'Author', 53 | }, 54 | build: { 55 | when: 'isNotTest', 56 | type: 'list', 57 | message: 'Vue build', 58 | choices: [ 59 | { 60 | name: 'Runtime + Compiler: recommended for most users', 61 | value: 'standalone', 62 | short: 'standalone', 63 | }, 64 | { 65 | name: 66 | 'Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed in .vue files - render functions are required elsewhere', 67 | value: 'runtime', 68 | short: 'runtime', 69 | }, 70 | ], 71 | }, 72 | router: { 73 | when: 'isNotTest', 74 | type: 'confirm', 75 | message: 'Install vue-router?', 76 | }, 77 | theme: { 78 | when: 'isNotTest', 79 | type: 'confirm', 80 | message: 'Need customize theme?', 81 | }, 82 | lint: { 83 | when: 'isNotTest', 84 | type: 'confirm', 85 | message: 'Use ESLint to lint your code?', 86 | }, 87 | lintConfig: { 88 | when: 'isNotTest && lint', 89 | type: 'list', 90 | message: 'Pick an ESLint preset', 91 | choices: [ 92 | { 93 | name: 'Standard (https://github.com/standard/standard)', 94 | value: 'standard', 95 | short: 'Standard', 96 | }, 97 | { 98 | name: 'Airbnb (https://github.com/airbnb/javascript)', 99 | value: 'airbnb', 100 | short: 'Airbnb', 101 | }, 102 | { 103 | name: 'none (configure it yourself)', 104 | value: 'none', 105 | short: 'none', 106 | }, 107 | ], 108 | }, 109 | unit: { 110 | when: 'isNotTest', 111 | type: 'confirm', 112 | message: 'Set up unit tests', 113 | }, 114 | runner: { 115 | when: 'isNotTest && unit', 116 | type: 'list', 117 | message: 'Pick a test runner', 118 | choices: [ 119 | { 120 | name: 'Jest', 121 | value: 'jest', 122 | short: 'jest', 123 | }, 124 | { 125 | name: 'Karma and Mocha', 126 | value: 'karma', 127 | short: 'karma', 128 | }, 129 | { 130 | name: 'none (configure it yourself)', 131 | value: 'noTest', 132 | short: 'noTest', 133 | }, 134 | ], 135 | }, 136 | e2e: { 137 | when: 'isNotTest', 138 | type: 'confirm', 139 | message: 'Setup e2e tests with Nightwatch?', 140 | }, 141 | autoInstall: { 142 | when: 'isNotTest', 143 | type: 'list', 144 | message: 145 | 'Should we run `npm install` for you after the project has been created? (recommended)', 146 | choices: [ 147 | { 148 | name: 'Yes, use NPM', 149 | value: 'npm', 150 | short: 'npm', 151 | }, 152 | { 153 | name: 'Yes, use Yarn', 154 | value: 'yarn', 155 | short: 'yarn', 156 | }, 157 | { 158 | name: 'No, I will handle that myself', 159 | value: false, 160 | short: 'no', 161 | }, 162 | ], 163 | }, 164 | }, 165 | filters: { 166 | '.eslintrc.js': 'lint', 167 | '.eslintignore': 'lint', 168 | 'config/test.env.js': 'unit || e2e', 169 | 'build/webpack.test.conf.js': "unit && runner === 'karma'", 170 | 'test/unit/**/*': 'unit', 171 | 'test/unit/index.js': "unit && runner === 'karma'", 172 | 'test/unit/jest.conf.js': "unit && runner === 'jest'", 173 | 'test/unit/karma.conf.js': "unit && runner === 'karma'", 174 | 'test/unit/specs/index.js': "unit && runner === 'karma'", 175 | 'test/unit/setup.js': "unit && runner === 'jest'", 176 | 'test/e2e/**/*': 'e2e', 177 | 'src/router/**/*': 'router', 178 | 'src/views/**/*': 'router', 179 | 'src/styles/theme.styl': 'theme', 180 | }, 181 | complete: function(data, { chalk }) { 182 | const green = chalk.green 183 | 184 | sortDependencies(data, green) 185 | 186 | const cwd = path.join(process.cwd(), data.inPlace ? '' : data.destDirName) 187 | 188 | if (data.autoInstall) { 189 | installDependencies(cwd, data.autoInstall, green) 190 | .then(() => { 191 | return runLintFix(cwd, data, green) 192 | }) 193 | .then(() => { 194 | printMessage(data, green) 195 | }) 196 | .catch(e => { 197 | console.log(chalk.red('Error:'), e) 198 | }) 199 | } else { 200 | printMessage(data, chalk) 201 | } 202 | }, 203 | } 204 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mand-mobile-template", 3 | "version": "1.0.0", 4 | "license": "Apache", 5 | "description": "A full-featured Webpack setup with hot-reload, lint-on-save, unit testing & css extraction.", 6 | "devDependencies": { 7 | "vue-cli": "^2.8.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scenarios/README.md: -------------------------------------------------------------------------------- 1 | ## What is this folder? 2 | 3 | This folder contains test scenarios for the automated tests of the template on CircleCI. 4 | 5 | Each `.json`file contains an object that represents a set of answers to the questions that vue-cli asks the user when installing the template. 6 | 7 | With the code from `index.js`, we insert those answers into the metalsmith metadata and skip the inquirer questions, thus allowing us to run different test scenarios in CI without having to actually provide any answers to inquirer or to mock it. 8 | 9 | ## The scenarios 10 | 11 | We currently have 3 scenarios set up: 12 | 13 | 1. 'minimal': it basically answers "no" to ever choice, so no router, no eslint, no tests 14 | 2. 'full': It answers "yes" to every choice. With router, with linting (standard), with full tests (jest & e2e) 15 | 3. 'full-airbnb-karma': like 'full', but using airbnb eslint config instead of standard and karma instead of jest for unit tests. 16 | 17 | Other permutations might be worth testing to secure against edge cases, but this gives us a decent level of security over common combinations. 18 | 19 | ## How to use it? 20 | 21 | Choosing a scenario is done through setting an ENV variable named `VUE_TEMPL_TEST`. 22 | 23 | You can run a scenario yourself by running this in your terminal: 24 | 25 | ```` 26 | VUE_TEMPL_TEST=minimal vue init webpack your-directory 27 | ``` 28 | -------------------------------------------------------------------------------- /scenarios/full-karma-airbnb.json: -------------------------------------------------------------------------------- 1 | { 2 | "noEscape": true, 3 | "name": "test", 4 | "description": "A Vue.js project", 5 | "author": "CircleCI", 6 | "build": "standalone", 7 | "router": false, 8 | "lint": true, 9 | "lintConfig": "airbnb", 10 | "unit": true, 11 | "runner": "karma", 12 | "e2e": true, 13 | "autoInstall": false 14 | } 15 | -------------------------------------------------------------------------------- /scenarios/full.json: -------------------------------------------------------------------------------- 1 | { 2 | "noEscape": true, 3 | "name": "test", 4 | "description": "A Vue.js project", 5 | "author": "CircleCI", 6 | "build": "runtime", 7 | "router": false, 8 | "lint": true, 9 | "lintConfig": "standard", 10 | "unit": true, 11 | "runner": "jest", 12 | "e2e": true, 13 | "autoInstall": false 14 | } 15 | -------------------------------------------------------------------------------- /scenarios/index.js: -------------------------------------------------------------------------------- 1 | const scenarios = [ 2 | 'full', 3 | 'full-karma-airbnb', 4 | 'minimal' 5 | ] 6 | 7 | const index = scenarios.indexOf(process.env.VUE_TEMPL_TEST) 8 | 9 | const isTest = exports.isTest = index !== -1 10 | 11 | const scenario = isTest && require(`./${scenarios[index]}.json`) 12 | 13 | exports.addTestAnswers = (metalsmith, options, helpers) => { 14 | Object.assign( 15 | metalsmith.metadata(), 16 | { isNotTest: !isTest }, 17 | isTest ? scenario : {} 18 | ) 19 | } -------------------------------------------------------------------------------- /scenarios/minimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "noEscape": true, 3 | "name": "test-minimal", 4 | "description": "Testing the minimal template setup", 5 | "author": "CircleCI", 6 | "build": "standalone", 7 | "router": false, 8 | "lint": false, 9 | "lintConfig": "standard", 10 | "unit": false, 11 | "runner": "jest", 12 | "e2e": false, 13 | "autoInstall": false 14 | } 15 | -------------------------------------------------------------------------------- /template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": [ 12 | "transform-vue-jsx", 13 | "transform-runtime", 14 | [ 15 | "import", 16 | { 17 | "libraryName": "mand-mobile"{{#theme}}, 18 | "libraryDirectory": "components" 19 | {{/theme}} 20 | } 21 | ] 22 | ]{{#if_or unit e2e}}, 23 | "env": { 24 | "test": { 25 | "presets": ["env", "stage-2"]{{#if_eq runner "karma"}}, 26 | "plugins": ["transform-vue-jsx", "istanbul"]{{/if_eq}}{{#if_eq runner "jest"}}, 27 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]{{/if_eq}} 28 | } 29 | }{{/if_or}} 30 | } 31 | -------------------------------------------------------------------------------- /template/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /template/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | {{#unit}} 6 | /test/unit/coverage/ 7 | {{/unit}} 8 | -------------------------------------------------------------------------------- /template/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | {{#if_eq lintConfig "standard"}} 12 | extends: [ 13 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 14 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 15 | 'plugin:vue/essential', 16 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 17 | 'standard' 18 | ], 19 | {{/if_eq}} 20 | {{#if_eq lintConfig "airbnb"}} 21 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 22 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 23 | extends: ['plugin:vue/essential', 'airbnb-base'], 24 | {{/if_eq}} 25 | {{#if_eq lintConfig "none"}} 26 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 27 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 28 | extends: ['plugin:vue/essential'], 29 | {{/if_eq}} 30 | // required to lint *.vue files 31 | plugins: [ 32 | 'vue' 33 | ], 34 | {{#if_eq lintConfig "airbnb"}} 35 | // check if imports actually resolve 36 | settings: { 37 | 'import/resolver': { 38 | webpack: { 39 | config: 'build/webpack.base.conf.js' 40 | } 41 | } 42 | }, 43 | {{/if_eq}} 44 | // add your custom rules here 45 | rules: { 46 | {{#if_eq lintConfig "standard"}} 47 | // allow async-await 48 | 'generator-star-spacing': 'off', 49 | {{/if_eq}} 50 | {{#if_eq lintConfig "airbnb"}} 51 | // don't require .vue extension when importing 52 | 'import/extensions': ['error', 'always', { 53 | js: 'never', 54 | vue: 'never' 55 | }], 56 | // disallow reassignment of function parameters 57 | // disallow parameter object manipulation except for specific exclusions 58 | 'no-param-reassign': ['error', { 59 | props: true, 60 | ignorePropertyModificationsFor: [ 61 | 'state', // for vuex state 62 | 'acc', // for reduce accumulators 63 | 'e' // for e.returnvalue 64 | ] 65 | }], 66 | // allow optionalDependencies 67 | 'import/no-extraneous-dependencies': ['error', { 68 | optionalDependencies: ['test/unit/index.js'] 69 | }], 70 | {{/if_eq}} 71 | // allow debugger during development 72 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | {{#unit}} 8 | /test/unit/coverage/ 9 | {{/unit}} 10 | {{#e2e}} 11 | /test/e2e/reports/ 12 | selenium-debug.log 13 | {{/e2e}} 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | -------------------------------------------------------------------------------- /template/.postcssrc.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | 3 | // https://github.com/michael-ciniawsky/postcss-load-config 4 | module.exports = { 5 | "plugins": { 6 | "postcss-import": {}, 7 | "postcss-url": {}, 8 | // to edit target browsers: use "browserslist" field in package.json 9 | "autoprefixer": {}, 10 | "postcss-pxtorem": config.mand.pxtorem 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | 3 | > {{ description }} 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | {{#unit}} 20 | 21 | # run unit tests 22 | npm run unit 23 | {{/unit}} 24 | {{#e2e}} 25 | 26 | # run e2e tests 27 | npm run e2e 28 | {{/e2e}} 29 | {{#if_or unit e2e}} 30 | 31 | # run all tests 32 | npm test 33 | {{/if_or}} 34 | ``` 35 | -------------------------------------------------------------------------------- /template/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // if you are using ts-loader, setting this to true will make typescript errors show up during build 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /template/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /template/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mand-mobile/mand-mobile-template/94471398ca8c269a229b6d01c58966bc1489631d/template/build/logo.png -------------------------------------------------------------------------------- /template/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | exports.assetsPath = function (_path) { 12 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 13 | ? config.build.assetsSubDirectory 14 | : config.dev.assetsSubDirectory 15 | 16 | return path.posix.join(assetsSubDirectory, _path) 17 | } 18 | 19 | exports.cssLoaders = function (options) { 20 | options = options || {} 21 | 22 | const cssLoader = { 23 | loader: 'css-loader', 24 | options: { 25 | sourceMap: options.sourceMap 26 | } 27 | } 28 | 29 | const postcssLoader = { 30 | loader: 'postcss-loader', 31 | options: { 32 | sourceMap: options.sourceMap 33 | } 34 | } 35 | 36 | // generate loader string to be used with extract text plugin 37 | function generateLoaders (loader, loaderOptions) { 38 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 39 | 40 | if (loader) { 41 | loaders.push({ 42 | loader: loader + '-loader', 43 | options: Object.assign({}, loaderOptions, { 44 | sourceMap: options.sourceMap 45 | }) 46 | }) 47 | } 48 | 49 | // Extract CSS when that option is specified 50 | // (which is the case during production build) 51 | if (options.extract) { 52 | return ExtractTextPlugin.extract({ 53 | use: loaders, 54 | fallback: 'vue-style-loader' 55 | }) 56 | } else { 57 | return ['vue-style-loader'].concat(loaders) 58 | } 59 | } 60 | 61 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 62 | return { 63 | css: generateLoaders(), 64 | postcss: generateLoaders(), 65 | less: generateLoaders('less'), 66 | sass: generateLoaders('sass', { indentedSyntax: true }), 67 | scss: generateLoaders('sass'), 68 | stylus: generateLoaders('stylus'{{#theme}}, { 69 | import: [config.mand.theme.path] 70 | }{{/theme}}), 71 | styl: generateLoaders('stylus'{{#theme}}, { 72 | import: [config.mand.theme.path] 73 | }{{/theme}}) 74 | } 75 | } 76 | 77 | // Generate loaders for standalone style files (outside of .vue) 78 | exports.styleLoaders = function (options) { 79 | const output = [] 80 | const loaders = exports.cssLoaders(options) 81 | 82 | for (const extension in loaders) { 83 | const loader = loaders[extension] 84 | output.push({ 85 | test: new RegExp('\\.' + extension + '$'), 86 | use: loader 87 | }) 88 | } 89 | 90 | return output 91 | } 92 | 93 | exports.createNotifierCallback = () => { 94 | const notifier = require('node-notifier') 95 | 96 | return (severity, errors) => { 97 | if (severity !== 'error') return 98 | 99 | const error = errors[0] 100 | const filename = error.file && error.file.split('!').pop() 101 | 102 | notifier.notify({ 103 | title: packageConfig.name, 104 | message: severity + ': ' + error.name, 105 | subtitle: filename || '', 106 | icon: path.join(__dirname, 'logo.png') 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /template/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /template/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | {{#lint}}const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }){{/lint}} 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | {{#if_eq build "standalone"}} 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | {{/if_eq}} 40 | '@': resolve('src'), 41 | } 42 | }, 43 | module: { 44 | rules: [ 45 | {{#lint}} 46 | ...(config.dev.useEslint ? [createLintingRule()] : []), 47 | {{/lint}} 48 | { 49 | test: /\.vue$/, 50 | loader: 'vue-loader', 51 | options: vueLoaderConfig 52 | }, 53 | { 54 | test: /\.js$/, 55 | loader: 'babel-loader', 56 | include: [ 57 | resolve('src'), 58 | resolve('test'), 59 | resolve('node_modules/webpack-dev-server/client'), 60 | resolve('node_modules/mand-mobile/components') 61 | ] 62 | }, 63 | { 64 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 65 | loader: 'url-loader', 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 77 | } 78 | }, 79 | { 80 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 81 | loader: 'url-loader', 82 | options: { 83 | limit: 10000, 84 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 85 | } 86 | } 87 | ] 88 | }, 89 | node: { 90 | // prevent webpack from injecting useless setImmediate polyfill because Vue 91 | // source contains it (although only uses it if it's native). 92 | setImmediate: false, 93 | // prevent webpack from injecting mocks to Node native modules 94 | // that does not make sense for the client 95 | dgram: 'empty', 96 | fs: 'empty', 97 | net: 'empty', 98 | tls: 'empty', 99 | child_process: 'empty' 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /template/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /template/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = {{#if_or unit e2e}}process.env.NODE_ENV === 'testing' 15 | ? require('../config/test.env') 16 | : {{/if_or}}require('../config/prod.env') 17 | 18 | const webpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ 21 | sourceMap: config.build.productionSourceMap, 22 | extract: true, 23 | usePostCSS: true 24 | }) 25 | }, 26 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 31 | }, 32 | plugins: [ 33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 34 | new webpack.DefinePlugin({ 35 | 'process.env': env 36 | }), 37 | new UglifyJsPlugin({ 38 | uglifyOptions: { 39 | compress: { 40 | warnings: false 41 | } 42 | }, 43 | sourceMap: config.build.productionSourceMap, 44 | parallel: true 45 | }), 46 | // extract css into its own file 47 | new ExtractTextPlugin({ 48 | filename: utils.assetsPath('css/[name].[contenthash].css'), 49 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 53 | allChunks: true, 54 | }), 55 | // Compress extracted CSS. We are using this plugin so that possible 56 | // duplicated CSS from different components can be deduped. 57 | new OptimizeCSSPlugin({ 58 | cssProcessorOptions: config.build.productionSourceMap 59 | ? { safe: true, map: { inline: false } } 60 | : { safe: true } 61 | }), 62 | // generate dist index.html with correct asset hash for caching. 63 | // you can customize output by editing /index.html 64 | // see https://github.com/ampedandwired/html-webpack-plugin 65 | new HtmlWebpackPlugin({ 66 | filename: {{#if_or unit e2e}}process.env.NODE_ENV === 'testing' 67 | ? 'index.html' 68 | : {{/if_or}}config.build.index, 69 | template: 'index.html', 70 | inject: true, 71 | minify: { 72 | removeComments: true, 73 | collapseWhitespace: true, 74 | removeAttributeQuotes: true 75 | // more options: 76 | // https://github.com/kangax/html-minifier#options-quick-reference 77 | }, 78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 79 | chunksSortMode: 'dependency' 80 | }), 81 | // keep module.id stable when vendor modules does not change 82 | new webpack.HashedModuleIdsPlugin(), 83 | // enable scope hoisting 84 | new webpack.optimize.ModuleConcatenationPlugin(), 85 | // split vendor js into its own file 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'vendor', 88 | minChunks (module) { 89 | // any required modules inside node_modules are extracted to vendor 90 | return ( 91 | module.resource && 92 | /\.js$/.test(module.resource) && 93 | module.resource.indexOf( 94 | path.join(__dirname, '../node_modules') 95 | ) === 0 96 | ) 97 | } 98 | }), 99 | // extract webpack runtime and module manifest to its own file in order to 100 | // prevent vendor hash from being updated whenever app bundle is updated 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'manifest', 103 | minChunks: Infinity 104 | }), 105 | // This instance extracts shared chunks from code splitted chunks and bundles them 106 | // in a separate chunk, similar to the vendor chunk 107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 108 | new webpack.optimize.CommonsChunkPlugin({ 109 | name: 'app', 110 | async: 'vendor-async', 111 | children: true, 112 | minChunks: 3 113 | }), 114 | 115 | // copy custom static assets 116 | new CopyWebpackPlugin([ 117 | { 118 | from: path.resolve(__dirname, '../static'), 119 | to: config.build.assetsSubDirectory, 120 | ignore: ['.*'] 121 | } 122 | ]) 123 | ] 124 | }) 125 | 126 | if (config.build.productionGzip) { 127 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 128 | 129 | webpackConfig.plugins.push( 130 | new CompressionWebpackPlugin({ 131 | asset: '[path].gz[query]', 132 | algorithm: 'gzip', 133 | test: new RegExp( 134 | '\\.(' + 135 | config.build.productionGzipExtensions.join('|') + 136 | ')$' 137 | ), 138 | threshold: 10240, 139 | minRatio: 0.8 140 | }) 141 | ) 142 | } 143 | 144 | if (config.build.bundleAnalyzerReport) { 145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 147 | } 148 | 149 | module.exports = webpackConfig 150 | -------------------------------------------------------------------------------- /template/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is the webpack config used for unit tests. 3 | 4 | const utils = require('./utils') 5 | const webpack = require('webpack') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | 9 | const webpackConfig = merge(baseWebpackConfig, { 10 | // use inline sourcemap for karma-sourcemap-loader 11 | module: { 12 | rules: utils.styleLoaders() 13 | }, 14 | devtool: '#inline-source-map', 15 | resolveLoader: { 16 | alias: { 17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 19 | 'scss-loader': 'sass-loader' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': require('../config/test.env') 25 | }) 26 | ] 27 | }) 28 | 29 | // no need for app entry during tests 30 | delete webpackConfig.entry 31 | 32 | module.exports = webpackConfig 33 | -------------------------------------------------------------------------------- /template/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /template/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: {{ template_version }} 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | mand: { 13 | {{#theme}} 14 | theme: { 15 | path: resolve('src/styles/theme.styl') 16 | }, 17 | {{/theme}} 18 | pxtorem: { 19 | // If you modify rootValue, you also need to modify rem in static/responsive.js 20 | rootValue: 100, // 1rem=100px 21 | propWhiteList: [] 22 | } 23 | }, 24 | dev: { 25 | 26 | // Paths 27 | assetsSubDirectory: 'static', 28 | assetsPublicPath: '/', 29 | proxyTable: {}, 30 | 31 | // Various Dev Server settings 32 | host: 'localhost', // can be overwritten by process.env.HOST 33 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 34 | autoOpenBrowser: false, 35 | errorOverlay: true, 36 | notifyOnErrors: true, 37 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 38 | 39 | {{#lint}}// Use Eslint Loader? 40 | // If true, your code will be linted during bundling and 41 | // linting errors and warnings will be shown in the console. 42 | useEslint: true, 43 | // If true, eslint errors and warnings will also be shown in the error overlay 44 | // in the browser. 45 | showEslintErrorsInOverlay: false, 46 | {{/lint}} 47 | 48 | /** 49 | * Source Maps 50 | */ 51 | 52 | // https://webpack.js.org/configuration/devtool/#development 53 | devtool: 'cheap-module-eval-source-map', 54 | 55 | // If you have problems debugging vue-files in devtools, 56 | // set this to false - it *may* help 57 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 58 | cacheBusting: true, 59 | 60 | cssSourceMap: true 61 | }, 62 | 63 | build: { 64 | // Template for index.html 65 | index: path.resolve(__dirname, '../dist/index.html'), 66 | 67 | // Paths 68 | assetsRoot: path.resolve(__dirname, '../dist'), 69 | assetsSubDirectory: 'static', 70 | assetsPublicPath: '/', 71 | 72 | /** 73 | * Source Maps 74 | */ 75 | 76 | productionSourceMap: true, 77 | // https://webpack.js.org/configuration/devtool/#production 78 | devtool: '#source-map', 79 | 80 | // Gzip off by default as many popular static hosts such as 81 | // Surge or Netlify already gzip all static assets for you. 82 | // Before setting to `true`, make sure to: 83 | // npm install --save-dev compression-webpack-plugin 84 | productionGzip: false, 85 | productionGzipExtensions: ['js', 'css'], 86 | 87 | // Run the build command with an extra argument to 88 | // View the bundle analyzer report after build finishes: 89 | // `npm run build --report` 90 | // Set to `true` or `false` to always turn it on or off 91 | bundleAnalyzerReport: process.env.npm_config_report 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /template/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /template/config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ name }} 7 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ name }}", 3 | "version": "1.0.0", 4 | "description": "{{ description }}", 5 | "author": "{{ author }}", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | {{#if_eq runner "jest"}} 11 | "unit": "jest --config test/unit/jest.conf.js --coverage", 12 | {{/if_eq}} 13 | {{#if_eq runner "karma"}} 14 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 15 | {{/if_eq}} 16 | {{#e2e}} 17 | "e2e": "node test/e2e/runner.js", 18 | {{/e2e}} 19 | {{#if_or unit e2e}} 20 | "test": "{{#unit}}npm run unit{{/unit}}{{#unit}}{{#e2e}} && {{/e2e}}{{/unit}}{{#e2e}}npm run e2e{{/e2e}}", 21 | {{/if_or}} 22 | {{#lint}} 23 | "lint": "eslint --ext .js,.vue src{{#unit}} test/unit{{/unit}}{{#e2e}} test/e2e/specs{{/e2e}}", 24 | {{/lint}} 25 | "build": "node build/build.js" 26 | }, 27 | "dependencies": { 28 | "mand-mobile": "^1.2.1", 29 | "fastclick": "^1.0.6", 30 | {{#theme}} 31 | "nib": "^1.1.2", 32 | {{/theme}} 33 | "vue": "^2.5.2"{{#router}}, 34 | "vue-router": "^3.0.1"{{/router}} 35 | }, 36 | "devDependencies": { 37 | {{#lint}} 38 | "babel-eslint": "^7.2.3", 39 | "eslint": "^4.15.0", 40 | "eslint-friendly-formatter": "^3.0.0", 41 | "eslint-loader": "^1.7.1", 42 | "eslint-plugin-vue": "^4.0.0", 43 | {{#if_eq lintConfig "standard"}} 44 | "eslint-config-standard": "^10.2.1", 45 | "eslint-plugin-promise": "^3.4.0", 46 | "eslint-plugin-standard": "^3.0.1", 47 | "eslint-plugin-import": "^2.7.0", 48 | "eslint-plugin-node": "^5.2.0", 49 | {{/if_eq}} 50 | {{#if_eq lintConfig "airbnb"}} 51 | "eslint-config-airbnb-base": "^11.3.0", 52 | "eslint-import-resolver-webpack": "^0.8.3", 53 | "eslint-plugin-import": "^2.7.0", 54 | {{/if_eq}} 55 | {{/lint}} 56 | {{#if_eq runner "jest"}} 57 | "babel-jest": "^21.0.2", 58 | "babel-plugin-dynamic-import-node": "^1.2.0", 59 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 60 | "jest": "^22.0.4", 61 | "jest-serializer-vue": "^0.3.0", 62 | "vue-jest": "^1.0.2", 63 | {{/if_eq}} 64 | {{#if_eq runner "karma"}} 65 | "cross-env": "^5.0.1", 66 | "karma": "^1.4.1", 67 | "karma-coverage": "^1.1.1", 68 | "karma-mocha": "^1.3.0", 69 | "karma-phantomjs-launcher": "^1.0.2", 70 | "karma-phantomjs-shim": "^1.4.0", 71 | "karma-sinon-chai": "^1.3.1", 72 | "karma-sourcemap-loader": "^0.3.7", 73 | "karma-spec-reporter": "0.0.31", 74 | "karma-webpack": "^2.0.2", 75 | "mocha": "^3.2.0", 76 | "chai": "^4.1.2", 77 | "sinon": "^4.0.0", 78 | "sinon-chai": "^2.8.0", 79 | "inject-loader": "^3.0.0", 80 | "babel-plugin-istanbul": "^4.1.1", 81 | "phantomjs-prebuilt": "^2.1.14", 82 | {{/if_eq}} 83 | {{#e2e}} 84 | "babel-register": "^6.22.0", 85 | "chromedriver": "^2.27.2", 86 | "cross-spawn": "^5.0.1", 87 | "nightwatch": "^0.9.12", 88 | "selenium-server": "^3.0.1", 89 | {{/e2e}} 90 | "autoprefixer": "^7.1.2", 91 | "babel-core": "^6.22.1", 92 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 93 | "babel-loader": "^7.1.1", 94 | "babel-plugin-import": "^1.7.0", 95 | "babel-plugin-syntax-jsx": "^6.18.0", 96 | "babel-plugin-transform-runtime": "^6.22.0", 97 | "babel-plugin-transform-vue-jsx": "^3.5.0", 98 | "babel-preset-env": "^1.3.2", 99 | "babel-preset-stage-2": "^6.22.0", 100 | "chalk": "^2.0.1", 101 | "copy-webpack-plugin": "^4.0.1", 102 | "css-loader": "^0.28.0", 103 | "extract-text-webpack-plugin": "^3.0.0", 104 | "file-loader": "^1.1.4", 105 | "friendly-errors-webpack-plugin": "^1.6.1", 106 | "html-webpack-plugin": "^2.30.1", 107 | "webpack-bundle-analyzer": "^2.9.0", 108 | "node-notifier": "^5.1.2", 109 | "postcss-import": "^11.0.0", 110 | "postcss-loader": "^2.0.8", 111 | "postcss-pxtorem": "^4.0.1", 112 | "postcss-url": "^7.2.1", 113 | "semver": "^5.3.0", 114 | "shelljs": "^0.7.6", 115 | "optimize-css-assets-webpack-plugin": "^3.2.0", 116 | "ora": "^1.2.0", 117 | "rimraf": "^2.6.0", 118 | "stylus": "^0.54.5", 119 | "stylus-loader": "^3.0.2", 120 | "uglifyjs-webpack-plugin": "^1.1.1", 121 | "url-loader": "^0.5.8", 122 | "vue-loader": "^13.3.0", 123 | "vue-style-loader": "^3.0.1", 124 | "vue-template-compiler": "^2.5.2", 125 | "portfinder": "^1.0.13", 126 | "webpack": "^3.6.0", 127 | "webpack-dev-server": "^2.9.1", 128 | "webpack-merge": "^4.1.0" 129 | }, 130 | "engines": { 131 | "node": ">= 6.0.0", 132 | "npm": ">= 3.0.0" 133 | }, 134 | "browserslist": [ 135 | "> 1%", 136 | "last 2 versions", 137 | "not ie <= 8" 138 | ] 139 | } 140 | -------------------------------------------------------------------------------- /template/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 76 | -------------------------------------------------------------------------------- /template/src/main.js: -------------------------------------------------------------------------------- 1 | {{#if_eq build "standalone"}} 2 | // The Vue build version to load with the `import` command 3 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 4 | {{/if_eq}} 5 | import Vue from 'vue' 6 | import * as FastClick from "fastclick" 7 | import App from './App' 8 | {{#router}} 9 | import router from './router' 10 | {{/router}} 11 | {{#theme}} 12 | import './styles/theme.styl' 13 | {{/theme}} 14 | import './styles/global.css' 15 | 16 | FastClick.attach(document.body) 17 | 18 | Vue.config.productionTip = false 19 | 20 | /* eslint-disable no-new */ 21 | new Vue({ 22 | el: '#app', 23 | {{#router}} 24 | router, 25 | {{/router}} 26 | {{#if_eq build "runtime"}} 27 | render: h => h(App) 28 | {{/if_eq}} 29 | {{#if_eq build "standalone"}} 30 | components: { App }, 31 | template: '' 32 | {{/if_eq}} 33 | }) 34 | -------------------------------------------------------------------------------- /template/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/views/home' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | component: Home 13 | } 14 | ] 15 | }) 16 | -------------------------------------------------------------------------------- /template/src/styles/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | -------------------------------------------------------------------------------- /template/src/styles/theme.styl: -------------------------------------------------------------------------------- 1 | @import '~mand-mobile/components/_style/mixin/util' 2 | @import '~mand-mobile/components/_style/mixin/theme' 3 | 4 | // 建议安装并引入css拓展nib 5 | @import '~nib/lib/nib/vendor' 6 | @import '~nib/lib/nib/gradients' 7 | 8 | // 覆盖样式变量 9 | // 完整的变量列表可以查看 https://github.com/didi/mand-mobile/blob/master/components/_style/mixin/theme.styl 10 | color-primary = #fc9153 11 | -------------------------------------------------------------------------------- /template/src/views/home.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | 28 | 58 | -------------------------------------------------------------------------------- /template/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mand-mobile/mand-mobile-template/94471398ca8c269a229b6d01c58966bc1489631d/template/static/.gitkeep -------------------------------------------------------------------------------- /template/static/responsive.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | function resize () { 3 | var ww = window.innerWidth 4 | if (ww > window.screen.width) { 5 | window.requestAnimationFrame(resize) 6 | } else { 7 | if (ww > 750) { 8 | ww = 750 9 | } 10 | document.documentElement.style.fontSize = ww * 100 / 750 + 'px' 11 | } 12 | } 13 | 14 | resize() 15 | 16 | window.addEventListener('resize', resize) 17 | })(window, document) -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const spawn = require('child_process').spawn 4 | 5 | const lintStyles = ['standard', 'airbnb'] 6 | 7 | /** 8 | * Sorts dependencies in package.json alphabetically. 9 | * They are unsorted because they were grouped for the handlebars helpers 10 | * @param {object} data Data from questionnaire 11 | */ 12 | exports.sortDependencies = function sortDependencies(data) { 13 | const packageJsonFile = path.join( 14 | data.inPlace ? '' : data.destDirName, 15 | 'package.json' 16 | ) 17 | const packageJson = JSON.parse(fs.readFileSync(packageJsonFile)) 18 | packageJson.devDependencies = sortObject(packageJson.devDependencies) 19 | packageJson.dependencies = sortObject(packageJson.dependencies) 20 | fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2) + '\n') 21 | } 22 | 23 | /** 24 | * Runs `npm install` in the project directory 25 | * @param {string} cwd Path of the created project directory 26 | * @param {object} data Data from questionnaire 27 | */ 28 | exports.installDependencies = function installDependencies( 29 | cwd, 30 | executable = 'npm', 31 | color 32 | ) { 33 | console.log(`\n\n# ${color('Installing project dependencies ...')}`) 34 | console.log('# ========================\n') 35 | return runCommand(executable, ['install'], { 36 | cwd, 37 | }) 38 | } 39 | 40 | /** 41 | * Runs `npm run lint -- --fix` in the project directory 42 | * @param {string} cwd Path of the created project directory 43 | * @param {object} data Data from questionnaire 44 | */ 45 | exports.runLintFix = function runLintFix(cwd, data, color) { 46 | if (data.lint && lintStyles.indexOf(data.lintConfig) !== -1) { 47 | console.log( 48 | `\n\n${color( 49 | 'Running eslint --fix to comply with chosen preset rules...' 50 | )}` 51 | ) 52 | console.log('# ========================\n') 53 | const args = 54 | data.autoInstall === 'npm' 55 | ? ['run', 'lint', '--', '--fix'] 56 | : ['run', 'lint', '--fix'] 57 | return runCommand(data.autoInstall, args, { 58 | cwd, 59 | }) 60 | } 61 | return Promise.resolve() 62 | } 63 | 64 | /** 65 | * Prints the final message with instructions of necessary next steps. 66 | * @param {Object} data Data from questionnaire. 67 | */ 68 | exports.printMessage = function printMessage(data, { green, yellow }) { 69 | const message = ` 70 | # ${green('Project initialization finished!')} 71 | # ======================== 72 | 73 | To get started: 74 | 75 | ${yellow( 76 | `${data.inPlace ? '' : `cd ${data.destDirName}\n `}${installMsg( 77 | data 78 | )}${lintMsg(data)}npm run dev` 79 | )} 80 | 81 | Documentation can be found at https://vuejs-templates.github.io/webpack 82 | ` 83 | console.log(message) 84 | } 85 | 86 | /** 87 | * If the user will have to run lint --fix themselves, it returns a string 88 | * containing the instruction for this step. 89 | * @param {Object} data Data from questionnaire. 90 | */ 91 | function lintMsg(data) { 92 | return !data.autoInstall && 93 | data.lint && 94 | lintStyles.indexOf(data.lintConfig) !== -1 95 | ? 'npm run lint -- --fix (or for yarn: yarn run lint --fix)\n ' 96 | : '' 97 | } 98 | 99 | /** 100 | * If the user will have to run `npm install` or `yarn` themselves, it returns a string 101 | * containing the instruction for this step. 102 | * @param {Object} data Data from the questionnaire 103 | */ 104 | function installMsg(data) { 105 | return !data.autoInstall ? 'npm install (or if using yarn: yarn)\n ' : '' 106 | } 107 | 108 | /** 109 | * Spawns a child process and runs the specified command 110 | * By default, runs in the CWD and inherits stdio 111 | * Options are the same as node's child_process.spawn 112 | * @param {string} cmd 113 | * @param {array} args 114 | * @param {object} options 115 | */ 116 | function runCommand(cmd, args, options) { 117 | return new Promise((resolve, reject) => { 118 | const spwan = spawn( 119 | cmd, 120 | args, 121 | Object.assign( 122 | { 123 | cwd: process.cwd(), 124 | stdio: 'inherit', 125 | shell: true, 126 | }, 127 | options 128 | ) 129 | ) 130 | 131 | spwan.on('exit', () => { 132 | resolve() 133 | }) 134 | }) 135 | } 136 | 137 | function sortObject(object) { 138 | // Based on https://github.com/yarnpkg/yarn/blob/v1.3.2/src/config.js#L79-L85 139 | const sortedObject = {} 140 | Object.keys(object) 141 | .sort() 142 | .forEach(item => { 143 | sortedObject[item] = object[item] 144 | }) 145 | return sortedObject 146 | } 147 | --------------------------------------------------------------------------------