搭建一个vue-cli4+webpack移动端框架(开箱即用)

简介

这是基于 vue-cli4 实现的移动端框架,其中包含项目经常使用的配置,组件封装及webpack优化方法,可供快速开发使用。javascript

技术栈:vue-cli4 + webpack4 + vant + axios + less + postcss-px2remcss

源码 github.com/Michael-lzg…html

// 安装依赖
npm install

// 本地启动
npm run dev

// 生产打包
npm run build
复制代码

在一两年前,vue-cli3已经声驾到3.0+版本,可是因为旧项目一致习惯于vue-cli2的脚手架的使用,以前也写过一篇 搭建一个vue-cli的移动端H5开发模板 简单总结了一点移动端的开发技巧。前端

近日升级vue-cli脚手架才发现,这已经升级到4.0+版本了,以为不少必要在新的项目中使用vue-cli4进行开发,加上近来对webpack有了进一步理解,因此结合了vue-cli4和webpack搭建了一个移动端框架,以便开箱即用。 主要包括以下技术点:vue

  • vue-cli4脚手架
  • vant按需引入
  • 移动端rem适配
  • axios拦截封装
  • util工具类函数封装
  • vue-router配置
  • 登陆权限校验
  • 多环境变量配置
  • vue.config.js配置
  • toast组件封装
  • dialog组件封装
  • 跨域代理设置
  • webpack打包可视化分析
  • CDN资源优化
  • gzip打包优化
  • 首页添加骨架屏

关于更多的webpack优化方法,可参考 github.com/Michael-lzg…java

配置 vant

vant 是一套轻量、可靠的移动端 Vue 组件库,很是适合基于 vue 技术栈的移动端开发。在过去很长的一段时间内,本人用的移动端 UI 框架都是 vux。后来因为 vux 不支持 vue-cli3,就转用了 vant,不得不说,不管是在交互体验上,仍是代码逻辑上,vant 都比 vux 好不少,并且 vant 的坑比较少。webpack

对于第三方 UI 组件,若是是所有引入的话,好比会形成打包体积过大,加载首页白屏时间过长的问题,因此按需加载很是必要。vant 也提供了按需加载的方法。babel-plugin-import 是一款 babel 插件,它会在编译过程当中将 import 的写法自动转换为按需引入的方式。ios

一、安装依赖git

npm i babel-plugin-import -D
复制代码

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

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

// 对于使用 babel7 的用户,能够在 babel.config.js 中配置
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
};
复制代码

三、按需引入

你能够在代码中直接引入 Vant 组件,插件会自动将代码转化为方式二中的按需引入形式

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

Vue.use(Button)
复制代码

rem 适配

移动端适配是开发过程当中不得不面对的事情。在此,咱们使用 postcss 中的 px2rem-loader,将咱们项目中的 px 按必定比例转化 rem,这样咱们就能够对着蓝湖上的标注写 px 了。

咱们将 html 字跟字体设置为 100px,不少人选择设置为 375px,可是我以为这样换算出来的 rem 不够精确,并且咱们在控制台上调试代码的时候没法很快地口算得出它原本的 px 值。若是设置 1rem=100px,这样咱们看到的 0.16rem,0.3rem 就很快得算出原来是 16px,30px 了。

具体步骤以下;

一、安装依赖

npm install px2rem-loader --save-dev
复制代码

二、在 vue.config.js 进行以下配置

css: {
    // css预设器配置项
    loaderOptions: {
      postcss: {
        plugins: [
          require('postcss-px2rem')({
            remUnit: 100
          })
        ]
      }
    }
  },
复制代码

三、在 main.js 设置 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 请求封装

