vue-cli3 从搭建到优化

前言
github地址: github.com/LeeStaySmal… (完整分支:optimize分支)

demo地址: vue-project-demo.eloco.cn

安装与初始化架构
安装

node >= 8.9 推荐:8.11.0 +

安装:npm install -g @vue/cli

检查:vue --version

如果已安装旧版本,需要先npm uninstall vue-cli -g 卸载掉旧版本。

初始化架构

创建:vue create project-name
image
注:项目名称不能驼峰命名。

选择一个预设(这里我选择更多功能):
image
选择需要安装的(Babel、Router、Vuex、Pre-processors、Linter / Formatter):
image
是否使用history路由模式(Yes):
image
选择css 预处理器(Sass/SCSS):

image
选择eslint 配置(ESLint + Standard config):

image
选择什么时候执行eslint校验(Lint on save):

image
选择以什么样的形式配置以上所选的功能(In dedicated config files):

image
是否将之前的设置保存为一个预设模板(y):
image
如果选择 y 会让输入名称,以便下次直接使用,否则直接开始初始化项目。

最后,看一下生成的基本架构目录:
image
在项目中优雅的使用svg
首先在/src/components 创建 SvgIcon.vue:
image
参考:未来必热:SVG Sprite技术介绍 - 张鑫旭

在src/下创建 icons文件夹,以及在其下创建svg文件夹用于存放svg文件,创建index.js作为入口文件:
image
编写index.js 的脚本:

import Vue from ‘vue’
import SvgIcon from ‘@/components/SvgIcon.vue’ // svg组件

// 全局注册
Vue.component(‘svg-icon’, SvgIcon)

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context(’./svg’, false, /.svg$/)
requireAll(req)
复制代码
使用svg-sprite-loader对项目中使用的svg进行处理:
npm install svg-sprite-loader --save-dev;

修改默认的webpack配置, 在项目根目录创建vue.config.js,代码如下;

const path = require(‘path’)

function resolve(dir) {
return path.join(__dirname, ‘./’, dir)
}

module.exports = {
chainWebpack: config => {
// svg loader
const svgRule = config.module.rule(‘svg’) // 找到svg-loader
svgRule.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后
svgRule.exclude.add(/node_modules/) // 正则匹配排除node_modules目录
svgRule // 添加svg新的loader处理
.test(/.svg$/)
.use(‘svg-sprite-loader’)
.loader(‘svg-sprite-loader’)
.options({
symbolId: ‘icon-[name]’
})

// 修改images loader 添加svg处理
const imagesRule = config.module.rule('images')
imagesRule.exclude.add(resolve('src/icons'))
config.module
  .rule('images')
  .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)

}
}
复制代码
最后,在main.js 中引入import '@/icons’即可;
// 使用示例

复制代码
PS:至于svg ,个人比较建议使用阿里开源的图标库 iconFont

axios封装api、模块化vuex
axios篇
项目中安装axios: npm install axios;
在src目录下创建utils/, 并创建request.js用来封装axios,上代码:
import axios from ‘axios’

// 创建axios 实例
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: 10000 // 请求超时时间
})

// request 拦截器
service.interceptors.request.use(
config => {
// 这里可以自定义一些config 配置

return config

},
error => {
// 这里处理一些请求出错的情况

console.log(error)
Promise.reject(error)

}
)

// response 拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 这里处理一些response 正常放回时的逻辑

return res

},
error => {
// 这里处理一些response 出错时的逻辑

return Promise.reject(error)

}
)

export default service
复制代码
既然要使用axios ,必不可少的需要配置环境变量以及需要请求的地址,这里可以简单的修改poackage.json:
“scripts”: {
“dev”: “vue-cli-service serve --project-mode dev”,
“test”: “vue-cli-service serve --project-mode test”,
“pro”: “vue-cli-service serve --project-mode pro”,
“pre”: “vue-cli-service serve --project-mode pre”,
“build:dev”: “vue-cli-service build --project-mode dev”,
“build:test”: “vue-cli-service build --project-mode test”,
“build:pro”: “vue-cli-service build --project-mode pro”,
“build:pre”: “vue-cli-service build --project-mode pre”,
“build”: “vue-cli-service build”,
“lint”: “vue-cli-service lint”
},
复制代码
同时修改vue.config.js:

const path = require(‘path’)

function resolve(dir) {
return path.join(__dirname, ‘./’, dir)
}

