基于 vue-cli4 實(shí)現(xiàn)的移動(dòng)端 H5 開發(fā)模板

vue-cli4-vant

簡介

這是基于 vue-cli4 實(shí)現(xiàn)的移動(dòng)端 H5 開發(fā)模板,其中包含項(xiàng)目常用的配置及組件封裝,可供快速開發(fā)使用。

技術(shù)棧:vue-cli4 + webpack4 + vant + axios + less + postcss-px2rem

// 安裝依賴
npm install

// 本地啟動(dòng)
npm run dev

// 生產(chǎn)打包
npm run build

配置 vant

vant 是一套輕量、可靠的移動(dòng)端 Vue 組件庫,非常適合基于 vue 技術(shù)棧的移動(dòng)端開發(fā)。在過去很長的一段時(shí)間內(nèi),本人用的移動(dòng)端 UI 框架都是 vux。后來由于 vux 不支持 vue-cli3,就轉(zhuǎn)用了 vant,不得不說,無論是在交互體驗(yàn)上,還是代碼邏輯上,vant 都比 vux 好很多,而且 vant 的坑比較少。

對(duì)于第三方 UI 組件,如果是全部引入的話,比如會(huì)造成打包體積過大,加載首頁白屏?xí)r間過長的問題,所以按需加載非常必要。vant 也提供了按需加載的方法。babel-plugin-import 是一款 babel 插件,它會(huì)在編譯過程中將 import 的寫法自動(dòng)轉(zhuǎn)換為按需引入的方式。

1、安裝依賴

npm i babel-plugin-import -D

2、配置 .babelrc 或者 babel.config.js 文件

// 在.babelrc 中添加配置
{
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}

// 對(duì)于使用 babel7 的用戶,可以在 babel.config.js 中配置
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
};

3、按需引入

你可以在代碼中直接引入 Vant 組件,插件會(huì)自動(dòng)將代碼轉(zhuǎn)化為方式二中的按需引入形式

import Vue from 'vue'
import { Button } from 'vant'

Vue.use(Button)

rem 適配

移動(dòng)端適配是開發(fā)過程中不得不面對(duì)的事情。在此,我們使用 postcss 中的 px2rem-loader,將我們項(xiàng)目中的 px 按一定比例轉(zhuǎn)化 rem,這樣我們就可以對(duì)著藍(lán)湖上的標(biāo)注寫 px 了。

我們將 html 字跟字體設(shè)置為 100px,很多人選擇設(shè)置為 375px,但是我覺得這樣換算出來的 rem 不夠精確,而且我們?cè)诳刂婆_(tái)上調(diào)試代碼的時(shí)候無法很快地口算得出它本來的 px 值。如果設(shè)置 1rem=100px,這樣我們看到的 0.16rem,0.3rem 就很快得算出原來是 16px,30px 了。

具體步驟如下;

1、安裝依賴

npm install px2rem-loader --save-dev

2、在 vue.config.js 進(jìn)行如下配置

  css: {
    // css預(yù)設(shè)器配置項(xiàng)
    loaderOptions: {
      postcss: {
        plugins: [
          require('postcss-px2rem')({
            remUnit: 100
          })
        ]
      }
    }
  },

3、在 main.js 設(shè)置 html 跟字體大小

function initRem() {
  let cale = window.screen.availWidth > 750 ? 2 : window.screen.availWidth / 375
  window.document.documentElement.style.fontSize = `${100 * cale}px`
}

window.addEventListener('resize', function() {
  initRem()
})

axios 請(qǐng)求封裝

1、設(shè)置請(qǐng)求攔截和響應(yīng)攔截

const PRODUCT_URL = 'https://xxxx.com'
const MOCK_URL = 'http://xxxx.com'
let http = axios.create({
  baseURL: process.env.NODE_ENV === 'production' ? PRODUCT_URL : MOCK_URL,
})
// 請(qǐng)求攔截器
http.interceptors.request.use(
  (config) => {
    // 設(shè)置token,Content-Type
    var token = sessionStorage.getItem('token')
    config.headers['token'] = token
    config.headers['Content-Type'] = 'application/json;charset=UTF-8'
    // 請(qǐng)求顯示loading效果
    if (config.loading === true) {
      vm.$loading.show()
    }
    return config
  },
  (error) => {
    vm.$loading.hide()
    return Promise.reject(error)
  }
)
// 響應(yīng)攔截器
http.interceptors.response.use(
  (res) => {
    vm.$loading.hide()
    // token失效,重新登錄
    if (res.data.code === 401) {
      //  重新登錄
    }
    return res
  },
  (error) => {
    vm.$loading.hide()
    return Promise.reject(error)
  }
)

