技术栈vue-cli4 webpack4 vant axios less postcss-px2rem
源码 github.com/Michael-lzg…
// 安装依赖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-cli4脚手架
- vant按需引入
- 移动端rem适配
- axios拦截封装
- util工具类函数封装
- vue-router配置
- 登录权限校验
- 多环境变量配置
- vue.config.js配置
- toast组件封装
- dialog组件封装
- 跨域代理设置
- webpack打包可视化分析
- CDN资源优化
- gzip打包优化
- 首页添加骨架屏
关于更多的webpack优化方法可参考 github.com/Michael-lzg…
配置 vant
vant 是一套轻量、可靠的移动端 Vue 组件库非常适合基于 vue 技术栈的移动端开发。在过去很长的一段时间内本人用的移动端 UI 框架都是 vux。后来由于 vux 不支持 vue-cli3就转用了 vant不得不说无论是在交互体验上还是代码逻辑上vant 都比 vux 好很多而且 vant 的坑比较少。
对于第三方 UI 组件如果是全部引入的话比如会造成打包体积过大加载首页白屏时间过长的问题所以按需加载非常必要。vant 也提供了按需加载的方法。babel-plugin-import 是一款 babel 插件它会在编译过程中将 import 的写法自动转换为按需引入的方式。
1、安装依赖
npm i babel-plugin-import -D
2、配置 .babelrc 或者 babel.config.js 文件
// 在.babelrc 中添加配置{ "plugins": [ ["import", { "libraryName": "vant", "libraryDirectory": "es", "style": true }] ]}// 对于使用 babel7 的用户可以在 babel.config.js 中配置module.exports { plugins: [ [import, { libraryName: vant, libraryDirectory: es, style: true }, vant] ]};
3、按需引入
你可以在代码中直接引入 Vant 组件插件会自动将代码转化为方式二中的按需引入形式
import Vue from vueimport { Button } from vantVue.use(Button)
rem 适配
移动端适配是开发过程中不得不面对的事情。在此我们使用 postcss 中的 px2rem-loader将我们项目中的 px 按一定比例转化 rem这样我们就可以对着蓝湖上的标注写 px 了。
我们将 html 字跟字体设置为 100px很多人选择设置为 375px但是我觉得这样换算出来的 rem 不够精确而且我们在控制台上调试代码的时候无法很快地口算得出它本来的 px 值。如果设置 1rem100px这样我们看到的 0.16rem0.3rem 就很快得算出原来是 16px30px 了。
具体步骤如下
1、安装依赖
npm install px2rem-loader --save-dev
2、在 vue.config.js 进行如下配置
css: { // css预设器配置项 loaderOptions: { postcss: { plugins: [ require(postcss-px2rem)({ remUnit: 100 }) ] } } },
3、在 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 请求封装
1、设置请求拦截和响应拦截
const PRODUCT_URL https://xxxx.comconst MOCK_URL http://xxxx.comlet http axios.create({ baseURL: process.env.NODE_ENV production ? PRODUCT_URL : MOCK_URL,})// 请求拦截器http.interceptors.request.use( (config) > { // 设置tokenContent-Type var token sessionStorage.getItem(token) config.headers[token] token config.headers[Content-Type] application/json;charsetUTF-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) })
2、封装 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 }
3、把 getpost 方法挂载到 vue 实例上。
// main.jsimport { get, post } from ./js/ajaxVue.prototype.$http { get, post }
工具类函数封装
1、添加方法到 vue 实例的原型链上
export default { install (Vue, options) { Vue.prototype.util { method1(val) { ... }, method2 (val) { ... }, }}
2、在 main.js 通过 vue.use()注册
import utils from ./js/utilsVue.use(utils)
vue-router 配置
平时很多人对 vue-router 的配置可配置了 path 和 component实现了路由跳转即可。其实 vue-router 可做的事情还有很多比如
- 路由懒加载配置
- 改变单页面应用的 title
- 登录权限校验
- 页面缓存配置
路由懒加载配置
Vue 项目中实现路由按需加载(路由懒加载)的 3 中方式
// 1、Vue异步组件技术{ 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)}
本项目采用的是第二种方式为了后续 webpack 打包优化。
改变单页面应用的 title
由于单页面应用只有一个 html所有页面的 title 默认是不会改变的但是我们可以才路由配置中加入相关属性再在路由守卫中通过 js 改变页面的 title
router.beforeEach((to, from, next) > { document.title to.meta.title})
登录权限校验
在应用中通常会有以下的场景比如商城有些页面是不需要登录即可访问的如首页商品详情页等都是用户在任何情况都能看到的但是也有是需要登录后才能访问的如个人中心购物车等。此时就需要对页面访问进行控制了。
此外像一些需要记录用户信息和登录状态的项目也是需要做登录权限校验的以防别有用心的人通过直接访问页面的 url 打开页面。
此时。路由守卫可以帮助我们做登录校验。具体如下
1、配置路由的 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 }, },]
2、在路由首页进行判断。当to.meta.auth为true(需要登录)且不存在登录信息缓存时需要重定向去登录页面
router.beforeEach((to, from, next) > { document.title to.meta.title const userInfo sessionStorage.getItem(userInfo) || null if (!userInfo /login) } else { next() }})
页面缓存配置
项目中总有一些页面我们是希望加载一次就缓存下来的此时就用到 keep-alive 了。keep-alive 是 Vue 提供的一个抽象组件用来对组件进行缓存从而节省性能由于是一个抽象组件所以在 v 页面渲染完毕后不会被渲染成一个 DOM 元素。
1、通过配置路由的 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 }, },]
2、在 app.vue 做缓存判断
多环境变量配置
首先我们先来了解一下环境变量一般情况下我们的项目会有三个环境本地环境(development)测试环境(test)生产环境(production)我们可以在项目根目录下建三个配置环境变量的文件.env.development.env.test.env.production
环境变量文件中只包含环境变量的“键值”对
NODE_ENV productionVUE_APP_ENV production // 只有VUE_APP开头的环境变量可以在项目代码中直接使用
除了自定义的 VUEAPP*变量之外还有两个可用的变量
- NODE_ENV : "development"、"production" 或 "test"中的一个。具体的值取决于应用运行的模式。
- BASE_URL : 和 vue.config.js 中的 publicPath 选项相符即你的应用会部署到的基础路径。
下面开始配置我们的环境变量
1、在项目根目录中新建.env.*
- .env.development 本地开发环境配置
NODE_ENVdevelopmentVUE_APP_ENV development
- env.staging 测试环境配置
NODE_ENVproductionVUE_APP_ENV staging
- env.production 正式环境配置
NODE_ENVproductionVUE_APP_ENV production
为了在不同环境配置更多的变量我们在 src 文件下新建一个 config/index
// 根据环境引入不同配置 process.env.NODE_ENVconst 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请求地址}
2、配置打包命令
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 配置我们项目的东西。主要包括
- 打包后文件输出位置
- 关闭生产环境 souecemap
- 配置 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 了。 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()) }}
打包后我们可以看到这样一份依赖图

