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

最近在遷移開源項目 vue-admin 到最新技術上的時候,遇到了一些技術隱形的問題,畢竟是最新的技術點,不免有些疑難雜症,因此分享給有須要的朋友javascript

預覽效果css

Vite 與 webpack 使用注意點

node.js 文件系統html

瀏覽器環境文件操做api使用,webpack對應Vitevue

// webpack
require.context

// 對應 vite 二選一,詳細使用說明看文檔 https://vitejs.dev/guide/features.html#glob-import
import.meta.globEager
import.meta.glob
複製代碼

舉個例子,當前項目須要讀取src/icons/svg/目錄下的全部svg名稱,那麼就要這樣寫:java

<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的文件系統模塊,這裏跟webpack環境基本一致。一樣是當前項目的svg組件,這裏要單獨寫一個svg的加載插件(vite插件),那麼要像這樣:node

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文件中使用:webpack

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 報錯git

這個問題比較詭異,npm run dev連警告都沒有,npm run build打包竟然報錯了,後面摸索了一下,原來在tsconfig.json中,須要在include的全部路徑前面加個/webpack環境表示沒有出現過這類問題。像這樣:github

{
    ...more,
    // 這裏全部的路徑前面都要加上 / 猜想應該是 vite 在處理文件的時候,路徑校驗規則不太同樣
    "include": ["/src/**/*.ts", "/src/**/*.d.ts", "/src/**/*.tsx", "/src/**/*.vue"]
}
複製代碼

可是呢,在全部路徑前面加上/以後又致使在開發中沒法正常配置ts的一些類型檢測,因此又得把前面的/給手動刪掉,等npm run build的時候再加上去,不知道這是否是vite的一個bugweb

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
        }
    }
})
複製代碼

不肯定其餘庫的hooks使用方式是否也是須要把聲明寫在頂層,但vue-router是須要的。

scss 變量在 js 或 ts 中使用

以前webpack環境中導出的方式依然不變,稍做變更的是文件命名,例如variables.scss要做爲js/ts中導入使用,只須要在名字後面加個.module便可,像這樣:variables.module.scss

$--color-primary: #1890FF;

// The :export directive is the magic sauce for webpack
// https://mattferderer.com/use-sass-variables-in-typescript-and-javascript
:export {
    theme: $--color-primary;
}
複製代碼

其餘非.scss文件導入使用

import variables from "../styles/variables.module.scss";

console.log(variables) // 輸出 { theme: "#1890FF" }
複製代碼

注意事項

main.ts中引入帶有module.scss後綴的文件做爲樣式引入使用,默認是不會加載到<style>去的,因此須要在沒有module.scss後綴的文件中@import ./xxx.module.scss,而後再在main.ts引入該文件

npm run build 報錯

目前還不肯定是什麼緣由,npm run dev的時候正常使用,可是npm run build就報錯,出現:

[vite:css-post] value.replace is not a function
複製代碼

因此我在項目中放棄xxx.module.scss這種命名導入使用方式,而是採用直接暴力的解決方案:正常導入xxx.scss以後,寫一個提取導出變量的工具函數,這樣就實現相同的功能了。

處理導出工具函數:

/** * 格式化`.scss`文件中導出的變量 * @param val */
function formatStyleModule(val: string) {
    val = val.replace(/:export {/":export{").replace(/(\n|\t|\s)*/g, "");
    const matchInfo = val.match(/:export{(\S*)}/);
    if (matchInfo && matchInfo[1]) {
        let match = matchInfo[1];
        if (match[match.length - 1] == ";") {
            match = match.slice(0, match.length - 1);
        }
        val = match.replace(/;/g, `","`).replace(/:/g, `":"`);
    }
    // console.log(`{"${val}"}`);
    return JSON.parse(`{"${val}"}`);
}
複製代碼

使用示例:

import style from "../styles/variables.scss";
const variables = formatStyleModule(style);
console.log(variables);
複製代碼

ES模塊

第三方插件庫

像一些比較老的插件,是不支持ES模塊的,也就是不支持import,只能使用require的這類插件,目前是不支持在vite中使用的,由於它只支持ES模塊,或者本身手動去修改源碼導出方式。

生產環境

最後須要注意的是,咱們在開發環境使用的原生ES模塊並不會由於打包後轉成以往的兼容模式,意思就是打包後仍是ES模塊,而且只能用服務端形式來打開index.html,一些低版本的瀏覽器是不支持或者存在兼容性的,仔細看下構建後的index.html引用的js標籤就明白了;若是追求兼容、穩定,建議仍是用vue 2.x+vue-cli...

相關文章
相關標籤/搜索