import isPlainObject from 'lodash/isPlainObject' ; import merge from 'lodash/merge' ; import { parse } from 'url' ; import { load } from 'cheerio' ; import { Handler , RenderOpts , FilterContext } from './index' ; type FilterRootContainer = ( ssrHtml : string , functor ? : ( html : string , context ? : FilterContext ) => string ) => string ; param html param functor export const filterRootContainer = ( html , functor ) => { const bodyExp = /<body>(.*?)<\/body>/is ; const placeholderExp = /<!-- UMI_SERVER_TMP_PLACEHOLDER -->/gs ; const placeholder = '<body><!-- UMI_SERVER_TMP_PLACEHOLDER --></body>' ; const layout = html . replace ( bodyExp , placeholder ) ; const root = html . match ( bodyExp ) ?. [ 1 ] || '' ; const matchPublicPath = root . match ( /<script.*src="([^"]*)\/?umi\.(\w+\.)?js"[^>]*>/i ) ; const publicPath = matchPublicPath ?. [ 1 ] || '/' ; const context = { publicPath , } ; const layoutHtml = functor ( layout , context ) ; return layoutHtml . replace ( placeholderExp , root ) ; } ; export const _getDocumentHandler = ( html , option = { } ) => { const docTypeHtml = /^<!DOCTYPE html>/ . test ( html ) ? html : `<!DOCTYPE html>${ html }` ; return load ( docTypeHtml , { decodeEntities : false , recognizeSelfClosing : true , ... option , } ) ; } ; export const injectChunkMaps = ( $ , args ) => { const { chunkMap , publicPath = '/' } = args ; const { js = [ ] , css = [ ] } = chunkMap || { } ; const styles = css . filter ( style => ! /^umi\.(\w+\.)?css$/g . test ( style ) ) || [ ] ; styles . forEach ( style => { if ( style ) { $ ( 'head' ) . append ( `<link rel="stylesheet" href="${ publicPath }${ style }" />` ) ; } } ) ; const scripts = js . filter ( script => ! /^umi([.\w]*)?\.js$/g . test ( script ) ) || [ ] ; scripts . forEach ( script => { if ( script ) { $ ( 'head' ) . append ( `<link rel="preload" href="${ publicPath }${ script }" as="script"/>` ) ; } } ) ; return $ ; } ; export type INodePolyfillDecorator = ( enable : boolean , url ? : string ) => ( renderOpts ? : RenderOpts , context ? : { url : string ; } ) => void ; export const nodePolyfillDecorator = ( enable = false , origin = 'http://localhost' ) => { const { pathname : defaultPathname } = parse ( origin ) ; global . window = { } ; if ( enable ) { const ssrPolyfill = require ( 'ssr-polyfill' ) ; const mockWin = ssrPolyfill ( { url : origin , } ) ; const mountGlobal = [ 'document' , 'location' , 'navigator' , 'Image' , 'self' ] ; mountGlobal . forEach ( mount => { global [ mount ] = mockWin [ mount ] ; } ) ; global . window = mockWin ; mountGlobal . forEach ( mount => { global [ mount ] = mockWin [ mount ] ; } ) ; return ( renderOpts , context = { url : defaultPathname , } ) => { const { url } = context ; const { polyfill , runInMockContext } = renderOpts ; let nextOrigin = url ; if ( ( polyfill as any ) ?. host ) { nextOrigin = `${ ( polyfill as any ) . host }${ url }` ; } const { protocol , host } = parse ( origin ) ; const nextUrl = /^https?:\/\// . test ( nextOrigin ) ? nextOrigin : `${ protocol }//${ host }${ url }` ; const nextObj = parse ( nextUrl ) ; Object . defineProperty ( global . window , 'location' , { writable : true , value : { origin : `${ nextObj . protocol }//${ nextObj . hostname }${ nextObj ?. port || '' }` , ... nextObj , } , } ) ; global . location = { origin : `${ nextObj . protocol }//${ nextObj . hostname }${ nextObj ?. port || '' }` , ... nextObj , } ; if ( runInMockContext ) { let mockContext ; if ( isPlainObject ( runInMockContext ) ) { mockContext = runInMockContext ; } if ( typeof runInMockContext === 'function' ) { mockContext = runInMockContext ( ) ; } merge ( global . window , mockContext || { } ) ; } } ; } return ( ) => { } ; } ;