当前位置:网站首页>[vite] 1371 - develop vite plug-ins by hand
[vite] 1371 - develop vite plug-ins by hand
2022-07-05 10:32:00 【pingan8787】
Preface
Hello everyone , I'm Master Yi , Now vite Tools are beginning to prevail , Can we do something meaningful , For example, write a vite plug-in unit , How did you like it? ?
We can just take advantage of vite plug-in unit The ecology is not yet very mature , Be a person who makes you happy , Let leaders appreciate , A plug-in to make the community happy , Go hand in hand with it .
If you're right vite If you are interested, you can go to the column : 《Vite From entry to mastery 》:juejin.cn/column/7074954144817086472[1]
Through this article, you can learn
How to create a
vite Plug in templatesvite The plug-in
Each hook functionsvite The plug-in
Hook execution sequenceHow to write your own plug-in
understand vite plug-in unit
1. What is? vite plug-in unit
vite In fact, it is an original ES Module The new drive Web Develop front-end build tools .
vite plug-in unit It can be well extended vite Things you can't do by yourself , such as File image compression 、 Yes commonjs Support for 、 Pack progress bar wait .
2. Why write vite plug-in unit
I believe that every student here , Up to now, yes webpack You know the relevant configurations and common plug-ins of ;
vite As a new front-end building tool , It's still very young , It also has a lot of scalability , So why don't we join hands with it now ? Do something more meaningful to you, me and everyone ?
Quick experience
To write a plug-in , That must start with creating a project , Below vite Plug in common template You can write plug-ins directly in the future clone Use ;
Plug in common template github: Experience entrance :github.com/jeddygong/vite-templates/tree/master/vite-plugin-template[2]
plug-in unit github: Experience entrance :github.com/jeddygong/vite-plugin-progress[3]
It is recommended that the package manager use priority :pnpm > yarn > npm > cnpm
Cut a long story short , Let's go straight to work ~
establish vite Plug in common template
1. initialization
1.1 Create a folder and initialize : Initialize and follow the prompts
mkdir vite-plugin-progress && cd vite-plugin-progress && pnpm init
Copy code 1.2 install typescript
pnpm i typescript @types/node -D
Copy code 1.3 To configure 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"
]
}
Copy code 1.4 install vite
// Get into package.json
{
...
"devDependencies": {
"vite": "*"
}
...
}
Copy code 2. To configure eslint and prettier( Optional )
install
eslint
pnpm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-devTo configure
.eslintrc: configure connections [4]install
prettier( Optional )
pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-devTo configure
.prettierrc: configure connections [5]
3. newly added src/index.ts entrance
import type { PluginOption } from 'vite';
export default function vitePluginTemplate(): PluginOption {
return {
// The plug-in name
name: 'vite-plugin-template',
// pre Compared with post Execute first
enforce: 'pre', // post
// Indicates that they are available only in 'build' or 'serve' Called when in mode
apply: 'build', // apply It can also be a function
config(config, { command }) {
console.log(' Here is config hook ');
},
configResolved(resolvedConfig) {
console.log(' Here is configResolved hook ');
},
configureServer(server) {
console.log(' Here is configureServer hook ');
},
transformIndexHtml(html) {
console.log(' Here is transformIndexHtml hook ');
},
}
}
Copy code Among them vite The plug-in function hook will be explained in detail below ~
Come here , Then our basic template will be built , But let's think about it now , How should we run this plug-in ?
So we need to create some examples Example to run this code ;
4. establish examples Catalog
I have created three sets of projects here demo, Everybody directly copy That's it , I won't go into details here
vite-react:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-react[6]
vite-vue2:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-vue2[7]
vite-vue3:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-vue3[8]
If your plug-in needs to run more demo, Create your own project ;
Then we need to configure examples The project under and the plug-in of the current root directory have been debugged ( Let's say examples/vite-vue3 For example ).
5. To configure examples/vite-vue3 project
modify
examples/vite-vue3/package.json
{
...
"devDependencies": {
...
"vite": "link:../../node_modules/vite",
"vite-plugin-template": "link:../../"
}
}What the above means is :
To put
examples/vite-vue3In the project vite Version and root directoryvite-plugin-templateConsistent versions of ;At the same time
examples/vite-vue3In the projectvite-plugin-templatePoint to the plug-ins developed in your current root directory ;
Introducing plug-ins :
examples/vite-vue3/vite.config.ts
import template from 'vite-plugin-template';
export default defineConfig({
...
plugins: [vue(), template()],
...
});install :
cd examples/vite-vue3 && pnpm install
cd examples/vite-vue3 && pnpm installBe careful :
examples/vite-vue2andexamples/vite-reactThe configuration of is consistent with this
reflection :
Come here , Let's think about it again , We put examples/vite-vue3 The items in are configured , But how should we run it ?
Go directly to examples/vite-vue3 Run in directory pnpm run build perhaps pnpm run dev ?
Obviously, this will not run successfully , Because our root directory src/index.ts It can't run directly , So we need to take .ts The file is escaped to .js file ;
So what do we do ?
So we have to try to use a light and small tool without configuration tsup 了 .
6. install tsup Configure run command
tsup Is a light, small and non configurable , from esbuild Supported build tools ;
At the same time, it can directly put .ts、.tsx Convert to a different format esm、cjs、iife Tools for ;
install
tsup
pnpm i tsup -DIn the root directory
package.jsonMiddle configuration
{
...
"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. Development environment running
Development environment running: The real-time monitoring file is repackaged after modification ( Hot update )
pnpm run devfunction
examplesAny item in ( With vite-vue3 For example )
pnpm run example:vue3Be careful :
If your plug-in will only be in build run , Then set up.
"example:vue3": "cd examples/vite-vue3 && pnpm run build";On the contrary, it runs
pnpm run dev
Output :

Here you can Run while developing 了 , Youyuxi looked at it and said it was refreshing ~
8. Release
install `bumpp` Add version control and tag
pnpm i bumpp -D
Copy code To configure `package.json`
{
...
"scripts": {
...
"prepublishOnly": "pnpm run build",
"release": "npx bumpp --push --tag --commit && pnpm publish",
},
...
}
Copy code Run the release after developing the plug-in
# First step
pnpm run prepublishOnly
# The second step
pnpm run release
Copy code So over here , our vite Plug in templates It's already written , You can clone directly vite-plugin-template Templates [9] Use ;
If you are right about vite Plug-in hook and Achieve a real vite plug-in unit Interested can continue to look down ;
vite Plug-in hook hooks People
1. vite Unique hook
enforce: Values can bepreorpost,preCompared withpostExecute first ;apply: Values can bebuildorserveIt can also be a function , Indicates that they are available only inbuildorserveCalled when in mode ;config(config, env): Can be in vite Modify before being parsed vite Related configuration of . The hook receives the original user configuration config And a variable that describes the configuration environment env;configResolved(resolvedConfig): In parsing vite Call after configuration . Use this hook to read and store the final parsed configuration . When a plug-in needs to do something different according to the command it runs , It's very useful .configureServer(server): It is mainly used to configure the development server , by dev-server (connect Applications ) Add custom middleware ;transformIndexHtml(html): transformation index.html Special hook for . The hook receives the current HTML String and conversion context ;handleHotUpdate(ctx): Perform customization HMR to update , Can pass ws Send custom events to the client ;
2. vite And rollup The construction phase of the universal hook
options(options): Called when the server starts : obtain 、 manipulation Rollup Options , Strictly speaking , It executes before it belongs to the construction phase ;buildStart(options): Call each time you start a build ;resolveId(source, importer, options): Called on each incoming module request , Create a custom confirmation function , Can be used to locate third-party dependencies ;load(id): Called on each incoming module request , You can customize the loader , Can be used to return customized content ;transform(code, id): Called on each incoming module request , It is mainly used to convert a single module ;buildEnd(): Called at the end of the build phase , The end of construction here just means that all modules have been escaped ;
3. vite And rollup The output phase of the universal hook
outputOptions(options): Accept output parameters ;renderStart(outputOptions, inputOptions): Every time bundle.generate and bundle.write It will be triggered when calling ;augmentChunkHash(chunkInfo): To give chunk increase hash;renderChunk(code, chunk, options): Translate single chunk Trigger when .rollup Output every chunk File will be called ;generateBundle(options, bundle, isWrite): Calling bundle.write Trigger this immediately before hook;writeBundle(options, bundle): Calling bundle.write after , be-all chunk After writing to the file , It will be called once finally writeBundle;closeBundle(): Called when the server shuts down
4. Plug in hook function hooks Execution order of ( Here's the picture )

5. The order in which the plug-ins are executed
The alias processing Alias
User plug-in settings
enforce: 'pre'vite Core plug-ins
User plug-in is not set
enforcevite Build plug-ins
User plug-in settings
enforce: 'post'vite Build post plug-ins (minify, manifest, reporting)
Roll a hand vite plug-in unit
Let's say vite Pack progress bar Plug in as an example ;

Plug-in address :github[10] If you feel good, welcome star ️
The plug-in has been vite Official collection to official documents : Link address [11]
Because the focus of this article is not on the detailed implementation process of this plug-in , So this article will only post the source code for your reference , The detailed introduction will be explained in the next article , Please wait and see !
`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)
}
}
Copy code `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 => {
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) => {
!isExists && fs.mkdirSync(dirPath);
fs.writeFileSync(filePath, JSON.stringify(data));
};
Copy code Last
The series will be a continuous update series , On the whole 《Vite From entry to mastery 》 special column [12], I will mainly explain from the following aspects , Please wait and see !!!

Babies , You can see it here , Why don't you like it
About this article
come from : Master Yi
https://juejin.cn/post/7103165205483356168
边栏推荐
- GO项目实战 — Gorm格式化时间字段
- How to plan the career of a programmer?
- 面试:List 如何根据对象的属性去重?
- Pseudo class elements -- before and after
- 重磅:国产IDE发布,由阿里研发,完全开源!
- StaticLayout的使用详解
- Events and bubbles in the applet of "wechat applet - Basics"
- 分享.NET 轻量级的ORM
- Should the dependency given by the official website be Flink SQL connector MySQL CDC, with dependency added
- Workmanager learning 1
猜你喜欢

Fluent generates icon prompt logo widget

《微信小程序-基础篇》小程序中的事件与冒泡

Unity particle special effects series - the poison spray preform is ready, and the unitypackage package can be used directly - next

How to plan the career of a programmer?

What is the most suitable book for programmers to engage in open source?

Window下线程与线程同步总结

Learning II of workmanager

程序员搞开源,读什么书最合适?

Redis如何实现多可用区?

Detailed explanation of the use of staticlayout
随机推荐
Pseudo class elements -- before and after
flex4 和 flex3 combox 下拉框长度的解决办法
mongoDB副本集
SQL Server 监控统计阻塞脚本信息
@Serializedname annotation use
@Jsonadapter annotation usage
微信小程序中,从一个页面跳转到另一个页面后,在返回后发现页面同步滚动了
学习笔记5--高精地图解决方案
Swift tableview style (I) system basic
《通信软件开发与应用》课程结业报告
SLAM 01.人类识别环境&路径的模型建立
Go项目实战—参数绑定,类型转换
Have you learned to make money in Dingding, enterprise micro and Feishu?
Idea create a new sprintboot project
php解决redis的缓存雪崩,缓存穿透,缓存击穿的问题
非技术部门,如何参与 DevOps?
Z-blog template installation and use tutorial
Using directive in angualr2 to realize that the picture size changes with the window size
ConstraintLayout的流式布局Flow
C language QQ chat room small project [complete source code]