項(xiàng)目地址 vue-cli3-project 歡迎 star
1. 創(chuàng)建一個(gè)vue項(xiàng)目
相信大部分人都已經(jīng)知道怎么創(chuàng)建項(xiàng)目的盏筐,可以跳過(guò)這一節(jié),看下一節(jié)。
1.1 安裝@vue/cli
# 全局安裝 vue-cli腳手架
npm install -g @vue/cli
等待安裝完成后開(kāi)始下一步
1.2 初始化項(xiàng)目
vue create vue-cli3-project
- 選擇一個(gè)預(yù)設(shè)
可以選擇默認(rèn)預(yù)設(shè),默認(rèn)預(yù)設(shè)包含了
babel
,eslint
我們選擇更多功能 Manually select features
回車后來(lái)到選擇插件
- 插件選擇
這邊選擇了(Babel斗幼、Router包颁、Vuex、Css預(yù)處理器咆耿、Linter / Formatter 格式檢查、Unit測(cè)試框架)
- 路由模式選擇
是否使用 history
模式的路由 (Yes)
- 選擇一個(gè)css預(yù)處理器 (Sass/SCSS)
- 選擇一個(gè)eslint配置
這邊選擇 ESLint + Standard config
爹橱,個(gè)人比較喜歡這個(gè)代碼規(guī)范
- 選擇什么時(shí)候進(jìn)行
eslint
校驗(yàn)
選擇(Lint on save)保存是檢查
如果你正在使用的vscode編輯器的話萨螺,可以配置eslint插件進(jìn)行代碼自動(dòng)格式化
-
選擇測(cè)試框架 (Mocha + Chai)
- 選擇將這些配置文件寫(xiě)入到什么地方 (In dedicated config files)
- 是否保存這份預(yù)設(shè)配置?(y)
選是的話愧驱,下次創(chuàng)建一個(gè)vue項(xiàng)目慰技,可以直接使用這個(gè)預(yù)設(shè)文件,而無(wú)需再進(jìn)行配置组砚。
等待依賴完成
2. 全局組件自動(dòng)注冊(cè)
在components
目錄下創(chuàng)建一個(gè)global
目錄吻商,里面放置一些需要全局注冊(cè)的組件。
index.js
作用只要是引入main.vue
糟红,導(dǎo)出組件對(duì)象
在
components
中創(chuàng)建一個(gè)index.js
艾帐,用來(lái)掃描全局對(duì)象并自動(dòng)注冊(cè)。
// components/index.js
import Vue from 'vue'
// 自動(dòng)加載 global 目錄下的 .js 結(jié)尾的文件
const componentsContext = require.context('./global', true, /\.js$/)
componentsContext.keys().forEach(component => {
const componentConfig = componentsContext(component)
/**
* 兼容 import export 和 require module.export 兩種規(guī)范
*/
const ctrl = componentConfig.default || componentConfig
Vue.component(ctrl.name, ctrl)
})
最后在入口文件main.js
中導(dǎo)入這個(gè)index.js
中就可以了
3.路由自動(dòng)引入
在 Vue
項(xiàng)目中使用路由盆偿,相信想熟的人已經(jīng)很熟悉怎么使用了柒爸,要新增一個(gè)頁(yè)面的話,需要到路由配置中配置該頁(yè)面的信息陈肛。
如果頁(yè)面越來(lái)越多的話揍鸟,那么如何讓我們的路由更簡(jiǎn)潔呢?
3.1 拆分路由
根據(jù)不同的業(yè)務(wù)模塊進(jìn)行拆分路由
在每個(gè)子模塊中導(dǎo)出一個(gè)路由配置數(shù)組
在根 index.js
中導(dǎo)入所有子模塊
3.2 自動(dòng)掃描子模塊路由并導(dǎo)入
當(dāng)我們的業(yè)務(wù)越來(lái)越龐大,每次新增業(yè)務(wù)模塊的時(shí)候阳藻,我們都要在路由下面新增一個(gè)子路由模塊晰奖,然后在index.js
中導(dǎo)入。
那么如何簡(jiǎn)化這種操作呢腥泥?
通過(guò)上面的自動(dòng)掃描全局組件注冊(cè)匾南,我們也可以實(shí)現(xiàn)自動(dòng)掃描子模塊路由并導(dǎo)入
4. 通過(guò)node來(lái)生成組件
作為前端開(kāi)發(fā)者,放著 node
這么好用的東西如果不能運(yùn)用起來(lái)蛔外,豈不是很浪費(fèi)蛆楞?
雖然我們通過(guò)上面已經(jīng)實(shí)現(xiàn)了組件的自動(dòng)注冊(cè),不過(guò)每次新建組件的時(shí)候夹厌,都要?jiǎng)?chuàng)建一個(gè)目錄豹爹,然后新增一個(gè)
.vue
文件,然后寫(xiě)template
矛纹、script
臂聋、style
這些東西,然后新建一個(gè)index.js
或南、導(dǎo)出vue組件孩等、雖然有插件能實(shí)現(xiàn)自動(dòng)補(bǔ)全,但還是很麻煩有木有采够。
那么我們能不能通過(guò)node
來(lái)幫助我們干這些事情呢肄方?只要告訴node
幫我生成的組件名稱就行了。其它的事情讓node
來(lái)干
4.1 通過(guò)node來(lái)生成組件
- 安裝一下
chalk
蹬癌,這個(gè)插件能讓我們的控制臺(tái)輸出語(yǔ)句有各種顏色區(qū)分
npm install chalk --save-dev
在根目錄中創(chuàng)建一個(gè) scripts
文件夾权她,
新增一個(gè)generateComponent.js
文件,放置生成組件的代碼冀瓦、
新增一個(gè)template.js
文件伴奥,放置組件模板的代碼
- template.js
// template.js
module.exports = {
vueTemplate: compoenntName => {
return `<template>
<div class="${compoenntName}">
${compoenntName}組件
</div>
</template>
<script>
export default {
name: '${compoenntName}'
}
</script>
<style lang="scss" scoped>
.${compoenntName} {
}
</style>
`
},
entryTemplate: `import Main from './main.vue'
export default Main`
}
- generateComponent.js`
// generateComponent.js`
const chalk = require('chalk')
const path = require('path')
const fs = require('fs')
const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
const { vueTemplate, entryTemplate } = require('./template')
const generateFile = (path, data) => {
if (fs.existsSync(path)) {
errorLog(`${path}文件已存在`)
return
}
return new Promise((resolve, reject) => {
fs.writeFile(path, data, 'utf8', err => {
if (err) {
errorLog(err.message)
reject(err)
} else {
resolve(true)
}
})
})
}
log('請(qǐng)輸入要生成的組件名稱写烤、如需生成全局組件翼闽,請(qǐng)加 global/ 前綴')
let componentName = ''
process.stdin.on('data', async chunk => {
const inputName = String(chunk).trim().toString()
/**
* 組件目錄路徑
*/
const componentDirectory = resolve('../src/components', inputName)
/**
* vue組件路徑
*/
const componentVueName = resolve(componentDirectory, 'main.vue')
/**
* 入口文件路徑
*/
const entryComponentName = resolve(componentDirectory, 'index.js')
const hasComponentDirectory = fs.existsSync(componentDirectory)
if (hasComponentDirectory) {
errorLog(`${inputName}組件目錄已存在,請(qǐng)重新輸入`)
return
} else {
log(`正在生成 component 目錄 ${componentDirectory}`)
await dotExistDirectoryCreate(componentDirectory)
// fs.mkdirSync(componentDirectory);
}
try {
if (inputName.includes('/')) {
const inputArr = inputName.split('/')
componentName = inputArr[inputArr.length - 1]
} else {
componentName = inputName
}
log(`正在生成 vue 文件 ${componentVueName}`)
await generateFile(componentVueName, vueTemplate(componentName))
log(`正在生成 entry 文件 ${entryComponentName}`)
await generateFile(entryComponentName, entryTemplate)
successLog('生成成功')
} catch (e) {
errorLog(e.message)
}
process.stdin.emit('end')
})
process.stdin.on('end', () => {
log('exit')
process.exit()
})
function dotExistDirectoryCreate (directory) {
return new Promise((resolve) => {
mkdirs(directory, function () {
resolve(true)
})
})
}
// 遞歸創(chuàng)建目錄
function mkdirs (directory, callback) {
var exists = fs.existsSync(directory)
if (exists) {
callback()
} else {
mkdirs(path.dirname(directory), function () {
fs.mkdirSync(directory)
callback()
})
}
}
- 配置package.json
"new:comp": "node ./scripts/generateComponent"
- 執(zhí)行
如果使用 npm
的話 就是 npm run new:comp
如果使用 yarn
的話 就是 yarn new:comp
4.2 通過(guò)node來(lái)生成頁(yè)面組件
通過(guò)上面的邏輯代碼我們可以通過(guò)node
來(lái)生成組件了洲炊,那么也可以舉一反三來(lái)生成頁(yè)面組件感局。只需稍微修改一下生成組件代碼的邏輯。
在scripts
目錄下新建一個(gè)generateView.js
文件
// generateView.js
const chalk = require('chalk')
const path = require('path')
const fs = require('fs')
const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
const { vueTemplate } = require('./template')
const generateFile = (path, data) => {
if (fs.existsSync(path)) {
errorLog(`${path}文件已存在`)
return
}
return new Promise((resolve, reject) => {
fs.writeFile(path, data, 'utf8', err => {
if (err) {
errorLog(err.message)
reject(err)
} else {
resolve(true)
}
})
})
}
log('請(qǐng)輸入要生成的頁(yè)面組件名稱暂衡、會(huì)生成在 views/目錄下')
let componentName = ''
process.stdin.on('data', async chunk => {
const inputName = String(chunk).trim().toString()
/**
* Vue頁(yè)面組件路徑
*/
let componentVueName = resolve('../src/views', inputName)
// 如果不是以 .vue 結(jié)尾的話询微,自動(dòng)加上
if (!componentVueName.endsWith('.vue')) {
componentVueName += '.vue'
}
/**
* vue組件目錄路徑
*/
const componentDirectory = path.dirname(componentVueName)
const hasComponentExists = fs.existsSync(componentVueName)
if (hasComponentExists) {
errorLog(`${inputName}頁(yè)面組件已存在,請(qǐng)重新輸入`)
return
} else {
log(`正在生成 component 目錄 ${componentDirectory}`)
await dotExistDirectoryCreate(componentDirectory)
}
try {
if (inputName.includes('/')) {
const inputArr = inputName.split('/')
componentName = inputArr[inputArr.length - 1]
} else {
componentName = inputName
}
log(`正在生成 vue 文件 ${componentVueName}`)
await generateFile(componentVueName, vueTemplate(componentName))
successLog('生成成功')
} catch (e) {
errorLog(e.message)
}
process.stdin.emit('end')
})
process.stdin.on('end', () => {
log('exit')
process.exit()
})
function dotExistDirectoryCreate (directory) {
return new Promise((resolve) => {
mkdirs(directory, function () {
resolve(true)
})
})
}
// 遞歸創(chuàng)建目錄
function mkdirs (directory, callback) {
var exists = fs.existsSync(directory)
if (exists) {
callback()
} else {
mkdirs(path.dirname(directory), function () {
fs.mkdirSync(directory)
callback()
})
}
}
- 配置package.json
新增一個(gè)scripts
腳本
"new:view": "node ./scripts/generateView"
- 執(zhí)行
如果使用 npm
的話 就是 npm run new:view
如果使用 yarn
的話 就是 yarn new:view
5. axios封裝
- 安裝 axios
npm install axios --save
// or
yarn add axios
5.1 配置不同的環(huán)境
在根目錄新建三個(gè)環(huán)境變量文件
分別輸入不同的地址狂巢,
比如
dev
就寫(xiě) dev
的api地址撑毛、test
就寫(xiě)test
的api地址
# // .env
NODE_ENV = "development"
BASE_URL = "https://easy-mock.com/mock/5c4c50b9888ef15de01bec2c/api"
接著在根目錄中新建一個(gè) vue.config.js
// vue.config.js
module.exports = {
chainWebpack: config => {
// 這里是對(duì)環(huán)境的配置,不同環(huán)境對(duì)應(yīng)不同的BASE_URL唧领,以便axios的請(qǐng)求地址不同
config.plugin('define').tap(args => {
args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
return args
})
}
}
然后在src
目錄下新建一個(gè) api
文件夾藻雌,創(chuàng)建一個(gè) index.js
用來(lái)配置 axios
的配置信息
// src/api/index.js
import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
// 設(shè)置超時(shí)時(shí)間
timeout: 60000,
baseURL: process.env.BASE_URL
})
// post請(qǐng)求的時(shí)候雌续,我們需要加上一個(gè)請(qǐng)求頭,所以可以在這里進(jìn)行一個(gè)默認(rèn)的設(shè)置
// 即設(shè)置post的請(qǐng)求頭為application/x-www-form-urlencoded;charset=UTF-8
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8''
export default service
5.2 請(qǐng)求響應(yīng)封裝
import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
// 設(shè)置超時(shí)時(shí)間
timeout: 60000,
baseURL: process.env.BASE_URL
})
/**
* 請(qǐng)求前攔截
* 用于處理需要在請(qǐng)求前的操作
*/
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
}, (error) => {
return Promise.reject(error)
})
/**
* 請(qǐng)求響應(yīng)攔截
* 用于處理需要在請(qǐng)求返回后的操作
*/
service.interceptors.response.use(response => {
const responseCode = response.status
// 如果返回的狀態(tài)碼為200胯杭,說(shuō)明接口請(qǐng)求成功驯杜,可以正常拿到數(shù)據(jù)
// 否則的話拋出錯(cuò)誤
if (responseCode === 200) {
return Promise.resolve(response)
} else {
return Promise.reject(response)
}
}, error => {
// 服務(wù)器返回不是 2 開(kāi)頭的情況,會(huì)進(jìn)入這個(gè)回調(diào)
// 可以根據(jù)后端返回的狀態(tài)碼進(jìn)行不同的操作
const responseCode = error.response.status
switch (responseCode) {
// 401:未登錄
case 401:
// 跳轉(zhuǎn)登錄頁(yè)
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
break
// 403: token過(guò)期
case 403:
// 彈出錯(cuò)誤信息
Message({
type: 'error',
message: '登錄信息過(guò)期做个,請(qǐng)重新登錄'
})
// 清除token
localStorage.removeItem('token')
// 跳轉(zhuǎn)登錄頁(yè)面鸽心,并將要瀏覽的頁(yè)面fullPath傳過(guò)去,登錄成功后跳轉(zhuǎn)需要訪問(wèn)的頁(yè)面
setTimeout(() => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}, 1000)
break
// 404請(qǐng)求不存在
case 404:
Message({
message: '網(wǎng)絡(luò)請(qǐng)求不存在',
type: 'error'
})
break
// 其他錯(cuò)誤居暖,直接拋出錯(cuò)誤提示
default:
Message({
message: error.response.data.message,
type: 'error'
})
}
return Promise.reject(error)
})
export default service
Message
方法是 element-ui
提供的一個(gè)消息提示組件顽频、大家可以根據(jù)自己的消息提示組件進(jìn)行替換
5.3 斷網(wǎng)處理
在響應(yīng)攔截中添加處理邏輯
service.interceptors.response.use(response => {
const responseCode = response.status
// 如果返回的狀態(tài)碼為200,說(shuō)明接口請(qǐng)求成功太闺,可以正常拿到數(shù)據(jù)
// 否則的話拋出錯(cuò)誤
if (responseCode === 200) {
return Promise.resolve(response.data)
} else {
return Promise.reject(response)
}
}, error => {
// 斷網(wǎng) 或者 請(qǐng)求超時(shí) 狀態(tài)
if (!error.response) {
// 請(qǐng)求超時(shí)狀態(tài)
if (error.message.includes('timeout')) {
console.log('超時(shí)了')
Message.error('請(qǐng)求超時(shí)冲九,請(qǐng)檢查網(wǎng)絡(luò)是否連接正常')
} else {
// 可以展示斷網(wǎng)組件
console.log('斷網(wǎng)了')
Message.error('請(qǐng)求失敗,請(qǐng)檢查網(wǎng)絡(luò)是否已連接')
}
return
}
// 省略其它代碼 ······
return Promise.reject(error)
})
5.4 封裝圖片上傳
// src/api/index.js
export const uploadFile = formData => {
const res = service.request({
method: 'post',
url: '/upload',
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
return res
}
調(diào)用
async uploadFile (e) {
const file = document.getElementById('file').files[0]
const formdata = new FormData()
formdata.append('file', file)
await uploadFile(formdata)
}
5.5 請(qǐng)求 顯示 Loading 效果
let loading = null
service.interceptors.request.use(config => {
// 在請(qǐng)求先展示加載框
loading = Loading.service({
text: '正在加載中......'
})
// 省略其它代碼 ······
return config
}, (error) => {
return Promise.reject(error)
})
service.interceptors.response.use(response => {
// 請(qǐng)求響應(yīng)后關(guān)閉加載框
if (loading) {
loading.close()
}
// 省略其它代碼 ······
}, error => {
// 請(qǐng)求響應(yīng)后關(guān)閉加載框
if (loading) {
loading.close()
}
// 省略其它代碼 ······
return Promise.reject(error)
})
6. 巧用 Mixins
6.1 封裝 store 公用方法
假設(shè)有這樣一個(gè)場(chǎng)景跟束,我們通過(guò) vuex
封裝了獲取新聞列表的 function
import Vue from 'vue'
import Vuex from 'vuex'
import { getNewsList } from '../api/news'
Vue.use(Vuex)
const types = {
NEWS_LIST: 'NEWS_LIST'
}
export default new Vuex.Store({
state: {
[types.NEWS_LIST]: []
},
mutations: {
[types.NEWS_LIST]: (state, res) => {
state[types.NEWS_LIST] = res
}
},
actions: {
[types.NEWS_LIST]: async ({ commit }, params) => {
const res = await getNewsList(params)
return commit(types.NEWS_LIST, res)
}
},
getters: {
getNewsResponse (state) {
return state[types.NEWS_LIST]
}
}
})
然后在新聞列表頁(yè)莺奸,我們通過(guò) mapAction
、mapGetters
來(lái)調(diào)用Action
和getters
我們需要寫(xiě)上這些代碼
import { mapActions, mapGetters } from 'vuex'
computed: {
...mapGetters(['getNewsResponse'])
},
methods: {
...mapActions(['NEWS_LIST'])
}
在假設(shè)冀宴,在另一個(gè)頁(yè)面又需要重新調(diào)用獲取新聞列表的接口灭贷,我們又要在寫(xiě)一遍上面的代碼對(duì)吧?
復(fù)制粘貼就是干有木有略贮?
如果接口突然加了一個(gè)參數(shù)甚疟,那豈不是每個(gè)要用到這個(gè)接口的代碼都得加這個(gè)參數(shù)。
復(fù)制粘貼一時(shí)爽逃延,需求一改你就爽
既然是重復(fù)的代碼览妖,我們肯定要復(fù)用,這時(shí)候Vue
提供的Mixin
就起了大作用了
- 封裝 news-mixin.js
在src
下創(chuàng)建一個(gè)mixins
目錄揽祥,用來(lái)管理所有的mixins
新建一個(gè)news-mixin.js
import { mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getNewsResponse'])
},
methods: {
...mapActions(['NEWS_LIST'])
}
}
然后在需要用到的組件中引入這個(gè)mixin
讽膏,就能直接調(diào)用這個(gè)方法了。不管多少個(gè)頁(yè)面拄丰,只要引入這個(gè)mixin
府树,直接就能使用。
需求一改的話料按,也只需要修改這個(gè)mixin
文件
// news/index.vue
import Vue from 'vue'
import newsMixin from '@/mixins/news-mixin'
export default {
name: 'news',
mixins: [newsMixin],
data () {
return {}
},
async created () {
await this.NEWS_LIST()
console.log(this.getNewsResponse)
}
}
6.2 擴(kuò)展
除了封裝 vuex
的公用方法奄侠,其實(shí)還有很多的東西也能做封裝。例如:分頁(yè)對(duì)象
,表格數(shù)據(jù)
,公用方法
载矿、等等就不一一舉例了垄潮。可以看github
在多個(gè)地方經(jīng)常使用,就可以考慮封裝成mixin
弯洗,不過(guò)請(qǐng)寫(xiě)好注釋哦甫题。不然就會(huì)有人在背后罵你了!涂召!你懂的~~
7. 優(yōu)化
7.1 gzip壓縮
- 安裝
compression-webpack-plugin
插件
npm install compression-webpack-plugin --save-dev
// or
yarn add compression-webpack-plugin --dev
- 在 vue.config.js 中添加配置
// vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
chainWebpack: config => {
// 這里是對(duì)環(huán)境的配置坠非,不同環(huán)境對(duì)應(yīng)不同的BASE_URL,以便axios的請(qǐng)求地址不同
config.plugin('define').tap(args => {
args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
return args
})
if (process.env.NODE_ENV === 'production') {
// #region 啟用GZip壓縮
config
.plugin('compression')
.use(CompressionPlugin, {
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')$'),
threshold: 10240,
minRatio: 0.8,
cache: true
})
.tap(args => { })
// #endregion
}
}
}
npm run build
后能看到生成 .gz
文件就OK了果正。如果你的服務(wù)器使用nginx的話炎码,nginx也需要配置開(kāi)啟GZIP
、下面會(huì)講到如何在nginx
中開(kāi)啟GZIP
7.2 第三方庫(kù)引用cdn
對(duì)于 vue
秋泳、vue-router
潦闲、vuex
、axios
和element-ui
等等這些不經(jīng)常改動(dòng)的庫(kù)迫皱、我們讓webpack
不對(duì)他們進(jìn)行打包歉闰,通過(guò)cdn
引入,可以減少代碼的大小卓起、也可以減少服務(wù)器的帶寬和敬,更能把這些文件緩存到客戶端,客戶端加載的會(huì)更快戏阅。
- 配置
vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
chainWebpack: config => {
// 省略其它代碼 ······
// #region 忽略生成環(huán)境打包的文件
var externals = {
vue: 'Vue',
axios: 'axios',
'element-ui': 'ELEMENT',
'vue-router': 'VueRouter',
vuex: 'Vuex'
}
config.externals(externals)
const cdn = {
css: [
// element-ui css
'//unpkg.com/element-ui/lib/theme-chalk/index.css'
],
js: [
// vue
'//cdn.staticfile.org/vue/2.5.22/vue.min.js',
// vue-router
'//cdn.staticfile.org/vue-router/3.0.2/vue-router.min.js',
// vuex
'//cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
// axios
'//cdn.staticfile.org/axios/0.19.0-beta.1/axios.min.js',
// element-ui js
'//unpkg.com/element-ui/lib/index.js'
]
}
config.plugin('html')
.tap(args => {
args[0].cdn = cdn
return args
})
// #endregion
}
}
}
- 修改
index.html
<!--public/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<% if (process.env.NODE_ENV === 'production') { %>
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%=css%>" rel="preload" as="style">
<link rel="stylesheet" href="<%=css%>" as="style">
<% } %>
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%=js%>" rel="preload" as="script">
<script src="<%=js%>"></script>
<% } %>
<% } %>
<title>vue-cli3-project</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-cli3-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
7.3 全站cdn
我們已經(jīng)把第三方庫(kù)使用cdn
替代了昼弟,那么我們build
后生成的 js
,css
之類的文件能否也用cdn
呢?
申請(qǐng)自己的cdn域名
要想把自己的資源上傳到cdn
上奕筐,前提是得有自己的cdn
域名舱痘,如果沒(méi)有的話,可以到七牛云官網(wǎng)上注冊(cè)申請(qǐng)一個(gè)
- 注冊(cè)七牛云賬號(hào)
- 到七牛云對(duì)象存儲(chǔ)模塊中新建存儲(chǔ)空間
-
輸入存儲(chǔ)空間信息
- 確定創(chuàng)建
-
創(chuàng)建成功后會(huì)跳轉(zhuǎn)到這個(gè)存儲(chǔ)空間的控制臺(tái)頁(yè)面
- 其中有個(gè)域名就是你的測(cè)試域名
- 我們可以在內(nèi)容管理那上傳我們的
js
离赫、css
之類的文件芭逝、不過(guò)我們的文件那么多,一個(gè)一個(gè)上傳明顯不合理渊胸。要你你也不干旬盯。
這時(shí)候,這些批量又重復(fù)的操作應(yīng)該由我們的node
出馬蹬刷,讓我們來(lái)通過(guò) node
來(lái)批量上傳我們的資源文件
將生成的js瓢捉、css資源上傳到七牛cdn
在七牛云官網(wǎng)的文檔中心有介紹如何通過(guò)node
上傳文件、感興趣的人可以自己去研究一下办成。
- 查看
AccessKey
和SecretKey
,在你的個(gè)人面板 -> 秘鑰管理 搂漠,這兩個(gè)秘鑰待會(huì)會(huì)用到
- 安裝需要的插件
npm install qiniu glob mime --save-dev
- 在
scripts
目錄下創(chuàng)建一個(gè)upcdn.js
文件
// /scripts/upcdn.js
const qiniu = require('qiniu')
const glob = require('glob')
const mime = require('mime')
const path = require('path')
const isWindow = /^win/.test(process.platform)
let pre = path.resolve(__dirname, '../dist/') + (isWindow ? '\\' : '')
const files = glob.sync(
`${path.join(
__dirname,
'../dist/**/*.?(js|css|map|png|jpg|svg|woff|woff2|ttf|eot)'
)}`
)
pre = pre.replace(/\\/g, '/')
const options = {
scope: 'source' // 空間對(duì)象名稱
}
var config = {
qiniu: {
accessKey: '', // 個(gè)人中心 秘鑰管理里的 AccessKey
secretKey: '', // 個(gè)人中心 秘鑰管理里的 SecretKey
bucket: options.scope,
domain: 'http://ply4cszel.bkt.clouddn.com'
}
}
var accessKey = config.qiniu.accessKey
var secretKey = config.qiniu.secretKey
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
var putPolicy = new qiniu.rs.PutPolicy(options)
var uploadToken = putPolicy.uploadToken(mac)
var cf = new qiniu.conf.Config({
zone: qiniu.zone.Zone_z2
})
var formUploader = new qiniu.form_up.FormUploader(cf)
async function uploadFileCDN (files) {
files.map(async file => {
const key = getFileKey(pre, file)
try {
await uploadFIle(key, file)
console.log(`上傳成功 key: ${key}`)
} catch (err) {
console.log('error', err)
}
})
}
async function uploadFIle (key, localFile) {
const extname = path.extname(localFile)
const mimeName = mime.getType(extname)
const putExtra = new qiniu.form_up.PutExtra({ mimeType: mimeName })
return new Promise((resolve, reject) => {
formUploader.putFile(uploadToken, key, localFile, putExtra, function (
respErr,
respBody,
respInfo
) {
if (respErr) {
reject(respErr)
}
resolve({ respBody, respInfo })
})
})
}
function getFileKey (pre, file) {
if (file.indexOf(pre) > -1) {
const key = file.split(pre)[1]
return key.startsWith('/') ? key.substring(1) : key
}
return file
}
(async () => {
console.time('上傳文件到cdn')
await uploadFileCDN(files)
console.timeEnd('上傳文件到cdn')
})()
修改 publicPath
修改vue.config.js
的配置信息迂卢,讓其publicPath
指向我們cdn
的域名
const IS_PROD = process.env.NODE_ENV === 'production'
const cdnDomian = 'http://ply4cszel.bkt.clouddn.com'
module.exports = {
publicPath: IS_PROD ? cdnDomian : '/',
// 省略其它代碼 ·······
}
修改package.json配置
修改package.json配置,使我們build
完成后自動(dòng)上傳資源文件到cdn服務(wù)器
"build": "vue-cli-service build --mode prod && node ./scripts/upcdn.js",
運(yùn)行查看效果
npm run build
然后到你的
cdn
控制臺(tái)的內(nèi)容管理看看文件是否已經(jīng)上傳成功
8. docker部署
這邊使用的是 centOS7
環(huán)境,不過(guò)使用的是不同的系統(tǒng)而克,可以參考一下其它系統(tǒng)的安裝方法
8.1 安裝docker
- 更新軟件庫(kù)
yum update -y
- 安裝docker
yum install docker
- 啟動(dòng)docker服務(wù)
service docker start
- 安裝docker-compose
// 安裝epel源
yum install -y epel-release
// 安裝docker-compose
yum install docker-compose
8.2 編寫(xiě)docker-compose.yaml
version: '2.1'
services:
nginx:
restart: always
image: nginx
volumes:
#~ /var/local/nginx/nginx.conf為本機(jī)目錄, /etc/nginx為容器目錄
- /var/local/nginx/nginx.conf:/etc/nginx/nginx.conf
#~ /var/local/app/dist 為本機(jī) build 后的dist目錄, /usr/src/app為容器目錄,
- /var/local/app/dist:/usr/src/app
ports:
- 80:80
privileged: true
8.3 編寫(xiě) nginx.conf 配置
#user nobody;
worker_processes 2;
#工作模式及連接數(shù)上線
events {
worker_connections 1024; #單個(gè)工作進(jìn)程 處理進(jìn)程的最大并發(fā)數(shù)
}
http {
include mime.types;
default_type application/octet-stream;
#sendfile 指令指定 nginx 是否調(diào)用 sendfile 函數(shù)(zero copy 方式)來(lái)輸出文件靶壮,對(duì)于普通應(yīng)用,
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
# 開(kāi)啟GZIP
gzip on;
# # 監(jiān)聽(tīng) 80 端口员萍,轉(zhuǎn)發(fā)請(qǐng)求到 3000 端口
server {
#監(jiān)聽(tīng)端口
listen 80;
#編碼格式
charset utf-8;
# 前端靜態(tài)文件資源
location / {
root /usr/src/app;
index index.html index.htm;
try_files $uri $uri/ @rewrites;
}
# 配置如果匹配不到資源腾降,將url指向 index.html, 在 vue-router 的 history 模式下使用碎绎,就不會(huì)顯示404
location @rewrites {
rewrite ^(.*)$ /index.html last;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
8.4 執(zhí)行 docker-compose
docker-compose -d up
8.5 docker + jenkins 自動(dòng)化部署
使用docker
+ jenkins
能實(shí)現(xiàn)代碼提交到github后自動(dòng)部署環(huán)境螃壤、這個(gè)要講起來(lái)內(nèi)容太多,有興趣的可以看我這一篇文章
從零搭建docker+jenkins+node.js自動(dòng)化部署環(huán)境
6. 擴(kuò)展
如果大家還有什么更好的實(shí)踐方式筋帖,歡迎評(píng)論區(qū)指教<榍纭!
項(xiàng)目地址 vue-cli3-project 歡迎 star
歡迎關(guān)注
歡迎關(guān)注公眾號(hào)“碼上開(kāi)發(fā)”日麸,每天分享最新技術(shù)資訊