Webpack-进阶知识

2020/7/18 Webpack
// ES6 Module和CommonJs的区别
1. ES6 Module是静态引入,编译时引入
2. CommonJs是动态引入,执行时引入
3. 只有ES6 Module才能做静态分析即打包,实现Tree-Shaking
4. 示例:
    // CommonJs
    let apiList = require('../config/api.js')
    if (isDev) {
      // 可以动态引入,执行时引入
      apiList = require('../config/api_dev.js')
    }
    // ES6 Module
    import apiList from '../config/api.js'
    if (isDev) {
      // 编译时报错,只能静态引入
      // esm 不能放在判断条件里去引入
      import apiList from '../config/api_dev.js'
    }
----------------------------------------------------------------------------------------------
// resolve 配置
1. 如果使用 ES Module 方式引入文件,Webpack 默认会查找以 js、json 结尾的文件
   在 ts 中要求引入文件时不加后缀,这样就会和 Webpack 的默认行为冲突导致到不到文件
   解决方法是在 resolve 配置项中配置 extensions 扩展 ts 结尾的文件
2. 示例
  module.export = {
      resolve: {
        extensions: ['.ts', '.js', '.jsx'], // 优先查找以 ts、js、jsx 结尾文件
        mainFiles: ['index', 'child'], // 以index、child开头的文件,引入文件时默认是index文件
        alias: {
          child: path.resolve(__dirname, '../src/a/b') // 重定向,别名
        }
      }
    }
----------------------------------------------------------------------------------------------
// webpack性能优化1
1. 打包工具(node、npm、yarn)版本尽量最新
2. 在尽可能少的模块上应用loader,loader范围小一点
3. plugin尽可能精简并准确可靠
4. resolve参数合理配置(用于引入文件时不用加后缀等)
    module.export = {
      resolve: {
        extensions: ['.js', '.jsx'], // 以js、jsx结尾的文件
        mainFiles: ['index', 'child'], // 以index、child开头的文件,引入文件时默认是index文件
        alias: {
          child: path.resolve(__dirname, '../src/a/b') // 重定向,别名
        }
      }
    }
5. 使用DllPlugin提高打包速度,针对于不变的模块单独打包到另一个文件夹中,下次打包时不用重新打包,而是直接拿来用
    新建webpack.dll.js文件
    module.export = {
      plugins: [
        new webpack.DllPlugin({
          name: '[name]'
        })
      ],
      path: path.resolve(__dirname, '../dll/[name].manifest.json')
    }
    在webpack.js中配置使用webpacck.DllReferencePlugin把映射文件引入
    在webpack.js中使用AddAssetHtmlWebpackPlugin把在dll文件夹下的js挂载到html上
6. 控制包文件的大小
7. thread-loader、parallel-webpack、happypack多进程打包
8. 合理使用sourceMap
9. 结合stats分析打包结果
10. 开发环境内存编译
11. 开发环境无用插件剔除
----------------------------------------------------------------------------------------------
// webpack性能优化2
1. 优化打包构建速度 // 开发体验和效率
    优化babel-loader // 开发和生产环境都可以
    IgnorePlugin // 避免引入无用模块,生产环境
    noPass // 配置,不去打包哪些,生产环境
    happyPackPlugin // 多进程打包工具,生产环境,不维护了,建议使用 thread-loader
    ParallelUglifyPlugin // 多进程压缩工具,生产环境
    自动刷新 // 开发环境
    热更新 // 开发环境
    DllPlugin // 开发环境,针对于不变的模块单独打包到另一个文件夹中,下次打包时不用重新打包,而是直接拿来用
2. 优化产出代码 // 产品性能,体积更小,合理分包,不重复加载,速度更快,内存使用更少
    小图片base64编码
    bundle加hash
    懒加载
    提取公共代码 // 代码分割
    IgnorePlugin
    使用CDN加速
    使用production
    Scope Hosting
----------------------------------------------------------------------------------------------
// 通过 webpack-bundle-analyzer 插件可以分析当前打包的情况
1. 看看有没有什么重复的模块,或者没有用的模块被打包到了最终的代码中
2. 看看 package.json,对比一下是否有应该在 devEeps 的模块,被错误的放置到了 deps 当中
3. 检查是否有重复加载的模块,或者是功能大体相同的模块
4. 使用 es 版本的第三方库,享受 tree-shaking 的红利
5. 使用 webpack ignore plugin 可以忽略模块的引入
----------------------------------------------------------------------------------------------
// webpack性能优化2之优化打包构建速度
1. 优化babel-loader
    module.export = {
     moudle: {
       rules: [
         {
           test: /\.js$/,
           exclude: /node_modules/, // 明确范围include,排除范围exclude,二选一即可
           use: ['babel-loader?cacheDirectoty'] // ?cacheDirectoty开启缓存
         }
       ]
     }
   }
2. IgnorePlugin // 避免引入无用模块
    module.export = {
    plugins: [
      // moment是npm安装的一个日期库,支持多个国家语言
      // 忽略 moment 下的 /locale目录
      new webpack.IgnorePlugin(/\.\/locale/, /moment/)
    ]
   }
   这样配置会导致moment不会引入,即使你写了import
   import moment from 'moment' // 初始引入,会把全部支持的国家语言都引入
   import 'moment/locale/zh-cn' // 在上面通过ignoreplugin忽略后,手动引入需要的使用的语言包
3. noPass // 避免重复打包
    module.export = {
      module: {
        // 遇到vue.min.js文件就不需要进行打包,一般min文件都是处理过的
        noPass: [/vue\.min\.js$/]
      }
   }
4. IgnorePlugin和noPass的区别
    IgnorePlugin: 直接不引入,代码中没有
    noPass: 引入模块,但不进行打包
5. happyPack // 多进程打包工具
    const HappyPack = require('happyPack') // npm
    module.export = {
      module: {
        rules: [{
          test: /\.js$/,
          // 把对js文件的处理转交给id为babel的HappyPack实例
          use: ['happypack/loader?id=babel'],
          include: srcPath
        }]
      },
      plugins: [
        new HappyPack({
          // 用唯一标识符id来代表当前的HappyPack是用来处理一类特定的文件
          id: 'babel',
          // 如何处理js文件,用法和loader配置中一样
          loaders: ['babel-loader?cacheDirectory']
        })
      ]
   }
6. ParallelUglifyPlugin // 多进程压缩工具,生产环境
    webpack内置Uglify工具压缩js
    js单线程,开启多进程压缩更快
    和happypack同理
    const HappyPack = require('webpack-parallel-uglify-plugin') // npm
    module.export = {
      plugins: [
        // 使用ParallelUglifyPlugin并行压缩输出js代码
        new ParallelUglifyPlugin({
          // 传递给UglifyJs的参数
          // 还是使用了UglifyJs压缩,只不过帮助开启了多进程
          uglifyJS: {
            output: {
              beautify: false, // 最紧凑的输出
              comments: false // 删除所有的注释
            },
            compress: {
              // 删除所有的console语句,可以兼容IE浏览器
              drop_console: true,
              // 内嵌定义了但是只用到一次的变量
              collapse_vars: true,
              // 提取出出现多次但是没有定义成变量去引用的静态值
              reduce_vars: true
            }
          }
        })
      ]
   }
7. 关于开启多进程
    项目较大,打包较慢,开启多进程能提高速度
    项目较小,打包很快,开启多进程会降低速度 // 进程开销
8. 自动刷新, 开发环境使用
    整个网页全部刷新,速度较慢
    整个网页全部刷新,状态会丢失
    module.export = {
      watch: true, // 开启监听,默认为false
      watchOptions: {
        ignored: /node_modules/, // 忽略哪些
        // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
        aggregateTimeout: 300, // 默认为300
        // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
        poll: 1000 // 默认每隔1000ms询问一次
      }
    }
9. 热更新, 开发环境使用
    新代码生成,网页不刷新,状态不丢失
    css做以下配置即可生效,js需要在js文件中手动添加代码实现:
      if(module.hot) {
        module.hot.accept(['./math'], () => {
          console.log('hot')
        })
      }
    配置文件:
    const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin')
    module.export = {
     entry: {
       index: [
         'webpack-dev-server/client?http://localhost:8080/',
         'webpack/hot/dev-server',
         path.join(srcPath, 'index.js')
       ]
     },
     devServer: {
       hot: true
     },
     plugins: [
       new HotModuleReplacementPlugin()
     ]
    }