module.exports = {
chainWebpack: config => {
// 这里是对环境的配置,不同环境对应不同的BASE_API,以便axios的请求地址不同
config.plugin(‘define’).tap(args => {
const argv = process.argv
const mode = argv[argv.indexOf(’–project-mode’) + 1]
args[0][‘process.env’].MODE = "${mode}"
args[0][‘process.env’].BASE_API = ‘“http://47.94.138.75:8000”’
return args
})

// svg loader
const svgRule = config.module.rule('svg') // 找到svg-loader
svgRule.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后
svgRule.exclude.add(/node_modules/) // 正则匹配排除node_modules目录
svgRule // 添加svg新的loader处理
  .test(/\.svg$/)
  .use('svg-sprite-loader')
  .loader('svg-sprite-loader')
  .options({
    symbolId: 'icon-[name]'
  })

// 修改images loader 添加svg处理
const imagesRule = config.module.rule('images')
imagesRule.exclude.add(resolve('src/icons'))
config.module
  .rule('images')
  .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)

}
}
复制代码
如何使用? 我比较建议在src/下创建api目录,用来统一管理所有的请求,比如下面这样:
image
这样的好处是方便管理、后期维护,还可以和后端的微服务对应,建立多文件存放不同模块的api。剩下的就是你使用到哪个api时,自己引入便可。

拓展:服务端的cors设置
牵涉到跨域,这里采用cors,很多朋友在面试中经常会被问到cors的实现原理,这个网上有很多理论大多是这样讲的:

image
其实,这样理解很抽象,服务器端到底是怎么做验证的?

这里大家可以通俗的理解为后端在接收前端的request请求的时候,会有一个request拦截器,像axios response拦截器一样。下面以php lumen框架为例,来深入理解一下这个流程:

