当前位置:网站首页>[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
边栏推荐
- How can PostgreSQL CDC set a separate incremental mode, debezium snapshot. mo
- A high density 256 channel electrode cap for dry EEG
- How do programmers live as they like?
- Lepton 无损压缩原理及性能分析
- Interview: is bitmap pixel memory allocated in heap memory or native
- [paper reading] ckan: collaborative knowledge aware autonomous network for adviser systems
- How can non-technical departments participate in Devops?
- Glide Mastery
- 各位大佬,我测试起了3条线程同时往3个mysql表中写入,每条线程分别写入100000条数据,用了f
- C#函数返回多个值方法
猜你喜欢

重磅:国产IDE发布,由阿里研发,完全开源!

Swift set pickerview to white on black background

字节跳动面试官:一张图片占据的内存大小是如何计算

uniapp + uniCloud+unipay 实现微信小程序支付功能

Blockbuster: the domestic IDE is released, developed by Alibaba, and is completely open source!

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

手机厂商“互卷”之年:“机海战术”失灵,“慢节奏”打法崛起

How did automated specification inspection software develop?

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

Solution of ellipsis when pytorch outputs tensor (output tensor completely)
随机推荐
Ad20 make logo
php解决redis的缓存雪崩,缓存穿透,缓存击穿的问题
Design and Simulation of fuzzy PID control system for liquid level of double tank (matlab/simulink)
【Vite】1371- 手把手开发 Vite 插件
Blockbuster: the domestic IDE is released, developed by Alibaba, and is completely open source!
程序员搞开源,读什么书最合适?
Swift tableview style (I) system basic
Design of stepping motor controller based on single chip microcomputer (forward rotation and reverse rotation indicator gear)
flink cdc不能监听mysql日志,大家遇到过这个问题吧?
Qt实现json解析
SQL Server 监控统计阻塞脚本信息
钉钉、企微、飞书学会赚钱了吗?
Learning notes 5 - high precision map solution
小程序中自定义行内左滑按钮,类似于qq和wx消息界面那种
Solution of ellipsis when pytorch outputs tensor (output tensor completely)
Have the bosses ever encountered such problems in the implementation of flinksql by Flink CDC mongdb?
Customize the left sliding button in the line in the applet, which is similar to the QQ and Wx message interface
C语言实现QQ聊天室小项目 [完整源码]
uniapp + uniCloud+unipay 实现微信小程序支付功能
LiveData 面试题库、解答---LiveData 面试 7 连问~