vue-cli 环境配置

127 阅读6分钟

开发环境配置

package.json
{
  "name": "vue-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/eslint-parser": "^7.19.1",
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^9.1.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "eslint-plugin-vue": "^9.8.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "less-loader": "^11.1.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "sass-loader": "^13.2.0",
    "stylus-loader": "^7.1.0",
    "vue-loader": "^17.0.1",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  }
}

config/webpack.dev.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader'); // vue-loader
const { DefinePlugin } = require('webpack'); // DefinePlugin用来定义环境变量给代码使用

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    "vue-style-loader", // 处理样式需要用vue-style-loader
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: undefined,
    filename: 'static/js/[name].js',
    chunkFilename: 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.js?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
        }
      },
      // 解析vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    new VueLoaderPlugin(), // 编译vue
    // cross-env定义的环境变量时给webpack使用
    // DefinePlugin 定义环境变量给源代码使用,从而解决vue3页面相关警告
    new DefinePlugin({
      __VUE_OPTIONS_API__: true, // 打开options api
      __VUE_PROD_DEVTOOLS__: false, // 开发环境不开启devtool
    })
  ],
  // 模式
  mode: 'development',
  // SourceMap
  devtool: 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    }
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.vue', '.js', '.json']
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
}
.eslintrc.js
module.exports = {
  root: true, // 跟目录是当前
  env: {
    node: true // 启用node环境变量
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended"], // 继承vue/vue3的官方规则、以及eslint的官方规则
  parserOptions: {
    parser: "@babel/eslint-parser"
  }
}
babel.config.js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"]
}
src/main.js
import { createApp } from 'vue'
import App from './App'
import router from './router'

createApp(App).use(router).mount(document.getElementById('app'));
src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const Home = () => import('../views/Home');
const About = () => import('../views/About');

export default createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]
})

生产环境配置

package.json
{
  "name": "vue-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/eslint-parser": "^7.19.1",
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^9.1.0",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-plugin-vue": "^9.8.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.8.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "sass-loader": "^13.2.0",
    "stylus-loader": "^7.1.0",
    "vue-loader": "^17.0.1",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  }
}

config/webpack.prod.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件
const { VueLoaderPlugin } = require('vue-loader'); // vue-loader
const { DefinePlugin } = require('webpack'); // DefinePlugin用来定义环境变量给代码使用

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    MiniCssExtractPlugin.loader, // 提取css为单独文件
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'static/js/[name].[contenthash:10].js',
    chunkFilename: 'static/js/[name].[contenthash:10].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.js?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          // plugins: ['react-refresh/babel'], // 激活js的HMR 生产模式没HMR功能
        }
      },
      // 解析vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
    new VueLoaderPlugin(), // 编译vue
    // cross-env定义的环境变量时给webpack使用
    // DefinePlugin 定义环境变量给源代码使用,从而解决vue3页面相关警告
    new DefinePlugin({
      __VUE_OPTIONS_API__: true, // 打开options api
      __VUE_PROD_DEVTOOLS__: false, // 开发环境不开启devtool
    })
  ],
  // 模式
  mode: 'production',
  // SourceMap
  devtool: 'source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中)
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }
                  ]
                }
              ]
            ]
          }
        }
      }),
    ]
  },
  // webpack解析模块加载选项(文件拓展名)
  resolve: {
    extensions: ['.vue', '.js', '.json']
  },
}

开发环境和生产环境合并配置

package.json
{
  "name": "vue-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/eslint-parser": "^7.19.1",
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^9.1.0",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-plugin-vue": "^9.8.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.8.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "sass-loader": "^13.2.0",
    "stylus-loader": "^7.1.0",
    "vue-loader": "^17.0.1",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  }
}

config/webpack.config.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件
const { VueLoaderPlugin } = require('vue-loader'); // vue-loader
const { DefinePlugin } = require('webpack'); // DefinePlugin用来定义环境变量给代码使用
// 是否是生产环境
const isProduction = process.env.NODE_ENV === 'production'; 
// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
    filename: isProduction ? 'static/js/[name].[contenthash:10].js' : 'static/js/[name].js',
    chunkFilename: isProduction ? 'static/js/[name].[contenthash:10].chunk.js' : 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.js?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          // plugins: ['react-refresh/babel'], // 激活js的HMR 生产模式没HMR功能
        }
      },
      // 解析vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    isProduction && new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    isProduction && new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
    new VueLoaderPlugin(), // 编译vue
    // cross-env定义的环境变量时给webpack使用
    // DefinePlugin 定义环境变量给源代码使用,从而解决vue3页面相关警告
    new DefinePlugin({
      __VUE_OPTIONS_API__: true, // 打开options api
      __VUE_PROD_DEVTOOLS__: false, // 开发环境不开启devtool
    })
  ].filter(Boolean),
  // 模式
  mode: isProduction ? 'production' : 'development',
  // SourceMap
  devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 其它的都用默认值即可
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimize: isProduction, // 是否需要压缩
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中)
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }
                  ]
                }
              ]
            ]
          }
        }
      }),
    ]
  },
  // webpack解析模块加载选项(文件拓展名)
  resolve: {
    extensions: ['.vue', '.js', '.json']
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
}

vue cli优化配置

1、vue中常用的ui库是 element-plus。有全部引入和按需引入两种方式

安装element-plus

npm i element-plus

全局引入:

import { createApp } from 'vue'
import App from './App'
import router from './router'
// 全局引入element-plus
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'

createApp(App).use(router).use(ElementPlus).mount(document.getElementById('app'));

按需引入:element-plus.org/zh-CN/guide…

按需引入不需要在main.js中引入所有的组件和样式,在组件内用哪个组件,引入注册就行

2、自定义主题

文档:element-plus.org/zh-CN/guide…
原理是定义scss变量去覆盖原有的element-plus中的变量

创建一个样式文件,定义变量
src/styles/element/index.scss

// styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': green,
    ),
  ),
);

引入这个文件有两种方式:1:手动引入, 2:自动引入
1、手动引入
在main.js中,在element-plus引入后引入此样式即可
2、自动引入

现在不需要安装node-sass,安装sass即可

// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre && {
      loader: pre,
      options: pre === 'sass-loader' ? {
        additionalData: `@use "@/styles/element/index.scss" as *;`, // element-plus自定义主题引入scss文件
      } : {}
    }
  ].filter(Boolean);
}

plugins中的变动
Components({
      resolvers: [ElementPlusResolver({
        importStyle: "sass", // 告知将来会引入sass文件(element-plus自定义主题,引入sass)
      })],
    }),
3、分包

将vue相关的单独打包、element-plus单独打包、剩余的单独打包

optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 分开打包
      cacheGroups: {
        vue: { // vue相关的分到一个包
          test: /[\\/]node_modules[\\/]vue(.*)?/,
          name: 'vue-chunk', // 打包名字
          priority: 40, // 权重设置为40 (目的是让权重大于node_modules的权重,否则会打到node_modules包中)
        },
        elementPlus: { // element-plus打包到一个包
          test: /[\\/]node_modules[\\/]element-plus[\\/]/,
          name: 'elementPlus-chunk',
          priority: 30,
        },
        libs: { // 剩余的打到一个包
          test: /[\\/]node_modules[\\/]/,
          name: 'libs-chunk',
          priority: 20,
        },
      }
    },
  },
4、关闭性能分析、提升打包速度
performance: false, // 关闭性能分析,提上打包速度
5、vue-loader开启缓存,提升第二次以后打包速度
// 解析vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          // 开启缓存(提升第二次以后打包速度)
          cacheDirectory: path.resolve(__dirname, '../node_modules/.cache/vue-loader')
        }
      },

优化后的总体配置

package.json
{
  "name": "vue-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/eslint-parser": "^7.19.1",
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^9.1.0",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-plugin-vue": "^9.8.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "sass-loader": "^13.2.0",
    "stylus-loader": "^7.1.0",
    "unplugin-auto-import": "^0.12.0",
    "unplugin-vue-components": "^0.22.11",
    "vue-loader": "^17.0.1",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "element-plus": "^2.2.25",
    "sass": "^1.56.1",
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  }
}

webpack.config.js
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css为单独文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // css压缩
const TerserWebpackPlugin = require('terser-webpack-plugin'); // 内置(压缩js)
// const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 图片压缩
const CopyPlugin = require("copy-webpack-plugin"); // 复制插件
const { VueLoaderPlugin } = require('vue-loader'); // vue-loader
const { DefinePlugin } = require('webpack'); // DefinePlugin用来定义环境变量给代码使用
// element-plus按需引入
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
// 是否是生产环境
const isProduction = process.env.NODE_ENV === 'production'; 
// 返回处理样式loader的函数
const getStyleLoaders = (pre) => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    "css-loader",
    {
      // 处理浏览器兼容性问题
      // 需要配合package.json中的browserslist来指定兼容性处理到什么程度
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins:['postcss-preset-env']
        }
      }
    },
    pre && {
      loader: pre,
      options: pre === 'sass-loader' ? {
        additionalData: `@use "@/styles/element/index.scss" as *;`, // element-plus自定义主题引入scss文件
      } : {}
    }
  ].filter(Boolean);
}
module.exports = {
  entry: './src/main.js',
  output: {
    path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
    filename: isProduction ? 'static/js/[name].[contenthash:10].js' : 'static/js/[name].js',
    chunkFilename: isProduction ? 'static/js/[name].[contenthash:10].chunk.js' : 'static/js/[name].chunk.js',
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // 处理css
      {
        test: /\.css$/i,
        use: getStyleLoaders()
      },
      {
        test: /\.less$/i,
        use: getStyleLoaders('less-loader')
      },
      {
        test: /\.s[ac]ss$/i,
        use: getStyleLoaders('sass-loader')
      },
      {
        test: /\.styl$/i,
        use: getStyleLoaders('stylus-loader')
      },
      // 处理图片
      {
        test: /.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            // 小于10kb的图片会转base64
            maxSize: 10 * 1024 // 10kb
          }
        },
      },
      // 处理其他资源
      {
        test: /.(ttf|woff2?|mp3|mp4|avi)$/, 
        type: "asset/resource"
      },
      // 处理js
      // 配置babel
      {
        test: /\.js?$/,
        include: path.resolve(__dirname, '../src'), // 只处理src
        loader: 'babel-loader',
        options: {
          cacheDirectory: true, // 开启babel缓存
          cacheCompression: false, // 关闭缓存文件压缩
          // plugins: ['react-refresh/babel'], // 激活js的HMR 生产模式没HMR功能
        }
      },
      // 解析vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          // 开启缓存(提升第二次打包速度)
          cacheDirectory: path.resolve(__dirname, '../node_modules/.cache/vue-loader')
        }
      },
    ]
  },
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, '../src'),
      exclude: ['node_modules'],
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), // eslint缓存目录
    }),
    // 处理html
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    isProduction && new MiniCssExtractPlugin({ // 提取css为单独文件
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
    }),
    isProduction && new CopyPlugin({
      patterns: [
        { // 把public下的文件复制到dist目录
          from: path.resolve(__dirname, '../public'),
          to: path.resolve(__dirname, '../dist'),
          globOptions: {
            ignore: ["**/index.html"], // 忽略public中的index.html
          },
        },
      ],
    }),
    new VueLoaderPlugin(), // 编译vue
    // cross-env定义的环境变量时给webpack使用
    // DefinePlugin 定义环境变量给源代码使用,从而解决vue3页面相关警告
    new DefinePlugin({
      __VUE_OPTIONS_API__: true, // 打开options api
      __VUE_PROD_DEVTOOLS__: false, // 开发环境不开启devtool
    }),
    // element-plus按需导入
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver({
        importStyle: "sass", // 告知将来会引入sass文件(element-plus自定义主题,引入sass)
      })],
    }),
  ].filter(Boolean),
  // 模式
  mode: isProduction ? 'production' : 'development',
  // SourceMap
  devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      // 分开打包
      cacheGroups: {
        vue: { // vue相关的分到一个包
          test: /[\\/]node_modules[\\/]vue(.*)?/,
          name: 'vue-chunk', // 打包名字
          priority: 40, // 权重设置为40 (目的是让权重大于node_modules的权重,否则会打到node_modules包中)
        },
        elementPlus: { // element-plus打包到一个包
          test: /[\\/]node_modules[\\/]element-plus[\\/]/,
          name: 'elementPlus-chunk',
          priority: 30,
        },
        libs: { // 剩余的打到一个包
          test: /[\\/]node_modules[\\/]/,
          name: 'libs-chunk',
          priority: 20,
        },
      }
    },
    // runtime文件来保存文件的hash值
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}.js`
    },
    minimize: isProduction, // 是否需要压缩
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      new TerserWebpackPlugin(), // 压缩js
      // 压缩图片(也可以放到plugin配置中) 依赖难下 image-minimizer-webpack-plugin、imagemin-gifsicle、imagemin-jpegtran、imagemin-optipng、imagemin-svgo
      // new ImageMinimizerPlugin({
      //   minimizer: {
      //     implementation: ImageMinimizerPlugin.imageminGenerate,
      //     options: {
      //       plugins: [
      //         ["gifsicle", { interlaced: true }],
      //         ["jpegtran", { progressive: true }],
      //         ["optipng", { optimizationLevel: 5 }],
      //         [
      //           "svgo",
      //           {
      //             plugins: [
      //               "preset-default",
      //               "prefixIds",
      //               {
      //                 name: "sortAttrs",
      //                 params: {
      //                   xmlnsOrder: "alphabetical"
      //                 }
      //               }
      //             ]
      //           }
      //         ]
      //       ]
      //     }
      //   }
      // }),
    ]
  },
  // webpack解析模块加载选项
  resolve: {
    extensions: ['.vue', '.js', '.json'], // 文件拓展名
    alias: { // 路径别名
      '@': path.resolve(__dirname, '../src')
    }
  },
  devServer: {
    host: 'localhost', // 启动服务器的域名
    port: 4000, // 启动服务器端口号
    open: true,
    hot: true, // 默认开启
    historyApiFallback: true, // 解决前端路由刷新404问题
  },
  performance: false, // 关闭性能分析,提上打包速度
}