当前位置:网站首页>[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 templates
vite The plug-in
Each hook functions
vite The plug-in
Hook execution sequence
How 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-dev
To configure
.eslintrc
: configure connections [4]install
prettier
( Optional )
pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
To 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-vue3
In the project vite Version and root directoryvite-plugin-template
Consistent versions of ;At the same time
examples/vite-vue3
In the projectvite-plugin-template
Point 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 install
Be careful :
examples/vite-vue2
andexamples/vite-react
The 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 -D
In the root directory
package.json
Middle 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 dev
function
examples
Any item in ( With vite-vue3 For example )
pnpm run example:vue3
Be 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 bepre
orpost
,pre
Compared withpost
Execute first ;apply
: Values can bebuild
orserve
It can also be a function , Indicates that they are available only inbuild
orserve
Called 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
enforce
vite 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
边栏推荐
- How can PostgreSQL CDC set a separate incremental mode, debezium snapshot. mo
- Detailed explanation of the use of staticlayout
- Unity particle special effects series - the poison spray preform is ready, and the unitypackage package can be used directly - next
- Ad20 make logo
- Swift tableview style (I) system basic
- Uni app running to wechat development tool cannot Preview
- Redis如何实现多可用区?
- Learning Note 6 - satellite positioning technology (Part 1)
- uniapp + uniCloud+unipay 实现微信小程序支付功能
- 【Vite】1371- 手把手开发 Vite 插件
猜你喜欢
程序员如何活成自己喜欢的模样?
AtCoder Beginner Contest 258「ABCDEFG」
Swift tableview style (I) system basic
手机厂商“互卷”之年:“机海战术”失灵,“慢节奏”打法崛起
What is the most suitable book for programmers to engage in open source?
[论文阅读] KGAT: Knowledge Graph Attention Network for Recommendation
非技術部門,如何參與 DevOps?
字节跳动面试官:一张图片占据的内存大小是如何计算
一个程序员的职业生涯到底该怎么规划?
Design of stepping motor controller based on single chip microcomputer (forward rotation and reverse rotation indicator gear)
随机推荐
QT implements JSON parsing
Dedecms website building tutorial
> Could not create task ‘:app:MyTest. main()‘. > SourceSet with name ‘main‘ not found. Problem repair
IDEA新建sprintboot项目
Excerpt from "sword comes" (VII)
PHP solves the problems of cache avalanche, cache penetration and cache breakdown of redis
How can non-technical departments participate in Devops?
Learning II of workmanager
Customize the left sliding button in the line in the applet, which is similar to the QQ and Wx message interface
isEmpty 和 isBlank 的用法区别
Uni app running to wechat development tool cannot Preview
Workmanager Learning one
Window下线程与线程同步总结
DDOS攻击原理,被ddos攻击的现象
Pseudo class elements -- before and after
How did automated specification inspection software develop?
学习笔记6--卫星定位技术(上)
@Jsonadapter annotation usage
Universal double button or single button pop-up
Apple 5g chip research and development failure? It's too early to get rid of Qualcomm