Blame view

README.md 30.8 KB
406803045 committed
1 2
# vue-h5-template

sunnie committed
3
基于 vue-cli4.0 + webpack 4 + vant ui + sass+ rem 适配方案+axios 封装,构建手机端模板脚手架
宋楠 committed
4

sunnie committed
5
掘金: [vue-cli4 vant rem 移动端框架方案](https://juejin.im/post/5cfefc73f265da1bba58f9f7)
406803045 committed
6

sunniejs committed
7
[查看 demo](https://sunniejs.cn/vue-h5-template/#/) 建议手机端查看
sunnie committed
8

sunnie committed
9
<p>
sunnie committed
10
  <img src="./static/demo.png" width="320" style="display:inline;">
sunnie committed
11 12
</p>

宋楠 committed
13
### Node 版本要求
sunnie committed
14

sunnie committed
15 16
`Vue CLI` 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 [nvm](https://github.com/nvm-sh/nvm)
[nvm-windows](https://github.com/coreybutler/nvm-windows) 在同一台电脑中管理多个 Node 版本。
宋楠 committed
17 18

本示例 Node.js 12.14.1
sunnie committed
19

sunnie committed
20 21 22 23 24 25 26 27 28 29 30 31
### 启动项目

```bash

git clone https://github.com/sunniejs/vue-h5-template.git

cd vue-h5-template

npm install

npm run serve
```
sunnie committed
32

sunnie committed
33 34
<span id="top">目录</span>

sunnie committed
35
- √ Vue-cli4
sunnie committed
36 37
- [√ 配置多环境变量](#env)
- [√ rem 适配方案](#rem)
sunniejs committed
38
- [√ vm 适配方案](#vm)
sunnie committed
39
- [√ VantUI 组件按需加载](#vant)
sunnie committed
40 41
- [√ Sass 全局样式](#sass)
- [√ Vuex 状态管理](#vuex)
sunnie committed
42
- [√ Vue-router](#router)
sunnie committed
43
- [√ Axios 封装及接口管理](#axios)
sunnie committed
44
- [√ Webpack 4 vue.config.js 基础配置](#base)
宋楠 committed
45
- [√ 配置 alias 别名](#alias)
sunnie committed
46
- [√ 配置 proxy 跨域](#proxy)
宋楠 committed
47 48 49
- [√ 配置 打包分析](#bundle)
- [√ 配置 externals 引入 cdn 资源 ](#externals)
- [√ 去掉 console.log ](#console)
sunnie committed
50
- [√ splitChunks 单独打包第三方模块](#chunks)
宋楠 committed
51
- [√ 添加 IE 兼容 ](#ie)
sunnie committed
52
- [√ Eslint+Pettier 统一开发规范 ](#pettier)
sunnie committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

### <span id="env">✅ 配置多环境变量 </span>

`package.json` 里的 `scripts` 配置 `serve` `stage` `build`,通过 `--mode xxx` 来执行不同环境

- 通过 `npm run serve` 启动本地 , 执行 `development`
- 通过 `npm run stage` 打包测试 , 执行 `staging`
- 通过 `npm run build` 打包正式 , 执行 `production`

```javascript
"scripts": {
  "serve": "vue-cli-service serve --open",
  "stage": "vue-cli-service build --mode staging",
  "build": "vue-cli-service build",
}
```

##### 配置介绍

&emsp;&emsp;`VUE_APP_` 开头的变量,在代码中可以通过 `process.env.VUE_APP_` 访问。  
sunnie committed
73
&emsp;&emsp;比如,`VUE_APP_ENV = 'development'` 通过`process.env.VUE_APP_ENV` 访问。  
sunnie committed
74
&emsp;&emsp;除了 `VUE_APP_*` 变量之外,在你的应用代码中始终可用的还有两个特殊的变量`NODE_ENV``BASE_URL`
sunnie committed
75

sunnie committed
76
在项目根目录中新建`.env.*`
sunnie committed
77 78

- .env.development 本地开发环境配置
sunnie committed
79

sunnie committed
80 81 82 83
```bash
NODE_ENV='development'
# must start with VUE_APP_
VUE_APP_ENV = 'development'
sunnie committed
84

sunnie committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
```

- .env.staging 测试环境配置

```bash
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'staging'
```

- .env.production 正式环境配置

```bash
 NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'production'
```

这里我们并没有定义很多变量,只定义了基础的 VUE_APP_ENV `development` `staging` `production`  
sunnie committed
104 105
变量我们统一在 `src/config/env.*.js` 里进行管理。

sunnie committed
106 107
这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去 config 下新建三个对应的文件呢?  
**修改起来方便,不需要重启项目,符合开发习惯。**
sunnie committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

config/index.js

```javascript
// 根据环境引入不同配置 process.env.NODE_ENV
const config = require('./env.' + process.env.VUE_APP_ENV)
module.exports = config
```

配置对应环境的变量,拿本地环境文件 `env.development.js` 举例,用户可以根据需求修改

```javascript
// 本地环境配置
module.exports = {
  title: 'vue-h5-template',
  baseUrl: 'http://localhost:9018', // 项目地址
  baseApi: 'https://test.xxx.com/api', // 本地api请求地址
  APPID: 'xxx',
  APPSECRET: 'xxx'
}
```

根据环境不同,变量就会不同了

```javascript
// 根据环境不同引入不同baseApi地址
sunnie committed
134
import { baseApi } from '@/config'
sunnie committed
135 136
console.log(baseApi)
```
sunnie committed
137

sunnie committed
138
[▲ 回顶部](#top)
sunnie committed
139

sunnie committed
140
### <span id="rem">✅ rem 适配方案 </span>
sunnie committed
141

sunnie committed
142
不用担心,项目已经配置好了 `rem` 适配, 下面仅做介绍:
sunnie committed
143

sunnie committed
144
Vant 中的样式默认使用`px`作为单位,如果需要使用`rem`单位,推荐使用以下两个工具:
sunnie committed
145

sunnie committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
- [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 是一款 `postcss` 插件,用于将单位转化为 `rem`
- [lib-flexible](https://github.com/amfe/lib-flexible) 用于设置 `rem` 基准值

##### PostCSS 配置

下面提供了一份基本的 `postcss` 配置,可以在此配置的基础上根据项目需求进行修改

```javascript
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
  plugins: {
    autoprefixer: {
      overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
    },
    'postcss-pxtorem': {
      rootValue: 37.5,
      propList: ['*']
    }
  }
}
```

更多详细信息: [vant](https://youzan.github.io/vant/#/zh-CN/quickstart#jin-jie-yong-fa)

**新手必看,老鸟跳过**

172 173
很多小伙伴会问我,适配的问题,因为我们使用的是 Vant UI,所以必须根据 Vant UI 375 的设计规范走,一般我们的设计会将 UI 图上
传到蓝湖,我们就可以需要的尺寸了。下面就大搞普及一下 rem。
sunnie committed
174

sunnie committed
175 176
我们知道 `1rem` 等于`html` 根元素设定的 `font-size``px` 值。Vant UI 设置 `rootValue: 37.5`,你可以看到在 iPhone 6 下
看到 (`1rem 等于 37.5px`):
sunnie committed
177 178 179 180 181

```html
<html data-dpr="1" style="font-size: 37.5px;"></html>
```

sunnie committed
182
切换不同的机型,根元素可能会有不同的`font-size`。当你写 css px 样式时,会被程序换算成 `rem` 达到适配。
sunnie committed
183

sunnie committed
184
因为我们用了 Vant 的组件,需要按照 `rootValue: 37.5` 来写样式。
sunnie committed
185

sunnie committed
186
举个例子:设计给了你一张 750px \* 1334px 图片,在 iPhone6 上铺满屏幕,其他机型适配。
sunnie committed
187

sunnie committed
188
-`rootValue: 75` , 样式 `width: 750px;height: 1334px;` 图片会撑满 iPhone6 屏幕,这个时候切换其他机型,图片也会跟着撑
sunnie committed
189 190
  满。
-`rootValue: 37.5` 的时候,样式 `width: 375px;height: 667px;` 图片会撑满 iPhone6 屏幕。
sunnie committed
191

sunnie committed
192
也就是 iphone 6 下 375px 宽度写 CSS。其他的你就可以根据你设计图,去写对应的样式就可以了。
sunnie committed
193

sunnie committed
194
当然,想要撑满屏幕你可以使用 100%,这里只是举例说明。
sunnie committed
195 196

```html
sunniejs committed
197
<img class="image" src="https://www.sunniejs.cn/static/weapp/logo.png" />
sunnie committed
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

<style>
  /* rootValue: 75 */
  .image {
    width: 750px;
    height: 1334px;
  }
  /* rootValue: 37.5 */
  .image {
    width: 375px;
    height: 667px;
  }
</style>
```

[▲ 回顶部](#top)

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
### <span id="vw">✅ vm 适配方案 </span>

本项目使用的是 rem 的 适配方案,其实无论你使用哪种方案,都不需要你去计算 12px 是多少 rem 或者 vw, 会有专门的工具去帮你做
。如果你想用 vw,你可以按照下面的方式切换。

#### 1.安装依赖

```bash

npm install postcss-px-to-viewport -D

```

#### 2.修改 .postcssrc.js

将根目录下 .postcssrc.js 文件修改如下

```javascript
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
  plugins: {
    autoprefixer: {
      overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
    },
    'postcss-px-to-viewport': {
      viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
      unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
      selectorBlackList: ['.ignore', '.hairlines'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
      minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
      mediaQuery: false // 允许在媒体查询中转换`px`
    }
  }
}
```

#### 3.删除原来的 rem 相关代码

src/main.js 删除如下代码

```javascript
// 移动端适配
import 'lib-flexible/flexible.js'
```

package.json 删除如下代码

```javascript
"lib-flexible": "^0.3.2",
"postcss-pxtorem": "^5.1.1",
```

运行起来,F12 元素 css 就是 vw 单位了

[▲ 回顶部](#top)

宋楠 committed
271 272
### <span id="vant">✅ VantUI 组件按需加载 </span>

sunnie committed
273 274 275
项目采
[Vant 自动按需引入组件 (推荐)](https://youzan.github.io/vant/#/zh-CN/quickstart#fang-shi-yi.-zi-dong-an-xu-yin-ru-zu-jian-tui-jian)
面安装插件介绍:
宋楠 committed
276

sunnie committed
277 278
[babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 是一款 `babel` 插件,它会在编译过程中将
`import` 的写法自动转换为按需引入的方式
宋楠 committed
279

sunnie committed
280
#### 安装插件
宋楠 committed
281

sunnie committed
282
```bash
宋楠 committed
283
npm i babel-plugin-import -D
sunnie committed
284 285
```

sunnie committed
286
`babel.config.js` 设置
sunnie committed
287 288

```javascript
宋楠 committed
289
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
sunnie committed
290 291 292 293 294 295 296 297 298
const plugins = [
  [
    'import',
    {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    },
    'vant'
宋楠 committed
299
  ]
sunnie committed
300 301
]
module.exports = {
sunnie committed
302
  presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
sunnie committed
303
  plugins
宋楠 committed
304 305 306
}
```

sunnie committed
307
#### 使用组件
宋楠 committed
308 309 310 311 312 313

项目在 `src/plugins/vant.js` 下统一管理组件,用哪个引入哪个,无需在页面里重复引用

```javascript
// 按需全局引入 vant组件
import Vue from 'vue'
sunnie committed
314
import { Button, List, Cell, Tabbar, TabbarItem } from 'vant'
宋楠 committed
315 316 317 318
Vue.use(Button)
Vue.use(Cell)
Vue.use(List)
Vue.use(Tabbar).use(TabbarItem)
sunnie committed
319 320 321 322
```

[▲ 回顶部](#top)

sunnie committed
323
### <span id="sass">✅ Sass 全局样式</span>
sunnie committed
324 325

首先 你可能会遇到 `node-sass` 安装不成功,别放弃多试几次!!!
宋楠 committed
326

sunnie committed
327 328 329 330
每个页面自己对应的样式都写在自己的 .vue 文件之中 `scoped` 它顾名思义给 css 加了一个域的概念。

```html
<style lang="scss">
sunnie committed
331
  /* global styles */
sunnie committed
332 333 334
</style>

<style lang="scss" scoped>
sunnie committed
335
  /* local styles */
sunnie committed
336 337 338 339 340 341
</style>
```

#### 目录结构

vue-h5-template 所有全局样式都在 `@/src/assets/css` 目录下设置
sunnie committed
342

sunnie committed
343 344 345 346 347 348 349
```bash
├── assets
│   ├── css
│   │   ├── index.scss               # 全局通用样式
│   │   ├── mixin.scss               # 全局mixin
│   │   └── variables.scss           # 全局变量
```
sunnie committed
350

sunnie committed
351
#### 自定义 vant-ui 样式
sunnie committed
352

sunnie committed
353 354
现在我们来说说怎么重写 `vant-ui` 样式。由于 `vant-ui` 的样式我们是在全局引入的,所以你想在某个页面里面覆盖它的样式就不能
`scoped`,但你又想只覆盖这个页面的 `vant` 样式,你就可在它的父级加一个 `class`,用命名空间来解决问题。
sunnie committed
355

sunnie committed
356 357
```css
.about-container {
sunnie committed
358 359 360 361 362
  /* 你的命名空间 */
  .van-button {
    /* vant-ui 元素*/
    margin-right: 0px;
  }
sunnie committed
363 364 365 366 367 368 369 370 371 372
}
```

#### 父组件改变子组件样式 深度选择器

当你子组件使用了 `scoped` 但在父组件又想修改子组件的样式可以 通过 `>>>` 来实现:

```css
<style scoped>
.a >>> .b { /* ... */ }
sunnie committed
373 374
</style>
```
sunnie committed
375

sunnie committed
376
#### 全局变量
sunnie committed
377

sunnie committed
378 379
`vue.config.js` 配置使用 `css.loaderOptions` 选项,注入 `sass``mixin` `variables` 到全局,不需要手动引入 ,配
`$cdn`通过变量形式引入 cdn 地址,这样向所有 Sass/Less 样式传入共享的全局变量:
sunnie committed
380 381 382

```javascript
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
sunnie committed
383
const defaultSettings = require('./src/config/index.js')
sunnie committed
384
module.exports = {
sunnie committed
385 386 387 388 389 390 391 392 393
  css: {
    extract: IS_PROD,
    sourceMap: false,
    loaderOptions: {
      // 给 scss-loader 传递选项
      scss: {
        // 注入 `sass` 的 `mixin` `variables` 到全局, $cdn可以配置图片cdn
        // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
        prependData: `
sunnie committed
394 395 396
                @import "assets/css/mixin.scss";
                @import "assets/css/variables.scss";
                $cdn: "${defaultSettings.$cdn}";
sunnie committed
397 398 399 400
                 `
      }
    }
  }
sunnie committed
401 402 403 404 405 406 407 408 409 410 411
}
```

设置 js 中可以访问 `$cdn`,`.vue` 文件中使用`this.$cdn`访问

```javascript
// 引入全局样式
import '@/assets/css/index.scss'

// 设置 js中可以访问 $cdn
// 引入cdn
sunnie committed
412
import { $cdn } from '@/config'
sunnie committed
413 414 415
Vue.prototype.$cdn = $cdn
```

sunnie committed
416
在 css 和 js 使用
sunnie committed
417 418 419

```html
<script>
sunnie committed
420
  console.log(this.$cdn)
sunnie committed
421 422
</script>
<style lang="scss" scoped>
sunnie committed
423 424 425
  .logo {
    width: 120px;
    height: 120px;
426
    background: url($cdn + '/weapp/logo.png') center / contain no-repeat;
sunnie committed
427
  }
sunnie committed
428
</style>
宋楠 committed
429
```
sunnie committed
430

宋楠 committed
431 432
[▲ 回顶部](#top)

sunnie committed
433
### <span id="vuex">✅ Vuex 状态管理</span>
宋楠 committed
434

sunnie committed
435
目录结构
sunnie committed
436

sunnie committed
437 438 439 440 441 442 443
```bash
├── store
│   ├── modules
│   │   └── app.js
│   ├── index.js
│   ├── getters.js
```
宋楠 committed
444

sunnie committed
445
`main.js` 引入
宋楠 committed
446

sunnie committed
447 448 449 450 451 452 453 454 455 456 457 458
```javascript
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
```

sunnie committed
459
使用
宋楠 committed
460 461

```html
sunnie committed
462
<script>
sunnie committed
463
  import { mapGetters } from 'vuex'
sunnie committed
464 465 466 467
  export default {
    computed: {
      ...mapGetters(['userName'])
    },
sunnie committed
468

sunnie committed
469 470 471 472 473
    methods: {
      // Action 通过 store.dispatch 方法触发
      doDispatch() {
        this.$store.dispatch('setUserName', '真乖,赶紧关注公众号,组织都在等你~')
      }
sunnie committed
474 475
    }
  }
sunnie committed
476
</script>
sunnie committed
477
```
sunnie committed
478

sunnie committed
479 480
[▲ 回顶部](#top)

sunnie committed
481
### <span id="router">✅ Vue-router </span>
sunnie committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

本案例采用 `hash` 模式,开发者根据需求修改 `mode` `base`

**注意**:如果你使用了 `history` 模式,`vue.config.js` 中的 `publicPath` 要做对应的**修改**

前往:[vue.config.js 基础配置](#base)

```javascript
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
export const router = [
  {
    path: '/',
    name: 'index',
    component: () => import('@/views/home/index'), // 路由懒加载
    meta: {
      title: '首页', // 页面标题
      keepAlive: false // keep-alive 标识
    }
  }
]
const createRouter = () =>
  new Router({
    // mode: 'history', // 如果你是 history模式 需要配置 vue.config.js publicPath
    // base: '/app/',
sunnie committed
509
    scrollBehavior: () => ({ y: 0 }),
sunnie committed
510 511 512 513 514 515 516
    routes: router
  })

export default createRouter()
```

更多:[Vue Router](https://router.vuejs.org/zh/)
宋楠 committed
517

sunnie committed
518 519
[▲ 回顶部](#top)

sunnie committed
520 521 522 523 524 525 526 527 528 529 530
### <span id="axios">✅ Axios 封装及接口管理</span>

`utils/request.js` 封装 axios ,开发者需要根据后台接口做修改。

- `service.interceptors.request.use` 里可以设置请求头,比如设置 `token`
- `config.hideloading` 是在 api 文件夹下的接口参数里设置,下文会讲
- `service.interceptors.response.use` 里可以对接口返回数据处理,比如 401 删除本地信息,重新登录

```javascript
import axios from 'axios'
import store from '@/store'
sunnie committed
531
import { Toast } from 'vant'
sunnie committed
532
// 根据环境不同引入不同api地址
sunnie committed
533
import { baseApi } from '@/config'
sunnie committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
// create an axios instance
const service = axios.create({
  baseURL: baseApi, // url = base api url + request url
  withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request 拦截器 request interceptor
service.interceptors.request.use(
  config => {
    // 不传递默认开启loading
    if (!config.hideloading) {
      // loading
      Toast.loading({
        forbidClick: true
      })
    }
    if (store.getters.token) {
      config.headers['X-Token'] = ''
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)
// respone拦截器
service.interceptors.response.use(
  response => {
    Toast.clear()
    const res = response.data
    if (res.status && res.status !== 200) {
      // 登录超时,重新登录
      if (res.status === 401) {
        store.dispatch('FedLogOut').then(() => {
          location.reload()
        })
      }
      return Promise.reject(res || 'error')
    } else {
      return Promise.resolve(res)
    }
  },
  error => {
    Toast.clear()
    console.log('err' + error) // for debug
    return Promise.reject(error)
  }
)
export default service
```

sunnie committed
588 589
#### 接口管理

sunnie committed
590 591 592 593 594
`src/api` 文件夹下统一管理接口

- 你可以建立多个模块对接接口, 比如 `home.js` 里是首页的接口这里讲解 `user.js`
- `url` 接口地址,请求的时候会拼接上 `config` 下的 `baseApi`
- `method` 请求方法
sunnie committed
595
- `data` 请求参数 `qs.stringify(params)` 是对数据系列化操作
sunnie committed
596
- `hideloading` 默认 `false`,设置为 `true` 后,不显示 loading ui 交互中有些接口不需要让用户感知
sunnie committed
597 598 599 600 601 602 603

```javascript
import qs from 'qs'
// axios
import request from '@/utils/request'
//user api

sunnie committed
604 605
// 用户信息
export function getUserInfo(params) {
sunnie committed
606
  return request({
sunnie committed
607
    url: '/user/userinfo',
sunnie committed
608
    method: 'post',
sunnie committed
609 610
    data: qs.stringify(params),
    hideloading: true // 隐藏 loading 组件
sunnie committed
611 612 613 614
  })
}
```

sunnie committed
615 616 617 618
#### 如何调用

```javascript
// 请求接口
sunnie committed
619
import { getUserInfo } from '@/api/user.js'
sunnie committed
620

sunnie committed
621
const params = { user: 'sunnie' }
sunnie committed
622
getUserInfo(params)
sunnie committed
623 624 625 626
  .then(() => {})
  .catch(() => {})
```

sunnie committed
627 628
[▲ 回顶部](#top)

sunnie committed
629
### <span id="base">✅ Webpack 4 vue.config.js 基础配置 </span>
sunnie committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650

如果你的 `Vue Router` 模式是 hash

```javascript
publicPath: './',
```

如果你的 `Vue Router` 模式是 history 这里的 publicPath 和你的 `Vue Router` `base` **保持一直**

```javascript
publicPath: '/app/',
```

```javascript
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)

module.exports = {
  publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
  //  publicPath: '/app/', // 署应用包时的基本 URL。  vue-router history模式使用
  outputDir: 'dist', //  生产环境构建文件的目录
  assetsDir: 'static', //  outputDir的静态资源(js、css、img、fonts)目录
sunnie committed
651
  lintOnSave: !IS_PROD,
sunnie committed
652
  productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
sunnie committed
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
  devServer: {
    port: 9020, // 端口号
    open: false, // 启动后打开浏览器
    overlay: {
      //  当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
      warnings: false,
      errors: true
    }
    // ...
  }
}
```

[▲ 回顶部](#top)

sunnie committed
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
### <span id="alias">✅ 配置 alias 别名 </span>

```javascript
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)

module.exports = {
  chainWebpack: config => {
    // 添加别名
    config.resolve.alias
      .set('@', resolve('src'))
      .set('assets', resolve('src/assets'))
      .set('api', resolve('src/api'))
      .set('views', resolve('src/views'))
      .set('components', resolve('src/components'))
  }
}
```

[▲ 回顶部](#top)

宋楠 committed
690
### <span id="proxy">✅ 配置 proxy 跨域 </span>
sunnie committed
691

sunnie committed
692 693
如果你的项目需要跨域设置,你需要打来 `vue.config.js` `proxy` 注释 并且配置相应参数

sunnie committed
694
<u>**!!!注意:你还需要将 `src/config/env.development.js` 里的 `baseApi` 设置成 '/'**</u>
sunnie committed
695

sunnie committed
696 697 698 699 700 701 702 703
```javascript
module.exports = {
  devServer: {
    // ....
    proxy: {
      //配置跨域
      '/api': {
        target: 'https://test.xxx.com', // 接口的域名
sunnie committed
704
        // ws: true, // 是否启用websockets
sunnie committed
705 706 707 708 709 710 711 712 713 714
        changOrigin: true, // 开启代理,在本地创建一个虚拟服务端
        pathRewrite: {
          '^/api': '/'
        }
      }
    }
  }
}
```

sunnie committed
715
使用 例如: `src/api/home.js`
sunnie committed
716 717

```javascript
sunnie committed
718 719 720
export function getUserInfo(params) {
  return request({
    url: '/api/userinfo',
sunnie committed
721
    method: 'post',
sunnie committed
722 723 724
    data: qs.stringify(params)
  })
}
sunnie committed
725 726 727 728
```

[▲ 回顶部](#top)

宋楠 committed
729 730 731
### <span id="bundle">✅ 配置 打包分析 </span>

```javascript
sunnie committed
732
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
宋楠 committed
733 734 735 736 737

module.exports = {
  chainWebpack: config => {
    // 打包分析
    if (IS_PROD) {
sunnie committed
738
      config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
宋楠 committed
739
        {
sunnie committed
740
          analyzerMode: 'static'
宋楠 committed
741
        }
sunnie committed
742
      ])
宋楠 committed
743 744
    }
  }
sunnie committed
745
}
宋楠 committed
746
```
sunnie committed
747

宋楠 committed
748 749 750
```bash
npm run build
```
sunnie committed
751

宋楠 committed
752 753
[▲ 回顶部](#top)

sunnie committed
754
### <span id="externals">✅ 配置 externals 引入 cdn 资源 </span>
宋楠 committed
755

sunnie committed
756 757 758 759 760 761 762 763 764 765
这个版本 CDN 不再引入,我测试了一下使用引入 CDN 和不使用,不使用会比使用时间少。网上不少文章测试 CDN 速度块,这个开发者可
以实际测试一下。

另外项目中使用的是公共 CDN 不稳定,域名解析也是需要时间的(如果你要使用请尽量使用同一个域名)

因为页面每次遇到`<script>`标签都会停下来解析执行,所以应该尽可能减少`<script>`标签的数量 `HTTP`请求存在一定的开销,100K
的文件比 5 个 20K 的文件下载的更快,所以较少脚本数量也是很有必要的

暂时还没有研究放到自己的 cdn 服务器上。

宋楠 committed
766 767 768
```javascript
const defaultSettings = require('./src/config/index.js')
const name = defaultSettings.title || 'vue mobile template'
sunnie committed
769
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
宋楠 committed
770 771 772 773 774 775 776 777 778

// externals
const externals = {
  vue: 'Vue',
  'vue-router': 'VueRouter',
  vuex: 'Vuex',
  vant: 'vant',
  axios: 'axios'
}
sunnie committed
779
// CDN外链,会插入到index.html中
宋楠 committed
780
const cdn = {
sunnie committed
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
  // 开发环境
  dev: {
    css: [],
    js: []
  },
  // 生产环境
  build: {
    css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'],
    js: [
      'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
      'https://cdn.jsdelivr.net/npm/vue-router@3.1.5/dist/vue-router.min.js',
      'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js',
      'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js',
      'https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js'
    ]
  }
宋楠 committed
797 798 799
}
module.exports = {
  configureWebpack: config => {
sunnie committed
800
    config.name = name
宋楠 committed
801 802 803 804
    // 为生产环境修改配置...
    if (IS_PROD) {
      // externals
      config.externals = externals
sunnie committed
805
    }
宋楠 committed
806 807
  },
  chainWebpack: config => {
sunnie committed
808 809 810
    /**
     * 添加CDN参数到htmlWebpackPlugin配置中
     */
宋楠 committed
811 812
    config.plugin('html').tap(args => {
      if (IS_PROD) {
sunnie committed
813 814 815 816
        args[0].cdn = cdn.build
      } else {
        args[0].cdn = cdn.dev
      }
宋楠 committed
817 818 819
      return args
    })
  }
sunnie committed
820
}
宋楠 committed
821
```
sunnie committed
822 823

在 public/index.html 中添加
宋楠 committed
824 825

```javascript
sunnie committed
826
    <!-- 使用CDNCSS文件 -->
宋楠 committed
827 828
    <% for (var i in
      htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
sunnie committed
829
      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
宋楠 committed
830 831 832 833 834 835 836 837 838 839 840 841 842
      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
    <% } %>
     <!-- 使用CDN加速的JS文件,配置在vue.config.js -->
    <% for (var i in
      htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
      <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
```

[▲ 回顶部](#top)

### <span id="console">✅ 去掉 console.log </span>

sunnie committed
843 844
保留了测试环境和本地环境的 `console.log`

宋楠 committed
845 846 847
```bash
npm i -D babel-plugin-transform-remove-console
```
sunnie committed
848

宋楠 committed
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
在 babel.config.js 中配置

```javascript
// 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
const plugins = [
  [
    'import',
    {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    },
    'vant'
  ]
]
// 去除 console.log
if (IS_PROD) {
  plugins.push('transform-remove-console')
}

module.exports = {
sunnie committed
871
  presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'entry' }]],
宋楠 committed
872 873
  plugins
}
sunnie committed
874 875 876 877 878
```

[▲ 回顶部](#top)

### <span id="chunks">✅ splitChunks 单独打包第三方模块</span>
宋楠 committed
879

sunnie committed
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
```javascript
module.exports = {
  chainWebpack: config => {
    config.when(IS_PROD, config => {
      config
        .plugin('ScriptExtHtmlWebpackPlugin')
        .after('html')
        .use('script-ext-html-webpack-plugin', [
          {
            // 将 runtime 作为内联引入不单独存在
            inline: /runtime\..*\.js$/
          }
        ])
        .end()
      config.optimization.splitChunks({
        chunks: 'all',
        cacheGroups: {
          // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块
          commons: {
            name: 'chunk-commons',
            test: resolve('src/components'),
            minChunks: 3, //  被至少用三次以上打包分离
            priority: 5, // 优先级
            reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
          },
          node_vendors: {
            name: 'chunk-libs',
            chunks: 'initial', // 只打包初始时依赖的第三方
            test: /[\\/]node_modules[\\/]/,
            priority: 10
          },
          vantUI: {
            name: 'chunk-vantUI', // 单独将 vantUI 拆包
            priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的
            test: /[\\/]node_modules[\\/]_?vant(.*)/
          }
        }
      })
      config.optimization.runtimeChunk('single')
    })
  }
}
宋楠 committed
922
```
sunnie committed
923

宋楠 committed
924 925
[▲ 回顶部](#top)

宋楠 committed
926 927
### <span id="ie">✅ 添加 IE 兼容 </span>

sunnie committed
928 929 930 931 932
之前的方式 会报 `@babel/polyfill` is deprecated. Please, use required parts of `core-js` and
`regenerator-runtime/runtime` separately

`@babel/polyfill` 废弃,使用 `core-js``regenerator-runtime`

sunnie committed
933
```bash
sunnie committed
934
npm i --save core-js regenerator-runtime
sunnie committed
935
```
宋楠 committed
936

sunnie committed
937 938 939
`main.js` 中添加

```javascript
sunnie committed
940 941 942 943
// 兼容 IE
// https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfill
import 'core-js/stable'
import 'regenerator-runtime/runtime'
sunnie committed
944 945 946 947 948 949
```

配置 `babel.config.js`

```javascript
const plugins = []
宋楠 committed
950

sunnie committed
951
module.exports = {
sunnie committed
952
  presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
sunnie committed
953 954 955
  plugins
}
```
宋楠 committed
956

sunnie committed
957
[▲ 回顶部](#top)
宋楠 committed
958

sunnie committed
959
### <span id="pettier">✅ Eslint + Pettier 统一开发规范 </span>
960

961 962
VScode (版本 1.47.3)安装 `eslint` `prettier` `vetur` 插件 `.vue` 文件使用 vetur 进行格式化,其他使用`prettier`,后面会
专门写个如何使用配合使用这三个玩意
sunnie committed
963 964 965

在文件 `.prettierrc` 里写 属于你的 pettier 规则

sunnie committed
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
```bash
{
   "printWidth": 120,
   "tabWidth": 2,
   "singleQuote": true,
   "trailingComma": "none",
   "semi": false,
   "wrap_line_length": 120,
   "wrap_attributes": "auto",
   "proseWrap": "always",
   "arrowParens": "avoid",
   "bracketSpacing": false,
   "jsxBracketSameLine": true,
   "useTabs": false,
   "overrides": [{
       "files": ".prettierrc",
       "options": {
           "parser": "json"
       }
   }]
sunnie committed
986
}
sunnie committed
987 988 989
```

Vscode setting.json 设置
sunnie committed
990

991
```bash
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    {
  // 将设置放入此文件中以覆盖默认设置
  "files.autoSave": "off",
  // 控制字体系列。
  "editor.fontFamily": "Consolas, 'Courier New', monospace,'宋体'",
  "terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
  // 以像素为单位控制字号。
  "editor.fontSize": 16,
  // 控制选取范围是否有圆角
  "editor.roundedSelection": false,
  // 建议小组件的字号
  "editor.suggestFontSize": 16,
  // 在“打开的编辑器”窗格中显示的编辑器数量。将其设置为 0 可隐藏窗格。
  "explorer.openEditors.visible": 0,
  // 是否已启用自动刷新
  "git.autorefresh": true,
  // 以像素为单位控制终端的字号,这是 editor.fontSize 的默认值。
  "terminal.integrated.fontSize": 14,
  // 控制终端游标是否闪烁。
  "terminal.integrated.cursorBlinking": true,
  // 一个制表符等于的空格数。该设置在 `editor.detectIndentation` 启用时根据文件内容进行重写。
  // Tab Size
  "editor.tabSize": 2,
  // By default, common template. Do not modify it!!!!!
  "editor.formatOnType": true,
  "window.zoomLevel": 0,
  "editor.detectIndentation": false,
  "css.fileExtensions": ["css", "scss"],
  "files.associations": {
    "*.string": "html",
    "*.vue": "vue",
    "*.wxss": "css",
    "*.wxml": "wxml",
    "*.wxs": "javascript",
    "*.cjson": "jsonc",
    "*.js": "javascript"
  },
  // 为指定的语法定义配置文件或使用带有特定规则的配置文件。
  "emmet.syntaxProfiles": {
    "vue-html": "html",
    "vue": "html"
  },
  "search.exclude": {
    "**/node_modules": true,
    "**/bower_components": true
  },
  //保存时eslint自动修复错误
  "editor.formatOnSave": true,
  // Enable per-language
  //配置 ESLint 检查的文件类型
  "editor.quickSuggestions": {
    "strings": true
  },
  // 添加 vue 支持
  // 这里是针对vue文件的格式化设置,vue的规则在这里生效
  "vetur.format.options.tabSize": 2,
  "vetur.format.options.useTabs": false,
  "vetur.format.defaultFormatter.html": "js-beautify-html",
  "vetur.format.defaultFormatter.css": "prettier",
  "vetur.format.defaultFormatter.scss": "prettier",
  "vetur.format.defaultFormatter.postcss": "prettier",
  "vetur.format.defaultFormatter.less": "prettier",
  "vetur.format.defaultFormatter.js": "vscode-typescript",
  "vetur.format.defaultFormatter.sass": "sass-formatter",
  "vetur.format.defaultFormatter.ts": "prettier",
  "vetur.format.defaultFormatterOptions": {
    "js-beautify-html": {
      "wrap_attributes": "aligned-multiple", // 超过150折行
      "wrap-line-length": 150
1061
    },
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
    // #vue组件中html代码格式化样式
    "prettier": {
      "printWidth": 120,
      "tabWidth": 2,
      "singleQuote": false,
      "trailingComma": "none",
      "semi": false,
      "wrap_line_length": 120,
      "wrap_attributes": "aligned-multiple", // 超过150折行
      "proseWrap": "always",
      "arrowParens": "avoid",
      "bracketSpacing": true,
      "jsxBracketSameLine": true,
      "useTabs": false,
      "overrides": [
        {
          "files": ".prettierrc",
          "options": {
            "parser": "json"
          }
        }
      ]
    }
  },
  // Enable per-language
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "vetur.validation.template": false,
  "html.format.enable": false,
  "json.format.enable": false,
  "javascript.format.enable": false,
  "typescript.format.enable": false,
  "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "octref.vetur"
  },
  "emmet.includeLanguages": {
    "wxml": "html"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  // 开启eslint自动修复js/ts功能
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "minapp-vscode.disableAutoConfig": true,
  "javascript.implicitProjectConfig.experimentalDecorators": true,
  "editor.maxTokenizationLineLength": 200000
}

1123
```
sunnie committed
1124

sunnie committed
1125
[▲ 回顶部](#top)
sunnie committed
1126

sunnie committed
1127
# 鸣谢 ​
sunnie committed
1128

1129
[vue-cli4-config](https://github.com/staven630/vue-cli4-config)  
sunnie committed
1130
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
sunnie committed
1131

406803045 committed
1132
# 关于我
sunnie committed
1133

sunnie committed
1134 1135 1136
获取更多技术相关文章,关注公众号”前端女塾“。

回复加群,即可加入”前端仙女群“
sunnie committed
1137

sunnie committed
1138 1139 1140
 <p>
  <img src="./static/gognzhonghao.jpg" width="256" style="display:inline;">
</p>
sunnie committed
1141

sunnie committed
1142 1143
扫描添加下方的微信并备注 Sol 加交流群,交流学习,及时获取代码最新动态。

406803045 committed
1144
<p>
sunnie committed
1145
  <img src="./static/me.png" width="256" style="display:inline;">
406803045 committed
1146 1147
</p>
 
sunnie committed
1148
如果对你有帮助送我一颗小星星(づ ̄3 ̄)づ╭❤~