vue3+webpack5+ts+pinia (一、项目搭建)

601 阅读4分钟

webpack5配置

使用babel-loader编译ts配合fork-ts-checker-webpack-plugin完成类型检查

注意点:fork-ts-checker-webpack-plugin默认使用vue-template-compiler解析vue文件,需换成vue3.x对应的解析器@vue/compiler-sfc

css提取、压缩

注意点:需使用 mini-css-extract-pluginloader替换style-loader

vue-loader 编译打包.vue文件

注意点:webpack插件需要引入vue-loader提供的的插件VueLoaderPlugin

引入代码格式校验插件 eslint-webpack-pluginstylelint-webpack-plugin,并开启自动修复功能

注意点: 使用eslint-webpack-plugin代替之前的eslint-loader

使用资源模块asset/resource支持图片、mp4、和其他文件得导入

注意:webpack5使用:

  • asset/source代替raw-loader 将文件导入为字符串
  • asset/inline代替url-loader 将文件作为 data URI 内联到 bundle 中
  • asset/resource代替file-loader将文件发送到输出目录
  • asset 在导出为dateURL(即base64)和一个单独文件之间选择,与以前得url-loader通过文件体积大小决定打包为base64还是单独文件功能一样

具体配置文件

// build/webpack.base.config.js
/*
 * @Description:
 * @Version: 2.0
 * @Date: 2021-05-14 14:37:45
 * @LastEditTime: 2022-07-01 13:43:56
 */

const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const StylelintPlugin = require('stylelint-webpack-plugin');
const chalk = require('chalk');
const ESLintPlugin = require('eslint-webpack-plugin');
// const webpack = require('webpack')
const path = require('path')
const devMode = process.env.ENV_MODE === 'dev';
const { resolve } = require('./utils');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
// const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const compileConf = require('../config')

module.exports = {
  entry: {
    app: './src/main.ts',
  },
  performance: {
    hints: false,
  },
  resolve: {
    alias: {
      '@': resolve('../src'),
      'assets': resolve('../src/assets'),
      '~utils': resolve('../src/utils'),
      '~views': resolve('../src/views'),
      '~services': resolve('../src/services'),
      '~components': resolve('../src/components'),
      '~composition': resolve('../src/composition'),
      '~models': resolve('../src/models'),
    },
    extensions: ['.ts', '.js', '.tsx', '.vue', '.json','.d.ts'],
  },
  module: {
    rules: [
      {
        test: /\.(js|ts|tsx|jsx)$/,
        include: resolve('../src'),
        use: [
          'thread-loader',
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
            },
          },
        ],
      },
      {
        test: /\.vue$/,
        use: ['thread-loader', 'vue-loader'],
        include: resolve('../src'),
      },
      {
       oneOf:[
          {
            test: /\.css$/,
            use: [devMode?"style-loader":MiniCssExtractPlugin.loader, 'css-loader'],
          },
          {
            test: /\.(less)$/i,
            use: [
              devMode? "style-loader":MiniCssExtractPlugin.loader,
              'css-loader',
              {
                loader: "less-loader",
                options: {
                  lessOptions:{
                    javascriptEnabled: true
                  }
                }
              },
              'postcss-loader',
            ],
          },
          {
            test: /\.(scss)$/i,
            use: [
              devMode? "style-loader":MiniCssExtractPlugin.loader,
              'css-loader',
              'sass-loader',
              {
                loader: 'sass-resources-loader',
                options: {
                    resources: [
                        path.resolve(__dirname, '../src/assets/css/variable.scss'),
                    ]
                }
            },
              'postcss-loader',
            ],
          },
          {
            test: /\.(png|jpg|gif|svg)$/,
            type: 'asset',
            parser: {
              dataUrlCondition: {
                maxSize: 4 * 1024, // 4kb
              },
            },
            generator: {
              filename: 'imgs/[name].[contenthash:6][ext]',
            },
          },
          {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            type: 'asset/resource',
            generator: {
              filename: 'fonts/[name].[contenthash:6][ext]',
            },
          },
          {
            test: /\.(mp4)$/i,
            type: 'asset/resource',
            generator: {
              filename: 'videos/[name].[contenthash:6][ext]',
            },
          },
       ],
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: resolve('../public/index.ejs'),
    }),
    new VueLoaderPlugin(),
    new CopyPlugin({
      patterns: [
        { from: "static", to: "static" },
      ],
    }),
    new ESLintPlugin({
      formatter: require('eslint-friendly-formatter'),
      extensions: ['js', 'ts', 'vue', 'tsx'],
      fix: true,
    }),
    new ProgressBarPlugin({
      format: `:msg ${chalk.yellow('[:bar]')} ${chalk.green.bold(':percent')} (:elapsed seconds)`,
      width: 250,
      clear: false,
    }),
    new StylelintPlugin({
      fix: true,
      files: ['src/**/*.vue', 'src/assets/css/*.(css|scss|less)']
    }),
    new ForkTsCheckerWebpackPlugin({
      async: false, // webpack是否等待typescript类型检查的结果
      typescript:{
        extensions:{
          vue:{
            enabled:true,
            compiler:'@vue/compiler-sfc' // 需指定vue单文件目标解析器
          },
          
        }
      }
    }),
    new webpack.DefinePlugin({
      compileConf:JSON.stringify(compileConf),
    }),
  ],
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },
};

开发环境配置:

  • source-map选择eval-cheap-module-source-map,关闭构建体积优化,提高打包速度

注意:

  • 设置target属性为web不然热更新会失效
  • devServer->host属性设置0.0.0.0否则不能通过ip访问.
  • 需要通过webpack.DefinePlugin设置全局变量__VUE_OPTIONS_API____VUE_PROD_DEVTOOLS__true告诉vue当前得环境,否则vue会抛出警告
// build/webpack.dev.conf.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.conf');
const { resolve } = require('./utils');
const isMock = process.env.MOCK
const { addOnProxyRes, use } = require('@gc082/mock')
const proxyTable = require('@gc082/dog-cli/config/proxyTable.json')
const { assemblyProxyTable } = require('../config/utils')
const webpack = require('webpack')
const compileConf = require('../config')
module.exports = merge(baseConfig, {
    mode: 'development',
    target: 'web', // 不加会热更新失效
    devtool: 'eval-cheap-module-source-map',
    stats: 'errors-warnings',
    output: {
        filename: '[name].js',
        path: resolve(`../${compileConf.projectName}`),
        publicPath: compileConf.publicPath
    },
    optimization: {
        runtimeChunk: true,
        removeAvailableModules: false,
        removeEmptyChunks: false,
        splitChunks: false,
    },
    devServer: {
        // contentBase: resolve('../dist'),
        hot: true,
        hotOnly: true,
        // compress: true,
        host: '0.0.0.0',
        port: 9000,
        before: function (app, server, compiler) {
            if (isMock) {
                use('/smartPark', app)
                use('/park-system', app)
                use('/smartcommunitySit', app)
            }
        },
        proxy: assemblyProxyTable(proxyTable, addOnProxyRes)

    },
    plugins: [
        new webpack.DefinePlugin({
            'ENV_MODE': JSON.stringify(process.env.ENV_MODE),
            '__VUE_OPTIONS_API__': true,
            '__VUE_PROD_DEVTOOLS__':true
        }),
    ],
});

生产环境配置

  • source-map使用source-map
  • 打包输出文件名称添加文件指纹filename: 'js/[name].[contenthash].js',
  • 使用terser-webpack-plugin压缩优化js代码,剔除代码中的debugerconsole
  • 使用css-minimizer-webpack-plugin压缩优化css
  • 使用mini-css-extract-plugin将vue文件中的css代码提取到单独的文件中
  • 添加打包分性能析插件webpack-bundle-analyzer,打包时添加 --report命令,即可打开打包分析页面
// build/webpack.prod.conf.js
/*
 * @Description: 
 * @Version: 2.0
 * @Date: 2021-05-14 14:38:13
 * @LastEditTime: 2022-06-09 17:35:39
 */
const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const baseConfig = require('./webpack.base.conf')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); // 不支持es6
const TerserPlugin = require("terser-webpack-plugin");

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const compileConf = require('../config')
const { resolve } = require('./utils')
const config = merge(baseConfig, {
    mode: 'production',
    devtool: 'source-map',
    stats: 'errors-only',
    output: {
        filename: 'js/[name].[contenthash].js',
        path: resolve(`../${compileConf.projectName}`),
        publicPath: compileConf.publicPath
    },
    optimization: {
        emitOnErrors: true,
        minimizer: [
            new CssMinimizerPlugin(),
            new TerserPlugin({
                terserOptions:{
                    compress:{
                        drop_console: process.env.ENV_MODE==='prod',
                        drop_debugger: true,
                        pure_funcs: process.env.ENV_MODE==='prod'?['console.log']:[],
                    },
                    keep_fnames: true,
                    warnings:!process.env.ENV_MODE==='prod',
                },
                parallel: true,
            }),
        ],
        splitChunks:{
            minSize: 20000,
            maxSize: 500*1024,
            automaticNameDelimiter:"~",
            chunks: "all", 
        }
    },
    plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: 'css/[name].[contenthash].css',
            chunkFilename: 'css/[id].[contenthash].css',
          }),
    ]
})


if (process.env.npm_config_report) config.plugins.push(new BundleAnalyzerPlugin())

module.exports = config

打包优化

  • thread-loader开启多进程
  • 开启webpack5自带的缓存功能,第一次会在node_models/.cache/webpack中写入缓存,后续打包速度会有显著提升
// 在buidl/webpack.base.conf.js 中添加:

...
  cache: {
    // 1. 将缓存类型设置为文件系统
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },
...

  • 使用webpack.DefinePlugin配合add-asset-html-webpack-plugin预打包第三方包,提升打包速度

    1. 配置webpack.dll.js
    // build/webpack.dll.js
    const path = require('path');
    const webpack = require('webpack');
    const {CleanWebpackPlugin} = require('clean-webpack-plugin')
    // const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
    config = {
      mode: 'production',
      entry: {
        "common":['js-sha256', 'dayjs'],
        'vendor': ['vue','vue-router','pinia','axios',]
      },
      output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]'
      },
      plugins: [
        new CleanWebpackPlugin(),
        new webpack.DllPlugin({
          name: '[name]',
          path: path.resolve(__dirname, '../dll/[name].manifest.json')
        }),
        // new AntdDayjsWebpackPlugin({
        //   preset: 'antdv3'
        // })
      ]
    }
    
    if (process.env.npm_config_report) config.plugins.push(new BundleAnalyzerPlugin())
    
    module.exports = config
    
    1. package.json中添加"dll": "webpack --config ./build/webpack.dll.js",
    2. 执行npm run dll打包出第三方模块
    3. 动态读取打包出好的文件,使用add-asset-html-webpack-pluginindex.html中引入
    // build/utils.js
    const path = require('path')
    const AddAssetHtmlWebpackPlugin  = require('add-asset-html-webpack-plugin')
    const fs = require('fs')
    const webpack  = require('webpack')
    const compileConf = require('../config')
    const resolve = (url) => {
        return path.resolve(__dirname, url)
    }
    
    const getDllPlugins = ()=>{
        const plugins = []
        const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
        files.forEach(file => {
            if(/.*\.dll.js/.test(file)) {
              plugins.push(new AddAssetHtmlWebpackPlugin({
                filepath: path.resolve(__dirname, '../dll', file),
                outputPath:'/js',
                publicPath:`${compileConf.publicPath}js`
              }))
            }
            if(/.*\.manifest.json/.test(file)) {
              plugins.push(new webpack.DllReferencePlugin({
                manifest: path.resolve(__dirname, '../dll', file)
              }))
            }
        })
        return plugins
    }
    
    module.exports = {
        resolve,
        getDllPlugins
    }
    
    1. webpack.prod.conf添加引入
    
    ... //省略
    
    ++ const { getDllPlugins, resolve } = require('./utils') 
    
    ... //省略
    const config = merge(baseConfig, {
          ... //省略
        plugins: [
            new CleanWebpackPlugin(),
            new MiniCssExtractPlugin({
                filename: 'css/[name].[contenthash].css',
                chunkFilename: 'css/[id].[contenthash].css',
              }),
              
           ++ ...getDllPlugins(),
           
        ]
    })
     ... //省略
    

    bable配置

    注意: 解释器使用@babel/preset-typescript,并设置allExtensions属性为true,否则不能识别script lang=ts

    // babel.config.js
        module.exports = {
          presets: [
            '@babel/preset-env',
            [
              '@babel/preset-typescript', {
                allExtensions: true, // 不加识别不了script lang=ts
              },
            ],
          ],
          plugins: [
            [
              '@babel/plugin-transform-runtime',
              {
                corejs: 2, // 指定 runtime-corejs 的版本,目前有 2 3 两个版本
              },
            ],
          ],
    };