欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

手寫vite插件教程示例

 更新時(shí)間:2022年06月22日 15:38:59   作者:易師傅  
這篇文章主要為大家介紹了手寫一個(gè)vite插件教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

大家好,我是易師傅,在現(xiàn)如今 vite 工具快開始盛行之下,我們是不是可以去做一件有意義的事呢,比如寫一個(gè) vite 插件,你覺得怎么樣?

剛好我們可以趁 vite 插件 生態(tài)還未很成熟階段,做一個(gè)讓自己順心,讓領(lǐng)導(dǎo)賞心,讓社區(qū)開心的插件,與之?dāng)y手共進(jìn)。

如果大家對 vite 感興趣可以去看看專欄: 《Vite 從入門到精通》

通過本文你可以學(xué)到

  • 如何創(chuàng)建一個(gè) vite 插件模板
  • vite 插件的 各個(gè)鉤子作用
  • vite 插件的 鉤子執(zhí)行順序
  • 如何寫一個(gè)自己的插件

1. 什么是 vite 插件

vite 其實(shí)就是一個(gè)由原生 ES Module 驅(qū)動的新型 Web 開發(fā)前端構(gòu)建工具。

vite 插件 就可以很好的擴(kuò)展 vite 自身不能做到的事情,比如 文件圖片的壓縮、 對 commonjs 的支持、 打包進(jìn)度條 等等。

2. 為什么要寫 vite 插件

相信在座的每位同學(xué),到現(xiàn)在對 webpack 的相關(guān)配置以及常用插件都了如指掌了吧;

vite 作為一個(gè)新型的前端構(gòu)建工具,它還很年輕,也有很多擴(kuò)展性,那么為什么我們不趁現(xiàn)在與它一起攜手前進(jìn)呢?做一些于你于我于大家更有意義的事呢?

要想寫一個(gè)插件,那必須從創(chuàng)建一個(gè)項(xiàng)目開始,下面的 vite 插件通用模板 大家以后寫插件可以直接clone使用;

插件通用模板 github:體驗(yàn)入口

插件 github:體驗(yàn)入口

建議包管理器使用優(yōu)先級:pnpm > yarn > npm > cnpm

長話短說,直接開干 ~

創(chuàng)建  vite 插件通用模板

1. 初始化

1.1 創(chuàng)建一個(gè)文件夾并且初始化:初始化按照提示操作即可

mkdir vite-plugin-progress && cd vite-plugin-progress && pnpm init 

1.2 安裝 typescript

pnpm i typescript @types/node -D

1.3 配置 tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "declaration": true,
    "noUnusedLocals": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "lib": ["ESNext"],
    "sourceMap": false,
    "noEmitOnError": true,
    "noImplicitAny": false
  },
  "include": [
    "src/*",
    "*.d.ts"
  ],
  "exclude": [
    "node_modules",
    "examples",
    "dist"
  ]
}

1.4 安裝 vite

// 進(jìn)入 package.json
{
    ...
    "devDependencies": {
        "vite": "*"
    }
    ...
}

2. 配置 eslint 和 prettier(可選)

安裝 eslint

pnpm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

配置 .eslintrc:配置連接

安裝 prettier (可選)

pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev

配置 .prettierrc :配置連接

3. 新增 src/index.ts 入口

import type { PluginOption } from 'vite';
export default function vitePluginTemplate(): PluginOption {
  return {
    // 插件名稱
    name: 'vite-plugin-template',
    // pre 會較于 post 先執(zhí)行
    enforce: 'pre', // post
    // 指明它們僅在 'build' 或 'serve' 模式時(shí)調(diào)用
    apply: 'build', // apply 亦可以是一個(gè)函數(shù)
    config(config, { command }) {
      console.log('這里是config鉤子');
    },
    configResolved(resolvedConfig) {
      console.log('這里是configResolved鉤子');
    },
    configureServer(server) {
      console.log('這里是configureServer鉤子');
    },
    transformIndexHtml(html) {
      console.log('這里是transformIndexHtml鉤子');
    },
  }
}

其中的 vite 插件函數(shù)鉤子會在下面詳細(xì)詳解 ~

