vite项目,如何使script提前执行?

前言

相信大家都遇到过这么个场景,就是某些script代码需要在业务代码执行之前执行

通俗来说,就是在框架 如 ReactDOM.render,或者 Vue.mount 之前执行一些script代码

解决方案

看到这里的你会觉得:这个问题很好解决,不就是把script标签放在header上吗

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>这里写一些代码,比如设置html字体大小</script>
  </head>
  <body>


    <script src="/src/main.ts"></script>
  </body>
</html>

可是在typescript大行其道的时代,硬写js并不是一个稳健的选择。

在vite中,我们可以写typescript脚本,直接放在html中即可。因为vite会从index.html作为入口打包,所以html中的ts也会被构建打包。

很简单:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
+   <script src="/some-scripts/hello-world.ts"></script>
  </head>
  <body>


    <script src="/src/main.ts"></script>
  </body>
</html>

当vite解析到 /some-scripts/hello-world.ts 这个脚本时,会自然经过vite的处理,即使代码中包含了三方库,也会被vite通通处理

hello-world.ts

console.log('hello world!')
console.log(import.meta.env, 'import.meta.env')

vite 运行起来看看打印结果:

vite项目,如何使script提前执行?

而这些代码,是在我们的App被挂载之前执行的,这样做的意义是什么呢?

假设你的网页需要做rem适配,如果在 useEffect 中设置字体,在进入页面的一瞬间,网页字体是原始的 16px ,而在DOM挂载后,才设置html字体大小,导致网页短暂的变形(如果网页原始字体跟当前页面分辨率应该展示的字体不同的情况下)

打包

可以看到,执行顺序也是ok的

vite项目,如何使script提前执行?

但是打包后,在没有手动拆包的情况下,代码都打到一起去了

一般来说,需要提前执行的代码,几乎不会有变动,所以我们最好是手动拆包,这样有利于缓存

// https://vitejs.dev/config/
export default defineConfig(() => ({
  define: {
    custom_define: JSON.stringify('custom define!'),
    hello_world: JSON.stringify({ hello: 'world' }),
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes('some-scripts/hello-world.ts')) {
            return 'hello-world'
          }
        },
      },
    },
  },
}))

然后重新打包,看看结果:

vite项目,如何使script提前执行?vite项目,如何使script提前执行?

然后 vite preview 看看效果(这是打包后的效果):

vite项目,如何使script提前执行?

看到这里你可能会疑惑,index.html中明明是 index.js 在 hello-world.js 之前,打印顺序怎么还是hello-word先执行呢?

因为 index 中引入了 hello-world,而 link modulepreload 是告诉浏览器,预加载一个 JavaScript 模块,但并不执行它。

vite项目,如何使script提前执行?

兼容性处理

正式构建时,我们通常需要做代码兼容,在vite中,我们使用 @vitejs/pliugin-leagcy 做传统浏览器兼容,关于这个vite插件,不了解的同学可以看我的另一篇文章

在这里,我们直接引入legacy插件,看看效果即可

import legacy from '@vitejs/plugin-legacy'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig(() => ({
  define: {
    custom_define: JSON.stringify('custom define!'),
    hello_world: JSON.stringify({ hello: 'world' }),
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes('some-scripts/hello-world.ts')) {
            return 'hello-world'
          }
        },
      },
    },
  },
  // 使用legacy插件做传统浏览器兼容
  plugins: [legacy()],
}))

打包后可以看到 hello-world 也打了一个legacy包,完全OK!

vite项目,如何使script提前执行?

小总结

至此,我们对目前普遍的 spa vite项目做了一遍处理,那对于ssr项目,又该如何处理呢?

SSR服务端渲染处理

SSR项目目前主要有两种形式:

  1. 动态返回html字符串或stream流
  2. 根据html文件解析后返回

普遍来说,SSR框架都是第一种形式,比如Astro,Nuxt,

上文说的解决方案处理第二种情况,我们先抛开SSR框架,用原生的vite来做SSR,看看结果如何

我们先用模板命令生成一个vite SSR项目

pnpm create vite-extra

vite项目,如何使script提前执行?

这里我选择纯原生的,能最大程度简化教程的复杂度

生成好了之后,我们直接往index.html添加typescript文件,试试能否生效:

老套路了

<!doctype html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ssr</title>
    <script type="module" src="/some-scripts/hello-world.ts"></script>

  </head>

  <body>
    <div id="app"><!--app-html--></div>
    <script type="module" src="/src/entry-client.tsx"></script>
  </body>

</html>

开发环境

启动!查看浏览器打印台,没问题!

vite项目,如何使script提前执行?

生产环境

打包后预览再看看效果:

vite项目,如何使script提前执行?

也没问题!

分包

同样的,我们也分包,做好缓存效果

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes('some-scripts/hello-world.ts')) {
            return 'hello-world'
          }
        },
      },
    },
  },
})

构建之后,可以看到分包成功了

vite项目,如何使script提前执行?

兼容性处理

引入 @vitejs/plugin-legacy插件,打包。看看结果:

vite项目,如何使script提前执行?

ok,构建后运行也没问题

SSR框架

SSR框架通常是没有html文件的,都是动态返回html字符串或者string,这种情况下,我们无法从html入口下手

推荐使用 vite-plugin-public-typescript

使用方式:

import { defineConfig } from 'vite'
import { injectScripts, publicTypescript } from 'vite-plugin-public-typescript'

// https://vitejs.dev/config/
export default defineConfig(() => ({
  define: {
    custom_define: JSON.stringify('custom define!'),
    hello_world: JSON.stringify({ hello: 'world' }),
  },
  plugins: [
    publicTypescript({
      destination: 'file',
    }),
  ],
}))

这个插件会默认读取根目录下的 public-typescript文件目录,然后解析所有的typescript文件,并构建成js。

有两种方式引用ts文件

其一是在vite配置中

import { defineConfig } from 'vite'
import { injectScripts, publicTypescript } from 'vite-plugin-public-typescript'

// https://vitejs.dev/config/
export default defineConfig(() => ({
  define: {
    custom_define: JSON.stringify('custom define!'),
    hello_world: JSON.stringify({ hello: 'world' }),
  },
  plugins: [
    publicTypescript(),
    // 在这里通过 injectScrpts 把代码插入到最后的html中
    injectScripts((manifest) => [
      {
        attrs: { src: manifest['hello-world'] },
        injectTo: 'head',
      },
    ]),
  ],
}))

其二直接引入manifest

import { manifest } from 'vite-plugin-public-typescript/client'

console.log(manifest['hello-world'])

这个manifest里面包含了js对应的资源地址

vite项目,如何使script提前执行?

vite项目,如何使script提前执行?

总结

希望把某些script在页面挂载前执行,在大部分情况下,在 index.html 中直接写 script ts 就可以了,少部分ssr的情况下,可以使用 vite-plugin-public-typescript 插件辅助

原文链接:https://juejin.cn/post/7348762390387064872 作者:minko

(0)
上一篇 2024年3月22日 下午5:08
下一篇 2024年3月23日 上午10:00

相关推荐

发表评论

登录后才能评论