webpack5学习笔记

484 阅读11分钟

一、webpack 五个核心概念

  • Entry

    入口指示webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图

  • Output

    输出指示 webpack 打包后的资源 bundles 输出到哪里,以及如何命名

  • Loader

    Loader 让 webpack 能够去处理那些非Javascript 文件

  • Plugins

    插件可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境的变量等

  • Mode

    模式 指示 webpack 使用相应模式的配置

    选项描述特点
    development会将 process.env.NODE_ENV 的值设置为 development 启用NamedChunkPlugin 和 namedModulesPlugin能让代码本地调试运行环境
    production会将 process.env.NODE_ENV 的值设置为 production 启用FlagDependencyUsagePlugin 、 FlagIncludedChunksPlugin、 ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、 OccuerrenceOrderPlugin、SideEffectsFlagPlugin 和 UglifyJsPlugin能让代码优化上线运行的环境

二、webpack5

  1. 安装 webpack

    npm i webpack webpack-cli -D

  2. 使用 npx webpack 打包文件

    使用命令 修改 webpack 的入口和出口路径并进行打包

    npx webpack --entry ./src/main.js --output-path ./build

  3. 或者 将命令配置到 package.json

{
   "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "build": "npx webpack --entry ./src/main.js --output-path ./build"
     },
}

然后 使用 命令 npm run build 进行打包

  1. 或者 在根目录下创建 webpack.config.js 文件,进行更多配置

    const path  = require("path");
    ​
    module.exports = {
        entry: './src/index.js',  // 入口文件可以为相对路径
        output: {
            filename: 'build.js',
            path: path.resolve(__dirname, 'dist')  // 绝对路径
        }
    }
    

    然后,在package.json 中配置

    ​
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "build": "webpack"
      },
    

    可以自定义 webpack.config.js 的文件名称,比如修改为 lg.webpack.js ,需要在 package.json 中进行配置:

      "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "build": "webpack --config lg.webpack.js"
      },
    

loader

css 打包

  1. 安装 css-loader style-loader

    npm i css-loader style-loader -D

  2. 使用loader

    1. webpack5中使用行内loader
    import 'css-loader!../css/login.css'
    import 'style-loader!css-loader!../css/login.css'
    
    1. 使用配置文件配置css

      正常导入 css 文件

      import '../css/login.css'
      

      在 webpack 中配置 loader

      module: {
              rules: [
                  {
                      test: /.css$/, // 一般是一个正则表达式,用来匹配我们需要处理的文件类型
                      use: [
                          {
                              loader: "style-loader",
                              options:{}
                          },
                          {
                              loader: "css-loader",
                              options:{}
                          }
                      ]
                  },
                  // 如果不需要loader配置项,可简写为
                  {
                      test: /.css$/, // 一般是一个正则表达式,用来匹配我们需要处理的文件类型
                      use: [ "style-loader","css-loader" ]
                  }
              ]
          }
      

      loader 的执行顺序为 从下往上 或 从右往左 执行

less文件打包

  1. 安装

    npm i less less-loader -D

  2. 使用 less-loader

    {
        test: /.less$/, // 一般是一个正则表达式,用来匹配我们需要处理的文件类型
        use: ["style-loader", "css-loader", "less-loader" ]
    }
    

css js兼容 browserslist

css js 兼容不同的浏览器平台

  1. 到底需要兼容哪些浏览器平台?

    可以通过 https://caniuse.com/usage-table 网站查看不同平台的占有率。

  2. 查看默认兼容版本

    如果 在node_modules 文件夹中默认 安装了 browserslist,

    我们可以直接运行 npx browserslist 查看默认兼容的浏览器版本。

  3. 如何配置

    • 第一种:在 package.json 中配置,可以通过npx browserslist 查看配置结果

      {
           "browserslist": [
              ">1%",
              "last 2 version",
              "not dead"
            ]
      }
      
    • 第二种:

      • 在根目录下新建配置文件 .browserslistrc

        >1%
        last 2 version
        not dead
        
      • 通过npx browserslist 查看配置结果

postcss 兼容 css

Postcss 是一个使用js插件来转换样式的工具,Postcss 的插件会检查你的css。

