全棧之路:vue-cli3 項目從搭建優化到docker部署

上一篇,認識Docker

全棧之路:vue-cli3 項目從搭建優化到docker部署

1. 創建一個vue項目

相信大部分人都已經知道怎麼創建項目的,可以跳過這一節,看下一節。

1.1 安裝@vue/cli

# 全局安裝 vue-cli腳手架
npm install -g @vue/cli

等待安裝完成後開始下一步

1.2 初始化項目

vue create vue-cli3-project
  1. 選擇一個預設
全棧之路:vue-cli3 項目從搭建優化到docker部署

可以選擇默認預設,默認預設包含了babel,eslint我們選擇更多功能 Manually select features

回車後來到選擇插件

插件選擇

這邊選擇了(Babel、Router、Vuex、Css預處理器、Linter / Formatter 格式檢查、Unit測試框架)

路由模式選擇

是否使用 history模式的路由 (Yes)

選擇一個css預處理器 (Sass/SCSS)

全棧之路:vue-cli3 項目從搭建優化到docker部署

選擇一個eslint配置

這邊選擇 ESLint + Standard config,個人比較喜歡這個代碼規範

全棧之路:vue-cli3 項目從搭建優化到docker部署

選擇什麼時候進行 eslint 校驗

選擇(Lint on save)保存是檢查

如果你正在使用的vscode編輯器的話,可以配置eslint插件進行代碼自動格式化

全棧之路:vue-cli3 項目從搭建優化到docker部署

選擇測試框架 (Mocha + Chai)

全棧之路:vue-cli3 項目從搭建優化到docker部署

選擇將這些配置文件寫入到什麼地方 (In dedicated config files)

全棧之路:vue-cli3 項目從搭建優化到docker部署

是否保存這份預設配置?(y)

選是的話,下次創建一個vue項目,可以直接使用這個預設文件,而無需再進行配置。

全棧之路:vue-cli3 項目從搭建優化到docker部署

2. 全局組件自動註冊

在components目錄下創建一個global目錄,裡面放置一些需要全局註冊的組件。

index.js作用只要是引入main.vue,導出組件對象

全棧之路:vue-cli3 項目從搭建優化到docker部署

在components中創建一個index.js,用來掃描全局對象並自動註冊。

// components/index.js
import Vue from 'vue'
// 自動加載 global 目錄下的 .js 結尾的文件
const componentsContext = require.context('./global', true, /\\.js$/)
componentsContext.keys().forEach(component => {
const componentConfig = componentsContext(component)
/**
* 兼容 import export 和 require module.export 兩種規範
*/
const ctrl = componentConfig.default || componentConfig
Vue.component(ctrl.name, ctrl)
})

最後在入口文件main.js中導入這個index.js中就可以了

3.路由自動引入

在 Vue項目中使用路由,相信想熟的人已經很熟悉怎麼使用了,要新增一個頁面的話,需要到路由配置中配置該頁面的信息。

如果頁面越來越多的話,那麼如何讓我們的路由更簡潔呢?

3.1 拆分路由

根據不同的業務模塊進行拆分路由

全棧之路:vue-cli3 項目從搭建優化到docker部署

在每個子模塊中導出一個路由配置數組

全棧之路:vue-cli3 項目從搭建優化到docker部署

在根 index.js中導入所有子模塊

全棧之路:vue-cli3 項目從搭建優化到docker部署

3.2 自動掃描子模塊路由並導入

當我們的業務越來越龐大,每次新增業務模塊的時候,我們都要在路由下面新增一個子路由模塊,然後在index.js中導入。

那麼如何簡化這種操作呢?

通過上面的自動掃描全局組件註冊,我們也可以實現自動掃描子模塊路由並導入

全棧之路:vue-cli3 項目從搭建優化到docker部署

4. 通過node來生成組件

作為前端開發者,放著 node這麼好用的東西如果不能運用起來,豈不是很浪費?

雖然我們通過上面已經實現了組件的自動註冊,不過每次新建組件的時候,都要創建一個目錄,然後新增一個.vue文件,然後寫template、script、style這些東西,然後新建一個index.js、導出vue組件、雖然有插件能實現自動補全,但還是很麻煩有木有。那麼我們能不能通過node來幫助我們幹這些事情呢?只要告訴node幫我生成的組件名稱就行了。其它的事情讓node來幹

4.1 通過node來生成組件

  • 安裝一下chalk,這個插件能讓我們的控制檯輸出語句有各種顏色區分
npm install chalk --save-dev

在根目錄中創建一個>

新增一個generateComponent.js文件,放置生成組件的代碼、

新增一個template.js文件,放置組件模板的代碼

  • template.js
// template.js
module.exports = {
vueTemplate: compoenntName => {
return `<template>

${compoenntName}組件

/<template>

<style><br>.${compoenntName} {<br>}<br>/<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('請輸入要生成的組件名稱、如需生成全局組件,請加 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}組件目錄已存在,請重新輸入`)
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)
})
})
}
// 遞歸創建目錄
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"
複製代碼
  • 執行

如果使用 npm 的話 就是

npm run new:comp

如果使用 yarn 的話 就是

yarn new:comp

全棧之路:vue-cli3 項目從搭建優化到docker部署

4.2 通過node來生成頁面組件

通過上面的邏輯代碼我們可以通過node來生成組件了,那麼也可以舉一反三來生成頁面組件。只需稍微修改一下生成組件代碼的邏輯。 在scripts目錄下新建一個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('請輸入要生成的頁面組件名稱、會生成在 views/目錄下')
let componentName = ''
process.stdin.on('data', async chunk => {
const inputName = String(chunk).trim().toString()
/**
* Vue頁面組件路徑
*/

let componentVueName = resolve('../src/views', inputName)
// 如果不是以 .vue 結尾的話,自動加上
if (!componentVueName.endsWith('.vue')) {
componentVueName += '.vue'
}
/**
* vue組件目錄路徑
*/
const componentDirectory = path.dirname(componentVueName)
const hasComponentExists = fs.existsSync(componentVueName)
if (hasComponentExists) {
errorLog(`${inputName}頁面組件已存在,請重新輸入`)
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)
})
})
}
// 遞歸創建目錄
function mkdirs(directory, callback) {

var exists = fs.existsSync(directory)
if (exists) {
callback()
} else {
mkdirs(path.dirname(directory), function () {
fs.mkdirSync(directory)
callback()
})
}
}
  • 配置package.json 新增一個scripts腳本
"new:view": "node ./scripts/generateView"
複製代碼
  • 執行

如果使用 npm 的話 就是

npm run new:view

如果使用 yarn 的話 就是

yarn new:view

全棧之路:vue-cli3 項目從搭建優化到docker部署

5. axios封裝

  • 安裝 axios
npm install axios --save
// or
yarn add axios

5.1 配置不同的環境

在根目錄新建三個環境變量文件

全棧之路:vue-cli3 項目從搭建優化到docker部署

分別輸入不同的地址, 比如dev就寫 dev的api地址、test就寫test的api地址

# // .env
NODE_ENV = "development"
BASE_URL = "https://easy-mock.com/mock/5c4c50b9888ef15de01bec2c/api"

接著在根目錄中新建一個 vue.config.js

// vue.config.js
module.exports = {
chainWebpack: config => {
// 這裡是對環境的配置,不同環境對應不同的BASE_URL,以便axios的請求地址不同
config.plugin('define').tap(args => {
args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
return args
})
}
}

然後在src目錄下新建一個 api文件夾,創建一個 index.js用來配置 axios的配置信息

// src/api/index.js
import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
// 設置超時時間
timeout: 60000,
baseURL: process.env.BASE_URL
})
// post請求的時候,我們需要加上一個請求頭,所以可以在這裡進行一個默認的設置
// 即設置post的請求頭為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 請求響應封裝

import axios from 'axios'
import router from '../router'
import { Message } from 'element-ui'
const service = axios.create({
// 設置超時時間
timeout: 60000,
baseURL: process.env.BASE_URL
})
/**
* 請求前攔截
* 用於處理需要在請求前的操作
*/
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
}, (error) => {
return Promise.reject(error)
})
/**
* 請求響應攔截
* 用於處理需要在請求返回後的操作
*/
service.interceptors.response.use(response => {
const responseCode = response.status
// 如果返回的狀態碼為200,說明接口請求成功,可以正常拿到數據
// 否則的話拋出錯誤
if (responseCode === 200) {
return Promise.resolve(response)
} else {
return Promise.reject(response)
}
}, error => {

// 服務器返回不是 2 開頭的情況,會進入這個回調
// 可以根據後端返回的狀態碼進行不同的操作
const responseCode = error.response.status
switch (responseCode) {
// 401:未登錄
case 401:
// 跳轉登錄頁
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
break
// 403: token過期
case 403:
// 彈出錯誤信息
Message({
type: 'error',
message: '登錄信息過期,請重新登錄'
})
// 清除token
localStorage.removeItem('token')
// 跳轉登錄頁面,並將要瀏覽的頁面fullPath傳過去,登錄成功後跳轉需要訪問的頁面
setTimeout(() => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}, 1000)
break
// 404請求不存在
case 404:
Message({
message: '網絡請求不存在',
type: 'error'
})

break
// 其他錯誤,直接拋出錯誤提示
default:
Message({
message: error.response.data.message,
type: 'error'
})
}
return Promise.reject(error)
})
export default service

