当前位置:网站首页>[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 )

  1. install  eslint

pnpm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
  1. To configure  .eslintrc: configure connections [4]

  2. install  prettier ( Optional )

pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev
  1. 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

  1. vite-react:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-react[6]

  2. vite-vue2:github.com/jeddygong/vite-templates/tree/master/vite-plugin-template/examples/vite-vue2[7]

  3. 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

  1. 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 directory  vite-plugin-template  Consistent versions of ;

  • At the same time  examples/vite-vue3  In the project  vite-plugin-template  Point to the plug-ins developed in your current root directory ;

  1. Introducing plug-ins : examples/vite-vue3/vite.config.ts

import template from 'vite-plugin-template';

export default defineConfig({
    ...
    plugins: [vue(), template()],
    ...
});
  1. install : cd examples/vite-vue3 && pnpm install

cd examples/vite-vue3 && pnpm install

Be careful : examples/vite-vue2  and  examples/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 ;

  1. install  tsup

pnpm i tsup -D
  1. 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

  1. Development environment running : The real-time monitoring file is repackaged after modification ( Hot update )

pnpm run dev
  1. 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

  1. Output :

outside_default.png
Untitled.png

Here you can   Run while developing   了 , Youyuxi looked at it and said it was refreshing ~

8. Release

  1.  install  `bumpp`  Add version control and  tag
pnpm i bumpp -D
 Copy code 
  1.  To configure  `package.json`
{
  ...
  "scripts": {
    ...
    "prepublishOnly": "pnpm run build",
    "release": "npx bumpp --push --tag --commit && pnpm publish",
  },
  ...
}
 Copy code 
  1.  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

  1. enforce : Values can be pre  or  post , pre  Compared with  post  Execute first ;

  2. apply : Values can be  build  or  serve  It can also be a function , Indicates that they are available only in  build  or  serve  Called when in mode ;

  3. 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;

  4. 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 .

  5. configureServer(server) : It is mainly used to configure the development server , by dev-server (connect Applications ) Add custom middleware ;

  6. transformIndexHtml(html) : transformation index.html Special hook for . The hook receives the current HTML String and conversion context ;

  7. 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

  1. options(options) : Called when the server starts : obtain 、 manipulation Rollup Options , Strictly speaking , It executes before it belongs to the construction phase ;

  2. buildStart(options): Call each time you start a build ;

  3. resolveId(source, importer, options): Called on each incoming module request , Create a custom confirmation function , Can be used to locate third-party dependencies ;

  4. load(id): Called on each incoming module request , You can customize the loader , Can be used to return customized content ;

  5. transform(code, id): Called on each incoming module request , It is mainly used to convert a single module ;

  6. 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

  1. outputOptions(options): Accept output parameters ;

  2. renderStart(outputOptions, inputOptions): Every time bundle.generate and bundle.write It will be triggered when calling ;

  3. augmentChunkHash(chunkInfo): To give chunk increase hash;

  4. renderChunk(code, chunk, options): Translate single chunk Trigger when .rollup Output every chunk File will be called ;

  5. generateBundle(options, bundle, isWrite): Calling bundle.write Trigger this immediately before hook;

  6. writeBundle(options, bundle): Calling bundle.write after , be-all chunk After writing to the file , It will be called once finally writeBundle;

  7. closeBundle(): Called when the server shuts down

4. Plug in hook function hooks Execution order of ( Here's the picture )

outside_default.png
vite Plug in development hook function (1).png

5. The order in which the plug-ins are executed

  1. The alias processing Alias

  2. User plug-in settings enforce: 'pre'

  3. vite Core plug-ins

  4. User plug-in is not set enforce

  5. vite Build plug-ins

  6. User plug-in settings enforce: 'post'

  7. 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 ;

outside_default.png
demo.gif

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 !

  1. `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 
  1. `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 !!!

outside_default.png
Untitled.png

Babies , You can see it here , Why don't you like it

About this article

come from : Master Yi

https://juejin.cn/post/7103165205483356168

原网站

版权声明
本文为[pingan8787]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207051002138491.html