从以上的界面中我们可以得到以下信息
- 打包出的文件中都包含了什么以及模块之间的依赖关系
- 每个文件的大小在总体中的占比找出较大的文件思考是否有替换方案是否使用了它包含了不必要的依赖
- 是否有重复的依赖项对此可以如何优化
- 每个文件的压缩后的大小。
CDN 资源优化
CDN 的全称是 Content Delivery Network即内容分发网络。CDN 是构建在网络之上的内容分发网络依靠部署在各地的边缘服务器通过中心平台的负载均衡、内容分发、调度等功能模块使用户就近获取所需内容降低网络拥塞提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。
随着项目越做越大依赖的第三方 npm 包越来越多构建之后的文件也会越来越大。再加上又是单页应用这就会导致在网速较慢或者服务器带宽有限的情况出现长时间的白屏。此时我们可以使用 CDN 的方法优化网络加载速度。
1、将 vue、vue-router、vuex、axios 这些 vue 全家桶的资源全部改为通过 CDN 链接获取在 index.html 里插入 相应链接。
2、在 vue.config.js 配置 externals 属性
module.exports { ··· externals: { vue: Vue, vuex: Vuex, vue-router: VueRouter, axios:axios } }
3、卸载相关依赖的 npm 包
npm uninstall vue vue-router vuex axios
此时启动项目运行就可以了。我们在控制台就能发现项目加载了以上四个 CDN 资源。
不过现在有不少声音说vue 全家桶加载 CDN 资源其实作用并不大而且公共的 CDN 资源也没有 npm 包那么稳定这个就见仁见智了。所以我在源码时新建的分支做这个优化。当项目较小的就不考虑 CDN 优化了。
当然当引入其他较大第三方资源比如 echartsAMAP(高德地图)采用 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插件为单页面应用注入骨架屏。
1、在src的common文件夹下面创建了Skeleton1.vueSkeleton2.vue具体的结构和样式自行设计此处省略一万字。。。。
2、在同级目录下新建entry-skeleton.js
import Vue from vueimport Skeleton1 from ./Skeleton1import Skeleton2 from ./Skeleton2export default new Vue({ components: { Skeleton1, Skeleton2 }, template:
在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
作者lzg9527链接https://juejin.im/post/5eb766296fb9a0432f0ff8c7来源掘金著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。
【感谢龙石为本站提供数据中台建设http://www.longshidata.com/pages/government.html】