import * as path from 'path' ; import * as fs from 'fs' ; import { isObject , isArray , set , get } from 'lodash' ; import { Context , ITaskConfig } from 'build-scripts' ; import { InlineConfig , BuildOptions } from 'vite' ; import type { ProcessOptions } from 'postcss' ; type Option = BuildOptions & InlineConfig ; type Transformer = ( value : any , ctx ? : Context , chain ? : ITaskConfig [ 'chainConfig' ] ) => any ; type ConfigMap = Record < string , string | string [ ] | { name : string | string [ ] ; transform : Transformer ; } > ; interface MinifierConfig { type : 'esbuild' | 'swc' | 'terser' ; options ? : Record < string , any > ; } interface PostcssOptions extends ProcessOptions { plugins ? : { [ pluginName ] : Record < string , any > ; } ; } /**
 * 设置 vite 字段的值
 */ const setViteConfig = ( acc , value , viteRow , isTransform = false ) => { if ( value === undefined ) return ; const config = isArray ( viteRow ) ? viteRow : [ viteRow ] ; config . forEach ( ( cfg , index ) => { set ( acc , cfg , isTransform && isArray ( viteRow ) ? value [ index ] : value ) ; } ) ; } ; const transformPlugin = ( pluginName ) : Transformer => { return ( ... [ , , webpackChain ] ) => { if ( ! webpackChain || ! webpackChain . plugins . has ( pluginName ) ) return ; const opts = webpackChain . plugin ( pluginName ) . get ( 'args' ) ?? [ ] ; return opts [ 0 ] ; } ; } ; const transformMinimizer = ( minimizerName ) : Transformer => { return ( ... [ , , webpackChain ] ) => { if ( ! webpackChain || ! webpackChain . optimization . minimizers . has ( minimizerName ) ) return ; const opts = webpackChain . optimization . minimizer ( minimizerName ) . get ( 'args' ) ?? [ ] ; return opts [ 0 ] ; } ; } ; const transformPreProcess = ( loaderName , rule ) : Transformer => { const optionsMap = { scss : 'sassOptions' , less : 'lessOptions' , } ; const pickOptions = [ 'additionalData' ] ; const optionsKey = optionsMap [ rule ] ; return ( ... args ) => { const opt = args [ 2 ] . module . rules . get ( rule ) . use ( loaderName ) . get ( 'options' ) ; const preProcessOptions = ( optionsKey ? opt ?. [ optionsKey ] : opt ) || { } ; pickOptions . forEach ( pickKey => { if ( opt ?. [ pickKey ] ) { preProcessOptions [ pickKey ] = opt [ pickKey ] ; } } ) ; return preProcessOptions ; } ; } ; const mapWithBrowserField = ( packageName , resolvePath ) : Record < string , string > => { const aliasMap = { } ; try { const packagePath = require . resolve ( `${ resolvePath }/package.json` ) ; const data = JSON . parse ( fs . readFileSync ( packagePath , 'utf8' ) ) ; if ( isObject ( data . browser ) ) { Object . keys ( data . browser ) . forEach ( ( requirePath ) => { const pathExtname = path . extname ( requirePath ) ; const aliasKey = path . join ( packageName , pathExtname ? requirePath . replace ( pathExtname , '' ) : requirePath ) ; aliasMap [ aliasKey ] = path . join ( resolvePath , data . browser [ requirePath ] ) ; } ) ; } } catch ( err ) { console . error ( `[Error] fail to resolve alias {${ packageName }: ${ resolvePath }}` , err ) ; } return aliasMap ; } ; /**
 * 常用简单配置转换
 */ const configMap = { 'output.path' : { name : 'build.outDir' , transform : ( value , ctx ) => path . relative ( ctx . rootDir , value ) , } , 'output.publicPath' : 'base' , 'resolve.alias' : { name : 'resolve.alias' , transform : ( value , ctx ) => { const { rootDir } = ctx ; const blackList = [ 'webpack/hot' ] ; const packagesWithBrowserField = [ 'react-dom' ] ; const data = Object . keys ( value ) . reduce ( ( acc , key ) => { if ( ! blackList . some ( ( word ) => value [ key ] ?. includes ( word ) ) ) { if ( packagesWithBrowserField . includes ( key ) ) { const aliasMap = mapWithBrowserField ( key , value [ key ] ) ; Object . keys ( aliasMap ) . forEach ( ( aliasKey ) => { acc [ aliasKey ] = aliasMap [ aliasKey ] ; } ) ; } acc [ key ] = value [ key ] ; } return acc ; } , { } ) ; data . ice = path . resolve ( rootDir , '.ice/index.ts' ) ; [ 'antd' , '@alifd/next' ] . forEach ( ( pkg ) => { data [ `~${ pkg }` ] = pkg ; } ) ; return Object . entries ( data ) . map ( ( [ find , replacement ] ) => { return { find , replacement } ; } ) ; } , } , 'resolve.extensions' : { name : 'resolve.extensions' , transform : ( value ) => [ '.mjs' , ... value ] , } , 'optimization.minimize' : { name : 'build.minify' , transform : ( value , { userConfig } ) => { if ( value ) { const { minify } = userConfig ; if ( minify ) { const minifier = ( minify as unknown as MinifierConfig ) . type || ( typeof minify === 'boolean' ? 'terser' : minify ) as string ; if ( [ 'esbuild' , 'terser' ] . includes ( minifier ) ) { return minifier ; } else { console . log ( `minify '${ minifier }' is not supported in vite mode, specify 'terser' as minifier` ) ; } } } else { return false ; } } , } , 'devtool' : { name : 'build.sourcemap' , transform : ( devtool ) => { if ( devtool ) { const sourcemap = [ 'inline' , 'hidden' ] . find ( ( mapType ) => { return ! ! devtool . match ( new RegExp ( mapType ) ) ; } ) ; return sourcemap || ! ! devtool ; } return false ; } , } , 'devServer.watchOptions.static.watch' : 'server.watch' , 'devServer.proxy' : { name : 'server.proxy' , transform : ( value ) => { let hasProxy = false ; const proxyConfig = { } ; ( value || [ ] ) . forEach ( ( { context , enable , onProxyRes , onError , pathRewrite , ... rest } ) => { if ( enable !== false ) { if ( ! hasProxy ) { hasProxy = true ; console . log ( 'Proxy setting detected. HTTPS will be downgraded to TLS only (HTTP/2 will be disabled)' ) ; } proxyConfig [ context ] = { ... rest , rewrite : ( requestPath ) => { if ( pathRewrite && isObject ( pathRewrite ) ) { return Object . keys ( pathRewrite ) . reduce ( ( acc , rewriteRule ) => { return acc . replace ( new RegExp ( rewriteRule ) , pathRewrite [ rewriteRule ] ) ; } , requestPath ) ; } else { return requestPath ; } } , configure : ( proxy , options ) => { if ( rest . configure ) { rest . configure ( proxy , options ) ; } if ( onProxyRes ) { proxy . on ( 'proxyRes' , onProxyRes ) ; } if ( onError ) { proxy . on ( 'error' , onError ) ; } } , } ; } } ) ; return hasProxy ? proxyConfig : undefined ; } , } , 'devServer.https' : 'server.https' , 'plugins.DefinePlugin' : { name : 'define' , transform : transformPlugin ( 'DefinePlugin' ) , } , 'plugins.TerserPlugin' : { name : 'build.terserOptions' , transform : ( ... args ) => { const terserPluginOptions = transformMinimizer ( 'TerserPlugin' ) ( ... args ) ; return terserPluginOptions ?. terserOptions ; } , } , 'plugin.ESBuild' : { name : 'build.target' , transform : ( ... args ) => { const esbuildMinifyOptions = transformMinimizer ( 'ESBuild' ) ( ... args ) ; return esbuildMinifyOptions ?. target ; } , } , sass : { name : 'css.preprocessorOptions.scss' , transform : transformPreProcess ( 'sass-loader' , 'scss' ) , } , less : { name : 'css.preprocessorOptions.less' , transform : transformPreProcess ( 'less-loader' , 'less' ) , } , dedupe : { name : 'resolve.dedupe' , transform : ( ) => [ 'react' , 'react-dom' ] , } , postcss : { name : 'css.postcss' , transform : ( e , { userConfig } ) => { if ( userConfig ?. postcssOptions ) { const postcssPlugins = ( userConfig ?. postcssOptions as PostcssOptions ) ?. plugins || { } ; const normalizedPlugins = Object . keys ( postcssPlugins ) . filter ( ( pluginKey ) => ! ! postcssPlugins [ pluginKey ] ) . map ( pluginKey => require ( pluginKey ) ( postcssPlugins [ pluginKey ] ) ) ; return { ... ( userConfig ?. postcssOptions as PostcssOptions ) , plugins : normalizedPlugins . length > 0 ? normalizedPlugins : [ ] , } ; } } } , vendor : { name : 'build.rollupOptions.output.manualChunks' , transform : ( e , { userConfig } ) => { if ( ! userConfig . vendor ) { return false ; } } } , hashAndOAP : { name : [ 'build.rollupOptions.output.entryFileNames' , 'build.rollupOptions.output.chunkFileNames' , 'build.rollupOptions.output.assetFileNames' , ] , transform : ( e , { userConfig } ) => { const data = userConfig . outputAssetsPath as { css : string ; js : string ; } ; const hash = userConfig . hash === true ? 'hash' : userConfig . hash ; const hashStr = hash ? `.[${ hash }]` : '' ; const { js , css } = data ; const assetFileNames = ( assetInfo ) => { if ( path . extname ( assetInfo . name ) === '.css' ) { return `${ css }/[name]${ hashStr }[extname]` ; } return `[name]${ hashStr }[extname]` ; } ; return [ `${ js }/[name]${ hashStr }.js` , `${ js }/[name]${ hashStr }.js` , assetFileNames , ] ; } } , } ; /**
 * 配置转化函数
 */ export const recordMap = ( chain , ctx ) : Partial < Record < keyof Option , any > > => { const cfg = chain . toConfig ( ) ; return Object . keys ( configMap ) . reduce ( ( acc , key ) => { const viteConfig = configMap [ key ] ; const webpackValue = get ( cfg , key ) ; if ( isObject ( viteConfig ) && ! isArray ( viteConfig ) ) { const value = viteConfig . transform ( webpackValue , ctx , chain ) ; setViteConfig ( acc , value , viteConfig . name , true ) ; return acc ; } setViteConfig ( acc , webpackValue , viteConfig ) ; return acc ; } , { } ) ; } ;