vite插件开发--实现项目的后端集成

前言

本文结合vite后端集成的需求和大家探讨如何开发一款vite插件。

背景

公司的业务是一个低代码PAAS平台,名叫kintone,同时这个低代码平台有非常高的可定制性。它接受用户上传自定义js。

然后你懂得,这样我们就有了非常大的操作空间。可以直接上react,或者vue创建一个前端spa项目后挂载在指定的dom上, 这样平台的界面就完全被我们自定义了。同时它还有rest api。我们能通过rest api获取后端数据,这样业务信息也可以扩展到我们想要展示的页面。

但是它也有一个问题,它引用你上传的js文件的方式是固定的,只能使用同步,不能使用esmodule的方式(也就是type="module")来引入。这样我们就无法使用vite来进行开发构建啦。 所以我就想到了用vite插件来改造它。

这里也用它来作为一个例子,相信也有很多小伙伴有遇到过同样的问题,其实这个问题就是泛化为后端html渲染已经固定,你如何通过vite来服务其他资产。

翻看vite文档。其实有专门的一章来讲解后端集成

vite后端集成

分析文档

我们今天就来参考这篇vite后端集成文档,一步一步分析该如何解决这些问题。

1. 启用和创建manifest

首先在你使用vite配置文件中

js 复制代码
// vite.config.js
export default defineConfig({
  build: {
    manifest: true,
  }
})

2. 禁用module preload 的 polyfill

参考文档,也就是build.modulePreload.polyfill设置为false

js 复制代码
// vite.config.js
export default defineConfig({
  build: {
    modulePreload: { polyfill: false },
  }
})

这两件事都是build阶段要做的,所以后面我们要在插件的生命周期的build的阶段加上去。

3. 注入文件

在开发环境中,在服务器的 HTML 模板中注入以下内容

js 复制代码
<!-- 如果是在开发环境中 -->
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/main.js"></script>

这部分,注入是关键。但是这个本地环境地址,包括端口,其实是随用户设置可变的,我们在插件中需要获取到用户的自定义配置,然后动态生成,并且注入。

4. 得到正确的资源路径

为了正确地提供资源,你有两种选项:

  • 确保服务器被配置过,将会拦截代理资源请求给到 Vite 服务器
  • 设置 server.origin 以求生成的资源链接将以服务器 URL 形式被解析而非一个相对路径

这对于图片等资源的正确加载是必需的。
这部分其实挺关键,我们可以通过第二种选项来实现。就是在serve阶段,设置server.config.server.origin为我们本地地址即可。

5. react框架的Inject

如果你正使用 @vitejs/plugin-react 配合 React,你还需要在上述脚本前添加下面这个,因为插件不能修改你正在服务的 HTML:

js 复制代码
<script type="module">
  import RefreshRuntime from 'http://localhost:5173/@react-refresh'
  RefreshRuntime.injectIntoGlobalHook(window)
  window.$RefreshReg$ = () => {}
  window.$RefreshSig$ = () => (type) => type
  window.__vite_plugin_react_preamble_installed__ = true
</script>

同样我们需要获取用户实际的本地开发地址,端口。并且进行注入。

插件开发

接下来,我们就结合刚才分析的文档,一步步实现这个插件。

vite生命周期

我们先找到Vite 独有钩子这块。

config钩子

这个生命周期是用来获取用户的设置,并且可以通过这个钩子加入你想给用户额外添加的设置。此时回顾到上文1和2部分,就是需要我们在build的配置。 那我们就把这个配置在config这个生命周期中重写。

js 复制代码
  config(config, env) {
    envConfig = env;
    const entry = getEntry(config);
    config.build = {
      modulePreload: { polyfill: false },
      manifest: true,
      cssCodeSplit: false,
      rollupOptions: {
        input: entry,
        output: {
          format: "iife",
        },
      },
    };
    ...
  }

github代码

configResolved钩子

这个钩子和config的那个钩子有什么不同呢?这个钩子已经不再支持插件对用户配置信息的修改了。通过ts也可以观察到,他接受的参数也是Readonly了。

那这个钩子能做什么?比如说我需要写一些配置到.env中,此时我们就能在这个钩子里进行操作,因为这个钩子中读到的是vite的最终配置,它不会被改了,此时读到的.env所在的文件夹等等都是最终的。然后我们就能通过inquirer等库,建立和用户的交互,写入一些用户的自定义设置到.env文件中。

js 复制代码
const envDir = viteConfig.envDir
  ? normalizePath(path.resolve(resolvedRoot, viteConfig.envDir))
  : resolvedRoot;


let envUrl =
  envConfig.mode === "development"
    ? path.resolve(envDir, ".env.development")
    : path.resolve(envDir, ".env.production");


const envContent = { ...existingEnv, ...env };


// 将环境变量写入到.env文件
const envContentStr = Object.entries(envContent)
  .map(([key, value]) => `${key}=${value}`)
  .join("\n");
fs.writeFileSync(envUrl, envContentStr);

github代码

configureServer钩子

