這是基於 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開發經常使用技巧(乾貨滿滿哦!)