Skip to content

Commit 6f99ee3

Browse files
committed
fix(migrate): keep declare module 'vite' augmentations on 'vite'
Partially reverts e29973c. Through the core alias a preserved declare module 'vite' augmentation reaches the same @voidzero-dev/vite-plus-core module whose UserConfig types defineConfig from vite-plus, so it keeps working after migration. vite-plus exports no UserConfig symbol, so the rewritten declare module 'vite-plus' augmentation merged with nothing and silently stopped applying. Users extending vite-plus's own surface write declare module 'vite-plus' directly. Restores the #2004 config-files-only scoping for all vite specifiers, flips the regression test to assert preservation, regenerates the migration-rewrite-declare-module snapshot, and documents the rationale in migrate-rules.md.
1 parent e29973c commit 6f99ee3

4 files changed

Lines changed: 36 additions & 73 deletions

File tree

crates/vite_migration/src/import_rewriter.rs

Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,25 +1571,6 @@ static PARSED_VITE_RULES: LazyLock<Vec<RuleConfig<SupportLang>>> = LazyLock::new
15711571
ast_grep::load_rules(REWRITE_VITE_RULES).expect("failed to parse vite rewrite rules")
15721572
});
15731573

1574-
const VITE_DECLARE_MODULE_RULE_IDS: [&str; 2] =
1575-
["rewrite-declare-module-vite", "rewrite-declare-module-vite-subpath"];
1576-
1577-
/// The `declare module 'vite'` augmentation rules, applied in EVERY file (not
1578-
/// only config entry files). The Issue #2004 config-file scoping covers value
1579-
/// imports, whose unrewritten `vite` specifiers still resolve through the core
1580-
/// alias; a module augmentation instead binds to the literal specifier of the
1581-
/// import it augments, and the config's own import IS rewritten to `vite-plus`,
1582-
/// so leaving the augmentation on `vite` would fail type-checking after
1583-
/// migration.
1584-
static PARSED_VITE_DECLARE_MODULE_RULES: LazyLock<Vec<RuleConfig<SupportLang>>> =
1585-
LazyLock::new(|| {
1586-
ast_grep::load_rules(REWRITE_VITE_RULES)
1587-
.expect("failed to parse vite rewrite rules")
1588-
.into_iter()
1589-
.filter(|rule| VITE_DECLARE_MODULE_RULE_IDS.contains(&rule.id.as_str()))
1590-
.collect()
1591-
});
1592-
15931574
static PARSED_VITEST_RULES: LazyLock<Vec<RuleConfig<SupportLang>>> = LazyLock::new(|| {
15941575
ast_grep::load_rules(REWRITE_VITEST_RULES).expect("failed to parse vitest rewrite rules")
15951576
});
@@ -2289,12 +2270,14 @@ fn rewrite_import(
22892270
// Read the file
22902271
let content = std::fs::read_to_string(file_path)?;
22912272

2292-
// Issue #2004: `vite` value imports are rewritten only in config entry
2293-
// files; everything else keeps its `vite` imports (they still resolve
2294-
// through the core alias). `declare module 'vite'` augmentations are the
2295-
// exception and are rewritten everywhere — see
2296-
// `PARSED_VITE_DECLARE_MODULE_RULES`. This is layered ON TOP of the
2297-
// package-level skip (a skipped package stays skipped).
2273+
// Issue #2004: `vite` specifiers are rewritten only in config entry files;
2274+
// everything else keeps its `vite` imports (they still resolve through the
2275+
// core alias). This includes `declare module 'vite'` augmentations: through
2276+
// the alias they reach the SAME `@voidzero-dev/vite-plus-core` module whose
2277+
// `UserConfig` types `defineConfig` from `vite-plus`, so they keep working,
2278+
// whereas `vite-plus` re-exports no `UserConfig` symbol for a rewritten
2279+
// augmentation to merge with. This is layered ON TOP of the package-level
2280+
// skip (a skipped package stays skipped).
22982281
rewrite_import_content_full(
22992282
&content,
23002283
skip_packages,
@@ -2359,13 +2342,12 @@ fn rewrite_import_content_full(
23592342
let mut updated = false;
23602343
let mut preserved_vitest = false;
23612344

2362-
// Apply vite rules if not skipped (using pre-parsed rules). Outside config
2363-
// entry files only the `declare module 'vite'` augmentation rules apply
2364-
// (Issue #2004 keeps every other `vite` specifier).
2365-
if !skip_packages.skip_vite {
2366-
let vite_rules: &[RuleConfig<SupportLang>] =
2367-
if vite_config_file { &PARSED_VITE_RULES } else { &PARSED_VITE_DECLARE_MODULE_RULES };
2368-
let vite_content = ast_grep::apply_loaded_rules(&new_content, vite_rules);
2345+
// Apply vite rules if not skipped (using pre-parsed rules). Issue #2004:
2346+
// they apply only in config entry files; every other file keeps its `vite`
2347+
// specifiers, including `declare module 'vite'` augmentations (see the
2348+
// comment in `rewrite_import`).
2349+
if !skip_packages.skip_vite && vite_config_file {
2350+
let vite_content = ast_grep::apply_loaded_rules(&new_content, &PARSED_VITE_RULES);
23692351
if vite_content != new_content {
23702352
new_content = vite_content;
23712353
updated = true;
@@ -3074,11 +3056,14 @@ describe('test', () => {});"#,
30743056
}
30753057

30763058
#[test]
3077-
fn test_declare_module_vite_rewritten_outside_config_files() {
3078-
// A `declare module 'vite'` augmentation binds to the literal specifier
3079-
// of the import it augments. The config's own import IS rewritten to
3080-
// `vite-plus`, so the augmentation must follow even though it lives in
3081-
// a non-config .d.ts (unlike value imports, which Issue #2004 keeps).
3059+
fn test_declare_module_vite_preserved_outside_config_files() {
3060+
// A `declare module 'vite'` augmentation must stay on `vite`: through
3061+
// the core alias it reaches the same `@voidzero-dev/vite-plus-core`
3062+
// module whose `UserConfig` types `defineConfig` from `vite-plus`.
3063+
// `vite-plus` re-exports no `UserConfig` symbol, so rewriting the
3064+
// augmentation to `declare module 'vite-plus'` would orphan it (it
3065+
// would merge with nothing). Users extending vite-plus itself write
3066+
// `declare module 'vite-plus'` by hand.
30823067
use std::fs;
30833068

30843069
let temp = tempdir().unwrap();
@@ -3099,37 +3084,14 @@ describe('test', () => {});"#,
30993084

31003085
let dts = fs::read_to_string(temp.path().join("types/vite.d.ts")).unwrap();
31013086
assert!(
3102-
dts.contains("declare module 'vite-plus'"),
3103-
"augmentation must track the rewritten config import, got: {dts}"
3104-
);
3105-
assert!(
3106-
dts.contains("from 'vite'"),
3107-
"non-config `vite` value/type imports must be preserved, got: {dts}"
3087+
!dts.contains("vite-plus"),
3088+
"non-config `vite` specifiers (including augmentations) must be preserved, got: {dts}"
31083089
);
3090+
assert!(dts.contains("declare module 'vite'"));
31093091
let config = fs::read_to_string(temp.path().join("vite.config.ts")).unwrap();
31103092
assert!(config.contains("from 'vite-plus'"));
31113093
}
31123094

3113-
#[test]
3114-
fn test_declare_module_vite_skipped_in_plugin_packages() {
3115-
// The package-level plugin skip stays authoritative: a vite-plugin-*
3116-
// package keeps its `declare module 'vite'` augmentation untouched.
3117-
use std::fs;
3118-
3119-
let temp = tempdir().unwrap();
3120-
fs::write(temp.path().join("package.json"), r#"{"name":"vite-plugin-example"}"#).unwrap();
3121-
fs::write(
3122-
temp.path().join("types.d.ts"),
3123-
"declare module 'vite' {\n interface UserConfig {\n example?: object;\n }\n}\n",
3124-
)
3125-
.unwrap();
3126-
3127-
rewrite_imports_in_directory(temp.path()).unwrap();
3128-
3129-
let dts = fs::read_to_string(temp.path().join("types.d.ts")).unwrap();
3130-
assert!(dts.contains("declare module 'vite'"), "plugin package must be skipped: {dts}");
3131-
}
3132-
31333095
#[test]
31343096
fn test_preserves_unscoped_vitest_in_nuxt_test_utils_packages() {
31353097
use std::fs;

docs/guide/migrate-rules.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,13 @@ Plugin packages (an unscoped name starting with `vite-plugin-` or
177177
`unplugin-`, or `vite` in `peerDependencies`/`dependencies`) skip the rewrite
178178
even in config files. Only the `vite` specifier is in scope for this rule.
179179

180-
One exception goes the other way: `declare module 'vite'` augmentations are
181-
rewritten to `declare module 'vite-plus'` in every file, not only configs. A
182-
module augmentation binds to the literal specifier, so it must keep targeting
183-
the same module as the rewritten config import or the augmented options fail
184-
type-checking. (This differs from `vitest` augmentations, which are never
185-
rewritten; see below.)
180+
`declare module 'vite'` augmentations follow the same rule and are preserved
181+
outside config files. Through the core alias they reach the same
182+
`@voidzero-dev/vite-plus-core` module whose `UserConfig` types `defineConfig`
183+
from `vite-plus`, so they keep working after migration; `vite-plus` itself
184+
exports no `UserConfig` symbol, so a rewritten `declare module 'vite-plus'`
185+
augmentation would merge with nothing. Extensions aimed at `vite-plus`'s own
186+
surface are written against `vite-plus` by hand.
186187

187188
### `vitest` and Browser Imports
188189

packages/cli/snap-tests-global/migration-rewrite-declare-module/snap.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
> vp migrate --no-interactive # retained vitest augmentations should keep a package-local vitest
22
◇ Migrated . to Vite+ <semver>
33
• Node <semver> pnpm <semver>
4-
• 2 config updates applied, 1 file had imports rewritten
4+
• 2 config updates applied
55

6-
> cat src/index.ts # declare module 'vite' follows the rewritten config import; 'vitest' augmentations keep the upstream identity
6+
> cat src/index.ts # declare module 'vite'/'vitest' outside config files are preserved: through the core alias a 'vite' augmentation reaches the UserConfig that types vite-plus defineConfig, while vite-plus exports no UserConfig symbol to merge a rewritten augmentation with
77
import type { RuntimeEnvConfig } from './runtime.env.config.js';
88
import type { RuntimeHtmlConfig } from './runtime.html.config.js';
99

10-
declare module 'vite-plus' {
10+
declare module 'vite' {
1111
interface UserConfig {
1212
/**
1313
* Options for vite-plugin-runtime-env

packages/cli/snap-tests-global/migration-rewrite-declare-module/steps.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"commands": [
33
"vp migrate --no-interactive # retained vitest augmentations should keep a package-local vitest",
4-
"cat src/index.ts # declare module 'vite' follows the rewritten config import; 'vitest' augmentations keep the upstream identity",
4+
"cat src/index.ts # declare module 'vite'/'vitest' outside config files are preserved: through the core alias a 'vite' augmentation reaches the UserConfig that types vite-plus defineConfig, while vite-plus exports no UserConfig symbol to merge a rewritten augmentation with",
55
"cat package.json # check package.json",
66
"cat pnpm-workspace.yaml # check pnpm-workspace.yaml has overrides and catalog"
77
]

0 commit comments

Comments
 (0)