From 60c1b78ad0707260a4064ec3a375e491bcde52c0 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Wed, 30 Mar 2022 22:55:07 +0800 Subject: [PATCH 01/35] chore: setup vite-plugin-glob --- packages/playground/glob-import/package.json | 3 +++ packages/playground/glob-import/vite.config.ts | 4 +++- pnpm-lock.yaml | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index 32abb932424757..b340d84eeb288b 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -7,5 +7,8 @@ "build": "vite build", "debug": "node --inspect-brk ../../vite/bin/vite", "preview": "vite preview" + }, + "devDependencies": { + "vite-plugin-glob": "^0.1.1" } } diff --git a/packages/playground/glob-import/vite.config.ts b/packages/playground/glob-import/vite.config.ts index abc75b51656503..453d30b31230e4 100644 --- a/packages/playground/glob-import/vite.config.ts +++ b/packages/playground/glob-import/vite.config.ts @@ -1,10 +1,12 @@ import path from 'path' import { defineConfig } from 'vite' +import Glob from 'vite-plugin-glob' export default defineConfig({ resolve: { alias: { '@dir': path.resolve(__dirname, './dir/') } - } + }, + plugins: [Glob({ takeover: true })] }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a09304a6c8f50d..8494f96b79ce90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,7 +215,10 @@ importers: specifiers: {} packages/playground/glob-import: - specifiers: {} + specifiers: + vite-plugin-glob: ^0.1.1 + devDependencies: + vite-plugin-glob: 0.1.1 packages/playground/hmr: specifiers: {} @@ -9205,6 +9208,10 @@ packages: resolution: {integrity: sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==} dev: true + /ufo/0.8.1: + resolution: {integrity: sha512-1hqVxwcvBvzwwMRjR8pQEJ0CYOCGo49VF11caCe1hxKGiMGpL5ge/JiRX8ttf+YoIMomoEAim2SbD4jQ4tKbXw==} + dev: true + /uglify-js/3.15.0: resolution: {integrity: sha512-x+xdeDWq7FiORDvyIJ0q/waWd4PhjBNOm5dQUOq2AKC0IEjxOS66Ha9tctiVDGcRQuh69K7fgU5oRuTK4cysSg==} engines: {node: '>=0.8.0'} @@ -9301,6 +9308,15 @@ packages: engines: {node: '>= 0.8'} dev: true + /vite-plugin-glob/0.1.1: + resolution: {integrity: sha512-M2vtVfYOUZsEIikZeRz9au2E+LjpYVMrVFFXlEvqcmLW9wApTsq+wW/d632SPSaaj1scSbb1pNV6FO5wjcRTxw==} + dependencies: + fast-glob: 3.2.11 + magic-string: 0.26.1 + micromatch: 4.0.5 + ufo: 0.8.1 + dev: true + /vitepress/0.22.3: resolution: {integrity: sha512-Yfvu/rent2vp/TXIDZMutS6ft2TJPn4xngS48PYFWDEbuFI2ccUAXM481lF1qVVnCKxfh4g8e/KPvevSJdg1Bw==} engines: {node: '>=14.0.0'} From c0601e3dc42be2848dc36688ea352ad4abacfdcb Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 31 Mar 2022 01:10:12 +0800 Subject: [PATCH 02/35] chore: update --- packages/playground/glob-import/package.json | 2 +- packages/playground/glob-import/vite.config.ts | 5 ++++- pnpm-lock.yaml | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index b340d84eeb288b..a0534b94595f87 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite-plugin-glob": "^0.1.1" + "vite-plugin-glob": "^0.2.1" } } diff --git a/packages/playground/glob-import/vite.config.ts b/packages/playground/glob-import/vite.config.ts index 453d30b31230e4..e7123acdc6a63e 100644 --- a/packages/playground/glob-import/vite.config.ts +++ b/packages/playground/glob-import/vite.config.ts @@ -8,5 +8,8 @@ export default defineConfig({ '@dir': path.resolve(__dirname, './dir/') } }, - plugins: [Glob({ takeover: true })] + plugins: [Glob({ takeover: true })], + optimizeDeps: { + entries: [] + } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a56f9d695af8db..589a1d6afe7fb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,9 +218,9 @@ importers: packages/playground/glob-import: specifiers: - vite-plugin-glob: ^0.1.1 + vite-plugin-glob: ^0.2.1 devDependencies: - vite-plugin-glob: 0.1.1 + vite-plugin-glob: 0.2.1 packages/playground/hmr: specifiers: {} @@ -9378,8 +9378,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-glob/0.1.1: - resolution: {integrity: sha512-M2vtVfYOUZsEIikZeRz9au2E+LjpYVMrVFFXlEvqcmLW9wApTsq+wW/d632SPSaaj1scSbb1pNV6FO5wjcRTxw==} + /vite-plugin-glob/0.2.1: + resolution: {integrity: sha512-2I0erHho2M1opNT5vJ28RWZJKSHXZSFQgwpZTajr0Kg9C5ZEadn9EQ3yIu52KX43wSK3q+eewitbrT2x6eAGdg==} dependencies: fast-glob: 3.2.11 magic-string: 0.26.1 From a7d83bb84da3d14dbf2a332ecc06566b6e91eccd Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 6 May 2022 13:42:36 +0800 Subject: [PATCH 03/35] chore: update tests --- .../glob-import/__tests__/glob-import.spec.ts | 4 ++-- packages/playground/glob-import/package.json | 2 +- pnpm-lock.yaml | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/playground/glob-import/__tests__/glob-import.spec.ts b/packages/playground/glob-import/__tests__/glob-import.spec.ts index ebdf6c0ab29193..d738ccec1d4c97 100644 --- a/packages/playground/glob-import/__tests__/glob-import.spec.ts +++ b/packages/playground/glob-import/__tests__/glob-import.spec.ts @@ -42,7 +42,7 @@ const allResult = { }, '/dir/index.js': { globWithAlias: { - './alias.js': { + '/dir/alias.js': { default: 'hi' } }, @@ -67,7 +67,7 @@ const rawResult = { } const relativeRawResult = { - '../glob-import/dir/baz.json': { + './dir/baz.json': { msg: 'baz' } } diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index a0534b94595f87..845f7c73ff1898 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite-plugin-glob": "^0.2.1" + "vite-plugin-glob": "^0.2.7" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4ee1ebe102d95..c788c133b943f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,9 +224,9 @@ importers: packages/playground/glob-import: specifiers: - vite-plugin-glob: ^0.2.1 + vite-plugin-glob: ^0.2.7 devDependencies: - vite-plugin-glob: 0.2.1 + vite-plugin-glob: 0.2.7 packages/playground/hmr: specifiers: {} @@ -9512,8 +9512,8 @@ packages: resolution: {integrity: sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==} dev: true - /ufo/0.8.1: - resolution: {integrity: sha512-1hqVxwcvBvzwwMRjR8pQEJ0CYOCGo49VF11caCe1hxKGiMGpL5ge/JiRX8ttf+YoIMomoEAim2SbD4jQ4tKbXw==} + /ufo/0.8.3: + resolution: {integrity: sha512-AIkk06G21y/P+NCatfU+1qldCmI0XCszZLn8AkuKotffF3eqCvlce0KuwM7ZemLE/my0GSYADOAeM5zDYWMB+A==} dev: true /uglify-js/3.15.0: @@ -9612,13 +9612,14 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-glob/0.2.1: - resolution: {integrity: sha512-2I0erHho2M1opNT5vJ28RWZJKSHXZSFQgwpZTajr0Kg9C5ZEadn9EQ3yIu52KX43wSK3q+eewitbrT2x6eAGdg==} + /vite-plugin-glob/0.2.7: + resolution: {integrity: sha512-eP7lM72q1IbQxhXNbks6WCzU1s81fkiLuTffV4g8H/Ls6NgB7HYSe+pcN7ECTRj+JBTz/4xtm00eeL7dheL8Bg==} dependencies: + acorn: 8.7.1 fast-glob: 3.2.11 magic-string: 0.26.1 micromatch: 4.0.5 - ufo: 0.8.1 + ufo: 0.8.3 dev: true /vitepress/0.22.3: From 75c978ec2978b8921b512320363b50de9713f3f6 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 6 May 2022 16:35:31 +0800 Subject: [PATCH 04/35] chore: update --- packages/playground/glob-import/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index 845f7c73ff1898..52f583f5590f62 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite-plugin-glob": "^0.2.7" + "vite-plugin-glob": "^0.2.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a66e3ee7a2cd0d..c6876235d48ed5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,9 +226,9 @@ importers: packages/playground/glob-import: specifiers: - vite-plugin-glob: ^0.2.7 + vite-plugin-glob: ^0.2.8 devDependencies: - vite-plugin-glob: 0.2.7 + vite-plugin-glob: 0.2.8 packages/playground/hmr: specifiers: {} @@ -9681,8 +9681,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-glob/0.2.7: - resolution: {integrity: sha512-eP7lM72q1IbQxhXNbks6WCzU1s81fkiLuTffV4g8H/Ls6NgB7HYSe+pcN7ECTRj+JBTz/4xtm00eeL7dheL8Bg==} + /vite-plugin-glob/0.2.8: + resolution: {integrity: sha512-Xs9rlEdDW33yfs72Iugu/y3H0AK48DkldM8t5i+LAlXF3GaKA9oFerWZih7qp5cibg4JI99wnMSYHcj/awP7Eg==} dependencies: acorn: 8.7.1 fast-glob: 3.2.11 From 2abd81c54b1ffb476072a514fb9e065c890751ff Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 6 May 2022 16:55:08 +0800 Subject: [PATCH 05/35] chore: upgrade --- packages/playground/glob-import/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index 52f583f5590f62..4c365e45214776 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -9,6 +9,6 @@ "preview": "vite preview" }, "devDependencies": { - "vite-plugin-glob": "^0.2.8" + "vite-plugin-glob": "^0.2.9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6876235d48ed5..c449586a4d6c51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,9 +226,9 @@ importers: packages/playground/glob-import: specifiers: - vite-plugin-glob: ^0.2.8 + vite-plugin-glob: ^0.2.9 devDependencies: - vite-plugin-glob: 0.2.8 + vite-plugin-glob: 0.2.9 packages/playground/hmr: specifiers: {} @@ -9681,8 +9681,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-glob/0.2.8: - resolution: {integrity: sha512-Xs9rlEdDW33yfs72Iugu/y3H0AK48DkldM8t5i+LAlXF3GaKA9oFerWZih7qp5cibg4JI99wnMSYHcj/awP7Eg==} + /vite-plugin-glob/0.2.9: + resolution: {integrity: sha512-UMO76ZfylNTE+6nCmeA+jYlUOVPxft1+oHGaJpiR+nz+qYZWIlruaSBjf7b13RPvUrc79k6OR7Q5KMRFV8sG8A==} dependencies: acorn: 8.7.1 fast-glob: 3.2.11 From 147a8f4bca12d60b2b07f4918aea8225ec18e005 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 7 May 2022 07:41:09 +0800 Subject: [PATCH 06/35] feat: inline vite-plugin-glob --- packages/playground/glob-import/package.json | 3 - .../playground/glob-import/vite.config.ts | 1 - packages/vite/package.json | 1 + .../__snapshots__/fixture.test.ts.snap | 141 +++++ .../plugins/importGlob/fixture-a/.gitignore | 1 + .../plugins/importGlob/fixture-a/index.ts | 62 ++ .../plugins/importGlob/fixture-a/modules/a.ts | 1 + .../plugins/importGlob/fixture-a/modules/b.ts | 1 + .../importGlob/fixture-a/modules/index.ts | 6 + .../framework/pages/hello.page.js | 4 + .../plugins/importGlob/fixture-a/sibling.ts | 1 + .../plugins/importGlob/fixture-b/a.ts | 1 + .../plugins/importGlob/fixture-b/b.ts | 1 + .../plugins/importGlob/fixture-b/index.ts | 2 + .../plugins/importGlob/fixture.test.ts | 78 +++ .../plugins/importGlob/parse.test.ts | 262 ++++++++ .../plugins/importGlob/utils.test.ts | 31 + .../vite/src/node/plugins/importMetaGlob.ts | 565 ++++++++++++++++++ packages/vite/src/node/plugins/index.ts | 2 + packages/vite/src/node/server/hmr.ts | 4 +- packages/vite/types/shims.d.ts | 8 - pnpm-lock.yaml | 21 +- 22 files changed, 1167 insertions(+), 30 deletions(-) create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.test.ts.snap create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts create mode 100644 packages/vite/src/node/__tests__/plugins/importGlob/utils.test.ts create mode 100644 packages/vite/src/node/plugins/importMetaGlob.ts diff --git a/packages/playground/glob-import/package.json b/packages/playground/glob-import/package.json index 4c365e45214776..32abb932424757 100644 --- a/packages/playground/glob-import/package.json +++ b/packages/playground/glob-import/package.json @@ -7,8 +7,5 @@ "build": "vite build", "debug": "node --inspect-brk ../../vite/bin/vite", "preview": "vite preview" - }, - "devDependencies": { - "vite-plugin-glob": "^0.2.9" } } diff --git a/packages/playground/glob-import/vite.config.ts b/packages/playground/glob-import/vite.config.ts index e7123acdc6a63e..3bdb2d0486ed60 100644 --- a/packages/playground/glob-import/vite.config.ts +++ b/packages/playground/glob-import/vite.config.ts @@ -8,7 +8,6 @@ export default defineConfig({ '@dir': path.resolve(__dirname, './dir/') } }, - plugins: [Glob({ takeover: true })], optimizeDeps: { entries: [] } diff --git a/packages/vite/package.json b/packages/vite/package.json index 55c86878622111..4d0ee2273139d0 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -116,6 +116,7 @@ "tsconfck": "^1.2.2", "tslib": "^2.4.0", "types": "link:./types", + "ufo": "^0.8.4", "ws": "^8.6.0" }, "peerDependencies": { diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.test.ts.snap b/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.test.ts.snap new file mode 100644 index 00000000000000..fc4ff17e7036b0 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.test.ts.snap @@ -0,0 +1,141 @@ +// Vitest Snapshot v1 + +exports[`fixture > transform 1`] = ` +"import * as __vite_glob_next_1_0 from \\"./modules/a.ts\\" +import * as __vite_glob_next_1_1 from \\"./modules/b.ts\\" +import * as __vite_glob_next_1_2 from \\"./modules/index.ts\\" +import { name as __vite_glob_next_3_0 } from \\"./modules/a.ts\\" +import { name as __vite_glob_next_3_1 } from \\"./modules/b.ts\\" +import { name as __vite_glob_next_3_2 } from \\"./modules/index.ts\\" +import { default as __vite_glob_next_5_0 } from \\"./modules/a.ts?raw\\" +import { default as __vite_glob_next_5_1 } from \\"./modules/b.ts?raw\\" +export const basic = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\"), +\\"./modules/index.ts\\": () => import(\\"./modules/index.ts\\") +}; +export const basicEager = { +\\"./modules/a.ts\\": __vite_glob_next_1_0, +\\"./modules/b.ts\\": __vite_glob_next_1_1, +\\"./modules/index.ts\\": __vite_glob_next_1_2 +}; +export const ignore = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\") +}; +export const namedEager = { +\\"./modules/a.ts\\": __vite_glob_next_3_0, +\\"./modules/b.ts\\": __vite_glob_next_3_1, +\\"./modules/index.ts\\": __vite_glob_next_3_2 +}; +export const namedDefault = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\").then(m => m[\\"default\\"]), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\").then(m => m[\\"default\\"]), +\\"./modules/index.ts\\": () => import(\\"./modules/index.ts\\").then(m => m[\\"default\\"]) +}; +export const eagerAs = { +\\"./modules/a.ts\\": __vite_glob_next_5_0, +\\"./modules/b.ts\\": __vite_glob_next_5_1 +}; +export const excludeSelf = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts\\") +}; +export const customQueryString = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts?custom\\") +}; +export const customQueryObject = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts?foo=bar&raw=true\\") +}; +export const parent = { + +}; +export const rootMixedRelative = { +\\"/build.config.ts\\": () => import(\\"../../../build.config.ts?url\\").then(m => m[\\"default\\"]), +\\"/client.d.ts\\": () => import(\\"../../../client.d.ts?url\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/index.ts\\": () => import(\\"../fixture-b/index.ts?url\\").then(m => m[\\"default\\"]), +\\"/takeover.d.ts\\": () => import(\\"../../../takeover.d.ts?url\\").then(m => m[\\"default\\"]), +\\"/types.ts\\": () => import(\\"../../../types.ts?url\\").then(m => m[\\"default\\"]) +}; +export const cleverCwd1 = { +\\"./node_modules/framework/pages/hello.page.js\\": () => import(\\"./node_modules/framework/pages/hello.page.js\\") +}; +export const cleverCwd2 = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\"), +\\"../fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts\\"), +\\"../fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts\\") +}; +" +`; + +exports[`fixture > transform with restoreQueryExtension 1`] = ` +"import * as __vite_glob_next_1_0 from \\"./modules/a.ts\\" +import * as __vite_glob_next_1_1 from \\"./modules/b.ts\\" +import * as __vite_glob_next_1_2 from \\"./modules/index.ts\\" +import { name as __vite_glob_next_3_0 } from \\"./modules/a.ts\\" +import { name as __vite_glob_next_3_1 } from \\"./modules/b.ts\\" +import { name as __vite_glob_next_3_2 } from \\"./modules/index.ts\\" +import { default as __vite_glob_next_5_0 } from \\"./modules/a.ts?raw\\" +import { default as __vite_glob_next_5_1 } from \\"./modules/b.ts?raw\\" +export const basic = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\"), +\\"./modules/index.ts\\": () => import(\\"./modules/index.ts\\") +}; +export const basicEager = { +\\"./modules/a.ts\\": __vite_glob_next_1_0, +\\"./modules/b.ts\\": __vite_glob_next_1_1, +\\"./modules/index.ts\\": __vite_glob_next_1_2 +}; +export const ignore = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\") +}; +export const namedEager = { +\\"./modules/a.ts\\": __vite_glob_next_3_0, +\\"./modules/b.ts\\": __vite_glob_next_3_1, +\\"./modules/index.ts\\": __vite_glob_next_3_2 +}; +export const namedDefault = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\").then(m => m[\\"default\\"]), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\").then(m => m[\\"default\\"]), +\\"./modules/index.ts\\": () => import(\\"./modules/index.ts\\").then(m => m[\\"default\\"]) +}; +export const eagerAs = { +\\"./modules/a.ts\\": __vite_glob_next_5_0, +\\"./modules/b.ts\\": __vite_glob_next_5_1 +}; +export const excludeSelf = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts\\") +}; +export const customQueryString = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts?custom&lang.ts\\") +}; +export const customQueryObject = { +\\"./sibling.ts\\": () => import(\\"./sibling.ts?foo=bar&raw=true&lang.ts\\") +}; +export const parent = { + +}; +export const rootMixedRelative = { +\\"/build.config.ts\\": () => import(\\"../../../build.config.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/client.d.ts\\": () => import(\\"../../../client.d.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/src/__tests__/fixture-b/index.ts\\": () => import(\\"../fixture-b/index.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/takeover.d.ts\\": () => import(\\"../../../takeover.d.ts?url&lang.ts\\").then(m => m[\\"default\\"]), +\\"/types.ts\\": () => import(\\"../../../types.ts?url&lang.ts\\").then(m => m[\\"default\\"]) +}; +export const cleverCwd1 = { +\\"./node_modules/framework/pages/hello.page.js\\": () => import(\\"./node_modules/framework/pages/hello.page.js\\") +}; +export const cleverCwd2 = { +\\"./modules/a.ts\\": () => import(\\"./modules/a.ts\\"), +\\"./modules/b.ts\\": () => import(\\"./modules/b.ts\\"), +\\"../fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts\\"), +\\"../fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts\\") +}; +" +`; diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore new file mode 100644 index 00000000000000..2b9b8877da603f --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore @@ -0,0 +1 @@ +!/node_modules/ diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts new file mode 100644 index 00000000000000..73475b8d6d16b9 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/comma-dangle */ +export interface ModuleType { + name: string +} + +export const basic = import.meta.glob('./modules/*.ts') + +export const basicEager = import.meta.glob('./modules/*.ts', { + eager: true +}) + +export const ignore = import.meta.glob(['./modules/*.ts', '!**/index.ts']) + +export const namedEager = import.meta.glob('./modules/*.ts', { + eager: true, + import: 'name' +}) + +export const namedDefault = import.meta.glob('./modules/*.ts', { + import: 'default' +}) + +export const eagerAs = import.meta.glob( + ['./modules/*.ts', '!**/index.ts'], + { eager: true, as: 'raw' } +) + +export const excludeSelf = import.meta.glob( + './*.ts' + // for test: annotation contain ")" + /* + * for test: annotation contain ")" + * */ +) + +export const customQueryString = import.meta.glob('./*.ts', { query: 'custom' }) + +export const customQueryObject = import.meta.glob('./*.ts', { + query: { + foo: 'bar', + raw: true + } +}) + +export const parent = import.meta.glob('../../playground/src/*.ts', { + as: 'url' +}) + +export const rootMixedRelative = import.meta.glob( + ['/*.ts', '../fixture-b/*.ts'], + { as: 'url' } +) + +export const cleverCwd1 = import.meta.glob( + './node_modules/framework/**/*.page.js' +) + +export const cleverCwd2 = import.meta.glob([ + './modules/*.ts', + '../fixture-b/*.ts', + '!**/index.ts' +]) diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts new file mode 100644 index 00000000000000..facd48a0875e65 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts @@ -0,0 +1 @@ +export const name = 'a' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts new file mode 100644 index 00000000000000..0b1eb38d9087a2 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts @@ -0,0 +1 @@ +export const name = 'b' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts new file mode 100644 index 00000000000000..25b59ae7d30714 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts @@ -0,0 +1,6 @@ +export { name as a } from './a' +export { name as b } from './b' + +export const name = 'index' + +export default 'indexDefault' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js new file mode 100644 index 00000000000000..cbe518a8e79477 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js @@ -0,0 +1,4 @@ +// A fake Page file. (This technique of globbing into `node_modules/` +// is used by vite-plugin-ssr frameworks and Hydrogen.) + +export const a = 1 diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts new file mode 100644 index 00000000000000..b286816bf5d63a --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts @@ -0,0 +1 @@ +export const name = 'I am your sibling!' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts new file mode 100644 index 00000000000000..facd48a0875e65 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts @@ -0,0 +1 @@ +export const name = 'a' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts new file mode 100644 index 00000000000000..0b1eb38d9087a2 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts @@ -0,0 +1 @@ +export const name = 'b' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts new file mode 100644 index 00000000000000..39bdbfd1a8befb --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts @@ -0,0 +1,2 @@ +export { name as a } from './a' +export { name as b } from './b' diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts new file mode 100644 index 00000000000000..78a8e4e0dd2e0e --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts @@ -0,0 +1,78 @@ +import { resolve } from 'path' +import { promises as fs } from 'fs' +import { describe, expect, it } from 'vitest' +import { transform } from '../../../plugins/importMetaGlob' +import { transformWithEsbuild } from '../../../plugins/esbuild' + +describe('fixture', async () => { + const resolveId = (id: string) => id + const options = { takeover: true } + + it('transform', async () => { + const id = resolve(__dirname, './fixture-a/index.ts') + const code = ( + await transformWithEsbuild(await fs.readFile(id, 'utf-8'), id) + ).code + const root = process.cwd() + + expect( + (await transform(code, id, root, resolveId, options))?.s.toString() + ).toMatchSnapshot() + }) + + it('virtual modules', async () => { + const root = resolve(__dirname, './fixture-a') + const code = [ + "import.meta.glob('/modules/*.ts')", + "import.meta.glob(['/../fixture-b/*.ts'])" + ].join('\n') + expect( + ( + await transform(code, 'virtual:module', root, resolveId, options) + )?.s.toString() + ).toMatchInlineSnapshot(` + "{ + \\"/modules/a.ts\\": () => import(\\"/modules/a.ts\\"), + \\"/modules/b.ts\\": () => import(\\"/modules/b.ts\\"), + \\"/modules/index.ts\\": () => import(\\"/modules/index.ts\\") + } + { + \\"/../fixture-b/a.ts\\": () => import(\\"/../fixture-b/a.ts\\"), + \\"/../fixture-b/b.ts\\": () => import(\\"/../fixture-b/b.ts\\"), + \\"/../fixture-b/index.ts\\": () => import(\\"/../fixture-b/index.ts\\") + }" + `) + + try { + await transform( + "import.meta.glob('./modules/*.ts')", + 'virtual:module', + root, + resolveId, + options + ) + expect('no error').toBe('should throw an error') + } catch (err) { + expect(err).toMatchInlineSnapshot( + "[Error: In virtual modules, all globs must start with '/']" + ) + } + }) + + it('transform with restoreQueryExtension', async () => { + const id = resolve(__dirname, './fixture-a/index.ts') + const code = ( + await transformWithEsbuild(await fs.readFile(id, 'utf-8'), id) + ).code + const root = process.cwd() + + expect( + ( + await transform(code, id, root, resolveId, { + ...options, + restoreQueryExtension: true + }) + )?.s.toString() + ).toMatchSnapshot() + }) +}) diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts new file mode 100644 index 00000000000000..fb6cf86e2b97c2 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts @@ -0,0 +1,262 @@ +import { describe, expect, it } from 'vitest' +import { parseImportGlob } from '../../../plugins/importMetaGlob' + +async function run(input: string) { + const items = await parseImportGlob( + input, + process.cwd(), + process.cwd(), + (id) => id + ) + return items.map((i) => ({ globs: i.globs, options: i.options })) +} + +async function runError(input: string) { + try { + await run(input) + } catch (e) { + return e + } +} + +describe('parse positives', async () => { + it('basic', async () => { + expect( + await run(` + import.meta.importGlob(\'./modules/*.ts\') + `) + ).toMatchInlineSnapshot(` + [ + { + "globs": [ + "./modules/*.ts", + ], + "options": {}, + }, + ] + `) + }) + + it('array', async () => { + expect( + await run(` + import.meta.importGlob([\'./modules/*.ts\', './dir/*.{js,ts}\']) + `) + ).toMatchInlineSnapshot(` + [ + { + "globs": [ + "./modules/*.ts", + "./dir/*.{js,ts}", + ], + "options": {}, + }, + ] + `) + }) + + it('options with multilines', async () => { + expect( + await run(` + import.meta.importGlob([ + \'./modules/*.ts\', + "!./dir/*.{js,ts}" + ], { + eager: true, + import: 'named' + }) + `) + ).toMatchInlineSnapshot(` + [ + { + "globs": [ + "./modules/*.ts", + "!./dir/*.{js,ts}", + ], + "options": { + "eager": true, + "import": "named", + }, + }, + ] + `) + }) + + it('options with multilines', async () => { + expect( + await run(` + const modules = import.meta.glob( + '/dir/**' + // for test: annotation contain ")" + /* + * for test: annotation contain ")" + * */ + ) + `) + ).toMatchInlineSnapshot(` + [ + { + "globs": [ + "/dir/**", + ], + "options": {}, + }, + ] + `) + }) + + it('options query', async () => { + expect( + await run(` + const modules = import.meta.glob( + '/dir/**', + { + query: { + foo: 'bar', + raw: true, + } + } + ) + `) + ).toMatchInlineSnapshot(` + [ + { + "globs": [ + "/dir/**", + ], + "options": { + "query": { + "foo": "bar", + "raw": true, + }, + }, + }, + ] + `) + }) +}) + +describe('parse negatives', async () => { + it('syntax error', async () => { + expect(await runError('import.meta.importGlob(')).toMatchInlineSnapshot( + '[SyntaxError: Unexpected token (1:23)]' + ) + }) + + it('empty', async () => { + expect(await runError('import.meta.importGlob()')).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected 1-2 arguments, but got 0]' + ) + }) + + it('3 args', async () => { + expect( + await runError('import.meta.importGlob("", {}, {})') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected 1-2 arguments, but got 3]' + ) + }) + + it('in string', async () => { + expect(await runError('"import.meta.importGlob()"')).toBeUndefined() + }) + + it('variable', async () => { + expect(await runError('import.meta.importGlob(hey)')).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + }) + + it('template', async () => { + // eslint-disable-next-line no-template-curly-in-string + expect( + await runError('import.meta.importGlob(`hi ${hey}`)') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + }) + + it('be string', async () => { + expect(await runError('import.meta.importGlob(1)')).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected glob to be a string, but got "number"]' + ) + }) + + it('be array variable', async () => { + expect( + await runError('import.meta.importGlob([hey])') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + expect( + await runError('import.meta.importGlob(["1", hey])') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + }) + + it('options', async () => { + expect( + await runError('import.meta.importGlob("hey", hey)') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "Identifier"]' + ) + expect( + await runError('import.meta.importGlob("hey", [])') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "ArrayExpression"]' + ) + }) + + it('options props', async () => { + expect( + await runError('import.meta.importGlob("hey", { hey: 1 })') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Unknown options hey]' + ) + expect( + await runError('import.meta.importGlob("hey", { import: hey })') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + expect( + await runError('import.meta.importGlob("hey", { eager: 123 })') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected the type of option "eager" to be "boolean", but got "number"]' + ) + }) + + it('options query', async () => { + expect( + await runError( + 'import.meta.importGlob("./*.js", { as: "raw", query: "hi" })' + ) + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Options "as" and "query" cannot be used together]' + ) + expect( + await runError('import.meta.importGlob("./*.js", { query: 123 })') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Expected query to be a string, but got "number"]' + ) + expect( + await runError('import.meta.importGlob("./*.js", { query: { foo: {} } })') + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + expect( + await runError( + 'import.meta.importGlob("./*.js", { query: { foo: hey } })' + ) + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + expect( + await runError( + 'import.meta.importGlob("./*.js", { query: { foo: 123, ...a } })' + ) + ).toMatchInlineSnapshot( + '[Error: Invalid glob import syntax: Could only use literals]' + ) + }) +}) diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/utils.test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/utils.test.ts new file mode 100644 index 00000000000000..302df97ec92ede --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/importGlob/utils.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { getCommonBase } from '../../../plugins/importMetaGlob' + +describe('getCommonBase()', async () => { + it('basic', () => { + expect(getCommonBase(['/a/b/*.js', '/a/c/*.js'])).toBe('/a') + }) + it('common base', () => { + expect(getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx'])).toBe('/a/b') + }) + it('static file', () => { + expect( + getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx', '/a/b/foo.js']) + ).toBe('/a/b') + expect(getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx', '/a/foo.js'])).toBe( + '/a' + ) + }) + it('correct `scan()`', () => { + expect(getCommonBase(['/a/*.vue'])).toBe('/a') + expect(getCommonBase(['/a/some.vue'])).toBe('/a') + expect(getCommonBase(['/a/b/**/c/foo.vue', '/a/b/c/**/*.jsx'])).toBe('/a/b') + }) + it('single', () => { + expect(getCommonBase(['/a/b/c/*.vue'])).toBe('/a/b/c') + expect(getCommonBase(['/a/b/c/foo.vue'])).toBe('/a/b/c') + }) + it('no common base', () => { + expect(getCommonBase(['/a/b/*.js', '/c/a/b/*.js'])).toBe('/') + }) +}) diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts new file mode 100644 index 00000000000000..5b6cce04a52f65 --- /dev/null +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -0,0 +1,565 @@ +import path, { posix } from 'path' +import { isMatch, scan } from 'micromatch' +import type { ArrayExpression, CallExpression, Literal, Node } from 'estree' +import { parseExpressionAt } from 'acorn' +import MagicString from 'magic-string' +import fg from 'fast-glob' +import { stringifyQuery } from 'ufo' +import type { Worker } from 'okie' +import type { Plugin } from '../plugin' +import { getShortName, handleFileAddUnlink, updateModules } from '../server/hmr' +import { normalizePath } from '../utils' +import type { ViteDevServer } from '../server' +import type { ModuleNode } from '../server/moduleGraph' +import type { ResolvedConfig } from '../config' +import { isCSSRequest } from './css' + +export interface GlobOptions { + /** + * Import type for the import url. + */ + as?: AsType + /** + * Import as static or dynamic + * + * @default false + */ + eager?: Eager + /** + * Import only the specific named export. Set to `default` to import the default export. + */ + import?: string + /** + * Custom queries + */ + query?: string | Record + /** + * Search files also inside `node_modules/` and hidden directories (e.g. `.git/`). This might have impact on performance. + * + * @default false + */ + exhaustive?: boolean +} + +export type GeneralGlobOptions = GlobOptions + +export interface ParsedImportGlob { + match: RegExpMatchArray + index: number + globs: string[] + globsResolved: string[] + isRelative: boolean + options: GeneralGlobOptions + type: string + start: number + end: number +} + +export interface KnownAsType { + raw: string + url: string + worker: Worker +} + +export interface PluginOptions { + /** + * Take over the default import.meta.glob in Vite + * + * @default false + */ + takeover?: boolean + + /** + * Append fake `&lang.(ext)` when queries are specified, to preseve the file extension for following plugins to process. + * + * @experimental + * @default false + */ + restoreQueryExtension?: boolean +} + +type isTrue = T extends true ? true : false + +export interface GlobFunction { + /** + * 1. No generic provided, infer the type from `eager` and `as` + */ + < + Eager extends boolean, + As extends string, + T = As extends keyof KnownAsType ? KnownAsType[As] : unknown + >( + glob: string | string[], + options?: GlobOptions + ): isTrue extends true + ? Record + : Record Promise> + /** + * 2. Module generic provided, infer the type from `eager: false` + */ + (glob: string | string[], options?: GlobOptions): Record< + string, + () => Promise + > + /** + * 3. Module generic provided, infer the type from `eager: true` + */ + (glob: string | string[], options: GlobOptions): Record< + string, + M + > +} + +export interface GlobEagerFunction { + /** + * 1. No generic provided, infer the type from `as` + */ + < + As extends string, + T = As extends keyof KnownAsType ? KnownAsType[As] : unknown + >( + glob: string | string[], + options?: Omit, 'eager'> + ): Record + /** + * 2. Module generic provided + */ + ( + glob: string | string[], + options?: Omit, 'eager'> + ): Record +} + +export function importGlobPlugin( + config: ResolvedConfig, + options: PluginOptions = {} +): Plugin { + let server: ViteDevServer | undefined + const map = new Map() + + function updateMap(id: string, info: ParsedImportGlob[]) { + const allGlobs = info.map((i) => i.globsResolved) + map.set(id, allGlobs) + // add those allGlobs to the watcher + server?.watcher.add(allGlobs.flatMap((i) => i.filter((i) => i[0] !== '!'))) + } + + function getAffectedModules(file: string) { + const modules: ModuleNode[] = [] + for (const [id, allGlobs] of map) { + if (allGlobs.some((glob) => isMatch(file, glob))) + modules.push(...(server?.moduleGraph.getModulesByFile(id) || [])) + } + return modules + } + + return { + name: 'vite:import-glob', + buildStart() { + map.clear() + }, + configureServer(_server) { + server = _server + const handleFileAddUnlink = (file: string) => { + const modules = getAffectedModules(file) + modules.forEach((i) => { + if (i?.file) _server.moduleGraph.onFileChange(i.file) + }) + updateModules( + getShortName(file, config.root), + modules, + Date.now(), + _server + ) + } + server.watcher.on('unlink', handleFileAddUnlink) + server.watcher.on('add', handleFileAddUnlink) + }, + async transform(code, id) { + const result = await transform( + code, + id, + config.root, + (im) => this.resolve(im, id).then((i) => i?.id || im), + options + ) + if (result) { + updateMap(id, result.matches) + return { + code: result.s.toString(), + map: result.s.generateMap() + } + } + } + } +} + +const importGlobRE = + /\bimport\.meta\.(glob|globEager|globEagerDefault)(?:<\w+>)?\s*\(/g + +const knownOptions = { + as: 'string', + eager: 'boolean', + import: 'string', + exhaustive: 'boolean' +} as const + +const forceDefaultAs = ['raw', 'url'] + +export async function parseImportGlob( + code: string, + dir: string | null, + root: string, + resolveId: (id: string) => string | Promise +): Promise { + const matchs = Array.from(code.matchAll(importGlobRE)) + + const tasks = matchs.map(async (match, index) => { + const type = match[1] + const start = match.index! + + const err = (msg: string) => { + const e = new Error(`Invalid glob import syntax: ${msg}`) + ;(e as any).pos = start + return e + } + + let ast: CallExpression + + try { + ast = parseExpressionAt(code, start, { + ecmaVersion: 'latest', + sourceType: 'module', + ranges: true + }) as any + } catch (e) { + const _e = e as any + if (_e.message && _e.message.startsWith('Unterminated string constant')) + return undefined! + throw _e + } + + if (ast.type !== 'CallExpression') + throw err(`Expect CallExpression, got ${ast.type}`) + + if (ast.arguments.length < 1 || ast.arguments.length > 2) + throw err(`Expected 1-2 arguments, but got ${ast.arguments.length}`) + + const arg1 = ast.arguments[0] as ArrayExpression | Literal + const arg2 = ast.arguments[1] as Node | undefined + + const globs: string[] = [] + if (arg1.type === 'ArrayExpression') { + for (const element of arg1.elements) { + if (!element) continue + if (element.type !== 'Literal') throw err('Could only use literals') + if (typeof element.value !== 'string') + throw err( + `Expected glob to be a string, but got "${typeof element.value}"` + ) + + globs.push(element.value) + } + } else if (arg1.type === 'Literal') { + if (typeof arg1.value !== 'string') + throw err( + `Expected glob to be a string, but got "${typeof arg1.value}"` + ) + globs.push(arg1.value) + } else { + throw err('Could only use literals') + } + + // if (!globs.every(i => i.match(/^[.\/!]/))) + // throw err('pattern must start with "." or "/" (relative to project root) or alias path') + + // arg2 + const options: GeneralGlobOptions = {} + if (arg2) { + if (arg2.type !== 'ObjectExpression') + throw err( + `Expected the second argument o to be a object literal, but got "${arg2.type}"` + ) + + for (const property of arg2.properties) { + if ( + property.type === 'SpreadElement' || + property.key.type !== 'Identifier' + ) + throw err('Could only use literals') + + const name = property.key.name as keyof GeneralGlobOptions + + if (name === 'query') { + if (property.value.type === 'ObjectExpression') { + const data: Record = {} + for (const prop of property.value.properties) { + if ( + prop.type === 'SpreadElement' || + prop.key.type !== 'Identifier' || + prop.value.type !== 'Literal' + ) + throw err('Could only use literals') + data[prop.key.name] = prop.value.value as any + } + options.query = data + } else if (property.value.type === 'Literal') { + if (typeof property.value.value !== 'string') + throw err( + `Expected query to be a string, but got "${typeof property.value + .value}"` + ) + options.query = property.value.value + } else { + throw err('Could only use literals') + } + continue + } + + if (!(name in knownOptions)) throw err(`Unknown options ${name}`) + + if (property.value.type !== 'Literal') + throw err('Could only use literals') + + const valueType = typeof property.value.value + if (valueType === 'undefined') continue + + if (valueType !== knownOptions[name]) + throw err( + `Expected the type of option "${name}" to be "${knownOptions[name]}", but got "${valueType}"` + ) + options[name] = property.value.value as any + } + } + + if (options.as && forceDefaultAs.includes(options.as)) { + if (options.import && options.import !== 'default') + throw err( + `Option "export" can only be "default" when "as" is "${options.as}", but got "${options.import}"` + ) + options.import = 'default' + } + + if (options.as && options.query) + throw err('Options "as" and "query" cannot be used together') + + if (options.as) options.query = options.as + + const end = ast.range![1] + + const globsResolved = await Promise.all( + globs.map((glob) => toAbsoluteGlob(glob, root, dir ?? root, resolveId)) + ) + const isRelative = globs.every((i) => '.!'.includes(i[0])) + + return { + match, + index, + globs, + globsResolved, + isRelative, + options, + type, + start, + end + } + }) + + return (await Promise.all(tasks)).filter(Boolean) +} + +const importPrefix = '__vite_glob_' + +const { basename, dirname, relative, join } = posix + +export async function transform( + code: string, + id: string, + root: string, + resolveId: (id: string) => Promise | string, + { restoreQueryExtension = false }: PluginOptions = {} +) { + id = toPosixPath(id) + root = toPosixPath(root) + const dir = isVirtualModule(id) ? null : dirname(id) + const matches = await parseImportGlob(code, dir, root, resolveId) + + // TODO: backwards compatibility + matches.forEach((i) => { + if (i.type === 'globEager') i.options.eager = true + if (i.type === 'globEagerDefault') { + i.options.eager = true + i.options.import = 'default' + } + }) + + if (!matches.length) return + + const s = new MagicString(code) + + const staticImports = ( + await Promise.all( + matches.map( + async ({ globsResolved, isRelative, options, index, start, end }) => { + const cwd = getCommonBase(globsResolved) ?? root + const files = ( + await fg(globsResolved, { + cwd, + absolute: true, + dot: !!options.exhaustive, + ignore: options.exhaustive + ? [] + : [join(cwd, '**/node_modules/**')] + }) + ) + .filter((file) => file !== id) + .sort() + + const objectProps: string[] = [] + const staticImports: string[] = [] + + let query = !options.query + ? '' + : typeof options.query === 'string' + ? options.query + : stringifyQuery(options.query as any) + + if (query && !query.startsWith('?')) query = `?${query}` + + const resolvePaths = (file: string) => { + if (!dir) { + if (isRelative) + throw new Error( + "In virtual modules, all globs must start with '/'" + ) + const filePath = `/${relative(root, file)}` + return { filePath, importPath: filePath } + } + + let importPath = relative(dir, file) + if (!importPath.startsWith('.')) importPath = `./${importPath}` + + let filePath: string + if (isRelative) { + filePath = importPath + } else { + filePath = relative(root, file) + if (!filePath.startsWith('.')) filePath = `/${filePath}` + } + + return { filePath, importPath } + } + + files.forEach((file, i) => { + const paths = resolvePaths(file) + const filePath = paths.filePath + let importPath = paths.importPath + let importQuery = query + + if (isCSSRequest(file)) + importQuery = importQuery ? `${importQuery}&used` : '?used' + + if (importQuery && importQuery !== '?raw') { + const fileExtension = basename(file).split('.').slice(-1)[0] + if (fileExtension && restoreQueryExtension) + importQuery = `${importQuery}&lang.${fileExtension}` + } + + importPath = `${importPath}${importQuery}` + + if (options.eager) { + const variableName = `${importPrefix}${index}_${i}` + const expression = options.import + ? `{ ${options.import} as ${variableName} }` + : `* as ${variableName}` + staticImports.push( + `import ${expression} from ${JSON.stringify(importPath)}` + ) + objectProps.push(`${JSON.stringify(filePath)}: ${variableName}`) + } else { + let importStatement = `import(${JSON.stringify(importPath)})` + if (options.import) + importStatement += `.then(m => m[${JSON.stringify( + options.import + )}])` + objectProps.push( + `${JSON.stringify(filePath)}: () => ${importStatement}` + ) + } + }) + + const replacement = `{\n${objectProps.join(',\n')}\n}` + s.overwrite(start, end, replacement) + + return staticImports + } + ) + ) + ).flat() + + if (staticImports.length) s.prepend(`${staticImports.join('\n')}\n`) + + return { + s, + matches + } +} + +export async function toAbsoluteGlob( + glob: string, + root: string, + dirname: string, + resolveId: (id: string) => string | Promise +): Promise { + let pre = '' + if (glob.startsWith('!')) { + pre = '!' + glob = glob.slice(1) + } + + if (glob.startsWith('/')) return pre + posix.join(root, glob.slice(1)) + if (glob.startsWith('./')) return pre + posix.join(dirname, glob.slice(2)) + if (glob.startsWith('../')) return pre + posix.join(dirname, glob) + if (glob.startsWith('**')) return pre + glob + + const resolved = await resolveId(glob) + if (resolved.startsWith('/')) return pre + resolved + + throw new Error(`Invalid glob: ${glob}. It must starts with '/' or './'`) +} + +export function getCommonBase(globsResolved: string[]): null | string { + const bases = globsResolved + .filter((g) => !g.startsWith('!')) + .map((glob) => { + let { base } = scan(glob) + // `scan('a/foo.js')` returns `base: 'a/foo.js'` + if (path.posix.basename(base).includes('.')) + base = path.posix.dirname(base) + + return base + }) + + if (!bases.length) return null + + let commonAncestor = '' + const dirS = bases[0].split('/') + for (let i = 0; i < dirS.length; i++) { + const candidate = dirS.slice(0, i + 1).join('/') + if (bases.every((base) => base.startsWith(candidate))) + commonAncestor = candidate + else break + } + if (!commonAncestor) commonAncestor = '/' + + return commonAncestor +} + +export function toPosixPath(p: string) { + return p.split('\\').join('/') +} + +export function isVirtualModule(id: string) { + // https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention + return id.startsWith('virtual:') || id.startsWith('\0') || !id.includes('/') +} diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 2d34b99aebf1c5..cc8204455babd6 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -19,6 +19,7 @@ import { ssrRequireHookPlugin } from './ssrRequireHook' import { workerImportMetaUrlPlugin } from './workerImportMetaUrl' import { ensureWatchPlugin } from './ensureWatch' import { metadataPlugin } from './metadata' +import { importGlobPlugin } from './importMetaGlob' export async function resolvePlugins( config: ResolvedConfig, @@ -65,6 +66,7 @@ export async function resolvePlugins( wasmPlugin(config), webWorkerPlugin(config), assetPlugin(config), + importGlobPlugin(config), ...normalPlugins, definePlugin(config), cssPostPlugin(config), diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 8d33554706dee2..e695ff67db4325 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -34,7 +34,7 @@ export interface HmrContext { server: ViteDevServer } -function getShortName(file: string, root: string) { +export function getShortName(file: string, root: string) { return file.startsWith(root + '/') ? path.posix.relative(root, file) : file } @@ -125,7 +125,7 @@ export async function handleHMRUpdate( updateModules(shortFile, hmrContext.modules, timestamp, server) } -function updateModules( +export function updateModules( file: string, modules: ModuleNode[], timestamp: number, diff --git a/packages/vite/types/shims.d.ts b/packages/vite/types/shims.d.ts index 68aa799cb6a863..0f49e9c2181e98 100644 --- a/packages/vite/types/shims.d.ts +++ b/packages/vite/types/shims.d.ts @@ -85,14 +85,6 @@ declare module 'rollup-plugin-web-worker-loader' { export default p } -declare module 'micromatch' { - export function isMatch( - path: string, - pattern: string, - options?: { matchBase?: boolean } - ): boolean -} - // LESS' types somewhat references this which doesn't make sense in Node, // so we have to shim it declare interface HTMLLinkElement {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c449586a4d6c51..25fac2f267f169 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,10 +225,7 @@ importers: specifiers: {} packages/playground/glob-import: - specifiers: - vite-plugin-glob: ^0.2.9 - devDependencies: - vite-plugin-glob: 0.2.9 + specifiers: {} packages/playground/hmr: specifiers: {} @@ -900,6 +897,7 @@ importers: tsconfck: ^1.2.2 tslib: ^2.4.0 types: link:./types + ufo: ^0.8.4 ws: ^8.6.0 dependencies: esbuild: 0.14.27 @@ -973,6 +971,7 @@ importers: tsconfck: 1.2.2_typescript@4.5.4 tslib: 2.4.0 types: link:types + ufo: 0.8.4 ws: 8.6.0 packages: @@ -9581,8 +9580,8 @@ packages: resolution: {integrity: sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==} dev: true - /ufo/0.8.3: - resolution: {integrity: sha512-AIkk06G21y/P+NCatfU+1qldCmI0XCszZLn8AkuKotffF3eqCvlce0KuwM7ZemLE/my0GSYADOAeM5zDYWMB+A==} + /ufo/0.8.4: + resolution: {integrity: sha512-/+BmBDe8GvlB2nIflWasLLAInjYG0bC9HRnfEpNi4sw77J2AJNnEVnTDReVrehoh825+Q/evF3THXTAweyam2g==} dev: true /uglify-js/3.15.0: @@ -9681,16 +9680,6 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-plugin-glob/0.2.9: - resolution: {integrity: sha512-UMO76ZfylNTE+6nCmeA+jYlUOVPxft1+oHGaJpiR+nz+qYZWIlruaSBjf7b13RPvUrc79k6OR7Q5KMRFV8sG8A==} - dependencies: - acorn: 8.7.1 - fast-glob: 3.2.11 - magic-string: 0.26.1 - micromatch: 4.0.5 - ufo: 0.8.3 - dev: true - /vitepress/0.22.3: resolution: {integrity: sha512-Yfvu/rent2vp/TXIDZMutS6ft2TJPn4xngS48PYFWDEbuFI2ccUAXM481lF1qVVnCKxfh4g8e/KPvevSJdg1Bw==} engines: {node: '>=14.0.0'} From 86c3b97eaee5c846387ed5c48b18eb0e4b7f9502 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 7 May 2022 07:49:14 +0800 Subject: [PATCH 07/35] chore: comment out core importGlob logic --- packages/vite/src/node/index.ts | 1 + packages/vite/src/node/optimizer/scan.ts | 122 +++++++++--------- packages/vite/src/node/plugins/css.ts | 26 ++-- .../vite/src/node/plugins/importAnalysis.ts | 81 ++++++------ .../src/node/plugins/importAnalysisBuild.ts | 74 +++++------ packages/vite/types/importMeta.d.ts | 14 +- 6 files changed, 158 insertions(+), 160 deletions(-) diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index c15359f45b69de..64d9282a890838 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -73,6 +73,7 @@ export type { TransformOptions as EsbuildTransformOptions } from 'esbuild' export type { ESBuildOptions, ESBuildTransformResult } from './plugins/esbuild' export type { Manifest, ManifestChunk } from './plugins/manifest' export type { ResolveOptions, InternalResolveOptions } from './plugins/resolve' +export type { GlobFunction, GlobEagerFunction } from './plugins/importMetaGlob' export type { WebSocketServer, WebSocketClient, diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index ef59a35b1d22d3..6c63a8186a0666 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -302,18 +302,18 @@ function esbuildScanPlugin( const key = `${path}?id=${scriptId++}` if (contents.includes('import.meta.glob')) { - scripts[key] = { - // transformGlob already transforms to js - loader: 'js', - contents: await transformGlob( - contents, - path, - config.root, - loader, - resolve, - config.logger - ) - } + // scripts[key] = { + // // transformGlob already transforms to js + // loader: 'js', + // contents: await transformGlob( + // contents, + // path, + // config.root, + // loader, + // resolve, + // config.logger + // ) + // } } else { scripts[key] = { loader, @@ -467,19 +467,19 @@ function esbuildScanPlugin( config.optimizeDeps?.esbuildOptions?.loader?.[`.${ext}`] || (ext as Loader) - if (contents.includes('import.meta.glob')) { - return transformGlob( - contents, - id, - config.root, - loader, - resolve, - config.logger - ).then((contents) => ({ - loader, - contents - })) - } + // if (contents.includes('import.meta.glob')) { + // return transformGlob( + // contents, + // id, + // config.root, + // loader, + // resolve, + // config.logger + // ).then((contents) => ({ + // loader, + // contents + // })) + // } return { loader, @@ -490,42 +490,42 @@ function esbuildScanPlugin( } } -async function transformGlob( - source: string, - importer: string, - root: string, - loader: Loader, - resolve: (url: string, importer?: string) => Promise, - logger: Logger -) { - // transform the content first since es-module-lexer can't handle non-js - if (loader !== 'js') { - source = (await transform(source, { loader })).code - } - - await init - const imports = parse(source)[0] - const s = new MagicString(source) - for (let index = 0; index < imports.length; index++) { - const { s: start, e: end, ss: expStart } = imports[index] - const url = source.slice(start, end) - if (url !== 'import.meta') continue - if (source.slice(end, end + 5) !== '.glob') continue - const { importsString, exp, endIndex } = await transformImportGlob( - source, - start, - normalizePath(importer), - index, - root, - logger, - undefined, - resolve - ) - s.prepend(importsString) - s.overwrite(expStart, endIndex, exp, { contentOnly: true }) - } - return s.toString() -} +// async function transformGlob( +// source: string, +// importer: string, +// root: string, +// loader: Loader, +// resolve: (url: string, importer?: string) => Promise, +// logger: Logger +// ) { +// // transform the content first since es-module-lexer can't handle non-js +// if (loader !== 'js') { +// source = (await transform(source, { loader })).code +// } + +// await init +// const imports = parse(source)[0] +// const s = new MagicString(source) +// for (let index = 0; index < imports.length; index++) { +// const { s: start, e: end, ss: expStart } = imports[index] +// const url = source.slice(start, end) +// if (url !== 'import.meta') continue +// if (source.slice(end, end + 5) !== '.glob') continue +// const { importsString, exp, endIndex } = await transformImportGlob( +// source, +// start, +// normalizePath(importer), +// index, +// root, +// logger, +// undefined, +// resolve +// ) +// s.prepend(importsString) +// s.overwrite(expStart, endIndex, exp, { contentOnly: true }) +// } +// return s.toString() +// } /** * when using TS + (Vue + `