vue-cli3 從搭建到優化

lMadman發表於2019-01-14

前言

github地址: github.com/LeeStaySmal… (完整分支:optimize分支)

demo地址: vue-project-demo.eloco.cn

安裝與初始化架構

安裝

node >= 8.9 推薦:8.11.0 +

安裝:npm install -g @vue/cli

檢查:vue --version

如果已安裝舊版本,需要先npm uninstall vue-cli -g 解除安裝掉舊版本。

初始化架構

  • 建立:vue create project-name

image

注:專案名稱不能駝峰命名。

  • 選擇一個預設(這裡我選擇更多功能):

image

  • 選擇需要安裝的(Babel、Router、Vuex、Pre-processors、Linter / Formatter):

image

  • 是否使用history路由模式(Yes):

image

  • 選擇css 前處理器(Sass/SCSS):

    image

  • 選擇eslint 配置(ESLint + Standard config):

image

  • 選擇什麼時候執行eslint校驗(Lint on save):

    image

  • 選擇以什麼樣的形式配置以上所選的功能(In dedicated config files):

image

  • 是否將之前的設定儲存為一個預設模板(y):

image

如果選擇 y 會讓輸入名稱,以便下次直接使用,否則直接開始初始化專案。

  • 最後,看一下生成的基本架構目錄:
    image

在專案中優雅的使用svg

  • 首先在/src/components 建立 SvgIcon.vue
    image

參考:未來必熱:SVG Sprite技術介紹 - 張鑫旭

  • src/下建立 icons資料夾,以及在其下建立svg資料夾用於存放svg檔案,建立index.js作為入口檔案:

image

編寫index.js 的指令碼:

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue' // svg元件

// 全域性註冊
Vue.component('svg-icon', SvgIcon)

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)
複製程式碼
  • 使用svg-sprite-loader對專案中使用的svg進行處理:

npm install svg-sprite-loader --save-dev

修改預設的webpack配置, 在專案根目錄建立vue.config.js,程式碼如下;

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

module.exports = {
  chainWebpack: config => {
    // svg loader
    const svgRule = config.module.rule('svg') // 找到svg-loader
    svgRule.uses.clear() // 清除已有的loader, 如果不這樣做會新增在此loader之後
    svgRule.exclude.add(/node_modules/) // 正則匹配排除node_modules目錄
    svgRule // 新增svg新的loader處理
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })

    // 修改images loader 新增svg處理
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/icons'))
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  }
}
複製程式碼
  • 最後,在main.js 中引入import '@/icons'即可;
// 使用示例
<svg-icon icon-class="add" />
複製程式碼

PS:至於svg ,個人比較建議使用阿里開源的圖示庫 iconFont

axios封裝api、模組化vuex

axios篇
  • 專案中安裝axiosnpm install axios
  • src目錄下建立utils/, 並建立request.js用來封裝axios,上程式碼:
import axios from 'axios'

// 建立axios 例項
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 10000 // 請求超時時間
})

// request 攔截器
service.interceptors.request.use(
  config => {
    // 這裡可以自定義一些config 配置

    return config
  },
  error => {
    //  這裡處理一些請求出錯的情況

    console.log(error)
    Promise.reject(error)
  }
)

// response 攔截器
service.interceptors.response.use(
  response => {
    const res = response.data
    // 這裡處理一些response 正常放回時的邏輯

    return res
  },
  error => {
    // 這裡處理一些response 出錯時的邏輯

    return Promise.reject(error)
  }
)

export default service
複製程式碼
  • 既然要使用axios ,必不可少的需要配置環境變數以及需要請求的地址,這裡可以簡單的修改poackage.json:
"scripts": {
    "dev": "vue-cli-service serve --project-mode dev",
    "test": "vue-cli-service serve --project-mode test",
    "pro": "vue-cli-service serve --project-mode pro",
    "pre": "vue-cli-service serve --project-mode pre",
    "build:dev": "vue-cli-service build --project-mode dev",
    "build:test": "vue-cli-service build --project-mode test",
    "build:pro": "vue-cli-service build --project-mode pro",
    "build:pre": "vue-cli-service build --project-mode pre",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
複製程式碼

同時修改vue.config.js:

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

module.exports = {
  chainWebpack: config => {
    // 這裡是對環境的配置,不同環境對應不同的BASE_API,以便axios的請求地址不同
    config.plugin('define').tap(args => {
      const argv = process.argv
      const mode = argv[argv.indexOf('--project-mode') + 1]
      args[0]['process.env'].MODE = `"${mode}"`
      args[0]['process.env'].BASE_API = '"http://47.94.138.75:8000"'
      return args
    })

    // svg loader
    const svgRule = config.module.rule('svg') // 找到svg-loader
    svgRule.uses.clear() // 清除已有的loader, 如果不這樣做會新增在此loader之後
    svgRule.exclude.add(/node_modules/) // 正則匹配排除node_modules目錄
    svgRule // 新增svg新的loader處理
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })

    // 修改images loader 新增svg處理
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/icons'))
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  }
}
複製程式碼
  • 如何使用? 我比較建議在src/下建立api目錄,用來統一管理所有的請求,比如下面這樣:

