likes
comments
collection
share

老记不住的Webpack知识点汇总(上)

作者站长头像
站长
· 阅读数 49

前言

自从学习Webpack后,Webpack的知识点总是老忘,而且有些知识点很难用言语给描述出来,没办法讲给别人听,秉着好记性不如烂笔头的原则,我还是总结下来吧,便于后期回顾。

Webpack的loader和plugin

很多人都会有这样的一个疑问,包括面试中,面试官也会常问,Webpack中的loader和plugin有什么区别,下面,我们来看看,他们到底有什么区别。

如何配置使用

老记不住的Webpack知识点汇总(上)

//webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  // entry: './src/index.js',
  entry: {
    index: {
      import: './src/index.js',
      dependOn: 'shared'
    },
    main: {
      import: './src/main.js',
      dependOn: 'shared'
    },
    shared: ['axios']
  },
  output: {
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
        }
      },
      {
        test: /\.ts$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}

可以看到,配置用法上,其实看不出什么区别,但是我们可以从配置的插件入手,观看区别,我们先看babel-loader有什么作用,babel-loader是将部门浏览器不支持的语法和API转换成所有浏览器都支持的语法、polyfill的作用,那么它是如何做到这个的呢?是不是将我们需要打包的代码进行转换,然后输出到打包文件,我们仔细想一想,less-loader、css-loader、style-loader这种我们常用的loader是不是都是将目标代码进行转换,转换成我们所需要的代码。

经过上边的思路,那我们就总结一下loader的作用。

loader的作用:loader 通常指打包的方案,即按什么方式来处理打包,打包的时候它可以拿到模块源代码,经过特定 loader 的转换后返回新的结果。执行时机是打包的时机。

那么plugin呢?

我们仔细想一想,我们一般使用plugin做什么?是不是拓展一些Webpack没有的功能?,就像上边代码中写的HtmlWebpackPlugin,它的作用是在打包目录文件中生成html文件,他是不是拓展了Webpack在打包过程中无法生成html文件的功能?那我们不就可以总结一下了嘛。

Webpack的作用:plugin的作用就是拓展Webpack的功能,当我们的Webpack运行到某一个时刻的时候,就会触发plugin,帮助Webpack做一些拓展功能,plugin的执行时机在Webpack运行的全过程都有可能执行,Webpack在运行的过程中,会广播出很多Webpack的生命周期,plugin会监听这些生命周期,在合适的时机通过 Webpack 提供的 API 改变输出结果。

官方的解释是:插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。

source-map

const path = require('path')

module.exports = {
  mode: 'production',
  devtool: 'nosources-source-map',
  // 常见的值:
  // 1.false
  // 2.none => production
  // 3.eval => development
  // 4.source-map => production
  // 不常见的值: 
  // 1.eval-source-map: 添加到eval函数的后面
  // 2.inline-source-map: 添加到文件的后面
  // 3.cheap-source-map(dev环境): 低开销, 更加高效
  // 4.cheap-module-source-map: 和cheap-source-map比如相似, 但是对来自loader的source-map处理的更好
  // 5.hidden-source-map: 会生成sourcemap文件, 但是不会对source-map文件进行引用
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './build'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  }
}

babel配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './build'),
    filename: 'bundle.js',
    // 重新打包时, 先将之前打包的文件夹删除掉
    clean: true
  },
  resolve: {
    extensions: ['.js', '.json', '.wasm', '.jsx', '.ts']
  },
  module: {
    rules: [
      // 针对jsx?代码进行babel处理
      {
        test: /\.jsx?$/, // x?: 0或者1个x
        // exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        //   options: {
                //指定babel插件转换
        //     // plugins: [
        //     //   "@babel/plugin-transform-arrow-functions",
        //     //   "@babel/plugin-transform-block-scoping"
        //     // ]
                //使用预设
        //     presets: [
        //       ["@babel/preset-env", {
        //         // 在开发中针对babel的浏览器兼容查询使用browserslist工具, 而不是设置target
        //         // 因为browserslist工具, 可以在多个前端工具之间进行共享浏览器兼容性(postcss/babel)
        //         // targets: ">5%"
        //       }]
        //     ]
        //   }
        }
      },
      {
        test: /\.ts$/,
        // use: "ts-loader"
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}

babel-loader的配置可以配置具体的插件进行代码转换,也可以配置预设,让babel检测那些代码需要转换,从而自动转换。

使用预设时,我们需要在babel.config.js文件中进行配置,给preset-env配置一些属性:

useBuiltIns:设置以什么样的方式来使用polyfill;

corejs:设置corejs的版本,目前使用较多的是3.x的版本,比如我使用的是3.8.x的版本;

另外corejs可以设置是否对提议阶段的特性进行支持;

设置 proposals属性为true即可;

//babel.config.js
module.exports = {
  // plugins: [
  //   "@babel/plugin-transform-arrow-functions",
  //   "@babel/plugin-transform-block-scoping"
  // ]
  presets: [
    ["@babel/preset-env", {
      // 在开发中针对babel的浏览器兼容查询使用browserslist工具, 而不是设置target
      // 因为browserslist工具, 可以在多个前端工具之间进行共享浏览器兼容性(postcss/babel)
      // targets: ">5%"
      // corejs: 3,
      // // false: 不使用polyfill进行填充
      // useBuiltIns: "entry"
    }],
    ["@babel/preset-react"],
    ["@babel/preset-typescript", {
      corejs: 3,
      useBuiltIns: "usage"
    }]
  ]
}

useBuiltIns属性有三个常见的值

false 打包后的文件不使用polyfill来进行适配; 并且这个时候是不需要设置corejs属性的;

usage 会根据源代码中出现的语言特性,自动检测所需要的polyfill; 这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些; 可以设置corejs属性来确定使用的corejs的版本;

entry 如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器可能会报错; 所以,如果你担心出现这种情况,可以使用 entry; 并且需要在入口文件中添加 import 'core-js/stable'; import 'regenerator-runtime/runtime'; 这样做会根据 browserslist 目标导入所有的polyfill,但是对应的包也会变大;

Webpack服务器

为什么需要搭建Webpack服务器?

目前我们开发的代码,为了运行需要有两个操作:

操作一:npm run build,编译相关的代码;

操作二:通过live server或者直接通过浏览器,打开index.html代码,查看效果; 这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成 编译 和 展示; 为了完成自动编译,webpack提供了几种可选的方式:

  1. webpack watch mode;
  2. webpack-dev-server(常用);
  3. webpack-dev-middleware;

webpack-dev-server

上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:

当然,目前我们可以在VSCode中使用live-server来完成这样的功能;

但是,我们希望在不使用live-server的情况下,可以具备live reloading(实时重新加载)的功能;这就是热重载。

webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中:

Webpack性能优化-分包

webpack分包主要分为三种

  1. 多入口分包
  2. 动态导入模块
  3. 自定义分包。

多入口分包就是定义多个入口,打包成多个文件。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  // entry: './src/index.js',
  entry: {
     //定义多个入口文件
    index: {
      import: './src/index.js',
      dependOn: 'shared'
    },
    main: {
      import: './src/main.js',
      dependOn: 'shared'
    },
    shared: ['axios']
  },
  output: {
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    clean: true
  },
  resolve: {
    extensions: ['.js', '.json', '.wasm', '.jsx', '.ts']
  },
  devServer: {
    static: ['public', 'content'],
    port: 3000,
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        pathRewrite: {
          '^/api': ''
        },
        changeOrigin: true
      }
    },
    historyApiFallback: true
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
        }
      },
      {
        test: /\.ts$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}

动态导入就是通过import函数来动态导入文件。

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  // entry: './src/index.js',
  entry: './src/main.js',
  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    // 单独针对分包的文件进行命名
    chunkFilename: '[name]_chunk.js'
  },
  resolve: {
    extensions: ['.js', '.json', '.wasm', '.jsx', '.ts']
  },
  devServer: {
    static: ['public', 'content'],
    port: 3000,
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        pathRewrite: {
          '^/api': ''
        },
        changeOrigin: true
      }
    },
    historyApiFallback: true
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
        }
      },
      {
        test: /\.ts$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}

自定义分包

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'production',
  devtool: false,
  // entry: './src/index.js',
  entry: './src/main.js',
  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    // 单独针对分包的文件进行命名
    chunkFilename: '[name]_chunk.js',
    // publicPath: 'http://coderwhycdn.com/'
  },
  resolve: {
    extensions: ['.js', '.json', '.wasm', '.jsx', '.ts']
  },
  devServer: {
    static: ['public', 'content'],
    port: 3000,
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        pathRewrite: {
          '^/api': ''
        },
        changeOrigin: true
      }
    },
    historyApiFallback: true
  },
  // 优化配置
  optimization: {
    // 设置生成的chunkId的算法
    // development: named
    // production: deterministic(确定性)
    // webpack4中使用: natural
    chunkIds: 'deterministic',
    // runtime的代码是否抽取到单独的包中(早Vue2脚手架中)
    runtimeChunk: {
      name: "runtime"
    },
    // 分包插件: SplitChunksPlugin
    splitChunks: {
      chunks: "all",
      // 当一个包大于指定的大小时, 继续进行拆包
      // maxSize: 20000,
      // // 将包拆分成不小于minSize的包
      // minSize: 10000,
      minSize: 10,

      // 自己对需要进行拆包的内容进行分包
      cacheGroups: {
        utils: {
          test: /utils/,
          filename: "[id]_utils.js"
        },
        vendors: {
          // /node_modules/
          // window上面 /\
          // mac上面 /
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_vendors.js"
        }
      }
    },
    // 代码优化: TerserPlugin => 让代码更加简单 => Terser
    minimizer: [
      // JS代码简化
      new TerserPlugin({
        extractComments: false
      })
      // CSS代码简化
    ]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
        }
      },
      {
        test: /\.ts$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}
转载自:https://juejin.cn/post/7267091810380103740
评论
请登录