From 9f37a4ec055e0177535a45227163c174d45cdb68 Mon Sep 17 00:00:00 2001 From: dominikg Date: Thu, 25 Aug 2022 16:40:14 +0200 Subject: [PATCH 1/9] fix: escape glob special chars for import.meta.glob --- .../vite/src/node/plugins/importMetaGlob.ts | 14 +++++++++++--- .../glob-import/__tests__/glob-import.spec.ts | 17 +++++++++++++++++ playground/glob-import/escape/$dollar/glob.js | 3 +++ .../glob-import/escape/$dollar/mod/index.js | 1 + playground/glob-import/escape/(braces)/glob.js | 3 +++ .../glob-import/escape/(braces)/mod/index.js | 1 + playground/glob-import/escape/*star/glob.js | 3 +++ .../glob-import/escape/*star/mod/index.js | 1 + playground/glob-import/escape/+plus/glob.js | 3 +++ .../glob-import/escape/+plus/mod/index.js | 1 + .../glob-import/escape/[brackets]/glob.js | 3 +++ .../glob-import/escape/[brackets]/mod/index.js | 1 + playground/glob-import/escape/^caret/glob.js | 3 +++ .../glob-import/escape/^caret/mod/index.js | 1 + playground/glob-import/index.html | 14 +++++++++++++- 15 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 playground/glob-import/escape/$dollar/glob.js create mode 100644 playground/glob-import/escape/$dollar/mod/index.js create mode 100644 playground/glob-import/escape/(braces)/glob.js create mode 100644 playground/glob-import/escape/(braces)/mod/index.js create mode 100644 playground/glob-import/escape/*star/glob.js create mode 100644 playground/glob-import/escape/*star/mod/index.js create mode 100644 playground/glob-import/escape/+plus/glob.js create mode 100644 playground/glob-import/escape/+plus/mod/index.js create mode 100644 playground/glob-import/escape/[brackets]/glob.js create mode 100644 playground/glob-import/escape/[brackets]/mod/index.js create mode 100644 playground/glob-import/escape/^caret/glob.js create mode 100644 playground/glob-import/escape/^caret/mod/index.js diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index bd88c971fa4343..83c9482602a6f0 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -463,6 +463,12 @@ type IdResolver = ( importer?: string ) => Promise | string | undefined +function globSafePath(path: string) { + // slash path to ensure \ is converted to / as \ could lead to a double escape scenario + // see https://github.com/mrmlnc/fast-glob#advanced-syntax + return normalizePath(path).replace(/[$^*+?()[\]]/g, '\\$&') +} + export async function toAbsoluteGlob( glob: string, root: string, @@ -474,14 +480,16 @@ export async function toAbsoluteGlob( pre = '!' glob = glob.slice(1) } - - const dir = importer ? dirname(importer) : root + root = globSafePath(root) + const dir = importer ? globSafePath(dirname(importer)) : root if (glob.startsWith('/')) return pre + posix.join(root, glob.slice(1)) if (glob.startsWith('./')) return pre + posix.join(dir, glob.slice(2)) if (glob.startsWith('../')) return pre + posix.join(dir, glob) if (glob.startsWith('**')) return pre + glob - const resolved = normalizePath((await resolveId(glob, importer)) || glob) + // moved normalizePath into globSafePath, but a problem arises here if glob is starting with an alias, we escape too much + // `~alias/**/*.js` gets resolved to `/some/path/**.*.js and we have to escape the /some/path part without touching the user supplied part + const resolved = globSafePath((await resolveId(glob, importer)) || glob) if (isAbsolute(resolved)) return pre + resolved throw new Error( diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts index 078ac0cdd0a6e1..d8339b39acdff5 100644 --- a/playground/glob-import/__tests__/glob-import.spec.ts +++ b/playground/glob-import/__tests__/glob-import.spec.ts @@ -1,3 +1,5 @@ +import path from 'node:path' +import { readdir } from 'node:fs/promises' import { expect, test } from 'vitest' import { addFile, @@ -187,3 +189,18 @@ test('tree-shake eager css', async () => { expect(content).not.toMatch('.tree-shake-eager-css') } }) + +test.only('escape special glob chars in path', async () => { + // escape contains subdirectories where each has a name that needs escaping for glob safety + // in each of them is a glob.js that imports a child with `./**/*.js` + // TODO testcase for an alias glob + const files = await readdir(path.join(__dirname, '..', 'escape'), { + withFileTypes: true + }) + const expectedNames = files + .filter((f) => f.isDirectory()) + .map((f) => `/escape/${f.name}/glob.js`) + .sort() + const foundNames = (await page.textContent('.escape')).split('\n').sort() + expect(expectedNames).toEqual(foundNames) +}) diff --git a/playground/glob-import/escape/$dollar/glob.js b/playground/glob-import/escape/$dollar/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/$dollar/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/$dollar/mod/index.js b/playground/glob-import/escape/$dollar/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/$dollar/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/escape/(braces)/glob.js b/playground/glob-import/escape/(braces)/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/(braces)/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/(braces)/mod/index.js b/playground/glob-import/escape/(braces)/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/(braces)/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/escape/*star/glob.js b/playground/glob-import/escape/*star/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/*star/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/*star/mod/index.js b/playground/glob-import/escape/*star/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/*star/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/escape/+plus/glob.js b/playground/glob-import/escape/+plus/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/+plus/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/+plus/mod/index.js b/playground/glob-import/escape/+plus/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/+plus/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/escape/[brackets]/glob.js b/playground/glob-import/escape/[brackets]/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/[brackets]/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/[brackets]/mod/index.js b/playground/glob-import/escape/[brackets]/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/[brackets]/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/escape/^caret/glob.js b/playground/glob-import/escape/^caret/glob.js new file mode 100644 index 00000000000000..6f1aba3ce90aa4 --- /dev/null +++ b/playground/glob-import/escape/^caret/glob.js @@ -0,0 +1,3 @@ +const results = import.meta.glob('./**/*.js', { eager: true }) +console.log('results', results) +export default results diff --git a/playground/glob-import/escape/^caret/mod/index.js b/playground/glob-import/escape/^caret/mod/index.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/playground/glob-import/escape/^caret/mod/index.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/playground/glob-import/index.html b/playground/glob-import/index.html index 85e9e98d2c5ae7..1d77a1ee863a9d 100644 --- a/playground/glob-import/index.html +++ b/playground/glob-import/index.html @@ -17,7 +17,8 @@

Tree shake Eager CSS

Should be orange

Should be orange


-
+

Escape

+

 
 
+
+

From 01fd7a6d358fa404947978495025eb86dfefa930 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 16:44:46 +0200
Subject: [PATCH 2/9] chore: remove console.log

---
 playground/glob-import/escape/$dollar/glob.js    | 2 +-
 playground/glob-import/escape/(braces)/glob.js   | 2 +-
 playground/glob-import/escape/*star/glob.js      | 2 +-
 playground/glob-import/escape/+plus/glob.js      | 2 +-
 playground/glob-import/escape/[brackets]/glob.js | 2 +-
 playground/glob-import/escape/^caret/glob.js     | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/playground/glob-import/escape/$dollar/glob.js b/playground/glob-import/escape/$dollar/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/$dollar/glob.js
+++ b/playground/glob-import/escape/$dollar/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results
diff --git a/playground/glob-import/escape/(braces)/glob.js b/playground/glob-import/escape/(braces)/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/(braces)/glob.js
+++ b/playground/glob-import/escape/(braces)/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results
diff --git a/playground/glob-import/escape/*star/glob.js b/playground/glob-import/escape/*star/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/*star/glob.js
+++ b/playground/glob-import/escape/*star/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results
diff --git a/playground/glob-import/escape/+plus/glob.js b/playground/glob-import/escape/+plus/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/+plus/glob.js
+++ b/playground/glob-import/escape/+plus/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results
diff --git a/playground/glob-import/escape/[brackets]/glob.js b/playground/glob-import/escape/[brackets]/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/[brackets]/glob.js
+++ b/playground/glob-import/escape/[brackets]/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results
diff --git a/playground/glob-import/escape/^caret/glob.js b/playground/glob-import/escape/^caret/glob.js
index 6f1aba3ce90aa4..74da8535fbfb58 100644
--- a/playground/glob-import/escape/^caret/glob.js
+++ b/playground/glob-import/escape/^caret/glob.js
@@ -1,3 +1,3 @@
 const results = import.meta.glob('./**/*.js', { eager: true })
-console.log('results', results)
+
 export default results

From 13d61f229280556825f9aa16cc4808f2e5bc3076 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 17:28:24 +0200
Subject: [PATCH 3/9] fix: try and use backtracking to only escape static part
 of a resolved path

---
 .../vite/src/node/plugins/importMetaGlob.ts   | 22 +++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts
index 83c9482602a6f0..cdb6eae085f5af 100644
--- a/packages/vite/src/node/plugins/importMetaGlob.ts
+++ b/packages/vite/src/node/plugins/importMetaGlob.ts
@@ -489,8 +489,26 @@ export async function toAbsoluteGlob(
 
   // moved normalizePath into globSafePath, but a problem arises here if glob is starting with an alias, we escape too much
   // `~alias/**/*.js` gets resolved to `/some/path/**.*.js and we have to escape the /some/path part without touching the user supplied part
-  const resolved = globSafePath((await resolveId(glob, importer)) || glob)
-  if (isAbsolute(resolved)) return pre + resolved
+  const resolved = normalizePath((await resolveId(glob, importer)) || glob)
+  if (isAbsolute(resolved)) {
+    // we have to escape special glob characters in the resolved path, but keep the user specified globby suffix
+    // walk back both strings until a character difference is found
+    // then slice up the resolved path at that pos and escape the first part
+    let similarEndLength = 0
+    while (
+      resolved.charAt(resolved.length - 1 - similarEndLength) ===
+        glob.charAt(glob.length - 1 - similarEndLength) &&
+      similarEndLength < glob.length &&
+      similarEndLength < resolved.length
+    ) {
+      similarEndLength += 1
+    }
+    const staticPartEnd = resolved.length - similarEndLength
+    const staticPart = resolved.slice(0, staticPartEnd)
+    const dynamicPart = resolved.slice(staticPartEnd)
+    const safeResolved = globSafePath(staticPart) + dynamicPart
+    return pre + safeResolved
+  }
 
   throw new Error(
     `Invalid glob: "${glob}" (resolved: "${resolved}"). It must start with '/' or './'`

From bee085ad04d8c6ed1b5797cc8ae94512c37af0f2 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 19:55:26 +0200
Subject: [PATCH 4/9] fix: add alias test

---
 .../glob-import/__tests__/glob-import.spec.ts | 21 +++++++++++-----
 playground/glob-import/escape/$dollar/glob.js |  6 ++---
 .../glob-import/escape/(braces)/glob.js       |  6 ++---
 playground/glob-import/escape/*star/glob.js   |  6 ++---
 playground/glob-import/escape/+plus/glob.js   |  6 ++---
 .../glob-import/escape/[brackets]/glob.js     |  8 ++++---
 playground/glob-import/escape/^caret/glob.js  |  6 ++---
 playground/glob-import/index.html             | 24 +++++++++++++------
 playground/glob-import/vite.config.ts         | 14 +++++++++++
 9 files changed, 66 insertions(+), 31 deletions(-)

diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts
index d8339b39acdff5..766b871ce073b0 100644
--- a/playground/glob-import/__tests__/glob-import.spec.ts
+++ b/playground/glob-import/__tests__/glob-import.spec.ts
@@ -190,10 +190,13 @@ test('tree-shake eager css', async () => {
   }
 })
 
-test.only('escape special glob chars in path', async () => {
-  // escape contains subdirectories where each has a name that needs escaping for glob safety
-  // in each of them is a glob.js that imports a child with `./**/*.js`
-  // TODO testcase for an alias glob
+test.only('escapes special chars in globs without mangling user supplied glob suffix', async () => {
+  // the escape dir contains subdirectories where each has a name that needs escaping for glob safety
+  // inside each of them is a glob.js that expprts the result of a relative glob `./**/*.js`
+  // and an alias glob `@escape__mod/**/*.js`. The matching aliases are generated in vite.config.ts
+  // index.html has a script that loads all these glob.js files and prints the globs that returned the expected result
+  // this test finally compares the printed output of index.js with the list of directories with special chars,
+  // expecting that they all work
   const files = await readdir(path.join(__dirname, '..', 'escape'), {
     withFileTypes: true
   })
@@ -201,6 +204,12 @@ test.only('escape special glob chars in path', async () => {
     .filter((f) => f.isDirectory())
     .map((f) => `/escape/${f.name}/glob.js`)
     .sort()
-  const foundNames = (await page.textContent('.escape')).split('\n').sort()
-  expect(expectedNames).toEqual(foundNames)
+  const foundRelativeNames = (await page.textContent('.escape-relative'))
+    .split('\n')
+    .sort()
+  expect(expectedNames).toEqual(foundRelativeNames)
+  const foundAliasNames = (await page.textContent('.escape-alias'))
+    .split('\n')
+    .sort()
+  expect(expectedNames).toEqual(foundAliasNames)
 })
diff --git a/playground/glob-import/escape/$dollar/glob.js b/playground/glob-import/escape/$dollar/glob.js
index 74da8535fbfb58..a1583b36c9097c 100644
--- a/playground/glob-import/escape/$dollar/glob.js
+++ b/playground/glob-import/escape/$dollar/glob.js
@@ -1,3 +1,3 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_$dollar_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/escape/(braces)/glob.js b/playground/glob-import/escape/(braces)/glob.js
index 74da8535fbfb58..3bb29ac5b0ffdd 100644
--- a/playground/glob-import/escape/(braces)/glob.js
+++ b/playground/glob-import/escape/(braces)/glob.js
@@ -1,3 +1,3 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_(braces)_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/escape/*star/glob.js b/playground/glob-import/escape/*star/glob.js
index 74da8535fbfb58..aa0d8245e98a71 100644
--- a/playground/glob-import/escape/*star/glob.js
+++ b/playground/glob-import/escape/*star/glob.js
@@ -1,3 +1,3 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_*star_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/escape/+plus/glob.js b/playground/glob-import/escape/+plus/glob.js
index 74da8535fbfb58..f39cfc516a8386 100644
--- a/playground/glob-import/escape/+plus/glob.js
+++ b/playground/glob-import/escape/+plus/glob.js
@@ -1,3 +1,3 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_+plus_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/escape/[brackets]/glob.js b/playground/glob-import/escape/[brackets]/glob.js
index 74da8535fbfb58..7cc656bfe97464 100644
--- a/playground/glob-import/escape/[brackets]/glob.js
+++ b/playground/glob-import/escape/[brackets]/glob.js
@@ -1,3 +1,5 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_[brackets]_mod/**/*.js', {
+  eager: true
+})
+export { relative, alias }
diff --git a/playground/glob-import/escape/^caret/glob.js b/playground/glob-import/escape/^caret/glob.js
index 74da8535fbfb58..042172c9cc3b68 100644
--- a/playground/glob-import/escape/^caret/glob.js
+++ b/playground/glob-import/escape/^caret/glob.js
@@ -1,3 +1,3 @@
-const results = import.meta.glob('./**/*.js', { eager: true })
-
-export default results
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_^caret_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/index.html b/playground/glob-import/index.html
index 1d77a1ee863a9d..359b4fb75ef5f8 100644
--- a/playground/glob-import/index.html
+++ b/playground/glob-import/index.html
@@ -17,8 +17,11 @@ 

Tree shake Eager CSS

Should be orange

Should be orange


-

Escape

-

+

Escape relative glob

+

+

Escape alias glob

+

+
 
 
diff --git a/playground/glob-import/vite.config.ts b/playground/glob-import/vite.config.ts
index a90136bb449662..298a471907cfec 100644
--- a/playground/glob-import/vite.config.ts
+++ b/playground/glob-import/vite.config.ts
@@ -1,9 +1,23 @@
+import fs from 'node:fs'
 import path from 'node:path'
 import { defineConfig } from 'vite'
 
+const escapeAliases = fs
+  .readdirSync(path.join(__dirname, 'escape'), { withFileTypes: true })
+  .filter((f) => f.isDirectory())
+  .map((f) => f.name)
+  .reduce((aliases: Record, dir) => {
+    aliases[`@escape_${dir}_mod`] = path.resolve(
+      __dirname,
+      `./escape/${dir}/mod`
+    )
+    return aliases
+  }, {})
+
 export default defineConfig({
   resolve: {
     alias: {
+      ...escapeAliases,
       '@dir': path.resolve(__dirname, './dir/')
     }
   },

From e952301a9edcde9572008e0c94b985642fa3b48a Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 20:19:29 +0200
Subject: [PATCH 5/9] fix: update globSafePath with a comment and reduce amount
 of escaped chars

---
 packages/vite/src/node/plugins/importMetaGlob.ts     | 8 +++++---
 playground/glob-import/__tests__/glob-import.spec.ts | 2 +-
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts
index cdb6eae085f5af..7e23e398569920 100644
--- a/packages/vite/src/node/plugins/importMetaGlob.ts
+++ b/packages/vite/src/node/plugins/importMetaGlob.ts
@@ -466,7 +466,11 @@ type IdResolver = (
 function globSafePath(path: string) {
   // slash path to ensure \ is converted to / as \ could lead to a double escape scenario
   // see https://github.com/mrmlnc/fast-glob#advanced-syntax
-  return normalizePath(path).replace(/[$^*+?()[\]]/g, '\\$&')
+
+  // full list of chars to escape $^*+?()[] according to the docs above
+  // ^,$,+ leads to errors, but surprisingly not escaping them works
+  // ? escaping isn't needed as vite doesn't work with ? in paths
+  return normalizePath(path).replace(/[*()[\]]/g, '\\$&')
 }
 
 export async function toAbsoluteGlob(
@@ -487,8 +491,6 @@ export async function toAbsoluteGlob(
   if (glob.startsWith('../')) return pre + posix.join(dir, glob)
   if (glob.startsWith('**')) return pre + glob
 
-  // moved normalizePath into globSafePath, but a problem arises here if glob is starting with an alias, we escape too much
-  // `~alias/**/*.js` gets resolved to `/some/path/**.*.js and we have to escape the /some/path part without touching the user supplied part
   const resolved = normalizePath((await resolveId(glob, importer)) || glob)
   if (isAbsolute(resolved)) {
     // we have to escape special glob characters in the resolved path, but keep the user specified globby suffix
diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts
index 766b871ce073b0..74e35ef118ef3f 100644
--- a/playground/glob-import/__tests__/glob-import.spec.ts
+++ b/playground/glob-import/__tests__/glob-import.spec.ts
@@ -190,7 +190,7 @@ test('tree-shake eager css', async () => {
   }
 })
 
-test.only('escapes special chars in globs without mangling user supplied glob suffix', async () => {
+test('escapes special chars in globs without mangling user supplied glob suffix', async () => {
   // the escape dir contains subdirectories where each has a name that needs escaping for glob safety
   // inside each of them is a glob.js that expprts the result of a relative glob `./**/*.js`
   // and an alias glob `@escape__mod/**/*.js`. The matching aliases are generated in vite.config.ts

From 8bb20ee9c5a00c61020d43efca2aaaad08a82734 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 20:48:05 +0200
Subject: [PATCH 6/9] fix: use fast-globs escapePath util and test for correct
 chars to escape

---
 packages/vite/src/node/plugins/importMetaGlob.ts            | 6 +-----
 .../glob-import/escape/{(braces) => (parenthesis)}/glob.js  | 4 +++-
 .../escape/{$dollar => (parenthesis)}/mod/index.js          | 0
 playground/glob-import/escape/^caret/glob.js                | 3 ---
 playground/glob-import/escape/^caret/mod/index.js           | 1 -
 .../glob-import/escape/{+plus => chars_*|(){}[]}/glob.js    | 4 +++-
 .../escape/{(braces) => chars_*|(){}[]}/mod/index.js        | 0
 .../glob-import/escape/{$dollar => {curlies}}/glob.js       | 2 +-
 .../glob-import/escape/{*star => {curlies}}/mod/index.js    | 0
 playground/glob-import/escape/{*star => |pipe}/glob.js      | 2 +-
 playground/glob-import/escape/{+plus => |pipe}/mod/index.js | 0
 11 files changed, 9 insertions(+), 13 deletions(-)
 rename playground/glob-import/escape/{(braces) => (parenthesis)}/glob.js (51%)
 rename playground/glob-import/escape/{$dollar => (parenthesis)}/mod/index.js (100%)
 delete mode 100644 playground/glob-import/escape/^caret/glob.js
 delete mode 100644 playground/glob-import/escape/^caret/mod/index.js
 rename playground/glob-import/escape/{+plus => chars_*|(){}[]}/glob.js (50%)
 rename playground/glob-import/escape/{(braces) => chars_*|(){}[]}/mod/index.js (100%)
 rename playground/glob-import/escape/{$dollar => {curlies}}/glob.js (52%)
 rename playground/glob-import/escape/{*star => {curlies}}/mod/index.js (100%)
 rename playground/glob-import/escape/{*star => |pipe}/glob.js (61%)
 rename playground/glob-import/escape/{+plus => |pipe}/mod/index.js (100%)

diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts
index 7e23e398569920..afdb0eb94ddd03 100644
--- a/packages/vite/src/node/plugins/importMetaGlob.ts
+++ b/packages/vite/src/node/plugins/importMetaGlob.ts
@@ -466,11 +466,7 @@ type IdResolver = (
 function globSafePath(path: string) {
   // slash path to ensure \ is converted to / as \ could lead to a double escape scenario
   // see https://github.com/mrmlnc/fast-glob#advanced-syntax
-
-  // full list of chars to escape $^*+?()[] according to the docs above
-  // ^,$,+ leads to errors, but surprisingly not escaping them works
-  // ? escaping isn't needed as vite doesn't work with ? in paths
-  return normalizePath(path).replace(/[*()[\]]/g, '\\$&')
+  return fg.escapePath(normalizePath(path))
 }
 
 export async function toAbsoluteGlob(
diff --git a/playground/glob-import/escape/(braces)/glob.js b/playground/glob-import/escape/(parenthesis)/glob.js
similarity index 51%
rename from playground/glob-import/escape/(braces)/glob.js
rename to playground/glob-import/escape/(parenthesis)/glob.js
index 3bb29ac5b0ffdd..e97de892c30e2b 100644
--- a/playground/glob-import/escape/(braces)/glob.js
+++ b/playground/glob-import/escape/(parenthesis)/glob.js
@@ -1,3 +1,5 @@
 const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_(braces)_mod/**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_(parenthesis)_mod/**/*.js', {
+  eager: true
+})
 export { relative, alias }
diff --git a/playground/glob-import/escape/$dollar/mod/index.js b/playground/glob-import/escape/(parenthesis)/mod/index.js
similarity index 100%
rename from playground/glob-import/escape/$dollar/mod/index.js
rename to playground/glob-import/escape/(parenthesis)/mod/index.js
diff --git a/playground/glob-import/escape/^caret/glob.js b/playground/glob-import/escape/^caret/glob.js
deleted file mode 100644
index 042172c9cc3b68..00000000000000
--- a/playground/glob-import/escape/^caret/glob.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_^caret_mod/**/*.js', { eager: true })
-export { relative, alias }
diff --git a/playground/glob-import/escape/^caret/mod/index.js b/playground/glob-import/escape/^caret/mod/index.js
deleted file mode 100644
index 4eeb2ac0e1dbb4..00000000000000
--- a/playground/glob-import/escape/^caret/mod/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export const msg = 'foo'
diff --git a/playground/glob-import/escape/+plus/glob.js b/playground/glob-import/escape/chars_*|(){}[]/glob.js
similarity index 50%
rename from playground/glob-import/escape/+plus/glob.js
rename to playground/glob-import/escape/chars_*|(){}[]/glob.js
index f39cfc516a8386..4ad5afc0f62108 100644
--- a/playground/glob-import/escape/+plus/glob.js
+++ b/playground/glob-import/escape/chars_*|(){}[]/glob.js
@@ -1,3 +1,5 @@
 const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_+plus_mod/**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_chars_*|(){}[]_mod/**/*.js', {
+  eager: true
+})
 export { relative, alias }
diff --git a/playground/glob-import/escape/(braces)/mod/index.js b/playground/glob-import/escape/chars_*|(){}[]/mod/index.js
similarity index 100%
rename from playground/glob-import/escape/(braces)/mod/index.js
rename to playground/glob-import/escape/chars_*|(){}[]/mod/index.js
diff --git a/playground/glob-import/escape/$dollar/glob.js b/playground/glob-import/escape/{curlies}/glob.js
similarity index 52%
rename from playground/glob-import/escape/$dollar/glob.js
rename to playground/glob-import/escape/{curlies}/glob.js
index a1583b36c9097c..a6d286001567e9 100644
--- a/playground/glob-import/escape/$dollar/glob.js
+++ b/playground/glob-import/escape/{curlies}/glob.js
@@ -1,3 +1,3 @@
 const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_$dollar_mod/**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_{curlies}_mod/**/*.js', { eager: true })
 export { relative, alias }
diff --git a/playground/glob-import/escape/*star/mod/index.js b/playground/glob-import/escape/{curlies}/mod/index.js
similarity index 100%
rename from playground/glob-import/escape/*star/mod/index.js
rename to playground/glob-import/escape/{curlies}/mod/index.js
diff --git a/playground/glob-import/escape/*star/glob.js b/playground/glob-import/escape/|pipe/glob.js
similarity index 61%
rename from playground/glob-import/escape/*star/glob.js
rename to playground/glob-import/escape/|pipe/glob.js
index aa0d8245e98a71..1c2425a78ae89c 100644
--- a/playground/glob-import/escape/*star/glob.js
+++ b/playground/glob-import/escape/|pipe/glob.js
@@ -1,3 +1,3 @@
 const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_*star_mod/**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_|pipe_mod/**/*.js', { eager: true })
 export { relative, alias }
diff --git a/playground/glob-import/escape/+plus/mod/index.js b/playground/glob-import/escape/|pipe/mod/index.js
similarity index 100%
rename from playground/glob-import/escape/+plus/mod/index.js
rename to playground/glob-import/escape/|pipe/mod/index.js

From ce283dca336b2ab4a8d095d014af3fc9df96f878 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 20:53:33 +0200
Subject: [PATCH 7/9] fix: remove multi char test, windows didn't like the
 directory name

---
 playground/glob-import/escape/chars_*|(){}[]/glob.js      | 5 -----
 playground/glob-import/escape/chars_*|(){}[]/mod/index.js | 1 -
 2 files changed, 6 deletions(-)
 delete mode 100644 playground/glob-import/escape/chars_*|(){}[]/glob.js
 delete mode 100644 playground/glob-import/escape/chars_*|(){}[]/mod/index.js

diff --git a/playground/glob-import/escape/chars_*|(){}[]/glob.js b/playground/glob-import/escape/chars_*|(){}[]/glob.js
deleted file mode 100644
index 4ad5afc0f62108..00000000000000
--- a/playground/glob-import/escape/chars_*|(){}[]/glob.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_chars_*|(){}[]_mod/**/*.js', {
-  eager: true
-})
-export { relative, alias }
diff --git a/playground/glob-import/escape/chars_*|(){}[]/mod/index.js b/playground/glob-import/escape/chars_*|(){}[]/mod/index.js
deleted file mode 100644
index 4eeb2ac0e1dbb4..00000000000000
--- a/playground/glob-import/escape/chars_*|(){}[]/mod/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export const msg = 'foo'

From 41c25053d96f49cce8b7a952912b3e1fa812bb65 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Thu, 25 Aug 2022 20:57:03 +0200
Subject: [PATCH 8/9] fix: windows doesn't like pipe either

---
 playground/glob-import/escape/|pipe/glob.js      | 3 ---
 playground/glob-import/escape/|pipe/mod/index.js | 1 -
 2 files changed, 4 deletions(-)
 delete mode 100644 playground/glob-import/escape/|pipe/glob.js
 delete mode 100644 playground/glob-import/escape/|pipe/mod/index.js

diff --git a/playground/glob-import/escape/|pipe/glob.js b/playground/glob-import/escape/|pipe/glob.js
deleted file mode 100644
index 1c2425a78ae89c..00000000000000
--- a/playground/glob-import/escape/|pipe/glob.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const relative = import.meta.glob('./**/*.js', { eager: true })
-const alias = import.meta.glob('@escape_|pipe_mod/**/*.js', { eager: true })
-export { relative, alias }
diff --git a/playground/glob-import/escape/|pipe/mod/index.js b/playground/glob-import/escape/|pipe/mod/index.js
deleted file mode 100644
index 4eeb2ac0e1dbb4..00000000000000
--- a/playground/glob-import/escape/|pipe/mod/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export const msg = 'foo'

From fd7e4ab6ef1ff9bd8bdbcf80a994c8dfdd6d03b8 Mon Sep 17 00:00:00 2001
From: dominikg 
Date: Sat, 27 Aug 2022 12:58:58 +0200
Subject: [PATCH 9/9] refactor: extract helper functions to reduce code
 verbosity

---
 .../vite/src/node/plugins/importMetaGlob.ts   | 40 +++++++++++--------
 .../glob-import/__tests__/glob-import.spec.ts |  2 +-
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts
index afdb0eb94ddd03..f0aac1e8dc98b5 100644
--- a/packages/vite/src/node/plugins/importMetaGlob.ts
+++ b/packages/vite/src/node/plugins/importMetaGlob.ts
@@ -469,6 +469,28 @@ function globSafePath(path: string) {
   return fg.escapePath(normalizePath(path))
 }
 
+function lastNthChar(str: string, n: number) {
+  return str.charAt(str.length - 1 - n)
+}
+
+function globSafeResolvedPath(resolved: string, glob: string) {
+  // we have to escape special glob characters in the resolved path, but keep the user specified globby suffix
+  // walk back both strings until a character difference is found
+  // then slice up the resolved path at that pos and escape the first part
+  let numEqual = 0
+  const maxEqual = Math.min(resolved.length, glob.length)
+  while (
+    numEqual < maxEqual &&
+    lastNthChar(resolved, numEqual) === lastNthChar(glob, numEqual)
+  ) {
+    numEqual += 1
+  }
+  const staticPartEnd = resolved.length - numEqual
+  const staticPart = resolved.slice(0, staticPartEnd)
+  const dynamicPart = resolved.slice(staticPartEnd)
+  return globSafePath(staticPart) + dynamicPart
+}
+
 export async function toAbsoluteGlob(
   glob: string,
   root: string,
@@ -489,23 +511,7 @@ export async function toAbsoluteGlob(
 
   const resolved = normalizePath((await resolveId(glob, importer)) || glob)
   if (isAbsolute(resolved)) {
-    // we have to escape special glob characters in the resolved path, but keep the user specified globby suffix
-    // walk back both strings until a character difference is found
-    // then slice up the resolved path at that pos and escape the first part
-    let similarEndLength = 0
-    while (
-      resolved.charAt(resolved.length - 1 - similarEndLength) ===
-        glob.charAt(glob.length - 1 - similarEndLength) &&
-      similarEndLength < glob.length &&
-      similarEndLength < resolved.length
-    ) {
-      similarEndLength += 1
-    }
-    const staticPartEnd = resolved.length - similarEndLength
-    const staticPart = resolved.slice(0, staticPartEnd)
-    const dynamicPart = resolved.slice(staticPartEnd)
-    const safeResolved = globSafePath(staticPart) + dynamicPart
-    return pre + safeResolved
+    return pre + globSafeResolvedPath(resolved, glob)
   }
 
   throw new Error(
diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts
index 74e35ef118ef3f..a438ce00d2d62b 100644
--- a/playground/glob-import/__tests__/glob-import.spec.ts
+++ b/playground/glob-import/__tests__/glob-import.spec.ts
@@ -192,7 +192,7 @@ test('tree-shake eager css', async () => {
 
 test('escapes special chars in globs without mangling user supplied glob suffix', async () => {
   // the escape dir contains subdirectories where each has a name that needs escaping for glob safety
-  // inside each of them is a glob.js that expprts the result of a relative glob `./**/*.js`
+  // inside each of them is a glob.js that exports the result of a relative glob `./**/*.js`
   // and an alias glob `@escape__mod/**/*.js`. The matching aliases are generated in vite.config.ts
   // index.html has a script that loads all these glob.js files and prints the globs that returned the expected result
   // this test finally compares the printed output of index.js with the list of directories with special chars,