image

這樣的好處是方便管理、後期維護,還可以和後端的微服務對應,建立多檔案存放不同模組的api。剩下的就是你使用到哪個api時,自己引入便可。

擴充:服務端的cors設定

牽涉到跨域,這裡採用cors,很多朋友在面試中經常會被問到cors的實現原理,這個網上有很多理論大多是這樣講的:

image

其實,這樣理解很抽象,伺服器端到底是怎麼做驗證的?

這裡大家可以通俗的理解為後端在接收前端的request請求的時候,會有一個request攔截器,像axios response攔截器一樣。下面以php lumen框架為例,來深入理解一下這個流程:

<?php

namespace App\Http\Middleware;

use App\Http\Utils\Code;
use Closure;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;

class CorsMiddleware
{
    private $headers;

    /**
     * 全域性 : 解決跨域
     * @param $request
     * @param \Closure $next
     * @return mixed
     * @throws \HttpException
     */
    public function handle($request, Closure $next)
    {
        //請求引數
        Log::info('http request:'.json_encode(["request_all" => $request->all()]));

        $allowOrigin = [
            'http://47.94.138.75',
            'http://localhost',
        ];
        $Origin = $request->header("Origin");

        $this->headers = [
            'Access-Control-Allow-Headers'     => 'Origin,x-token,Content-Type',
            'Access-Control-Allow-Methods'     => 'GET, POST, PUT, DELETE, OPTIONS',
            'Access-Control-Allow-Credentials' => 'true',//允許客戶端傳送cookie
            'Access-Control-Allow-Origin'      => $Origin,
            //'Access-Control-Max-Age'           => 120, //該欄位可選,間隔2分鐘驗證一次是否允許跨域。
        ];
        //獲取請求方式
        if ($request->isMethod('options')) {
            if (in_array($Origin, $allowOrigin)) {
                return $this->setCorsHeaders(new Response(json_encode(['code' => Code::SUCCESS, "data" => 'success', "msg" => ""]), Code::SUCCESS));
            } else {
                return new Response(json_encode('fail', 405));
            }
        }
        $response = $next($request);
        //返回引數
        Log::info('http response:'.json_encode($response));
        return $this->setCorsHeaders($response);

    }

    /**
     * @param $response
     * @return mixed
     */
    public function setCorsHeaders($response)
    {
        foreach ($this->headers as $key => $val) {
            $response->header($key, $val);
        }
        return $response;
    }
}
複製程式碼

vuex 篇

如果建立專案的時候,選擇了vuex,那麼預設會在src目錄下有一個store.js作為倉庫檔案。但在更多實際場景中,如果引入vuex,那麼肯定避免不了分模組,先來看一下預設檔案程式碼:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  }
})
複製程式碼

那麼現在改造一下,比如先劃分出appuser兩個模組,可以這樣:

import Vue from 'vue'
import Vuex from 'vuex'
import app from './store/modules/app'
import user from './store/modules/user'
import getters from './store/getters'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    user
  },
  getters
})

export default store
複製程式碼

src/下建立store/目錄:

image

app module 可以用來儲存應用的狀態,比如接下來要講到的全域性loading,或者控制第三方元件的全域性大小,比如element ui中的全域性元件size

user module 可以用來儲存當前使用者的資訊;

當然,store 配合本地儲存比較完美,這裡採用js-cookie

全域性loading、合理利用vue router守衛

全域性loading

上面說完了axios、vuex,現在結合之前說一下設定全域性loading效果。

平常寫程式碼每個請求之前一般都需要設定loading ,成功之後結束loading效果,這就迫使我們不得不寫大量重複程式碼,如果不想這樣做,可以結合axiosvuex統一做了。

  • 首先,在說vuex的時候,我在src/下建立了一個store,現在就在store/modules/app.js 寫這個Loading效果的程式碼;
