Skip to content

Commit

Permalink
feat!: rework import.meta.glob (#7537)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 8, 2022
1 parent b215493 commit 330e0a9
Show file tree
Hide file tree
Showing 38 changed files with 1,308 additions and 502 deletions.
115 changes: 107 additions & 8 deletions docs/guide/features.md
Expand Up @@ -282,10 +282,10 @@ for (const path in modules) {
}
```

Matched files are by default lazy loaded via dynamic import and will be split into separate chunks during build. If you'd rather import all the modules directly (e.g. relying on side-effects in these modules to be applied first), you can use `import.meta.globEager` instead:
Matched files are by default lazy-loaded via dynamic import and will be split into separate chunks during build. If you'd rather import all the modules directly (e.g. relying on side-effects in these modules to be applied first), you can pass `{ eager: true }` as the second argument:

```js
const modules = import.meta.globEager('./dir/*.js')
const modules = import.meta.glob('./dir/*.js', { eager: true })
```

The above will be transformed into the following:
Expand All @@ -300,7 +300,9 @@ const modules = {
}
```

`import.meta.glob` and `import.meta.globEager` also support importing files as strings (similar to [Importing Asset as String](https://vitejs.dev/guide/assets.html#importing-asset-as-string)) with the [Import Reflection](https://github.com/tc39/proposal-import-reflection) syntax:
### Glob Import As

`import.meta.glob` also supports importing files as strings (similar to [Importing Asset as String](https://vitejs.dev/guide/assets.html#importing-asset-as-string)) with the [Import Reflection](https://github.com/tc39/proposal-import-reflection) syntax:

```js
const modules = import.meta.glob('./dir/*.js', { as: 'raw' })
Expand All @@ -311,18 +313,115 @@ The above will be transformed into the following:
```js
// code produced by vite
const modules = {
'./dir/foo.js': '{\n "msg": "foo"\n}\n',
'./dir/bar.js': '{\n "msg": "bar"\n}\n'
'./dir/foo.js': 'export default "foo"\n',
'./dir/bar.js': 'export default "bar"\n'
}
```

`{ as: 'url' }` is also supported for loading assets as URLs.

### Multiple Patterns

The first argument can be an array of globs, for example

```js
const modules = import.meta.glob(['./dir/*.js', './another/*.js'])
```

### Negative Patterns

Negative glob patterns are also supported (prefixed with `!`). To ignore some files from the result, you can add exclude glob patterns to the first argument:

```js
const modules = import.meta.glob(['./dir/*.js', '!**/bar.js'])
```

```js
// code produced by vite
const modules = {
'./dir/foo.js': () => import('./dir/foo.js')
}
```

#### Named Imports

It's possible to only import parts of the modules with the `import` options.

```ts
const modules = import.meta.glob('./dir/*.js', { import: 'setup' })
```

```ts
// code produced by vite
const modules = {
'./dir/foo.js': () => import('./dir/foo.js').then((m) => m.setup),
'./dir/bar.js': () => import('./dir/bar.js').then((m) => m.setup)
}
```

When combined with `eager` it's even possible to have tree-shaking enabled for those modules.

```ts
const modules = import.meta.glob('./dir/*.js', { import: 'setup', eager: true })
```

```ts
// code produced by vite:
import { setup as __glob__0_0 } from './dir/foo.js'
import { setup as __glob__0_1 } from './dir/bar.js'
const modules = {
'./dir/foo.js': __glob__0_0,
'./dir/bar.js': __glob__0_1
}
```

Set `import` to `default` to import the default export.

```ts
const modules = import.meta.glob('./dir/*.js', {
import: 'default',
eager: true
})
```

```ts
// code produced by vite:
import __glob__0_0 from './dir/foo.js'
import __glob__0_1 from './dir/bar.js'
const modules = {
'./dir/foo.js': __glob__0_0,
'./dir/bar.js': __glob__0_1
}
```

#### Custom Queries

You can also use the `query` option to provide custom queries to imports for other plugins to consume.

```ts
const modules = import.meta.glob('./dir/*.js', {
query: { foo: 'bar', bar: true }
})
```

```ts
// code produced by vite:
const modules = {
'./dir/foo.js': () =>
import('./dir/foo.js?foo=bar&bar=true').then((m) => m.setup),
'./dir/bar.js': () =>
import('./dir/bar.js?foo=bar&bar=true').then((m) => m.setup)
}
```

### Glob Import Caveats

Note that:

- This is a Vite-only feature and is not a web or ES standard.
- The glob patterns are treated like import specifiers: they must be either relative (start with `./`) or absolute (start with `/`, resolved relative to project root) or an alias path (see [`resolve.alias` option](/config/#resolve-alias)).
- The glob matching is done via `fast-glob` - check out its documentation for [supported glob patterns](https://github.com/mrmlnc/fast-glob#pattern-syntax).
- You should also be aware that glob imports do not accept variables, you need to directly pass the string pattern.
- The glob patterns cannot contain the same quote string (i.e. `'`, `"`, `` ` ``) as outer quotes, e.g. `'/Tom\'s files/**'`, use `"/Tom's files/**"` instead.
- The glob matching is done via [`fast-glob`](https://github.com/mrmlnc/fast-glob) - check out its documentation for [supported glob patterns](https://github.com/mrmlnc/fast-glob#pattern-syntax).
- You should also be aware that all the arguments in the `import.meta.glob` must be **passed as literals**. You can NOT use variables or expressions in them.

## WebAssembly

Expand Down
4 changes: 2 additions & 2 deletions packages/playground/assets/__tests__/assets.spec.ts
Expand Up @@ -145,8 +145,8 @@ describe('css url() references', () => {
expect(await getBg('.css-url-quotes-base64-inline')).toMatch(match)
const icoMatch = isBuild ? `data:image/x-icon;base64` : `favicon.ico`
const el = await page.$(`link.ico`)
const herf = await el.getAttribute('href')
expect(herf).toMatch(icoMatch)
const href = await el.getAttribute('href')
expect(href).toMatch(icoMatch)
})

test('multiple urls on the same line', async () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/css/main.js
Expand Up @@ -80,7 +80,9 @@ text('.inlined-code', inlined)

// glob
const glob = import.meta.glob('./glob-import/*.css')
Promise.all(Object.keys(glob).map((key) => glob[key]())).then((res) => {
Promise.all(
Object.keys(glob).map((key) => glob[key]().then((i) => i.default))
).then((res) => {
text('.imported-css-glob', JSON.stringify(res, null, 2))
})

Expand Down
4 changes: 2 additions & 2 deletions packages/playground/glob-import/__tests__/glob-import.spec.ts
Expand Up @@ -42,7 +42,7 @@ const allResult = {
},
'/dir/index.js': {
globWithAlias: {
'./alias.js': {
'/dir/alias.js': {
default: 'hi'
}
},
Expand All @@ -67,7 +67,7 @@ const rawResult = {
}

const relativeRawResult = {
'../glob-import/dir/baz.json': {
'./dir/baz.json': {
msg: 'baz'
}
}
Expand Down
28 changes: 28 additions & 0 deletions packages/vite/LICENSE.md
Expand Up @@ -3606,6 +3606,34 @@ Repository: git+https://github.com/dominikg/tsconfck.git
---------------------------------------

## ufo
License: MIT
Repository: unjs/ufo

> MIT License
>
> Copyright (c) 2020 Nuxt Contrib
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
---------------------------------------

## unpipe
License: MIT
By: Douglas Christopher Wilson
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/package.json
Expand Up @@ -112,11 +112,12 @@
"source-map-js": "^1.0.2",
"source-map-support": "^0.5.21",
"strip-ansi": "^6.0.1",
"strip-literal": "^0.2.0",
"strip-literal": "^0.3.0",
"terser": "^5.13.1",
"tsconfck": "^1.2.2",
"tslib": "^2.4.0",
"types": "link:./types",
"ufo": "^0.8.4",
"ws": "^8.6.0"
},
"peerDependencies": {
Expand Down
@@ -0,0 +1,154 @@
// Vitest Snapshot v1

exports[`fixture > transform 1`] = `
"import * as __vite_glob_1_0 from \\"./modules/a.ts\\"
import * as __vite_glob_1_1 from \\"./modules/b.ts\\"
import * as __vite_glob_1_2 from \\"./modules/index.ts\\"
import { name as __vite_glob_3_0 } from \\"./modules/a.ts\\"
import { name as __vite_glob_3_1 } from \\"./modules/b.ts\\"
import { name as __vite_glob_3_2 } from \\"./modules/index.ts\\"
import { default as __vite_glob_5_0 } from \\"./modules/a.ts?raw\\"
import { default as __vite_glob_5_1 } from \\"./modules/b.ts?raw\\"
import \\"../../../../../../types/importMeta\\";
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_1_0,
\\"./modules/b.ts\\": __vite_glob_1_1,
\\"./modules/index.ts\\": __vite_glob_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_3_0,
\\"./modules/b.ts\\": __vite_glob_3_1,
\\"./modules/index.ts\\": __vite_glob_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_5_0,
\\"./modules/b.ts\\": __vite_glob_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 = {
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/index.ts\\": () => import(\\"../fixture-b/index.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_1_0 from \\"./modules/a.ts\\"
import * as __vite_glob_1_1 from \\"./modules/b.ts\\"
import * as __vite_glob_1_2 from \\"./modules/index.ts\\"
import { name as __vite_glob_3_0 } from \\"./modules/a.ts\\"
import { name as __vite_glob_3_1 } from \\"./modules/b.ts\\"
import { name as __vite_glob_3_2 } from \\"./modules/index.ts\\"
import { default as __vite_glob_5_0 } from \\"./modules/a.ts?raw\\"
import { default as __vite_glob_5_1 } from \\"./modules/b.ts?raw\\"
import \\"../../../../../../types/importMeta\\";
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_1_0,
\\"./modules/b.ts\\": __vite_glob_1_1,
\\"./modules/index.ts\\": __vite_glob_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_3_0,
\\"./modules/b.ts\\": __vite_glob_3_1,
\\"./modules/index.ts\\": __vite_glob_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_5_0,
\\"./modules/b.ts\\": __vite_glob_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 = {
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/index.ts\\": () => import(\\"../fixture-b/index.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\\")
};
"
`;

exports[`fixture > virtual modules 1`] = `
"{
\\"/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\\")
}"
`;
@@ -0,0 +1 @@
!/node_modules/

0 comments on commit 330e0a9

Please sign in to comment.