前端工程化~強(qiáng)大的glob語(yǔ)法

glob 在正則出現(xiàn)之前就有了,主要用于匹配文件路徑,例如大名鼎鼎的 gulp 就使用了 glob 規(guī)則來(lái)匹配、查找并處理各種后綴的文件。在前端工程化的過(guò)程中,不可避免地會(huì)用 Node.js 來(lái)讀取文件,例如想找到 src 目錄下所有 jsjsx 文件,代碼應(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.jsa9.js、b1.jsb9.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è)片段,分別是 srcindex.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.jssrc/index.md,但不能匹配 src/index.jsx

中括號(hào)

概念:同樣是匹配單個(gè)片段中的單個(gè)字符,但是字符集只能從括號(hào)內(nèi)選擇,如果字符集內(nèi)有-,表示范圍。

示例:

  • test/[bc]at.js 只能匹配test/bat.jstest/cat.js
  • test/[c-f]at.js 能匹配 test/cat.js、test/dat.jstest/eat.jstest/fat.js

驚嘆號(hào)

概念:表示取反,即排除那些去掉驚嘆號(hào)之后能夠匹配到的文件。
示例:

  • test/[!bc]at.js不能匹配 test/bat.jstest/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.pnga.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)小福利~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容