一起来理解webpack

249 阅读19分钟

1、事件(插件)机制

       webpack采用事件驱动的方式打包web应用,其事件机制主要依靠一个很小的核心库tapable来实现。tabable库主要实现了事件定义、事件处理程序注册、事件触发等基本的事件系统功能。

1-1、事件对象---Hook

在tapable库,事件对象被抽象为Hook,Hook主要结构如下:

class Hook {
    construnctor(args = [], name = undefined) {

        // 存储事件处理程序
        this.taps = []
        // 存储hook对象的拦截器,拦截器会分别在注册与触发事件处理程序时,
        // 对事件处理程序进行一系列前置处理
        this.interceptors = []

        // 下面三个属性,表示触发hook,并传入参数args;不同的触发方式对应相应的hook的行为
        // fn表示hook的行为为正常函数, asyncFn表示hook的行为为回调函数, prmiseFn
        // 表示hook的行为为promise

        this.call = fn;    
        this.callAsync = asyncFn
        this.promise = promiseFn    
    }

    // 注册事件处理函数的三种方式,
    // options : string | { name: string, before: boolean, after: boolean },
    // name为事件处理函数的标识,fn为事件处理函数, after, before,用于调度触发hook时,
    // hook关联的函数执行顺序
    
    tap(options, fn) {}
    tapAsync(options, fn) {}
    tapPromise(options) { /* function body */ }

    // 添加拦截器对象
    intercept(interceptor) { /* function body */}
}

此外,根据hook以一定的调用方式执行过程中事件处理函数的不同行为,Hook可以分为几下几类:

  • 基本型:事件处理函数按照某种调用方式执行;

  • 瀑布型:事件处理程序按照某种调用方式执行,但是上一个函数的返回值会传递到下一个函数而替换第一个参数;

  • 保释型:事件处理程序按照某种调用方式执行,但若某个函数缺省返回值,则立即返回;

  • 循环型:事件处理程序按照某种调用方式执行,若在此过程中若某个函数返回值不是undefined,则回到第一个函数,再次开始执行;

1-2、事件处理函数---Tap

  tap结构如下:

type tap {
    name: string,     // 事件处理函数标识符
    fn:               // 事件处理函数
    type: string      // 事件处理函数的注册方式,"sync" | "async" || "promise"
    before: array,       
    stage: number,
}

1-3、拦截器---Interceptor

interceptor主要结构如下:

class interceptor {

  // 1)*** 拦截器的功能函数:在hook注册与调用事件处理函数时的拦截行为 ***

    // 在使用hook.tap、hook.tapAsync、hook.tapPromise注册事件处理函数时的拦截行为,
    // 不能修改数参数tap
    tap(tap) {},

    // 在使用hook.tap、hook.tapAsync、hook.tapPromise注册事件处理函数时的拦截行为,
    // 可以修改参数tap 
    register(tap) {},

    // 在hook触发是的拦截行为,在hook的事件处理程序之前执行
    call(...args),
    
    // 在LoopHook中的每轮拦截行为
    loop(...args) {} 

 // 2)*** 拦截器的结果函数:如何处理拦截结果 ***
    
    // 拦截器结果的处理函数,result与done只执行一个;
    done() {}
    result() {} 
    
    // 拦截过程中出现错误的处理函数
    error() {}
}

1-4、事件处理函数工厂---HookCodeFactory

       HookCodeFactory主要功能是通过new Function(...args, functionBody) 将与hook关联的事件处理函数与拦截器,以不同的调用方式(顺序、并行、循环) 组合动态生成一个具有不同触发方式(call,callAsync,promise)的不同类型(sync,async)的hook触发函数;

1-5、Hook类型

       通过组合Hook的事件处理函数的不同行为与hook的不同类型 tapable核心库暴露以下九类Hook:

const (
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook // 解释为 异步hook函数, 允许tap为异步函数顺序执行, 且参数会传递
} = require("tapable")

2、基本流程

    webpack整体的构建流程大致可以分:

  1. 实例化compiler,加载指定插件,设置编译环境,加载内部插件,设置解析器;

  2. 运行compiler,创建构建过程complication,并对complication进行优化;

2-1、实例化compiler

实例化complier的代码,在webpack源代码的webpack.js文件中,简化后的代码如下:

const createCompiler = rawOptions => {     
    const options = getNormalizedWebpackOptions(rawOptions)  
    applyWebpackOptionsBaseDefaults(options)  

     // 1)创建compiler实例
    const compiler = new Compiler(options.context, options)
    applyWebpackOptionsDefaults(options)

    // 2)初始化config里的插件
    if (Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {			
            if (typeof plugin === "function") {				
                plugin.call(compiler, compiler)			
            } else {				
                plugin.apply(compiler)			
            }		
        }	
    }   
    
    // 3)触发enviromentHook     
    compiler.hooks.environment.call()	
    
    // 4) 触发afterEnvironmentHook    
    compiler.hooks.afterEnvironment.call()

    // 5)触发entryOptionHook ---> 实例化各种内部插件 --->  触发afterPluginsHook ---> 
    //   设置解析器---> 触发afterResolversHook
    new WebpackOptionsApply().process(options, compiler)

    // 6)触发initializeHook
    compiler.hooks.initialize.call();
	
    return compiler
}; 

// 单个配置对象
const webpack = (options, callback) => {
    const create = () => {
        // 检测并获取有效配置 
        [options].every(webpackOptionsSchemaCheck)
        getValidateSchema()(webpackOptionsSchema, options)
        const webpackOptions = options
        
        compiler = createCompiler(webpackOptions)
        watch = webpackOptions.watch
        watchOptions = webpackOptions.watchOptions || {}   
        return {compiler, watch, watchOptions }
    }

    const { compiler, watch, watchOptions } = create()

    if(callback) {
        if (watch) {     
            compiler.watch(watchOptions, callback)
        } else {
            compiler.run((err, stats) => { 
                compiler.close(err2 => {
                    callback(err || err2, stats)
                })
            })
        }
    } 
  
    return compiler
}

2-2、运行compiler

compiler类的伪代码如下:

class Compiler {
    constructor() { /* function body */}
        run() {
        this.readRecords()
        this.compile()
    }
    
    readRecords() { /* function body */ }
     
    emitAssets(compilation) { /* function body */ }  
    
    emitRecords(compilation) { /* function body */ }    
    
    onCompiled(compilation) {
        this.emitAssets(compication)
        this.emitRecords(complication)
    }
    
    createNormalModuleFactory() { /* function body */ }
    
    createContextModuleFactory() { /* function body */ }
    
    newCompilationParams() {
       const params = {     
           normalModuleFactory: this.createNormalModuleFactory()     
           contextModuleFactory: this.createContextModuleFactory()
       }
       return params
    } 
    
    newCompilation(params) { /* function body */ }
    
    compile() {
        const params = this.newCompilationParams()    
        const compilation = this.newCompilation(params)                      
        this.hooks.make.callAsync(compilation)            
        this.onCompiled(compilation)
    }   
}

compiler运行过程,可以分为以下几个阶段:

  1. readRecords:若存在构建记录,则读取构建记录,用于分包缓存优化(未设置recordsInputPath时直接返回);否则,直接返回;

  2. compile:解析并构建模块,生成构建过程compilation实例,并优化构建过程;

  3. 实例化normalModuleFactory与contextModuleFactory,生成compilationParams;

  4. 传入compilationParams,创建编译过程compilation实例,从入口文件递归添加和构建模块;

  5. 触发make的hook,执行所有监听make的插件;

  6. 调用complication.finish,报告构建过程中的错误与警告;

  7. 调用complication.seal,优化构建过程;

  8. emitAssets:将构建结果写入到输出目录;

  9. emitRecords:若已设置recordsInputPath,将构建记录写入到构建记录;否则直接返回;

2-3、插件系统

        webpack的插件系统是建立在事件机制与生命周期(或内部事件)上的,用户只需要在内部事件(webpack运行过程中各个组成部分的生命周期节点)上使用tap函数(tap、tapAsync、tapPromise)添加具体的插件功能函数,插件函数(事件处理函数)会在内部特定生命周期节点被触发时而执行。如:

// 定义插件函数
function MyPlugin() { 
    console.log('Synchronously tapping the compile hook.')
}

// 在特定生命周期注册插件函数
compiler.hooks.compile.tap(MyPlugin.name,  MyPlugin);

2-4、生命周期

      webpack的生命周期由运行过程中各个组成部分的生命周期节点构成,其组成部分及各个部分内的不同阶段的生命周期节点如下:

2-4-1、compiler的生命周期

