備戰(zhàn)2021:vite 工程化實(shí)踐,建議收藏
(給前端大學(xué)加星標(biāo),提升前端技能.)
轉(zhuǎn)自:楊村長(zhǎng)
juejin.cn/post/6910014283707318279
vite 是個(gè)啥
vite是一個(gè)開發(fā)構(gòu)建工具,開發(fā)過程中它利用瀏覽器native ES Module特性導(dǎo)入組織代碼,生產(chǎn)中利用rollup作為打包工具,它有如下特點(diǎn):
光速啟動(dòng) 熱模塊替換 按需編譯
本文目標(biāo)
說白了vite就是為開發(fā)者量身定做的一套先進(jìn)的開發(fā)工具,按需編譯、熱模塊替換等特性使我們開發(fā)時(shí)免除了重新打包的等待時(shí)間,開發(fā)體驗(yàn)絲滑,默認(rèn)還整合了vue3,是居家旅行、殺人滅口之必備良藥。目前vite已經(jīng)是正式版,相關(guān)的生態(tài)正在迅速繁榮起來,我也第一時(shí)間在工程化方面做了一些探索,希望能夠拋磚引玉,歡迎廣大掘友拍磚。
安裝vite
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
復(fù)制代碼
代碼組織形式分析
關(guān)鍵變化是index.html中的入口文件導(dǎo)入方式

這樣main.js中就可以使用ES6 Module方式組織代碼

瀏覽器會(huì)自動(dòng)加載這些導(dǎo)入,vite會(huì)啟動(dòng)一個(gè)本地服務(wù)器處理不同這些加載請(qǐng)求,對(duì)于相對(duì)地址的導(dǎo)入,要根據(jù)后綴名處理文件內(nèi)容并返回,對(duì)于裸模塊導(dǎo)入要修改它的路徑為相對(duì)地址并再次請(qǐng)求處理。

再根據(jù)模塊package.json中的入口文件選項(xiàng)獲取要加載的文件。

對(duì)于開發(fā)者而言,整體沒有大的變化。
資源加載方式解析
CSS文件導(dǎo)入
vite中可以直接導(dǎo)入.css文件,樣式將影響導(dǎo)入的頁(yè)面,最終會(huì)被打包到style.css。

在我們程序中,除了全局樣式大部分樣式都是以形式存在于SFC中
CSS Module
SFC中使用CSS Module
<style module>
復(fù)制代碼
范例:修改組件樣式為CSS Module形式


JS中導(dǎo)入CSS Module:將css文件命名為*.module.css即可

