import path from 'path' ; import webpack , { Compilation , WebpackPluginInstance } from 'webpack' ; type Attributes = Record < string , any > ; type Context = { title ? : string ; htmlAttributes ? : Attributes ; cssAttributes ? : Attributes ; jsAttributes ? : Attributes ; } ; type Files = Record < string , string [ ] > ; type TemplateParameters = { css ? : string [ ] ; js ? : string [ ] ; body ? : string ; head ? : string ; publicPath : string ; } & Context ; type Options = { filename ? : string ; publicPath ? : string ; context ? : Context ; template ? : ( args : TemplateParameters ) => string | Promise < string > ; chunks ? : string [ ] ; } ; function getFiles ( entrypoints , chunks ? ) { const ret = { } ; entrypoints . forEach ( ( entry ) => { if ( chunks && ! chunks . includes ( entry . name ) ) { return ; } entry . getFiles ( ) . forEach ( ( file ) => { const extension = path . extname ( file ) . replace ( /\./ , '' ) ; if ( ! ret [ extension ] ) { ret [ extension ] = [ ] ; } ret [ extension ] . push ( file ) ; } ) ; } ) ; return ret ; } function generateAttributes ( attributes = { } ) { const stringAttributes = Object . entries ( attributes ) ; if ( stringAttributes . length === 0 ) { return '' ; } return ( ' ' + stringAttributes . map ( ( attr ) => { if ( attr [ 1 ] === true ) { return attr [ 0 ] ; } return `${ attr [ 0 ] }="${ attr [ 1 ] }"` ; } ) . join ( ' ' ) ) ; } function generateCSSReferences ( { files = [ ] , publicPath = '' , attributes = { } , } ) { if ( ! attributes ) { return '' ; } const allAttributes = { ... attributes , rel : 'rel' in attributes ? attributes . rel : 'stylesheet' , } ; return files . map ( ( file ) => `<link href="${ publicPath }${ file }"${ generateAttributes ( allAttributes ) }>` ) . join ( '' ) ; } function generateJSReferences ( { files = [ ] , publicPath = '' , attributes = { } , } ) { if ( ! attributes ) { return '' ; } return files . map ( ( file ) => `<script src="${ publicPath }${ file }"${ generateAttributes ( attributes ) }></script>` ) . join ( '' ) ; } function defaultTemplate ( { css = [ ] , js = [ ] , publicPath = '' , title = '' , htmlAttributes = { lang : 'en' , } , head = '' , body = '' , cssAttributes = { } , jsAttributes = { } , } ) { const htmlAttrs = generateAttributes ( htmlAttributes ) ; const cssTags = generateCSSReferences ( { files : css , attributes : cssAttributes , publicPath , } ) ; const jsTags = generateJSReferences ( { files : js , attributes : jsAttributes , publicPath , } ) ; return `<!DOCTYPE html>
  <html${ htmlAttrs }>
    <head>
      <meta charset="UTF-8">
      <title>${ title }</title>
      ${ head }${ cssTags }
    </head>
    <body>
      ${ body }${ jsTags }
    </body>
  </html>` ; } function isWebpack4 ( ) { return webpack . version . split ( '.' ) [ 0 ] === '4' ; } class MiniHtmlWebpackPlugin implements WebpackPluginInstance { private options : Options ; public constructor ( options ) { this . options = options || { } ; this . webpack4plugin = this . webpack4plugin . bind ( this ) ; this . webpack5plugin = this . webpack5plugin . bind ( this ) ; } private webpack4plugin ( compilation , callback ) { const { filename = 'index.html' , publicPath = '' , template , context , chunks , } = this . options ; const files = getFiles ( compilation . entrypoints , chunks ) ; const options = Object . assign ( { } , { publicPath } , context , files ) ; Promise . resolve ( ( template || defaultTemplate ) ( options ) ) . then ( ( source ) => { const sources = require ( 'webpack-sources' ) ; compilation . assets [ filename ] = new sources . RawSource ( source , true ) ; callback ( ) ; } ) ; } private webpack5plugin ( compilation ) { const { filename = 'index.html' , publicPath = '' , template , context , chunks , } = this . options ; return Promise . resolve ( ( template || defaultTemplate ) ( { publicPath , ... context , ... getFiles ( compilation . entrypoints , chunks ) , } ) ) . then ( ( source ) => { const { sources } = require ( 'webpack' ) ; compilation . emitAsset ( filename , new sources . RawSource ( source , true ) ) ; } ) ; } public apply ( compiler ) { const pluginName = 'MiniHtmlWebpackPlugin' ; if ( isWebpack4 ( ) ) { compiler . hooks . emit . tapAsync ( pluginName , this . webpack4plugin ) ; } else { compiler . hooks . thisCompilation . tap ( pluginName , ( compilation ) => { compilation . hooks . processAssets . tapPromise ( { name : pluginName , stage : Compilation . PROCESS_ASSETS_STAGE_ADDITIONAL , } , ( ) => this . webpack5plugin ( compilation ) ) ; } ) ; } } } export { MiniHtmlWebpackPlugin , defaultTemplate , generateAttributes , generateCSSReferences , generateJSReferences , } ;