Blame view

README.md 25.3 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

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

### <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
72
&emsp;&emsp;比如,`VUE_APP_ENV = 'development'` 通过`process.env.VUE_APP_ENV` 访问。  
sunnie committed
73
&emsp;&emsp;除了 `VUE_APP_*` 变量之外,在你的应用代码中始终可用的还有两个特殊的变量`NODE_ENV``BASE_URL`
sunnie committed
74

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

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

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

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

- .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
103 104
变量我们统一在 `src/config/env.*.js` 里进行管理。

sunnie committed
105 106
这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去 config 下新建三个对应的文件呢?   
**修改起来方便,不需
sunnie committed
107
要重启项目,符合开发习惯。**
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 134 135 136

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地址
import {baseApi} from '@/config'
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 172 173
- [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)

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

很多小伙伴会问我,适配的问题。

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

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

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

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

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

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

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

sunnie committed
193
当然,想要撑满屏幕你可以使用 100%,这里只是举例说明。
sunnie committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

```html
<img class="image" src="https://imgs.solui.cn/weapp/logo.png" />

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

[▲ 回顶部](#top)

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

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

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

sunnie committed
223
#### 安装插件
宋楠 committed
224

sunnie committed
225
```bash
宋楠 committed
226
npm i babel-plugin-import -D
sunnie committed
227 228 229 230 231
```

` babel.config.js` 设置

```javascript
宋楠 committed
232 233

// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
sunnie committed
234 235 236 237 238 239 240 241 242
const plugins = [
  [
    'import',
    {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    },
    'vant'
宋楠 committed
243
  ]
sunnie committed
244 245 246 247
]
module.exports = {
  presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'usage', corejs: 3}]],
  plugins
宋楠 committed
248
}
sunnie committed
249

宋楠 committed
250 251
```

sunnie committed
252
#### 使用组件
宋楠 committed
253 254 255 256 257 258 259 260 261 262 263

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

```javascript
// 按需全局引入 vant组件
import Vue from 'vue'
import {Button, List, Cell, Tabbar, TabbarItem} from 'vant'
Vue.use(Button)
Vue.use(Cell)
Vue.use(List)
Vue.use(Tabbar).use(TabbarItem)
sunnie committed
264 265 266 267
```

[▲ 回顶部](#top)

sunnie committed
268
### <span id="sass">✅ Sass 全局样式</span>
sunnie committed
269 270

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

sunnie committed
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
每个页面自己对应的样式都写在自己的 .vue 文件之中 `scoped` 它顾名思义给 css 加了一个域的概念。

```html
<style lang="scss">
    /* global styles */
</style>

<style lang="scss" scoped>
    /* local styles */
</style>
```

#### 目录结构

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

sunnie committed
288 289 290 291 292 293 294
```bash
├── assets
│   ├── css
│   │   ├── index.scss               # 全局通用样式
│   │   ├── mixin.scss               # 全局mixin
│   │   └── variables.scss           # 全局变量
```
sunnie committed
295

sunnie committed
296
#### 自定义 vant-ui 样式
sunnie committed
297

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

sunnie committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
```css
.about-container {
    /* 你的命名空间 */
    .van-button {
        /* vant-ui 元素*/
        margin-right: 0px;
    }
}
```

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

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

```css
<style scoped>
.a >>> .b { /* ... */ }
sunnie committed
317 318
</style>
```
sunnie committed
319
#### 全局变量
sunnie committed
320

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

```javascript
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
sunnie committed
325
const defaultSettings = require('./src/config/index.js')
sunnie committed
326
module.exports = {
sunnie committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
    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: `
                @import "assets/css/mixin.scss";
                @import "assets/css/variables.scss";
                $cdn: "${defaultSettings.$cdn}";
                 `,
            },
        },
    },
sunnie committed
343 344 345 346 347 348 349 350 351 352 353
}
```

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

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

// 设置 js中可以访问 $cdn
// 引入cdn
sunnie committed
354
import { $cdn } from '@/config'
sunnie committed
355 356 357
Vue.prototype.$cdn = $cdn
```

sunnie committed
358
在 css 和 js 使用
sunnie committed
359 360 361

```html
<script>
sunnie committed
362
    console.log(this.$cdn)
sunnie committed
363 364
</script>
<style lang="scss" scoped>
sunnie committed
365 366 367 368 369
    .logo {
        width: 120px;
        height: 120px;
        background: url($cdn+'/weapp/logo.png') center / contain no-repeat;
    }
sunnie committed
370
</style>
宋楠 committed
371
```
sunnie committed
372