CSS預(yù)處理器
安裝對(duì)應(yīng)的預(yù)處理器就可以直接在vite項(xiàng)目中使用。
<style lang="scss">
/* use scss */
</style>
復(fù)制代碼
或者在JS中導(dǎo)入
import './style.scss'
復(fù)制代碼
PostCSS
Vite自動(dòng)對(duì) *.vue 文件和導(dǎo)入的.css 文件應(yīng)用PostCSS配置,我們只需要安裝必要的插件和添加 postcss.config.js 文件即可。
module.exports = {
plugins: [
require('autoprefixer'),
]
}
復(fù)制代碼
npm i postcss [email protected]
復(fù)制代碼
資源URL處理
引用靜態(tài)資源
我們可以在*.vue 文件的template, style和純.css文件中以相對(duì)和絕對(duì)路徑方式引用靜態(tài)資源。
<!-- 相對(duì)路徑 -->
<img src="./assets/logo.png">
<!-- 絕對(duì)路徑 -->
<img src="/src/assets/logo.png">
復(fù)制代碼
<style scoped>
#app {
background-image: url('./assets/logo.png');
}
</style>
復(fù)制代碼
public目錄
public 目錄下可以存放未在源碼中引用的資源,它們會(huì)被留下且文件名不會(huì)有哈希處理。
這些文件會(huì)被原封不動(dòng)拷貝到發(fā)布目錄的根目錄下。
<img src="/logo.png">
復(fù)制代碼
注意引用放置在
public下的文件需要使用絕對(duì)路徑,例如public/icon.png應(yīng)該使用/icon.png引用
代碼規(guī)范:eslint
我們借助eslint規(guī)范項(xiàng)目代碼,通過prettier做代碼格式化。
首先在項(xiàng)目安裝依賴,package.json
{
"scripts": {
"lint": "eslint \"src/**/*.{js,vue}\""
},
"devDependencies": {
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
"prettier": "^1.19.1"
}
}
復(fù)制代碼
然后配置lint規(guī)則,.eslintrc.js
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint",
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"prettier/prettier": [
"warn",
{
// singleQuote: none,
// semi: false,
trailingComma: "es5",
},
],
},
};
復(fù)制代碼
如果有必要還可以配置prettier.config.js修改prettier的默認(rèn)格式化規(guī)則
module.exports = {
printWidth: 80, // 每行代碼長(zhǎng)度(默認(rèn)80)
tabWidth: 2, // 每個(gè)tab相當(dāng)于多少個(gè)空格(默認(rèn)2)
useTabs: false, // 是否使用tab進(jìn)行縮進(jìn)(默認(rèn)false)
singleQuote: false, // 使用單引號(hào)(默認(rèn)false)
semi: true, // 聲明結(jié)尾使用分號(hào)(默認(rèn)true)
trailingComma: 'es5', // 多行使用拖尾逗號(hào)(默認(rèn)none)
bracketSpacing: true, // 對(duì)象字面量的大括號(hào)間使用空格(默認(rèn)true)
jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的結(jié)尾,而不是另起一行(默認(rèn)false)
arrowParens: "avoid", // 只有一個(gè)參數(shù)的箭頭函數(shù)的參數(shù)是否帶圓括號(hào)(默認(rèn)avoid)
};
復(fù)制代碼
測(cè)試環(huán)境
利用jest和@vue/test-utils測(cè)試組件
安裝依賴
"jest": "^24.0.0",
"vue-jest": "^5.0.0-alpha.3",
"babel-jest": "^26.1.0",
"@babel/preset-env": "^7.10.4",
"@vue/test-utils": "^2.0.0-beta.9"
復(fù)制代碼
配置babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env", {
targets: {
node: "current"
}
}
]
],
};
復(fù)制代碼
配置jest.config.js
module.exports = {
testEnvironment: "jsdom",
transform: {
"^.+\\.vue$": "vue-jest",
"^.+\\js$": "babel-jest",
},
moduleFileExtensions: ["vue", "js", "json", "jsx", "ts", "tsx", "node"],
testMatch: ["**/tests/**/*.spec.js", "**/__tests__/**/*.spec.js"],
moduleNameMapper: {
"^main(.*)$": "<rootDir>/src$1",
},
};
復(fù)制代碼
啟動(dòng)腳本
"test": "jest --runInBand"
復(fù)制代碼
測(cè)試代碼,tests/example.spec.js
import HelloWorld from "main/components/HelloWorld.vue";
import { shallowMount } from "@vue/test-utils";
describe("aaa", () => {
test("should ", () => {
const wrapper = shallowMount(HelloWorld, {
props: {
msg: "hello,vue3",
},
});
expect(wrapper.text()).toMatch("hello,vue3");
});
});
復(fù)制代碼
lint配置添加jest環(huán)境,要不然會(huì)有錯(cuò)誤提示:
module.exports = {
env: {
jest: true
},
}
復(fù)制代碼
將lint、test和git掛鉤
npm i lint-staged yorkie -D
復(fù)制代碼
"gitHooks": {
"pre-commit": "lint-staged",
"pre-push": "npm run test"
},
"lint-staged": {
"*.{js,vue}": "eslint"
},
復(fù)制代碼
正常情況下安裝 yorkie 后會(huì)自動(dòng)安裝提交鉤子 如果提交鉤子未生效可以手動(dòng)運(yùn)行
node node_modules/yorkie/bin/install.js來安裝。 當(dāng)然,你也可以運(yùn)行node node_modules/yorkie/bin/uninstall.js來卸載提交鉤子。
typescript整合
ite可直接導(dǎo)入.ts 文件,在SFC中通過<script lang="ts">使用
范例:使用ts創(chuàng)建一個(gè)組件
<script lang="ts">
import { defineComponent } from 'vue'
interface Course {
id: number;
name: string;
}
export default defineComponent({
setup() {
const state = ref<Course[]>([]);
setTimeout(() => {
state.value.push({ id: 1, name: "全棧架構(gòu)師" });
}, 1000);
},
});
</script>
復(fù)制代碼
ts版本指定,package.json
{
"devDependencies": {
"typescript": "^3.9.7"
}
}
復(fù)制代碼
ts參考配置,tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"isolatedModules": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"experimentalDecorators": true,
"lib": ["dom", "esnext"]
},
"exclude": ["node_modules", "dist"]
}
復(fù)制代碼
項(xiàng)目配置
項(xiàng)目根目錄創(chuàng)建vite.config.js,可以對(duì)vite項(xiàng)目進(jìn)行深度配置。
定義別名
導(dǎo)入的別名,避免出現(xiàn)大量相對(duì)路徑,優(yōu)雅且不易出錯(cuò)
給src/components定義別名,vite.config.js
const path = require("path");
module.exports = {
alias: {
// 路徑映射必須以/開頭和結(jié)尾
"/comps/": path.resolve(__dirname, "src/components"),
},
};
復(fù)制代碼
使用
import CourseAdd from "/comps/CourseAdd.vue";
import Comp from "/comps/Comp.vue";
復(fù)制代碼
代理
配置服務(wù)器代理,vite.config.js
export default {
proxy: {
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
復(fù)制代碼
使用
fetch("/api/users")
.then(response => response.json())
.then(json => console.log(json));
復(fù)制代碼
數(shù)據(jù)mock
安裝依賴
npm i mockjs -S
npm i vite-plugin-mock cross-env -D
復(fù)制代碼
引入插件,vite.config.js
plugins: [
createMockServer({
// close support .ts file
supportTs: false,
}),
],
復(fù)制代碼
設(shè)置環(huán)境變量,package.json
"dev": "cross-env NODE_ENV=development vite"
復(fù)制代碼
創(chuàng)建mock文件,mock/test.js
export default [
{
url: "/api/users",
method: "get",
response: req => {
return {
code: 0,
data: [
{
name: "tom",
},
{
name: "jerry",
},
],
};
},
},
{
url: "/api/post",
method: "post",
timeout: 2000,
response: {
code: 0,
data: {
name: "vben",
},
},
},
];
復(fù)制代碼
模式和環(huán)境變量
使用模式做多環(huán)境配置,vite serve時(shí)模式默認(rèn)是development,vite build時(shí)是production。
創(chuàng)建配置文件.env.development
VITE_TOKEN=this is token
復(fù)制代碼
代碼中讀取
import.meta.env.VITE_TOKEN
復(fù)制代碼
打包和部署
打包
使用npm run build執(zhí)行打包
部署
手動(dòng)上傳dist中的內(nèi)容到服務(wù)器,再配置好nginx當(dāng)然可以,但是這一過程最好自動(dòng)化處理,避免前面這些繁瑣的操作。我們這里利用github actions實(shí)現(xiàn)ci/cd過程。
Github Actions讓我們可以在Github倉(cāng)庫(kù)中直接創(chuàng)建自定義的軟件開發(fā)生命周期工作流程。

準(zhǔn)備工作:
阿里云linux服務(wù)器
linux操作
阿里云相關(guān)操作
第一步:配置workflow,下面的配置可以在我們push代碼時(shí)自動(dòng)打包我們應(yīng)用并部署到阿里云服務(wù)器上,在項(xiàng)目根目錄下創(chuàng)建.github/workflows/publish.yml
name: 打包應(yīng)用并上傳阿里云
on:
push:
branches:
- master
jobs:
build:
# runs-on 指定job任務(wù)運(yùn)行所需要的虛擬機(jī)環(huán)境(必填字段)
runs-on: ubuntu-latest
steps:
# 獲取源碼
- name: 遷出代碼
# 使用action庫(kù) actions/checkout獲取源碼
uses: actions/checkout@master
# 安裝Node10
- name: 安裝node.js
# 使用action庫(kù) actions/setup-node安裝node
uses: actions/setup-node@v1
with:
node-version: 14.0.0
# 安裝依賴
- name: 安裝依賴
run: npm install
# 打包
- name: 打包
run: npm run build
# 上傳阿里云
- name: 發(fā)布到阿里云
uses: easingthemes/[email protected]
env:
# 私鑰
SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
# scp參數(shù)
ARGS: "-avzr --delete"
# 源目錄
SOURCE: "dist"
# 服務(wù)器ip:換成你的服務(wù)器IP
REMOTE_HOST: "47.98.252.43"
# 用戶
REMOTE_USER: "root"
# 目標(biāo)地址
TARGET: "/root/vue-in-action"
復(fù)制代碼
第二步:在github當(dāng)前項(xiàng)目下設(shè)置私鑰選項(xiàng)

復(fù)制本地私鑰,~/.ssh/id_rsa
# ssh秘鑰生成過程自行百度
cd .ssh/
cat id_rsa
復(fù)制代碼

復(fù)制并填寫到github-secretes

第三步:在阿里云服務(wù)器上配置nginx
登錄服務(wù)器
ssh [email protected] # ip換成你的
復(fù)制代碼
配置nginx
cd /etc/nginx/sites-enabled/
vi vue-in-action
復(fù)制代碼
添加如下配置
server {
listen 8080;
server_name 47.98.252.43;
location / {
root /root/vue-in-action/dist/;
index index.html index.htm;
}
}
復(fù)制代碼
重啟
nginx:nginx \-s reload
第四步:push代碼,觸發(fā)workflow

大功告成,激動(dòng),趕緊驗(yàn)證一下上傳結(jié)果
image-20201122221429983 訪問:47.98.252.43:8080 試試效果吧?。?!


