Vue 3.x + Typescript + Vite 踩坑指南

最近在迁移开源项目 vue-admin 到最新技术上的时候,遇到了一些技术隐形的问题,毕竟是最新的技术点,难免有些疑难杂症,所以分享给有需要的朋友

预览效果

vue-router

vue-router 4.x之后剔除了路由路径匹配,什么意思呢?看个代码片段

import { createRouter, createWebHashHistory } from "vue-router";

const base = [
    {
        path: "https://github.com/Hansen-hjs/vue-admin",
        name: "baidu",
        component: () => import("../views/404.vue"), // 这里一定要给个组件(虽然不会显示),不然会卡死
        meta: {
            icon: "star",
            title: "跳转外部链接"
        }
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes: base
})
复制代码

这个时候会警告并卡死,因为现在不能匹配path为非/开头的路径了,这时候需要在外链前面加个/即可,然后对应的获取路由的时候做对应的的处理即可,像这样:

const base = [
    {
        path: "/https://github.com/Hansen-hjs/vue-admin",
        ...more
    }
]
复制代码

同时vue-router 4.x加入以往没有的新apiremoveRoute现在可以轻松的做退出登陆删除之前动态拼接的路由了,不过这个api是以路由定义中name作为删除唯一键值的,所以我们在定义路由的时候最好写上,且唯一,删除路由操作可以看代码片段

removeRoutes 方法

因为改用了Composition API,所以路由的使用方式变了,不过需要注意的是:useRouteuseRouter这两个hooks函数必选要写在顶层,如果是写在代码运行之后的函数中,是获取不到的,看下面代码:

import { useRouter, useRoute } from "vue-router";
import { defineComponent } from "vue";

export default defineComponent({
    setup() {
        const route = useRoute();
        const router = useRouter();
        
        function getRouterInfo() {
            // const route = useRoute();    // 如果写在这里,是获取不到对象的
            // const router = useRouter();  // 如果写在这里,是获取不到对象的

            console.log(route, router);
            
        }
        return {
            getRouterInfo
        }
    }
})
复制代码

Vite 与 webpack 使用注意点

node.js 文件系统

以往在webpack环境中,是可以在任意地方使用import path from "path"或者import fs from "fs"来做一些文件处理的。现在Vite环境有些特殊的区分,就是在浏览器运行的文件,包括任何.js.vue或者.ts,是不能正常使用import path from "path"或者import fs from "fs"等一些node.js模块的,必需要改用Vite环境特有的import.meta.globEagerimport.meta.glob作为文件处理api使用。举个例子,当前项目需要读取src/icons/svg/目录下的所有svg名称,那么就要这样写:

<template>
    <div v-for="item of svgIcons" :key="item">
        <svg-icon :name="item" />
    </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

const svgFileReg = /(?<=(svg\/)).*?(?=(.svg))/;

/** 获取所有`svg`名称 */
function getSvgNames() {
    const svgInfo = import.meta.globEager("../../icons/svg/*.svg");
    const svgs = Object.keys(svgInfo);
    const names = svgs.map(value => {
        const res = value.match(svgFileReg)![0];
        return res;
    });
    return names;
}

export default defineComponent({
    name: "Icons",
    setup() {

        return {
            svgIcons: getSvgNames()
        }
    }
})
</script>
复制代码

说完浏览器运行文件,还有一个就是在vite.config.ts文件中,import.meta.globEagerimport.meta.glob这个两个api就用不了了,只能用node.js的文件系统模块,也就是上说的那些import fs from fs。同样是当前项目的svg组件,这里要单独写一个svg的加载插件(vite插件),那么要像这样:

import { readFileSync, readdirSync } from "fs";

// svg-sprite-loader 这个貌似在 vite 中用不了
// 该文件只能作为`vite.config.ts`导入使用
// 其他地方导入会报错,因为浏览器环境不支持`fs`模块

/** `id`前缀 */
let idPerfix = "";

const svgTitle = /<svg([^>+].*?)>/;

const clearHeightWidth = /(width|height)="([^>+].*?)"/g;

const hasViewBox = /(viewBox="[^>+].*?")/g;

const clearReturn = /(\r)|(\n)/g;

/**
 * 查找`svg`文件
 * @param dir 文件目录
 */
function findSvgFile(dir: string): Array<string> {
    const svgRes = []
    const dirents = readdirSync(dir, {
        withFileTypes: true
    })
    for (const dirent of dirents) {
        if (dirent.isDirectory()) {
            svgRes.push(...findSvgFile(dir + dirent.name + "/"));
        } else {
            const svg = readFileSync(dir + dirent.name).toString().replace(clearReturn, "").replace(svgTitle, (value, group) => {
                // console.log(++i)
                // console.log(dirent.name)
                let width = 0;
                let height = 0;
                let content = group.replace(clearHeightWidth, (val1: string, val2: string, val3: number) => {
                        if (val2 === "width") {
                            width = val3;
                        } else if (val2 === "height") {
                            height = val3;
                        }
                        return "";
                    }
                )
                if (!hasViewBox.test(group)) {
                    content += `viewBox="0 0 ${width} ${height}"`;
                }
                return `<symbol id="${idPerfix}-${dirent.name.replace(".svg", "")}" ${content}>`;
            }).replace("</svg>", "</symbol>");
            svgRes.push(svg);
        }
    }
    return svgRes;
}

/**
 * `svg`打包器
 * @param path 资源路径
 * @param perfix 后缀名(标签`id`前缀)
 */
export function svgBuilder(path: string, perfix = "icon") {
    if (path.trim() === "") return;
    idPerfix = perfix;
    const res = findSvgFile(path);
    // console.log(res.length)
    return {
        name: "svg-transform",
        transformIndexHtml(html: string) {
            return html.replace("<body>",
                `<body>
                <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
                ${res.join("")}
                </svg>`)
        }
    }
}

复制代码

最后在vite.config.ts文件中使用:

import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import vueJsx from "@vitejs/plugin-vue-jsx";
import { svgBuilder } from "./src/icons/loader"; // 这里是上面写的`svg`加载插件

export default defineConfig({
    plugins: [vue(), vueJsx(), svgBuilder("./src/icons/svg/")],
})
复制代码

npm run build 报错

这个问题比较诡异,npm run dev连警告都没有,npm run build打包居然报错了,后面摸索了一下,原来在tsconfig.json中,需要在include的所有路径前面加个/,我佛,webpack环境表示没有出现过这类问题。像这样:

{
    ...more,
    // 这里所有的路径前面都要加上 / 猜测应该是 vite 在处理文件的时候,用的是严格路径校验
    "include": ["/src/**/*.ts", "/src/**/*.d.ts", "/src/**/*.tsx", "/src/**/*.vue"]
}
复制代码

但是呢,在所有路径前面加上/之后又导致在开发中无法正常配置ts的一些类型检测,所以又得把前面的/给手动删掉,等npm run build的时候再加上去,不知道这是不是vite的一个bug,已经向作者提问了…

打包后依然是使用最新的ES模块

最后需要注意的是,我们在开发环境使用的原生ES模块并不会因为打包后转成以往的兼容模式,意思就是只能用服务器形式来打开,并且不支持IE(好像是废话),仔细看下构建后的index.html引用的js标签就明白了,如果追求兼容、稳定,建议还是用vue 2.x+vue-cli

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享