到這里,那么我們的基本模版就建好了,但是我們現(xiàn)在思考一下,我們應(yīng)該怎么去運(yùn)行這個(gè)插件呢?

那么我們就需要創(chuàng)建一些 examples 例子來運(yùn)行這個(gè)代碼了;

4. 創(chuàng)建 examples 目錄

我這里創(chuàng)建了三套項(xiàng)目 demo,大家直接 copy 就行了,這里就不詳細(xì)介紹了

vite-react

vite-vue2

vite-vue3

如果你的插件需要多跑一些 demo,自行創(chuàng)建項(xiàng)目即可;

那么下面我們就需要配置 examples 下的項(xiàng)目與當(dāng)前根目錄的插件做一個(gè)聯(lián)調(diào)了(下面以 examples/vite-vue3 為例)。

5. 配置 examples/vite-vue3 項(xiàng)目

修改 examples/vite-vue3/package.json

{
    ...
    "devDependencies": {
        ...
        "vite": "link:../../node_modules/vite",
        "vite-plugin-template": "link:../../"
    }
}

上面意思就是說:

要把 examples/vite-vue3 項(xiàng)目中的 vite 版本與根目錄 vite-plugin-template 的版本一致;

同時(shí)要把 examples/vite-vue3 項(xiàng)目中的 vite-plugin-template 指向你當(dāng)前根目錄所開發(fā)的插件;

引入插件: examples/vite-vue3/vite.config.ts

import template from 'vite-plugin-template';
export default defineConfig({
    ...
    plugins: [vue(), template()],
    ...
});

安裝: cd examples/vite-vue3 && pnpm install

cd examples/vite-vue3 && pnpm install

注意:

examples/vite-vue2 和 examples/vite-react 的配置與這一致

思考:

到這里,我們再思考一下,我們把 examples/vite-vue3 中的項(xiàng)目配置好了,但是我們應(yīng)該怎么去運(yùn)行呢?

直接去 examples/vite-vue3 目錄下運(yùn)行 pnpm run build 或者 pnpm run dev ?

這樣顯然是不能運(yùn)行成功的,因?yàn)槲覀兊母夸浵碌?src/index.ts 是沒法直接運(yùn)行的,所以我們需要把 .ts 文件轉(zhuǎn)義成 .js 文件;

那么我們怎么處理呢?

那么我們不得不去試著用用一個(gè)輕小且無需配置的工具 tsup 了。

6. 安裝 tsup 配置運(yùn)行命令

tsup 是一個(gè)輕小且無需配置的,由 esbuild 支持的構(gòu)建工具;

同時(shí)它可以直接把 .ts、.tsx 轉(zhuǎn)成不同格式 esm、cjs、iife 的工具;

安裝 tsup

pnpm i tsup -D

在根目錄下的 package.json 中配置

{
  ...
  "scripts": {
    "dev": "pnpm run build -- --watch --ignore-watch examples",
    "build": "tsup src/index.ts --dts --format cjs,esm",
    "example:react": "cd examples/vite-react && pnpm run build",
    "example:vue2": "cd examples/vite-vue2 && pnpm run build",
    "example:vue3": "cd examples/vite-vue3 && pnpm run build"
  },
  ...
}

7. 開發(fā)環(huán)境運(yùn)行

開發(fā)環(huán)境運(yùn)行:實(shí)時(shí)監(jiān)聽文件修改后重新打包(熱更新)

pnpm run dev

運(yùn)行 examples 中的任意一個(gè)項(xiàng)目(以 vite-vue3 為例)

pnpm run example:vue3

注意:

如果你的插件只會在 build 時(shí)運(yùn)行,那就設(shè)置

"example:vue3": "cd examples/vite-vue3 && pnpm run build" ;

反之就運(yùn)行

pnpm run dev

輸出:

到這里你就可以 邊開發(fā)邊運(yùn)行 了,尤雨溪看了都說爽歪歪 ~

8. 發(fā)布

安裝 bumpp 添加版本控制與 tag

pnpm i bumpp -D

配置 package.json

{
  ...
  "scripts": {
    ...
    "prepublishOnly": "pnpm run build",
    "release": "npx bumpp --push --tag --commit && pnpm publish",
  },
  ...
}

開發(fā)完插件后運(yùn)行發(fā)布

# 第一步
pnpm run prepublishOnly
# 第二步
pnpm run release

那么到這里,我們的 vite 插件模板 就已經(jīng)寫好了,大家可以直接克隆 vite-plugin-template 模板 使用;

如果你對 vite 的插件鉤子 和 實(shí)現(xiàn)一個(gè)真正的 vite 插件 感興趣可以繼續(xù)往下面看;

vite 的插件鉤子 hooks 們

1. vite 獨(dú)有的鉤子

  • enforce :值可以是pre 或 post , pre 會較于 post 先執(zhí)行;
  • apply :值可以是 build 或 serve 亦可以是一個(gè)函數(shù),指明它們僅在 build 或 serve 模式時(shí)調(diào)用;
  • config(config, env) :可以在 vite 被解析之前修改 vite 的相關(guān)配置。鉤子接收原始用戶配置 config 和一個(gè)描述配置環(huán)境的變量env;
  • configResolved(resolvedConfig) :在解析 vite 配置后調(diào)用。使用這個(gè)鉤子讀取和存儲最終解析的配置。當(dāng)插件需要根據(jù)運(yùn)行的命令做一些不同的事情時(shí),它很有用。
  • configureServer(server) :主要用來配置開發(fā)服務(wù)器,為 dev-server (connect 應(yīng)用程序) 添加自定義的中間件;
  • transformIndexHtml(html) :轉(zhuǎn)換 index.html 的專用鉤子。鉤子接收當(dāng)前的 HTML 字符串和轉(zhuǎn)換上下文;
  • handleHotUpdate(ctx):執(zhí)行自定義HMR更新,可以通過ws往客戶端發(fā)送自定義的事件;

2. vite 與 rollup 的通用鉤子之構(gòu)建階段

  • options(options) :在服務(wù)器啟動時(shí)被調(diào)用:獲取、操縱Rollup選項(xiàng),嚴(yán)格意義上來講,它執(zhí)行于屬于構(gòu)建階段之前;
  • buildStart(options):在每次開始構(gòu)建時(shí)調(diào)用;
  • resolveId(source, importer, options):在每個(gè)傳入模塊請求時(shí)被調(diào)用,創(chuàng)建自定義確認(rèn)函數(shù),可以用來定位第三方依賴;
  • load(id):在每個(gè)傳入模塊請求時(shí)被調(diào)用,可以自定義加載器,可用來返回自定義的內(nèi)容;
  • transform(code, id):在每個(gè)傳入模塊請求時(shí)被調(diào)用,主要是用來轉(zhuǎn)換單個(gè)模塊;
  • buildEnd():在構(gòu)建階段結(jié)束后被調(diào)用,此處構(gòu)建結(jié)束只是代表所有模塊轉(zhuǎn)義完成;

3. vite 與 rollup 的通用鉤子之輸出階段

  • outputOptions(options):接受輸出參數(shù);
  • renderStart(outputOptions, inputOptions):每次 bundle.generate 和 bundle.write 調(diào)用時(shí)都會被觸發(fā);
  • augmentChunkHash(chunkInfo):用來給 chunk 增加 hash;
  • renderChunk(code, chunk, options):轉(zhuǎn)譯單個(gè)的chunk時(shí)觸發(fā)。rollup 輸出每一個(gè)chunk文件的時(shí)候都會調(diào)用;
  • generateBundle(options, bundle, isWrite):在調(diào)用 bundle.write 之前立即觸發(fā)這個(gè) hook;
  • writeBundle(options, bundle):在調(diào)用 bundle.write后,所有的chunk都寫入文件后,最后會調(diào)用一次 writeBundle;
  • closeBundle():在服務(wù)器關(guān)閉時(shí)被調(diào)用

4. 插件鉤子函數(shù) hooks 的執(zhí)行順序(如下圖)

5. 插件的執(zhí)行順序

  • 別名處理Alias
  • 用戶插件設(shè)置enforce: 'pre'
  • vite 核心插件
  • 用戶插件未設(shè)置enforce
  • vite 構(gòu)建插件
  • 用戶插件設(shè)置enforce: 'post'
  • vite 構(gòu)建后置插件(minify, manifest, reporting)