PostCSS是一款使用插件去转换CSS的工具,有许多非常好用的插件,例如autoprefixer,cssnext以及CSS Modules。而上面列举出的这些特性,都是由对应的postcss插件去实现的。而使用PostCSS则需要与webpack或者parcel结合起来。

  1. 安装

    npm i postcss postcss-cli autoprefixer postcss-loader -D

  2. autoprefixer 插件的使用

    autoprefixer

    这个插件会根据 browserslistrc 中配置的浏览器兼容条件,通过js的方式为css添加浏览器的前缀,让我们不需要为了兼容而不断的写-webkit-这样无聊的代码。

  3. postcss-preset-env 插件集合的使用

    如:color:#12345678; 对于 color 的值的写法有些浏览器不能兼容,而又不能通过 autoprefixer 添加前缀的方式解决,所以需要借助其他的插件来进行处理。

    安装 npm i postcss-preset-env postcss-loader -D

  4. 配置 postcss

    • 方法一:配置 postcss
    {
        test: /.css$/, // 一般是一个正则表达式,用来匹配我们需要处理的文件类型
            use: [
                "style-loader",
                "css-loader",
                {
                    loader: "postcss-loader",
                    options: {
                        postcssOptions:{
                            plugins: [
                                //require("autoprefixer"),
                                //require('postcss-preset-env')
                                // postcss-preset-env 是一个插件的集合所以可直接简写为
                                'postcss-preset-env'
                            ]
                        }
                    }
                }
            ]
    }
    

    autoprefixer结果:

    .title{
        color: rgba(18,52,86,0.47059);
        transition: all .5s;
        -webkit-user-select: none;
           -moz-user-select: none;
            -ms-user-select: none;
                user-select: none;
        background: linear-gradient(to bottom, white, black);
    }
    
    • 方法二:postcss 的使用可以直接在 webpack 的配置文件中直接配置
    {
        test: /.less$/, // 一般是一个正则表达式,用来匹配我们需要处理的文件类型
            use: [
                "style-loader",
                "css-loader",
                {
                    loader: "postcss-loader",
                    options: {
                        postcssOptions:{
                            plugins: [ 'postcss-preset-env' ]
                        }
                    }
                },
                "less-loader"
            ]
    }
    

    如果 css 文件 和 less 文件都需要用到 postcss 那就需要重复配置两次,会出现代码冗余。

    所以也可以通过以下配置:

    • 方法三:postcss 配置文件

      • 在根目录下 创建 文件 postcss.config.js ,文件名不可修改
      module.exports={
          plugins: [
              require('postcss-preset-env')
             // require('autoprefixer') (postcss-preset-env 中包含该插件)
          ]
      }
      
      • webpack 配置文件
        {
            test: /.css$/,
                use: ["style-loader","css-loader", "postcss-loader"]
        },
        {
            test: /.less$/,
                use: ["style-loader","css-loader","postcss-loader","less-loader" ]
        }
      
  5. 场景

    • 在 postcssTest.css 文件中的代码想要被 postcss-loader 进行处理,但是在 login.css 中通过 @import 引用 postcssTest.css之后,发现页面中 postcssTest.css 并没有被做兼容性处理。

      页面login <---import--- login.css <---import--- postcssTest.css

      /*login.css*/
      @import './postcssTest.css';
      .title{
          color: #12345678;
      }
      /*postcssTest.css*/
      .title {
          transition: all .5s;
          user-select: none;
          background: linear-gradient(to bottom, white, black);
      }
      ​
      /*
      1、拿到login.css代码之后,postcss-loader 进行处理,发现login.css不需要被处理
      2、login.css 代码交给 css-loader 进行处理
      3、css-loader 可以处理 @import media url ,这个时候 拿到 postcssTest.css 文件,但是postcss-loader的处理时间已经过去了,所以不会在交给 postcss-loader 进行处理
      4、所以最后得到的就是没有被做兼容性处理的 css 文件
      */
    • 解决:

      {
          test: /.css$/,
              use: [
                  "style-loader",
                  {
                      loader: "css-loader",
                      options:{
                          importLoaders:1  // 往前找一个
                      }
                  },
                  "postcss-loader",
              ]
      },
      

image 打包

场景:

  • 通过img标签的src属性引用图片
  • 通过css background 的 url 属性引用图片

file-loader

  1. 安装

    npm i file-loader -D

  2. img标签的src属性引用图片的使用

    • 第一种方法

      // src/js/image.js
      function packImg(params) {
          // 创建一个容器元素
          const oElI = document.createElement('div')
      ​
          // 创建 img 标签,添加 src 属性
          const oImg = document.createElement('img')
          oImg.width  = 400
          // 
          oImg.src = require('../image/01.jpg').default
      ​
      ​
          oElI.appendChild(oImg)
          return oElI
      }
      ​
      document.body.appendChild(packImg())
      ​
      // src/index.js
      import './js/image.js'// src/index.html
      <script src="./dist/build.js"></script>
      

      webpack配置

       {
           test: /.(png|jpe?g|svg|gif)$/,
           use: [ "file-loader"]
       }
      

      在 webpack5 中,通过 require 引入图片时,webpack打包之后会返回一个对象。所以必须通过 require('../image/01.jpg').default 获取图片地址。

    • 第二种方法

      如果不想通过 require('../image/01.jpg').default 获取图片地址,可以在 webpack 中添加配置选项

         {
          test: /.(png|jpe?g|svg|gif)$/,
                 use: {
                  loader: "file-loader",
                     options: {
                          esModule: false // 不转为 esModule
                     }
                 }
         }
      
    • 第三种方法

      如果不想通过 require('../image/01.jpg').default 获取图片地址,可以通过 import 导入图片资源

      // src/js/image.js
      import oImgSrc from '../image/02.jpg'function packImg(params) {
          // 创建一个容器元素
          const oElI = document.createElement('div')
      ​
          // 创建 img 标签,添加 src 属性
          const oImg = document.createElement('img')
          oImg.width  = 400
          // 
          oImg.src = oImgSrc
      ​
      ​
          oElI.appendChild(oImg)
          return oElI
      }
      ​
      document.body.appendChild(packImg())
      
    1. 设置背景图片

      // src/js/image.js 
      import oImgSrc from '../image/02.jpg'
      import '../css/img.css'function packImg(params) {
          // 创建一个容器元素
          const oElI = document.createElement('div')
      ​
          // 创建 img 标签,添加 src 属性
          const oImg = document.createElement('img')
          oImg.width  = 400
          // oImg.src = require('../image/01.jpg').default
          // oImg.src = require('../image/01.jpg')
          oImg.src = oImgSrc
      ​
          oElI.appendChild(oImg)
      ​
          // 设置背景图片
          const oBgImg = document.createElement('div')
          oBgImg.className = 'bgBox'
          oElI.appendChild(oBgImg)
      ​
          return oElI
      }
      ​
      document.body.appendChild(packImg())
      
      /* src/css/img.css */
      .bgBox{
          width: 240px;
          height: 310px;
          border: 1px solid #000;
          background-image: url('../image/03.jpg');
      }
      

      webpack 配置文件

      {
          test: /.css$/,
              use: [
                  "style-loader",
                  {
                      loader: "css-loader",
                      options:{
                          importLoaders:1,
                          esModule: false
                      }
                  },
                  "postcss-loader",
              ]
      },
      {
           test: /.(png|jpe?g|svg|gif)$/,
           use: ["file-loader"]
      },
      

      在 webpack5 中,不管使用 file-loader 处理 src 引入的图片资源,还是使用 css-loader 处理 background-image 通过 url 引入的资源时,都会将图片资源打包成一个 esModule 资源,必须通过 .default获取具体的图片地址。

      所以:需要在 css-loader 对图片地址进行打包时,要添加 options 配置项 **esModule: false**

    2. 对图片打包之后的路径和文件名称进行处理

       {
           test: /.(png|jpe?g|svg|gif)$/,
               use: {
                   loader: "file-loader",
                       options: {
                           name: '[name].[hash:6].[ext]',
                           outputPath:'img'
                       }
               }
       }
      

      [name] 表示使用原来的文件名进行打包

      [hash:6] 表示以6位hash字符的长度拼接,保证图片地址的唯一

      [ext] 保留扩展名

      outputPath 打包之后的路径 dist/img/*.jpg

      options 还可以简写为:

       {
           test: /.(png|jpe?g|svg|gif)$/,
               use: {
                   loader: "file-loader",
                   options: {
                       name: 'img/[name].[hash:6].[ext]'
                   }
               }
       }
      
url-loader
  1. url-loader 会将 图片资源以 base64 uri 的格式,直接打包到文件中,会减少文件的请求次数,但会增加文件的体积,影响首屏渲染的速度

  2. file-loader 将资源拷贝至指定目录下,分开请求,增加请求次数

  3. url-loader 中可以调用 file-loadeer

  {
      test: /.(png|jpe?g|svg|gif)$/,
          use: {
              loader: "url-loader",
                  options: {
                      name: 'img/[name].[hash:6].[ext]',
                      limit: 25 * 1024
                  }
          }
  },

limit: 25 * 1024 文件大于 25k 采用 file-loader的形式进行拷贝,单独打包到dist目录 文件小于 25k 的话才用 url-loader 的形式,将图片直接打包到 main.js 文件中。

asset

在 webpack 5 中可以直接使用内置的 asset 进行图片的打包,不需要 下载 file-loader 或 url-loader 进行打包

asset 打包:

  • asset/resource 相当于 file-loader 的打包过程

    generator 对打包之后地址和文件名进行配置,[ext]之前不需要添加 .[ext]webpack5会默认添加

     {
         test: /.(png|jpe?g|svg|gif)$/,
             type: 'asset/resource',
             generator: {
                 filename: "img/[name].[hash:4][ext]"
              }
     },
    
  • asset/inline 相当于 url-loader 的打包过程

      {
          test: /.(png|jpe?g|svg|gif)$/,
          type: 'asset/inline'
      },
    
  • asset/source 相当于 raw-loader的打包过程

  • asset (图片资源require加载不需要default获取)

  • 配置 webpack

// 加载 图片资源
{
   test: /.(png|jpe?g|svg|gif)$/,
   type: 'asset',
   generator: {
        filename: "img/[name].[hash:4][ext]"
   },
   parser: {
        dataUrlCondition: {
                maxSize: 25 * 1024
        }
   }
},
// 加载视频资源
{
   test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,//加载视频资源
   type: 'asset', // 使用 webpack5 解析图片资源
   generator: {
       filename: "img/[name].[hash:4][ext]"
   },
   parser: {
       ataUrlCondition: {
           maxSize: 500 * 1024  // 大于 500k 直接打复制到 dist/img 目录中(file-loader),小于 25k 内置到 index.html(url-loader)
       }
   }
},
// asset 打包字体图标
{
   test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, //加载字体资源
   type: 'asset/resource',
   generator: {
       filename: "font/[name].[hash:4][ext]"
   },
}
             

html 打包

  1. 安装 npm i -D html-loader
  2. 配置
{
    module: {
        rules: [
             {
                test: /.html$/,
                use: ["html-loader"],
              }
        ]
    }
}

eslint

在webpack5 中不在使用 eslint-loader,使用 eslint-webpack-plugin

  1. 安装 npm i eslint eslint-webpack-plugin -D
  2. 配置
const ESLintPlugin = require('eslint-webpack-plugin');
{
     plugins: [
        // https://webpack.docschina.org/plugins/eslint-webpack-plugin/
        new ESLintPlugin()
      ],
}
  1. 在项目根目录中新建文件 .eslintrc.eslintrc.js, 如果同时存在以 .eslintrc.js 为准。
// .eslintrc
{
  "parserOptions": {
    "ecmaVersion": 2017,
    "sourceType": "module",
    "ecmaFeatures": {
        "experimentalObjectRestSpread": true,
        "jsx": true,
        "modules": true
    }
  },
  "env": {
      "browser": true,
      "node": true,
      "commonjs": true,
      "es6": true
  },
  "globals": {
    "$": "readonly",

  },
  "root": true,
  "rules": {
    "no-console": 1,
    "eqeqeq": 2,
    "no-alert": 2
  },
  "extends": "eslint:recommended" // 其他使用eslint默认配置
}

定义编译时全局变量 cross-env

  1. 安装 npm i cross-env -D
  2. 在 package.json 中配置
"scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve --config webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
 }, 
//webpack.common.js 
console.log('process.env.NODE_ENV',process.env.NODE_

webpack 中配置插件

  1. clean-webpack-plugin 插件

    • 安装 npm i clean-webpack-plugin -D

    • clean-webpack-plugin 可以每次打包时,清除上次的打包结果

    • 使用: webpack 中配置插件

      const { CleanWebpackPlugin } = require('clean-webpack-plugin')
      ​
      module.exports = {
          ...
           plugins: [
               new CleanWebpackPlugin()
           ]
       }
      
  2. html-webpck-plugin 插件

    • 安装 npm i html-webpck-plugin -D

    • 使用 html-webpck-plugin 可以自动生成 index.html 文件,但是自动生成的 index.html 文件中的body标签是一个空标签,我们在使用 vue 或 react 框架进行开发时,会将开发的页面挂载到 index.html 页面 body 标签下的 <div id="app"></div> 标签中。所以,我们可以手动创建一个 index.html 的页面作为 html-webpck-plugin 打包 html 文件的模板。

      <!-- /public/index.html 借用 vue 脚手架创建项目生成的 index.html 代码 -->
      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width,initial-scale=1.0">
          <link rel="icon" href="<%= BASE_URL %>favicon.ico">
          <title><%= htmlWebpackPlugin.options.title %></title>
        </head>
        <body>
          <noscript>
            <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
          </noscript>
          <div id="app"></div>
          <!-- built files will be auto injected -->
        </body>
      </html>
      
    • 配置

      1. 使用 htmlWebpackPlugin 传递 template 参数,生成 html 文件所用的母版文件。

      2. 使用 webpack 自带的 DefinePlugin 插件为打包生成的 index.html 文件传递常量参数 BASE_URL。

        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <!-- 打包之后生成的为,就可以直接使用 和 index.html 同级下的 favicon.ico 图标-->
        <link rel="icon" href="./favicon.ico">
        
      3. 具体配置

      const htmlWebpackPlugin = require('html-webpack-plugin');
      const { DefinePlugin } = require("webpack")
      // copy public下的静态资源 favicon.icon
      const CopyPlugin = require('copy-webpack-plugin');
      ​
      {
            plugins: [
              new CleanWebpackPlugin(),
              new htmlWebpackPlugin({
                  title: "html-webpack-plugin",
                  template: './public/index.html',
                  // 生产环境 添加压缩配置
                  minify: {
                    collapseWhitespace: true, //合并多余的空格
                    removeComments: true, //移除注释
                    removeAttributeQuotes: true, //移除属性上的双引号
                    removeEmptyAttributes: true, // 移除空属性
                    removeRedundantAttributes: true,  // 移除无用标签
                    removeStyleLinkTypeAttributes: true,  // 移除 rel="stylesheet"
                    useShortDoctype: true, // 使用短文档说明
                    keepClosingSlash: true, // 自结束
                    minifyCSS: true,  // 压缩 html 中的css
                    minifyJS: true, // 压缩 js
                    minifyURLs: true,  // 压缩 url
                  }
              }),
              new DefinePlugin({
                  BASE_URL: '"./"'
              }),
              new CopyPlugin({
                  patterns: [
                      {
                        from: "./public",
                        globOptions: {
                          ignore: ['**/index.html'] // **/ 表示在当前public目录下查找
                        }
                      },
                  ],
              })
          ]
      }
      
  3. mini-css-extract-plugin 插件(prod)

    • npm i -D mini-css-extract-plugin -D
    • 在生产环境中用于提取 css 代码。用MiniCssExtractPlugin.loader代替style-loader,配合MiniCssExtractPlugin使用。
    • 配置
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    {
        ...
        mode: 'production',
        module: {
          rules: [
            {
              test: /.css$/,
              use: [
                MiniCssExtractPlugin.loader,
                {
                  loader: 'css-loader',
                  options: {
                    importLoaders: 1,  // 向前找一个,在用 postcss 处理一次
                  }
                },
                'postcss-loader'
              ],
            },
            {
              test: /.less$/,
              use: [ 
                MiniCssExtractPlugin.loader,
                {
                  loader: 'css-loader',
                  options: {
                    importLoaders: 2,  // 向前找一个,在用 postcss 处理一次
                  }
                }, 
                'less-loader', 
                'postcss-loader' 
              ],
            },
          ]
        },
        plugins: [
          new MiniCssExtractPlugin({
            filename: 'css/[name].[hash:4].css',
          }),
        ],
    }
    
  4. optimize-css-assets-webpack-plugin 插件(prod)

    • 安装 npm i -D optimize-css-assets-webpack-plugin
    • 压缩 css 文件
    • 配置
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
    
    {
        plugins: [
           new OptimizeCSSAssetsPlugin({
                cssProcessorPluginOptions: {
                  preset: ['default', { discardComments: { removeAll: true } }], // 移除注释
                },
                cssProcessorOptions: {
                  map: { // 生成 map 文件
                    inline: false,
                    annotation: true
                  }
                }
             })
        ]
    
    }
    
  5. copy-webpack-plugin 插件(prod)

    • 安装 npm i copy-webpack-plugin -D
    • copy public 资源
    • 配置
    const CopyPlugin = require("copy-webpack-plugin");
    
    {
        plugins: [
            new CopyPlugin({
                patterns: [
                    {
                      from: "./public",
                      // to: "", // 目标地址
                      globOptions: {
                        ignore: ['**/index.html']
                      }
                    },
                ],
            }),
        ]
    }
    
  6. progress-bar-webpack-plugin 插件

    • 安装 npm i -D progress-bar-webpack-plugin
    • 增加编译进度条
    • 配置
      const ProgressBarPlugin = require("progress-bar-webpack-plugin");
      
    {
        plugins: [
            new ProgressBarPlugin({ // 使用进度条显示构建进度
               format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
            })
        ]
    }
    
  7. webpack-bundle-analyzer 插件(prod)

    • 安装 npm i -D webpack-bundle-analyzer
    • 它将bundle内容展示为一个便捷的、交互式、可缩放的树状图形式。方便我们更直观了解代码的分离。
    • 配置
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 
    
    {
        plugins: [
            new BundleAnalyzerPlugin()
        ]
    }
    

babel

对于 JSX ts ES6+ 的语法,浏览器是不支持的,我们可以借助 Babel 对浏览器不支持的语法做些处理,以实现 JS 兼容

  1. 安装 babel

    npm i @babel/core -D

  2. 测试代码

    // src/index.js
    const title = "前端"const fn = () => {
        console.log('title: ', title);
    }
    ​
    ​
    fn()
    
  3. 如果在命令行中使用 Babel 需要安装 @babel/cli

    npm i @babel/cli -D

    npx babel src/index.js --out-dir build
    

    通过 查看 转化结果 发现 build下生成index.js 和 src/index.js 是相同的文件,所以 @babel/core 只是对代码进行了源代码转换,并没有对js语法进行转换。

  4. 安装具体转换工具包

    • @babel/plugin-transform-arrow-functions 处理箭头函数

      npm i @babel/plugin-transform-arrow-functions -D

    npx babel src/index.js --out-dir build --plugins=@babel/plugin-transform-arrow-functions
    

    发现箭头函数被转换成了普通函数

    const title = "前端";
    ​
    const fn = function () {
      console.log('title: ', title);
    };
    ​
    fn();
    
    • @babel/plugin-transform-block-scoping 转换块作用域

      npm i @babel/plugin-transform-block-scoping -D

      npx babel src/index.js --out-dir build --plugins=@babel/plugin-transform-block-scoping 
      

      可以发现 const 被转换成了 var

      var title = "前端";
      ​
      var fn = () => {
        console.log('title: ', title);
      };
      ​
      fn();
      
    • 配置 webpack

      {
          test: /.js$/,
              use: {
                  loader: "babel-loader",
                      options: {
                          plugins: [
                              '@babel/plugin-transform-arrow-functions',
                              '@babel/plugin-transform-block-scoping'
                          ]
                      }
              }
      }
      
  5. 预设 @babel/preset-env

    可以发现如果为了转换不同的语法,我们需要借助很多工具包,开发会非常麻烦,所以我们可以直接使用 babel 提供的预设 @babel/preset-env 进行语法的转化,它集合了很多的工具包,不需要单独安装。

    • 安装

      npm i @babel/preset-env -D

      npx babel src/index.js --out-dir build --presets=@babel/preset-env 
      

      也可以帮我们转化成功:

      "use strict";
      ​
      var title = "前端";
      ​
      var fn = function fn() {
        console.log('title: ', title);
      };
      ​
      fn();
      
  6. 配置 webpack

    • 安装 babel-loader

      npm i babel-loader -D

      {
          test: /.js$/,
              use: {
                  loader: "babel-loader",
                      options: {
                          presets: [ // 预设可以有多个
                              '@babel/preset-env'
                          ]
                      }
              }
      }
      
    • @babel/preset-env 会根据 .browserlistrc 中配置的浏览器版本,去处理 js 代码

    • 也可以通过以下方式去配置浏览器版本处理兼容问题

      {
          test: /.js$/,
              use: {
                  loader: "babel-loader",
                      options: {
                          presets: [
                              [
                                  '@babel/preset-env',
                                  { targets: 'chrome 91' }
                              ]
                          ]
                      }
              }
      }
      

      此时,chrome 91 支持 const 和 箭头函数,所以打包时就不会去转换 const 和 箭头函数的语法。

  7. ** 常用 babel 配置方式 **

    • 在根目录下新建文件 babel.config.js

      module.exports = {
          presets: [
              '@babel/preset-env'
          ]
      }
      
    • wepback 配置

        {
            test: /.js$/,
            use: ["babel-loader"]
        }
      

polyfill

在网页需要向低版本兼容时,如IE不支持promise等新特性,我们仅仅使用babel进行es5转换是不够的,还需要把这些新特性进行转换

Babel 包含一个polyfill 库。这个库里包含 regenerator 和 core-js.

这个库将会模拟一个完全的 ES2015+ 的环境。

这意味着你可以使用 新的内置语法 比如 promise 或者 WeakMap, 静态方法比如Array.from 或 Object.assign, 实例方法 比如 Array.prototype.includes 和 generator 函数。

在 webpack5 之前,webpack 集成了 ployfill ,但如果不使用 ployfill 会影响打包速度,所以 webpack5 中webpack将 ployfill 单独抽离出来了,因此在使用 ployfill 是需要手动配置。

  1. 安装 npm i @babel/polyfill --save

    在 Babel7 之后 官网 不建议直接安装使用 @babel/polyfill

    npm i core-js regenerator-runtime

  2. 配置

    • babel.config.js
    module.exports = {
        presets: [
            [
                '@babel/preset-env',
                {
                    // false : 默认,不对当前的 JS 处理做 ployfill 的填充
                    // usage: 依据用户源代码当中使用的新语法做 ployfill 填充(没有使用的不填充),默认使用的是 corejs 2版本,安装的是 3 版本的,所以需要 corejs: 3 指明
                    // entry: 依据我们当前筛选出来的浏览器进行 ployfill 填充
                    useBuiltIns: 'usage',
                    corejs: 3
                }
            ]
        ]
    }
    
    • webpack 配置

       {
           test: /.js$/,
           use: ["babel-loader"],
           exclude: /node_modules/
       }
      

webpack 热更新

  1. 通过 在 package.json 中进行配置 --watch,实现热更新

    "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "build": "webpack --config lg.webpack.js --watch"
      },
    
  2. 在 wepback 配置文件中配置,实现热更新

    module.exports = {
        mode: 'development',
        watch: true,
        。。。
    }
    
  3. dev-server 实现热更新

    前两种方法的不足:

    • 每次更新所有的源代码都会重新编译

    • 每次编译成功都需要进行文件读写(打包生成 dist 目录)

    • 需要借助 vscode 插件 live server 实时更新,不能实现局部更新

    1. 安装

      npm i webpack-dev-server -D

    2. 配置

      • package.json

          "scripts": {
            "test": "echo "Error: no test specified" && exit 1",
            "build": "webpack --config lg.webpack.js",
            "serve": "webpack serve --config lg.webpack.js"
          },
        
      • webpack 配置

        module.exports = {
            mode: 'development',
            watch: false, // 为 false 或者不写
            devtool: false,
        }
        

webpack middleware

webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。

模块热替换 - HMR

模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。

  1. 配置 webpack

    {
        devServer: {
            hot: true
        },
            
    }
    
  2. title.js 模块

    module.exports = '前端开发'
    console.log('title.js 模块请求');
    
  3. 在 index.js 入口文件中

    import './js/title.js'if (module.hot) {
        module.hot.accept(['./js/title.js'], () => {
            console.log('title.js 模块更新了!');
        })
    }
    

react 项目

一、使用 webpack 搭建 react 项目,并实现 热更新HMR

  1. 安装 jsx 编译预设

    npm i @babel/preset-react react react-dom -D

  2. 安装 react 热更新插件

    npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D

  1. 配置

    • webpack配置

      const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
      ​
      ​
      {
           module: {
              rules: [
                  
                  {   
                      // 配置 jsx loader
                      test: /(\.jsx|\.js)$/,
                      use: ["babel-loader"],
                      exclude: /node_modules/
                  }
              
          },
          plugins: [
              new ReactRefreshPlugin(),
          ]
      }
      
    • 配置 babel.config.js

      module.exports = {
          presets: [
              [
                  '@babel/preset-env',
              ],
              [
                  '@babel/preset-react' // 配置 react 预设
              ]
          ],
          plugins: ['react-refresh/babel'],
      }
      

二、配置 css modules

  1. 需求:在react项目中,使用 styles.xxx ,配置 HTML 样式。

    • 配置:在 webpack 中,css-loader 处配置:
         {
          loader: 'css-loader',
          options: {
            importLoaders: 1,  // 向前找一个,在用 postcss 处理一次
            sourceMap: true,
            modules: {
              localIdentName: '[name]__[local]-[hash:base64:5]'
            }
          }
        }
      
    • 使用
      .helloContainer{
        width: 400px;
        height: 300px;
        border: 1px solid springgreen;
        .title{
          font-weight: 900;
          color: red;
        }
      
      }
      
      import React, { useState } from 'react'
      import styles from './index.less'
      
      function index() {
        let [count, setCount] = useState(0);
        return (
          <div className={styles.helloContainer}>
            <p className={styles.title}>hello 组件 { count }</p>
            <button onClick={ () => setCount(count++)}>add</button>
          </div>
        )
      }
      export default index
      

    使用以上方式使用css样式:

    1. 引入样式时额外增加了一个styles变量
    2. 需要不断写styles.xxx,重复代码
  2. babel-plugin-react-css-modules插件可以一定程度上缓解这些问题,使代码变为:<div styleName='xxx'></div>

    • 安装: npm i babel-plugin-react-css-modules postcss-less hasha -D

    • 配置

      • babel.config.js中配置:
      const path = require('path')
      const hasha = require('hasha')
      
      const generateScopedName = (name, filename) => {
        const hash = hasha(filename + name, { algorithm: 'md5' });
        let basename = ''
        if (filename.endsWith('.css')) {
          basename = path.basename(filename, '.css');
        } else {
          basename = path.basename(filename, '.less');
        }
        return `${basename}-${name}-${hash.slice(0, 5)}`;
      };
      module.exports = {
        presets: [
           。。。
        ],
        plugins: [
          [
            'react-css-modules',
            {
              context: path.join(__dirname, 'src'),
              exclude: 'node_modules',
              filetypes: {
                '.less': {
                  syntax: 'postcss-less'
                },
              },
              generateScopedName: generateScopedName,
            }
          ]
        ]
      }
      
      
      • 在 webpack 中配置:

        css-loader options.modules下的 localIdentName 改为 getLocalIdent()

      const hasha = require('hasha');
      const path = require('path')
      
      const generateScopedName = (name, filename) => {
        const hash = hasha(filename + name, { algorithm: 'md5' });
        let basename = ''
        if (filename.endsWith('.css')) {
          basename = path.basename(filename, '.css');
        } else {
          basename = path.basename(filename, '.less');
        }
        return `${basename}-${name}-${hash.slice(0, 5)}`;
      };
      
      module.exports = {
        module: {
          rules: [
            {
              test: /.css$/,
              use: [
                'style-loader',
                {
                  loader: 'css-loader',
                  options: {
                    importLoaders: 1,
                    sourceMap: true,
                    modules: {
                      getLocalIdent({ resourcePath }, localIdentName, localName) {
                        return generateScopedName(localName, resourcePath);
                      }
                    }
                  }
                },
                'postcss-loader'
              ],
            },
            {
              test: /.less$/,
              use: [
                'style-loader',
                {
                  loader: 'css-loader',
                  options: {
                    importLoaders: 2,  // 向前找一个,在用 postcss 处理一次
                    sourceMap: true,
                    modules: {
                      getLocalIdent({ resourcePath }, localIdentName, localName) {
                        return generateScopedName(localName, resourcePath);
                      }
                    }
                  }
                },
                'less-loader',
                'postcss-loader'
              ],
            },
          }
         ]
        }
      }
      
    • 使用:
      .helloContainer{
           width: 400px;
           height: 300px;
           border: 1px solid springgreen;
           .title{
             font-weight: 900;
             color: red;
           }
      
         }
         .title1{
           color: skyblue;
         }
      
      
      
      import React, { useState } from 'react'
      import './index.less'
      
      function index() {
       let [count, setCount] = useState(0);
       return (
         <div styleName="helloContainer">
           <p styleName="title1">使用 styleName 命名:className{ count }</p>
           <p styleName="title">hello 组件 { count }</p>
           <button onClick={ () => setCount(count++)}>add</button>
         </div>
       )
      }
      export default index
      
      
      如果想同时使用 <div className={styles.xxx}></div><div styleName='xx'></div> ,可以在当前配置项下使用:
      
      import React, { useState } from 'react'
      import styles from './index.less' // 引入 less
      
      function index() {
        return (
          <div className={styles.helloContainer}>
            <p styleName="title1">使用 styleName 命名:className</p> // 通过styleName 使用样式
            <p className={styles.title}>hello 组件</p>  // 通过className={styles.xxx}使用less
          </div>
        )
      }
      
      export default index
      
      

三、配置别名

modules.export = {
   resolve: {
    extensions: [".js", ".jsx", ".json"], // 省略文件后缀
    alias: { // 配置别名
      "@": path.resolve(__dirname, "./src"),
    },
  },
}

vue 项目

  1. 安装

    npm install vue-template-compiler -D

    npm install vue

  2. 使用 vue-loader@15 版本

    npm i vue-loader@15 -D

  3. 测试代码

    <!-- src/App.vue -->
    <template>
      <div class="example">{{ msg }}</div>
    </template><script>
    export default {
      data () {
        return {
          msg: 'Hello world!'
        }
      }
    }
    </script><style>
    .example {
      color: #00ff22;
    }
    </style>
    
    // src/index.js
    import './js/title.js'
    import Vue from 'vue'
    import App from './App.vue'if (module.hot) {
        module.hot.accept(['./js/title.js'], () => {
            console.log('title.js 模块更新了!');
        })
    }
    ​
    ​
    new Vue({
        render: h => h(App)
    }).$mount('#app')
    
  4. 配置

    webpack 配置 vue-loader

    const VueLoaderPlugin = require('vue-loader/lib/plugin')  // 如果是 vue-loader 是 15 版本需要配置module: {
         rules: [
             {
                 test: /.vue$/,
                 use: ["vue-loader"],
                 exclude: /node_modules/
             }
         ]
     },
     plugins: [
    ​
         new VueLoaderPlugin()
     ]
    

三、webpack 中的一些配置

  1. output.publicPath

    • publicPath 表示 打包之后 index.html 中 script 标签 src 的引用路径。
    • 如果是开启的是 devServer 服务器 publicPath : 域名 + publicPath + filename
  2. devserver

    • devServer.publicPath 表示本地服务开启在哪个路径下

      比如:通常 可以通过 http://localhost:8080 访问本地服务,如果 devServer.publicPath 设置为 /lg ,则 本地服务需要通过 http://localhost:8080/lg 访问,一般 要求 output.publicPath 和 devServer.publicPath 设置为相同的值。

    • devServer.contentBase: 如果我们打包之后的资源依赖其他资源,此时告知去哪找。

      比如:打包之后的资源 以来 public 下的资源,可以设置为 path.resolve(__dirname, 'public')

    • devServer.watchContentBase: 监控 public 文件的变化,默认为false

    • devServer.hotOnly :当某个组件中报错时,会影响其他组件的展示,默认为 false

    • devServer.port:本地服务器的端口号

    • devServer.open:是否自动开启页面,默认为 false

    • devServer.compress:浏览器加载的 main.js 是否需要压缩 ,默认 false

    • devServer.historyApiFallback:当浏览器加载失败时时候显示某个页面,默认 false,改为true后,加载失败后显示 index.html

    • devServer.proxy:设置代理

      proxy: {
          // axios 发送请求时 路径为 /api.user
          // http://localhost:4000/api/user
          // https://api.github.com/api/user
          '/api': {
              target: 'https://api.github.com',
              pathRewrite: { '^/api': "" },
              changeOrigin: true
          }
      }
      
  3. resolve

    components/index.js、components/Home.jsx

    当导入文件时:

    • import Home from './components',会默认查找 components 下的 index.js

    • import Home from './components/Home',wepback 会默认查找 Home.js 或者 Home.json,如果没有就会报错

      {
          // 如果不写扩展名,webpack会默认查找 js 或 json, 我们可以通过配置可以添加其他的扩展名
          resolve: ['js', 'json', '.ts', 'jsx'],
          // 设置别名
          alias: {
              "@": path.resolve(__dirname, 'src')
          }
      }
      
  4. source map sourceMap 是一种映射,可以帮助我们在代码的调试将错误信息定位到错误信息源代码中的具体位置。 设置 mode 为 development,当mode为 development 时,devtool 默认为 eval 模式,不能映射到具体的位置。 source map 有许多 可用选项:

  5. devtool: source-map: 会生成一个 map 文件用于映射到源代码中(vue)。

  6. devtool: eval-source-map: 不会生成具体的 map 文件,会将具体的 map 信息以base64的形式放到 eval 后面。

  7. devtool: inline-source-map: 不会生成具体的 map 文件,会将具体的 map 信息以base64的形式放到打包之后生产的 build.js 的最后一行 //# sourceMappingURL=XXX

  8. devtool: cheap-source-map: 区分于 source-map,只提供行报错信息。但是显示的是babel-loader处理(var转为const,删除多余的空行【不能正确的定位到源代码的某一行】)之后的位置信息。

  9. devtool: cheap-module-source-map: 区分于 source-mapcheap-source-map。只提供行报错信息行报错信息,可以正确定位错误信息(react)。

四、webpack 配置项目

webpack基本配置demo

webpack react js demo