我是如何在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地址

相关推荐
修己xj6 小时前
Anki:让记忆更高效、更智能的开源力量
开源
冬奇Lab12 小时前
一天一个开源项目(第17篇):ViMax - 多智能体视频生成框架,导演、编剧、制片人全包
开源·音视频开发
一个处女座的程序猿14 小时前
AI之Agent之VibeCoding:《Vibe Coding Kills Open Source》翻译与解读
人工智能·开源·vibecoding·氛围编程
一只大侠的侠15 小时前
React Native开源鸿蒙跨平台训练营 Day16自定义 useForm 高性能验证
flutter·开源·harmonyos
IvorySQL15 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
一只大侠的侠16 小时前
Flutter开源鸿蒙跨平台训练营 Day11从零开发商品详情页面
flutter·开源·harmonyos
一只大侠的侠16 小时前
React Native开源鸿蒙跨平台训练营 Day18自定义useForm表单管理实战实现
flutter·开源·harmonyos
一只大侠的侠16 小时前
React Native开源鸿蒙跨平台训练营 Day20自定义 useValidator 实现高性能表单验证
flutter·开源·harmonyos
晚霞的不甘17 小时前
Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
人工智能·算法·flutter·架构·开源·音视频
天下代码客18 小时前
使用electronc框架调用dll动态链接库流程和避坑
前端·javascript·vue.js·electron·node.js