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

前言

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

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

解决方案

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

html 复制代码
<!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也会被构建打包。

很简单:

html 复制代码
<!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

ts 复制代码
console.log('hello world!')
console.log(import.meta.env, 'import.meta.env')

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

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

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

打包

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

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

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

ts 复制代码
// 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 preview 看看效果(这是打包后的效果):

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

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

兼容性处理

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

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

ts 复制代码
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!

小总结

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

SSR服务端渲染处理

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

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

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

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

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

bash 复制代码
pnpm create vite-extra

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

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

老套路了

html 复制代码
<!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>

开发环境

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

生产环境

打包后预览再看看效果:

也没问题!

分包

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

ts 复制代码
import { defineConfig } from 'vite'

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

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

兼容性处理

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

ok,构建后运行也没问题

SSR框架

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

推荐使用 vite-plugin-public-typescript

使用方式:

ts 复制代码
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配置中

ts 复制代码
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

ts 复制代码
import { manifest } from 'vite-plugin-public-typescript/client'

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

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

总结

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

相关推荐
Darling02zjh24 分钟前
GUI图形化演示
前端
Channing Lewis26 分钟前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖37 分钟前
Web 架构之状态码全解
前端·架构
showmethetime44 分钟前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
左钦杨2 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
NaclarbCSDN2 小时前
Java集合框架
java·开发语言·前端