为了让vite打包更顺畅,我开发了这个vite插件

语言: CN / TW / HK

前言

大家好,我是易师傅,在上一篇给大家带来了《如何入门去写一个 vite 插件》,能看的出来同学们也甚是喜欢,如果大家对 vite 感兴趣,可以关注我的专栏《vite 从入门到精通》;

因为公司目前大部分项目都已转为 vite,为了给社区添加一份贡献,故开发了一个已经应用到项目中的vite 前端打包进度条 的插件 vite-plugin-progress

介绍

vite-plugin-progress 插件是一个在打包时展示进度条的插件,如果您觉得该插件对您的项目有帮助,欢迎 star ⭐️ 支持一下,感谢!

demo.gif

用法

  1. 安装

```bash  # npm  npm i vite-plugin-progress -D

# yarn  yarn add vite-plugin-progress -D

# pnpm  pnpm i vite-plugin-progress -D ```

  1. 使用(不带参数):在 vite.config.js / vite.config.ts 中配置

```ts  import progress from 'vite-plugin-progress'

export default {   plugins: [     progress()   ]  } ```

  1. 参数 0ptions
  2. format :自定义进度条的格式;
  3. width :进度条在终端中的宽度;
  4. complete :完成后的默认字符 \u2588
  5. incomplete :未完成的默认字符 \u2591
  6. renderThrottle :间隔更新时间默认16(毫秒);
  7. clear :完成后是否清除终端,默认 false;
  8. callback :完成后执行的回调函数;
  9. stream 终端中的输出格式流,默认 stderr
  10. head :进度条的头字符;

  11. 参数 options 中的 format 中各个标记含义:

  12. :bar :代表进度条;

  13. :current :当前执行到哪的刻度;
  14. :total :总刻度;
  15. :elapsed :所用时间(秒);
  16. :percent :完成的百分比;
  17. :eta :预计完成时间(秒);
  18. :rate :每一秒的速率;

  19. 使用(带参数): ```ts // vite.config.js / vite.config.ts import progress from 'vite-plugin-progress'

export default { plugins: [ progress({ format: 'building [:bar] :percent', total: 200, width: 60, complete: '=', incomplete: '', }) ] } ``` 6. 给自定义进度条加点颜色

安装 `picocolors` :

```bash
 pnpm i picocolors -D
```
使用:
```ts
 // vite.config.js / vite.config.ts
 import progress from 'vite-plugin-progress'
 import colors from 'picocolors'

 export default {
   plugins: [
     progress({
         format:  `${colors.green(colors.bold('Bouilding'))} ${colors.cyan('[:bar]')} :percent`
     })
   ]
 }
```

如果您只想使用该插件的话,那么现在就去安装使用吧!

如果您对实现思路感兴趣的话,那么您可以继续向下滚动查阅哟 ~

实现思路

其实实现这个功能,我们最主要的考虑就是当前 vite 打包的进度到哪里了,那么我们思考两个问题: 1. 考量当前 vite 打包的进度是到哪里了? 2. 如何知道当前打包的进度?

熟悉 webpack 的朋友,肯定对 webpack 的打包进度条也不陌生;会发现在 webpack 中,webpack 暴露了一个 webpack.ProgressPlugin 的事件钩子,这样就导致在 webpack 中实现进度条会很容易,直接通过该钩子去封装即可;

但是在 vite 中由于是基于 Rollup 来构建打包代码,所以我们是没法知道当前 vite 打包进度的;

借用 vite 其中某位作者的原话:

carbon_(1).png

简单理解意思就是说在 vite 打包时,是没法知道进度条的 0%-100%,因为您必须先确定要构建的模块的总数

虽然我们不知道模块总数,但是我们可以在第一次打包时模拟一个;

并且在第一次打包的时候,我们 记录下对应的模块数量,然后 缓存起来,这样我们不就可以知道对应的模块数量了吗?

说干就干 ~

第一次打包(模拟模块总数)

因为我们可以知道 src 目录 下所有的文件总数,所以就可以假设在第一次打包时用该总数来代替模块总数;

那么简单公式:进度条百分比 = 当前转换的模块 / 模拟的模块总数 ```ts import type { PluginOption } from 'vite'; import rd from 'rd';

export default function viteProgressBar(options?: PluginOptions): PluginOption { // 文件类型总数 let fileCount = 0 let transformCount = 0 let transformed = 0 // 当前已转换的数量 retun { ... config(config, { command }) {
if (command === 'build') { 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++
        const reg = /node_modules/gi;
        if (!reg.test(id){
    percent = +(transformed / fileCount).toFixed(2)         
        }
}
}

} ```

与进度条配合

那么既然我们已经算出了基本的进度条,也知道了基本思路,那么我们就把进度条加进去 ```ts import type { PluginOption } from 'vite'; import progress from 'progress'; import rd from 'rd';

export default function viteProgressBar(options?: PluginOptions): PluginOption { let fileCount = 0 // 文件类型总数 let transformCount = 0 // 转换的模块总数 let transformed = 0 // 当前已转换的数量 let lastPercent = 0; // 记录上一次进度条百分比 const bar: progress; retun { ...
config(config, { command }) {
if (command === 'build') {
// 初始化进度条 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);

                                // 统计目录下的文件总数
            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++
        const reg = /node_modules/gi;
        if (!reg.test(id){
             lastPercent = percent = +(transformed / fileCount).toFixed(2)
        }
        // 更新进度条
        bar.update(lastPercent, {
    transformTotal: cacheTransformCount,
            transformCur: transformCount,
            chunkTotal: cacheChunkCount,
            chunkCur: 0,
        })  
    },
closeBundle() {     
        // close progress
        bar.update(1)
        bar.terminate()
}
}

} ```

添加缓存

为了更准确的知道打包的进度,那么我们在第一次模拟了总数的时候,也要同时把真实的模块总数给缓存起来,这样在下一次打包时才能更加准确的计算出进度条;

新增缓存文件 cache.ts ```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 { /* * 转换的模块总数 / cacheTransformCount: number;

/**
 * chunk 的总数
 */
cacheChunkCount: number

}

/* * 判断是否有缓存 * @return boolean / export const isExists = fs.existsSync(filePath) || false;

/* * 获取缓存数据 * @returns ICacheData / export const getCacheData = (): ICacheData => { if (!isExists) return { cacheTransformCount: 0, cacheChunkCount: 0 };

return JSON.parse(fs.readFileSync(filePath, 'utf8'));

};

/* * 设置缓存数据 * @returns / export const setCacheData = (data: ICacheData) => { !isExists && fs.mkdirSync(dirPath); fs.writeFileSync(filePath, JSON.stringify(data)); }; ```

使用缓存

```ts // 缓存进度条计算 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)

} ```

实现架构图

vite插件1水印.png

最后

该系列会是一个持续更新系列,关于整个专栏 《Vite 从入门到精通》,我主要会从如下图几个方面讲解,请大家拭目以待吧!!!

Untitled.png

宝贝们,都看到这里了,要不点个赞呗 👍