import { PluginHooks } from '../../types/pluginHooks' ; import { findDependencies , getModuleMarker , isSameFilepath , parseOptions } from '../utils' ; import { builderInfo , EXPOSES_MAP , parsedOptions } from '../public' ; import { ConfigTypeSet , SharedRuntimeInfo , VitePluginFederationOptions } from 'types' ; import { walk } from 'estree-walker' ; import MagicString from 'magic-string' ; import * as path from 'path' ; const sharedFileReg = /^__federation_shared_.+\.js$/ ; const pickSharedNameReg = /(?<=^__federation_shared_).+(?=\.js$)/ ; export function prodSharedPlugin ( options ) { parsedOptions . prodShared = parseOptions ( options . shared || { } , ( ) => ( { import : true , shareScope : 'default' } ) , ( value ) => { value . import = value . import ?? true ; value . shareScope = value . shareScope || 'default' ; return value ; } ) as ( string | ( ConfigTypeSet & SharedRuntimeInfo ) ) [ ] ; const sharedNames = new Set < string > ( ) ; parsedOptions . prodShared . forEach ( ( value ) => sharedNames . add ( value [ 0 ] ) ) ; const exposesModuleIdSet = new Set ( ) ; EXPOSES_MAP . forEach ( ( value ) => { exposesModuleIdSet . add ( `${ value }.js` ) ; } ) ; let isHost ; let isRemote ; return { name : 'originjs:shared-production' , virtualFile : { __federation_lib_semver : 'void 0' , __federation_fn_import : `
      const moduleMap= ${ getModuleMarker ( 'moduleMap' , 'var' ) }
      const moduleCache = Object.create(null);
      async function importShared(name,shareScope = 'default') {
        return moduleCache[name] ? new Promise((r) => r(moduleCache[name])) : getProviderSharedModule(name, shareScope);
      }
      async function __federation_import(name){
        return import(name);
      }
      async function getProviderSharedModule(name,shareScope) {
        let module = null;
        if (globalThis?.__federation_shared__?.[shareScope]?.[name]) {
          const versionObj = globalThis.__federation_shared__[shareScope][name];
          const versionKey = Object.keys(versionObj)[0];
          const versionValue = Object.values(versionObj)[0];
          if (moduleMap[name]?.requiredVersion) {
            // judge version satisfy
            const semver= await import('__federation_lib_semver');
            const fn = semver.satisfy;
            if (fn(versionKey, moduleMap[name].requiredVersion)) {
               module = await versionValue.metaGet();
            } else {
              console.log(\`provider support \${name}(\${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})\`)
            }
          } else {
            module = await versionValue.metaGet();
          }
        }
        if(module){
          moduleCache[name] = module;
          return module;
        }else{
          return getConsumerSharedModule(name, shareScope);
        }
      }
      async function getConsumerSharedModule(name , shareScope) {
        if (moduleMap[name]?.import) {
          const module = await moduleMap[name].get()
          moduleCache[name] = module;
          return module;
        } else {
          console.error(\`consumer config import=false,so cant use callback shared module\`)
        }
      }
      export {importShared};
      ` } , options ( inputOptions ) { isHost = ! ! parsedOptions . prodRemote . length ; isRemote = ! ! parsedOptions . prodExpose . length ; if ( sharedNames . size ) { inputOptions . external = ( inputOptions . external as [ ] ) ?. filter ( ( item ) => { return ! sharedNames . has ( item ) ; } ) ; } return inputOptions ; } , async buildStart ( ) { for ( const arr of parsedOptions . prodShared ) { const id = ( await this . resolve ( arr [ 0 ] ) ) ?. id ; arr [ 1 ] . id = id ; if ( isHost && ! arr [ 1 ] . version ) { const regExp = new RegExp ( `node_modules[/\\\\]${ arr [ 0 ] }[/\\\\]` ) ; const packageJsonPath = `${ id ?. split ( regExp ) [ 0 ] }node_modules/${ arr [ 0 ] }/package.json` ; try { arr [ 1 ] . version = ( await import ( packageJsonPath ) ) . version ; arr [ 1 ] . version . length ; } catch ( e ) { this . error ( `No description file or no version in description file (usually package.json) of ${ arr [ 0 ] }(${ packageJsonPath }). Add version to description file, or manually specify version in shared config.` ) ; } } } if ( parsedOptions . prodShared . length && isRemote ) { this . emitFile ( { fileName : `${ builderInfo . assetsDir ? builderInfo . assetsDir + '/' : '' }__federation_fn_import.js` , type : 'chunk' , id : '__federation_fn_import' , preserveSignature : 'strict' } ) ; this . emitFile ( { fileName : `${ builderInfo . assetsDir ? builderInfo . assetsDir + '/' : '' }__federation_lib_semver.js` , type : 'chunk' , id : '__federation_lib_semver' , preserveSignature : 'strict' } ) ; } } , async buildEnd ( ) { for ( const sharedInfo of parsedOptions . prodShared ) { if ( ! sharedInfo [ 1 ] . id ) { const resolved = await this . resolve ( sharedInfo [ 0 ] ) ; sharedInfo [ 1 ] . id = resolved ?. id ; } } } , outputOptions : function ( outputOption ) { outputOption . hoistTransitiveImports = false ; const that = this ; const priority = [ ] ; const depInShared = new Map ( ) ; parsedOptions . prodShared . forEach ( ( value ) => { const shareName = value [ 0 ] ; const usedSharedModuleIds = new Set < string > ( ) ; const sharedModuleIds = new Map < string , string > ( ) ; parsedOptions . prodShared . filter ( ( item ) => item [ 0 ] !== shareName ) . forEach ( ( item ) => sharedModuleIds . set ( item [ 1 ] . id , item [ 0 ] ) ) ; depInShared . set ( shareName , usedSharedModuleIds ) ; const deps = new Set < string > ( ) ; findDependencies . apply ( that , [ value [ 1 ] . id , deps , sharedModuleIds , usedSharedModuleIds ] ) ; value [ 1 ] . dependencies = deps ; } ) ; const orderByDepCount = [ ] ; depInShared . forEach ( ( value , key ) => { if ( ! orderByDepCount [ value . size ] ) { orderByDepCount [ value . size ] = new Map ( ) ; } orderByDepCount [ value . size ] . set ( key , value ) ; } ) ; if ( orderByDepCount [ 0 ] ) { for ( const key of orderByDepCount [ 0 ] . keys ( ) ) { priority . push ( key ) ; } } orderByDepCount . filter ( ( item , index ) => item && index >= 1 ) . forEach ( ( item ) => { for ( const entry of item . entries ( ) ) { addDep ( entry , priority , depInShared ) ; } } ) ; function addDep ( [ key , value ] , priority , depInShared ) { for ( const dep of value ) { if ( ! priority . includes ( dep ) ) { addDep ( [ dep , depInShared . get ( dep ) ] , priority , depInShared ) ; } } if ( ! priority . includes ( key ) ) { priority . push ( key ) ; } } parsedOptions . prodShared . sort ( ( a , b ) => { const aIndex = priority . findIndex ( ( value ) => value === a [ 0 ] ) ; const bIndex = priority . findIndex ( ( value ) => value === b [ 0 ] ) ; return aIndex - bIndex ; } ) ; if ( typeof outputOption . manualChunks === 'function' ) { outputOption . manualChunks = new Proxy ( outputOption . manualChunks , { apply ( target , thisArg , argArray ) { const id = argArray [ 0 ] ; const find = parsedOptions . prodShared . find ( ( arr ) => arr [ 1 ] . dependencies . has ( id ) ) ; return find ? find [ 0 ] : target ( argArray [ 0 ] , argArray [ 1 ] ) ; } } ) ; } return outputOption ; } , renderChunk : function ( code , chunk , options ) { const sharedFlag = sharedFileReg . test ( path . basename ( chunk . fileName ) ) ; const exposesFlag = parsedOptions . prodExpose . some ( ( expose ) => isSameFilepath ( expose [ 1 ] . id , chunk . facadeModuleId as string ) ) ; const needSharedImport = isRemote && parsedOptions . prodShared . length > 0 && chunk . type === 'chunk' && ( sharedFlag || exposesFlag ) && chunk . imports . some ( ( importName ) => sharedFileReg . test ( path . basename ( importName ) ) ) ; if ( needSharedImport ) { const ast = this . parse ( code ) ; const magicString = new MagicString ( code ) ; let modify = false ; switch ( options . format ) { case 'es' : { walk ( ast , { enter ( node ) { if ( node . type === 'ImportDeclaration' && sharedFileReg . test ( path . basename ( node . source . value ) ) ) { const sharedName = path . basename ( node . source . value ) . match ( pickSharedNameReg ) ?. [ 0 ] ; if ( sharedName ) { const declaration = [ ] ; node . specifiers ?. forEach ( ( specify ) => { declaration . push ( `${ specify . imported ?. name ? `${ specify . imported . name === specify . local . name ? specify . local . name : `${ specify . imported . name }:${ specify . local . name }` }` : `default:${ specify . local . name }` }` ) ; } ) ; if ( declaration . length ) { magicString . overwrite ( node . start , node . end , `const {${ declaration . join ( ',' ) }} = await importShared('${ sharedName }')` ) ; modify = true ; } } } } } ) ; if ( modify ) { magicString . prepend ( `import {importShared} from './__federation_fn_import.js'\n` ) ; return magicString . toString ( ) ; } } break ; case 'system' : { walk ( ast , { enter ( node ) { const expression = node . body . length === 1 ? node . body [ 0 ] ?. expression : node . body . find ( ( item ) => item . type === 'ExpressionStatement' && item . expression ?. callee ?. object ?. name === 'System' && item . expression . callee . property ?. name === 'register' ) ?. expression ; if ( expression ) { const args = expression . arguments ; if ( args [ 0 ] . type === 'ArrayExpression' && args [ 0 ] . elements ?. length > 0 ) { const importIndex = [ ] ; let removeLast = false ; chunk . imports . forEach ( ( importName , index ) => { const baseName = path . basename ( importName ) ; if ( sharedFileReg . test ( baseName ) ) { importIndex . push ( { index : index , name : baseName . match ( pickSharedNameReg ) ?. [ 0 ] } ) ; if ( index === chunk . imports . length - 1 ) { removeLast = true ; } } } ) ; if ( importIndex . length && args [ 1 ] ?. type === 'FunctionExpression' ) { const functionExpression = args [ 1 ] ; const returnStatement = functionExpression ?. body ?. body . find ( ( item ) => item . type === 'ReturnStatement' ) ; magicString . prependLeft ( returnStatement . start , 'var __federation_import;\n' ) ; const setters = returnStatement . argument . properties . find ( ( property ) => property . key . name === 'setters' ) ; const settersElements = setters . value . elements ; magicString . appendRight ( setters . end - 1 , `${ removeLast ? '' : ',' }function (module){__federation_import=module.importShared}` ) ; const execute = returnStatement . argument . properties . find ( ( property ) => property . key . name === 'execute' ) ; const insertPos = execute . value . body . body [ 0 ] . start ; importIndex . forEach ( ( item ) => { const last = item . index === settersElements . length - 1 ; magicString . remove ( settersElements [ item . index ] . start , last ? settersElements [ item . index ] . end : settersElements [ item . index + 1 ] . start - 1 ) ; magicString . remove ( args [ 0 ] . elements [ item . index ] . start , last ? args [ 0 ] . elements [ item . index ] . end : args [ 0 ] . elements [ item . index + 1 ] . start - 1 ) ; const varName = `__federation_${ item . name }` ; magicString . prependLeft ( insertPos , `var  ${ varName } = await __federation_import('${ item . name }');\n` ) ; setters . value . elements [ item . index ] . body . body . forEach ( ( setFn ) => { magicString . appendLeft ( insertPos , `${ setFn . expression . left . name } = ${ varName }.${ setFn . expression . right . property . name ?? setFn . expression . right . property . value };\n` ) ; } ) ; } ) ; magicString . prependLeft ( execute . value . start , ' async ' ) ; magicString . appendRight ( args [ 0 ] . end - 1 , `${ removeLast ? '' : ',' }'./__federation_fn_import.js'` ) ; modify = true ; } } } this . skip ( ) ; } } ) ; if ( modify ) { return magicString . toString ( ) ; } } break ; } } return null ; } } ; }