这是基于 vue-cli4 实现的移动端框架,其中包含项目经常使用的配置,组件封装及webpack优化方法,可供快速开发使用。css
技术栈:vue-cli4 + webpack4 + vant + axios + less + postcss-px2remhtml
// 安装依赖 npm install // 本地启动 npm run dev // 生产打包 npm run build
在一两年前,vue-cli3已经声驾到3.0+版本,可是因为旧项目一致习惯于vue-cli2的脚手架的使用,以前也写过一篇 搭建一个vue-cli的移动端H5开发模板 简单总结了一点移动端的开发技巧。vue
近日升级vue-cli脚手架才发现,这已经升级到4.0+版本了,以为不少必要在新的项目中使用vue-cli4进行开发,加上近来对webpack有了进一步理解,因此结合了vue-cli4和webpack搭建了一个移动端框架,以便开箱即用。 主要包括以下技术点:webpack
关于更多的webpack优化方法,可参考 github.com/Michael-lzg…ios
vant 是一套轻量、可靠的移动端 Vue 组件库,很是适合基于 vue 技术栈的移动端开发。在过去很长的一段时间内,本人用的移动端 UI 框架都是 vux。后来因为 vux 不支持 vue-cli3,就转用了 vant,不得不说,不管是在交互体验上,仍是代码逻辑上,vant 都比 vux 好不少,并且 vant 的坑比较少。git
对于第三方 UI 组件,若是是所有引入的话,好比会形成打包体积过大,加载首页白屏时间过长的问题,因此按需加载很是必要。vant 也提供了按需加载的方法。babel-plugin-import
是一款 babel 插件,它会在编译过程当中将 import 的写法自动转换为按需引入的方式。es6
一、安装依赖github
npm i babel-plugin-import -D
二、配置 .babelrc 或者 babel.config.js 文件web
// 在.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)
移动端适配是开发过程当中不得不面对的事情。在此,咱们使用 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() })
一、设置请求拦截和响应拦截
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 的配置可配置了 path 和 component,实现了路由跳转便可。其实 vue-router 可作的事情还有不少,好比
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 打包优化。
因为单页面应用只有一个 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.auth
为true
(须要登陆),且不存在登陆信息缓存时,须要重定向去登陆页面
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开头的环境变量能够在项目代码中直接使用
除了自定义的 VUE_APP_*变量以外,还有两个可用的变量:
下面开始配置咱们的环境变量
一、在项目根目录中新建.env.*
NODE_ENV='development' VUE_APP_ENV = 'development'
NODE_ENV='production' VUE_APP_ENV = 'staging'
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.js
,env.test.js
,env.production.js
,在里面配置须要的变量。
以 env.development.js 为例
module.exports = { baseUrl: 'http://localhost:8089', // 项目地址 baseApi: 'https://www.mock.com/api', // 本地api请求地址 }
二、配置打包命令
package.json 里的 scripts 不一样环境的打包命令
"scripts": { "dev": "vue-cli-service serve", "build": "vue-cli-service build", "test": "vue-cli-service build --mode test", }
vue-cli3 开始,新建的脚手架都须要咱们在 vue.config.js 配置咱们项目的东西。主要包括
此外,还有不少属于优化打包的配置,后面会一一道来。
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('删除成功') }, })
效果图以下
从这里开始,咱们开始进行 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 的全称是 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 对基于文本格式文件的压缩效果最好(如: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" /> <skeleton2 id="skeleton2" /> </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
搭建一个vue-cli的移动端H5开发模板
封装一个toast和dialog组件并发布到npm
从零开始构建一个webpack项目
总结几个webpack打包优化的方法
总结vue知识体系之高级应用篇
总结vue知识体系之实用技巧
总结vue知识体系之基础入门篇
总结移动端H5开发经常使用技巧(干货满满哦!)