@@ -7,6 +7,7 @@ import type { ImportSpecifier } from 'es-module-lexer'
7
7
import { init , parse as parseImports } from 'es-module-lexer'
8
8
import { parse as parseJS } from 'acorn'
9
9
import type { Node } from 'estree'
10
+ import { findStaticImports , parseStaticImport } from 'mlly'
10
11
import { makeLegalIdentifier } from '@rollup/pluginutils'
11
12
import type { ViteDevServer } from '..'
12
13
import {
@@ -20,7 +21,8 @@ import {
20
21
import {
21
22
debugHmr ,
22
23
handlePrunedModules ,
23
- lexAcceptedHmrDeps
24
+ lexAcceptedHmrDeps ,
25
+ lexAcceptedHmrExports
24
26
} from '../server/hmr'
25
27
import {
26
28
cleanUrl ,
@@ -84,6 +86,48 @@ function markExplicitImport(url: string) {
84
86
return url
85
87
}
86
88
89
+ async function extractImportedBindings (
90
+ id : string ,
91
+ source : string ,
92
+ importSpec : ImportSpecifier ,
93
+ importedBindings : Map < string , Set < string > >
94
+ ) {
95
+ let bindings = importedBindings . get ( id )
96
+ if ( ! bindings ) {
97
+ bindings = new Set < string > ( )
98
+ importedBindings . set ( id , bindings )
99
+ }
100
+
101
+ const isDynamic = importSpec . d > - 1
102
+ const isMeta = importSpec . d === - 2
103
+ if ( isDynamic || isMeta ) {
104
+ // this basically means the module will be impacted by any change in its dep
105
+ bindings . add ( '*' )
106
+ return
107
+ }
108
+
109
+ const exp = source . slice ( importSpec . ss , importSpec . se )
110
+ const [ match0 ] = findStaticImports ( exp )
111
+ if ( ! match0 ) {
112
+ return
113
+ }
114
+ const parsed = parseStaticImport ( match0 )
115
+ if ( ! parsed ) {
116
+ return
117
+ }
118
+ if ( parsed . namespacedImport ) {
119
+ bindings . add ( '*' )
120
+ }
121
+ if ( parsed . defaultImport ) {
122
+ bindings . add ( 'default' )
123
+ }
124
+ if ( parsed . namedImports ) {
125
+ for ( const name of Object . keys ( parsed . namedImports ) ) {
126
+ bindings . add ( name )
127
+ }
128
+ }
129
+ }
130
+
87
131
/**
88
132
* Server-only plugin that lexes, resolves, rewrites and analyzes url imports.
89
133
*
@@ -116,6 +160,7 @@ function markExplicitImport(url: string) {
116
160
export function importAnalysisPlugin ( config : ResolvedConfig ) : Plugin {
117
161
const { root, base } = config
118
162
const clientPublicPath = path . posix . join ( base , CLIENT_PUBLIC_PATH )
163
+ const enablePartialAccept = config . experimental ?. hmrPartialAccept
119
164
let server : ViteDevServer
120
165
121
166
return {
@@ -143,9 +188,10 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
143
188
const start = performance . now ( )
144
189
await init
145
190
let imports : readonly ImportSpecifier [ ] = [ ]
191
+ let exports : readonly string [ ] = [ ]
146
192
source = stripBomTag ( source )
147
193
try {
148
- imports = parseImports ( source ) [ 0 ]
194
+ ; [ imports , exports ] = parseImports ( source )
149
195
} catch ( e : any ) {
150
196
const isVue = importer . endsWith ( '.vue' )
151
197
const maybeJSX = ! isVue && isJSRequest ( importer )
@@ -204,6 +250,11 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
204
250
start : number
205
251
end : number
206
252
} > ( )
253
+ let isPartiallySelfAccepting = false
254
+ const acceptedExports = new Set < string > ( )
255
+ const importedBindings = enablePartialAccept
256
+ ? new Map < string , Set < string > > ( )
257
+ : null
207
258
const toAbsoluteUrl = ( url : string ) =>
208
259
path . posix . resolve ( path . posix . dirname ( importerModule . url ) , url )
209
260
@@ -344,7 +395,14 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
344
395
hasHMR = true
345
396
if ( source . slice ( end + 4 , end + 11 ) === '.accept' ) {
346
397
// further analyze accepted modules
347
- if (
398
+ if ( source . slice ( end + 4 , end + 18 ) === '.acceptExports' ) {
399
+ lexAcceptedHmrExports (
400
+ source ,
401
+ source . indexOf ( '(' , end + 18 ) + 1 ,
402
+ acceptedExports
403
+ )
404
+ isPartiallySelfAccepting = true
405
+ } else if (
348
406
lexAcceptedHmrDeps (
349
407
source ,
350
408
source . indexOf ( '(' , end + 11 ) + 1 ,
@@ -464,6 +522,16 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
464
522
// make sure to normalize away base
465
523
const urlWithoutBase = url . replace ( base , '/' )
466
524
importedUrls . add ( urlWithoutBase )
525
+
526
+ if ( enablePartialAccept && importedBindings ) {
527
+ extractImportedBindings (
528
+ resolvedId ,
529
+ source ,
530
+ imports [ index ] ,
531
+ importedBindings
532
+ )
533
+ }
534
+
467
535
if ( ! isDynamicImport ) {
468
536
// for pre-transforming
469
537
staticImportedUrls . add ( { url : urlWithoutBase , id : resolvedId } )
@@ -531,6 +599,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
531
599
`${
532
600
isSelfAccepting
533
601
? `[self-accepts]`
602
+ : isPartiallySelfAccepting
603
+ ? `[accepts-exports]`
534
604
: acceptedUrls . size
535
605
? `[accepts-deps]`
536
606
: `[detected api usage]`
@@ -585,10 +655,22 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
585
655
if ( ssr && importerModule . isSelfAccepting ) {
586
656
isSelfAccepting = true
587
657
}
658
+ // a partially accepted module that accepts all its exports
659
+ // behaves like a self-accepted module in practice
660
+ if (
661
+ ! isSelfAccepting &&
662
+ isPartiallySelfAccepting &&
663
+ acceptedExports . size >= exports . length &&
664
+ exports . every ( ( name ) => acceptedExports . has ( name ) )
665
+ ) {
666
+ isSelfAccepting = true
667
+ }
588
668
const prunedImports = await moduleGraph . updateModuleInfo (
589
669
importerModule ,
590
670
importedUrls ,
671
+ importedBindings ,
591
672
normalizedAcceptedUrls ,
673
+ isPartiallySelfAccepting ? acceptedExports : null ,
592
674
isSelfAccepting ,
593
675
ssr
594
676
)
0 commit comments