<?php namespace App\Http\Middleware; use App\Http\Utils\Code; use Closure; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; class CorsMiddleware { private $headers; /** * 全局 : 解决跨域 * @param $request * @param \Closure $next * @return mixed * @throws \HttpException */ public function handle($request, Closure $next) { //请求参数 Log::info('http request:'.json_encode(["request_all" => $request->all()])); $allowOrigin = [ 'http://47.94.138.75', 'http://localhost', ]; $Origin = $request->header("Origin"); $this->headers = [ 'Access-Control-Allow-Headers' => 'Origin,x-token,Content-Type', 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Credentials' => 'true',//允许客户端发送cookie 'Access-Control-Allow-Origin' => $Origin, //'Access-Control-Max-Age' => 120, //该字段可选,间隔2分钟验证一次是否允许跨域。 ]; //获取请求方式 if ($request->isMethod('options')) { if (in_array($Origin, $allowOrigin)) { return $this->setCorsHeaders(new Response(json_encode(['code' => Code::SUCCESS, "data" => 'success', "msg" => ""]), Code::SUCCESS)); } else { return new Response(json_encode('fail', 405)); } } $response = $next($request); //返回参数 Log::info('http response:'.json_encode($response)); return $this->setCorsHeaders($response); } /** * @param $response * @return mixed */ public function setCorsHeaders($response) { foreach ($this->headers as $key => $val) { $response->header($key, $val); } return $response; } } 复制代码 vuex 篇 如果创建项目的时候,选择了vuex,那么默认会在src目录下有一个store.js作为仓库文件。但在更多实际场景中,如果引入vuex,那么肯定避免不了分模块,先来看一下默认文件代码: import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { } }) 复制代码 那么现在改造一下,比如先划分出app、user两个模块,可以这样: import Vue from 'vue' import Vuex from 'vuex' import app from './store/modules/app' import user from './store/modules/user' import getters from './store/getters' Vue.use(Vuex) const store = new Vuex.Store({ modules: { app, user }, getters }) export default store 复制代码 在src/下创建store/目录: image app module 可以用来存储应用的状态,比如接下来要讲到的全局loading,或者控制第三方组件的全局大小,比如element ui中的全局组件size; user module 可以用来存储当前用户的信息; 当然,store 配合本地存储比较完美,这里采用js-cookie。 全局loading、合理利用vue router守卫 全局loading 上面说完了axios、vuex,现在结合之前说一下设置全局loading效果。 平常写代码每个请求之前一般都需要设置loading ,成功之后结束loading效果,这就迫使我们不得不写大量重复代码,如果不想这样做,可以结合axios和vuex统一做了。 首先,在说vuex的时候,我在src/下创建了一个store,现在就在store/modules/app.js 写这个Loading效果的代码; const app = { state: { requestLoading: 0 }, mutations: { SET_LOADING: (state, status) => { // error 的时候直接重置 if (status === 0) { state.requestLoading = 0 return } state.requestLoading = status ? ++state.requestLoading : --state.requestLoading } }, actions: { SetLoading ({ commit }, status) { commit('SET_LOADING', status) } } } export default app 复制代码 再来修改一下utils/request.js import axios from 'axios' import store from '@/store' // 创建axios 实例 const service = axios.create({ baseURL: process.env.BASE_API, // api的base_url timeout: 10000 // 请求超时时间 }) // request 拦截器 service.interceptors.request.use( config => { // 这里可以自定义一些config 配置 // loading + 1 store.dispatch('SetLoading', true) return config }, error => { // 这里处理一些请求出错的情况 // loading 清 0 setTimeout(function () { store.dispatch('SetLoading', 0) }, 300) console.log(error) Promise.reject(error) } ) // response 拦截器 service.interceptors.response.use( response => { const res = response.data // 这里处理一些response 正常放回时的逻辑 // loading - 1 store.dispatch('SetLoading', false) return res }, error => { // 这里处理一些response 出错时的逻辑 // loading - 1 store.dispatch('SetLoading', false) return Promise.reject(error) } ) export default service 复制代码 其次,在src/components/下创建 RequestLoading.vue 组件:
<% } %> 复制代码 最后去除main.js 中引入的import 'element-ui/lib/theme-chalk/index.css' OK ,现在执行一下build: image.png 可以看到,相对于 793.20KB,61.94k小了将近13倍!!! 把这个分支部署到服务器,话不多说,对比一下就好: image.png 使用Gzip 加速 引入 compression-webpack-plugin : npm i -D compression-webpack-plugin www.webpackjs.com/plugins/com… 修改vue.config.js,老规矩,上最全的代码: const path = require('path') const CompressionWebpackPlugin = require('compression-webpack-plugin') function resolve(dir) { return path.join(__dirname, './', dir) } // cdn预加载使用 const externals = { 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex': 'Vuex', 'axios': 'axios', 'element-ui': 'ELEMENT', 'js-cookie': 'Cookies', 'nprogress': 'NProgress' } const cdn = { // 开发环境 dev: { css: [ 'https://unpkg.com/element-ui/lib/theme-chalk/index.css', 'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css' ], js: [] }, // 生产环境 build: { css: [ 'https://unpkg.com/element-ui/lib/theme-chalk/index.css', 'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css' ], js: [ 'https://cdn.bootcss.com/vue/2.5.21/vue.min.js', 'https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js', 'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js', 'https://cdn.bootcss.com/axios/0.18.0/axios.min.js', 'https://unpkg.com/element-ui/lib/index.js', 'https://cdn.bootcss.com/js-cookie/2.2.0/js.cookie.min.js', 'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js' ] } } // 是否使用gzip const productionGzip = true // 需要gzip压缩的文件后缀 const productionGzipExtensions = ['js', 'css'] module.exports = { chainWebpack: config => { // 这里是对环境的配置,不同环境对应不同的BASE_API,以便axios的请求地址不同 config.plugin('define').tap(args => { const argv = process.argv const mode = argv[argv.indexOf('--project-mode') + 1] args[0]['process.env'].MODE = `"${mode}"` args[0]['process.env'].BASE_API = '"http://47.94.138.75:8000"' return args }) /** * 添加CDN参数到htmlWebpackPlugin配置中, 详见public/index.html 修改 */ config.plugin('html').tap(args => { if (process.env.NODE_ENV === 'production') { args[0].cdn = cdn.build } if (process.env.NODE_ENV === 'development') { args[0].cdn = cdn.dev } return args }) // svg loader const svgRule = config.module.rule('svg') // 找到svg-loader svgRule.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后 svgRule.exclude.add(/node_modules/) // 正则匹配排除node_modules目录 svgRule // 添加svg新的loader处理 .test(/\.svg$/) .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) // 修改images loader 添加svg处理 const imagesRule = config.module.rule('images') imagesRule.exclude.add(resolve('src/icons')) config.module .rule('images') .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/) }, // 修改webpack config, 使其不打包externals下的资源 configureWebpack: config => { const myConfig = {} if (process.env.NODE_ENV === 'production') { // 1. 生产环境npm包转CDN myConfig.externals = externals myConfig.plugins = [] // 2. 构建时开启gzip,降低服务器压缩对CPU资源的占用,服务器也要相应开启gzip productionGzip && myConfig.plugins.push( new CompressionWebpackPlugin({ test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), threshold: 8192, minRatio: 0.8 }) ) } if (process.env.NODE_ENV === 'development') { /** * 关闭host check,方便使用ngrok之类的内网转发工具 */ myConfig.devServer = { disableHostCheck: true } } // open: true, // hot: true // // https: true, // // proxy: { // // '/proxy': { // // target: 'http://47.94.138.75', // // // changeOrigin: true, // // pathRewrite: { // // '^/proxy': '' // // } // // } // // }, // } return myConfig } } 复制代码 再次运行build,我们会发现dist/下所有的.js和.css都会多出一个.js.gz、.css.gz的文件,这就是我们需要的压缩文件,可以看到最大的只有18.05KB,想想是不是比较激动... image.png 当然,这玩意还需要服务端支持,也就是配置nginx: gzip on; gzip_static on; gzip_min_length 1024; gzip_buffers 4 16k; gzip_comp_level 2; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml; gzip_vary off; gzip_disable "MSIE [1-6]\."; 复制代码 配置完重启nginx: image.png 配置成功的话,可以看到加载的是比较小的Gzip: image.png 在 response headers 里会有一个Content-Encoding:gzip image.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值