手?jǐn)]一個(gè) vite 插件

下面以 vite 打包進(jìn)度條 插件為例;

插件地址:github 

該插件已被 vite 官方收集至官方文檔:鏈接地址

因?yàn)槲恼碌闹攸c(diǎn)不在于這個(gè)插件的詳細(xì)實(shí)現(xiàn)過程,所以本文只會貼上源代碼供大家參考,詳細(xì)介紹會在下一篇文章中講解,請大家拭目以待吧!

inde.ts

import type { PluginOption } from 'vite';
import colors from 'picocolors';
import progress from 'progress';
import rd from 'rd';
import { isExists, getCacheData, setCacheData } from './cache';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
type PluginOptions = Merge<
    ProgressBar.ProgressBarOptions,
    {
        /**
         * total number of ticks to complete
         * @default 100
         */
        total?: number;
        /**
         * The format of the progress bar
         */
        format?: string;
    }
>;
export default function viteProgressBar(options?: PluginOptions): PluginOption {
    const { cacheTransformCount, cacheChunkCount } = getCacheData()
    let bar: progress;
    const stream = options?.stream || process.stderr;
    let outDir: string;
    let transformCount = 0
    let chunkCount = 0
    let transformed = 0
    let fileCount = 0
    let lastPercent = 0
    let percent = 0
    return {
        name: 'vite-plugin-progress',
        enforce: 'pre',
        apply: 'build',
        config(config, { command }) {
            if (command === 'build') {
                config.logLevel = 'silent';
                outDir = config.build?.outDir || 'dist';
                options = {
                    width: 40,
                    complete: '\u2588',
                    incomplete: '\u2591',
                    ...options
                };
                options.total = options?.total || 100;
                const transforming = isExists ? `${colors.magenta('Transforms:')} :transformCur/:transformTotal | ` : ''
                const chunks = isExists ? `${colors.magenta('Chunks:')} :chunkCur/:chunkTotal | ` : ''
                const barText = `${colors.cyan(`[:bar]`)}`
                const barFormat =
                    options.format ||
                    `${colors.green('Bouilding')} ${barText} :percent | ${transforming}${chunks}Time: :elapseds`
                delete options.format;
                bar = new progress(barFormat, options as ProgressBar.ProgressBarOptions);
                // not cache: Loop files in src directory
                if (!isExists) {
                    const readDir = rd.readSync('src');
                    const reg = /\.(vue|ts|js|jsx|tsx|css|scss||sass|styl|less)$/gi;
                    readDir.forEach((item) => reg.test(item) && fileCount++);
                }
            }
        },
        transform(code, id) {
            transformCount++
            // not cache
            if(!isExists) {
                const reg = /node_modules/gi;
                if (!reg.test(id) && percent < 0.25) {
                    transformed++
                    percent = +(transformed / (fileCount * 2)).toFixed(2)
                    percent < 0.8 && (lastPercent = percent)
                  }
                if (percent >= 0.25 && lastPercent <= 0.65) {
                    lastPercent = +(lastPercent + 0.001).toFixed(4)
                } 
            }
            // go cache
            if (isExists) runCachedData()
            bar.update(lastPercent, {
                transformTotal: cacheTransformCount,
                transformCur: transformCount,
                chunkTotal: cacheChunkCount,
                chunkCur: 0,
            })
            return {
                code,
                map: null
            };
        },
        renderChunk() {
            chunkCount++
            if (lastPercent <= 0.95) 
                isExists ? runCachedData() : (lastPercent = +(lastPercent + 0.005).toFixed(4))
            bar.update(lastPercent, {
                transformTotal: cacheTransformCount,
                transformCur: transformCount,
                chunkTotal: cacheChunkCount,
                chunkCur: chunkCount,
            })
            return null
        },
        closeBundle() {
            // close progress
            bar.update(1)
            bar.terminate()
            // set cache data
            setCacheData({
                cacheTransformCount: transformCount,
                cacheChunkCount: chunkCount,
            })
            // out successful message
            stream.write(
                `${colors.cyan(colors.bold(`Build successful. Please see ${outDir} directory`))}`
            );
            stream.write('\n');
            stream.write('\n');
        }
    };
    /**
     * run cache data of progress
     */
    function runCachedData() {
        if (transformCount === 1) {
            stream.write('\n');
            bar.tick({
                transformTotal: cacheTransformCount,
                transformCur: transformCount,
                chunkTotal: cacheChunkCount,
                chunkCur: 0,
            })
        }
        transformed++
        percent = lastPercent = +(transformed / (cacheTransformCount + cacheChunkCount)).toFixed(2)
    }
}