1)初始化阶段

  1. 先在webpack.js中创建compiler实例,加载配置内插件后触发 :environmentafterEnvironment;

  2. 在执行webpackOptionsApply.prcocess中,执行EntryOptions插件后触发entryOption;加载内部插件后触发afterPlugins,在设置resolver配置后触发afterResolvers

  3. 最后,在webpack.js中,返回compiler前触发initialize

2)构建构成过程阶段

  1. 开始运行compiler阶段:在compiler.js中,当运行compiler时,依次触发beforeRunrunwatchRun(运行compiler.watch创建watcher时,在watch.js中触发)
  2. 运行构建过程中:在compiler.js中,当创建compilationParams时触发分别触发normalmodulefactorycontextModuleFactory;之后,触发beforeCompilecompile,然后在创建compilation实例中依次触发thisCompilationcompilationmakeafterCompile;
  3. 之后生成assets阶段:依次触发shouldEmitemitassetEmittedafterEmit
  4. 之后达到构建结果状态,触发donefailed

2-4-2、compilation的生命周期

1)构建阶段

     构建阶段:在创建构建队列(build queue)中触发buildModulesucceedModulefailedModule;在创建再次构建队列(rebuild queue)中触发rebuildModulefinishRebuildingModule;构建完所有模块且无错误时,触发finishModules

2)优化阶段

  1. 依赖项优化:触发optimizeDependenciesafterOptimizeDependencies

  2. Chunks生成:在chunks的生成过程前后触发 beforeChunks(2811行),afterChunks(2943行);见compilation.js;

  3. 模块优化:在模块的优化过程前后触发optimizeModulesafterOptimizeModules

  4. Chunk优化:在Chunk的优化过程前后触发optimizeChunksafterOptimizeChunks

  5. Tree优化:在Tree的优化过程前后触发optimizeTreesafterOptimizeTrees

  6. ChunkModules优化:在ChunkModules的优化过程前后触发optimizeChunkModulesafterOptimizeChunkModules

  7.  检测是否存储记录:触发shouldRecord;  

  8. ModuleId生成:在ModuleId的生成过程前后触发reviveModulesbeforeModuleIdsmoduleIdsoptimizeModuleIdsafterOptimizeModuleIds

  9. ChunkId生成:在ChunkId的生成过程前后触发reviveChunksbeforeChunks、chunksIdsoptimizeChunksafterOptimizeChunks

  10. 存储模块和Chunk记录:当shouldRecord为true时,依次触发recordModulesrecordChunks

  11. 哈希模块和构建过程compilation:在模块的哈希过程前后触发beforeModuleHashafterModuleHash;在构建过程的哈希过程前后触发beforeHashafterHash

  12. 存储记录哈希与构建过程的信息:触发recordHashrecord

  13. 生成ModuleAssets与ChunkAssets:在ModuleAssets的生成过程前后触发beforeModuleAssetsmoduleAsset;在ChunkAssets的生成过程前后触发shouldGenerateChunkAssetbeforeChunkAssetschunkAsset

  14. Assets优化:在Assets的优化过程前后触发optimizeChunkAssetsafterOptimizeChunkAssetsoptimizeAssetsprocessAssetsafterProcessAssets

3、配置

3-1、entry

