mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
## What? Adds support for [Vite's `import.meta.glob`](https://vite.dev/guide/features.html#glob-import) in Turbopack. This is a compile-time transform that resolves glob patterns into a map of module paths to lazy/eager imports. ## Why? This is a commonly used Vite feature for dynamically importing groups of files (e.g., all markdown files in a directory, all route modules matching a pattern). ## How? ### Core implementation (`import_meta_glob.rs`) 1. **Analysis phase**: `import.meta.glob` is recognized as a well-known function via `WellKnownFunctionKind::ImportMetaGlob`. When called, arguments are statically analyzed to extract patterns and options. 2. **File discovery**: Uses Turbopack's `read_glob` (with `Glob::can_match_in_directory` for efficient directory pruning) to find matching files. Negative patterns (prefixed with `!`) are applied via a separate `Glob` matcher. 3. **Virtual module**: Each unique `import.meta.glob()` call generates a virtual `ImportMetaGlobAsset` module that exports an object mapping file paths to either: - **Lazy mode** (default): `() => import('./path')` thunks — resolved with `EcmaScriptModulesReferenceSubType::DynamicImport` - **Eager mode**: Direct `require('./path')` results — resolved with `EcmaScriptModulesReferenceSubType::Import` 4. **Code generation**: The `import.meta.glob(...)` call site is replaced with `__turbopack_require__(virtual_module_id)`. ### Architecture - **`ImportMetaGlobAsset`** is the virtual module. It stores only the origin, patterns, and options — no `source` reference. - **`ImportMetaGlobAsset::map()`** is a `#[turbo_tasks::function]` that builds glob matchers, scans the filesystem, and resolves all matched files as ESM imports. Being a turbo-tasks function, the result is memoised. Both `references()` and `chunk_item_content()` call this single cached function. - **`ident()`** is derived from `AssetIdent::from_path(origin_path)` with a modifier encoding all glob options, so two `import.meta.glob()` calls with different options produce different module idents. - **Side effects**: Lazy mode → `SideEffectFree` (only exports thunks, nothing evaluated). Eager mode → `ModuleEvaluationIsSideEffectFree` (the virtual module itself has no side effects, but its synchronous requires trigger real module evaluations). ### Supported options | Option | Description | |--------|-------------| | `eager` | `boolean` — load modules synchronously (default: `false`) | | `import` | `string` — select a named export (e.g., `'default'`) | | `query` | `string` — append query to imports (e.g., `'?raw'`) | | `base` | `string` — base directory for glob scanning | ### Error handling Unsupported or invalid usage produces clear compile-time diagnostics (error code `TP1008`): - `as` option → "not supported, use `query` instead" - Unknown option keys → lists supported options - Non-constant `eager` → "must be a constant boolean" - Non-constant patterns → "must be string literals" ### Not supported (intentionally) - `import.meta.globEager()` (removed in Vite 3) — users should use `{ eager: true }` - `as` option (deprecated in Vite 5) — users should use `query` ## Test Plan - [x] **Turbopack execution test** (`turbopack/crates/turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob/`) - Lazy mode, eager mode, named import (`import: 'default'`), negative patterns - [x] **Turbopack snapshot tests** (`turbopack/crates/turbopack-tests/tests/snapshot/import-meta/glob/`) - Lazy glob, eager glob, named import - Negative patterns (`!**/bar.js`) - Multiple patterns (`['./dir/*.js', './other/*.js']`) - [x] **Turbopack snapshot error tests** (`turbopack/crates/turbopack-tests/tests/snapshot/import-meta/glob-error/`) - `as` option error, non-constant `eager` error, unknown option error - [x] **Next.js e2e tests** (`test/e2e/import-meta-glob/`) - Lazy/eager/named import modules, negative patterns, multi-pattern - Passes in both dev (`next dev`) and production (`next build && next start`) modes - Skipped under webpack (`IS_WEBPACK_TEST=1`) since this is a Turbopack-only feature <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Niklas Mischkulnig <[email protected]>
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
// Lazy glob
|
|
const lazyModules = import.meta.glob('./modules/*.ts')
|
|
|
|
// Eager glob
|
|
const eagerModules = import.meta.glob('./modules/*.ts', { eager: true })
|
|
|
|
// Named import (eager)
|
|
const defaultExports = import.meta.glob('./modules/*.ts', {
|
|
import: 'default',
|
|
eager: true,
|
|
})
|
|
|
|
// Negative pattern
|
|
const filteredModules = import.meta.glob(['./modules/*.ts', '!**/skip.ts'], {
|
|
eager: true,
|
|
})
|
|
|
|
// Multiple patterns (modules + other)
|
|
const multiModules = import.meta.glob(['./modules/*.ts', './other/*.ts'], {
|
|
eager: true,
|
|
})
|
|
|
|
export default async function Page() {
|
|
// Resolve lazy modules
|
|
const lazyKeys = Object.keys(lazyModules).sort()
|
|
const lazyResults: Record<string, string> = {}
|
|
for (const key of lazyKeys) {
|
|
const mod = await lazyModules[key]()
|
|
lazyResults[key] = (mod as any).name
|
|
}
|
|
|
|
// Get eager module names
|
|
const eagerKeys = Object.keys(eagerModules).sort()
|
|
const eagerResults: Record<string, string> = {}
|
|
for (const key of eagerKeys) {
|
|
eagerResults[key] = (eagerModules[key] as any).name
|
|
}
|
|
|
|
// Get default exports
|
|
const defaultKeys = Object.keys(defaultExports).sort()
|
|
const defaultResults: Record<string, string> = {}
|
|
for (const key of defaultKeys) {
|
|
defaultResults[key] = (defaultExports[key] as any)()
|
|
}
|
|
|
|
// Get filtered module names (skip.ts should be excluded)
|
|
const filteredKeys = Object.keys(filteredModules).sort()
|
|
const filteredResults: Record<string, string> = {}
|
|
for (const key of filteredKeys) {
|
|
filteredResults[key] = (filteredModules[key] as any).name
|
|
}
|
|
|
|
// Get multi-pattern results (modules + other)
|
|
const multiKeys = Object.keys(multiModules).sort()
|
|
const multiResults: Record<string, string> = {}
|
|
for (const key of multiKeys) {
|
|
multiResults[key] = (multiModules[key] as any).name
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div id="lazy-keys">{JSON.stringify(lazyKeys)}</div>
|
|
<div id="lazy-results">{JSON.stringify(lazyResults)}</div>
|
|
<div id="eager-keys">{JSON.stringify(eagerKeys)}</div>
|
|
<div id="eager-results">{JSON.stringify(eagerResults)}</div>
|
|
<div id="default-results">{JSON.stringify(defaultResults)}</div>
|
|
<div id="filtered-keys">{JSON.stringify(filteredKeys)}</div>
|
|
<div id="filtered-results">{JSON.stringify(filteredResults)}</div>
|
|
<div id="multi-keys">{JSON.stringify(multiKeys)}</div>
|
|
<div id="multi-results">{JSON.stringify(multiResults)}</div>
|
|
</div>
|
|
)
|
|
}
|