cache.ts

import fs from 'fs';
import path from 'path';
const dirPath = path.join(process.cwd(), 'node_modules', '.progress');
const filePath = path.join(dirPath, 'index.json');
export interface ICacheData {
    /**
     * Transform all count
     */
    cacheTransformCount: number;
    /**
     * chunk all count
     */
    cacheChunkCount: number
}
/**
 * It has been cached
 * @return boolean
 */
export const isExists = fs.existsSync(filePath) || false;
/**
 * Get cached data
 * @returns ICacheData
 */
export const getCacheData = (): ICacheData =&gt; {
    if (!isExists) return {
        cacheTransformCount: 0,
        cacheChunkCount: 0
    };
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
};
/**
 * Set the data to be cached
 * @returns 
 */
export const setCacheData = (data: ICacheData) =&gt; {
    !isExists &amp;&amp; fs.mkdirSync(dirPath);
    fs.writeFileSync(filePath, JSON.stringify(data));
};

最后

該系列會是一個(gè)持續(xù)更新系列,關(guān)于整個(gè)《Vite 從入門到精通》專欄,我主要會從如下圖幾個(gè)方面講解,請大家拭目以待吧!??!

以上就是手寫vite插件教程示例的詳細(xì)內(nèi)容,更多關(guān)于vite插件教程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue實(shí)現(xiàn)日歷備忘錄功能

    vue實(shí)現(xiàn)日歷備忘錄功能

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)日歷備忘錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-06-06
  • vue router返回到指定的路由的場景分析

    vue router返回到指定的路由的場景分析

    這篇文章主要介紹了vue router返回到指定的路由的場景分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • element ui table 增加篩選的方法示例

    element ui table 增加篩選的方法示例

    這篇文章主要介紹了element ui table 增加篩選的方法示例,詳細(xì)的介紹了如何添加規(guī)則內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • vue實(shí)現(xiàn)頁面加載動畫效果

    vue實(shí)現(xiàn)頁面加載動畫效果

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)頁面加載動畫效果,vue頁面出現(xiàn)正在加載的初始頁面與實(shí)現(xiàn)動畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • element-plus報(bào)錯(cuò)ResizeObserver?loop?limit?exceeded解決辦法

    element-plus報(bào)錯(cuò)ResizeObserver?loop?limit?exceeded解決辦法

    這篇文章主要給大家介紹了關(guān)于element-plus報(bào)錯(cuò)ResizeObserver?loop?limit?exceeded的解決辦法,文中通過代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • vue如何動態(tài)實(shí)時(shí)的顯示時(shí)間淺析

    vue如何動態(tài)實(shí)時(shí)的顯示時(shí)間淺析

    這篇文章主要給大家介紹了關(guān)于vue如何動態(tài)實(shí)時(shí)的顯示時(shí)間,以及vue時(shí)間戳 獲取本地時(shí)間實(shí)時(shí)更新的相關(guān)資料,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • vue如何動態(tài)修改meta的title

    vue如何動態(tài)修改meta的title

    這篇文章主要介紹了vue如何動態(tài)修改meta的title,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 通過vue寫一個(gè)瀑布流插件代碼實(shí)例

    通過vue寫一個(gè)瀑布流插件代碼實(shí)例

    這篇文章主要介紹了通過vue寫一個(gè)瀑布流插件代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • 詳解vue-cli官方腳手架配置

    詳解vue-cli官方腳手架配置

    這篇文章主要介紹了詳解vue-cli官方腳手架配置,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • vue3?emits事件使用示例詳解

    vue3?emits事件使用示例詳解

    這篇文章主要為大家介紹了vue3?emits事件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07

最新評論