最后是configureServer这个钩子,因为它能获取到服务器启动的配置,所以之前提到3,4,5这些部分,需要知道本地配置,端口的这些代码,就需要写在这个钩子中。
获取本地开发的服务器信息:

js 复制代码
import { type ViteDevServer } from "vite";

function isIpv6(address: any) {
  return address.family === "IPv6" || address.family === 6;
}

export default function getServerInfo(server: ViteDevServer) {
  const address = server.httpServer?.address();
  if (!address || typeof address === "string") {
    console.error("Unexpected dev server address", address);
    process.exit(1);
  }
  const protocol = server.config.server.https ? "https" : "http";
  const host = isIpv6(address) ? `[${address.address}]` : address.address;
  const port = address.port;
  const devServerUrl = `${protocol}://${host}:${port}`;
  return devServerUrl;
}

设置server.config.server.origin

js 复制代码
const devServerUrl = getServerInfo(server);
if (!server.config.server.origin) {
server.config.server.origin = devServerUrl;
}

inject js代码

js 复制代码
import type { ScriptList } from "kintone-types";

function reactInject(devServerUrl: string) {
  return `const scriptElement = document.createElement("script");
  scriptElement.type = "module";
  scriptElement.textContent = \`import RefreshRuntime from '${devServerUrl}/@react-refresh';
  RefreshRuntime.injectIntoGlobalHook(window);
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => (type) => type;
  window.__vite_plugin_react_preamble_installed__ = true;\`;
  document.body.appendChild(scriptElement);`;
}

export default function kintoneModuleInject(
  devServerUrl: string,
  scriptList: ScriptList,
  react?: boolean
): string {
  return `(function () {
    const viteClientInject = document.createElement("script");
    viteClientInject.type = "module";
    viteClientInject.src = "${devServerUrl}"+'/@vite/client';
    document.body.appendChild(viteClientInject);
    ${react ? reactInject(devServerUrl) : ""}
    const scriptList = ${JSON.stringify(scriptList)};
    function loadScript(src,type) {
      const script = document.createElement("script");
      script.type = type;
      script.src = "${devServerUrl}"+src;
      document.body.appendChild(script);
    }
    for (const script of scriptList){
      const {src,type}=script
      loadScript(src,type)
    }
  })();
  `;
}

github代码

开发(serve)和构建(build)模式的区分

因为1,2部分是build阶段的。其它部分,只需要在serve阶段执行就行了,那我们可以用 apply: "serve" 或者 apply: "build"。这个按需调用参数来指定它在哪种模式下执行。

插件开发中遇到的问题以及一些工具

命令交互工具

推荐使用@inquirer/prompts 而不是 inquirer。 @inquirer/prompts是重构后的新版本。inquirer是老版本。老版本不仅9.x只支持esm,而且在进行构建时还有bug(8.x)。

esm打包

很多工具都已经只发布esm版本,会导致导入打包后出现ESM的引用错误,这个问题比较复杂,可以参考其他小伙伴总结的一些经验 typescript 项目中的 esm 模块依赖问题,简单的解决办法就是改用一些老版本来尝试。

插件打包工具

插件打包工具从一开始最基本的tsc,到tsup,最后到unbuild,配置一步步简化。如果你想得到更多有帮助的预设,减少配置,就直接使用unbuild吧。

开源发包库

使用bumpp

同时建立一个npm script: "release": "npm run build && bumpp"

可以让你发布大版本,小版本,建立tag。非常方便。

changelog

写changelog,可以使用这个:changelogen

发布到npm

一条命令:npm publish

统计图标

类似于这些图标都是来源于shields.io这个网站来提供服务。

本文vite插件项目git地址

github地址

运用文章

#使用vite插件进行kintone自定义开发(手机版自定义范例)

相关推荐
Martin -Tang8 小时前
vite和webpack的区别
前端·webpack·node.js·vite
Amd7941 天前
Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解
json·vite·配置·nuxt·验证·钩子·动态
Amd7942 天前
Nuxt.js 应用中的 schema:resolved 事件钩子详解
json·自定义·vite·配置·nuxt·schema·钩子
Amd7943 天前
Nuxt.js 应用中的 vite:extendConfig 事件钩子详解
自定义·开发·vite·配置·nuxt·构建·钩子
Amd7944 天前
Nuxt.js 应用中的 vite:extend 事件钩子详解
自定义·开发·vite·nuxt·插件·构建·钩子
friend_ship6 天前
Vite与Vue Cli的区别与详解
vue.js·webpack·rollup·vite·vue脚手架·vue cli
Amodoro6 天前
解决vite项目tailwindcss不生效!!(Vue3、tailwindcss失效)
前端·javascript·vue.js·vite·tailwindcss
zykk7 天前
一文带你彻底了解vite
前端·面试·vite
王小金Ryan10 天前
开发一个Vite插件,给所有DOM节点插入自定义属性
vue.js·vite
前端霸王防脱发洗发水12 天前
Vite常用插件配置
javascript·vue.js·vite