10. DllPlugin // 动态链接库插件,开发环境使用,预打包
      前端框架,体积大,构建慢,较稳定,不常升级版本
      同一版本只构建一次即可,不用每次都重新构建
      webpack已内置DllPlugin支持
      DllPlugin // 打包出dll文件和manifest.json索引文件
      DllReferencePlugin // 使用dll文件
      先使用webpack.dll.js这个配置文件进行打包
      const DllPlugin = require('webpack/lib/DllPlugin')
      module.export = {
        entry: {
          ['react', 'react-dom'] // 把react相关模块放到一个单独的动态链接库
        },
        output: {
          // 输出的动态链接库的文件名称, [name]代表当前动态链接库的名称
          // 也就是entry中配置的react和polyfill
          filename: '[name].dll.js',
          // 输出文件都放到dist目录下
          path: distPath,
          // 存放动态链接库的全局变量名称,例如对应react来说就是_dll_react
          // 之所以在前面加上_dll_是为了防止全局变量冲突
          library: '_dll_[name]' // 输出一个第三方的库
        },
        plugin: [
          // 接入DllPlugin
          new DllPlugin({
            // 动态链接库的全局变量名称,需要和output.library保持一致
            // 该字段的值也就是输出的manifest.json文件中name字段的值
            // 例如 react.manifest.json 中就有 "name": "_dll_react"
            name: '_dll_[name]',
            // 描述动态链接库的manifest.json文件输出时的文件名称
            path: path.join(distPath, '[name].manifest.json')
          })
        ]
      }
      在html页面中通过script标签引入打包出来的dll文件
      在dev配置中使用:
      const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
      module.export = {
        module: {
          rules: [
            {
              test: /\.js$/,
              loader: ['babel-loader'],
              include: srcPath,
              exclude: /node_modules/ // 不要再转换node_modules
            }
          ]
        },
        plugin: [
          new DllReferencePlugin({
            manifest: require(path.join(distPath, 'react.manifest.json'))
          })
        ]
      }
----------------------------------------------------------------------------------------------
// webpack性能优化2之产品性能
1. Scope Hosting // 打包后一个文件对应一个函数,配置Scope Hosting可以让代码体积更小,创建函数作用域更少,代码可读性更好
    const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
    module.export = {
      resolve: {
        // 针对npm中的第三方模块优先采用jsnext:main中指向ES6模块化语法的文件
        mainFields: ['jsnext:main', 'browser', 'main'],
        plugin: [
          // 开启Scope Hosting
          new ModuleConcatenationPlugin()
        ]
      }
    }
2. Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,
   但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。
3. 代码体积更小,因为函数申明语句会产生大量代码,代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。
----------------------------------------------------------------------------------------------
// Tree Shaking
1. 当引入模块时,不引入模块所有代码,可以通过Tree Shaking按需引入,默认是全部引入,只支持es module模式,commonJs不生效
2. 在生产环境中: 可不用配置默认启用
    mode: 'production'package.json中配置:
    "sideEffects": false, // 对所有文件开启
    "sideEffects": ["*.css"] // 不对css文件开启
3. 在开发环境中:
    module.export = {
      mode: 'development',
      optimization: {
        usedExports: true
      }
    }
4. 通过结构的方式,可以触发 treeshaking,调用的包必须使用 ESM
5. 同一个文件的 treeshaking,打包 mode 必须是 production 才会生效,不同文件,只要满足第四点就可以
6. 注意:使用 export default 整体导出,里面的内容不会进行 treeshaking,因为不能解构
----------------------------------------------------------------------------------------------
// code splitting代码分割, 一般用于prod, 抽离公共代码
1. 浏览器加载两个1mb文件比加载一个2mb文件快
    1. http1.1 可能有并发请求的瓶颈,在 http2.0 中就没有这个限制
2. 所以有必要把逻辑代码和工具库代码进行拆分 // 多个文件引入同一个库只会加载一次
3. 同步引入代码要做配置
4. 异步代码(import)不用配置
5. module.export = {
    optimization: {
      // 分割代码块
      splitChunks: {
        chunks: 'all', // initial同步引入的、async异步引入的、all全部不管同步异步
        minsize: 30000, // 小于30kb不分隔
        michunks: 2, // 最少使用2次(频率)才会分隔
        maxAsyncRequests: 5, // 最多分割5个
        maxInitialRequests: 3, // 首页最多分割3个
        autoMaticNameDelimite: '~', // 连接符
        name: true,
        // 缓存分组
        cacheGroups: { // chunks: 'all'会走到组里面来
        // 第三方模块
          antdVendors: {
            name: 'ant-design-vue', // chunk名称
            test: /[\\/]node_modules[\\/]/, // 只有在node_modules里面的内容才走这个配置
            priority: -10, // 优先级,哪个大就先打包哪个,如现在是-10而common是1则优先抽离common
            filename: 'rendors.js',
            minSize: 0, // 大小限制
            michunks: 2, // 最少使用2次(频率)才会分隔
            default: { // 如果没有在node_modules里面的内容就会走default的配置
              priority: -20, // 优先级,哪个大就先打包哪个
              reuseExistingChunk: true, // 如果之前有缓存就使用之前的打包缓存
              filename: 'common.js'
            }
          },
            //   超过 minsize 大小会自动分包
          autoVendors: {
            test: /[\\/]node_modules[\\/]/, // 只有在node_modules里面的内容才走这个配置
            name(module) {
                // 因为这边的名称有可能获取到两种格式,所以需要正则来获取到名称
                // pag name = node_modules/pkgName/sub/path || node_modules/pkgName
                const reg = /[\\/]node_modules[\\/](.*?)([\\/]|$)/
                const pkgName = module.context.match(reg)[1]
                // 以 @ 开头的名称做 url 可能有问题,需要过滤掉
                return `npm/${pkgName.replace('@', '')}`
            }
          }
          // 公共的模块
          common: {
            name: 'common', // chunk名称
            priority: 1 // 优先级,哪个大就先打包哪个,如现在是1而vendrs是-10则优先抽离common
          }
        }
      }
    }
   }
----------------------------------------------------------------------------------------------
// css文件代码分割, 抽离css文件
1. 在output下的filename只有入口文件会走这个配置
2. 在output下的chunkFileName是入口文件中的代码去引入别个文件时会走这个配置
3. 通过MiniCssExtractPlugin把css文件进行分割,只支持生产环境,因为不支持热模块替换(HMR)
4. 通过optimizeCssAssetsPlugin把分割的css代码压缩合并
5. 在dev环境中使用:
    module.export = {
      mode: 'development',
      module: {
        rules: [{
          test: /\.css$/,
          use: {
            loader: ['style-loader', 'css-loader', 'postcss-loader'] // 开发环境下会把css直接挂载到style下面
          }
        }]
      }
    }
6. 在pro环境中使用:
    const MiniCssExtractPlugin = require('mini-css-extract-plugin') // npm
    const TerserJSPlugin = require('terser-webpack-lugin') // npm
    const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') // npm
    module.export = {
      mode: 'production',
      module: {
        rules: [{
          test: /\.css$/,
          use: {
            loader: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] // 这里不用style-loader,会通过Link标签引入
          }
        }]
      },
      plugins: [
        new MiniCssExtractPlugin({
          filename: 'css/main.[contentHash:8].css' // 抽离css文件
        })
      ],
      optimization: {
        minimizer: [new TerserJSPlugin({}), new OptimizeCssAssetsPlugin({})] // 压缩css
      }
    }
----------------------------------------------------------------------------------------------
// 热模块替换(HMR)
1. 当css或js模块发生变化时,不会影响到页面,会自己更新 // css的热更新webpak已集成,js需要自己写module.hot
2. const webpack = require('webpack')
    module.export = {
      devServer: {
        hot: true, // 开启热模块替换功能
        hotOnly: true // 当hot不生效时,页面也不会刷新
      }
      plugins: [
        new webpack.HotModuleReplacementPlugin()
      ]
    }