宋楠 committed
373 374
[▲ 回顶部](#top)

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

sunnie committed
377
目录结构
sunnie committed
378

sunnie committed
379 380 381 382 383 384 385
```bash
├── store
│   ├── modules
│   │   └── app.js
│   ├── index.js
│   ├── getters.js
```
宋楠 committed
386

sunnie committed
387
`main.js` 引入
宋楠 committed
388

sunnie committed
389 390 391 392 393 394 395 396 397 398 399 400
```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
401
使用
宋楠 committed
402 403

```html
sunnie committed
404
<script>
sunnie committed
405 406 407 408 409
  import {mapGetters} from 'vuex'
  export default {
    computed: {
      ...mapGetters(['userName'])
    },
sunnie committed
410

sunnie committed
411 412 413 414 415
    methods: {
      // Action 通过 store.dispatch 方法触发
      doDispatch() {
        this.$store.dispatch('setUserName', '真乖,赶紧关注公众号,组织都在等你~')
      }
sunnie committed
416 417
    }
  }
sunnie committed
418
</script>
sunnie committed
419
```
sunnie committed
420

sunnie committed
421 422
[▲ 回顶部](#top)

sunnie committed
423
### <span id="router">✅ Vue-router </span>
sunnie committed
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458

本案例采用 `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/',
    scrollBehavior: () => ({y: 0}),
    routes: router
  })

export default createRouter()
```

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

sunnie committed
460 461
[▲ 回顶部](#top)

sunnie committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 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 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
### <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'
import {Toast} from 'vant'
// 根据环境不同引入不同api地址
import {baseApi} from '@/config'
// 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
530 531
#### 接口管理

sunnie committed
532 533 534 535 536
`src/api` 文件夹下统一管理接口

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

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

sunnie committed
546 547
// 用户信息
export function getUserInfo(params) {
sunnie committed
548
  return request({
sunnie committed
549
    url: '/user/userinfo',
sunnie committed
550
    method: 'post',
sunnie committed
551 552
    data: qs.stringify(params),
    hideloading: true // 隐藏 loading 组件
sunnie committed
553 554 555 556
  })
}
```

sunnie committed
557 558 559 560 561 562 563
#### 如何调用

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

const params = {user: 'sunnie'}
sunnie committed
564
getUserInfo(params)
sunnie committed
565 566 567 568
  .then(() => {})
  .catch(() => {})
```

sunnie committed
569 570
[▲ 回顶部](#top)

sunnie committed
571
### <span id="base">✅ Webpack 4 vue.config.js 基础配置 </span>
sunnie committed
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592

如果你的 `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)目录
593
  lintOnSave: process.env.NODE_ENV !== IS_PROD,
sunnie committed
594
  productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
sunnie committed
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
  devServer: {
    port: 9020, // 端口号
    open: false, // 启动后打开浏览器
    overlay: {
      //  当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
      warnings: false,
      errors: true
    }
    // ...
  }
}
```

[▲ 回顶部](#top)

sunnie committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
### <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
632
### <span id="proxy">✅ 配置 proxy 跨域 </span>
sunnie committed
633

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

sunnie committed
636
<u>**!!!注意:你还需要将 `src/config/env.development.js` 里的 `baseApi` 设置成 '/'**</u>
sunnie committed
637 638 639 640 641 642 643 644
```javascript
module.exports = {
  devServer: {
    // ....
    proxy: {
      //配置跨域
      '/api': {
        target: 'https://test.xxx.com', // 接口的域名
sunnie committed
645
        // ws: true, // 是否启用websockets
sunnie committed
646 647 648 649 650 651 652 653 654 655
        changOrigin: true, // 开启代理,在本地创建一个虚拟服务端
        pathRewrite: {
          '^/api': '/'
        }
      }
    }
  }
}
```

sunnie committed
656
使用 例如: `src/api/home.js`
sunnie committed
657 658

```javascript
sunnie committed
659 660 661
export function getUserInfo(params) {
  return request({
    url: '/api/userinfo',
sunnie committed
662
    method: 'post',
sunnie committed
663 664 665
    data: qs.stringify(params)
  })
}
sunnie committed
666 667 668 669
```

[▲ 回顶部](#top)

宋楠 committed
670 671 672 673

### <span id="bundle">✅ 配置 打包分析 </span>

```javascript
sunnie committed
674
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
宋楠 committed
675 676 677 678 679

module.exports = {
  chainWebpack: config => {
    // 打包分析
    if (IS_PROD) {
sunnie committed
680
      config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
宋楠 committed
681
        {
sunnie committed
682
          analyzerMode: 'static'
宋楠 committed
683
        }
sunnie committed
684
      ])
宋楠 committed
685 686
    }
  }
