Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: rework import.meta.glob #7537

Merged
merged 43 commits into from
May 8, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
60c1b78
chore: setup vite-plugin-glob
antfu Mar 30, 2022
7f8150e
chore: merge
antfu Mar 30, 2022
c0601e3
chore: update
antfu Mar 30, 2022
b2e01cc
chore: merge branch 'main' into chore/vite-plugin-glob
antfu May 6, 2022
a7d83bb
chore: update tests
antfu May 6, 2022
df0d8a3
chore: merge
antfu May 6, 2022
75c978e
chore: update
antfu May 6, 2022
2abd81c
chore: upgrade
antfu May 6, 2022
147a8f4
feat: inline vite-plugin-glob
antfu May 6, 2022
86c3b97
chore: comment out core importGlob logic
antfu May 6, 2022
2d61513
chore: lint
antfu May 6, 2022
bf147a0
chore: cleanup
antfu May 7, 2022
b592c3f
fix: backtick escape in cleanString
antfu May 7, 2022
bfe527b
chore: update
antfu May 7, 2022
1ace8af
chore: update
antfu May 7, 2022
a2e557b
fix: use `strip-literal` to strip string lterals
antfu May 7, 2022
4ebd33a
chore: update license
antfu May 7, 2022
d16ea81
chore: update
antfu May 7, 2022
5a4b87a
chore: update
antfu May 7, 2022
544961b
chore: update
antfu May 7, 2022
8f84137
chore: cleanup
antfu May 7, 2022
a3d5325
chore: update
antfu May 7, 2022
e44a2b3
chore: update
antfu May 7, 2022
76fa2ea
chore: Merge branch 'main' into chore/vite-plugin-glob
antfu May 7, 2022
1c1fbe3
chore: cleanup
antfu May 7, 2022
b194219
chore: update
antfu May 7, 2022
bf771dd
chore: cleanup
antfu May 7, 2022
1cd6f2d
chore: update
antfu May 7, 2022
a8b7e53
chore: Merge branch 'main' into chore/vite-plugin-glob
antfu May 7, 2022
9637203
chore: cleanup
antfu May 7, 2022
9779078
chore: update
antfu May 7, 2022
3c3c93b
chore: try
antfu May 7, 2022
d2c6104
chore: update
antfu May 7, 2022
1c6c295
chore: cleanup
antfu May 7, 2022
ff22800
chore: fix plugin order
antfu May 8, 2022
5f9388b
chore: update
antfu May 8, 2022
2294300
docs: update docs [ci-skip]
antfu May 8, 2022
ceffc8c
chore: improve watcher without globs
antfu May 8, 2022
f4a364c
chore: cleanup
antfu May 8, 2022
50b762a
fix: windows path
antfu May 8, 2022
3fe2d5c
chore: try fix windows
antfu May 8, 2022
8ac8193
Apply suggestions from code review
antfu May 8, 2022
74c1e0f
feat: support scan
antfu May 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 107 additions & 8 deletions docs/guide/features.md
Original file line number Diff line number Diff line change
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 passing `{ eager: true }` the second argument:
antfu marked this conversation as resolved.
Show resolved Hide resolved

```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 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:
antfu marked this conversation as resolved.
Show resolved Hide resolved

```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' })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are multiple imports supported with an array? Should we add a section about this or maybe just use it in the example?

Suggested change
const modules = import.meta.glob('./dir/*.js', { import: 'setup' })
const modules = import.meta.glob('./dir/*.js', { import: ['setup', 'render'] })

Copy link
Member Author

@antfu antfu May 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it does not support arrays. Referring to the eager: true example, the named import will be directly used as the value instead of the "Module" object. For multiple named imports they might better use multiple statements.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Sound good since most of the cases when using glob would require a single import, so looks good to me to wait until users request the feature. We could support both string or array if we need to go there anyway.

```

```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)
}
```

Combining with `eager`, it's even possible to have tree-shaking enable for those modules.
antfu marked this conversation as resolved.
Show resolved Hide resolved

```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 }
})
```
Comment on lines +401 to +405
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, it would be good to avoid the name query since the way we add metadata/modifiers to imports is probably going to change in the future. But overriding as to allow for multiple values doesn't sound future-proof, and any other more generic name may actually end up conflicting with whatever gets standardized in the end. I thought of metadata, modifiers, with... we still have time until v3 to bike shed a bit if we find a more generic name that we are sure will not collide later. But for the moment, I think we can move forward with query

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess unless we are going to break our convention of using queries in v3.0, maybe naming it as query is still the most intuitive way to do it. In the future, if we find an alternative that does not use queries, (if it's not v3, probably v4) we could break this to align. It might be hard for now to foresee what's the format we gonna use in the future.


```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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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) => {
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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\\")
}"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!/node_modules/
Loading