一、设置请求拦截和响应拦截

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,
})
// 请求拦截器
http.interceptors.request.use(
  (config) => {
    // 设置token,Content-Type
    var token = sessionStorage.getItem('token')
    config.headers['token'] = token
    config.headers['Content-Type'] = 'application/json;charset=UTF-8'
    // 请求显示loading效果
    if (config.loading === true) {
      vm.$loading.show()
    }
    return config
  },
  (error) => {
    vm.$loading.hide()
    return Promise.reject(error)
  }
)
// 响应拦截器
http.interceptors.response.use(
  (res) => {
    vm.$loading.hide()
    // token失效,从新登陆
    if (res.data.code === 401) {
      // 从新登陆
    }
    return res
  },
  (error) => {
    vm.$loading.hide()
    return Promise.reject(error)
  }
)
复制代码

二、封装 get 和 post 请求方法

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 }
复制代码

三、把 get,post 方法挂载到 vue 实例上。

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

工具类函数封装

一、添加方法到 vue 实例的原型链上

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

二、在 main.js 经过 vue.use()注册

import utils from './js/utils'
Vue.use(utils)
复制代码

vue-router 配置

平时不少人对 vue-router 的配置可配置了 path 和 component,实现了路由跳转便可。其实 vue-router 可作的事情还有不少,好比

  • 路由懒加载配置
  • 改变单页面应用的 title
  • 登陆权限校验
  • 页面缓存配置

路由懒加载配置

Vue 项目中实现路由按需加载(路由懒加载)的 3 中方式:

// 一、Vue异步组件技术:
{
  path: '/home',
  name: 'Home',
  component: resolve => reqire(['../views/Home.vue'], resolve)
}

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

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

本项目采用的是第二种方式,为了后续 webpack 打包优化。

改变单页面应用的 title

因为单页面应用只有一个 html,全部页面的 title 默认是不会改变的,可是咱们能够才路由配置中加入相关属性,再在路由守卫中经过 js 改变页面的 title

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

登陆权限校验

在应用中,一般会有如下的场景,好比商城:有些页面是不须要登陆便可访问的,如首页,商品详情页等,都是用户在任何状况都能看到的;可是也有是须要登陆后才能访问的,如我的中心,购物车等。此时就须要对页面访问进行控制了。

此外,像一些须要记录用户信息和登陆状态的项目,也是须要作登陆权限校验的,以防别有用心的人经过直接访问页面的 url 打开页面。

此时。路由守卫能够帮助咱们作登陆校验。具体以下:

一、配置路由的 meta 对象的 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 },
  },
]
复制代码

二、在路由首页进行判断。当to.meta.authtrue(须要登陆),且不存在登陆信息缓存时,须要重定向去登陆页面

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()
  }
})
复制代码

页面缓存配置

项目中,总有一些页面咱们是但愿加载一次就缓存下来的,此时就用到 keep-alive 了。keep-alive 是 Vue 提供的一个抽象组件,用来对组件进行缓存,从而节省性能,因为是一个抽象组件,因此在 v 页面渲染完毕后不会被渲染成一个 DOM 元素。

一、经过配置路由的 meta 对象的 keepAlive 属性值来区分页面是否须要缓存

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 },
  },
]
复制代码

二、在 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>
复制代码

多环境变量配置

首先咱们先来了解一下环境变量,通常状况下咱们的项目会有三个环境,本地环境(development),测试环境(test),生产环境(production),咱们能够在项目根目录下建三个配置环境变量的文件.env.development.env.test.env.production

环境变量文件中只包含环境变量的“键=值”对:

NODE_ENV = 'production'
VUE_APP_ENV = 'production' // 只有VUE_APP开头的环境变量能够在项目代码中直接使用
复制代码

除了自定义的 VUEAPP*变量以外,还有两个可用的变量:

  • NODE_ENV : "development"、"production" 或 "test"中的一个。具体的值取决于应用运行的模式。
  • BASE_URL : 和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。

下面开始配置咱们的环境变量

一、在项目根目录中新建.env.*

  • .env.development 本地开发环境配置
NODE_ENV='development'
VUE_APP_ENV = 'development'
复制代码
  • env.staging 测试环境配置
NODE_ENV='production'
VUE_APP_ENV = 'staging'
复制代码
  • env.production 正式环境配置
NODE_ENV='production'
VUE_APP_ENV = 'production'
复制代码

为了在不一样环境配置更多的变量,咱们在 src 文件下新建一个 config/index

// 根据环境引入不一样配置 process.env.NODE_ENV
const config = require('./env.' + process.env.VUE_APP_ENV)
module.exports = config
复制代码

在同级目录下新建 env.development.jsenv.test.jsenv.production.js,在里面配置须要的变量。
以 env.development.js 为例

module.exports = {
  baseUrl: 'http://localhost:8089', // 项目地址
  baseApi: 'https://www.mock.com/api', // 本地api请求地址
}
复制代码

二、配置打包命令

package.json 里的 scripts 不一样环境的打包命令

  • 经过 npm run serve 启动本地
  • 经过 npm run test 打包测试
  • 经过 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 开始,新建的脚手架都须要咱们在 vue.config.js 配置咱们项目的东西。主要包括

  • 打包后文件输出位置
  • 关闭生产环境 sourcemap
  • 配置 rem 转化 px
  • 配置 alias 别名
  • 去除生产环境 console
  • 跨域代理设置

此外,还有不少属于优化打包的配置,后面会一一道来。

module.exports = {
  publicPath: './', // 默认为'/'

  // 将构建好的文件输出到哪里,本司要求
  outputDir: 'dist/static',

  // 放置生成的静态资源(js、css、img、fonts)的目录。
  assetsDir: 'static',

  // 指定生成的 index.html 的输出路径
  indexPath: 'index.html',

  // 是否使用包含运行时编译器的 Vue 构建版本。
  runtimeCompiler: false,

  transpileDependencies: [],

  // 若是你不须要生产环境的 source map
  productionSourceMap: false,

  // 配置css
  css: {
    // 是否使用css分离插件 ExtractTextPlugin
    extract: true,
    sourceMap: true,
    // css预设器配置项
    loaderOptions: {
      postcss: {
        plugins: [
          require('postcss-px2rem')({
            remUnit: 100,
          }),
        ],
      },
    },
    // 启用 CSS modules for all css / pre-processor files.
    modules: false,
  },

  // 是一个函数,容许对内部的 webpack 配置进行更细粒度的修改。
  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) => {
      // 去除生产环境console
      args[0].terserOptions.compress.drop_console = true
      return args
    })
  },

  // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅做用于生产构建。
  parallel: require('os').cpus().length > 1,

  devServer: {
    host: '0.0.0.0',
    port: 8088, // 端口号
    https: false, // https:{type:Boolean}
    open: false, // 配置自动启动浏览器 open: 'Google Chrome'-默认启动谷歌

    // 配置多个代理
    proxy: {
      '/api': {
        target: 'https://www.mock.com',
        ws: true, // 代理的WebSockets
        changeOrigin: true, // 容许websockets跨域
        pathRewrite: {
          '^/api': '',
        },
      },
    },
  },
}
复制代码

基础组件封装

在开发项目过程当中,一般会用到不少功能和设计相相似的组件,toast 和 dialog 组件基本是每个移动端项目都会用到的。为了更好匹配本身公司的 UI 设计风格,咱们没有直接用 vant 的 toast 和 dialog 组件,而是本身封装了相似的组件,可供直接调用,如:

this.$toast({ msg: '手机号码不能为空' })

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

this.$dialog({
  title: '删除提示',
  text: '是否肯定删除此标签?',
  showCancelBtn: true,
  confirmText: '确认',
  confirm(content) {
    alert('删除成功')
  },
})
复制代码

效果图以下

toast 传入参数

Props

name type default description
msg String '' 弹窗提示语
type String '' 弹窗类型:success(成功提示),fail(失败提示),warning(警告),loading(加载)

dialog 传入参数

Props

