Skip to content

最终配置

base

  • webpack.base.js
js
// webpack基础配置
// 1. 入口文件
// 2. 出口文件
// 3. loader
// 4. plugins

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
const fs = require("fs");

const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
  throw new Error(
    "The NODE_ENV environment variable is required but was not specified."
  );
}

const dotenvFiles = [
  `.env`,
  `.env.${NODE_ENV}`,
  `.env.local`,
  `.env.${NODE_ENV}.local`,
].filter(Boolean);

dotenvFiles.forEach((dotenvFile) => {
  if (fs.existsSync(dotenvFile)) {
    require("dotenv").config({
      path: dotenvFile,
    });
  }
});

const isProduction = process.env.NODE_ENV === "production";

const getStyleLoaders = (preProcessor) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
    "css-loader",
    "postcss-loader",
    preProcessor,
  ].filter(Boolean);
};

/**
 * @type {import('webpack').Configuration}
 */
module.exports = {
  entry: path.resolve(__dirname, "../src/main.ts"),
  output: {
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
    filename: "js/[name].[contenthash:6].js", // 打包后的文件名
    chunkFilename: "js/[name].[contenthash:8].js",
    clean: true, // 清除上一次打包的文件
    publicPath: "/", // 打包后的资源的访问路径前缀
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src"), // @ 代表 src 路径
      vue$: "vue/dist/vue.runtime.esm-bundler.js",
    },
    // 引入文件的时候不需要添加后缀,这个配置也会稍微的提升构建速度
    extensions: [".js", ".ts", ".vue", ".json"],
  },
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      title: process.env.VUE_APP_TITLE,
      BASE_URL: process.env.BASE_URL,
    }),
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
          toType: "dir",
          globOptions: {
            ignore: ["**/.DS_Store", "**/index.html"],
          },
          info: {
            minimized: true,
          },
        },
      ],
    }),
    new VueLoaderPlugin(), // vue-loader插件
    new webpack.DefinePlugin({
      "process.env.VUE_APP_API_URL": JSON.stringify(
        process.env.VUE_APP_API_URL
      ),
      "process.env.BASE_URL": JSON.stringify(process.env.BASE_URL),
      "process.env.CURRENT_ENV": JSON.stringify(process.env.CURRENT_ENV),
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false,
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:6].css",
    }),
    new ESLintPlugin({
      extensions: ["js", "ts", "vue", "jsx", "tsx"],
      exclude: "node_modules",
      context: path.resolve(__dirname, "../src"),
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
    }),
  ],
  module: {
    rules: [
      {
        test: /\.m?jsx?$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /\.vue$/,
        use: "vue-loader",
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "ts-loader",
            options: {
              transpileOnly: true, // 关闭类型检测,即只进行转译
              appendTsSuffixTo: ["\\.vue$"], // 给vue文件添加ts后缀
            },
          },
        ],
      },
      {
        test: /\.(png|jpe?g|gif|webp|avif)(\?.*)?$/,
        type: "asset", // webpack5通用资源处理模块,默认8kb以下的资源会被转换为base64
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 10kb以下的资源会被转换为base64
          },
        },
        generator: {
          filename: "img/[name].[contenthash:6][ext]", //文件打包输出目录
        },
      },
      {
        test: /\.(svg)(\?.*)?$/,
        type: "asset/resource", // webpack5通用资源处理模块,默认会导出出单独的文件
        generator: {
          filename: "img/[name].[contenthash:6][ext]", //文件打包输出目录
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 2 * 1024, // 2kb以下的资源会被转换为base64
          },
        },
        generator: {
          filename: "fonts/[name].[contenthash:6][ext]", //文件打包输出目录
        },
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 20 * 1024, // 20kb以下的资源会被转换为base64
          },
        },
        generator: {
          filename: "media/[name].[contenthash:6][ext]", //文件打包输出目录
        },
      },
      {
        oneOf: [
          {
            test: /\.css$/i,
            exclude: [/\.module\.css$/],
            use: getStyleLoaders(),
          },
          {
            test: /\.module\.css$/i,
            use: [
              isProduction ? MiniCssExtractPlugin.loader : "style-loader",
              {
                loader: "css-loader",
                options: {
                  modules: {
                    localIdentName: "[name]__[local]__[hash:base64:5]",
                  },
                },
              },
              {
                loader: "postcss-loader",
              },
            ],
          },
          {
            test: /\.s[ac]ss$/i,
            use: [
              ...getStyleLoaders("sass-loader"),
              {
                loader: "style-resources-loader",
                options: {
                  patterns: [
                    path.resolve(
                      __dirname,
                      "../src/assets/styles/variables.scss"
                    ),
                  ],
                },
              },
            ],
          },
          {
            test: /\.less$/i,
            use: getStyleLoaders("less-loader"),
          },
          {
            test: /\.styl$/i,
            use: getStyleLoaders("stylus-loader"),
          },
        ],
      },
      // {
      //   test: /\.css$/i,
      //   use: [
      //     MiniCssExtractPlugin.loader, // 代替style-loader,提取css到单独的文件中
      //     'css-loader'
      //   ]
      // },
      // {
      //   test: /\.s[ac]ss$/i,
      //   use: [
      //     MiniCssExtractPlugin.loader,
      //     'css-loader', 'sass-loader'
      //   ]
      // },
      // {
      //   test: /\.less$/i,
      //   use: [
      //     MiniCssExtractPlugin.loader,
      //     'css-loader', 'less-loader'
      //   ]
      // }
    ],
  },
};

dev

  • webpack.dev.js
js
const baseConfig = require("./webpack.base.js");
const { merge } = require("webpack-merge");

/**
 * @type {import('webpack').Configuration}
 */
const devConfig = {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: process.env.VUE_APP_PORT || 8080,
    open: JSON.parse(process.env.VUE_APP_OPEN),
    historyApiFallback: true, // 该选项的作用所有的404都连接到index.html
    // hot:true // 模块热替换在webpack5中默认开启
    setupMiddlewares: require("../mock"),
  },
  plugins: [
    // new webpack.HotModuleReplacementPlugin() // 模块热替换插件
  ],
};

module.exports = merge(baseConfig, devConfig);

prod

  • webpack.prod.js
js
const baseConfig = require("./webpack.base.js");
const { merge } = require("webpack-merge");
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

/**
 * @type {import('webpack').Configuration}
 */
const prodConfig = {
  mode: "production",
  plugins: [new BundleAnalyzerPlugin()],
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin({
        parallel: true, // 开启多进程并行压缩
        terserOptions: {
          format: {
            comments: false, // 去除注释
          },
          compress: {
            drop_console: true, // 去除console.log
            drop_debugger: true, // 去除debugger
            pure_funcs: ["console.log", "console.info", "console.error"], // 配置发布时,不被打包的函数
          },
        },
        extractComments: false, // 不将注释提取到单独的文件中
      }),
    ],
    chunkIds: "named",
    runtimeChunk: "single",
    splitChunks: {
      chunks: "all", //initial、async和all
      // minSize: 30000, // 形成一个新代码块最小的体积
      // minChunks: 1, // 在分割之前,这个代码块最小应该被引用的次数
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          priority: 10,
          reuseExistingChunk: true,
        },
        echarts: {
          test: /[\\/]node_modules[\\/]echarts|zrender(.*)/,
          name: "chunk-echarts",
          priority: 25,
          reuseExistingChunk: true,
        },
        element: {
          test: /[\\/]node_modules[\\/]element(.*)/,
          name: "chunk-element",
          priority: 25,
          reuseExistingChunk: true,
        },
        commons: {
          name: "chunk-commons",
          minChunks: 2,
          priority: 5,
          minSize: 0,
          reuseExistingChunk: true,
        },
        lib: {
          test(module) {
            return (
              module.size() > 160000 &&
              module.nameForCondition() &&
              module.nameForCondition().includes("node_modules")
            );
          },
          name: "chunk-lib",
          priority: 20,
          minChunks: 1,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

module.exports = merge(baseConfig, prodConfig);

babel.config

  • babel.config.js
js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage", // 按需引入 polyfill
        corejs: 3,
      },
    ],
    [
      "@babel/preset-typescript",
      {
        allExtensions: true,
      },
    ],
  ],
  // plugins: [
  //   [
  //     "@babel/plugin-transform-runtime",
  //     {
  //       corejs: 3
  //     }
  //   ]
  // ]
};

mock

  • mock/index.js
js
const Mock = require("mockjs"); //mockjs 导入依赖模块
const util = require("./util"); //自定义工具模块
//返回一个函数
module.exports = function (middlewares, devServer) {
  //监听http请求
  devServer.app.get("/api/users/list", function (_rep, res) {
    //每次响应请求时读取mock data的json文件
    //util.getJsonFile方法定义了如何读取json文件并解析成数据对象
    var json = util.getJsonFile("./userInfo.json");
    //将json传入 Mock.mock 方法中,生成的数据返回给浏览器
    res.json(Mock.mock(json));
  });
  return middlewares;
};
  • mock/userinfo.json
json
{
  "code": 200,
  "data|20-30": [
    {
      "_id": "@guid",
      "loginId": "@string(2,6)",
      "loginPwd": "@word(3,5)",
      "name": "@cname",
      "age|18-45": 1,
      "loves": "@pick(['电影','阅读','旅游','运动','游戏'],1,3)",
      "address": {
        "province": "@province",
        "city": "@city"
      }
    }
  ],
  "msg": "成功"
}
  • mock/util.js
js
const fs = require("fs"); //引入文件系统模块
const path = require("path"); //引入path模块

module.exports = {
  //读取json文件
  getJsonFile: function (filePath) {
    //读取指定json文件
    var json = fs.readFileSync(path.resolve(__dirname, filePath), "utf-8");
    //解析并返回
    return JSON.parse(json);
  },
};