作者:花花小仙女
转发链接:https://mp.weixin.qq.com/s/c9uAYkWJu-zvKfELh_3V0A
基于 vue-cli4.0+webpack 4+vant ui + sass+ rem 适配方案+axios 封装,构建手机端模板脚手架,开箱即用,让开发变得更大略。

先夸一下自己,收到了很多人的好评。这次好好整理一文,你们的鼓励便是我提高的动力。
github:https://github.com/sunniejs/vue-h5-template
Node 版本哀求Vue CLI它须要 Node.js 8.9 或更高版本 (推举 8.11.0+)。你可以利用 nvm 或nvm-windows 在同一台电脑中管理多个 Node 版本。
本示例 Node.js 12.14.1
启动项目gitclonehttps://github.com/sunniejs/vue-h5-template.gitcdvue-h5-templatenpminstallnpmrunserve
目录√ Vue-cli4√ 配置多环境变量√ rem 适配方案√ VantUI 组件按需加载√ Sass 全局样式√ Vuex 状态管理√ Axios 封装及接口管理√ Vue-router√ Webpack 4 vue.config.js 根本配置√ 配置 proxy 跨域√ 配置 alias 别名√ 配置 打包剖析√ 配置 externals 引入 cdn 资源√ 去掉 console.log√ splitChunks 单独打包第三方模块√ 添加 IE 兼容√ Eslint+Pettier 统一开拓规范✅ 配置多环境变量
package.json 里的 scripts 配置 serve stage build,通过 --mode xxx 来实行不同环境
通过 npm run serve 启动本地 , 实行 development通过 npm run stage 打包测试 , 实行 staging通过 npm run build 打包正式 , 实行 production"scripts":{"serve":"vue-cli-serviceserve--open","stage":"vue-cli-servicebuild--modestaging","build":"vue-cli-servicebuild",}
配置先容
以 VUE_APP_ 开头的变量,在代码中可以通过 process.env.VUE_APP_ 访问。 比如,VUE_APP_ENV = 'development' 通过process.env.VUE_APP_ENV 访问。 除了 VUE_APP_ 变量之外,在你的运用代码中始终可用的还有两个分外的变量NODE_ENV 和BASE_URL
在项目根目录中新建.env.
.env.development 本地开拓环境配置NODE_ENV='development'#muststartwithVUE_APP_VUE_APP_ENV='development'
.env.staging 测试环境配置
NODE_ENV='production'#muststartwithVUE_APP_VUE_APP_ENV='staging'
.env.production 正式环境配置
NODE_ENV='production'#muststartwithVUE_APP_VUE_APP_ENV='production'
这里我们并没有定义很多变量,只定义了根本的 VUE_APP_ENV development staging production变量我们统一在 src/config/env..js 里进行管理。
这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去 config 下新建三个对应的文件呢?修正起来方便,不需 要重启项目,符合开拓习气。
config/index.js
//根据环境引入不同配置process.env.NODE_ENVconstconfig=require('./env.'+process.env.VUE_APP_ENV)module.exports=config
配置对应环境的变量,拿本地环境文件 env.development.js 举例,用户可以根据需求修正
//本地环境配置module.exports={title:'vue-h5-template',baseUrl:'http://localhost:9018',//项目地址baseApi:'https://test.xxx.com/api',//本地api要求地址APPID:'xxx',APPSECRET:'xxx'}
根据环境不同,变量就会不同了
//根据环境不同引入不同baseApi地址import{baseApi}from'@/config'console.log(baseApi)
✅ rem 适配方案
不用担心,项目已经配置好了 rem 适配, 下免仅做先容:
Vant 中的样式默认利用px作为单位,如果须要利用rem单位,推举利用以下两个工具:
postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 remlib-flexible 用于设置 rem 基准值PostCSS 配置下面供应了一份基本的 postcss 配置,可以在此配置的根本上根据项目需求进行修正
//https://github.com/michael-ciniawsky/postcss-load-configmodule.exports={plugins:{autoprefixer:{overrideBrowserslist:['Android4.1','iOS7.1','Chrome>31','ff>31','ie>=8']},'postcss-pxtorem':{rootValue:37.5,propList:['']}}}
更多详细信息:vant
新手必看,老鸟跳过
很多小伙伴会问我,适配的问题。
我们知道 1rem 即是html 根元素设定的 font-size 的 px 值。Vant UI 设置 rootValue: 37.5,你可以看到在 iPhone 6 下 看到 (1rem 即是 37.5px):
<htmldata-dpr="1"style="font-size:37.5px;"></html>
切换不同的机型,根元素可能会有不同的font-size。当你写 css px 样式时,会被程序换算成 rem 达到适配。
由于我们用了 Vant 的组件,须要按照 rootValue: 37.5 来写样式。
举个例子:设计给了你一张 750px 1334px 图片,在 iPhone6 上铺满屏幕,其他机型适配。
当rootValue: 70 , 样式 width: 750px;height: 1334px; 图片会撑满 iPhone6 屏幕,这个时候切换其他机型,图片也会随着撑 满。当rootValue: 37.5 的时候,样式 width: 375px;height: 667px; 图片会撑满 iPhone6 屏幕。也便是 iphone 6 下 375px 宽度写 CSS。其他的你就可以根据你设计图,去写对应的样式就可以了。
当然,想要撑满屏幕你可以利用 100%,这里只是举例解释。
<imgclass="image"src="https://imgs.solui.cn/weapp/logo.png"/><style>/rootValue:75/.image{width:750px;height:1334px;}/rootValue:37.5/.image{width:375px;height:667px;}</style>
✅ VantUI 组件按需加载
项目采 用 Vant 自动按需引入组件 (推举) 下 面安装插件先容:
babel-plugin-import 是一款 babel 插件,它会在编译过程中将import 的写法自动转换为按需引入的办法
安装插件npmibabel-plugin-import-D
在babel.config.js 设置
//对付利用babel7的用户,可以在babel.config.js中配置constplugins=[['import',{libraryName:'vant',libraryDirectory:'es',style:true},'vant']]module.exports={presets:[['@vue/cli-plugin-babel/preset',{useBuiltIns:'usage',corejs:3}]],plugins}
利用组件
项目在 src/plugins/vant.js 下统一管理组件,用哪个引入哪个,无需在页面里重复引用
//按需全局引入vant组件importVuefrom'vue'import{Button,List,Cell,Tabbar,TabbarItem}from'vant'Vue.use(Button)Vue.use(Cell)Vue.use(List)Vue.use(Tabbar).use(TabbarItem)
✅ Sass 全局样式
首先 你可能会碰着 node-sass 安装不堪利,别放弃多试几次!
!
!
目录构造,在 src/assets/css/文件夹下包含了三个文件
├──assets│├──css││├──index.scss#全局通用样式││├──mixin.scss#全局mixin││└──variables.scss#全局变量
每个页面自己对应的样式都写在自己的 .vue 文件之中
<stylelang="scss">/globalstyles/</style><stylelang="scss"scoped>/localstyles/</style>
vue.config.js 配置注入 sass 的 mixin variables 到全局,不须要手动引入 ,配置$cdn通过变量形式引入 cdn 地址
constIS_PROD=['production','prod'].includes(process.env.NODE_ENV)constdefaultSettings=require('./src/config/index.js')module.exports={css:{extract:IS_PROD,sourceMap:false,loaderOptions:{scss:{//注入`sass`的`mixin``variables`到全局,$cdn可以配置图片cdn//详情:https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loadersprependData:`@import"assets/css/mixin.scss";@import"assets/css/variables.scss";$cdn:"${defaultSettings.$cdn}";`}}}}
在 main.js 中引用全局样式(创造在上面的,prependData 里设置@import "assets/css/index.scss";并没有运用全局样式这里在 main.js 引入)
设置 js 中可以访问 $cdn,.vue 文件中利用this.$cdn访问
//引入全局样式import'@/assets/css/index.scss'//设置js中可以访问$cdn//引入cdnimport{$cdn}from'@/config'Vue.prototype.$cdn=$cdn
在 css 和 js 利用
<script>console.log(this.$cdn)</script><stylelang="scss"scoped>.logo{width:120px;height:120px;background:url($cdn+'/weapp/logo.png')center/containno-repeat;}</style>
✅ Vuex 状态管理
目录构造
├──store│├──modules││└──app.js│├──index.js│├──getters.js
main.js 引入
importVuefrom'vue'importAppfrom'./App.vue'importstorefrom'./store'newVue({el:'#app',router,store,render:h=>h(App)})
利用
<script>import{mapGetters}from'vuex'exportdefault{computed:{...mapGetters(['userName'])},methods:{//Action通过store.dispatch方法触发doDispatch(){this.$store.dispatch('setUserName','真乖,赶紧关注公众年夜众号,组织都在等你~')}}}</script>
✅ Vue-router
本案例采取 hash 模式,开拓者根据需求修正 mode base
把稳:如果你利用了 history 模式,vue.config.js 中的 publicPath 要做对应的修正
utils/request.js 封装 axios ,开拓者须要根据后台接口做修正。 在src/api 文件夹下统一管理接口 如果你的 Vue Router 模式是 hash 如果你的 Vue Router 模式是 history 这里的 publicPath 和你的 Vue Routerbase 保持一贯 如果你的项目须要跨域设置,你须要打开 vue.config.js proxy 注释 并且配置相应参数 把稳:你还须要将 src/config/env.development.js 里的 baseApi 设置成 '/' 利用 例如: src/api/home.js 这个版本 CDN 不再引入,我测试了一下利用引入 CDN 和不该用,不该用会比利用韶光少。网上不少文章测试 CDN 速率快,这个开拓者可 以实际测试一下。 其余项目中利用的是公共 CDN 不稳定,域名解析也是须要韶光的(如果你要利用请只管即便利用同一个域名) 由于页面每次碰着<script>标签都会停下来解析实行,以是该当尽可能减少<script>标签的数量 HTTP要求存在一定的开销,100K 的文件比 5 个 20K 的文件下载的更快,以是较少脚本数量也是很有必要的 暂时还没有研究放到自己的 cdn 做事器上。 在 public/index.html 中添加 保留了测试环境和本地环境的 console.log 在 babel.config.js 中配置 之前的办法 会报 @babel/polyfill is deprecated. Please, use required parts of core-js andregenerator-runtime/runtime separately @babel/polyfill 废弃,利用 core-js 和 regenerator-runtime 在 main.js 中添加 配置 babel.config.js 该文件 .prettierrc 里写 属于你的 pettier 规则 vue-cli4-config: https://github.com/staven630/vue-cli4-config vue-element-admin:https://github.com/PanJiaChen/vue-element-admin mdnice:https://mdnice.com 作者:花花小仙女 转发链接:https://mp.weixin.qq.com/s/c9uAYkWJu-zvKfELh_3V0AimportVuefrom'vue'importRouterfrom'vue-router'Vue.use(Router)exportconstrouter=[{path:'/',name:'index',component:()=>import('@/views/home/index'),//路由✅ Axios 封装及接口管理
importaxiosfrom'axios'importstorefrom'@/store'import{Toast}from'vant'//根据环境不同引入不同api地址import{baseApi}from'@/config'//createanaxiosinstanceconstservice=axios.create({baseURL:baseApi,//url=baseapiurl+requesturlwithCredentials:true,//sendcookieswhencross-domainrequeststimeout:5000//requesttimeout})//request拦截器requestinterceptorservice.interceptors.request.use(config=>{//不通报默认开启loadingif(!config.hideloading){//loadingToast.loading({forbidClick:true})}if(store.getters.token){config.headers['X-Token']=''}returnconfig},error=>{//dosomethingwithrequesterrorconsole.log(error)//fordebugreturnPromise.reject(error)})//respone拦截器service.interceptors.response.use(response=>{Toast.clear()constres=response.dataif(res.status&&res.status!==200){//登录超时,重新登录if(res.status===401){store.dispatch('FedLogOut').then(()=>{location.reload()})}returnPromise.reject(res||'error')}else{returnPromise.resolve(res)}},error=>{Toast.clear()console.log('err'+error)//fordebugreturnPromise.reject(error)})exportdefaultservice
接口管理importqsfrom'qs'//axiosimportrequestfrom'@/utils/request'//userapi//用户信息exportfunctiongetUserInfo(params){returnrequest({url:'/user/userinfo',method:'get',data:qs.stringify(params),hideloading:true//隐蔽loading组件})}
如何调用//要求接口import{getUserInfo}from'@/api/user.js'constparams={user:'sunnie'}getUserInfo(params).then(()=>{}).catch(()=>{})
✅ Webpack 4 vue.config.js 根本配置publicPath:'./',
publicPath:'/app/',
constIS_PROD=['production','prod'].includes(process.env.NODE_ENV)module.exports={publicPath:'./',//署运用包时的基本 URL。vue-router hash 模式利用// publicPath:'/app/', //署运用包时的基本 URL。 vue-router history模式利用outputDir:'dist',//生产环境构建文件的目录assetsDir:'static',//outputDir的静态资源(js、css、img、fonts)目录lintOnSave:false,productionSourceMap:false,//如果你不须要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。devServer:{port:9020,//端口号open:false,//启动后打开浏览器overlay:{//当涌现编译器缺点或警告时,在浏览器中显示全屏覆盖层warnings:false,errors:true}//...}}
✅ 配置 proxy 跨域module.exports={devServer:{//....proxy:{//配置跨域'/api':{target:'https://test.xxx.com',//接口的域名//ws:true,//是否启用websocketschangOrigin:true,//开启代理,在本地创建一个虚拟做事端pathRewrite:{'^/api':'/'}}}}}
exportfunctiongetUserInfo(params){returnrequest({url:'/api/userinfo',method:'get',data:qs.stringify(params)})}
✅ 配置 alias 别名constpath=require('path')constresolve=dir=>path.join(__dirname,dir)constIS_PROD=['production','prod'].includes(process.env.NODE_ENV)module.exports={chainWebpack:config=>{//添加别名config.resolve.alias.set('@',resolve('src')).set('assets',resolve('src/assets')).set('api',resolve('src/api')).set('views',resolve('src/views')).set('components',resolve('src/components'))}}
✅ 配置 打包剖析constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPluginmodule.exports={chainWebpack:config=>{//打包剖析if(IS_PROD){config.plugin('webpack-report').use(BundleAnalyzerPlugin,[{analyzerMode:'static'}])}}}
npmrunbuild
✅ 配置 externals 引入 cdn 资源constdefaultSettings=require('./src/config/index.js')constname=defaultSettings.title||'vuemobiletemplate'constIS_PROD=['production','prod'].includes(process.env.NODE_ENV)//externalsconstexternals={vue:'Vue','vue-router':'VueRouter',vuex:'Vuex',vant:'vant',axios:'axios'}//CDN外链,会插入到index.html中constcdn={//开拓环境dev:{css:[],js:[]},//生产环境build:{css:['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'],js:['https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js','https://cdn.jsdelivr.net/npm/vue-router@3.1.5/dist/vue-router.min.js','https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js','https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js','https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js']}}module.exports={configureWebpack:config=>{config.name=name//为生产环境修正配置...if(IS_PROD){//externalsconfig.externals=externals}},chainWebpack:config=>{/添加CDN参数到htmlWebpackPlugin配置中/config.plugin('html').tap(args=>{if(IS_PROD){args[0].cdn=cdn.build}else{args[0].cdn=cdn.dev}returnargs})}}
<!--利用CDN的CSS文件--><%for(variinhtmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css){%><linkhref="<%=htmlWebpackPlugin.options.cdn.css[i]%>"rel="preload"as="style"/><linkhref="<%=htmlWebpackPlugin.options.cdn.css[i]%>"rel="stylesheet"/><%}%><!--利用CDN加速的JS文件,配置在vue.config.js下--><%for(variinhtmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js){%><scriptsrc="<%=htmlWebpackPlugin.options.cdn.js[i]%>"></script><%}%>
✅ 去掉 console.lognpmi-Dbabel-plugin-transform-remove-console
//获取VUE_APP_ENV非NODE_ENV,测试环境依然consoleconstIS_PROD=['production','prod'].includes(process.env.VUE_APP_ENV)constplugins=[['import',{libraryName:'vant',libraryDirectory:'es',style:true},'vant']]//去除console.logif(IS_PROD){plugins.push('transform-remove-console')}module.exports={presets:[['@vue/cli-plugin-babel/preset',{useBuiltIns:'entry'}]],plugins}
✅ splitChunks 单独打包第三方模块module.exports={chainWebpack:config=>{config.when(IS_PROD,config=>{config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin',[{//将runtime作为内联引入不单独存在inline:/runtime\..\.js$/}]).end()config.optimization.splitChunks({chunks:'all',cacheGroups:{//cacheGroups下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块commons:{name:'chunk-commons',test:resolve('src/components'),minChunks:3,//被至少用三次以上打包分离priority:5,//优先级reuseExistingChunk:true//表示是否利用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新天生新的。},node_vendors:{name:'chunk-libs',chunks:'initial',//只打包初始时依赖的第三方test:/[\\/]node_modules[\\/]/,priority:10},vantUI:{name:'chunk-vantUI',//单独将vantUI拆包priority:20,//数字大权重到,知足多个cacheGroups的条件时候分到权重高的test:/[\\/]node_modules[\\/]_?vant(.)/}}})config.optimization.runtimeChunk('single')})}}
✅ 添加 IE 兼容npmi--savecore-jsregenerator-runtime
//兼容IE//https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfillimport'core-js/stable'import'regenerator-runtime/runtime'
constplugins=[]module.exports={presets:[['@vue/cli-plugin-babel/preset',{useBuiltIns:'usage',corejs:3}]],plugins}
✅ Eslint+Pettier 统一开拓规范 {"printWidth":120,"tabWidth":2,"singleQuote":true,"trailingComma":"none","semi":false,"wrap_line_length":120,"wrap_attributes":"auto","proseWrap":"always","arrowParens":"avoid","bracketSpacing":false,"jsxBracketSameLine":true,"useTabs":false,"overrides":[{"files":".prettierrc","options":{"parser":"json"}}]}
鸣谢