sunnie committed
687
}
宋楠 committed
688
```
sunnie committed
689

宋楠 committed
690 691 692
```bash
npm run build
```
sunnie committed
693

宋楠 committed
694 695
[▲ 回顶部](#top)

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

sunnie committed
698 699 700 701 702 703 704 705 706 707
这个版本 CDN 不再引入,我测试了一下使用引入 CDN 和不使用,不使用会比使用时间少。网上不少文章测试 CDN 速度块,这个开发者可
以实际测试一下。

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

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

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

宋楠 committed
708 709 710
```javascript
const defaultSettings = require('./src/config/index.js')
const name = defaultSettings.title || 'vue mobile template'
sunnie committed
711
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
宋楠 committed
712 713 714 715 716 717 718 719 720

// externals
const externals = {
  vue: 'Vue',
  'vue-router': 'VueRouter',
  vuex: 'Vuex',
  vant: 'vant',
  axios: 'axios'
}
sunnie committed
721
// CDN外链,会插入到index.html中
宋楠 committed
722
const cdn = {
sunnie committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
  // 开发环境
  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
739 740 741
}
module.exports = {
  configureWebpack: config => {
sunnie committed
742
    config.name = name
宋楠 committed
743 744 745 746
    // 为生产环境修改配置...
    if (IS_PROD) {
      // externals
      config.externals = externals
sunnie committed
747
    }
宋楠 committed
748 749
  },
  chainWebpack: config => {
sunnie committed
750 751 752
    /**
     * 添加CDN参数到htmlWebpackPlugin配置中
     */
宋楠 committed
753 754
    config.plugin('html').tap(args => {
      if (IS_PROD) {
sunnie committed
755 756 757 758
        args[0].cdn = cdn.build
      } else {
        args[0].cdn = cdn.dev
      }
宋楠 committed
759 760 761
      return args
    })
  }
sunnie committed
762
}
宋楠 committed
763
```
sunnie committed
764 765

在 public/index.html 中添加
宋楠 committed
766 767

```javascript
sunnie committed
768
    <!-- 使用CDNCSS文件 -->
宋楠 committed
769 770
    <% for (var i in
      htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
sunnie committed
771
      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
宋楠 committed
772 773 774 775 776 777 778 779 780 781 782 783 784
      <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
785 786
保留了测试环境和本地环境的 `console.log`

宋楠 committed
787 788 789
```bash
npm i -D babel-plugin-transform-remove-console
```
sunnie committed
790

宋楠 committed
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
在 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 = {
  presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'entry'}]],
  plugins
}
sunnie committed
816 817 818 819 820
```

[▲ 回顶部](#top)

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

sunnie committed
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
```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
864
```
sunnie committed
865

宋楠 committed
866 867
[▲ 回顶部](#top)

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

sunnie committed
870 871 872 873 874
之前的方式 会报 `@babel/polyfill` is deprecated. Please, use required parts of `core-js` and
`regenerator-runtime/runtime` separately

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

sunnie committed
875
```bash
sunnie committed
876
npm i --save core-js regenerator-runtime
sunnie committed
877
```
宋楠 committed
878

sunnie committed
879 880 881
`main.js` 中添加

```javascript
sunnie committed
882 883 884 885 886
// 兼容 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
887 888 889 890 891 892
```

配置 `babel.config.js`

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

sunnie committed
894
module.exports = {
sunnie committed
895
  presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'usage', corejs: 3}]],
sunnie committed
896 897 898
  plugins
}
```
宋楠 committed
899

sunnie committed
900
[▲ 回顶部](#top)
宋楠 committed
901

902 903 904
### <span id="pettier">✅ Eslint + Pettier 统一开发规范  </span>

VScode 安装 `eslint` `prettier` `vetur` 插件
sunnie committed
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929

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

 ```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"
        }
    }]
}
 ```
930
 Vscode setting.json 设置
sunnie committed
931

932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
```bash
    "[vue]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
     // 保存时用eslint格式化
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    // 两者会在格式化js时冲突,所以需要关闭默认js格式化程序
    "javascript.format.enable": false,
    "typescript.format.enable": false,
    "vetur.format.defaultFormatter.html": "none",
    // js/ts程序用eslint,防止vetur中的prettier与eslint格式化冲突
    "vetur.format.defaultFormatter.js": "none",
    "vetur.format.defaultFormatter.ts": "none",
```
sunnie committed
951
[▲ 回顶部](#top)
sunnie committed
952

sunnie committed
953 954 955 956
# 鸣谢​

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

406803045 committed
958
# 关于我
sunnie committed
959

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

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

sunnie committed
964 965 966
 <p>
  <img src="./static/gognzhonghao.jpg" width="256" style="display:inline;">
</p>
sunnie committed
967

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

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