Message 方法是 element-ui 提供的一個消息提示組件、大家可以根據自己的消息提示組件進行替換

5.3 斷網處理

在響應攔截中添加處理邏輯

service.interceptors.response.use(response => {
const responseCode = response.status
// 如果返回的狀態碼為200,說明接口請求成功,可以正常拿到數據
// 否則的話拋出錯誤
if (responseCode === 200) {
return Promise.resolve(response.data)
} else {
return Promise.reject(response)
}
}, error => {
// 斷網 或者 請求超時 狀態
if (!error.response) {
// 請求超時狀態
if (error.message.includes('timeout')) {
console.log('超時了')
Message.error('請求超時,請檢查網絡是否連接正常')

} else {
// 可以展示斷網組件
console.log('斷網了')
Message.error('請求失敗,請檢查網絡是否已連接')
}
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
}

調用

async uploadFile (e) {
const file = document.getElementById('file').files[0]
const formdata = new FormData()
formdata.append('file', file)
await uploadFile(formdata)
}

5.5 請求 顯示 Loading 效果

let loading = null
service.interceptors.request.use(config => {
// 在請求先展示加載框

loading = Loading.service({
text: '正在加載中......'
})
// 省略其它代碼 ······
return config
}, (error) => {
return Promise.reject(error)
})
service.interceptors.response.use(response => {
// 請求響應後關閉加載框
if (loading) {
loading.close()
}
// 省略其它代碼 ······
}, error => {
// 請求響應後關閉加載框
if (loading) {
loading.close()
}
// 省略其它代碼 ······
return Promise.reject(error)
})

6. 巧用 Mixins

6.1 封裝 store 公用方法

假設有這樣一個場景,我們通過 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]
}
}
})

然後在新聞列表頁,我們通過 mapAction、mapGetters來調用Action和getters 我們需要寫上這些代碼

import { mapActions, mapGetters } from 'vuex'
computed: {
...mapGetters(['getNewsResponse'])
},
methods: {
...mapActions(['NEWS_LIST'])
}

在假設,在另一個頁面又需要重新調用獲取新聞列表的接口,我們又要在寫一遍上面的代碼對吧?

複製粘貼就是幹有木有?

如果接口突然加了一個參數,那豈不是每個要用到這個接口的代碼都得加這個參數。

複製粘貼一時爽,需求一改你就爽

既然是重複的代碼,我們肯定要複用,這時候Vue提供的Mixin就起了大作用了

  • 封裝 news-mixin.js 在 src下創建一個mixins目錄,用來管理所有的mixins 新建一個news-mixin.js
import { mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getNewsResponse'])
},
methods: {
...mapActions(['NEWS_LIST'])
}
}

然後在需要用到的組件中引入這個mixin,就能直接調用這個方法了。不管多少個頁面,只要引入這個mixin,直接就能使用。

需求一改的話,也只需要修改這個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 擴展

除了封裝 vuex 的公用方法,其實還有很多的東西也能做封裝。例如:分頁對象,表格數據,公用方法、等等就不一一舉例了。可以看github

在多個地方經常使用,就可以考慮封裝成mixin,不過請寫好註釋哦。不然就會有人在背後罵你了!!你懂的~~

