glob 在正則出現(xiàn)之前就有了,主要用于匹配文件路徑,例如大名鼎鼎的 gulp 就使用了 glob 規(guī)則來(lái)匹配、查找并處理各種后綴的文件。在前端工程化的過(guò)程中,不可避免地會(huì)用 Node.js 來(lái)讀取文件,例如想找到 src 目錄下所有 js 和 jsx 文件,代碼應(yīng)該怎么寫(xiě)呢?首先安裝依賴(lài)包:
yarn add glob
然后 3 行代碼搞定:
const glob = require('glob')
const files = glob.sync('src/**/*.js{,x}')
console.log(files)
有沒(méi)有感覺(jué)很強(qiáng)大呢?更重要的是 glob 語(yǔ)法在命令行就支持,不需要安裝任何依賴(lài),例如老板讓你創(chuàng)建 a1.js 到 a9.js、b1.js 到 b9.js 這 18 個(gè)測(cè)試文件的話(huà),怎么操作?一個(gè)個(gè)創(chuàng)建的話(huà)太傻了,glob 一句話(huà)就搞定:
$ touch {a,b}{1..9}.js
$ ls
a1.js a3.js a5.js a7.js a9.js b2.js b4.js b6.js b8.js
a2.js a4.js a6.js a8.js b1.js b3.js b5.js b7.js b9.js
更更更重要的是,glob 的語(yǔ)法非常簡(jiǎn)單,只要記住下面7個(gè)符號(hào)代表的含義就能掌握了:
- 基礎(chǔ)語(yǔ)法:
/、*、?、[] - 拓展語(yǔ)法:
**、{}、()
接下來(lái)就逐個(gè)解釋一下:
基礎(chǔ)語(yǔ)法
分隔符和片段
概念:分隔符是/,通過(guò)split('/') 得到的數(shù)組每一項(xiàng)是片段。
示例:
-
src/index.js有兩個(gè)片段,分別是src和index.js -
src/**/*.js有三個(gè)片段,分別是src、**和*.js
單個(gè)星號(hào)
概念:?jiǎn)蝹€(gè)星號(hào)* 用于匹配單個(gè)片段中的零個(gè)或多個(gè)字符。
示例:
-
src/*.js表示src目錄下所有以js結(jié)尾的文件,但是不能匹配src子目錄中的文件,例如src/login/login.js -
/home/*/.bashrc匹配所有用戶(hù)的.bashrc文件
需要注意的是,* 不能匹配分隔符/,也就是說(shuō)不能跨片段匹配字符。
問(wèn)號(hào)
概念:?jiǎn)柼?hào) ?匹配單個(gè)片段中的單個(gè)字符。
示例:
-
test/?at.js匹配形如test/cat.js、test/bat.js等所有3個(gè)字符且后兩位是at的 js 文件,但是不能匹配test/flat.js -
src/index.??匹配 src 目錄下以index打頭,后綴名是兩個(gè)字符的文件,例如可以匹配src/index.js和src/index.md,但不能匹配src/index.jsx
中括號(hào)
概念:同樣是匹配單個(gè)片段中的單個(gè)字符,但是字符集只能從括號(hào)內(nèi)選擇,如果字符集內(nèi)有-,表示范圍。
示例:
-
test/[bc]at.js只能匹配test/bat.js和test/cat.js -
test/[c-f]at.js能匹配test/cat.js、test/dat.js、test/eat.js和test/fat.js
驚嘆號(hào)
概念:表示取反,即排除那些去掉驚嘆號(hào)之后能夠匹配到的文件。
示例:
-
test/[!bc]at.js不能匹配test/bat.js和test/cat.js,但是可以匹配test/fat.js -
!test/tmp/**'排除test/tmp目錄下的所有目錄和文件
擴(kuò)展語(yǔ)法
基礎(chǔ)語(yǔ)法非常簡(jiǎn)單好記,但是功能非常局限,為了豐富 glob 的功能,衍生了下面三種擴(kuò)展語(yǔ)法:
兩個(gè)星號(hào)
概念:兩個(gè)星號(hào)** 可以跨片段匹配零個(gè)或多個(gè)字符,也就是說(shuō)**是遞歸匹配所有文件和目錄的,如果后面有分隔符,即 **/ 的話(huà),則表示只遞歸匹配所有目錄(不含隱藏目錄)。
示例:
-
/var/log/**匹配/var/log目錄下所有文件和文件夾,以及文件夾里面所有子文件和子文件夾 -
/var/log/**/*.log匹配/var/log及其子目錄下的所有以.log結(jié)尾的文件 -
/home/*/.ssh/**/*.key匹配所有用戶(hù)的.ssh目錄及其子目錄內(nèi)的以.key結(jié)尾的文件
大括號(hào)
概念:匹配大括號(hào)內(nèi)的所有模式,模式之間用逗號(hào)進(jìn)行分隔,支持大括號(hào)嵌套,支持用.. 匹配連續(xù)的字符,即{start..end} 語(yǔ)法。
示例:
-
a.{png,jp{,e}g}匹配a.png、a.jpg、a.jpeg -
{a..c}{1..2}匹配a1 a2 b1 b2 c1 c2
注意:{} 與 [] 有一個(gè)很重要的區(qū)別:如果匹配的文件不存在,[]會(huì)失去模式的功能,變成一個(gè)單純的字符串,而 {} 依然可以展開(kāi)。
小括號(hào)
概念:小括號(hào)必須跟在 ?、*、+、@、! 后面使用,且小括號(hào)里面的內(nèi)容是一組以 | 分隔符的模式集合,例如:abc|a?c|ac*。
示例:
-
?(pattern|pattern|pattern):匹配0次或1次給定的模式 -
*(pattern|pattern|pattern):匹配0次或多次給定的模式 -
+(pattern|pattern|pattern):匹配1次或多次給定的模式 -
@(pattern|pattern|pattern):嚴(yán)格匹配給定的模式 -
!(pattern|pattern|pattern):匹配非給定的模式
應(yīng)用場(chǎng)景
webpack 多頁(yè)面應(yīng)用自動(dòng)打包配置
在一個(gè) webpack 項(xiàng)目中,假如我們有多個(gè)入口,每個(gè)入口都有一個(gè) index.html 模板和 index.js 文件,而且這個(gè)入口是動(dòng)態(tài)變化的,不希望每增加一個(gè)入口就改 webpack.config.js 配置文件,應(yīng)該怎么辦呢?
此時(shí)可以約定在 src 下面創(chuàng)建的文件夾,只要里面有 index.js,我們就把它當(dāng)做一個(gè)入口文件進(jìn)行打包:
src
├── detail
│ ├── index.html
│ └── index.js
├── home
│ ├── index.html
│ └── index.js
├── login
│ ├── index.html
│ └── index.js
├── shop
│ ├── index.html
│ └── index.js
用 glob 很快就能寫(xiě)出下面的自動(dòng)打包代碼:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const glob = require('glob')
// 動(dòng)態(tài)生成 entry 和 html-webpack-plugin
function getMpa() {
const entry = {}, htmlPlugins = []
const files = glob.sync('src/*/index.js')
files.forEach((file) => {
const filename = file.split('/')[1]
entry[filename] = path.join(__dirname, file)
htmlPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${filename}/index.html`),
filename: `${filename}.html`,
chunks: [filename],
})
)
})
return { entry, htmlPlugins }
}
const mpa = getMpa()
// 動(dòng)態(tài)的配置文件
module.exports = {
entry: mpa.entry,
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]-[hash:6].js',
},
plugins: [...mpa.htmlPlugins],
}
這樣,無(wú)論增加多少個(gè)入口,webpack.config.js 都不用變。
手寫(xiě)一個(gè)約定大于配置的 Node.js 框架
egg.js 是一款優(yōu)秀的 Node.js 企業(yè)級(jí)開(kāi)發(fā)框架,就應(yīng)用了約定大于配置的思想,例如:
- 約定一個(gè)中間件是一個(gè)放置在
app/middleware目錄下的單獨(dú)文件 - 約定了
app/router.js文件用于統(tǒng)一所有路由規(guī)則 - 約定 Service 文件必須放在
app/service目錄,可以支持多級(jí)目錄,訪(fǎng)問(wèn)的時(shí)候可以通過(guò)目錄名級(jí)聯(lián)訪(fǎng)問(wèn)
因?yàn)橐粋€(gè)大規(guī)模的團(tuán)隊(duì)需要遵循一定的約束和約定,開(kāi)發(fā)效率才更高,有了這些約定之后,我們就可以利用 glob 寫(xiě)出匹配規(guī)則,找到用戶(hù)放到指定目錄下的文件并進(jìn)行動(dòng)態(tài)加載了,一個(gè)最基礎(chǔ)的 load 函數(shù)如下:
function load(folder, options) {
const extname = options.extname || '.{js,ts}'
return glob.sync(require('path').join(folder, `./**/*.js`)).forEach((item) => require(item))
}
使用 glob,配合相應(yīng)的規(guī)范,例如 RESTful,我們自己也能封裝一個(gè)簡(jiǎn)易的、基于約定的 Node.js 框架了。
放在最后
贈(zèng)與小伙伴們的一點(diǎn)點(diǎn)小福利~
