-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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: support moduleInfo.meta
in dev server
#5465
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
575cd7b
feat: synchronize `getModuleInfo` with ModuleGraph
aleclarson 7e9b058
fix: support `moduleInfo.meta` in dev server
aleclarson 9ee56ab
feat: set `meta.filename` for modules loaded from disk
aleclarson 8a251d6
revert "feat: set `meta.filename` for modules loaded from disk"
aleclarson 991e5a6
fix: remove pointless merging
aleclarson a8f111c
fix: allow returning `meta` from `transform` hook w/o overriding code
aleclarson 248f76c
test: add unit tests for plugin container
aleclarson File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
packages/vite/src/node/server/__tests__/pluginContainer.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { resolveConfig, UserConfig } from '../../config' | ||
import { Plugin } from '../../plugin' | ||
import { ModuleGraph } from '../moduleGraph' | ||
import { createPluginContainer, PluginContainer } from '../pluginContainer' | ||
|
||
let resolveId: (id: string) => any | ||
let moduleGraph: ModuleGraph | ||
|
||
describe('plugin container', () => { | ||
describe('getModuleInfo', () => { | ||
beforeEach(() => { | ||
moduleGraph = new ModuleGraph((id) => resolveId(id)) | ||
}) | ||
|
||
it('can pass metadata between hooks', async () => { | ||
const entryUrl = '/x.js' | ||
|
||
const metaArray: any[] = [] | ||
const plugin: Plugin = { | ||
name: 'p1', | ||
resolveId(id) { | ||
if (id === entryUrl) { | ||
// The module hasn't been resolved yet, so its info is null. | ||
const moduleInfo = this.getModuleInfo(entryUrl) | ||
expect(moduleInfo).toEqual(null) | ||
|
||
return { id, meta: { x: 1 } } | ||
} | ||
}, | ||
load(id) { | ||
if (id === entryUrl) { | ||
const { meta } = this.getModuleInfo(entryUrl) | ||
metaArray.push(meta) | ||
|
||
return { code: 'export {}', meta: { x: 2 } } | ||
} | ||
}, | ||
transform(code, id) { | ||
if (id === entryUrl) { | ||
const { meta } = this.getModuleInfo(entryUrl) | ||
metaArray.push(meta) | ||
|
||
return { meta: { x: 3 } } | ||
} | ||
}, | ||
buildEnd() { | ||
const { meta } = this.getModuleInfo(entryUrl) | ||
metaArray.push(meta) | ||
} | ||
} | ||
|
||
const container = await getPluginContainer({ | ||
plugins: [plugin] | ||
}) | ||
|
||
const entryModule = await moduleGraph.ensureEntryFromUrl(entryUrl) | ||
expect(entryModule.meta).toEqual({ x: 1 }) | ||
|
||
const loadResult: any = await container.load(entryUrl) | ||
expect(loadResult?.meta).toEqual({ x: 2 }) | ||
|
||
await container.transform(loadResult.code, entryUrl) | ||
await container.close() | ||
|
||
expect(metaArray).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) | ||
}) | ||
|
||
it('can pass metadata between plugins', async () => { | ||
const entryUrl = '/x.js' | ||
|
||
const plugin1: Plugin = { | ||
name: 'p1', | ||
resolveId(id) { | ||
if (id === entryUrl) { | ||
return { id, meta: { x: 1 } } | ||
} | ||
} | ||
} | ||
|
||
const plugin2: Plugin = { | ||
name: 'p2', | ||
load(id) { | ||
if (id === entryUrl) { | ||
const { meta } = this.getModuleInfo(entryUrl) | ||
expect(meta).toEqual({ x: 1 }) | ||
return null | ||
} | ||
} | ||
} | ||
|
||
const container = await getPluginContainer({ | ||
plugins: [plugin1, plugin2] | ||
}) | ||
|
||
await moduleGraph.ensureEntryFromUrl(entryUrl) | ||
await container.load(entryUrl) | ||
|
||
expect.assertions(1) | ||
}) | ||
}) | ||
}) | ||
|
||
async function getPluginContainer( | ||
inlineConfig?: UserConfig | ||
): Promise<PluginContainer> { | ||
const config = await resolveConfig( | ||
{ configFile: false, ...inlineConfig }, | ||
'serve' | ||
) | ||
|
||
// @ts-ignore: This plugin requires a ViteDevServer instance. | ||
config.plugins = config.plugins.filter((p) => !/pre-alias/.test(p.name)) | ||
|
||
resolveId = (id) => container.resolveId(id) | ||
const container = await createPluginContainer(config, moduleGraph) | ||
return container | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
import { extname } from 'path' | ||
import { ModuleInfo, PartialResolvedId } from 'rollup' | ||
import { parse as parseUrl } from 'url' | ||
import { isDirectCSSRequest } from '../plugins/css' | ||
import { | ||
cleanUrl, | ||
|
@@ -8,8 +10,6 @@ import { | |
} from '../utils' | ||
import { FS_PREFIX } from '../constants' | ||
import { TransformResult } from './transformRequest' | ||
import { PluginContainer } from './pluginContainer' | ||
import { parse as parseUrl } from 'url' | ||
|
||
export class ModuleNode { | ||
/** | ||
|
@@ -22,6 +22,8 @@ export class ModuleNode { | |
id: string | null = null | ||
file: string | null = null | ||
type: 'js' | 'css' | ||
info?: ModuleInfo | ||
meta?: Record<string, any> | ||
importers = new Set<ModuleNode>() | ||
importedModules = new Set<ModuleNode>() | ||
acceptedHmrDeps = new Set<ModuleNode>() | ||
|
@@ -45,17 +47,23 @@ function invalidateSSRModule(mod: ModuleNode, seen: Set<ModuleNode>) { | |
mod.ssrModule = null | ||
mod.importers.forEach((importer) => invalidateSSRModule(importer, seen)) | ||
} | ||
|
||
export type ResolvedUrl = [ | ||
url: string, | ||
resolvedId: string, | ||
meta: object | null | undefined | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. non-blocking: Is there a better type then |
||
] | ||
|
||
export class ModuleGraph { | ||
urlToModuleMap = new Map<string, ModuleNode>() | ||
idToModuleMap = new Map<string, ModuleNode>() | ||
// a single file may corresponds to multiple modules with different queries | ||
fileToModulesMap = new Map<string, Set<ModuleNode>>() | ||
safeModulesPath = new Set<string>() | ||
container: PluginContainer | ||
|
||
constructor(container: PluginContainer) { | ||
this.container = container | ||
} | ||
constructor( | ||
private resolveId: (url: string) => Promise<PartialResolvedId | null> | ||
) {} | ||
|
||
async getModuleByUrl(rawUrl: string): Promise<ModuleNode | undefined> { | ||
const [url] = await this.resolveUrl(rawUrl) | ||
|
@@ -81,6 +89,7 @@ export class ModuleGraph { | |
} | ||
|
||
invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()): void { | ||
mod.info = undefined | ||
mod.transformResult = null | ||
mod.ssrTransformResult = null | ||
invalidateSSRModule(mod, seen) | ||
|
@@ -140,10 +149,11 @@ export class ModuleGraph { | |
} | ||
|
||
async ensureEntryFromUrl(rawUrl: string): Promise<ModuleNode> { | ||
const [url, resolvedId] = await this.resolveUrl(rawUrl) | ||
const [url, resolvedId, meta] = await this.resolveUrl(rawUrl) | ||
let mod = this.urlToModuleMap.get(url) | ||
if (!mod) { | ||
mod = new ModuleNode(url) | ||
if (meta) mod.meta = meta | ||
this.urlToModuleMap.set(url, mod) | ||
mod.id = resolvedId | ||
this.idToModuleMap.set(resolvedId, mod) | ||
|
@@ -187,14 +197,15 @@ export class ModuleGraph { | |
// 1. remove the HMR timestamp query (?t=xxxx) | ||
// 2. resolve its extension so that urls with or without extension all map to | ||
// the same module | ||
async resolveUrl(url: string): Promise<[string, string]> { | ||
async resolveUrl(url: string): Promise<ResolvedUrl> { | ||
url = removeImportQuery(removeTimestampQuery(url)) | ||
const resolvedId = (await this.container.resolveId(url))?.id || url | ||
const resolved = await this.resolveId(url) | ||
const resolvedId = resolved?.id || url | ||
const ext = extname(cleanUrl(resolvedId)) | ||
const { pathname, search, hash } = parseUrl(url) | ||
if (ext && !pathname!.endsWith(ext)) { | ||
url = pathname + ext + (search || '') + (hash || '') | ||
} | ||
return [url, resolvedId] | ||
return [url, resolvedId, resolved?.meta] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates a tight coupling between
ModuleGraph
andcreatePluginContainer
, but I don't see any way to avoid that. And it's probably not a big deal.