1- import type { OutputOptions } from 'rollup'
2- import { HtmlTagDescriptor , Plugin } from 'vite'
1+ import glob from 'fast-glob'
2+ import fs from 'fs-extra'
3+ import { dirname , join } from 'node:path'
4+ import { HtmlTagDescriptor , Plugin , normalizePath } from 'vite'
35
4- interface VendorLibrary {
6+ interface ImportMapSource {
57 name : string
6- pattern : RegExp
8+ pattern : string | RegExp
9+ entry : string
10+ recursiveDependence ?: boolean
11+ override ?: Record < string , Partial < ImportMapSource > >
12+ }
13+
14+ const parseDeps = ( root : string , pkg : string ) => {
15+ const pkgPath = join ( root , 'node_modules' , pkg , 'package.json' )
16+ if ( fs . existsSync ( pkgPath ) ) {
17+ const content = fs . readFileSync ( pkgPath , 'utf-8' )
18+ const pkg = JSON . parse ( content )
19+ return Object . keys ( pkg . dependencies || { } )
20+ }
21+ return [ ]
722}
823
924/**
@@ -23,53 +38,89 @@ interface VendorLibrary {
2338 * @returns {Plugin } A Vite plugin that generates and injects an import map
2439 */
2540export function generateImportMapPlugin (
26- vendorLibraries : VendorLibrary [ ]
41+ importMapSources : ImportMapSource [ ]
2742) : Plugin {
2843 const importMapEntries : Record < string , string > = { }
44+ const resolvedImportMapSources : Map < string , ImportMapSource > = new Map ( )
45+ const assetDir = 'assets/lib'
46+ let root : string
2947
3048 return {
3149 name : 'generate-import-map-plugin' ,
3250
3351 // Configure manual chunks during the build process
3452 configResolved ( config ) {
53+ root = config . root
54+
3555 if ( config . build ) {
3656 // Ensure rollupOptions exists
3757 if ( ! config . build . rollupOptions ) {
3858 config . build . rollupOptions = { }
3959 }
4060
41- const outputOptions : OutputOptions = {
42- manualChunks : ( id : string ) => {
43- for ( const lib of vendorLibraries ) {
44- if ( lib . pattern . test ( id ) ) {
45- return `vendor-${ lib . name } `
46- }
61+ for ( const source of importMapSources ) {
62+ resolvedImportMapSources . set ( source . name , source )
63+ if ( source . recursiveDependence ) {
64+ const deps = parseDeps ( root , source . name )
65+
66+ while ( deps . length ) {
67+ const dep = deps . shift ( ) !
68+ const depSource = Object . assign ( { } , source , {
69+ name : dep ,
70+ pattern : dep ,
71+ ...source . override ?. [ dep ]
72+ } )
73+ resolvedImportMapSources . set ( depSource . name , depSource )
74+
75+ const _deps = parseDeps ( root , depSource . name )
76+ deps . unshift ( ..._deps )
4777 }
48- return null
49- } ,
50- // Disable minification of internal exports to preserve function names
51- minifyInternalExports : false
78+ }
79+ }
80+
81+ const external : ( string | RegExp ) [ ] = [ ]
82+ for ( const [ , source ] of resolvedImportMapSources ) {
83+ external . push ( source . pattern )
5284 }
53- config . build . rollupOptions . output = outputOptions
85+ config . build . rollupOptions . external = external
5486 }
5587 } ,
5688
57- generateBundle ( _options , bundle ) {
58- for ( const fileName in bundle ) {
59- const chunk = bundle [ fileName ]
60- if ( chunk . type === 'chunk' && ! chunk . isEntry ) {
61- // Find matching vendor library by chunk name
62- const vendorLib = vendorLibraries . find (
63- ( lib ) => chunk . name === `vendor-${ lib . name } `
64- )
65-
66- if ( vendorLib ) {
67- const relativePath = `./${ chunk . fileName . replace ( / \\ / g, '/' ) } `
68- importMapEntries [ vendorLib . name ] = relativePath
69-
70- console . log (
71- `[ImportMap Plugin] Found chunk: ${ chunk . name } -> Mapped '${ vendorLib . name } ' to '${ relativePath } '`
72- )
89+ generateBundle ( _options ) {
90+ for ( const [ , source ] of resolvedImportMapSources ) {
91+ if ( source . entry ) {
92+ const moduleFile = join ( source . name , source . entry )
93+ const sourceFile = join ( root , 'node_modules' , moduleFile )
94+ const targetFile = join ( root , 'dist' , assetDir , moduleFile )
95+
96+ importMapEntries [ source . name ] =
97+ './' + normalizePath ( join ( assetDir , moduleFile ) )
98+
99+ const targetDir = dirname ( targetFile )
100+ if ( ! fs . existsSync ( targetDir ) ) {
101+ fs . mkdirSync ( targetDir , { recursive : true } )
102+ }
103+ fs . copyFileSync ( sourceFile , targetFile )
104+ }
105+
106+ if ( source . recursiveDependence ) {
107+ const files = glob . sync ( [ '**/*.{js,mjs}' ] , {
108+ cwd : join ( root , 'node_modules' , source . name )
109+ } )
110+
111+ for ( const file of files ) {
112+ const moduleFile = join ( source . name , file )
113+ const sourceFile = join ( root , 'node_modules' , moduleFile )
114+ const targetFile = join ( root , 'dist' , assetDir , moduleFile )
115+
116+ importMapEntries [ normalizePath ( join ( source . name , dirname ( file ) ) ) ] =
117+ './' + normalizePath ( join ( assetDir , moduleFile ) )
118+
119+ const targetDir = dirname ( targetFile )
120+ if ( ! fs . existsSync ( targetDir ) ) {
121+ fs . mkdirSync ( targetDir , { recursive : true } )
122+ }
123+ fs . copyFileSync ( sourceFile , targetFile )
73124 }
74125 }
75126 }
0 commit comments