const app = {
  state: {
    requestLoading: 0
  },
  mutations: {
    SET_LOADING: (state, status) => {
      // error 的時候直接重置
      if (status === 0) {
        state.requestLoading = 0
        return
      }
      state.requestLoading = status ? ++state.requestLoading : --state.requestLoading
    }
  },
  actions: {
    SetLoading ({ commit }, status) {
      commit('SET_LOADING', status)
    }
  }
}

export default app
複製程式碼
  • 再來修改一下utils/request.js
import axios from 'axios'
import store from '@/store'

// 建立axios 例項
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 10000 // 請求超時時間
})

// request 攔截器
service.interceptors.request.use(
  config => {
    // 這裡可以自定義一些config 配置

    // loading + 1
    store.dispatch('SetLoading', true)

    return config
  },
  error => {
    //  這裡處理一些請求出錯的情況

    // loading 清 0 
    setTimeout(function () {
      store.dispatch('SetLoading', 0)
    }, 300)

    console.log(error)
    Promise.reject(error)
  }
)

// response 攔截器
service.interceptors.response.use(
  response => {
    const res = response.data
    // 這裡處理一些response 正常放回時的邏輯

    // loading - 1
    store.dispatch('SetLoading', false)

    return res
  },
  error => {
    // 這裡處理一些response 出錯時的邏輯

    // loading - 1
    store.dispatch('SetLoading', false)

    return Promise.reject(error)
  }
)

export default service
複製程式碼
  • 其次,在src/components/下建立 RequestLoading.vue 元件:
<template>
  <transition name="fade-transform" mode="out-in">
    <div class="request-loading-component" v-if="requestLoading">
      <svg-icon icon-class="loading" />
    </div>
  </transition>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'RequestLoading',
  computed: {
    ...mapGetters([
      'requestLoading'
    ])
  }
}
</script>

<style lang='scss' scoped>
.request-loading-component {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  //background-color: rgba(48, 65, 86, 0.2);
  background-color: transparent;
  font-size: 150px;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  z-index: 999999;
}
</style>
複製程式碼

最後,在app.vue中引入即可。

附: 為了方便演示,專案裡出了初始化包括axiosvuexvue-router, 專案使用了js-cookieelement-ui等,此步驟之後,會改造一下app.vue

vue router守衛

vue-router 提供了非常方便的鉤子,可以讓我們在做路由跳轉的時候做一些操作,比如常見的許可權驗證。

  • 首先,需要在src/utils/下建立auth.js,用於儲存token;
import Cookies from 'js-cookie'

const TokenKey = 'project-token'

export function getToken () {
  return Cookies.get(TokenKey)
}

export function setToken (token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken () {
  return Cookies.remove(TokenKey)
}
複製程式碼

src/utils/下建立permission.js:

import router from '@/router'
import store from '@/store'
import {
  getToken
} from './auth'
import NProgress from 'nprogress' // 進度條
import 'nprogress/nprogress.css' // 進度條樣式
import {
  Message
} from 'element-ui'

const whiteList = ['/login'] // 不重定向白名單
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({
        path: '/'
      })
      NProgress.done()
    } else { // 實時拉取使用者的資訊
      store.dispatch('GetUserInfo').then(res => {
        next()
      }).catch(err => {
        store.dispatch('FedLogOut').then(() => {
          Message.error('拉取使用者資訊失敗,請重新登入!' + err)
          next({
            path: '/'
          })
        })
      })
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 結束Progress
})
複製程式碼
Nginx try_files 以及 404

nginx配置如下:

location / {
        root   /www/vue-project-demo/;
        try_files $uri $uri/ /index.html index.htm;
}
複製程式碼

try_files : 可以理解為nginx 不處理你的這些url地址請求; 那麼伺服器如果不處理了,前端要自己做一些404 操作,比如下面這樣:

// router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    { path: '/404', component: () => import('@/views/404') },
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    },
    { path: '*', redirect: '/404' }
  ]
})
複製程式碼

然後寫一個404 的view 就ok 。

常用的utils

到現在為止,utils/目錄下應該有auth.js 、permission.js、request.js

  • 那麼對與一些常用的方法,你可以放到utils/common.js 裡,統一installvue 例項上,並通過Vue.use()使用;

  • 對於一些全域性的過濾器,你仍可以放到utils/filters.js裡,使用Vue.fileter()註冊到全域性;

  • 對於一些全域性方法,又不是很長用到的,可以放到utils/index.js,哪裡使用哪裡import

