Написание Vite плагина

от автора

Сборщик Vite предоставляет не только хороший функционал, но и удобный API для создания плагинов, позволяющих кастомизировать его практически под любую задачу. То есть, плагины можно писать не только для публикации их в npmjs.com репозитории, но и для автоматизации исключительно своих задач.

Сложность написания плагина сравнима со сложностью написания сценария для Gulp или GitHub Actions. Для примера напишем плагин, который будет вставлять фрагменты кода в файл index.html. В зависимости от проекта в данный файл необходимо помещать код Google Analytics, метатэги Open Graph и Twitter, подключение Service worker-a, виджета чата поддержки, сплэш скрин и многое другое. В результате index.html становится очень большим и ориентироваться в нем и блоках кода довольно сложно.

Наш плагин позволить держать фрагменты кода в отдельных файлах, а при сборке все будет помещаться в index.html. Причем это будет происходить не только при непосредственно сборке ( npm build ), но и при запуске Vite dev сервера с поддержкой HRM (Hot Module Replacement).

Репозиторий плагина — https://github.com/altrusl/vite-plugin-html-injection
NPM — https://www.npmjs.com/package/vite-plugin-html-injection

Система плагинов Vite

Плагины Vite являются расширением плагинов сборщика Rollup, который используется для сборки Vite проекта под капотом. За небольшим исключением плагины Rollup работают в Vite, в то же время последний добавляет несколько хуков в API для плагинов, которые мы и будет использовать.

Написание плагина по большому счету является написанием кода для данных хуков. Мы задействуем два из них.

vite-plugin-html-injection

Первый используемый хук — configResolved. Он нужен для того, чтобы получить конфиг Vite, из которого мы позже получим абсолютный путь к директории проекта — config.root

Второй хук — transformIndexHtml. В нем непосредственно нужно произвести изменение содержимого index.html. Аргументом мы получим строку с оригинальным содержимым index.html, вернуть надо модифицированный контент.

import { Plugin, ResolvedConfig } from "vite"; import path from "path"; import fs from "fs"; import { IHtmlInjectionConfig, IHtmlInjectionConfigInjection } from "./types";  export function htmlInjectionPlugin(   htmlInjectionConfig: IHtmlInjectionConfig ): Plugin {   let config: undefined | ResolvedConfig;    return {     name: "html-injection",      configResolved(resolvedConfig) {       config = resolvedConfig;     },      transformIndexHtml(html: string) {       let out = html;       for (let i = 0; i < htmlInjectionConfig.injections.length; i++) {         const injection: IHtmlInjectionConfigInjection =           htmlInjectionConfig.injections[i];          let root = (config as ResolvedConfig).root;         const filePath = path.resolve(root, injection.path);         let data = fs.readFileSync(filePath, "utf8");         if (injection.type === "js") {           data = `<script>\n${data}\n</script>\n`;         } else if (injection.type === "css") {           data = `<style>\n${data}\n</style>\n`;         }         switch (injection.injectTo) {           case "head":             out = out.replace("</head>", `${data}\n</head>`);             break;           case "head-prepend":             out = out.replace(/<head(.*)>/i, `$&\n${data}`);             break;           case "body":             out = out.replace("</body>", `${data}\n</body>`);             break;           case "body-prepend":             out = out.replace(/<body(.*)>/i, `$&\n${data}`);             break;            default:             break;         }       }        return out;     },   }; } 

В массиве htmlInjectionConfig.injections содержатся описания вставляемых фрагментов кода — конфигурация плагина, передаваемая ему как аргумент в vite.config.js проекта, использующего наш плагин.

// vite.config.js // example for Vue project  import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import { htmlInjectionPlugin } from "vite-plugin-html-injection";  export default defineConfig({   plugins: [     vue(),     htmlInjectionPlugin({       // Example configuration. Can be stored in a separate json file.       injections: [         {           name: "Open Graph",           path: "./src/injections/open-graph.html",           type: "raw",           injectTo: "head",         },         {           name: "Splash screen",           path: "./src/injections/splash-screen.html",           type: "raw",           injectTo: "body-prepend",         },         {           name: "Service worker",           path: "./src/injections/sw.js",           type: "js",           injectTo: "head",         },       ],     }),   ] }); 

В случае написания «локального» плагина для себя, вместо добавления его в devDependencies и импорта из node_modules:

import { htmlInjectionPlugin } from "vite-plugin-html-injection";

можно импортить его локально:

import { htmlInjectionPlugin } from "./src/plugins/vite-plugin-html-injection";

Типы вставляемых фрагментов кода

Существует три типа фрагментов кода, с которыми работает плагин — raw, js и css.

Raw фрагменты вставляются как есть, js и css оборачиваются в теги <script> и <style> соответственно.

Также есть четыре места, куда можно вставить фрагмент кода: начало и конец тега head и начало и конец body. Соответствующие значения свойства injectTo: head-prepend, head, body-prepend и body.

Файл ./src/injections/ga.html может выглядеть как-то так:

<!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-8W4X32XXXX" /> <script>   window.dataLayer = window.dataLayer || [];   function gtag() {     dataLayer.push(arguments);   }   gtag("js", new Date());    gtag("config", "G-8W4X32XXXX"); </script>

Сборка плагина как пакета

В коде плагина мы используем библиотеки fs и path, которые помещать с сборку плагина не нужно, потому что они будут предоставлены самым Vite во время выполнения плагина. В vite.config.js проекта плагина это нужно указать явно.

// vite.config.js  import { defineConfig } from "vite"; import { resolve } from "path";  export default defineConfig({   plugins: [],   build: {     lib: {       entry: resolve(__dirname, "./index.ts"),       name: "HtmlInjection",       fileName: "index",       formats: ["es", "cjs"],     },     rollupOptions: {       external: ["fs", "path"],     },   }, }); 

В итоге размер плагина получается меньше 1КБ.

Ну и package.json плагина:

{   "name": "vite-plugin-html-injection",   "version": "1.1.9",   "description": "Vite plugin for injecting html, js, css code snippets into index.html",   "license": "MIT",   "homepage": "https://github.com/altrusl/vite-plugin-html-injection",   "type": "module",   "main": "./dist/index.cjs",   "module": "./dist/index.js",   "types": "./dist/index.d.ts",   "exports": {     ".": {       "import": "./dist/index.js",       "require": "./dist/index.cjs",       "types": "./dist/index.d.ts"     }   },   "files": [     "dist"   ],   "scripts": {     "build": "vite build && copy types.d.ts dist\\index.d.ts"   },   "devDependencies": {     "@antfu/eslint-config": "^0.39.8",     "@types/node": "^20.4.5",     "eslint": "^8.46.0",     "typescript": "^5.1.6",     "vite": "^4.4.7"   },   "peerDependencies": {     "vite": ">= 2.0.0"   } }

После сборки плагина публикуем его на npmjs.com:

npm publish --access public


ссылка на оригинал статьи https://habr.com/ru/articles/753788/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *