使用webpack5 搭建 Vue3 + typescript 项目

3,147 阅读6分钟

项目技术栈

  • 构建工具:webpack @5.63.0
  • 编程语言: TypeScript @4.4.4
  • 前端框架:vue @3.2.23
  • CSS预编译:sass
  • 请求工具:axios
  • 代码规范:eslint+prettier+stylelint

项目搭建

初始化项目

首先新建一个空文件夹,在该文件夹下执行

npm init -y

webpack相关依赖安装

npm i webpack webpack-cli webpack-merge webpack-dev-server -D

vue相关依赖安装

npm i vue@next vue-router@next vuex@next

这里笔者安装完成的版本分别为:

  • vue : "3.2.23"
  • vue-router : "4.0.12"
  • vuex : "4.0.2"

TS相关依赖安装

npm i typescript -D

并创建tsconfig.json

{
  "compilerOptions": {
    "target": "es5", // 指定编译后的ECMAScript目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'
    "module": "esnext", // 用来指定要使用的模块标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'
    "strict": true, // 启用所有严格类型检查选项。
    "jsx": "preserve", // 指定jsx代码用于的开发环境: 'preserve', 'react-native', or 'react'
    "importHelpers": true, // 从 tslib 导入辅助工具函数(比如 __extends, __rest等)
    "moduleResolution": "node", // 用于选择模块解析策略,有'node'和'classic'两种类型'
    "experimentalDecorators": true, // 模块名到基于 baseUrl的路径映射的列表。
    "skipLibCheck": true, // 忽略所有的声明文件( *.d.ts)的类型检查。
    "esModuleInterop": true, // 支持在 CommonJs 模块下使用 import d from 'cjs', 解决TypeScript 对于 CommonJs/AMD/UMD 模块与 ES6 模块处理方式相同导致的问题
    "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。
    "sourceMap": true, // 生成相应的 .map文件。
    "baseUrl": ".", // 解析非相对模块名的基准目录, 相对模块不会受baseUrl的影响
    "paths": {
      // 用于设置模块名称到基于baseUrl的路径映射
      "@/*": ["src/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"] // lib用于指定要包含在编译中的库文件
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "types/**/*.d.ts",
    "types/*.d.ts"
  ], // 指定要编译的路径列表,但是和files的区别在于,这里的路径可以是文件夹,也可以是文件,可以使用相对和绝对路径,而且可以使用通配符,比如"./src"即表示要编译src文件夹下的所有文件以及子文件夹的文件
  "exclude": ["node_modules"] // exclude表示要排除的、不编译的文件,它也可以指定一个列表,规则和include一样,可以是文件或文件夹,可以是相对路径或绝对路径,可以使用通配符
}

HTML集成依赖安装

npm i html-webpack-plugin -D

创建入口文件

1、在文件夹下创建public目录,目录下创建 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <title>webpack5+ts+vue3</title>
    <meta
      name="description"
      content="webpack5+ts+vue3搭建项目"
    />
    <meta
      name="keyword"
      content="vue3 kai"
    />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

2、在文件下创建src目录,然后

创建 App.vue 文件

<template>
  <router-view />
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "App",
});
</script>

并创建 index.ts 文件

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.mount('#app')

创建webpack配置文件

​ 在文件夹下创建build文件夹,并在build文件夹下创建三个文件 webpack.base.conf.js、webpack.dev.js、webpack.prod.js,写入基本的配置文件,后续再慢慢完善

  • webpack.base.conf.js文件

    const path = require("path");
    
    function resolve(dir) {
      return path.join(__dirname, "..", dir);
    }
    
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      entry: {
        app: resolve("src/index.ts"),
      },
      resolve: {
        extensions: [".js", ".vue", ".json", ".ts", ".tsx", ".mjs"],
        alias: {
          "@": resolve("src"),
        },
      },
      module: {
        rules: [
          {
            test: /\.vue$/,
            use: [
              {
                loader: "vue-loader",
              },
            ],
            include: /(src)/,
          },
        ],
      },
      plugins: [
        // vue-loader插件
        new vueLoader.VueLoaderPlugin(),
        new HtmlWebpackPlugin({
          filename: "index.html",
          template: resolve("public/index.html"),
          favicon: resolve("public/favicon.ico"),
          inject: true,
        }),
      ],
    };
    
  • webpack.dev.js

    const { merge } = require("webpack-merge");
    const webpack = require("webpack");
    const common = require("./webpack.base.conf");
    const path = require("path");
    function resolve(dir) {
      return path.join(__dirname, "..", dir);
    }
    
    const devWebpackConfig = merge(common, {
      mode: "development",
      devtool: "eval-cheap-module-source-map",
      module: {
        rules: [],
      },
      output: {
        path: resolve("dist"),
        filename: "js/[name].[hash].js",
        chunkFilename: "js/[name].[hash].js",
        publicPath: "/",
      },
      // 日志打印只打印错误和警告
      stats: "errors-warnings",
      devServer: {
        host: "0.0.0.0",
        historyApiFallback: {
          rewrites: [
            {
              from: /.*/g,
              to: "/index.html",
            },
          ],
        },
        allowedHosts: "all",
        port: 8080, // 端口号
        open: false, // 自动打开
        hot: true, // 热更新
        client: {
          progress: true, // 将运行进度输出到控制台。
          overlay: { warnings: false, errors: true }, // 全屏显示错误信息
        },
        compress: true, // 为所有服务启用gzip 压缩
        proxy: {
          "/api": {
            target: "...",
            changeOrigin: true, // 是否是跨域请求
            pathRewrite: {
              "^/api": "",
            },
          },
        },
      },
      plugins: [
        new webpack.DefinePlugin({
          "process.env.NODE_ENV": "'development'",
          __VUE_OPTIONS_API__: true,
          __VUE_PROD_DEVTOOLS__: false,
        }),
      ],
    });
    
    module.exports = devWebpackConfig;
    
  • webpack.prod.js

    const { merge } = require("webpack-merge");
    const webpack = require("webpack");
    const common = require("./webpack.base.conf");
    const path = require("path");
    function resolve(dir) {
      return path.join(__dirname, "..", dir);
    }
    module.exports = function (env, argv) {
      const nodeEnv = env.dev ? "development" : env.test ? "test" : "production";
      return merge(common, {
        mode: "production",
        devtool: "source-map",
        module: {
          rules: [],
        },
        plugins: [
          new webpack.DefinePlugin({
            "process.env.NODE_ENV": JSON.stringify(nodeEnv),
            __VUE_OPTIONS_API__: true,
            __VUE_PROD_DEVTOOLS__: false,
          }),
        ],
        output: {
          path: resolve("dist"),
          filename: "js/[name].[hash].js",
          chunkFilename: "js/[name].[hash].js",
        },
      });
    };
    

接着修改 package.json 文件中的 scripts 配置

  "scripts": {
	"dev": "webpack serve --config build/webpack.dev.js",
    "build:dev": "webpack --env dev --config build/webpack.prod.js",
    "build:test": "webpack --env test --config build/webpack.prod.js",
    "build:prod": "webpack --env prod --config build/webpack.prod.js"
  },

样式相关配置

npm i css-loader sass-loader sass postcss postcss-loader postcss-preset-env vue-style-loader -D

对应webpack配置

module: {
  rules: [
    {
      test: /\.(sa|sc|c)ss$/,
      use: [
        'vue-style-loader',
        {
          loader: 'css-loader',
          options: {
            sourceMap: false,
          },
        },
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              // postcss-preset-env 内部集成了 autoprefixer 添加css第三方前缀
              plugins: ['postcss-preset-env'],
            },
          },
        },
        {
          loader: 'sass-loader',
          options: {
            additionalData: `	
              @use "@/styles/variables.scss" as *;  // 全局导入sass变量
              @use "@/styles/mixin.scss" as *; // 全局导入sass混入
            `,
          },
        },
      ],
    },
  ],
}

图片字体等文件配置

webpack5 内置了 asset 模块, 可以代替 file-loader & url-loader & raw-loader 处理静态资源,比以前简化了很多

rules: [
  {
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    type: 'asset',
    parser: {
      dataUrlCondition: {
        maxSize: 10 * 1024,
      },
    },
    generator: {
      filename: 'images/[base]',
    },
    exclude: [resolve('src/assets/svg')],
  },
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    type: 'asset',
    generator: {
      filename: 'files/[base]',
    },
  },
  {
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
    type: 'asset',
    generator: {
      filename: 'media/[base]',
    },
  },
],

babel配置

npm i @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-typescript babel-loader -D
npm i @babel/runtime-corejs3 core-js
  • @babel/cli : Babel 自带了一个内置的 CLI 命令行工具,可通过命令行( 例如 npx babel script.js )编译文件,调试用,可装可不装

  • @babel/core :Bable进行转码的核心npm包

  • @babel/plugin-transform-runtime 和 @babel/runtime-corejs3:

    js文件在babel转码后会生成很多helper函数(可能有大量重复),polyfill会在全局变量上挂载目标浏览器缺失的功能,这个插件的作用是将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的

    @babel/plugin-transform-runtime这个包的作用是转译代码,转译后的代码中可能会引入@babel/runtime-corejs3 里面的模块,所以前者运行在编译时,后者运行在运行时。

  • @babel/preset-env : 预制套件,里面包含了各种可能用到的转译工具。

  • @babel/preset-typescript : 解析 typescript 的 babel 预设

  • babel-loader : // webpack loader,依赖于 @babel/core,babel-loader作为一个中间桥梁,通过调用babel/core中的api来告诉webpack要如何处理js

  • core-js : JavaScript标准库的 polyfill,@babel/preset-env 引用的包

let babelLoaderConf = {
  loader: 'babel-loader',
  options: {
    presets: [
      [
        '@babel/preset-env',
        {
          targets: {
            browsers: ['ie>=8', 'chrome>=62'],
            node: '8.9.0',
          }, // 转换目标
          debug: false, // 是否打印prese-env对当前配置用了哪些插件,以及我们所支持的浏览器集合的数据
          useBuiltIns: 'usage', // 按需引入
          corejs: '3.0', // corejs版本
        },
      ],
      [
        '@babel/preset-typescript',
        {
          allExtensions: true, // 支持所有文件扩展名,否则在vue文件中使用ts会报错
        },
      ],
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          corejs: 3,
        },
      ],
    ],
  },
};

// webpack
rules: [
	{
        test: /\.(ts|js)x?$/,
        use: [babelLoaderConf],
        exclude: /node_modules/,
    },
],

​ babel7之后已经有了解析 typescript 的能力,也就不再需要 ts-loader

vue文件webpack配置

​ 因为是vue3的项目,所以 vue-loader 要安装最新的版本,这里笔者安装的版本是16.8.3

npm i vue-loader@next @vue/compiler-sfc -D

​ webpack 配置

const vueLoader = require('vue-loader')
...

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: [
          {
            loader: 'vue-loader',
          },
        ],
        include: /(src)/,
      },
    ],
  },

  plugins: [
    // vue-loader插件
    new vueLoader.VueLoaderPlugin(),
  ],
}

​ 加入 vue 对应的类型声明,防止类型报错

新建 types 文件夹, 添加 shims-vue.d.ts 文件

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

代码规范

eslint + prettier + stylelint ,外加 .editorconfig 文件

.editorconfig

​ .editorconfig 文件可以统一编辑时的规范,例如规范缩进空格等等

// .editorconfig 文件
root = true 
[*.{js,jsx,ts,tsx,vue,json,html,css,scss}]
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = crlf # 控制换行类型(lf | cr | crlf)
insert_final_newline = true # 始终在文件末尾插入一个新行

eslint

​ 用于检测代码是否符合编码规范,符合代码语法逻辑,代码格式等等

npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-prettier eslint-config-prettier eslint-plugin-vue
  • eslint: ESLint的核心代码
  • @typescript-eslint/parser:ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码
  • @typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范
  • eslint-plugin-prettier、eslint-config-prettier : eslint 和 prettier 规则兼容
  • eslint-plugin-vue : vue的相关解析,提供了vue的rules,包含了vue-eslint-parser,所以不用再手动装 vue-eslint-parser

.eslintrc.js 文件

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
  },
  parserOptions: {
    parser: "@typescript-eslint/parser", // 解析 .vue 文件里面的 script 标签
    sourceType: "module",
    ecmaVersion: 12,
  },
  plugins: ["vue", "@typescript-eslint"],
  extends: [
    "plugin:vue/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
    "plugin:@typescript-eslint/recommended",
  ],
  rules: {
    indent: ["warn", 2], //缩进风格
  },
};

prettier

​ 美化代码

npm i prettier -D

.prettierrc.js 文件

module.exports = {
  singleQuote: true, // 使用单引号
  tabWidth: 2, // Tab 缩进的长度
  endOfLine: 'auto', // 文件尾部换行的形式
  semi: false, // 不加分号
};

stylelint

npm i stylelint stylelint-config-prettier stylelint-config-recess-order stylelint-config-standard stylelint-order stylelint-scss -D

根目录下添加 stylelint.config.js 文件

module.exports = {
  defaultSeverity: 'warn',
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recess-order', // 属性排序规则
  ],
  plugins: ['stylelint-scss', 'stylelint-order'],
  rules: {
    'no-invalid-double-slash-comments': null, // 允许双斜杠注释
    'custom-property-no-missing-var-function': null,
    'no-empty-source': null,
    'selector-class-pattern': null,
    'alpha-value-notation': null, // 允许小数
    'color-function-notation': null, // 允许rgb颜色
    'media-feature-name-no-vendor-prefix': true, // 不允许媒体特性名称的前缀, 插件自动添加
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['deep'], // 忽略deep伪元素
      },
    ],
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: [
          'mixin',
          'include',
          'if',
          'else',
          'extend',
          'for',
          '$',
          'forward',
          'use',
        ], // 忽略规则
      },
    ],
  },
}

UI框架引入(element-plus)

​ 这个项目采用的 UI 框架是 element-plus,因为目前这个框架是都是 beta 版本,踩了不少坑后,目前使用的是 1.1.0-beta.24 版本,查看框架官方文档,按需引入使用 unplugin-vue-components 插件

npm i element-plus@1.1.0-beta.24 unplugin-vue-components

按需引入

配置 webpack

const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

...

module.exports = {
  ...
  plugins: [
    ...
    // element-plus 按需引入
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: false, // 不引入style文件,便于后面做主题化
        }),
      ],
    }),
  ],
};

同时需要在webpack配置下mjs的loader

修改 fullySpecified 为 false, 让 element-plus 导入模块可以不加扩展名,避免出现 Module not found 的错误且编译失败

module.exports = {
  module: {
    rules: [
      // element-plus
      {
        test: /\.mjs$/,
        include: /node_modules/,
        resolve: {
          fullySpecified: false,
        },
        type: 'javascript/auto',
      },
    ],
  },
}

添加按需引入 ts 文件

// element-plus.ts
import type { App } from 'vue'

import {
  ElButton,
  ElSelect,
  ...
} from 'element-plus'

const components = [
  ElButton,
  ElSelect,
  ...
]

const option = {
  size: 'medium',
}

export default function introduceElement(app: App): void {
  components.forEach((component) => {
    app.use(component)
  })
  app.config.globalProperties.$ELEMENT = option
}

并把该文件导入入口文件

// index.ts

import introduceElement from '@/utils/vue/element-plus'
const app = createApp(App)
// 按需引入element ui组件
introduceElement(app)

主题化

新建 theme.scss 文件

@forward "element-plus/theme-chalk/src/common/var.scss" with (
  $colors: (
    "primary": (
      "base": #016EFD,
    ),
    "success": (
      "base": #016EFD,
    ),
    "warning": (
      "base": #faad14,
    ),
    "danger": (
      "base": #f56c6c,
    ),
    "error": (
      "base": #f56c6c,
    ),
    "info": (
      "base": #0076ff,
    ),
  ),
  $font-path : '~element-plus/dist/fonts' ,
  $button-padding-horizontal: (
    "default": 80px
  )
);

新建 elementPlus.scss 文件

@use './theme.scss' as *;
@use 'element-plus/theme-chalk/src/index.scss' as *;

并在入口文件 index.ts 中引入

import './styles/elementPlus.scss'

至此就完成了主题化设置,试了几个 element-plus 的版本,最后才试出这种 主题化 写法能成功也不会有报错

项目优化

打包体积分析

通过分析各个 bundle 文件的占比大小,来进行针对优化。

npm i -D webpack-bundle-analyzer

我们可以通过在 package.json 中增加 scripts 命令

"build:analyzer": "webpack --env analyzer --config build/webpack.prod.js"

webpack 配置文件

function resolve(dir) {
  return path.join(__dirname, "..", dir);
};

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
...
module.exports = function (env, argv) {
  const analyzerPlugins = env.analyzer
    ? [
        new BundleAnalyzerPlugin({
          analyzerMode: "static",
          openAnalyzer: false,
          //   generateStatsFile: true,
          reportFilename: resolve("./report/report.html"),
          statsFilename: resolve("./report/stats.json"),
        }),
      ]
    : [];
  return merge(common, {
    mode: "production",
	
    ...  
      
    plugins: [
      
      ...
        
      ...analyzerPlugins,
    ],
  });
};

最终的打包大小结果会输出到根目录下的 report 文件夹中

c2.PNG

增加编译进度条,优化编译提示

npm i -D progress-bar-webpack-plugin friendly-errors-webpack-plugin 
  • progress-bar-webpack-plugin : 进度条插件
  • friendly-errors-webpack-plugin :优化提示插件
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const { resolve } = require('./utils.js')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const common = require('./webpack.base.conf')
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')

const devWebpackConfig = merge(common, {
  // 日志打印只打印错误和警告
  stats: 'errors-warnings',
  
  ...
  
})