3. 配置了2的参数只是开启了css的热模块替换功能
4. 如果要让js模块也开启热模块替换功能需要:
    if(module.hot) {
      module.hot.accept('模块', () => { code })
    }
----------------------------------------------------------------------------------------------
// 懒加载
1. es6的import请求
2. 提高利用率,交互写在异步组件中,当主要逻辑加载完,在空闲时加载异步组件:
    document.addEventListener('click', () => {
      import(/* webpackPrefetch: true */ './click.js').then(() => {
        func()
      })
    })
3. 引入动态数据,懒加载
    setTimeout(() => {
      import('./dynamic-data.js').then(res => {
        console.log(res)
      })
    }, 1500)
----------------------------------------------------------------------------------------------
// Babel
1. Babel 用来处理新语法和新 API,webpack 处理模块化
  1. preset-env 是用来处理 es6 的新语法,如 constlet2. polyfill 是用来处理新 API,如 Promise、map 等
  3. babel 默认只处理新语法,而新 API 的兼容是由 polyfill 来做的,现在推荐直接使用 core-js 和 regenerator
2. npm install babel-loader -D // 打通 webpack 和 babel
   npm install @babel/code -D // babel 核心,用来识别 js 生成抽象语法树
   npm install @babel/preset-env -D // 常用的语法集合
   npm install @babel/polyfill -D // 新方法的补丁集合
    module.export = {
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_module/, // 这里面的文件不用转换
            loader: 'babel-loader'options: { // 或新建.babelrc文件在里面配置
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage' // 有用的方法才配置,默认全部
                  }
                ]
              ]
            }
          }
        ]
      }
    }
3. .babelrc配置
    npm install @babel/code -D
    npm install @babel/preset-env -D
    npm install @babel/polyfill -S
    npm install @babel/plugin-transform-runtime -D
    npm install @babel/runtime -S
    {
      "presets": [
        [
          "@babel/preset-env",
          {
            // 如果不配置需要在文件中引入babel-polyfill,配置了则不需要引入
            // 实现按需引入,且不需要安装babel-polyfill依赖,直接安装corejs即可
            // 因为本身babel-polyfill是个集合,所以需要按需引入
            "useBuiltIns": 'usage', // 有用的方法才配置,默认全部
            "corejs": 3 // 使用最新版本3
          }
        ]
      ],
      "plugins": [
        [
          // babel-runtime
          "@babel/plugin-transform-runtime",
          {
            "absoluteRuntime": false,
            "corejs": 3,
            "helpers": true,
            "regenerator": true,
            "useESModules": false
          }
        ]
      ]
    }
    es6的语法很多,如箭头函数等,而每种需要转换的语法都要在plugins中配置
    而presets即预设就是我们常用语法的一个集合,只要配置了这个就无需一个一个配置plugins
    有些不在presets中的语法就需要自己在plugins中配置
4. babel-polyfill
    polyfill就是补丁或者模拟实现,会根据浏览器是否支持新语法进行补丁 // 单个的补丁
    core-js就是所有新语法的集合的polyfill
    es6的generator在core-js中不支持,所以有一个regenerator库
    babel-polyfill就是core-js和regenerator的集合
    但是在babel7.4之后弃用了babel-polyfill
    推荐直接使用core-js和regenerator
    在使用babel-polyfill时会污染全局环境 // 会把方法挂载到window上面
    如果别人再通过window.一样的方法名定义就会出错,所以需要babel-runtime
5. babel-runtime
    会把方法名做转换再挂载到window上面如
    window.promise会变成window._promise等
    在做第三方库时必须使用
----------------------------------------------------------------------------------------------
// webpack5 及周边插件升级配置
1. package.json 中的 script 脚本
  1. 原本 "dev": "webpack-dev-server --config build/webpack.dev.js"
  2. 改成 "dev": "webpack server --config build/webpack.dev.js"
2. webpack-merge
  1. 原本 const { smart } = require('webpack-merge')
  2. 改成 const { merge } = require('webpack-merge')
3. clean-webpack-plugin
  1. 原本 const CleanWebpackPlugin = require('clean-webpack-plugin')
  2. 改成 const { CleanWebpackPlugin } = require('clean-webpack-plugin')
4. loader 配置
  1. 原本 loader: ['xxx-loader']
  2. 改成 use: ['xxx-loader']
5. 哈希后缀
  1. 原本 filename: 'bundle.[contentHash:8].js'
  2. 改成 filename: 'bundle.[contenthash:8].js'
----------------------------------------------------------------------------------------------
// 面试真题
1. 前端为何要进行打包和构建?
    代码相关:
      体积更小(Tree-Shaking、压缩、合并),加载更快
      编译高级语言和语法(TSES6+、模块化、scss)
      兼容性和错误检查(polyfill、postcss、eslint)
    流程相关:
      统一、高效的开发环境
      统一的构建流程和产出标准
      集成公司构建规范(提测、上线等)
2. babel和webpack的区别?
    babel是js新语法编译工具,不关心模块化
    webpack是打包构建工具,是多个loader、plugin的集合
3. babel-polyfill和babel-runtime的区别?
    babel-polyfill会污染全局
    babel-runtime不会污染全局
    产出第三方lib要用babel-runtime
4. webpack如何实现懒加载?
    import()
    结合vue、react异步组件
    结合vue-router、react-router异步加载路由
6. 为何proxy不能被polyfill?class可以用function模拟
    如promise可以用callback模拟
    但proxy的功能用Object.defineProperty无法模拟
7. loader 本质是一个函数,接收源代码,返回处理过的内容
   对于需要操作源代码的话可以考虑使用 loader
  const loaderUtils = require('loader-utils') // npm
  module.export = function(source) {
    // use: [{ loader: loaderName, options: { name: 'chnejie' }}]
    const options = loaderUtils.getOptions(this) // 也可以使用 this.options 获取,不过需要保证传递的是对象
    const callback = this.async()
    settimeout(() => {
      const result = source.replace('chenj', options.name)
      callback(result) // 异步时使用 async,同步直接 return
    }, 1000)
  }
8. plugin 的机制是事件驱动或者说是发布订阅模式(是一个 class)
  module.export = class TestWebpackPlugin {
    constructor(options) {
      console.log('使用时传递的参数 new TestWebpackPlugin({ name: 'chenj' })', options)
    }
    apply(compiler) {
      console.log('webpack 实例(配置文件、打包过程等信息)', compiler)
      // compiler.hooks 指的是 webpack 打包时在某一时刻会自动执行的钩子
      // compiler.hooks.emit 指的就是将打包好的文件在进 dist 目录前的时刻
      // 同步的时刻和异步的时刻写法不一样
      compiler.hooks.compile.tap('TestWebpackPlugin', (compilation) => {
        console.log('本次打包的信息', compilation)
        // 同步时刻不需要 callback
      })
      // 异步时刻
      compiler.hooks.emit.tapAsync('TestWebpackPlugin', (compilation, callback) => {
        console.log('本次打包的信息', compilation)
        // 往 dist 目录增加一个 test.txt 文件,内容是 this is test,大小是 12 个字符
        compilation.assets['test.txt'] = {
          source: function() {
            return 'this is test'
          },
          size: function() {
            return 12
          }
        }
        callback()
      })
    }
  }
9. loader 和 plugin 的区别
  1. 功能:loader 做的事情,plugin 也可以做
  2. 执行顺序:
    1. plugin 可以在 webpack 编译的整个过程执行,类似生命周期
    2. loader 只能在固定的阶段执行
        1. 多个 loader 会从后往前执行
        2. 最后调用的 loader 需要输出 js 格式的代码
        3. 中间执行的 loader 接收到的参数是前一个 loader 传出的结果
  3. 本质上的区别
    1. loader 本质上是一个翻译官
    2. plugin 执行的是一些副操作
10. webpack 构建流程
  1. 合并初始参数:配置文件 + shell 语句
  2. 初始化 compiler 对象(负责文件的监听和启动编译,包含了完整的 webpack 配置)
  3. 加载所有插件,依次调用插件的 apply 方法,并传入 compiler 对象
  4. 找到入口文件 entry,建立文件依赖树
  5. 调用所有 loader 对象源文件进行翻译
  6. 输出生产包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
Last Updated: 2024/7/31 12:57:25
    飘向北方
    那吾克热-NW,尤长靖