7. 優化

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 => {
// 這裡是對環境的配置,不同環境對應不同的BASE_URL,以便axios的請求地址不同

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了。如果你的服務器使用nginx的話,nginx也需要配置開啟GZIP、下面會講到如何在nginx中開啟GZIP

全棧之路:vue-cli3 項目從搭建優化到docker部署

7.2 第三方庫引用cdn

對於 vue、vue-router、vuex、axios和element-ui等等這些不經常改動的庫、我們讓webpack不對他們進行打包,通過cdn引入,可以減少代碼的大小、也可以減少服務器的帶寬,更能把這些文件緩存到客戶端,客戶端加載的會更快。

  • 配置vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
chainWebpack: config => {
// 省略其它代碼 ······
// #region 忽略生成環境打包的文件
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







<link>


<link>
<link>


<link>




<title>vue-cli3-project/<title>


<noscript>
We're sorry but vue-cli3-project doesn't work properly without JavaScript enabled. Please enable it to continue.
/<noscript>




複製代碼

7.3 全站cdn

我們已經把第三方庫使用cdn替代了,那麼我們build後生成的 js,css之類的文件能否也用cdn呢?

申請自己的cdn域名

要想把自己的資源上傳到cdn上,前提是得有自己的cdn域名,如果沒有的話,可以到七牛雲官網上註冊申請一個

  1. 註冊七牛雲賬號
  2. 到七牛雲對象存儲模塊中新建存儲空間
  3. 輸入存儲空間信息
全棧之路:vue-cli3 項目從搭建優化到docker部署

4、確定創建

5、創建成功後會跳轉到這個存儲空間的控制檯頁面

全棧之路:vue-cli3 項目從搭建優化到docker部署

6、其中有個域名就是你的測試域名

7、我們可以在內容管理那上傳我們的js、css之類的文件、不過我們的文件那麼多,一個一個上傳明顯不合理。要你你也不幹。

這時候,這些批量又重複的操作應該由我們的node出馬,讓我們來通過 node來批量上傳我們的資源文件

將生成的js、css資源上傳到七牛cdn

在七牛雲官網的文檔中心有介紹如何通過node上傳文件、感興趣的人可以自己去研究一下。

  1. 查看AccessKey和SecretKey,在你的個人面板 -> 秘鑰管理 ,這兩個秘鑰待會會用
全棧之路:vue-cli3 項目從搭建優化到docker部署

  1. 安裝需要的插件
npm install qiniu glob mime --save-dev
  1. 在>
// /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' // 空間對象名稱
}
var config = {
qiniu: {
accessKey: '', // 個人中心 秘鑰管理裡的 AccessKey
secretKey: '', // 個人中心 秘鑰管理裡的 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完成後自動上傳資源文件到cdn服務器

"build": "vue-cli-service build --mode prod && node ./scripts/upcdn.js",

運行查看效果

npm run build

全棧之路:vue-cli3 項目從搭建優化到docker部署

然後到你的cdn控制檯的內容管理看看文件是否已經上傳成功

全棧之路:vue-cli3 項目從搭建優化到docker部署

8. docker部署

這邊使用的是 centOS7 環境,不過使用的是不同的系統,可以參考一下其它系統的安裝方法

8.1 安裝docker

  • 更新軟件庫
yum update -y
  • 安裝docker
yum install docker
  • 啟動docker服務
service docker start
  • 安裝docker-compose
// 安裝epel源
yum install -y epel-release
// 安裝docker-compose
yum install docker-compose

8.2 編寫docker-compose.yaml

version: '2.1'
services:
nginx:
restart: always
image: nginx
volumes:
#~ /var/local/nginx/nginx.conf為本機目錄, /etc/nginx為容器目錄
- /var/local/nginx/nginx.conf:/etc/nginx/nginx.conf
#~ /var/local/app/dist 為本機 build 後的dist目錄, /usr/src/app為容器目錄,
- /var/local/app/dist:/usr/src/app
ports:
- 80:80

privileged: true

8.3 編寫 nginx.conf 配置

#user nobody;
worker_processes 2;
#工作模式及連接數上線
events {
worker_connections 1024; #單個工作進程 處理進程的最大併發數
}
http {
include mime.types;
default_type application/octet-stream;
#sendfile 指令指定 nginx 是否調用 sendfile 函數(zero copy 方式)來輸出文件,對於普通應用,
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
# 開啟GZIP
gzip on;
# # 監聽 80 端口,轉發請求到 3000 端口
server {
#監聽端口
listen 80;
#編碼格式
charset utf-8;
# 前端靜態文件資源
location / {
\t root /usr/src/app;
index index.html index.htm;
try_files $uri $uri/ @rewrites;
}
# 配置如果匹配不到資源,將url指向 index.html, 在 vue-router 的 history 模式下使用,就不會顯示404
location @rewrites {
rewrite ^(.*)$ /index.html last;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

8.4 執行 docker-compose

docker-compose -d up
全棧之路:vue-cli3 項目從搭建優化到docker部署

8.5 docker + jenkins 自動化部署

使用docker + jenkins 能實現代碼提交到github後自動部署環境、這個要講起來內容太多,有興趣的可以看我後面一篇文章。


參考文章

https://juejin.im/post/5c4a6fcd518825469414e062

"


分享到:


相關文章: