使用webpack的一些总结

146 阅读11分钟
// npx webpack
 
const path = require('path'); //path是nodejs核心模块,专门用来处理路径问题
 
// nodejs核心模块
const os = require('os');
// cpu核数
const threads = os.cpus().length;
// 下载包: npm i thread-loader -D
// cpu loader需要放在我们需要处理的loader的前面
 
//插件引入
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
// 内置的terser插件
const TerserWebpackPlugin = require('terser-webpack-plugin');
 
module.exports = {
  // entry入口,单个入口
  entry: './src/index.js', //相对路径
  // 多入口
  entry: {
    index1: '',
    index2: '',
    index3: '',
    index4: '',
    // .
    // .
    // .
  },
 
  // output输出
  output: {
    // 所有打包文件输出的路径
    // __dirname是nodejs的变量,代表当前文件的文件夹目录(webpack文件夹)
    path: path.resolve(__dirname, 'dist'), // 绝对路径
    // 入口文件(js)文件名,单入口
    filename: 'static/js/index.js',
    // 多入口
    filename: 'static/js/[name].js', // webpack的命名方式,以文件名[name]为名字输出, index1.js, index2.js,...
 
    // 魔法命名,给打包输出的其他文件命名
    chunkFilename: 'static/js/[name].chunk.js', // anyName
 
    // 统一资源命名: 图片\字体通过type asset命名
    assetModuleFilename: '',
    // 自动清理上次打包的内容
    clean: true,
  },
 
  // loader,webpack默认只能处理js文件(例如es模块化语法import/export等在浏览器无法直接运行),
  // 如需要处理其他文件(如css/less/sass文件、字体图标、图片资源、html资源等)则需要用到loader
  module: {
    rules: [
      // loader的配置
      {
        // 每个文件只能被其中一个loader处理,稍微提升速度
        oneOf: [
          // 1🌟.css-loader(npm install --svae-dev css-loader)
          // 2🌟.style-loader(npm install --svae-dev style-loader)
          {
            test: /\.css$/i, // 可以是正则表达式,检测.css结尾的文件,用下面的loder编译
            use: [
              // loader的执行顺序是从右到左(css-loader -> style-loader)
              // 'style-loader', // 将js中的css通过创建style标签添加到html文件中使样式生效
              MiniCssExtractPlugin.loader, //提取css成单独文件
              'css-loader', // 将css资源编译成commonjs模块到js中
 
              // postcss-loader
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: [
                      'postcss-preset-env', // postcss的预设只能模式,能解决大多数样式兼容问题
                    ],
                  },
                },
              },
            ],
          },
 
          // 3🌟.less-loader(npm install less less-loader --save-dev)
          {
            test: /\.less$/i,
            // loader: '', // loader属性只能使用单个loader,use可以是多个(数组)
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              // postcss-loader
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: [
                      'postcss-preset-env', // postcss的预设只能模式,能解决大多数样式兼容问题
                    ],
                  },
                },
              },
              'less-loader', // 将less编译成css文件
            ],
          },
 
          // 4🌟.sass-loader/scss-loader(npm install sass sass-loader --save-dev)
          {
            test: /\.s[ac]ss$/i, // 匹配sass和scss文件
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              // postcss-loader
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: [
                      'postcss-preset-env', // postcss的预设只能模式,能解决大多数样式兼容问题
                    ],
                  },
                },
              },
              'sass-loader', // 将sass编译成css文件
            ],
          },
 
          // 5🌟.file-loader(将文件/资源输出)和url-loader(将小于某些尺寸的文件转换成base64的格式输出)在webpack5已经内置了
          {
            test: /\.(png|jpe?g|gif|webp|svg)$/,
            type: 'asset', // 使用asset type相当于启动了file-loader
            // base64格式:将图片资源转化成一长串字符串,但是体积会增大,对于小图片来说可以转化成base64以减少请求数量;但是大图片就没必要
            parser: {
              dataUrlCondition: {
                // 小于10kb的图片则转换成base64格式
                maxSize: 10 * 1024, // 10kb
              },
            },
            genertator: {
              // 输出图片的文件夹和图片名称
              // hash:唯一的hash值,防止名称冲突,可自定义hash值长度([hash:10]表示取前十位)
              // ext:文件扩展名
              // query:其他参数
              filename: 'static/images/[hash:10][ext][query]',
            },
          },
 
          // 6🌟.处理字体图标(以iconfont为例),最好用font-class类型(还有unicode、symbol)
          {
            test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
            type: 'asset/resource', // 不会对ttf/woff文件进行转换成base64,对于某些不需要转换的资源可这样设置
            genertator: {
              // 输出的media文件位置
              filename: 'static/media/[hash:10][ext][query]',
            },
          },
 
          // 7🌟.处理其他资源:mp3、mp4、avi等
 
          // 9🌟.使用babel(npm install -D babel-loader @babel/core @babel/preset-env)
          // @babel/core是babel的核心包,@babel/preset-env是智能预设
          {
            test: /\.js$/, // 编译js文件
            // include: path.resolve(__dirname, 'src'), // 只处理src下的文件
            exclude: /node_modules/, // 排除node_modules文件(不处理node_modules)
            use: [
              {
                loader: 'thread-loader', // 开启多进程处理babel编译
                options: {
                  works: threads,
                },
              },
              {
                loader: 'babel-loader',
 
                //可以在babel.config.js里面配置这些选项
                // options: {
                //   presets: ['@babel/preset-env'], // 使用智能预设
                // },
 
                // 开启缓存babel
                options: {
                  cacheDirectory: true,
                  cacheCompression: false, // 关闭缓存文件的压缩,省去缓存文件压缩的时间
 
                  // npm i @babel/plugin-transform-runtime -D
                  plugins: ['@babel/plugin-transform-runtime'], // 减少代码体积, 生产环境和开发环境都可以使用
                },
              },
            ],
          },
 
          // 12🌟.样式的兼容性处理(npm i postcss-loader postcss postcss-preset-env -D)
          // 需要在css-loader后面、在less和sass的前面使用
        ],
      },
    ],
  },
 
  // 插件plugins
  plugins: [
    // plugins的配置,插件需要引入再new一个实例才能使用
 
    // 8🌟.使用eslint(npm install eslint-webpack-plugin eslint --save-dev)
    // new ESLintPlugin(options),
    new ESLintPlugin({
      context: path.resolve(__dirname, 'src'), // context:指定哪些文件需要做eslint检查
      exclude: 'node_modules', // 默认值
      cache: true, // 开启缓存
      // 指定缓存路径
      cacheLocation: path.resolve(
        __dirname,
        '../node_modules/.cache/eslintcache'
      ),
      threads, // 开启多进程
    }),
 
    // 10🌟.处理html资源,自动引入HTML文件(npm install --save-dev html-webpack-plugin)
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'public/index.html'), // 以public/index.html为模板创建新的html,结构和原来一致&自动引入打包的资源
    }),
 
    // 11🌟.提取css文件(npm install mini-css-extract-plugin --save-dev),css会成编译一个独立文件并通过link标签引入
    // 将style-loader改成这个loader
    new MiniCssExtractPlugin({
      filename: 'static/css/index.[contenthash:10].css',
      // 动态导入的css chunk文件名
      chunkFilename: 'static/css/[name].chunk.[contenthash:10].css',
    }),
 
    // 13🌟.css压缩(npm install css-minimizer-webpack-plugin --save-dev)该plugin支持缓存和并发模式下运行
    new CssMinimizerWebpackPlugin(),
 
    // 使用Terser内置插件 多进程压缩js文件
    new TerserWebpackPlugin({
      parallel: threads,
    }),
 
    // preload 和 prefetch
    new PreloadWebpackPlugin({
      rel: 'preload',
      as: 'script',
    }),
    // new PreloadWebpackPlugin({
    //   rel: 'prefetch',
    // }),
  ],
 
  optimization: {
    // 该地方放置压缩的操作
    minimizer: [
      // 13🌟.css压缩(npm install css-minimizer-webpack-plugin --save-dev)该plugin支持缓存和并发模式下运行
      new CssMinimizerWebpackPlugin(),
      // 使用Terser内置插件 多进程压缩js文件
      new TerserWebpackPlugin({
        parallel: threads,
      }),
    ],
 
    // 代码分割配置(打包的资源叫chunk(如indexjs),输出的叫bundle)
    splitChunks: {
      chunks: 'all', // 对所有模块进行分割
      // 以下是默认值
      minSize: 20000, // 分割代码的最小大小
      minRemainingSize: 0, // 类似于minSize,确保最后提取的文件大小不为0
      minChunks: 1, // 至少被引用的次数,满足条件即进行分割
      maxAsyncRequest: 30, // 按需加载时并行加载的文件最大数量
      maxInitialRequest: 30, // 入口js文件的最大并行请求数量
      enforceSizeThreadhold: 50000, // 超过50kb一定会单独打包(会忽略minRemainingSize maxAsyncRequest maxInitialRequest)
      // 确定哪些模块要打包到一个组
      cacheGroup: {
        // 组名defaultVendors
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块匹配正则
          priority: -10, // 权重,越大越高
          reuseExistingChunk: true, // 如果当前chunk包含已从主bundle中拆分的模块,则将它重用,而不是生成新的模块
        },
 
        // 组名default
        // 其他没有写的配置会用上面的默认值
        default: {
          minSize: 0,
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
 
    // 修改配置
    cacheGroup: {},
 
    // network cache
    // 当某个被引用模块改变时,其输出文件名的hash值会改变;引入该模块的文件也同时会改变.我们不希望这样
    // 会多生成出一个runtime文件和一个runtime.map文件(将hash值提取),作用是将导入文件的文件和被引用的文件做一个映射,
    // 改变时只有被引用的文件和runtime文件和一个runtime.map文件改变
    // 引用该模块的其他文件不变
    runtimeChunk: {
      name: entryPoint => `runtime~${entryPoint.name}`,
    },
  },
 
  // 搭建开发服务器(自动化):npm i webpack-dev-server -D
  // 启动指令变为 npx webpack serve
  // 开发服务器没有output,是在内存中编译打包
  devServer: {
    host: 'localhost', // 服务器域名
    port: '3008', // 端口号
    open: true, // 是否自动打开浏览器
    hot: true, // webpack5默认是true,生产模式下不能用
  },
 
  //模式:开发环境 development / 生产环境 production
  mode: 'development',
 
  // sourceMap配置
  // 开发模式:cheap-module-source-map:打包编译速度快,但是只包含行映射(无列映射)
  // 生产模式:source-map:打包编译速度慢,但是包含行、列映射,更容易定位代码
  devtool: 'cheap-module-source-map',
};
 
/**
 * webpack优化
 * 1.提升开发体验
 *    ✨sourceMap 源代码映射,用来增强开发/生产环境的调试
 *        编译后的报错定位是编译后的文件,很难发现错误所在,所以要用到sourceMap
 *
 * 2.提升打包构建速度(主要是js文件的编译和压缩过程速度: 压缩是用到terser,webpack幕后压缩js的工具)
 *    ✨HMR hotModuleReplacement 开发过程中,修改了某一个模块的代码,webpack会默认将所有模块重新打包编译导致速度很慢
 *    而HMR则是可以做到需要更新的部分模块打包编译,提升编译速度。生产模式下不能用
 *    react-hot-loader
 *
 *    ✨OneOf 只匹配一个loader剩下的忽略,开发环境和生产环境都生效
 *    打包时每个文件都会经过所有loader处理,虽然因为test正则匹配不上没有实际处理,但是都要过一遍,所以会很慢
 *
 *    ✨include/exclude
 *    手动配置 包含/排除 需要的文件,二选一:要么包含要么排除
 *
 *    ✨cache 对eslint检查和babel编译进行缓存,每次打包js文件都要经过eslint和babel的编译,速度会慢
 *    可以缓存之前的eslint和babel的编译结果,这样再次打包就能提高性能。只能提升第二次后面的速度,初始加载还是正常
 *    缓存的文件可以在node_modules下的.cache下查看
 *
 *    ✨多进程打包 注意:每个进程开启都需要600ms左右的时间,当项目体量不是很大编译压缩耗时不长的时候多进程打包提升性能不明显
 *    当项目是某些很大的体量时,才考虑开启多进程打包. 体量到达一个临界点的时候才会体现出多进程打包的性能有明显提升
 *
 * 3.减少代码体积
 *    ✨tree shaking 开发过程中需要引入一些第三方的库,如果没有特别处理打包时会引入整个库,但实际上我们只用到这个库的一小部分.导致打包的体积变大
 *    依赖于es module, 移除js用不上的代码. webpack已经默认启用这个功能,无需其他配置
 *
 *    ✨减少babel生成文件的体积 babel为每一个文件都插入了辅助代码,导致代码体积过大,比如_extend,默认情况下会被添加到每一个需要用到的文件
 *    可以将重复的辅助代码作为一个独立模块,避免重复引用
 *    @babel/plugin-transform-runtime 禁用了babel自动对每个文件的runtime注入,改为引入. 并且所有的辅助代码从这里引用
 *    npm i @babel/plugin-transform-runtime -D
 *
 *    ✨图片压缩 只对本地静态图片资源起效, 如果是在线链接的图片则没必要
 *    npm i image-minimizer-webpack-plugin imagein -D
 *    无损压缩
 *    npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
 *    有损压缩
 *    npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
 *
 * 4.优化代码运行性能
 *    ✨代码分割code split 打包时将所有js打包到一个文件中的话体积会非常大,如果是只渲染首页的话,应该先加载首页的代码,其他的不加载
 *    需要将打包生成的文件进行代码分割,生成多个js文件,选择性的渲染,这样加载资源少加载速度就会提升
 *    1)分割文件
 *    2)按需加载
 *
 *        🌟多入口 在entry中配置
 *        🌟多入口提取公共模块 提取复用的逻辑代码,在optimization中配置splitChunks
 *        🌟多入口按需加载 动态导入模块 import(() => {}), webpack会将动态导入的文件自动代码分割,在需要用到的时候调用.
 *            import()返回一个promise对象, import('./import.js).then(res => res.default()).catch()
 *            例如路由的按需加载
 *
 *        🌟单入口code split 一般spa都是单入口, 一样是optimization中配置splitChunks: {chunks: 'all'}
 *        🌟给分割的模块命名:webpack中是通过注释命名(webpack魔法命名)
 *        🌟统一命名
 *
 *        🌟prefetch和preload 按需加载的资源如果很大的话,当需要用到的时候仍然会需要时间加载,会有明显卡顿
 *        如果需要在浏览器有空余时间的时候加载后续需要用到的资源,就要用到 prefetch和preload
 *        preload: 告诉浏览器立即加载资源(预加载)
 *        prefetch: 告诉浏览器在空余的时候加载资源
 *        两者都只是加载资源,并不执行,且都有缓存; 区别: preload优先级较高; preload只加载当前页面需要的资源,prefetch可以加载当前页面需要的资源和其他页面的资源
 *        npm install --save-dev @vue/preload-webpack-plugin
 *        注意:这两个方法其实是在<link rel='prefetch/preload' src='./xxx.js' />标签中写rel属性控制js的加载行为,兼容性很差
 *
 *        🌟network cache 在runtimeChunk中配置
 *
 *    ✨core-js(完全解决js兼容性问题) 专门用来做es6以上的polyfill
 *    npm i core-js
 *    import 'core-js' // 全部引入,不推荐
 *    按需加载 core-js
 *    import 'core-js/es/promise' // 只用到promise
 *
 *    babel遇到async/promise/高级数组方法(includes等)会没办法处理???
 *
 *    ✨PWA 给项目提供离线体验
 */
 
import(/* webpackChunkName: "anyName" */ './src/import');
 
// can i use !!!!!!!!!
 
// npm i babel-loader @babel/core @babel/preset-env -D
module.exports = {
  // 预设:扩展babel功能的插件
  // 常用预设
  // @babel/preset-env  智能预设,允许你使用最新的JavaScript,将es6+语法编译成es5之前,向后兼容
  // @babel/preset-react  用来编译react jsx语法的预设
  // @babel/preset-typescript  用来编译ts语法的预设
  presets: [
    [
      '@babel/preset-env',
      {
        // 自动分析引入es6+语法的部分,并按需加载
        // 不需要在import import 'core-js/es/x'了
        useBuiltIns: 'usage',
        corejs: 3, // 指定corejs版本
      },
    ],
  ],
};