'node:path'
API 的三種方式'node:os'
取得標準目錄的路徑path.parse()
:建立包含路徑部分的物件path.basename()
:擷取路徑的基礎path.dirname()
:擷取路徑的父目錄path.extname()
:擷取路徑的擴充名path.format()
:從部分建立路徑
file:
URL 來參照檔案
URL
file:
URL在本章中,我們將學習如何在 Node.js 上使用檔案系統路徑和檔案 URL。
在本章中,我們將探討 Node.js 上與路徑相關的功能
'node:path'
中。process
有用於變更 目前工作目錄 的方法(稍後會說明)。'node:os'
有傳回重要目錄路徑的函式。'node:path'
API 的三種方式模組 'node:path'
通常會以以下方式匯入
import * as path from 'node:path';
在本章中,這個匯入陳述式偶爾會被省略。我們也會省略以下匯入
import * as assert from 'node:assert/strict';
我們可以透過三種方式存取 Node 的路徑 API
我們可以存取 API 的特定於平台的版本
path.posix
支援包括 macOS 在內的 Unix。path.win32
支援 Windows。path
本身總是支援目前的平台。例如,這是 macOS 上的 REPL 互動
> path.parse === path.posix.parsetrue
讓我們看看函式 path.parse()
如何解析檔案系統路徑,它在兩個平台上有所不同
> path.win32.parse(String.raw`C:\Users\jane\file.txt`){
dir: 'C:\\Users\\jane',
root: 'C:\\',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
> path.posix.parse(String.raw`C:\Users\jane\file.txt`){
dir: '',
root: '',
base: 'C:\\Users\\jane\\file.txt',
name: 'C:\\Users\\jane\\file',
ext: '.txt',
}
我們解析一個 Windows 路徑 – 首先透過 path.win32
API 正確解析,然後透過 path.posix
API 解析。我們可以看到在後者的情況下,路徑沒有正確地分割成它的部分 – 例如,檔案的基本檔名應該是 file.txt
(稍後會詳細說明其他屬性的意義)。
術語
非空路徑包含一個或多個 路徑區段 – 通常是目錄或檔案的名稱。
路徑分隔符號用於分隔路徑中兩個相鄰的路徑區段。path.sep
包含當前平台的路徑分隔符號
.equal(
assert.posix.sep, '/' // Path separator on Unix
path;
).equal(
assert.win32.sep, '\\' // Path separator on Windows
path; )
路徑分隔符號分隔路徑清單中的元素。path.delimiter
包含當前平台的路徑分隔符號
.equal(
assert.posix.delimiter, ':' // Path delimiter on Unix
path;
).equal(
assert.win32.delimiter, ';' // Path delimiter on Windows
path; )
如果我們檢查 PATH shell 變數(其中包含作業系統在 shell 中輸入命令時尋找可執行檔的路徑),我們可以看到路徑分隔符號和路徑分隔符號。
以下是 macOS PATH(shell 變數 $PATH
)的範例
> process.env.PATH.split(/(?<=:)/)
['/opt/homebrew/bin:',
'/opt/homebrew/sbin:',
'/usr/local/bin:',
'/usr/bin:',
'/bin:',
'/usr/sbin:',
'/sbin',
]
分割分隔符號的長度為零,因為回顧斷言 (?<=:)
匹配給定位置前面是否有冒號,但不會擷取任何內容。因此,路徑分隔符號 ':'
包含在前一個路徑中。
以下是 Windows PATH(shell 變數 %Path%
)的範例
> process.env.Path.split(/(?<=;)/)
['C:\\Windows\\system32;',
'C:\\Windows;',
'C:\\Windows\\System32\\Wbem;',
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;',
'C:\\Windows\\System32\\OpenSSH\\;',
'C:\\ProgramData\\chocolatey\\bin;',
'C:\\Program Files\\nodejs\\',
]
許多 shell 都具有目前工作目錄 (CWD) 的概念,也就是「我目前所在的目錄」
cd
。process
是全域 Node.js 變數。它提供取得和設定 CWD 的方法
process.cwd()
傳回 CWD。process.chdir(dirPath)
將 CWD 變更為 dirPath
。
dirPath
中必須有目錄。Node.js 使用 CWD 填入路徑不是完全限定(完整)時的遺失部分。這讓我們可以使用部分限定路徑搭配各種函式,例如 fs.readFileSync()
。
以下程式碼示範 Unix 上的 process.chdir()
和 process.cwd()
process.chdir('/home/jane');
.equal(
assertprocess.cwd(), '/home/jane'
; )
到目前為止,我們已在 Unix 上使用目前工作目錄。Windows 的運作方式不同
我們可以使用 path.chdir()
同時設定兩者
process.chdir('C:\\Windows');
process.chdir('Z:\\tmp');
當我們重新拜訪一個磁碟時,Node.js 會記住該磁碟之前目前的目錄
.equal(
assertprocess.cwd(), 'Z:\\tmp'
;
)process.chdir('C:');
.equal(
assertprocess.cwd(), 'C:\\Windows'
; )
Unix 僅知道兩種路徑
絕對路徑 是完整限定路徑,且以斜線開頭
/home/john/proj
相對路徑 是部分限定路徑,且以檔案名稱或點開頭
. (current directory)
.. (parent directory)
dir
./dir
../dir
../../dir/subdir
讓我們使用 path.resolve()
(在 後面 會有更詳細的說明)來解析相對路徑與絕對路徑。結果是絕對路徑
> const abs = '/home/john/proj';
> path.resolve(abs, '.')'/home/john/proj'
> path.resolve(abs, '..')'/home/john'
> path.resolve(abs, 'dir')'/home/john/proj/dir'
> path.resolve(abs, './dir')'/home/john/proj/dir'
> path.resolve(abs, '../dir')'/home/john/dir'
> path.resolve(abs, '../../dir/subdir')'/home/dir/subdir'
Windows 區分四種類型的路徑(有關更多資訊,請參閱 Microsoft 的文件)
帶有磁碟機代號的絕對路徑是完整限定的。所有其他路徑都是部分限定的。
解析沒有磁碟機代號的絕對路徑針對完整限定路徑 full
,會選取 full
的磁碟機代號
> const full = 'C:\\Users\\jane\\proj';
> path.resolve(full, '\\Windows')'C:\\Windows'
解析沒有磁碟機代號的相對路徑針對完整限定路徑,可以視為更新後者
> const full = 'C:\\Users\\jane\\proj';
> path.resolve(full, '.')'C:\\Users\\jane\\proj'
> path.resolve(full, '..')'C:\\Users\\jane'
> path.resolve(full, 'dir')'C:\\Users\\jane\\proj\\dir'
> path.resolve(full, '.\\dir')'C:\\Users\\jane\\proj\\dir'
> path.resolve(full, '..\\dir')'C:\\Users\\jane\\dir'
> path.resolve(full, '..\\..\\dir')'C:\\Users\\dir'
解析帶有磁碟機代號的相對路徑 rel
針對完整限定路徑 full
取決於 rel
的磁碟機代號
full
相同的磁碟機代號?解析 rel
與 full
。full
不同的磁碟機代號?解析 rel
與 rel
磁碟機的目前目錄。如下所示
// Configure current directories for C: and Z:
process.chdir('C:\\Windows\\System');
process.chdir('Z:\\tmp');
const full = 'C:\\Users\\jane\\proj';
// Same drive letter
.equal(
assert.resolve(full, 'C:dir'),
path'C:\\Users\\jane\\proj\\dir'
;
).equal(
assert.resolve(full, 'C:'),
path'C:\\Users\\jane\\proj'
;
)
// Different drive letter
.equal(
assert.resolve(full, 'Z:dir'),
path'Z:\\tmp\\dir'
;
).equal(
assert.resolve(full, 'Z:'),
path'Z:\\tmp'
; )
'node:os'
取得標準目錄的路徑模組 'node:os'
提供我們兩個重要目錄的路徑
os.homedir()
傳回目前使用者的家目錄路徑 – 例如
> os.homedir() // macOS'/Users/rauschma'
> os.homedir() // Windows'C:\\Users\\axel'
os.tmpdir()
傳回作業系統的暫時檔案目錄路徑 – 例如
> os.tmpdir() // macOS'/var/folders/ph/sz0384m11vxf5byk12fzjms40000gn/T'
> os.tmpdir() // Windows'C:\\Users\\axel\\AppData\\Local\\Temp'
有兩個用於串接路徑的函式
path.resolve()
永遠傳回完整限定路徑path.join()
保留相對路徑path.resolve()
:串接路徑以建立完整限定路徑.resolve(...paths: Array<string>): string path
串接 paths
並傳回完整限定路徑。它使用下列演算法
path[0]
。path[1]
。若沒有參數,path.resolve()
會傳回目前工作目錄的路徑
> process.cwd()'/usr/local'
> path.resolve()'/usr/local'
一個或多個相對路徑會用於解析,從目前工作目錄開始
> path.resolve('.')'/usr/local'
> path.resolve('..')'/usr'
> path.resolve('bin')'/usr/local/bin'
> path.resolve('./bin', 'sub')'/usr/local/bin/sub'
> path.resolve('../lib', 'log')'/usr/lib/log'
任何完全限定的路徑會取代前一個結果
> path.resolve('bin', '/home')'/home'
這讓我們得以針對完全限定的路徑解析部分限定的路徑
> path.resolve('/home/john', 'proj', 'src')'/home/john/proj/src'
path.join()
:串接路徑,同時保留相對路徑.join(...paths: Array<string>): string path
從 paths[0]
開始,並將剩餘路徑解譯為上行或下行的指令。與 path.resolve()
相反,此函數會保留部分限定的路徑:如果 paths[0]
是部分限定的,結果也會是部分限定的。如果它是完全限定的,結果也會是完全限定的。
下行範例
> path.posix.join('/usr/local', 'sub', 'subsub')'/usr/local/sub/subsub'
> path.posix.join('relative/dir', 'sub', 'subsub')'relative/dir/sub/subsub'
兩個點表示上行
> path.posix.join('/usr/local', '..')'/usr'
> path.posix.join('relative/dir', '..')'relative'
一個點不執行任何動作
> path.posix.join('/usr/local', '.')'/usr/local'
> path.posix.join('relative/dir', '.')'relative/dir'
如果第一個參數之後的參數是完全限定的路徑,它們會被解譯為相對路徑
> path.posix.join('dir', '/tmp')'dir/tmp'
> path.win32.join('dir', 'C:\\Users')'dir\\C:\\Users'
使用超過兩個參數
> path.posix.join('/usr/local', '../lib', '.', 'log')'/usr/lib/log'
path.normalize()
:確保路徑已正規化.normalize(path: string): string path
在 Unix 上,path.normalize()
.
) 路徑區段。..
) 路徑區段。例如
// Fully qualified path
.equal(
assert.posix.normalize('/home/./john/lib/../photos///pet'),
path'/home/john/photos/pet'
;
)
// Partially qualified path
.equal(
assert.posix.normalize('./john/lib/../photos///pet'),
path'john/photos/pet'
; )
在 Windows 上,path.normalize()
.
) 路徑區段。..
) 路徑區段。/
)(這是合法的)轉換為偏好的路徑分隔符號 (\
)。例如
// Fully qualified path
.equal(
assert.win32.normalize('C:\\Users/jane\\doc\\..\\proj\\\\src'),
path'C:\\Users\\jane\\proj\\src'
;
)
// Partially qualified path
.equal(
assert.win32.normalize('.\\jane\\doc\\..\\proj\\\\src'),
path'jane\\proj\\src'
; )
請注意,具有單一參數的 path.join()
也會正規化,且與 path.normalize()
的運作方式相同
> path.posix.normalize('/home/./john/lib/../photos///pet')'/home/john/photos/pet'
> path.posix.join('/home/./john/lib/../photos///pet')'/home/john/photos/pet'
> path.posix.normalize('./john/lib/../photos///pet')'john/photos/pet'
> path.posix.join('./john/lib/../photos///pet')'john/photos/pet'
path.resolve()
(一個參數):確保路徑已正規化且完全限定我們已經遇到 path.resolve()
。如果呼叫此函數時只有一個參數,它會同時正規化路徑,並確保它們完全限定。
在 Unix 上使用 path.resolve()
> process.cwd()'/usr/local'
> path.resolve('/home/./john/lib/../photos///pet')'/home/john/photos/pet'
> path.resolve('./john/lib/../photos///pet')'/usr/local/john/photos/pet'
在 Windows 上使用 path.resolve()
> process.cwd()'C:\\Windows\\System'
> path.resolve('C:\\Users/jane\\doc\\..\\proj\\\\src')'C:\\Users\\jane\\proj\\src'
> path.resolve('.\\jane\\doc\\..\\proj\\\\src')'C:\\Windows\\System\\jane\\proj\\src'
path.relative()
:建立相對路徑.relative(sourcePath: string, destinationPath: string): string path
傳回一個相對路徑,讓我們從 sourcePath
到達 destinationPath
> path.posix.relative('/home/john/', '/home/john/proj/my-lib/README.md')'proj/my-lib/README.md'
> path.posix.relative('/tmp/proj/my-lib/', '/tmp/doc/zsh.txt')'../../doc/zsh.txt'
在 Windows 上,如果 sourcePath
和 destinationPath
在不同的磁碟機上,我們會取得一個完全限定路徑
> path.win32.relative('Z:\\tmp\\', 'C:\\Users\\Jane\\')'C:\\Users\\Jane'
此函式也可以用於相對路徑
> path.posix.relative('proj/my-lib/', 'doc/zsh.txt')'../../doc/zsh.txt'
path.parse()
:建立一個包含路徑部分的物件type PathObject = {
: string,
dir: string,
root: string,
base: string,
name: string,
ext;
}.parse(path: string): PathObject path
擷取 path
的各個部分,並在一個物件中傳回,物件包含下列屬性
.base
:路徑的最後一段
.ext
:基底的檔案名稱副檔名.name
:沒有副檔名的基底。此部分也稱為路徑的主檔名。.root
:路徑的開頭(第一個區段之前).dir
:基底所在的目錄 – 沒有基底的路徑稍後,我們會看到 函式 path.format()
,它是 path.parse()
的反函式:它會將包含路徑部分的物件轉換成一個路徑。
path.parse()
以下是在 Unix 上使用 path.parse()
的範例
> path.posix.parse('/home/jane/file.txt'){
dir: '/home/jane',
root: '/',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
下列圖示視覺化各個部分的範圍
/ home/jane / file .txt
| root | | name | ext |
| dir | base |
例如,我們可以看到 .dir
是沒有基底的路徑。而 .base
是 .name
加上 .ext
。
path.parse()
以下是在 Windows 上使用 path.parse()
的範例
> path.win32.parse(String.raw`C:\Users\john\file.txt`){
dir: 'C:\\Users\\john',
root: 'C:\\',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
這是結果的圖示
C:\ Users\john \ file .txt
| root | | name | ext |
| dir | base |
path.basename()
:擷取路徑的基底.basename(path, ext?) path
傳回 path
的基底
> path.basename('/home/jane/file.txt')'file.txt'
此函式也可以移除後綴字元(選擇性)
> path.basename('/home/jane/file.txt', '.txt')'file'
> path.basename('/home/jane/file.txt', 'txt')'file.'
> path.basename('/home/jane/file.txt', 'xt')'file.t'
移除副檔名時會區分大小寫 – 即使是在 Windows 上!
> path.win32.basename(String.raw`C:\Users\john\file.txt`, '.txt')'file'
> path.win32.basename(String.raw`C:\Users\john\file.txt`, '.TXT')'file.txt'
path.dirname()
:擷取路徑的父目錄.dirname(path) path
傳回 path
中檔案或目錄的父目錄
> path.win32.dirname(String.raw`C:\Users\john\file.txt`)'C:\\Users\\john'
> path.win32.dirname('C:\\Users\\john\\dir\\')'C:\\Users\\john'
> path.posix.dirname('/home/jane/file.txt')'/home/jane'
> path.posix.dirname('/home/jane/dir/')'/home/jane'
path.extname()
:擷取路徑的副檔名.extname(path) path
傳回 path
的副檔名
> path.extname('/home/jane/file.txt')'.txt'
> path.extname('/home/jane/file.')'.'
> path.extname('/home/jane/file')''
> path.extname('/home/jane/')''
> path.extname('/home/jane')''
path.isAbsolute()
:給定的路徑是否為絕對路徑?.isAbsolute(path: string): boolean path
如果 path
是絕對路徑,傳回 true
,否則傳回 false
。
在 Unix 上的結果很簡單
> path.posix.isAbsolute('/home/john')true
> path.posix.isAbsolute('john')false
在 Windows 上,「絕對」並不一定代表「完全限定」(只有第一個路徑是完全限定的)
> path.win32.isAbsolute('C:\\Users\\jane')true
> path.win32.isAbsolute('\\Users\\jane')true
> path.win32.isAbsolute('C:jane')false
> path.win32.isAbsolute('jane')false
path.format()
:從部分建立路徑type PathObject = {
: string,
dir: string,
root: string,
base: string,
name: string,
ext;
}.format(pathObject: PathObject): string path
從路徑物件建立路徑
> path.format({dir: '/home/jane', base: 'file.txt'})'/home/jane/file.txt'
我們可以使用 path.format()
來變更路徑的副檔名
function changeFilenameExtension(pathStr, newExtension) {
if (!newExtension.startsWith('.')) {
throw new Error(
'Extension must start with a dot: '
+ JSON.stringify(newExtension)
;
)
}const parts = path.parse(pathStr);
return path.format({
...parts,
base: undefined, // prevent .base from overriding .name and .ext
ext: newExtension,
;
})
}
.equal(
assertchangeFilenameExtension('/tmp/file.md', '.html'),
'/tmp/file.html'
;
).equal(
assertchangeFilenameExtension('/tmp/file', '.html'),
'/tmp/file.html'
;
).equal(
assertchangeFilenameExtension('/tmp/file/', '.html'),
'/tmp/file.html'
; )
如果我們知道原始檔案名稱副檔名,我們也可以使用正規表示式來變更檔案名稱副檔名
> '/tmp/file.md'.replace(/\.md$/i, '.html')'/tmp/file.html'
> '/tmp/file.MD'.replace(/\.md$/i, '.html')'/tmp/file.html'
有時候我們希望在不同平台上使用相同路徑。這時候我們會遇到兩個問題
舉例來說,考慮一個在有資料目錄中執行的 Node.js 應用程式。假設應用程式可以透過兩種路徑進行設定
由於上述問題
我們無法在不同平台之間重複使用完全限定路徑。
我們可以重複使用指向資料目錄的路徑。這些路徑可以儲存在設定檔(在資料目錄內或不在資料目錄內)和應用程式程式碼中的常數中。要做到這一點
下一個小節說明如何達成這兩點。
相對平台非相依路徑可以儲存為路徑區段陣列,並轉換成完全限定的平台特定路徑,如下所示
const universalRelativePath = ['static', 'img', 'logo.jpg'];
const dataDirUnix = '/home/john/data-dir';
.equal(
assert.posix.resolve(dataDirUnix, ...universalRelativePath),
path'/home/john/data-dir/static/img/logo.jpg'
;
)
const dataDirWindows = 'C:\\Users\\jane\\data-dir';
.equal(
assert.win32.resolve(dataDirWindows, ...universalRelativePath),
path'C:\\Users\\jane\\data-dir\\static\\img\\logo.jpg'
; )
若要建立相對平台特定路徑,我們可以使用
const dataDir = '/home/john/data-dir';
const pathInDataDir = '/home/john/data-dir/static/img/logo.jpg';
.equal(
assert.relative(dataDir, pathInDataDir),
path'static/img/logo.jpg'
; )
下列函式將相對平台特定路徑轉換成平台非相依路徑
import * as path from 'node:path';
function splitRelativePathIntoSegments(relPath) {
if (path.isAbsolute(relPath)) {
throw new Error('Path isn’t relative: ' + relPath);
}= path.normalize(relPath);
relPath const result = [];
while (true) {
const base = path.basename(relPath);
if (base.length === 0) break;
.unshift(base);
resultconst dir = path.dirname(relPath);
if (dir === '.') break;
= dir;
relPath
}return result;
}
在 Unix 上使用 splitRelativePathIntoSegments()
> splitRelativePathIntoSegments('static/img/logo.jpg')[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')[ 'file.txt' ]
在 Windows 上使用 splitRelativePathIntoSegments()
> splitRelativePathIntoSegments('static/img/logo.jpg')[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('C:static/img/logo.jpg')[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')[ 'file.txt' ]
> splitRelativePathIntoSegments('C:file.txt')[ 'file.txt' ]
npm 模組 'minimatch'
讓我們可以比對路徑與稱為 glob 表達式、glob 模式或 glob 的樣式
import minimatch from 'minimatch';
.equal(
assertminimatch('/dir/sub/file.txt', '/dir/sub/*.txt'), true
;
).equal(
assertminimatch('/dir/sub/file.txt', '/**/file.txt'), true
; )
glob 的使用案例
更多 glob 函式庫
Minimatch 的完整 API 記錄在 專案的自述檔案 中。在此小節中,我們將探討最重要的功能。
Minimatch 將 glob 編譯成 JavaScript RegExp
物件,並使用它們進行比對。
minimatch()
:編譯並比對一次minimatch(path: string, glob: string, options?: MinimatchOptions): boolean
如果 glob
與 path
相符,則傳回 true
,否則傳回 false
。
兩個有趣的選項
.dot: boolean
(預設值:false
)
如果為 true
,則萬用字元(例如 *
和 **
)會比對「隱藏」的路徑區段(其名稱以點號開頭)
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json')false
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json', {dot: true})true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt')false
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt', {dot: true})true
.matchBase: boolean
(預設值:false
)
如果為 true
,則不包含斜線的模式會與路徑的檔名比對
> minimatch('/dir/file.txt', 'file.txt')false
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})true
new minimatch.Minimatch()
:編譯一次,比對多次類別 minimatch.Minimatch
讓我們僅將 glob 編譯成正規表示式一次,並比對多次
new Minimatch(pattern: string, options?: MinimatchOptions)
以下是此類別的使用方式
import minimatch from 'minimatch';
const {Minimatch} = minimatch;
const glob = new Minimatch('/dir/sub/*.txt');
.equal(
assert.match('/dir/sub/file.txt'), true
glob;
).equal(
assert.match('/dir/sub/notes.txt'), true
glob; )
此小節涵蓋語法的基本要素。但還有更多功能。這些功能在此處有記錄
即使在 Windows 上,glob 區段也以斜線分隔,但它們會比對反斜線和斜線(這是 Windows 上合法的路徑分隔符號)
> minimatch('dir\\sub/file.txt', 'dir/sub/file.txt')true
Minimatch 沒有為我們正規化路徑
> minimatch('./file.txt', './file.txt')true
> minimatch('./file.txt', 'file.txt')false
> minimatch('file.txt', './file.txt')false
因此,如果我們沒有自己建立路徑,就必須正規化路徑
> path.normalize('./file.txt')'file.txt'
沒有萬用字元(可以更靈活地比對)的模式必須完全相符。特別是路徑分隔符號必須對齊
> minimatch('/dir/file.txt', '/dir/file.txt')true
> minimatch('dir/file.txt', 'dir/file.txt')true
> minimatch('/dir/file.txt', 'dir/file.txt')false
> minimatch('/dir/file.txt', 'file.txt')false
也就是說,我們必須決定使用絕對路徑或相對路徑。
使用選項 .matchBase
,我們可以比對路徑基本名稱中不含斜線的樣式
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})true
*
) 比對任何 (單一區段的) 部分萬用字元符號 星號 (*
) 比對任何路徑區段或任何區段的一部分
> minimatch('/dir/file.txt', '/*/file.txt')true
> minimatch('/tmp/file.txt', '/*/file.txt')true
> minimatch('/dir/file.txt', '/dir/*.txt')true
> minimatch('/dir/data.txt', '/dir/*.txt')true
星號不會比對名稱以點開頭的「隱藏檔案」。如果我們想要比對這些檔案,我們必須在星號前面加上一個點
> minimatch('file.txt', '*')true
> minimatch('.gitignore', '*')false
> minimatch('.gitignore', '.*')true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt')false
選項 .dot
讓我們可以關閉此行為
> minimatch('.gitignore', '*', {dot: true})true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt', {dot: true})true
**
) 比對零個或多個區段´**/
比對零個或多個區段
> minimatch('/file.txt', '/**/file.txt')true
> minimatch('/dir/file.txt', '/**/file.txt')true
> minimatch('/dir/sub/file.txt', '/**/file.txt')true
如果我們想要比對相對路徑,樣式仍然不能以路徑分隔符號開頭
> minimatch('file.txt', '/**/file.txt')false
雙星號不會比對名稱以點開頭的「隱藏」路徑區段
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json')false
我們可以使用選項 .dot
關閉此行為
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json', {dot: true})true
如果我們以驚嘆號開頭,glob 會比對驚嘆號後的樣式不比對的情況
> minimatch('file.txt', '!**/*.txt')false
> minimatch('file.js', '!**/*.txt')true
大括號內的逗號分隔樣式會在其中一個樣式比對時比對
> minimatch('file.txt', 'file.{txt,js}')true
> minimatch('file.js', 'file.{txt,js}')true
一對以雙點分隔的整數定義一個整數範圍,並在任何元素比對時比對
> minimatch('file1.txt', 'file{1..3}.txt')true
> minimatch('file2.txt', 'file{1..3}.txt')true
> minimatch('file3.txt', 'file{1..3}.txt')true
> minimatch('file4.txt', 'file{1..3}.txt')false
也支援以零填充
> minimatch('file1.txt', 'file{01..12}.txt')false
> minimatch('file01.txt', 'file{01..12}.txt')true
> minimatch('file02.txt', 'file{01..12}.txt')true
> minimatch('file12.txt', 'file{01..15}.txt')true
file:
URL 參照檔案在 Node.js 中有兩種常見的方式可以參照檔案
file:
的 URL
實例例如
.equal(
assert.readFileSync(
fs'/tmp/data.txt', {encoding: 'utf-8'}),
'Content'
;
).equal(
assert.readFileSync(
fsnew URL('file:///tmp/data.txt'), {encoding: 'utf-8'}),
'Content'
; )
URL
在本節中,我們將仔細探討類別 URL
。有關此類別的更多資訊
在本章中,我們透過全域變數存取類別 URL
,因為這是其他網路平台使用的方式。但也可以匯入
import {URL} from 'node:url';
URL 是 URI 的子集。URI 標準 RFC 3986 區分 兩種URI 參照
URL
的建構函式類別 URL
可用兩種方式實例化
new URL(uri: string)
uri
必須是 URI。它指定新實例的 URI。
new URL(uriRef: string, baseUri: string)
baseUri
必須是 URI。如果 uriRef
是相對參照,它會針對 baseUri
解析,而結果會成為新實例的 URI。
如果 uriRef
是 URI,它會完全取代 baseUri
,成為實例所根據的資料。
這裡我們可以看到類別的實際應用
// If there is only one argument, it must be a proper URI
.equal(
assertnew URL('https://example.com/public/page.html').toString(),
'https://example.com/public/page.html'
;
).throws(
assert=> new URL('../book/toc.html'),
() /^TypeError \[ERR_INVALID_URL\]: Invalid URL$/
;
)
// Resolve a relative reference against a base URI
.equal(
assertnew URL(
'../book/toc.html',
'https://example.com/public/page.html'
.toString(),
)'https://example.com/book/toc.html'
; )
URL
實例的相對參照讓我們重新檢視 URL
建構函式的這個變體
new URL(uriRef: string, baseUri: string)
參數 baseUri
會強制轉換為字串。因此,任何物件都可以使用,只要強制轉換為字串時會變成有效的 URL 即可
const obj = { toString() {return 'https://example.com'} };
.equal(
assertnew URL('index.html', obj).href,
'https://example.com/index.html'
; )
這讓我們可以解析相對於 URL
實例的相對參照
const url = new URL('https://example.com/dir/file1.html');
.equal(
assertnew URL('../file2.html', url).href,
'https://example.com/file2.html'
; )
以這種方式使用時,建構函式與 path.resolve()
非常類似。
URL
實例的屬性URL
實例具有下列屬性
type URL = {
: string,
protocol: string,
username: string,
password: string,
hostname: string,
port: string,
hostreadonly origin: string,
: string,
pathname
: string,
searchreadonly searchParams: URLSearchParams,
: string,
hash
: string,
hreftoString(): string,
toJSON(): string,
}
有三個常見的方式可以將 URL 轉換為字串
const url = new URL('https://example.com/about.html');
.equal(
assert.toString(),
url'https://example.com/about.html'
;
).equal(
assert.href,
url'https://example.com/about.html'
;
).equal(
assert.toJSON(),
url'https://example.com/about.html'
; )
方法 .toJSON()
讓我們可以在 JSON 資料中使用 URL
const jsonStr = JSON.stringify({
pageUrl: new URL('https://exploringjs.dev.org.tw')
;
}).equal(
assert, '{"pageUrl":"https://exploringjs.dev.org.tw"}'
jsonStr; )
URL
屬性URL
實例的屬性不是自己的資料屬性,它們是透過 getter 和 setter 實作的。在以下範例中,我們使用工具函式 pickProps()
(其程式碼顯示在最後),將這些 getter 回傳的值複製到一個純粹的物件中
const props = pickProps(
new URL('https://jane:pw@example.com:80/news.html?date=today#misc'),
'protocol', 'username', 'password', 'hostname', 'port', 'host',
'origin', 'pathname', 'search', 'hash', 'href'
;
).deepEqual(
assert,
props
{protocol: 'https:',
username: 'jane',
password: 'pw',
hostname: 'example.com',
port: '80',
host: 'example.com:80',
origin: 'https://example.com:80',
pathname: '/news.html',
search: '?date=today',
hash: '#misc',
href: 'https://jane:pw@example.com:80/news.html?date=today#misc'
};
)function pickProps(input, ...keys) {
const output = {};
for (const key of keys) {
= input[key];
output[key]
}return output;
}
唉,路徑名稱是一個單一的原子單位。也就是說,我們無法使用類別 URL
存取其部分 (基礎、副檔名等)。
我們也可以透過設定屬性 (例如 .hostname
) 來變更 URL 的部分
const url = new URL('https://example.com');
.hostname = '2ality.com';
url.equal(
assert.href, 'https://2ality.com/'
url; )
我們可以使用 setter 從部分建立 URL (Haroen Viaene 的點子)
// Object.assign() invokes setters when transferring property values
const urlFromParts = (parts) => Object.assign(
new URL('https://example.com'), // minimal dummy URL
// assigned to the dummy
parts ;
)
const url = urlFromParts({
protocol: 'https:',
hostname: '2ality.com',
pathname: '/p/about.html',
;
}).equal(
assert.href, 'https://2ality.com/p/about.html'
url; )
.searchParams
管理搜尋參數我們可以使用屬性 .searchParams
來管理 URL 的搜尋參數。它的值是 URLSearchParams
的實例。
我們可以使用它來讀取搜尋參數
const url = new URL('https://example.com/?topic=js');
.equal(
assert.searchParams.get('topic'), 'js'
url;
).equal(
assert.searchParams.has('topic'), true
url; )
我們也可以透過它變更搜尋參數
.searchParams.append('page', '5');
url.equal(
assert.href, 'https://example.com/?topic=js&page=5'
url;
)
.searchParams.set('topic', 'css');
url.equal(
assert.href, 'https://example.com/?topic=css&page=5'
url; )
手動在檔案路徑和 URL 之間進行轉換很誘人。例如,我們可以嘗試透過 `myUrl.pathname` 將 `URL` 執行個體 `myUrl` 轉換為檔案路徑。然而,這並不總是可行的,最好使用 此函式
.fileURLToPath(url: URL | string): string url
以下程式碼比較該函式的結果與 `pathname` 的值
import * as url from 'node:url';
//::::: Unix :::::
const url1 = new URL('file:///tmp/with%20space.txt');
.equal(
assert.pathname, '/tmp/with%20space.txt');
url1.equal(
assert.fileURLToPath(url1), '/tmp/with space.txt');
url
const url2 = new URL('file:///home/thor/Mj%C3%B6lnir.txt');
.equal(
assert.pathname, '/home/thor/Mj%C3%B6lnir.txt');
url2.equal(
assert.fileURLToPath(url2), '/home/thor/Mjölnir.txt');
url
//::::: Windows :::::
const url3 = new URL('file:///C:/dir/');
.equal(
assert.pathname, '/C:/dir/');
url3.equal(
assert.fileURLToPath(url3), 'C:\\dir\\'); url
此函式 是 `url.fileURLToPath()` 的反函式
.pathToFileURL(path: string): URL url
它將 `path` 轉換為檔案 URL
> url.pathToFileURL('/home/john/Work Files').href'file:///home/john/Work%20Files'
URL 的一個重要使用案例是存取與目前模組同層的檔案
function readData() {
const url = new URL('data.txt', import.meta.url);
return fs.readFileSync(url, {encoding: 'UTF-8'});
}
此函式使用 `import.meta.url`,其中包含目前模組的 URL(在 Node.js 上通常是 `file:` URL)。
使用 `fetch()` 會讓先前的程式碼更跨平台。然而,截至 Node.js 18.9.0,`fetch()` 尚未支援 `file:` URL
> await fetch('file:///tmp/file.txt')TypeError: fetch failed
cause: Error: not implemented... yet...
ESM 模組可以用兩種方式使用
如果我們希望模組以這兩種方式使用,我們需要一種方法來檢查目前模組是否為主模組,因為只有在這種情況下,我們才會執行指令碼功能。在本章中,我們將學習如何執行該檢查。
使用 CommonJS,我們可以使用以下模式來偵測目前模組是否為進入點(來源:Node.js 文件)
if (require.main === module) {
// Main CommonJS module
}
截至目前,ESM 模組沒有簡單的內建方式來檢查模組是否為主模組。相反地,我們必須使用以下解決方法(基於 Rich Harris 的推文)
import * as url from 'node:url';
if (import.meta.url.startsWith('file:')) { // (A)
const modulePath = url.fileURLToPath(import.meta.url);
if (process.argv[1] === modulePath) { // (B)
// Main ESM module
} }
說明
`import.meta.url` 包含目前執行的 ESM 模組的 URL。
如果我們確定我們的程式碼始終在本地執行(這在未來可能會變得不那麼常見),我們可以省略 A 行中的檢查。如果我們這樣做,而程式碼並未在本地執行,至少我們會得到一個例外(而不是靜默失敗),這要歸功於 `url.fileURLToPath()`(請參閱下一項)。
我們使用 `url.fileURLToPath()` 將 URL 轉換為本地路徑。如果協定不是 `file:`,此函式會擲回例外。
process.argv[1]
包含初始模組的路徑。B 行中的比較之所以有效,是因為這個值總是一個絕對路徑 – Node.js 會將其設定如下 (原始碼)
process.argv[1] = path.resolve(process.argv[1]);
file:
URL當 shell 腳本接收檔案的參考或匯出檔案的參考(例如,將它們記錄在螢幕上)時,它們幾乎總是路徑。但是,有兩種情況我們需要 URL(如前小節所述)