mixin減少專案冗餘程式碼

直接看程式碼吧,要寫奔潰了....

使用cdn減少檔案打包的體積

到此時,看我專案裡都用了什麼:

image.png
主要就是這些,那麼執行一下打包命令呢?
image.png

可能這時候你還覺得沒什麼, 單檔案最多的還沒超過800kb呢...

我把專案通過jenkins部署到伺服器上,看一下訪問:

image.png

可以看到,chunk-vendors 載入了將近12秒,這還是隻有框架沒有內容的前提下,當然你可能說你專案中用不到vuex、用不到js-cookie,但是隨著專案的迭代維護,最後肯定不比現在小。

那麼,有些檔案在生產環境是不是可以嘗試使用cdn呢?

為了方便對比,這裡保持原始碼不動(master分支),再切出來一個分支改動優化(optimize分支), 上程式碼:

// vue.config.js  修改
const path = require('path')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

// cdn預載入使用
const externals = {
  'vue': 'Vue',
  'vue-router': 'VueRouter',
  'vuex': 'Vuex',
  'axios': 'axios',
  'element-ui': 'ELEMENT',
  'js-cookie': 'Cookies',
 'nprogress': 'NProgress'
}

const cdn = {
  // 開發環境
  dev: {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
    ],
    js: []
  },
  // 生產環境
  build: {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
    ],
    js: [
      'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
      'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js',
      'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
      'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js',
      'https://unpkg.com/element-ui/lib/index.js',
      'https://cdn.bootcss.com/js-cookie/2.2.0/js.cookie.min.js',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js'
    ]
  }
}

module.exports = {
  chainWebpack: config => {
    // 這裡是對環境的配置,不同環境對應不同的BASE_API,以便axios的請求地址不同
    config.plugin('define').tap(args => {
      const argv = process.argv
      const mode = argv[argv.indexOf('--project-mode') + 1]
      args[0]['process.env'].MODE = `"${mode}"`
      args[0]['process.env'].BASE_API = '"http://47.94.138.75:8000"'
      return args
    })

    /**
     * 新增CDN引數到htmlWebpackPlugin配置中, 詳見public/index.html 修改
     */
    config.plugin('html').tap(args => {
      if (process.env.NODE_ENV === 'production') {
        args[0].cdn = cdn.build
      }
      if (process.env.NODE_ENV === 'development') {
        args[0].cdn = cdn.dev
      }
      return args
    })

    // svg loader
    const svgRule = config.module.rule('svg') // 找到svg-loader
    svgRule.uses.clear() // 清除已有的loader, 如果不這樣做會新增在此loader之後
    svgRule.exclude.add(/node_modules/) // 正則匹配排除node_modules目錄
    svgRule // 新增svg新的loader處理
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })

    // 修改images loader 新增svg處理
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/icons'))
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  },

  // 修改webpack config, 使其不打包externals下的資源
  configureWebpack: config => {
    const myConfig = {}
    if (process.env.NODE_ENV === 'production') {
      // 1. 生產環境npm包轉CDN
      myConfig.externals = externals
    }
    if (process.env.NODE_ENV === 'development') {
      /**
       * 關閉host check,方便使用ngrok之類的內網轉發工具
       */
      myConfig.devServer = {
        disableHostCheck: true
      }
    }
    //   open: true,
    //   hot: true
    //   // https: true,
    //   // proxy: {
    //   //   '/proxy': {
    //   //     target: 'http://47.94.138.75',
    //   //     // changeOrigin: true,
    //   //     pathRewrite: {
    //   //       '^/proxy': ''
    //   //     }
    //   //   }
    //   // },
    // }
    return myConfig
  }
}
複製程式碼
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">

  <!-- 使用CDN加速的CSS檔案,配置在vue.config.js下 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
  <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
  <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) { %>
  <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
  <% } %>

  <title>vue-project-demo</title>
</head>

<body>
  <noscript>
    <strong>We're sorry but vue-project-demo doesn't work properly without JavaScript enabled. Please enable it to
      continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- 使用CDN加速的JS檔案,配置在vue.config.js下 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  <% } %>
  <!-- built files will be auto injected -->
</body>

</html>
複製程式碼

最後去除main.js 中引入的import 'element-ui/lib/theme-chalk/index.css'

OK ,現在執行一下build

image.png

可以看到,相對於 793.20KB61.94k小了將近13倍!!!