devWebpackConfig.plugins.push(
  // 进度条
  new ProgressBarPlugin({
    format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`,
    clear: true,
  }),
  // 错误提示
  new FriendlyErrorsWebpackPlugin({
    // 成功的时候输出
    compilationSuccessInfo: {
      messages: [
        `Your application is running here: http://${devWebpackConfig.devServer.host}:${devWebpackConfig.devServer.port}`,
      ],
    },
    // 是否每次都清空控制台
    clearConsole: true,
  })
)

module.exports = devWebpackConfig

c1.PNG

cache缓存

​ 配置 webpack 持久化缓存 cache: filesystem,来缓存生成的 webpack 模块和 chunk,改善构建速度。

​ 使用 cache: filesystem 可以缓存构建过程的 webpack 模板,在二次构建时提速。

function resolve(dir) {
  return path.join(__dirname, "..", dir);
};

const devWebpackConfig = merge(common, {
  ...
  // 缓存
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename], // 针对构建的额外代码依赖的数组对象。webpack 将使用这些项和所有依赖项的哈希值来使文件系统缓存失效。
    },
    cacheDirectory: resolve('temp_cache'),
    name: 'scf-cache', // 路径temp_cache/scf-cache
    compression: 'gzip',
  },
})

清理 dist 文件夹

npm i clean-webpack-plugin -D

这个插件可以在打包时清理 dist 下的旧文件(上次构建的结果),以保证 dist 文件夹中构建结果是最新的。

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
	plugins: [
   		// 清空dist
    	new CleanWebpackPlugin(),
	]
}

css分离

npm i mini-css-extract-plugin -D

CSS 默认是放在 JS 文件中,mini-css-extract-plugin 插件可以将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

将CSS文件抽取出来配置, 还可以防止将样式打包在 js 中文件过大和因为文件大网络请求超时的情况。

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = function (env, argv) {
    module: {
      rules: [
        {
          test: /\.(sa|sc|c)ss$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader,
            },
            {
              loader: 'css-loader',
            },
            {
              loader: 'postcss-loader',
            },
            {
              loader: 'sass-loader',
            },
          ],
        },
      ],
    },
    plugins: [
      // css抽离
      new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash].css',
        chunkFilename: 'css/[name].[contenthash].css',
      }),
    ],
  })
}

css压缩

npm i css-minimizer-webpack-plugin -D

css-minimizer-webpack-plugin 插件可以优化、压缩 CSS文件。

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  plugins: [
    new CssMinimizerPlugin(),
  ],
};

SVG Sprites图

npm i svg-sprite-loader -D

修改webpack配置, 为指定目录下(src/assets/svg)的svg图片配置Sprites图

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: [resolve('src/assets/svg')],
        options: {
          symbolId: 'icon-[name]',
        },
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
        generator: {
          filename: 'images/[base]',
        },
        exclude: [resolve('src/assets/svg')],
      },
    ],
  },
}

插件 svg 组件

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName"></use>
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true,
    },
    className: {
      type: String,
    },
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
  },
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

创建ts文件,统一导入svg

import type { App } from 'vue'
import SvgIcon from '@/components/common/svg/svg-icon.vue' // svg组件

const requireAll = (requireContext: any) =>
  requireContext.keys().map(requireContext)
const req = require.context('@/assets/svg', false, /\.svg$/)
requireAll(req)

export default function svgIconRegistered(app: App): void {
  app.component('SvgIcon', SvgIcon)
}

在入口文件 index.ts 中

import { createApp } from 'vue'
import App from './App.vue'
import svgIconRegistered from '@/utils/vue/svg-component'
const app = createApp(App)
// svg Sprites图
svgIconRegistered(app)

使用示例,menu-icon 是 svg 图文件名

<svg-icon icon-class="menu-icon" class="menu-icon" />

git commit 前的检查、优化

集成 husky 和 lint-staged,husky 能够帮你阻挡住不好的代码提交和推送;lint-staged 可以让每次只检查次提交所修改的文件。

npm i lint-staged husky -D

安装完运行

npx husky install

运行之后会发现项目根目录生成了 .husky 文件夹,在该目录下新建 pre-commit 文件

// pre-commit 文件

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

然后在根目录下新建 lint-staged.config.js 文件,

module.exports = {
  'src/**/*.{js,vue,png,svg,jpg,jepg,gif}': [
    (filenames) =>
      filenames.map(
        (filename) => `prettier --write --ignore-unknown '${filename}'`
      ),
  ],
}

这样在提交前就会对提交文件进行 prettier 美化

项目地址

github.com/chenkai77/w…