我是如何在vite中使用第三方脚本的?

场景

不知各位是否遇到过以下痛点:

  • 希望在页面加载前执行一些脚本。比如修改网页html字体大小、注入全局变量等
  • 希望第三方脚本可以content-hash缓存
  • 出于项目维护性考虑(或强迫症,比如本人),想把这些js脚本改造为ts

思考方案

为了解决以上痛点,我想到了以下的方案:

方案1

搞个脚本来编译ts,然后把输出的js文件放在项目的public文件夹,在html中直接通过绝对路径访问即可

这样做有一些缺点:

  1. 如果希望生成的js带hash,那引用js的时候不得不使用硬编码
  2. 执行编译操作的时机不好把控,可能会增加开发者的维护负担
  3. 不容易跟vite hmr联动起来

方案2

借助vite强大的插件能力,解决以上缺点

  1. 把指定目录中的ts编译成js,生成一个manifest清单。开发者只需根据清单来查找脚本然后自行注入js脚本,解决了硬编码的问题
  2. 利用vite插件的某个合适hook中执行编译操作
  3. 使用vite server的websocket,自行控制hmr

方案有了,开搞开搞

写vite插件

首先,我需要知道开发者指定哪个目录为ts的输入目录

在插件初始化的时候获取开发者输入的参数即可

javascript 复制代码
function publicTypescript(options: VPPTPluginOptions = {}) {
  const opts = {
    ...DEFAULT_OPTIONS, // 默认的配置
    ...options,
  }
}

然后,需要获取到vite的配置,比如 root/publiDir 等,适合做这件事的vite插件hook是 configResolved

然后结合开发者传入的配置,解析到输入的ts文件的地址,作为之后编译的入口使用

javascript 复制代码
let viteConfig: ResolvedConfig

async configResolved(c) {
    viteConfig = c
    // 解析项目的根目录
    const resolvedRoot = normalizePath(viteConfig.root ? path.resolve(viteConfig.root) : process.cwd())

    // 确保用户的ts输入目录存在,若不存在则创建
    fs.ensureDirSync(getInputDir(resolvedRoot, opts.inputDir))

    // 把ts输入目录下的ts全部获取
    const tsFilesGlob = await glob(getInputDir(resolvedRoot, opts.inputDir, `/*${TS_EXT}`), {
      cwd: resolvedRoot,
      absolute: true,
    })

    // 初始化全局配置
  	globalConfigBuilder.init({
      tsFilesGlob,
      viteConfig,
      ...opts,
    })
}

拿到了待编译的ts文件,那咱需要用到编译工具。使用 esbuild ,跟vite底层保持一致,而且 esbuild 速度很快

在vite的 buildStart 阶段编译即可

csharp 复制代码
async buildStart() {
  const { tsFilesGlob } = globalConfigBuilder.get()
  // 这个方法需要做的就是把ts全部编译成js
  // 具体代码就不贴了
  // 代码地址在:https://github.com/hemengke1997/vite-plugin-public-typescript/blob/master/src/helper/build.ts
  await buildAll(tsFilesGlob)
}

编译后使用把结果输出为js文件,然后写在 public 文件夹中

csharp 复制代码
async addNewJs(args: IAddFile): Promise<void> {
  const { code = '' } = args
  const {
    viteConfig: { publicDir },
  } = globalConfigBuilder.get()

  const outPath = this.setCache(args, globalConfigBuilder.get())

  const fp = normalizePath(path.join(publicDir, outPath))

  await fs.ensureDir(path.dirname(fp))

	// 写到disk
  writeFile(fp, code)
}

成功后可以在 publicDir 中看到编译后的js文件

开发者如何引入这些js文件呢,总不能使用全路径 'file.contenthash.js' 这种方式引入吧,太硬核了。

如果你看到vite ssr的打包产物的话,你可能知道一个名为 ssr-manifest的json映射文件,其中就是把每个文件跟其依赖文件地址对应映射起来

那咱也搞个manifest文件来做映射,只需要在编译ts文件的时候,同时写一下manifest.json就可以了

最后的得到的效果是:

于是,咱可以在项目代码中使用 manifest.xx来引用对应的js文件了

配合vite的 transformIndexHtml hook,可以很方便地插入js

javascript 复制代码
import manifest from './public-typescript/manifest.json'


async transformIndexHtml(html) {
  const tags: HtmlTagDescriptor[] = [
    {
      tag: 'script',
      attrs: {
        'src': manifest.test,
      },
      injectTo: 'head-prepend',
    },
  ]
  return {
    html,
    tags,
  }
},

最后启动项目,就可以在控制台中看到相应的js请求了

至此,核心的步骤就走完了,完成了插件的工作之后,咱就可以很方便的在项目中写一些三方ts脚本了

以上是最基础的版本,咱还可以做很多的优化:

  • 监听ts文件变动,触发hmr
  • 开发环境不生成实体文件,减少开发者的心智负担
  • 暴露更多的配置项给开发者,让开发者自行控制编译的输出结果
  • 支持 vite 的环境变量
  • 。。。。。

具体实现可以看源码

放一个gif图看看效果,嘿嘿

最后

希望此插件可以帮助到大家伙 插件GitHub地址

相关推荐
带娃的IT创业者9 分钟前
开源之魂:Thunderbird 的生存困境与我们的数字主权
开源·邮件客户端·开源生态·非营利组织·thunderbird·数字主权
IvorySQL11 分钟前
开源共建分论坛圆桌讨论:如何真正融入 PostgreSQL 社区?
postgresql·开源·区块链
小歪不歪我是AI1 小时前
Pi 源码拆解:当一个极简主义的 agent harness 只有 4 个 tool
开源·agent
Teable任意门互动2 小时前
深度解析:AI 赋能开源多维表格,实现企业全场景数据整合与高效应用
数据库·人工智能·低代码·信息可视化·开源·数据库开发
码途漫谈2 小时前
把前端组件做成一座小岛:Animal-Island-UI 的自然风 React 组件库拆解
前端·开源
星栈2 小时前
Rust 全栈项目里,我写了一个不再重复造轮子的泛型表格组件
前端·前端框架·开源
008爬虫实战录2 小时前
【码上爬】 题九:webpack调试 堆栈分析
前端·webpack·node.js
码途漫谈2 小时前
让 Coding Agent 记得住:agentmemory 的长期记忆系统拆解
开源·ai编程
不爱吃糖的程序媛2 小时前
贡献指南 | 参与 Harmonybrew 开源社区共建规范
开源
日取其半万世不竭2 小时前
OpenCost:Kubernetes 成本监控,开源的云资源费用分析
容器·kubernetes·开源