把這個分支部署到伺服器,話不多說,對比一下就好:

image.png

使用Gzip 加速

  • 引入 compression-webpack-plugin : npm i -D compression-webpack-plugin www.webpackjs.com/plugins/com…

  • 修改vue.config.js,老規矩,上最全的程式碼:

const path = require('path')
const CompressionWebpackPlugin = require('compression-webpack-plugin')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

// cdn預載入使用
const externals = {
  'vue': 'Vue',
  'vue-router': 'VueRouter',
  'vuex': 'Vuex',
  'axios': 'axios',
  'element-ui': 'ELEMENT',
  'js-cookie': 'Cookies',
  'nprogress': 'NProgress'
}

const cdn = {
  // 開發環境
  dev: {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
    ],
    js: []
  },
  // 生產環境
  build: {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
    ],
    js: [
      'https://cdn.bootcss.com/vue/2.5.21/vue.min.js',
      'https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js',
      'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js',
      'https://cdn.bootcss.com/axios/0.18.0/axios.min.js',
      'https://unpkg.com/element-ui/lib/index.js',
      'https://cdn.bootcss.com/js-cookie/2.2.0/js.cookie.min.js',
      'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js'
    ]
  }
}

// 是否使用gzip
const productionGzip = true
// 需要gzip壓縮的檔案字尾
const productionGzipExtensions = ['js', 'css']

module.exports = {
  chainWebpack: config => {
    // 這裡是對環境的配置,不同環境對應不同的BASE_API,以便axios的請求地址不同
    config.plugin('define').tap(args => {
      const argv = process.argv
      const mode = argv[argv.indexOf('--project-mode') + 1]
      args[0]['process.env'].MODE = `"${mode}"`
      args[0]['process.env'].BASE_API = '"http://47.94.138.75:8000"'
      return args
    })

    /**
     * 新增CDN引數到htmlWebpackPlugin配置中, 詳見public/index.html 修改
     */
    config.plugin('html').tap(args => {
      if (process.env.NODE_ENV === 'production') {
        args[0].cdn = cdn.build
      }
      if (process.env.NODE_ENV === 'development') {
        args[0].cdn = cdn.dev
      }
      return args
    })

    // svg loader
    const svgRule = config.module.rule('svg') // 找到svg-loader
    svgRule.uses.clear() // 清除已有的loader, 如果不這樣做會新增在此loader之後
    svgRule.exclude.add(/node_modules/) // 正則匹配排除node_modules目錄
    svgRule // 新增svg新的loader處理
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })

    // 修改images loader 新增svg處理
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/icons'))
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  },

  // 修改webpack config, 使其不打包externals下的資源
  configureWebpack: config => {
    const myConfig = {}
    if (process.env.NODE_ENV === 'production') {
      // 1. 生產環境npm包轉CDN
      myConfig.externals = externals

      myConfig.plugins = []
      // 2. 構建時開啟gzip,降低伺服器壓縮對CPU資源的佔用,伺服器也要相應開啟gzip
      productionGzip && myConfig.plugins.push(
        new CompressionWebpackPlugin({
          test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
          threshold: 8192,
          minRatio: 0.8
        })
      )
    }
    if (process.env.NODE_ENV === 'development') {
      /**
       * 關閉host check,方便使用ngrok之類的內網轉發工具
       */
      myConfig.devServer = {
        disableHostCheck: true
      }
    }
    //   open: true,
    //   hot: true
    //   // https: true,
    //   // proxy: {
    //   //   '/proxy': {
    //   //     target: 'http://47.94.138.75',
    //   //     // changeOrigin: true,
    //   //     pathRewrite: {
    //   //       '^/proxy': ''
    //   //     }
    //   //   }
    //   // },
    // }
    return myConfig
  }
}
複製程式碼
  • 再次執行build,我們會發現dist/下所有的.js.css都會多出一個.js.gz、.css.gz的檔案,這就是我們需要的壓縮檔案,可以看到最大的只有18.05KB,想想是不是比較激動...

    image.png

  • 當然,這玩意還需要服務端支援,也就是配置nginx

gzip on;
gzip_static on;
gzip_min_length 1024;
gzip_buffers 4 16k;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
複製程式碼
  • 配置完重啟nginx
    image.png

配置成功的話,可以看到載入的是比較小的Gzip

image.png

response headers 裡會有一個Content-Encoding:gzip

image.png

---------------------------- 未完待續 -------------------------------

相關文章