// 应用入口与打包入口, 基本规则: 一个入口文件对应一个html文件,单页面应用只有一个入口文件
module.exports = {  
   // Entry descriptor object   
   main: {           
   dependOn: [],                 // 当前入口依赖的其他入口          
   filename: "bundled-filename", // 指定打包后的文件名,会覆盖output.filename    
   import: './index.js',         // 待打包文件的路径    
   library: {                    //  库选项,指定当前入口打包作为一个库时的使用名称、暴露方式、与输出的字段          
       name: "library-name",          
       type: "_",                // 定义库的暴露方式          
       export: ["a", "b"]    
   },    
   runtime: "bundled-runtime-chunk-name",    
   publicPath: "",           // output.path的 url in html page
},

3-2、output

const path = require('path')

const allOutputOptions = {    // filename for asset    
    asyncChunks: true,                            // 是否创建异步chunk用于按需加载    
    charset: true,                                // 是否使webapck在<script>标签上添加   
    charset='utf-8'    
    assetModuleFilename: '[hash][ext][query]',    // module, non-initial chunk name    
    // name: entry name, id: chunk id, contenthash,     
    filename: 'dirname/[name][contenthash][id].bundle.js',   // 设置entry对应的打包文件名称格式    
    path: "output-directory",                                // 设置打包文件的输出目录    
    chunkFilename: "[id].js",                                // 设置按需加载的chunk文件名称格式    
    chunkFormat: "array-push",                               // 'array-push' (web/WebWorker), 'commonjs' (node.js), 'module' (ESM)    
                             
    // 加载chunk的方法,false: 使按需加载失效,  'jsonp' (web), 'import' (ESM), 
    //'importScripts' (WebWorker), 'require' (sync node.js), 'async-node' (async node.js)  
    chunkLoading: false, 
    
    crossOriginLoading: false,                    // 是否允许跨域加载,只有 chunkLoading = 'jsonp'时有效    
    hotUpdateGlobal: "",                          //     
    chunkLoadTimeout: 120000,                     // chunk请求超时    
    chunkLoadingGlobal: "",                       // 加载chunk时的webpack全局变量    

    clean: { dry: false, keep: "" },              // 重新打包时,是否清理上次的打包文件    
    compareBeforeEmit: false,                     // 生成打包文件 前,检查是否存在 未发生变化的文件; webpack默认:不会再写输出未发生变化的文件    

    // 设置 devtool时的输入选项    
    devtoolNamespace: "",                          // 默认等于 output.uniqueName    
    devtoolModuleFilenameTemplate: "",             //     
    devtoolFallbackModuleFilenameTemplate: "",     //  Olny devtool = 'source-map'    
    sourceMapFilename: "[name][id][chunkhash]",    // 设置 source-map的名称    
    sourcePrefix: "",                               // 设置输出文件的前缀,可以进行缩进美化代码增加可读性    

    // 当 entry 为函数时,需要设置    
    enabledChunkLoadingTypes: [],                   // ['jsonp' | 'import-scripts' | 'require' | 'async-node' ]    
    enabledLibraryTypes: [],                        // ['module', 'commonjs', 'umd'] 详细见 
    output.library.type    

    // WASM module    
    enabledWasmLoadingTypes: ['fetch'],             // wasm    
    wasmLoading: "fetch",                           //     

    // globalObejct: "self",        

    // hash    
    hashDigest: 'hex',                            // hash摘要的编码方式    
    hashDigestlength: 20,                         // 截取hash值的前n位    
    hashFunction: "md4",                          // 设置hash算法    
    hashsalt: "",                                 // hash salt    

    // Hot update    
    hotUpdateChunkFilename: "[id][fullhash].hot-update.js",                // 通常使用默认值    
    hotUpdateMainFilename: "[runtime][fullhahs].hot-update.js",            // 通常使用默认值 

    // iife:立即执行函数       
    iife: true,                                   // 是否使用iife封装打包后的代码  

    // 生成代码的包含的ES features
    environment: {                                 
        arrowFunction: true,        
        bigIntLiteral: false,        
        const: true,        
        destructuring: true,        
        dynamicImport: false,        
        forOf: true,        
        module: false,        
        optionalChaining: true,        
        templateLiteral: true,    
     },      
 
     // importFunctionName    
     importFunctionName: "__import__",              // name for import() 
 
     uniqueName: "",                                // 打包文件成库时的选项      
     // 默认等于 output.library.name, 将用于output.chunkLoadingGlobal生成唯一global    
     library: {        
         name: 'library-name',        
         type: 'library-module-type',        
         export: ['default', 'subModeule'],          // 模块暴露那些 exports字段        
         auxiliaryComment: "Inserted Comment within exports wrapper",        
         umdNamedDefine: true,                       // For type = 'umd'    
     }, 
 
     // module: false,            // 输出JS为module类型 experimental feature. 

    scriptType: false,            // 设置script标签的type属性,false:表示禁止使用自定义script type异步加载脚本    
    pathinfo: false,              // 打包文件中是否包含打包文件里包含的模块的注释 true for developement; false for production    
    
    // publicPath: 输出文件在HTMl中的url的路径目录,
    
    // It automatically determines the public path from either `import.meta.url`,
    //`document.currentScript`, `<script />` or `self.location`.    
    publicPath: 'auto',
    // publicPath: 'https://cdn.example.com/assets/',     // CDN (always HTTPS)    
    // publicPath: '//cdn.example.com/assets/',           // CDN (same protocol)    
    // publicPath: '/assets/',                            // server-relative    
    // publicPath: 'assets/',                             // relative to HTML page    
    // publicPath: '../assets/',                          // relative to HTML page    
    // publicPath: '',                                    // relative to HTML page (same directory)  

    strictModuleExceptionHandling: true,           // 按照ES规范处理模块加载过程中的错误,会有性能代价
}

const typicalOutputOptions = {    
    path: path.resolve(__dirname, 'dist'),    
    filename: 'dirname/[name][contenthash][ext]',    
    chunkFilename: '[name][contenthash][chunkhash][id]',    
    assetModuleFilename: "[hash][ext][query]",    
    sourceMapFilename: '[name].map',    
    publicPath: path.resolve(__dirname, 'public'),    
    clean: true,    
    library: {        
        name: 'custom-library-name',        
        type: 'module',        
        export: ['default', 'subModule']    
    }
}

module.exports = typicalOutputOptions

3-3、module

const allModuleOptions = {    
        generator: {/* module-type: rule.generator */ },         // 设置不同模块类型对应的生成器    
        parser: {/* module-type: rule.parser */ },               // 模块不同类型对应的解析器    
        noParse: [],                  // 忽略对 未采用模块化方式的文件(不包含import, require,define等模块化语句) 的递归解析处理,提高构建性能;    
        unsafeCache: false,           // webpack5的缓存策略,默认下:开启cache选项且在node_modules下的依赖会被缓存,即true; 否则为false;    
        rules: [                      // Rule: 更改模块的创建方式,可对模块应用loader,或更改模块的解析器        
        { /* Rule */            
            // 设置loader类别,忽略等于normal; 所有loader会经历两个阶段:Pitch(不需要前面loader生成的结果)与Normal;            
            // Pitch阶段:loader从左向右依次进行;Normal阶段:loader从右向左依次进行     
            enforce: '', 
            
            // Resource condition: 被导入的模块,即 包含 export 的模块            
            test: "",              //  匹配模块类型            
            exclude: [],           //  匹配的文件不会经过test校验            
            include: [],           //  匹配的文件必须经过test校验            
            resource: "",          // 设置 被导入模块 的匹配规则            
            recourceQuery: "",     // 设置 被待query的导入模块的query规则            
            
            // Issuer condition:依赖其他模块的模块,即包含 import 的模块            
            issuer: "",             // 设置 发起模块导入请求的模块 的匹配规则            
            issuerLayer: "",        // 设置issuer的layer匹配规则          
            
            // Module            
            layer: "",              // 设置模块被放置的层            
            scheme: "",             // 设置 模块的uri的协议            
            mimetype: "",           // 设置 模块的uri媒体类型            
            type: "",               // 设置 模块类型,可绕过默认规则与默认模块行为,来设置自定义loader            
            sideEffects: "",        // Tree Shaking           
            use: [
                {   // UseEntry: loader object                    
                    loader: "loader-name",                    
                    options: {/* loadr options */ }                
                }            
            ],            
            rules: {},               // 嵌套规则            
            oneOf: [],               // 规则数组,选择满足条件的第一条规则,
            
            // generator, parser, resolve            
            generator: {                
                dataUrl: false,      // true时,为 {encoding: 'base64' | false, mimitype }                
                emit: fasle,         // 设置 是否生成Asset                
                filename: "",        // 同 output.assetModuleFilename,只对 模块类型为 asset 与 asset/resource的模块有效                
                outputPath: "",      // 相对 output.path的目录                
                publicPath: "",      // 同 output.publicPath,但是针对 Asset module            
           },                 
           parser: {                 // 解析器选项               
                parse: () => { },    // 解析器的解析函数,将toml,yaml等非JSON文件导入成JSON时很有用                
                dataUrlCondition: { maxSize: 4096 },  // 设置 可转换成内联的dataURl格式的模块最大值;低于此值,将会被内嵌为dataUrl格式            
           },            
           resolve: {}               // 模块级的config.resolve        
       }    
   ]
}
                    
const typicalModuleOptions = {}
                    
module.exports = typicalModuleOptions

3-4、resolve

const allResolveOptions = {      
    conditionNames: [/* 'require', 'node' */],  // 条件查找package.json中的exports的    
    modules: [],     // 按序查找 模块解析目录,绝对路径与相对路径,相对路径会向上查找,    
    alias: {        /** alternate-path: origin-path */    },    
    exportsFields: [/*'exports'*/],                 // 1) 包中package.json中的字段,     
    mainFields: [/*'browser', 'module', 'main'*/],  // 2) 解析含有package.json的目录时, 目录内package.json中的字段    
    mainFiles: [/* 'index' */],                     // 3)解析不含有package.json的目录时,或在2)中未找到有效文件时,查找该目录内的文件名称    
    extensions: [/* '.js', '.json' */],             // 4)当文件按不含扩展名时,依次推断文件的扩展名    
    extensionAlias: { /* '.js': ['.ts', '.js'], '.mjs': ['.mts', '.mjs'] */ },    
    restrictions: /**[string, RegExp] */"",        // 解析限制列表,只有被指定的模块路径可以被解析    
    // Cache    
    unsafeCache: [/** RegExp */],                  // webpack4的缓存策略 条件缓存, true:缓存所有    
    cachePredicate: /* function({ path , request} boolean) */ (module) => true,  // 条件缓存模块请求    
    // 其他    
    plugins: [],                                       // 模块解析插件    
    // import a from "a.js" , 设置true,将a.js 优先使用 ./a.js进行查找,否则在node_modules  
    preferRelative: /* Boolean: true | false */ true,  
    preferAbsolute: false,    
    aliasFields: [],     // 详情见:https://github.com/defunctzombie/package-browser-field-spec    
    byDependency: {}     // 为特定类型的模块,指定解析模块的选择项,
}
    
const typicalResolveOptions = {    
    modules: [],                 
    alias: { /** alternate-path: origin-path */ },    
    exportsFields: [/*'exports'*/],                     
    mainFields: [/*'browser', 'module', 'main'*/],     
    mainFiles: [/* 'index' */],                
}

module.exports = typicalResolveOptions

3-5、dev-server

const path = require('path');

// 设置webapck-dev-server的选项
const allDevServerOptions = {           
    allowedHosts: /* ['.host.com', 'host2.com'], */[],    // 可以访问服务的域名的白名单        
    bonjour: /* { type: 'http', protocol: 'udp' }*/ {},   // ???        
    client: {            
        logging: "", // 设置浏览器中的 log level             
        overlay: /**  errors: boolean, warnings: boolean } */ {}, // 设置错误或警告发生时,是否全屏覆盖,,广播天下            
        progress:  /** boolean */ false,                            // 打印编译进度            
        reconnect: /** true | bnumber */ true,                      // dev-server重连client的次数 ,true --->无限次            
        webSocketURL: {                
            protocol: 'ws',                
            password: 'dev-server',                
            username: 'webpack',                
            hostname: '0.0.0.0',                
            port: 8080,                
            pathname: '/ws',            
        },            
        webSocketTransport: /* 'ws' | 'sockjs' */'ws',        
    },        
    websocketServer: {            
        options: {                
            path: "/my/custom/path/to/web/socket/server"            
        }        
    },        
    compress: true,                           //  是否开启gzip压缩   
     // 设置webpack-dev-middleware的选项
    devMiddleware: {            
        index: true,            
        mimeTypes: { "text/html": ["phtml"] },            
        publicPath: "/publicPathForDevServe",            
        serverSideRender: true,            
        writeToDisk: true,        
     },                               
     static: {            
         directory: path.join(__dirname, 'public'),
         // 下列均为默认值      
         staticOptions: {             
             dotfiles: "ignore",        // 设置如何处理以 "."开头的文件或目录,                 
             etag: true,                // 是否生成etag                
             extensions: false /* ['html', 'js'] */,   // 设置文件扩展,当未找到文件时,依次收索指定扩展的文件                
             fallthrough: true,                         // 是否忽略中间件错误, true忽略,false返回错误                 
             index: "index.html",                       // 指定目录索引文件,                
             lastModified: true,                        // 是否设置 Last-Modified header                
             maxAge: 0,                                 // 是否设置 Cache-Control header的'max-age'属性                
             redirect: true,                            // 当路径为目录且以'/'结尾时, 重定向到该目录下                
             setHeaders: /** function(response, path, stat) */ () => { },  //      
         },            
         publicPath: ['/static-public-path-one/', '/static-public-path-two/'],            
         serveIndex: {},            
         watch: true,  // 默认tre        
     },        
     server: {            
         type: "https",            
         options: /* { ca, cert, key, pfx, cert, requestCert } */{},  // 使用https协议, dev-server默认http,也可以使用数字证书        
      },        
      headers: {},                       / add header to all responses        
      historyApiFallback: false,         // history模式时需要        
     
      watchFiles: false,        
      magicHtml: false,                  // 是否启用html-magic
       
      // middleware        
      onAfterSetupMiddleware: () => { },   // 在内部全部Middleware调用**后**调用        
      onBeforeSetupMiddleware: () => { },  // 在内部全部Middleware调用**前**调用    
       
      // local serve
      onListen: function (devServer) { },  // 开始监听端口时,调用       
      open: true,                          // 是否服务启动后,打开浏览器   
      host: 'local-ip',                   // 指定本地主机域名        
      port: 9000,                         // 服务端口 
       
      hot: true,                         // 是否开启热更新        
      liveReload: false,                 // 检测到变化时,重载/刷新页面         
      // Proxy
      proxy: [            
          {                
              context: ['/auth', '/api'],                
              target: 'http://localhost:3000',  //                
              // { '^/old/api': '/new/api' }                 
              pathRewrite: { '^/api': '' },     // 转发请求时,不会在路径上添加 '/api'          
              ws: false,                        // 是否代理websocket连接                
              secure: false,                    // 是否开启验证证书                
              changeOrigin: true,               // 是否支持虚拟主机站点                
              timeout: 5000,                    // 请求超时                
              proxyTimeout: 0,                  // 代理超时                
              // bypass:选择性进行代理                
              bypass(request, response, proxyOptions) {                    
                  return 'path'                
              }            
         }        
    ],        
    setupExitSignals: true,            // 允许退出    
}
    
const typicalDevServerOptions = {}

module.exports = typicalDevServerOptions

3-6、optimization

const allOptimizationOptions = {    
    // Module and Chunk         
    chunkIds: '',                       // chunk id 算法选择    
    moduleIds: '',                      // module id 算法选择
    // 是否 将模块图中的模块片段拼接成一个模块 默认:production: true, 其他为false    concatenateModules: true,          
    removeEmptyChunks: true,            // 是否 检测并移除空模块    
    removeAvailableModules: true,       // 同flagInculdedChunks,但对象为module    
    flagInculdedChunks: true,           // 是否 检测并标记子chunk, 当chunk已加载时,子chunk不会被加载, 默认:production: true, 其他为false    
    mergeDupicateChunks: true,          // 是否合并重复块 
    // 默认 :false, 表示每个chunk包含runtime; true| mutiple, 生成一个chunK间共享的包含runtime的chunk; single, 生成一个chunK间共享的runtime文件    
    runtimeChunk: false,                
    minimise: true,                     // 是否 使用默认的TersePlugin或 
    minimizer指定的Plugin压缩bundle    
    minimizer: {},    //    
    emitOnErrors: true,                 // 是否 在即使asset编译错误,仍然产生文件,可能会导致运行时错误    
    innerGraph: false,                  // 是否 对未使用的exports进行分析 
    // 是否生成相对路径的记录; 默认: false, 当设置recordsPath, recordsInputPath, recordsOutputPath中任一个时,为true
    portableRecords: false,             
    
    // ***Exports***   
    // 是否使用较短的name表示输出字段默认: true in production with two chars, for long cache    
    mangleExports: true,                                        
    mangleWasmImports: true,            // 同上,但针对wasm    
    providedExports: true,              // 是否 将 export * from ...转化为更具体的输出    
    usedExports: true,                           // true | false | 'global'    //     
    nodeEnv: false,                     // 是否 设置并覆盖process.env.NODE_ENV的值   
    // 是否 当Asset已生成哈希后,再hash一次; 设置false,使用内部数据计算hash,可能会导致内容未变化时hash值改变
    realHashContent: true,               
    sideEffects: true,                  // 是否 识别package.json或rules中的sideEffects,移除未使用且没有副作用的输出模块   
    
    //  使用动态导入方式生成的chunk的分块选项        
    splitChunks: {                         
    // 在下列情况下,webpack会默认分块:        
    //  1) 当ChunK可以共享, 或  模块来自node_modules目录中时;        
    //  2) 在进行 min+gzp前,chunk大小超过20KB        
    //  3) 当按需加载chunk时,并行请求最大值, 小于或等于 30        
    //  4) 初始页面加载时,并行请求最大值, 小于或等于 30        
    // 尝试 满足 3) 4)时 , 优先采用 使块的大小更大的分块方式        
    // 分隔条件        
        automaticNameDelimiter: '',          // 自动命名分隔符,用于命名chunk,默认 origin + name        
        chunks: 'async',                     // 设置进行优化的chunk类型, 'initial' | 'async' | 'all'        
        maxAsyncRequests: 30,                // 设置按需加载的最大异步请求数      maxInitialRequests: 30,        
        defaultSizeTypes: ['javascript', 'unknown'],   // 最小        
        minChunks: 1,                        // 设置 模块至少被chunk共享的次数,低于此值module不会被分隔成块       
        maxSize: /* n: number */0,           // 设置  可以被分成成chunk的最大尺寸;        
        maxInitialSize: /* n: number */0,    // 设置  按需entry模块可以被分成成chunk的最大尺寸;        
        maxAsyncSize: /* n: number */0,      // 设置  按需加载的模块可以被分成成chunk的最大尺寸;        
        layer: "",                           //  筛选layer复合规则的模块进行分组缓存        // 设置 被分割的 chunk的属性        
        minRemainingSize: /* n: number */ 0, // 设置 分割后chunk至少还有 n 字节        
        minSizeReduction: /* n: number */ 0, // 设置 当从chunk A 中分隔出的 chunk B使,chunk A至少减小  n 字节        
        hidePathInfo: false,                 // 设置 是否暴露通过 maxSize 分割时的被分隔块的路径信息,        
        name: '',                            // 设置 chunk name        // 设置分隔出的chunk属性        
        minSize: 20000,                      // 设置 模块可以被分隔成chunk的最小尺寸,单位bytes, 最大为 maxSize 
        
        // ***强制分隔***        
        // 忽略 minRemainingSize, maxAsyncRequests, maxInitialRequests
        // 设置,强制分隔chunk; 当设置时,大于 n 的chunk,被强制分隔        
        enforceSizeThreshold: /* n: number */ 50000,        
        usedExports: {},                         // 同 optimization.usedExports 
        // group: can interit or override any options fom optimization, and has below specific options  
        cacheGroups: {                      
        groupOne: {                
                test: '',                        // 设置当前缓存组的匹配规则          
                priority: /* n: number */ 0,     //  一个模块可以属于多个缓存组,优化优先应用到priority高的爨村组;                
                reuseExistingChunk: true,        // 若当前chunk包含main bundle中的模块,则main bundle                
                type: "",                        // 通过模块类型进行缓存              
                filename: ""                     // 覆盖 output.filename for initial entry            
         },            
         default: false,                         // 关闭默认缓存,默认缓存组的 priority = -20        }    
    },
}
         
onst typicalOptimizationOptions = {}

module.exports = typicalOptimizationOptions

3-7、cache

const allCacheOptions = {    
    type: "filesystem",                        
    // 设置 缓存方式: 'memory' | 'filesystem',    
    buildDependencies: {},              
    // ???    
    // only for type = 'filesystem'    
    allowCollectingMemory: false,              // 回收反序列化中未使用的内存;  默认 false for production, true for memoey    
    cacheDirectory: "",                        // 缓存的基目录,默认 node_modules/.cache/webpack    
    cacheLocation: "",                         // 缓存目录, 默认 path.resolve(cache.cacheDirectory, cache.name).    
    compression: "",                           // 缓存文件的压缩类型, 'gzip' | 'brotli'    
    hashAlgorithm: "",                         // 哈希生成算法, 默认 'md4'    
    idleTimeout: 60000,                        // 缓存有效时长    
    idleTimeoutAfterLargeChanges: 1000,        // 当有大的改变被检测到时,的缓存有效时长,    
    idleTimeoutForInitialStore: 5000,          //     
    maxAge: 5184000000,                        // 未使用的缓存记录的缓存时长, 默认一个月    
    memoryCacheUnaffected: true,    
    name: "",                                  // 缓存的名称,默认 `${config.name}-${config.mode}`    
    profile: false,                            // 追踪并记录缓存信息    
    store: "pack",                             // 使用一个文件缓存所有    
    version: "",                               // 缓存的版本      
    
    // only for type = 'memery'    
    cacheUnaffected: "",                       // 缓存未发生变化的模块    
    maxGenerations: 1,                         // 未使用的缓存记录的最大编译次数    //     
    managedPath: [],
}

const typicalCacheOptions = {}

module.exports = typicalCacheOptions