2、封裝 get 和 post 請(qǐng)求方法

function get(url, data, lodaing) {
  return new Promise((resolve, reject) => {
    http
      .get(url)
      .then(
        (response) => {
          resolve(response)
        },
        (err) => {
          reject(err)
        }
      )
      .catch((error) => {
        reject(error)
      })
  })
}

function post(url, data, loading) {
  return new Promise((resolve, reject) => {
    http
      .post(url, data, { loading: loading })
      .then(
        (response) => {
          resolve(response)
        },
        (err) => {
          reject(err)
        }
      )
      .catch((error) => {
        reject(error)
      })
  })
}

export { get, post }

3、把 get,post 方法掛載到 vue 實(shí)例上。

// main.js
import { get, post } from './js/ajax'
Vue.prototype.$http = { get, post }

工具類函數(shù)封裝

1、添加方法到 vue 實(shí)例的原型鏈上

export default {
  install (Vue, options) {
    Vue.prototype.util = {
      method1(val) {
        ...
      },
      method2 (val) {
       ...
      },
  }
}

2、在 main.js 通過 vue.use()注冊(cè)

import utils from './js/utils'
Vue.use(utils)

本文提供以下函數(shù)封裝

  • 獲取 url 參數(shù)值
  • 判斷瀏覽器類型
  • 判斷 IOS/android
  • 校驗(yàn)手機(jī)號(hào)碼
  • 檢驗(yàn)車牌號(hào)
  • 校驗(yàn)車架號(hào)
  • 檢驗(yàn)身份證號(hào)碼
  • 日期格式化
  • 時(shí)間格式化
  • 城市格式化
  • 壓縮圖片
  • 圖片轉(zhuǎn)成 base64

vue-router 配置

平時(shí)很多人對(duì) vue-router 的配置可配置了 path 和 component,實(shí)現(xiàn)了路由跳轉(zhuǎn)即可。其實(shí) vue-router 可做的事情還有很多,比如

  • 路由懶加載配置
  • 改變單頁面應(yīng)用的 title
  • 登錄權(quán)限校驗(yàn)
  • 頁面緩存配置

路由懶加載配置

Vue 項(xiàng)目中實(shí)現(xiàn)路由按需加載(路由懶加載)的 3 中方式:

// 1、Vue異步組件技術(shù):
{
    path: '/home',
    name: 'Home',
    component: resolve => reqire(['../views/Home.vue'], resolve)
}

// 2、es6提案的import()
{
  path: '/',
  name: 'home',
  component: () => import('../views/Home.vue')
}

// 3、webpack提供的require.ensure()
{
    path: '/home',
    name: 'Home',
    component: r => require.ensure([],() =>  r(require('../views/Home.vue')), 'home')
}

本項(xiàng)目采用的是第二種方式,為了后續(xù) webpack 打包優(yōu)化。

改變單頁面應(yīng)用的 title

由于單頁面應(yīng)用只有一個(gè) html,所有頁面的 title 默認(rèn)是不會(huì)改變的,但是我們可以才路由配置中加入相關(guān)屬性,再在路由守衛(wèi)中通過 js 改變頁面的 title

router.beforeEach((to, from, next) => {
  document.title = to.meta.title
})

登錄權(quán)限校驗(yàn)

在應(yīng)用中,通常會(huì)有以下的場景,比如商城:有些頁面是不需要登錄即可訪問的,如首頁,商品詳情頁等,都是用戶在任何情況都能看到的;但是也有是需要登錄后才能訪問的,如個(gè)人中心,購物車等。此時(shí)就需要對(duì)頁面訪問進(jìn)行控制了。

此外,像一些需要記錄用戶信息和登錄狀態(tài)的項(xiàng)目,也是需要做登錄權(quán)限校驗(yàn)的,以防別有用心的人通過直接訪問頁面的 url 打開頁面。

此時(shí)。路由守衛(wèi)可以幫助我們做登錄校驗(yàn)。具體如下:

1、配置路由的 meta 對(duì)象的 auth 屬性

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/Home.vue'),
    meta: { title: '首頁', keepAlive: false, auth: false },
  },
  {
    path: '/mine',
    name: 'mine',
    component: () => import('../views/mine.vue'),
    meta: { title: '我的', keepAlive: false, auth: true },
  },
]

2、在路由首頁進(jìn)行判斷。當(dāng)to.meta.authtrue(需要登錄),且不存在登錄信息緩存時(shí),需要重定向去登錄頁面

router.beforeEach((to, from, next) => {
  document.title = to.meta.title
  const userInfo = sessionStorage.getItem('userInfo') || null
  if (!userInfo && to.meta.auth) {
    next('/login')
  } else {
    next()
  }
})

頁面緩存配置

項(xiàng)目中,總有一些頁面我們是希望加載一次就緩存下來的,此時(shí)就用到 keep-alive 了。keep-alive 是 Vue 提供的一個(gè)抽象組件,用來對(duì)組件進(jìn)行緩存,從而節(jié)省性能,由于是一個(gè)抽象組件,所以在 v 頁面渲染完畢后不會(huì)被渲染成一個(gè) DOM 元素。

1、通過配置路由的 meta 對(duì)象的 keepAlive 屬性值來區(qū)分頁面是否需要緩存

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/Home.vue'),
    meta: { title: '首頁', keepAlive: false, auth: false },
  },
  {
    path: '/list',
    name: 'list',
    component: () => import('../views/list.vue'),
    meta: { title: '列表頁', keepAlive: true, auth: false },
  },
]

2、在 app.vue 做緩存判斷

<div id="app">
  <router-view v-if="!$route.meta.keepAlive"></router-view>
  <keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
</div>

多環(huán)境變量配置

首先我們先來了解一下環(huán)境變量,一般情況下我們的項(xiàng)目會(huì)有三個(gè)環(huán)境,本地環(huán)境(development),測(cè)試環(huán)境(test),生產(chǎn)環(huán)境(production),我們可以在項(xiàng)目根目錄下建三個(gè)配置環(huán)境變量的文件.env.development,.env.test,.env.production

環(huán)境變量文件中只包含環(huán)境變量的“鍵=值”對(duì):

NODE_ENV = 'production'
VUE_APP_ENV = 'production' // 只有VUE_APP開頭的環(huán)境變量可以在項(xiàng)目代碼中直接使用

除了自定義的 VUEAPP*變量之外,還有兩個(gè)可用的變量:

  • NODE_ENV : "development"、"production" 或 "test"中的一個(gè)。具體的值取決于應(yīng)用運(yùn)行的模式。
  • BASE_URL : 和 vue.config.js 中的 publicPath 選項(xiàng)相符,即你的應(yīng)用會(huì)部署到的基礎(chǔ)路徑。

下面開始配置我們的環(huán)境變量

1、在項(xiàng)目根目錄中新建.env.*

  • .env.development 本地開發(fā)環(huán)境配置
NODE_ENV='development'
VUE_APP_ENV = 'development'
  • env.staging 測(cè)試環(huán)境配置
NODE_ENV='production'
VUE_APP_ENV = 'staging'
  • env.production 正式環(huán)境配置
NODE_ENV='production'
VUE_APP_ENV = 'production'

為了在不同環(huán)境配置更多的變量,我們?cè)?src 文件下新建一個(gè) config/index

// 根據(jù)環(huán)境引入不同配置 process.env.NODE_ENV
const config = require('./env.' + process.env.VUE_APP_ENV)
module.exports = config

在同級(jí)目錄下新建 env.development.js,env.test.js,env.production.js,在里面配置需要的變量。
以 env.development.js 為例

module.exports = {
  baseUrl: 'http://localhost:8089', // 項(xiàng)目地址
  baseApi: 'https://www.mock.com/api', // 本地api請(qǐng)求地址
}

2、配置打包命令

package.json 里的 scripts 不同環(huán)境的打包命令

  • 通過 npm run serve 啟動(dòng)本地
  • 通過 npm run test 打包測(cè)試
  • 通過 npm run build 打包正式
"scripts": {
  "dev": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "test": "vue-cli-service build --mode test",
}

vue.config.js 配置

vue-cli3 開始,新建的腳手架都需要我們?cè)?vue.config.js 配置我們項(xiàng)目的東西。主要包括

  • 打包后文件輸出位置
  • 關(guān)閉生產(chǎn)環(huán)境 souecemap
  • 配置 rem 轉(zhuǎn)化 px
  • 配置 alias 別名
  • 去除生產(chǎn)環(huán)境 console
  • 跨域代理設(shè)置

此外,還有很多屬于優(yōu)化打包的配置,后面會(huì)一一道來。

module.exports = {
  // 部署應(yīng)用包時(shí)的基本URL,默認(rèn)為'/'
  publicPath: './',

  // 將構(gòu)建好的文件輸出到哪里,本司要求
  outputDir: 'dist/static',

  // 放置生成的靜態(tài)資源(js、css、img、fonts)的目錄。
  assetsDir: 'static',

  // 指定生成的 index.html 的輸出路徑
  indexPath: 'index.html',

  // 是否使用包含運(yùn)行時(shí)編譯器的 Vue 構(gòu)建版本。
  runtimeCompiler: false,

  // 默認(rèn)情況下 babel-loader 會(huì)忽略所有 node_modules 中的文件。如果你想要通過 Babel 顯式轉(zhuǎn)譯一個(gè)依賴,可以在這個(gè)選項(xiàng)中列出來。
  transpileDependencies: [],

  // 如果你不需要生產(chǎn)環(huán)境的 source map,可以將其設(shè)置為 false 以加速生產(chǎn)環(huán)境構(gòu)建。
  productionSourceMap: false,

  // 配置css
  css: {
    // 是否使用css分離插件 ExtractTextPlugin
    extract: true,
    sourceMap: true,
    // css預(yù)設(shè)器配置項(xiàng)
    loaderOptions: {
      postcss: {
        // options here will be passed to postcss-loader
        plugins: [
          require('postcss-px2rem')({
            remUnit: 100,
          }),
        ],
      },
    },
    // 啟用 CSS modules for all css / pre-processor files.
    modules: false,
  },

  // 是一個(gè)函數(shù),允許對(duì)內(nèi)部的 webpack 配置進(jìn)行更細(xì)粒度的修改。
  chainWebpack: (config) => {
    // 配置別名
    config.resolve.alias
      .set('@', resolve('src'))
      .set('assets', resolve('src/assets'))
      .set('components', resolve('src/components'))
      .set('views', resolve('src/views'))

    config.optimization.minimizer('terser').tap((args) => {
      // 去除生產(chǎn)環(huán)境console
      args[0].terserOptions.compress.drop_console = true
      return args
    })
  },

  // 是否為 Babel 或 TypeScript 使用 thread-loader。該選項(xiàng)在系統(tǒng)的 CPU 有多于一個(gè)內(nèi)核時(shí)自動(dòng)啟用,僅作用于生產(chǎn)構(gòu)建。
  parallel: require('os').cpus().length > 1,

  devServer: {
    host: '0.0.0.0',
    port: 8088, // 端口號(hào)
    https: false, // https:{type:Boolean}
    open: false, // 配置自動(dòng)啟動(dòng)瀏覽器  open: 'Google Chrome'-默認(rèn)啟動(dòng)谷歌

    // 配置多個(gè)代理
    proxy: {
      '/api': {
        target: 'https://www.mock.com',
        ws: true, // 代理的WebSockets
        changeOrigin: true, // 允許websockets跨域
        pathRewrite: {
          '^/api': '',
        },
      },
    },
  },
}

基礎(chǔ)組件封裝

在開發(fā)項(xiàng)目過程中,通常會(huì)用到很多功能和設(shè)計(jì)相類似的組件,toast 和 dialog 組件基本是每一個(gè)移動(dòng)端項(xiàng)目都會(huì)用到的。為了更好匹配自己公司的 UI 設(shè)計(jì)風(fēng)格,我們沒有直接用 vant 的 toast 和 dialog 組件,而是自己封裝了類似的組件,可供直接調(diào)用,如:

this.$toast({ msg: '手機(jī)號(hào)碼不能為空' })

this.$toast({
  msg: '成功提示',
  type: 'success',
})

this.$dialog({
  title: '刪除提示',
  text: '是否確定刪除此標(biāo)簽?',
  showCancelBtn: true,
  confirmText: '確認(rèn)',
  confirm(content) {
    alert('刪除成功')
  },
})

效果圖如下
<img src="./static/toast.png">

toast 傳入?yún)?shù)

Props

name type default description
msg String '' 彈窗提示語
type String '' 彈窗類型:success(成功提示),fail(失敗提示),warning(警告),loading(加載)

dialog 傳入?yún)?shù)

Props

name type default description
title String '' 標(biāo)題
text String '' 文本內(nèi)容
type String '' 默認(rèn)純文本,input(輸入框)
maxlength Number 20 輸入的最多字?jǐn)?shù)
confirmText String 確定 右邊按鈕
cancelText String 取消 左邊按鈕

Events

name params description
confirm null 選擇后的回調(diào)
cancel ull 取消后的回調(diào)

webpack 可視化分析

從這里開始,我們開始進(jìn)行 webpack 優(yōu)化打包。首先我們來分析一下 webpack 打包性能瓶頸,找出問題所在,然后才能對(duì)癥下藥。此時(shí)就用到 webpack-bundle-analyzer 了。
1、安裝依賴

npm install webpack-bundle-analyzer -D

2、在 vue.config.js 配置

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
configureWebpack: (config) => {
  if (process.env.NODE_ENV === 'production') {
    config.plugins.push(new BundleAnalyzerPlugin())
  }
}

打包后,我們可以看到這樣一份依賴圖
<img src="./static/cdn前.png">

從以上的界面中,我們可以得到以下信息:

  • 打包出的文件中都包含了什么,以及模塊之間的依賴關(guān)系
  • 每個(gè)文件的大小在總體中的占比,找出較大的文件,思考是否有替換方案,是否使用了它包含了不必要的依賴?
  • 是否有重復(fù)的依賴項(xiàng),對(duì)此可以如何優(yōu)化?
  • 每個(gè)文件的壓縮后的大小。

CDN 資源優(yōu)化

CDN 的全稱是 Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。CDN 是構(gòu)建在網(wǎng)絡(luò)之上的內(nèi)容分發(fā)網(wǎng)絡(luò),依靠部署在各地的邊緣服務(wù)器,通過中心平臺(tái)的負(fù)載均衡、內(nèi)容分發(fā)、調(diào)度等功能模塊,使用戶就近獲取所需內(nèi)容,降低網(wǎng)絡(luò)擁塞,提高用戶訪問響應(yīng)速度和命中率。CDN 的關(guān)鍵技術(shù)主要有內(nèi)容存儲(chǔ)和分發(fā)技術(shù)。

隨著項(xiàng)目越做越大,依賴的第三方 npm 包越來越多,構(gòu)建之后的文件也會(huì)越來越大。再加上又是單頁應(yīng)用,這就會(huì)導(dǎo)致在網(wǎng)速較慢或者服務(wù)器帶寬有限的情況出現(xiàn)長時(shí)間的白屏。此時(shí)我們可以使用 CDN 的方法,優(yōu)化網(wǎng)絡(luò)加載速度。

1、將 vue、vue-router、vuex、axios 這些 vue 全家桶的資源,全部改為通過 CDN 鏈接獲取,在 index.html 里插入 相應(yīng)鏈接。

<body>
  <div id="app"></div>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
  <script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
  <script src="https://cdn.bootcss.com/element-ui/2.6.1/index.js"></script>
</body>

2、在 vue.config.js 配置 externals 屬性

module.exports = {
 ···
    externals: {
      'vue': 'Vue',
      'vuex': 'Vuex',
      'vue-router': 'VueRouter',
      'axios':'axios'
    }
  }

3、卸載相關(guān)依賴的 npm 包

npm uninstall  vue vue-router vuex axios

此時(shí)啟動(dòng)項(xiàng)目運(yùn)行就可以了。我們?cè)诳刂婆_(tái)就能發(fā)現(xiàn)項(xiàng)目加載了以上四個(gè) CDN 資源。

不過現(xiàn)在有不少聲音說,vue 全家桶加載 CDN 資源其實(shí)作用并不大,而且公共的 CDN 資源也沒有 npm 包那么穩(wěn)定,這個(gè)就見仁見智了。所以我在源碼時(shí)新建的分支做這個(gè)優(yōu)化。當(dāng)項(xiàng)目較小的就不考慮 CDN 優(yōu)化了。

當(dāng)然,當(dāng)引入其他較大第三方資源,比如 echarts,AMAP(高德地圖),采用 CDN 資源還是很有必要的。

gZip 加速優(yōu)化

所有現(xiàn)代瀏覽器都支持 gzip 壓縮,啟用 gzip 壓縮可大幅縮減傳輸資源大小,從而縮短資源下載時(shí)間,減少首次白屏?xí)r間,提升用戶體驗(yàn)。

gzip 對(duì)基于文本格式文件的壓縮效果最好(如:CSS、JavaScript 和 HTML),在壓縮較大文件時(shí)往往可實(shí)現(xiàn)高達(dá) 70-90% 的壓縮率,對(duì)已經(jīng)壓縮過的資源(如:圖片)進(jìn)行 gzip 壓縮處理,效果很不好。

const CompressionPlugin = require('compression-webpack-plugin')
configureWebpack: (config) => {
  if (process.env.NODE_ENV === 'production') {
    config.plugins.push(
      new CompressionPlugin({
        // gzip壓縮配置
        test: /\.js$|\.html$|\.css/, // 匹配文件名
        threshold: 10240, // 對(duì)超過10kb的數(shù)據(jù)進(jìn)行壓縮
        deleteOriginalAssets: false, // 是否刪除原文件
      })
    )
  }
}

首頁添加骨架屏

隨著 SPA 在前端界的逐漸流行,單頁面應(yīng)用不可避免地給首頁加載帶來壓力,此時(shí)良好的首頁用戶體驗(yàn)至關(guān)重要。很多 APP 采用了“骨架屏”的方式去展示未加載內(nèi)容,給予了用戶煥然一新的體驗(yàn)。

所謂的骨架屏,就是在頁面內(nèi)容未加載完成的時(shí)候,先使用一些圖形進(jìn)行占位,待內(nèi)容加載完成之后再把它替換掉。在這個(gè)過程中用戶會(huì)感知到內(nèi)容正在逐漸加載并即將呈現(xiàn),降低了“白屏”的不良體驗(yàn)。

本文采用 vue-skeleton-webpack-plugin 插件為單頁面應(yīng)用注入骨架屏。

1、在 src 的 common 文件夾下面創(chuàng)建了 Skeleton1.vue,Skeleton2.vue,具體的結(jié)構(gòu)和樣式自行設(shè)計(jì),此處省略一萬字。。。。

2、在同級(jí)目錄下新建 entry-skeleton.js

import Vue from 'vue'
import Skeleton1 from './Skeleton1'
import Skeleton2 from './Skeleton2'

export default new Vue({
  components: {
    Skeleton1,
    Skeleton2,
  },
  template: `
    <div>
      <skeleton1 id="skeleton1" style="display:none"/>
      <skeleton2 id="skeleton2" style="display:none"/>
    </div>
  `,
})

在 vue.config.js 下配置插件

const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin')
configureWebpack: (config) => {
  config.plugins.push(
    new SkeletonWebpackPlugin({
      webpackConfig: {
        entry: {
          app: path.join(__dirname, './src/common/entry-skeleton.js'),
        },
      },
      minimize: true,
      quiet: true,
      router: {
        mode: 'hash',
        routes: [
          { path: '/', skeletonId: 'skeleton1' },
          { path: '/about', skeletonId: 'skeleton2' },
        ],
      },
    })
  )
}

此時(shí)重新加載頁面就可以看到我們的骨架屏了。注意:一定要配置樣式分離 extract: true

使用 SvgIcon 組件

svg 優(yōu)點(diǎn):

  • 圖標(biāo)易于實(shí)時(shí)修改,可以帶動(dòng)畫
  • 可以使用標(biāo)磚的 prop 和默認(rèn)值來將圖標(biāo)保持在一個(gè)典型的尺寸并隨時(shí)按需改變他們
  • 圖標(biāo)是內(nèi)聯(lián)的,所以不需要額外的 HTTP 請(qǐng)求
  • 可以動(dòng)態(tài)地使得圖標(biāo)可訪問

通常我們項(xiàng)目都是使用 iconfont 阿里巴巴圖標(biāo)矢量庫,但是操作比較麻煩,每次更新都要重新下載鏈接。另外我們可以使 svg-sprite-loader 實(shí)現(xiàn)。

1、新增 SvgIcon 組件

<template>
  <svg class="svg-icon" aria-hidden="true">
    <use :xlink:href="iconName" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true,
    },
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`
    },
  },
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

2、全局注冊(cè)組件并導(dǎo)入 svg

import SvgIcon from './SvgIcon.vue'
import Vue from 'vue'

// 注冊(cè)到全局
Vue.component('svg-icon', SvgIcon)

const requireAll = (requireContext) => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

3、在 main.js 中引入

import './components/icon'

4、配置 vue.config.js

module.exports = {
  chainWebpack: (config) => {
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.exclude.add(/node_modules/)
    svgRule
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]',
      })

    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/icons'))
    config.module.rule('images').test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  },
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容