vitejs / vitejs/vite-plugin-react
Resolve local function exports for file directive export validation
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.2k
- Forks
- 269
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 19
Description
- related to https://github.com/vitejs/vite-plugin-react/pull/1380
- related to https://github.com/vitejs/vite-plugin-react/issues/1335
Motivation
The inline directive alignment tracked by #1335 can expose a Next.js-aligned function validator and let directive owners apply it through existing transform callbacks:
runtime(value, name, meta) {
validateDirectiveFunction(meta.valueNode, directive)
return wrap(value, name)
}
This composes with transformHoistInlineDirective, transformModuleExportEffect, and transformWrapExport without baking Server Function policy into generic transforms.
For module directives, however, validation is only as complete as ModuleExportMeta.valueNode. Direct declarations and function-valued initializers expose their function node, but locally exported bindings do not currently resolve back to their declaration.
For example:
'use server'
async function action() {
console.log(arguments)
}
export { action }
and:
'use cache'
const cached = async function () {
console.log(arguments)
}
export default cached
The transform can register or wrap these exports, but callback metadata does not expose the source function node, so a directive owner cannot apply the same validation used for a direct export.
Next.js Behavior
Next.js performs a module prepass that maps exported local identifiers to export names. Exported functions then inherit the file-level "use server" or "use cache" directive before the function visitor checks this, super, and arguments.
This means validation does not depend on whether the function is written directly inside an export declaration or exported later through a local identifier. Non-exported functions do not inherit the file directive.
Error fixtures 23 and 24 in next-custom-transforms demonstrate the exported versus non-exported distinction and the lexical traversal rules. The local-binding prepass is implemented in server_actions.rs through export_name_by_local_id.
Prior Work
PR #1380 prototyped local function export resolution in module-export-scan.ts. It covered local aliases, default identifiers, exports before declarations, and direct-function metadata. The PR was closed in favor of #1387, which retained a simpler export-wrapper implementation and did not land local binding resolution.
Use #1380 as an implementation and test reference, but separate metadata discovery from its callable-rewriting requirements.
Proposed Direction
Extend module export scanning with an explicit representation for a statically resolved local function source.
Cover:
- Function declarations exported through local specifiers.
constbindings initialized with function expressions or arrows and exported through local specifiers.- Local aliases such as
export { action as renamed }. - Default identifier exports such as
export default action. - Exports appearing before their local declaration.
- Multiple export names referring to the same local function.
Do not assume that changing ModuleExportMeta.valueNode is necessarily the right API. It is currently documented as unavailable for export specifiers, and external consumers may rely on that distinction. Compare these shapes:
- Populate
valueNodeafter local resolution. - Add separate
resolvedValueNodemetadata. - Restore a narrow
directFunctionrepresentation similar to #1380. - Expose a separate local-binding resolver consumed by directive-aware integrations.
Prefer the smallest shape that lets transform callbacks validate the source function without coupling scanning to wrapper production.
Integration
Once a local function node is available, existing directive integrations should feed it into the same validator used for direct exports:
- Built-in file-level
"use server"throughtransformServerActionServer. - Framework-owned file-level
"use cache"throughtransformWrapExportor the preferred module wrapper transform. - Other custom Server Function directives using module export transform callbacks.
transformModuleExportEffect and transformWrapExport should remain generic. They should expose metadata but should not enforce this, super, or arguments restrictions themselves.
Semantics To Check
- Local export specifiers are live bindings, while default identifier exports have different binding behavior.
- Imported bindings and source re-exports do not have a local function body and must remain unresolved.
- Arbitrary call-expression initializers are not statically known functions.
- Mutable bindings can stop referring to their initial function, so metadata must not overstate certainty.
- Destructured declarations should remain unknown rather than assigning the container initializer to each binding.
- Duplicate aliases should not cause duplicate source rewriting or inconsistent validation.
- Metadata enrichment must not unexpectedly change existing
filterbehavior in public transform consumers.
Verification
Add focused module-export-scan and directive integration coverage for:
- Function declaration followed by a local export specifier.
- Local export before the function declaration.
constarrow and function-expression exports.- Renamed local exports.
- Default identifier exports.
- Multiple aliases of one local function.
- Imported bindings, re-exports, mutable bindings, call-expression initializers, and destructuring remaining unresolved where appropriate.
- File-level
"use server"and"use cache"validation rejectingthis,super, orargumentsthrough resolved local exports. - Non-exported functions remaining outside file-directive validation.
- Existing module wrapper, effect, filtering, and source-map snapshots remaining stable unless a deliberate metadata change requires updates.
Non-goals
- Changing inline method lowering from #1335.
- Baking directive validation into generic export transforms.
- Redesigning canonical callable production or local wrapped-binding semantics; coordinate those changes with #1350.
- Resolving functions across module boundaries.
- Changing proxy generation, reference manifests, closure transport, or cache policy.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with module-export-scan.ts and use PR #1380 as the implementation and test reference. Add focused module-export-scan and directive integration coverage for resolved local functions, unresolved bindings, and file-level "use server" or "use cache" validation, while checking existing wrapper, effect, filtering, and source-map snapshots. Done means callbacks can validate statically resolved local function nodes without generic transforms enforcing directive policy.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- build-system, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100