一直以來,前端工程中的配置大多都是 .js
檔案或者 .json
檔案,最常見的比如:
- package.json
- babel.config.js
- webpack.config.js
這些配置對前端非常友好,因為都是我們熟悉的 JS 物件結構。一般靜態化的配置會選擇 json 檔案,而動態化的配置,涉及到引入其他模組,因此會選擇 js 檔案。
還有現在許多新工具同時支援多種配置,比如 Eslint
,兩種格式的配置任你選擇:
- .eslintrc.json
- .eslintrc.js
後來不知道什麼時候,突然出現了一種以 .yaml
或 .yml
為字尾的配置檔案。一開始以為是某個程式的專有配置,後來發現這個字尾的檔案出現的頻率越來越高,甚至 Eslint 也支援了第三種格式的配置 .eslintrc.yml
。
既然遇到了,那就探索它!
下面我們從 YAML 的出現背景,使用場景,具體用法,高階操作四個方面,看一下這個流行的現代化配置的神祕之處。
出現背景
一個新工具的出現避免不了有兩個原因:
- 舊工具在某些場景表現吃力,需要更優的替代方案
- 舊工具也沒什麼不好,只是新工具出現,比較而言顯得它不太好
YAML 這種新工具就屬於後者。其實在 yaml 出現之前 js+json
用的也不錯,也沒什麼特別難以處理的問題;但是 yaml 出現以後,開始覺得它好亂呀什麼東西,後來瞭解它後,越用越喜歡,一個字就是優雅。
很多文章說選擇 yaml 是因為 json 的各種問題,json 不適合做配置檔案,這我覺得有些言過其實了。我更願意將 yaml 看做是 json 的升級,因為 yaml 在格式簡化和體驗上表現確實不錯,這個得承認。
下面我們對比 YAML 和 JSON,從兩方面分析:
精簡了什麼?
JSON 比較繁瑣的地方是它嚴格的格式要求。比如這個物件:
{
name: 'ruims'
}
在 JSON 中以下寫法通通都是錯的:
// key 沒引號不行
{
name: 'ruims'
}
// key 不是 "" 號不行
{
'name': 'ruims'
}
// value 不是 "" 號不行
{
"name": 'ruims'
}
字串的值必須 k->v 都是 ""
才行:
// 只能這樣
{
"name": "ruims"
}
雖然是統一格式,但是使用上確實有不便利的地方。比如我在瀏覽器上測出了介面錯誤。然後把引數拷貝到 Postman 裡除錯,這時就我要手動給每個屬性和值加 "" 號,非常繁瑣。
YAML 則是另闢蹊徑,直接把字串符號幹掉了。上面物件的同等 yaml 配置如下:
name: ruims
沒錯,就這麼簡單!
除了 ""
號,yaml 覺得 {}
和 []
這種符號也是多餘的,不如一起幹掉。
於是呢,以這個物件陣列為例:
{
"names": [{ "name": "ruims" }, { "name": "ruidoc" }]
}
轉換成 yaml 是這樣的:
names:
- name: ruims
- name: ruidoc
對比一下這個精簡程度,有什麼理由不愛它?
增加了什麼?
說起增加的部分,最值得一提的,是 YAML 支援了 註釋
。
用 JSON 寫配置是不能有註釋的,這就意味著我們的配置不會有備註,配置多了會非常凌亂,這是最不人性化的地方。
現在 yaml 支援了備註,以後配置可以是這樣的:
# 應用名稱
name: my_app
# 應用埠
port: 8080
把這種配置丟給新同事,還怕他看不懂配了啥嗎?
除註釋外,還支援配置複用的相關功能,這個後面說。
使用場景
我接觸的第一個 yaml 配置是 Flutter 專案的包管理檔案 pubspec.yaml
,這個檔案的作用和前端專案中的 package.json
一樣,用於存放一些全域性配置和應用依賴的包和版本。
看一下它的基本結構:
name: flutter_demo
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0
dependencies:
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_lints: ^1.0.0
你看這個結構和 package.json
是不是基本一致?dependencies
下列出應用依賴和版本,dev_dependencies
下的則是開發依賴。
後來在做 CI/CD 自動化部署的時候,我們用到了 GitHub Action。它需要多個 yaml 檔案來定義不同的工作流,這個配置可比 flutter 複雜的多。
其實不光 GitHub Action,其他流行的類似的構建工具如 GitLab CI/CD,circleci,全部都是齊刷刷的 yaml 配置,因此如果你的專案要做 CI/CD 持續整合,不懂 yaml 語法肯定是不行的。
還有,接觸過 Docker 的同學肯定知道 Docker Compose,它是 Docker 官方的單機編排工具,其配置檔案 docker-compose.yml
也是妥妥的 yaml 格式。現在 Docker 正是如日中天的時候,使用 Docker 必然免不了編排,因此 yaml 語法早晚也要攻克。
上面說的這 3 個案例,幾乎都是現代最新最流行的框架/工具。從它們身上可以看出來,yaml 必然是下一代配置檔案的標準,並且是前端-後端-運維的通用標準。
說了這麼多,你躍躍欲試了嗎?下面我們詳細介紹 yaml 語法。
YAML 語法
介紹 yaml 語法會對比 json 解釋,以便我們快速理解。
先看一下 yaml 的幾個特點:
- 大小寫敏感
- 使用縮排表示層級關係
- 縮排空格數不強制,但相同層級要對齊
#
表示註釋
相比於 JSON 來說,最大的區別是用 縮排
來表示層級,這個和 Python 非常接近。還有強化的一點是支援了註釋,JSON 預設是不支援的(雖然 TS 支援),這也對配置檔案非常重要。
YAML 支援以下幾種資料結構:
物件
:json 中的物件陣列
:json 中的陣列純量
:json 中的簡單型別(字串,數值,布林等)
物件
先看物件,上一個 json 例子:
{
"id": 1,
"name": "楊成功",
"isman": true
}
轉換成 yaml:
id: 1
name: 楊成功
isman: true
物件是最核心的結構,key 值的表示方法是 [key]:
,注意這裡冒號後面有個空格,一定不能少。value 的值就是一個純量
,且預設不需要引號。
陣列
陣列和物件的結構差不多,區別是在 key 前用一個 -
符號標識這個是陣列項。注意這裡也有一個空格,同樣也不能少。
- hello
- world
轉換成 JSON 格式如下:
["hello", "world"]
瞭解了基本的物件和陣列,我們再來看一個複雜的結構。
眾所周知,在實際專案配置中很少有簡單的物件或陣列,大多都是物件和陣列相互巢狀而成。在 js 中我們稱之為物件陣列,而在 yaml 中我們叫 複合結構
。
比如這樣一個稍複雜的 JSON:
{
"name": "楊成功",
"isman": true,
"age": 25,
"tag": ["陽光", "帥氣"],
"address": [
{ "c": "北京", "a": "海淀區" },
{ "c": "天津", "a": "濱海新區" }
]
}
轉換成複合結構的 YAML:
name: 楊成功
isman: true
age: 25
tag:
- 陽光
- 帥氣
address:
- c: 北京
a: 海淀區
- c: 天津
a: 濱海新區
若你想嘗試更復雜結構的轉換,可以在 這個 網頁中線上實踐。
純量
純量比較簡單,對應的就是 js 的基本資料型別,支援如下:
- 字串
- 布林
- 數值
- null
- 時間
比較特殊的兩個,null 用 ~
符號表示,時間大多用 2021-12-21
這種格式表示,如:
who: ~
date: 2019-09-10
轉換成 JS 後:
{
who: null,
date: new Date('2019-09-10')
}
高階操作
在 yaml 實戰過程中,遇到過一些特殊場景,可能需要一些特殊的處理。
字串過長
在 shell 中我們常見到一些引數很多,然後特別長的命令,如果命令都寫在一行的話可讀性會非常差。
假設下面的是一條長命令:
$ docker run --name my-nginx -d nginx
在 linux 中可以這樣處理:
$ docker run \
--name my-nginx \
-d nginx
就是在每行後加 \
符號標識換行。然而在 YAML 中更簡單,不需要加任何符號,直接換行即可:
cmd: docker run
--name my-nginx
-d nginx
YAML 預設會把換行符轉換成空格
,因此轉換後 JSON 如下,正是我們需要的:
{ "cmd": "docker run --name my-nginx -d nginx" }
然而有時候,我們的需求是保留換行符,並不是把它轉換成空格,又該怎麼辦呢?
這個也簡單,只需要在首行加一個 |
符號:
cmd: |
docker run
--name my-nginx
-d nginx
轉換成 JSON 變成了這樣:
{ "cmd": "docker run\n--name my-nginx\n-d nginx" }
獲取配置
獲取配置是指,在 YAML 檔案中定義的某個配置,如何在程式碼(JS)裡獲取?
比如前端在 package.json
裡有一個 version
的配置項表示應用版本,我們要在程式碼中獲取版本,可以這麼寫:
import pack from './package.json'
console.log(pack.version)
JSON 是可以直接匯入的,YAML 可就不行了,那怎麼辦呢?我們分環境解析:
在瀏覽器中
瀏覽器中程式碼用 webapck 打包,因此加一個 loader 即可:
$ yarn add -D yaml-loader
然後配置 loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.ya?ml$/,
type: 'json', // Required by Webpack v4
use: 'yaml-loader'
}
]
}
}
在元件中使用:
import pack from './package.yaml'
console.log(pack.version)
在 Node.js 中
Node.js 環境下沒有 Webpack,因此讀取 yaml 配置的方法也不一樣。
首先安裝一個 js-yaml
模組:
$ yarn add js-yaml
然後通過模組提供的方法獲取:
const yaml = require('js-yaml')
const fs = require('fs')
const doc = yaml.load(fs.readFileSync('./package.yaml', 'utf8'))
console.log(doc.version)
配置項複用
配置項複用的意思是,對於定義過的配置,在後面的配置直接引用,而不是再寫一遍,從而達到複用的目的。
YAML 中將定義的複用項稱為錨點,用&
標識;引用錨點則用 *
標識。
name: &name my_config
env: &env
version: 1.0
compose:
key1: *name
key2: *env
對應的 JSON 如下:
{
"name": "my_config",
"env": { "version": 1 },
"compose": { "key1": "my_config", "key2": { "version": 1 } }
}
但是錨點有個弊端,就是不能作為 變數
在字串中使用。比如:
name: &name my_config
compose:
key1: *name
key2: my name is *name
此時 key2 的值就是普通字串 _my name is *name_,引用變得無效了。
其實在實際開發中,字串中使用變數還是很常見的。比如在複雜的命令中多次使用某個路徑,這個時候這個路徑就應該是一個變數,在多個命令中複用。
GitHub Action 中有這樣的支援,定義一個環境變數,然後在其他的地方複用:
env:
NAME: test
describe: This app is called ${NAME}
這種實現方式與 webpack 中使用環境變數類似,在構建的時候將變數替換成對應的字串。
如果本文對你有啟發,請甩手一個贊 ?
如有疑問或轉發,請加微信 ruidoc
聯絡~