name type default description
title String '' 标题
text String '' 文本内容
type String '' 默认纯文本,input(输入框)
maxlength Number 20 输入的最多字数
confirmText String 肯定 右边按钮
cancelText String 取消 左边按钮

Events

name params description
confirm null 选择后的回调
cancel ull 取消后的回调

webpack 可视化分析

从这里开始,咱们开始进行 webpack 优化打包。首先咱们来分析一下 webpack 打包性能瓶颈,找出问题所在,而后才能对症下药。此时就用到 webpack-bundle-analyzer 了。 一、安装依赖

npm install webpack-bundle-analyzer -D
复制代码

二、在 vue.config.js 配置

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

打包后,咱们能够看到这样一份依赖图

从以上的界面中,咱们能够获得如下信息:

  • 打包出的文件中都包含了什么,以及模块之间的依赖关系
  • 每一个文件的大小在整体中的占比,找出较大的文件,思考是否有替换方案,是否使用了它包含了没必要要的依赖?
  • 是否有重复的依赖项,对此能够如何优化?
  • 每一个文件的压缩后的大小。

CDN 资源优化

CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,经过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,下降网络拥塞,提升用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。

随着项目越作越大,依赖的第三方 npm 包愈来愈多,构建以后的文件也会愈来愈大。再加上又是单页应用,这就会致使在网速较慢或者服务器带宽有限的状况出现长时间的白屏。此时咱们可使用 CDN 的方法,优化网络加载速度。

一、将 vue、vue-router、vuex、axios 这些 vue 全家桶的资源,所有改成经过 CDN 连接获取,在 index.html 里插入 相应连接。

<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>
复制代码

二、在 vue.config.js 配置 externals 属性

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

三、卸载相关依赖的 npm 包

npm uninstall  vue vue-router vuex axios
复制代码

此时启动项目运行就能够了。咱们在控制台就能发现项目加载了以上四个 CDN 资源。

不过如今有很多声音说,vue 全家桶加载 CDN 资源其实做用并不大,并且公共的 CDN 资源也没有 npm 包那么稳定,这个就见仁见智了。因此我在源码时新建的分支作这个优化。当项目较小的就不考虑 CDN 优化了。

固然,当引入其余较大第三方资源,好比 echarts,AMAP(高德地图),采用 CDN 资源仍是颇有必要的。

gZip 加速优化

全部现代浏览器都支持 gzip 压缩,启用 gzip 压缩可大幅缩减传输资源大小,从而缩短资源下载时间,减小首次白屏时间,提高用户体验。

gzip 对基于文本格式文件的压缩效果最好(如:CSS、JavaScript 和 HTML),在压缩较大文件时每每可实现高达 70-90% 的压缩率,对已经压缩过的资源(如:图片)进行 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, // 对超过10kb的数据进行压缩
        deleteOriginalAssets: false, // 是否删除原文件
      })
    )
  }
}
复制代码

首页添加骨架屏

随着 SPA 在前端界的逐渐流行,单页面应用不可避免地给首页加载带来压力,此时良好的首页用户体验相当重要。不少 APP 采用了“骨架屏”的方式去展现未加载内容,给予了用户面目一新的体验。

所谓的骨架屏,就是在页面内容未加载完成的时候,先使用一些图形进行占位,待内容加载完成以后再把它替换掉。在这个过程当中用户会感知到内容正在逐渐加载并即将呈现,下降了“白屏”的不良体验。

本文采用vue-skeleton-webpack-plugin插件为单页面应用注入骨架屏。

一、在src的common文件夹下面建立了Skeleton1.vue,Skeleton2.vue,具体的结构和样式自行设计,此处省略一万字。。。。

二、在同级目录下新建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' },
        ],
      },
    })
  )
}
复制代码

此时从新加载页面就能够看到咱们的骨架屏了。注意:必定要配置样式分离extract: true

推荐文章

关注的个人公众号不按期分享前端知识,与您一块儿进步!

相关文章
相关标签/搜索