webpack

基本使用

1. 安装配置

2.dev-server

3.解析ES6

4.解析样式

5.解析图片

6.输出资源名称和路径修改、处理字体图标、js兼容

高级特性

7.多入口

8.css抽离和压缩

9.抽离公共代码

10.懒加载

11.处理React 和vue

构建速度优化

12.优化 babel-loader

13.IgnorePlugin

14.noParse

15.thread-loader

16.自动刷新与热更新

17.DLLPlugin

优化产出代码(重要)

18.weback 产出代码优化

面试题:

19.ES6 Module 和 CommonJS 区别?为什么只有个ES6 Module 才能让tree-shaking 生效

20.前端为何要进行打包和构建?

21. module chunk bundle的区别

22.loader 和 plugin 区别?常见的loader和plugin有哪些?

23.babel 和webpack 区别

24.webpack 如何产出一个lib 库

25.babel-polyfill 和 babel-runtime 区别

26.webpack 如何实现懒加载

27.为何 Proxy 不能被 Polyfill?

28、webpack 常见的性能优化有哪些?

 

 

 

 

一、webpack 安装和配置

  webpack 安装和基础配置

      • 首先初始化一个npm
        npm init -y

        会创建一个package.json文件

      • 然后本地安装webpack(webpack 一般不会进行全局安装,因为不同的项目对版本不同会产生错误)
      • 接着安装webpack-cli (此工具用一在命令中运行webpack)
        npm install webpack webpack-cli --save-dev

        成功后,会多了一个json文件和 node_modules文件夹。package.json中欧了devDependencies属性

 

      • 在项目根目录手动创建文件夹src 和 index.html 文件, 在src文件夹内创建 index.js 和hello-world.js 文件
        • 目录结构:

          

        • index.js
          import helloWorld from './hello-world'
          helloWorld()
        • hello-world.js

          function helloWorld(){
              console.log('hello world')
          }
          
          export default helloWorld
      • 手动在项目根目录创建一个webpack.config.js配置文件

        const path = require('path') //管理路径模块的包
        
        //CommonJS 语法
        module.exports = {
            entry:'./src/index.js', //打包入口文件
            output:{
                filename: 'main.js', //打包输出的文件名
                path: path.resolve(__dirname,'dist') //打包输出的路径
            }
        }
      • 去 package.json 文件scripts里,配置脚本"build":"webpack"

          

      • 终端输入命令: npm run build 将文件打包,打包的文件会放到 dist/main.js
      • 在index.html 中引入 dist/main.js
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width;initial-scale=1.0">
                <title></title>
            </head>
            <body>
                <script src="./dist/main.js"></script>
            </body>
        </html>

        浏览器打开,可以看到成功打印

      webpack 拆分配置和merge:

    开发环境和生产环境下的构建存在差异,为了不重复配置,需要保留通用配置。为了将这些配置合并在一起,需要通过webpack-merge 工具 

      • 先需要安装 webpack-merge 的工具
        npm install webpack-merge --save-dev  
      • 创建三个文件,分别为:webpack.common.js(公共的配置)、webpack.dev.js(开发环境配置)、webpack.prod.js(生产环境配置)
        //webpack.common.js

        const path = require('path') //管理路径模块的包 //CommonJS 语法 module.exports = { entry:'./src/index.js', //打包入口文件 output:{ filename: 'main.js', //打包输出的文件名 path: path.resolve(__dirname,'dist') //打包输出的路径 } }
        // webpack.dev.js
        const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common,{ mode: 'development' })

        webpack.prod.js

        const { merge } = require('webpack-merge')
        const common = require('./webpack.common.js')
        
        module.exports = merge(common,{
            mode:'production'
        })
      • 删除之前的webpack.config.js             

二、启动本地服务器dev-server

  • webpack 提供了一个可选的本地开发服务,打包的文件不会写入磁盘,只是暂存在内存里的,所以会比较快
  • 安装 webpack-dev-server 
    npm install webpack-dev-server --save-dev
  • 配置 package.json, 以便更方便地运行 server:
     "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config webpack.prod.js",
        "serve": "webpack serve --open --config webpack.dev.js"
      }

    执行命令 npm run serve 自动开启服务

  • webpack.dev.js 中配置devServer
    const { merge } = require('webpack-merge');
    const common = require('./webpack.common.js');
    
    module.exports = merge(common,{
        mode: 'development',
        devServer:{
            static:'./dist', //静态文件地址
            progress: true ,// 
            port: 8080,
            open:true, //自动打开浏览器
            //设置代理
            proxy:{
                '/api':{
                    target:'http:localhost:3000',
                    changeOrigin: true,
                    pathRewrite:{ //重写路径 比如'/api/aaa'重写为'http:localhost:3000/aaa'
                        '^/api':''
                    }
                }
            }
        }
       
    })
  • 将index.html 文件打包到dist文件夹中,就需要使用HtmlWebpackPlugin插件
    npm install html-webpack-plugin --save-dev

    配置webpack.common.js文件

    const path = require('path') //管理路径模块的包
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    //CommonJS 语法
    module.exports = {
        entry:'./src/index.js', //打包入口文件
        output:{
            filename: 'main.js', //打包输出的文件名
            path: path.resolve(__dirname,'dist') // 
        },
        plugins:[
            new HtmlWebpackPlugin({
                template: path.join(__dirname,'index.html'),
                filename:'index.html'
            })
        ]
    }

三、解析ES6

四、处理样式

  • 首先安装插件
    npm install css-loader style-loader less less-loader sass sass-loader --save-dev

    处理css、less、scss 文件

    const path = require('path') //管理路径模块的包
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    //CommonJS 语法
    module.exports = {
        entry:'./src/index.js', //打包入口文件
        output:{
            filename: 'main.js', //打包输出的文件名
            path: path.resolve(__dirname,'dist') ,
            clean: true
        },
        plugins:[
            new HtmlWebpackPlugin({
                template: path.join(__dirname,'index.html'),
                filename:'index.html'
            })
        ],
        module:{
            rules:[
                {
                    //用来匹配 .css 结尾的
                    test:/\.css$/i,
                    //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader)
                    use:[
                        'style-loader', //将 JS 字符串生成 style 节点,最总插入到页面中
                        'css-loader' //将 CSS 转化成 CommonJS 模块
                    ]
                },
                {
                    test:/\.less$/i,
                    //use 执行顺序是:从后往前
                    use:[
                        'style-loader',
                        'css-loader',
                        'less-loader', 
                        
                    ]
                },
                {
                    test:/\.scss$/i,
                    //use 执行顺序是:从后往前
                    use:[
                        'style-loader',
                        'css-loader',
                        'sass-loader'
                    ]
                }
            ]
        }
    }
  • 处理css3 在不同浏览器兼容性问题
    • 安装插件postcss-loader
      npm install postcss postcss-loader postcss-preset-env --save-dev
    • 在根目录新建一个postcss.config.js 文件,里面的配置如下:

      module.exports = {
        plugins: [
          [
            "postcss-preset-env",
            {
              // Options
            },
          ],
        ],
      };
    • webpack.common.js 中的配置postcss-loader

      const path = require('path') //管理路径模块的包
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      
      //CommonJS 语法
      module.exports = {
          entry:'./src/index.js', //打包入口文件
          output:{
              filename: 'main.js', //打包输出的文件名
              path: path.resolve(__dirname,'dist') ,
              clean: true
          },
          plugins:[
              new HtmlWebpackPlugin({
                  template: path.join(__dirname,'index.html'),
                  filename:'index.html'
              })
          ],
          module:{
              rules:[
                  {
                      //用来匹配 .css 结尾的
                      test:/\.css$/i,
                      //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader)
                      use:[
                          'style-loader', //将 JS 字符串生成 style 节点,最总插入到页面中
                          'css-loader', //将 CSS 转化成 CommonJS 模块
                          'postcss-loader' //css兼容处理
                      ]
                  },
                  {
                      test:/\.less$/i,
                      //use 执行顺序是:从后往前
                      use:[
                          'style-loader',
                          'css-loader',
                          'less-loader', 
                          
                      ]
                  },
                  {
                      test:/\.scss$/i,
                      //use 执行顺序是:从后往前
                      use:[
                          'style-loader',
                          'css-loader',
                          'sass-loader'
                      ]
                  }
              ]
          }
      }
    • 在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度。

      {
        // 其他省略
        "browserslist": ["last 2 version", "> 1%"]
      }  

     或者在增加一个.browserslistrc配置文件

# Browsers that we support

last 1 version
> 1%

五、解析图片

    • 从Webpack5 开始,我们可以直接使用 asset/resource 或 asset/inline 模块类型来处理图片(webpack4 需要 安装 file-loader、url-loader)
    • asset/resource 适合处理大图片,
    • asset/inline 适用于小图片或图标,返回的是 Data URL
    • 在生产环境下,我们需要考虑小图片 base64编码情况。webpack.prod.js配置如下:
      const { merge } = require('webpack-merge')
      const common = require('./webpack.common.js')
      
      module.exports = merge(common,{
          mode:'production',
          module: {
              rules:[
                  //  图片考虑 base64 编码的情况
                  //  sset/resource:适用于较大的文件,它会将文件输出到输出目录,并返回一个public URL。
                  //  asset/inline:适用于较小的文件(如Base64编码的图片),它会将文件转换为DataURLs。
                  {
                      test:/\.(png|jpe?g|gif)$/i,
                      type:'asset',
                      parser:{ 
                          dataUrlCondition:{
                              maxSize: 30 * 1024 //文件大小小于 30 KB时,使用asset/inline模式
                          }
                      },
                      generator:{
                          //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱
                          filename:'images/[name].[hash:6][ext]'
                      }
                  }
              ]
          }
          
      })

      重新打包后,我们会看到根目录下多一个images 文件夹,存放了一张图片(页面中引入了两张图),而小于30 KB 的图片生成了base64 图片,在代码中引入

    •        

          优点: 减少请求次数; 缺点:体积变得更大

六、输出资源名称和路径修改、处理字体图标、js兼容

  • 输出资源和路径修改
    • 打包后的输出资源的路径以及名称都是可以修改的
    • 只需要在webpack.prod.js中配置output,webpack.coomon.js 以及 webpack.dev.js不需要配置
      const path = require('path') //管理路径模块的包
      const { merge } = require('webpack-merge')
      const common = require('./webpack.common.js')
      
      module.exports = merge(common,{
          mode:'production',
          output:{
              filename: 'static/js/main.js', //打包输出的文件名和路径
              path: path.resolve(__dirname,'dist') ,
              clean: true //自动将上次打包目录资源清空
          },
          module: {
              rules:[
                  //  图片考虑 base64 编码的情况
                  //  sset/resource:适用于较大的文件,它会将文件输出到输出目录,并返回一个public URL。
                  //  asset/inline:适用于较小的文件(如Base64编码的图片),它会将文件转换为DataURLs。
                  {
                      test:/\.(png|jpe?g|gif)$/i,
                      type:'asset',
                      parser:{ 
                          dataUrlCondition:{
                              maxSize: 8 * 1024 //文件大小小于 8 KB时,使用asset/inline模式
                          }
                      },
                      generator:{
                          //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱
                          filename:'static/images/[name].[hash:6][ext]'
                      }
                  }
              ]
          }
          
      })

      重新打包后:dist 目录下多出一个static文件夹

  • 字体图标
    • 如果我们的页面有引入字体图标:例如: fonts/iconfont/ttf ;fonts/iconfont/woff ; fonts/iconfont/woff2
    • webpack5 会自动处理,不需要额外的插件配置
    • 如果需要指定输出的目录,只需要进行简单配置即可,修改webpack.prod.js文件
      rules:[
                 
                  {
                      //将字体图标资源输 出到 static/media 目录中
                      // 字体图标命名为: [hash:8][ext][query]
                      //[hash:8] : hash 值取8位
                      //[ext]: 使用之前的文件扩展名
                      test:'/\.(ttf| woff2?)$/i',
                      type:'asset/resource',
                      generator:{
                          filename:'static/media/[hash:8][ext]'
                      }
                  },
                  
              ]
  • js兼容

七、多入口

  现在前端大多数项目都是SPA单页面应用,所以只有一个入口。但有些项目里会产出多个页面,那么webpack 怎么打包成多个入口文件的:

    • entry 修改
      // webpack.commom.js 文件
      
      entry:{
              index:'./src/index.js', //打包入口文件
              other: './src/other.js'//打包入口文件
      }

      有几个页面(这里指的是.html文件)

    • plugins 插件配置:生成多个html 文件
      //webpack.common.js 文件
      
      plugins:[
              // 多入口 - 生成 index.html
              //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 index.js 的 JS 文件。
              new HtmlWebpackPlugin({
                  template: path.join(__dirname,'index.html'),
                  filename:'index.html',
                  //chunks 表示该页面要引入哪些chunk(即上面入口配置的名称 index 和 other)
                  chunks:['index'] //只引入 index.js 文件,默认不配置会把入口的两个文件全部引入(即index.js 和 other.js)
              }),
              // 多入口 - 生成 other.html
              //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 other.js 的 JS 文件。
              new HtmlWebpackPlugin({
                  template: path.join(__dirname,'other.html'),
                  filename:'other.html',
                  chunks:['other'] //只引入 other.js 文件
              })
          ],

 

      注意:如果chunks 不写的话,会将entry中配置的入口全部引入

                  如果配置上,就会只引入配置的chunk

    • output 输出配置:直接用[name]来代替之前的main,[name] 会根据入口entry中配置的名称自动生成

      output:{
              //输出路径: static/js
              filename: 'static/js/[name].js', //打包输出的文件名和路径
              path: path.resolve(__dirname,'dist') ,
              clean: true //自动将上次打包目录资源清空
          }

八、css抽离和压缩

   目前打包后没有css文件,css是当js文件加载时,会创建一个style 标签来添加样式。这样用户体验不好,我们应该是打包单独css文件,然后通过link标签加载性能才更好

   css抽离出独立文件的配置:

      • 先下载插件
        npm install min-css-extract-plugin --save-dev
      • 把weback.common.js中的css处理去掉,dev环境沿用common的处理,prod环境需要进一步配置

        // webpack.common.js
            
        const path = require('path') //管理路径模块的包
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        //CommonJS 语法
        module.exports = {
           //省略
            module:{
                rules:[
                    //去掉之前的样式配置
                ]
            }
        }
        
                
        //webpack.dev.js
        
        const { merge } = require('webpack-merge');
        const common = require('./webpack.common.js');
        
        module.exports = merge(common,{
            mode: 'development',
            module:{
                rules:[
                    {
                        //用来匹配 .css 结尾的
                        test:/\.css$/i,
                        //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader)
                        use:[
                            'style-loader', //将模块导出内容作为样式,并添加到DOM中(以 style 标签形式插入)
                            'css-loader', //加载css文件,并且解析import 的 css文件,返回 css代码(然后再经过style-loader 处理)
                            'postcss-loader' //css兼容处理,使用Postcss加载,并转换成css 文件(然后再经过css-loader处理)
                        ]
                    },
                    {
                        test:/\.less$/i,
                        //use 执行顺序是:从后往前
                        use:[
                            'style-loader',
                            'css-loader', 
                            'less-loader', //加载并编译less文件
                        ]
                    },
                    {
                        test:/\.scss$/i,
                        //use 执行顺序是:从后往前
                        use:[
                            'style-loader',
                            'css-loader',
                            'sass-loader' //加载并编译sass/scss文件
                        ]
                    }
                ]
            }
           
        })
      • prod环境配置

        const path = require('path') //管理路径模块的包
        const { merge } = require('webpack-merge')
        const common = require('./webpack.common.js')
        const MiniCssExtractPlugin = require('mini-css-extract-plugin')
        
        module.exports = merge(common,{
            // 省略
            module: {
                rules:[
                   // 省略
                    {
                        //用来匹配 .css 结尾的
                        test:/\.css$/i,
                        //use 执行顺序是:从后往前(即现执行css-loader,再执行style-loader)
                        use:[
                            MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件
                            'css-loader', //加载css文件,并且解析import 的 css文件,返回 css代码(然后再经过style-loader 处理)
                            'postcss-loader' //css兼容处理,使用Postcss加载,并转换成css 文件(然后再经过css-loader处理)
                        ]
                    },
                    {
                        test:/\.less$/i,
                        //use 执行顺序是:从后往前
                        use:[
                            MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件
                            'css-loader', 
                            'less-loader', //加载并编译less文件
                        ]
                    },
                    {
                        test:/\.scss$/i,
                        //use 执行顺序是:从后往前
                        use:[
                            MiniCssExtractPlugin.loader, // 注意:这里不再用 style-loader,这里需要把css单独打包成一个文件
                            'css-loader',
                            'sass-loader' //加载并编译sass/scss文件
                        ]
                    }
                    
                ]
            },
            plugins:[
                //提取 css 成单独文件
                new MiniCssExtractPlugin({
                    //文件输出目录: static/css
                    //文件名: main.[contenthash:8].css
                    //[contenthash:8] contenthash 是根据抽取到的内容来生成hash。抽取的内容不变hash不变,这样可以命中缓存
                    filename: "static/css/main.[contenthash:8].css"
                })
            ]
            
        })

        此时重新打包,会发现多一个css文件

          将上面打包出来的样式文件进一步压缩:

      • 首先安装css-minimizer-webpack-plugin:
        npm install css-minimizer-webpack-plugin --save-dev
      • 在prod环境下引入,并使用

        const path = require('path') //管理路径模块的包
        const { merge } = require('webpack-merge')
        const common = require('./webpack.common.js')
        const MiniCssExtractPlugin = require('mini-css-extract-plugin')
        const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
        
        module.exports = merge(common,{
           
            // 省略
           
            plugins:[
                //提取 css 成单独文件
                new MiniCssExtractPlugin({
                    //文件输出目录: static/css
                    //文件名: main.[contenthash:8].css
                    //[contenthash:8] contenthash 是根据抽取到的内容来生成hash。抽取的内容不变hash不变,这样可以命中缓存
                    filename: "static/css/main.[contenthash:8].css"
                }),
                //css 压缩
                new CssMinimizerWebpackPlugin()
            ]
            
        })

        重新打包后,生成的main.css被压缩了,代码变成了一行,注视也会被去掉

            

九、抽离公共代码

  公共代码指的是:在多个模块或页面中被重复使用的代码。这些代码可能包括:import 引入的 第三方库(比如:lodash)、项目中自定义的工具函数等等

       抽离的目的: 抽离后的代码单独打包,可以被浏览器缓存。提升页面的加载效率

       使用方法: optimization.splitChunks 分割代码块

      • 在prod环境下进行代码分割
        optimization:{
                //分割代码块
                splitChunks:{
                    /**
                     * chunks有三个值:
                     *      initial: 入口 chunk,只对入口中配置的生成chunk
                     *      async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件)
                     *      all: 全部(包括入口配置的trunk 和 import 导入的文件)
                     */
                    chunks: 'all',
                    cacheGroups:{
                        //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash)
                        vendor:{
                            test:/node_modules/, //匹配 node_modules 下的trunk
                            priority: 1,// 权限更高,优先抽离
                            name:'vendor', //chunk 名称
                            minSize: 0, //大小限制,达到设置的限制就单拎出来打包
                            minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包
                        },
                        //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入)
                        common:{
                            name:'common', //chunk 名称
                            priority: 0, // 优先级
                            minSize: 0, //大小限制
                            minChunks: 2 // 最少复用过几次
                        }
        
                    }
                }
            }
      • 多入口时,如果设置了chuks,就需要把分割的chunk加进去,否则只会加载设置的trunk,webpack.common.js 配置如下:

         plugins:[
                // 多入口 - 生成 index.html
                //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 index.js 的 JS 文件。
                new HtmlWebpackPlugin({
                    template: path.join(__dirname,'index.html'),
                    filename:'index.html',
                    //chunks 表示该页面要引入哪些chunk(即上面入口配置的名称 index 和 other)
                    chunks:['index','common','vendor'] //引入 index.js common.js vendor.js 文件,默认不配置会把所有的chunk 引入(入口的两个,分组splitChunks 中命中的chunk、)
                }),
                // 多入口 - 生成 other.html
                //HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 other.js 的 JS 文件。
                new HtmlWebpackPlugin({
                    template: path.join(__dirname,'other.html'),
                    filename:'other.html',
                    chunks:['other'] //只引入 other.js 文件,不设置会加载所有的chunk文件(即打包出来的js文件)
                })
            ],

  测试一下:

    • index.js 文件
      // 引入第三方模块
       import _ from 'lodash'
      //引入 math.js 公共文件
       import {sum} from './math'
    • other.js 文件

      console.log('我是 other 页面')
      // 引入 math.js import {sum} from
      './math' console.log(sum(100,200))
    • math.js 文件

      export const sum = (a,b)=>{
          return a+ b
      }

      重新打包后,会多出两个文件:common.js 和 vendor.js 。index.js 和 other.js 体积变小了

十、懒加载

    • js中我们可以使用import()函数来异步加载模块,例如:
      //引入动态数据 -- 懒加载
      setTimeout(()=>{
          //import() 函数可以异步加载模块
          // import() 返回的是promise对象
          import('./dynamic-data').then(res=>{
              console.log(res.default.message)
          })
      })
    • dynamic-data.js
      export default{
          message: 'this is a dynamic data'
      
    • 在 index.j中动态引入数据,异步加载模块dynamic-data,重新打包后,dynamic-data会被单独打包(名称被随机命名)

 

 

十一、处理React 和vue

  针对React处理:

    • 下载包
      npm install --save-dev @babel/preset-react
    • common 环境配置:

      module:{
              //对js兼容处理
              rules:[
                  {
                      test:/\.js$/,
                      use:['babel-loader'],
                      exclude: /node_modules/ //排除node_modules代码 不编译
                  }
              ]
          },

 

  vue 处理:

    • 安装 vue-loader
      npm install --save-dev vue-loader  
    • common 环境配置:
       rules:[
                  {
                      test:/\.vue$/,
                      loader:'vue-loader'
                  }
                  
              ]
          

 

十二、webpack 构建速度优化-- 优化babel-loader

  • 启用Babel的缓存机制,使第二次构建速度更快。
  • common 环境 配置:
    module:{
            //对js兼容处理
            rules:[
                {
                    test:/\.vue$/,
                    loader:'vue-loader'
                },
                {
                    test:/\.js$/,
                    use:['babel-loader?cacheDirectory'], //开启缓存
                    exclude: /node_modules/ //排除node_modules代码 不编译
                }
            ]
        },

十三、webpack 构建速度优化-- IgnorePlugin 避免引入无用模块

  • 比如 Moment.js 库,只吃很多语言 
  • import moment from 'moment' 默认会引入所有语言的JS 代码
  • 避免引入过多的无用语言,我们可以使用IgnorePlugin(webpack 内置插件),来忽略第三方包指定的目录(让这些目录不要被打包进去)
  • prod 环境配置:
    plugins:[
           
            //忽略 moment 下的 /locale 目录
            new webpack.IgnorePlugin({
                resourceRegExp: /^\.\/locale$/,
                contextRegExp: /moment$/
            })
        ],
  • index.js 文件使用:
    // 引入第三方模块
     import moment from 'moment'
     import 'moment/locale/zh-cn' //locale目录被忽略掉后,需要手动引入语言包(自己需要的),
    
    
    console.log(moment().format('ll'),'333333333') //xxxx年x月x日

十四、webpack 构建速度优化-- noParse

  • noParse避免重复打包
  • 向xxx.min.js 文件,本身就已经打包过的,不需要再处理
  • prod环境配置:
    module: {
            //忽略对 xxx.min.js 文件的解析处理,本身已经打包过了
            noParse:[/react\.min\.js/],
            rules:[
                 // 省略
             ]
    }

 IgnorePlugin 和 noParse区别:

  • IgnorePlugin 是直接不引入,代码中没有。(需要什么,手动引入)
  • noParse引入,但不打包

十五、webpack 构建速度优化-- thread-loader 多进程打包

  • webpack5 多进程打包 由 happyPack 改成 thread-loader 
  • 使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行
  • 安装
    npm install thread-loader --save-dev
  • prod环境
    rules:[ {
                    test:/\.js$/,
                    exclude: /node_modules/, //排除node_modules代码 不编译
                    use:[
                        'thread-loader',
                        'babel-loader?cacheDirectory'
                    ]
                },
    ]
  • 由于启动进程开销有原因,打包内容少的不建议使用多进程,只有项目很大,打包时间很长时,我们可以考虑开启多进程

十六、webpack 构建速度优化-- 自动刷新与热更新(只使用开发环境)

  • 自动刷新:整个网页全部刷新,速度较慢,状态会丢失
  • 热更新:网页不刷新,新代码生效,状态不丢失。
  • webpack5 中使用webpack-dev-serve 会自动开启热更新
  • 如果想开启自动刷新关闭热更新,只需把hot 设为false
    module.exports = merge(common,{
        mode: 'development',
        devServer:{
            static:'./dist', //静态文件地址
            port: 8080,
            open:true, //自动打开浏览器
            hot: false, //是否开启热更新,默认不设置为true
            //设置代理
            proxy:[
                {
                    '/api':{
                        target:'http:localhost:3000',
                        changeOrigin: true,
                        pathRewrite:{ //重写路径 比如'/api/aaa'重写为'http:localhost:3000/aaa'
                            '^/api':''
                        }
                    }
                }
            ]
            
        },
       // 省略
        })
  • 自动刷新或者热更新只能用在dev环境,不能用在生产环境 

十七、webpack 构建速度优化-- DLLPlugin 动态链接库插件(只使用开发环境)

  • 前端框架项目 如 vue、react体积较大、比较稳定、不常升级版本
  • 同一个版本只构建一次即可,不用每次都重新打包
  • 如果你的项目每次打包很慢的情况,可以采用这个进行优化(如果小的项目,可以不使用)
  • 具体的实现:
    • webpack 已经内置 DellPlugin 支持
    • 先使用DellPlugin 插件 会把vue\react 预先打包出dll文件 
    • 下次运行的时候,DellReferencePlugin 插件会直接使用上次的 dll 文件
  • 代码实现:
    • 先要在你的react 项目中单独配置一个webpack.dll.js 文件
      const path = require('path')
      const DllPlugin = require('webpack/lib/DllPlugin')
      const { srcPath, distPath } = require('./paths')
      
      module.exports = {
        mode: 'development',
        // JS 执行入口文件
        entry: {
          // 把 React 相关模块的放到一个单独的动态链接库
          react: ['react', 'react-dom']
        },
        output: {
          // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
          // 也就是 entry 中配置的 react 和 polyfill
          filename: '[name].dll.js',
          // 输出的文件都放到 dist 目录下
          path: distPath,
          // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
          // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
          library: '_dll_[name]',
        },
        plugins: [
          // 接入 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'),
          }),
        ],
      }
    • package.json 中配置

      "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "dev": "webpack serve --config build/webpack.dev.js",
          "dll": "webpack --config build/webpack.dll.js"
        }

       

    • 执行 npm run dll ,会在dist 目录下多出两个文件:react.dll.js、 react.manifest.json       

    • 在 index.html 中需要引入 react.dll.js 文件
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>Document</title>
      </head>
      <body>
          <div id="root"></div>
      
          <script src="./react.dll.js"></script>
      </body>
      </html>
    • webpack.dev.js 配置
      // 第一,引入 DllReferencePlugin
      const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
      module.exports = merge(webpackCommonConf, {
          mode: 'development',
          module: {
              rules: [
                  {
                      test: /\.js$/,
                      use: ['babel-loader'],
                      include: srcPath,
                      exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
                  },
              ]
          },
          plugins: [
              // 第三,告诉 Webpack 使用了哪些动态链接库
              new DllReferencePlugin({
                  // 描述 react 动态链接库的文件内容
                  manifest: require(path.join(distPath, 'react.manifest.json')),
              }),
          ]
      })

十八、webpack 产出代码优化

    • 小图片 base64编码
      {
                      test:/\.(png|jpe?g|gif)$/i,
                      type:'asset',
                      parser:{ 
                          dataUrlCondition:{
                              maxSize: 10 * 1024 //文件大小小于 10 KB时,使用asset/inline模式
                          }
                      },
                      generator:{
                          //输出自定文件名,可以指定存放目录,如不指定的话会打包在根目录下,看起来很混乱
                          filename:'static/images/[name].[hash:6][ext]'
                      }
                  },
    • bundle 加hash 
        • 输出的文件名加 contenthash, 会根据文件内容是否改变来生成哈希值。内容改变了hash值就变,内容不变hash不变
           output:{
                  //输出路径: static/js
                  //输出文件名:main.[contenthash:8].js
                  //contenthash: 当文件内容改变了hash值才会变,这样就不用每次打包都生成一个新的
                  filename: 'static/js/[name][contenthash:8].js', //打包输出的文件名和路径
                  path: path.resolve(__dirname,'dist') ,
                  clean: true //自动将上次打包目录资源清空
              },  
    • 懒加载

      • 异步加载,可以通过import()函数引入

    • 提取公共代码

      • 将通过 第三方模块和一些公共工具函数 单独打包

      • 采用splitChunks 分割代码块

        optimization:{
                //分割代码块
                splitChunks:{
                    /**
                     * chunks有三个值:
                     *      initial: 入口 chunk,只对入口中配置的生成chunk
                     *      async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件)
                     *      all: 全部(包括入口配置的trunk 和 import 导入的文件)
                     */
                    chunks: 'all',
                    cacheGroups:{
                        //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash)
                        vendor:{
                            test:/node_modules/, //匹配 node_modules 下的trunk
                            priority: 1,// 权限更高,优先抽离
                            name:'vendor', //chunk 名称
                            minSize: 0, //大小限制,达到设置的限制就单拎出来打包
                            minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包
                        },
                        //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入)
                        common:{
                            name:'common', //chunk 名称
                            priority: 0, // 优先级
                            minSize: 0, //大小限制
                            minChunks: 2 // 最少复用过几次
                        }
        
                    }
                }
            }
    • 使用 IngorePlugin 忽略一些无用模块,需要什么自己手动引入

      • 例如Moment 库,忽略掉多语言模块,需要什么语言自己手动引入
      • 既可以优化速度、也可以优化体积

    • 使用 production

      • 项目打包环境分成开发和生产环境
      • 生产环境 通过mode 设置
        module.exports = merge(common,{
            mode:'production',
        }
      • 在生产环境下,webpack 会自动压缩代码。不用单独配置压缩代码

      • 在生产环境下,对Vue、React等等一些框架会自动删掉调试代码(如开发环境的warning)。这也就是为什么线上的Vue会比开发环境的体积小很多
      • 在生产环境下,webpack 会开启 Tree-Shaking。即项目中没有用到的代码 会自动删掉
            • eg: 项目中math.js文件中定义两个函数
              export const sum = (a,b)=>{
                  return a+ b
              }
              export const mult = (a,b) =>{
                  return a * b
              }
              // 注意: ES6 Module 才能让 tree-shaking 生效
              // CommonJS 就不行

              在index.js 中值使用了其中一个

               import {sum} from './math'
              
              console.log(sum(10,20))

              重新打包后,会发现 mult 函数会被删掉

            • 注意:必须使用ES6 Module才能让 tree-shaking 生效,CommonJS 就不行

 十九、ES6 Module 和 CommonJS 区别?为什么只有个ES6 Module 才能让tree-shaking 生效

  • ES6 Module 是静态引入,编译时就引入
    • 必须先引入 
    • 代码例子:
      //提前引入
      import apiList from '../config/api.js'
      
      
      //错误的引入方式:
      if(isDev){
        //编译报错,只能静态引入
           import apiList from '../config/api.js'   
      }
  • Commonjs 是动态引入,执行时才引入
    • 可以通过代码动态判断何时引入
    • 代码例子:
      let api = require('../config/api.js')
      
      if(isDev){
        // 可以动态引入,执行时引入 
           api = require('../config/spi.js')
      }
  • webpack 打包过程是编译过程,还没有执行代码
  • 所以,只有ES6 Module 才能静态分析,实现Tree-Shaking 。

20、前端为何要进行打包和构建?  

  • 代码层面
    • 体积更小加载越快
      • 可以通过打包对代码进行压缩、采用Tree-Shaking等 减少体积     
    • 编辑高级语言或者语法
      • 通过构建工具可以是我们很好的使用 ES6的新特性、TS、SCSS等等  
    • 跨浏览器兼容性和错误检查
      • 通过构建工具可以自动处理浏览器兼容性问题,通过polyfill转换成旧版本浏览器可运行的代码
  • 开发的流程,开发效率
    • 统一、高效的开发环境,提高开发效率
      • 构建工具可以执行自动化任务,如:代码校验、测试运行、文档生成等。提高开发效率 
    • 统一的构建流程和产出标准代 
    • 部署和发布更加简单:
      • 打包和构建会生成部署的优化文件,使部署过程更加简单     

21、module chunk bundle的区别 

 

  • module(模块): 我们的源码src文件夹下各个文件(webpack 中一切皆是模块),即所有js文件、css文件、图片都是模块
  • chunk: 多模块合成的,主要有:entry入口可以定义chunk、异步加载import()函数引入的模块、splitChunk分割的模块
  • bundle:最总的输出文件  

 

22、loader 和 plugin 区别?常见的loader和plugin有哪些?

  区别:

    • loader 是模块转换器,
      • 默认情况,在遇到import 或者 require 加载模块时,webpack只支持js 和 json文件打包 
      • 像css、sass 、png 等这些类型的文件,就需要配置对应的loader 进行文件内容的解析
    • plugin 是扩展插件, 以扩展webpack的功能 如 HtmlWbpaclPlugin这个插件可以把js和css 文件放到html中 
    • loader 运行在打包文件之前;plugins在整个编译周期都起作用 

   常见的loader:

    • style-loader:  将css 添加到DOM的内部样式标签style 里  
    • css-loader: 允许将css文件通过 require 方式引入,并返回css代码
    • less-loader: 处理less
    • sass-loader:处理sass
    • postcss-loader: 用postcss处理css兼容写法
    • babel-loader: 用 babel来转换es6文件

    常见的plugin:

    • HtmlWebpackPlugin: 简单创建HTML文件,并把打包生成的js模块引入到该html中       

23、babel 和webpack 区别 

  • babel 是JS新语法编译工具,不关心模块化
  • webpack是 打包构建工具,主要用于模块打包和资源管理、
  • babel 和 webpack 通常一起使用
    • webpack 可以配置babel作为其中的一个加载器(babel-loader)
    • 以便在打包过程中对JS代码进行Babel 转换  

24、webpack 如何产出一个lib 库

  在输出配置加上library指定库名

output:{
    // lib 文件名
     filename:'lodash',
    // 输出 lib 到 dist 目录 
     path:path.resolve(__dirname,'dist'), 
// 库名,使用时通过lodash 访问导出内容
library:'lodash' ,
libraryTarget:'umd' // 支持模块系统(CommonJS,Window)
}

 

25、babel-polyfill 和 babel-runtime 区别

  • babel-polyfill 
    • 主要用于添加确实的标准库特性
    • 它会修改全局变量,会污染全局  
  • babel-runtime
    • 用于避免全局变量,只注入辅助函数
    • 不会污染全局  
    • 产出第三方lib时,一定要用babel-runtime

26、webpack 如何实现懒加载

  webpack中实现懒加载(代码分割),主要是通过动态导入的方式实现的,将应用分割成较小的代码块,只在需要的时候才引入这些代码。提高了加载速度和性能

   具体的实现方式有哪些:

    • 1. 使用 import() 语法来实现动态导入从而创建懒加载模块(默认情况,webpack 会自动分割 调用import() 的代码)
          import('./dynamic-data').then(res=>{
              console.log(res.default.message)
          })
          import('./dynamic-data2').then(res=>{
              console.log(res.default.message)
          })

      打包后会自动生成两个文件

    • 2. 可以使用魔法注释进行更细的代码分割,指定名称和指定时候合并一个文件
      import(/*webpackChunkName:'dynamic-data'*/'./dynamic-data').then(res=>{
              console.log(res.default.message)
          })
          import(/*webpackChunkName:'dynamic-data'*/'./dynamic-data2').then(res=>{
              console.log(res.default.message)
          })

      此时打包会生成一个名称为:dynamic-data.js文件。可以指定哪些文件放在一个模块里进行打包

    • 3. 可以通过 optimization.splitChunks 配置,进一步代码分割
       optimization:{
              //分割代码块
              splitChunks:{
                  /**
                   * chunks有三个值:
                   *      initial: 入口 chunk,只对入口中配置的生成chunk
                   *      async: 异步chunk, 只对异步导入的文件进行抽离(import 导入的文件)
                   *      all: 全部(包括入口配置的trunk 和 import 导入的文件)
                   */
                  chunks: 'all',
                  cacheGroups:{
                      //对于import 引入的 第三方模块单独打包成一个文件(eg: import ~ from lodash)
                      vendor:{
                          test:/node_modules/, //匹配 node_modules 下的trunk
                          priority: 1,// 权限更高,优先抽离
                          name:'vendor', //chunk 名称
                          minSize: 0, //大小限制,达到设置的限制就单拎出来打包
                          minChunks: 1, // 最少复用过几次,达到设置的次数就单拎出来打包
                      },
                      //对于import 引入的 公共的模块(eg:工具函数,多个页面都需要引入)
                      common:{
                          name:'common', //chunk 名称
                          priority: 0, // 优先级
                          minSize: 0, //大小限制
                          minChunks: 2 // 最少复用过几次
                      }
      
                  }
              }
          }

      可以把第三方的、公共的函数 也进行分割单独打包。

    • 4.像 vue 中 异步组件、异步加载路由都是通过import() 方式来实现的

27、为何 Proxy 不能被polyfill 

   没有任何方法来替代Proxy,所以Proxy 不能polyfill 。其他的API都可以用其他功能来模拟

    • 如 Class 可以用 function 模拟
    • 如 Promise 可以用 callback 来模拟
    • 但 Proxy 的功能用Object.defineProperty 无法模拟。  

28、webpack 常见的性能优化有哪些?

  构建速度的优化

      • 优化babel-loader,加缓存
      • IgnorePlugin 忽略无用模块
      • noParse 避免重复打包
      • thread-loader 
      • 自动刷新和热更新 (只能用于开发环境)
      • DllPlugin (只能用于开发环境)     

      产出代码优化                              

posted @ 2025-02-15 14:58  yangkangkang  阅读(27)  评论(0)    收藏  举报