# vue3+webpack5 实践

# 搭建思路

  1. 必要配置

    1. 相关包

      1. vue 相关的包:vue@next,@vue/compiler-sfc,@vue/eslint-config-standard,vue-router,vue-loader
      2. babel 编译相关:webpack5 已不在需要
      3. eslint 校验相关:babel-eslint,eslint,eslint-plugin-html,eslint-plugin-import,eslint-plugin-vue
      4. prettier 格式化相关:eslint-plugin-prettier,eslint-config-prettier,prettier
      5. 环境变量控制相关:cross-env
      6. webpack 配置相关:webpack,webpack-cli,webpack-dev-server,webpack-merge
      7. css 以及样式相关:sass,node-sass,sass-loader,style-loader
    2. 配置

      1. 基础配置
      console.log('读取公共配置')
      const webpack = require('webpack')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      const { VueLoaderPlugin } = require('vue-loader')
      const path = require('path')
      
      module.exports = {
        // 公共配置
        entry: './src/index.js',
        output: {
          filename: '[name].[contenthash].bundle.js',
          publicPath: '/',
          clean: true
        },
        plugins: [
          new HtmlWebpackPlugin({
            title: '商城首页',
            filename: 'index.html',
            template: './public/index.html'
          }),
          new VueLoaderPlugin(),
          new webpack.DefinePlugin({
            'process.env': {
              env: JSON.stringify(process.env)
            }
          })
        ],
        module: {
          rules: [
            {
              test: /.vue$/,
              use: {
                loader: 'vue-loader',
                options: {
                  extractCSS: true
                }
              }
            },
            {
              test: /.(png|svg|jpg|jpeg|gif|eot|svg|ttf|woff|woff2)$/,
              use: [
                {
                  loader: 'url-loader',
                  options: {
                    limit: 1024 * 100,
                    name: 'assets/images/[name].[hash].[ext]',
                    esModule: false
                  }
                }
              ],
              type: 'javascript/auto'
            }
          ]
        },
        resolve: {
          alias: {
            _utils: path.resolve('./src/utils'),
            _views: path.resolve('./src/views'),
            _assets: path.resolve('./src/assets'),
            _router: path.resolve('./src/router'),
            _store: path.resolve('./src/store'),
            _public: path.resolve('./public'),
            _api: path.resolve('./src/api')
          },
          extensions: ['.js', '.vue', '.json']
        }
      }
      
      1. 开发环境配置

        console.log('development 模式')
        const path = require('path')
        const { merge } = require('webpack-merge')
        const base = require('./webpack.config.base')
        const devConfig = {
          mode: 'development',
          devtool: 'cheap-module-source-map',
          devServer: {
            static: {
              directory: path.join(__dirname, '../dist')
            },
            compress: true,
            port: 3000,
            historyApiFallback: true,
            proxy: {
              '/shop': {
                target: 'http://192.2.121.51:9136'
              }
            }
          },
          module: {
            rules: [
              {
                test: /.css$/,
                use: [
                  'style-loader',
                  {
                    loader: 'css-loader',
                    options: {
                      url: true
                    }
                  }
                ]
              },
              {
                test: /.(scss|sass)$/,
                use: [
                  'style-loader',
                  {
                    loader: 'css-loader',
                    options: {
                      url: true
                    }
                  },
                  {
                    loader: 'px2rem-loader',
                    options: {
                      remUnit: 37.5,
                      remPrecesion: 8
                    }
                  },
                  'sass-loader'
                ]
              }
            ]
          }
        }
        module.exports = merge(base, { ...devConfig })
        
      2. 生产/测试环境配置

        const { merge } = require('webpack-merge')
        const TerserJSPlugin = require('terser-webpack-plugin')
        const MiniCssExtractPlugin = require('mini-css-extract-plugin')
        const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
        const base = require('./webpack.config.base')
        const prod = {
          mode: 'production',
          devtool: 'cheap-module-source-map',
          output: {
            filename: '[name].[contenthash].bundle.js',
            publicPath: '/h5/shop/dist/',
            clean: true
          },
          plugins: [
            new MiniCssExtractPlugin({
              filename: '[name].[hash].css',
              chunkFilename: '[id].css'
            })
          ],
          optimization: {
            removeEmptyChunks: true,
            minimizer: [new TerserJSPlugin({}), new CssMinimizerPlugin({})]
          },
          module: {
            rules: [
              {
                test: /.css$/,
                use: [
                  MiniCssExtractPlugin.loader,
                  {
                    loader: 'css-loader',
                    options: {
                      url: true
                    }
                  }
                ]
              },
              {
                test: /.(scss|sass)$/,
                use: [
                  MiniCssExtractPlugin.loader,
                  {
                    loader: 'css-loader',
                    options: {
                      url: true
                    }
                  },
                  {
                    loader: 'px2rem-loader',
                    options: {
                      remUnit: 37.5,
                      remPrecesion: 8
                    }
                  },
                  'sass-loader'
                ]
              }
            ]
          }
        }
        module.exports = merge(base, { ...prod })
        
  2. 优化打包体积

    1. 压缩混淆
      1. js 压缩混淆
        optimization: {
           removeEmptyChunks: true,
           minimizer: [new TerserJSPlugin({}), new CssMinimizerPlugin({})]
        }
        
        两个压缩文件的插件,分别对应 js 和 css 压缩
      2. css 压缩单独提取文件
        plugins: [
          new MiniCssExtractPlugin({
            filename: '[name].[hash].css',
            chunkFilename: '[id].css'
          })
        ]
        
  3. 优化打包速度

    1. 多线程打包
      使用 happyPack 做多线程打包,可以提升打包速度
    2. 强打包缓存
      无需配置,webpack 默认强缓存,如需配置,参见:缓存配置 (opens new window)

# 新语法

  1. 开发常用方法
    1. ref 文档 (opens new window)
    2. reactive 文档 (opens new window)
    3. toRefs 文档 (opens new window)
    4. getCurrentInstance 文档 (opens new window)
    5. 生命周期钩子方法
  2. setup
    1. 参见官方文档 (opens new window)
    2. 参数说明:
      1. Props,组件的 props 值
      2. context 对象,暴露了以前在 this 上暴露的属性,attrs,slots,emit
      3. 参数不能使用...展开,如使用展开操作,会使得参数丢失响应性

# 遇到的问题

  1. key 值不更新问题
    原因:key 值相同的情况下,vue 认为组件无需更新,无论数据是否变动,均不会重新渲染组件
    解决方案:在需要重排组件内容的时候,重排时使用时间戳+id+循环序号作为 key
  2. 双向绑定丢失响应
    1. 在 setup 内定义的属性,直接使用等于号赋值,无响应式
    2. 在 setup 内定义属性,值类型的使用 ref,赋值使用 xxx.value,即可使用响应式;高级内容,需要使用 reactive 方法包裹,return 时,如果展开需要使用 toRefs 方法包起来。