
Gatsby Script API 深度指南用内置Script组件高效管理第三方脚本【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby本文基于 Gatsby 官方文档 Gatsby Script API自gatsby4.15.0起内置并结合仓库源码编写。Gatsby 内置的Script组件用于以高性能方式加载第三方脚本它为开发者提供了声明式加载策略Loading Strategies的能力并内置了开箱即用的默认策略同时支持带src的远程脚本与内联脚本。阅读本文后你将掌握Script组件的全部用法、三种加载策略的适用场景、基于 Partytown 的实验性off-main-thread策略的完整配置流程以及如何利用onLoad/onError回调实现脚本的依赖加载——从而在不牺牲核心 Web 指标如 Total Blocking Time的前提下为站点安全地接入分析、标签管理等第三方脚本。为什么需要 Gatsby Script API传统做法中开发者通常直接用原生script标签配合async或defer在页面中引入第三方脚本。Gatsby 文档明确指出这样做存在一个隐患——脚本很可能与负责页面水合hydration的框架 JavaScript并行加载从而干扰页面进入可交互状态对 Total Blocking TimeTBT 等关键 Web 指标产生负面影响。Gatsby 内置的Script组件源码位于 packages/gatsby-script/src/gatsby-script.tsx正是为解决这类问题而生提供声明式的加载策略post-hydrate、idle、off-main-thread默认的post-hydrate策略让站点在“零配置”下也能获得良好的加载性能同时支持带src的远程脚本与内联脚本内置去重、回调、代理等能力把管理脚本的“重活”交给 Gatsby。快速上手在页面中使用Script在站点的 JSX 或 TSX 源文件中从gatsby导入Script组件即可import React from react import { Script } from gatsby function MyPage() { return Script srchttps://my-example-script / } export default MyPage如果你已经有使用原生script标签的存量代码迁移成本极低只需导入Script并把小写的script标签名改成大写的Scriptimport React from react import { Script } from gatsby function MyPage() { return ( - script srchttps://my-example-script / Script srchttps://my-example-script / ) } export default MyPage默认情况下Script组件会在页面水合hydration完成之后再加载脚本。关于加载策略的详细说明见下文 加载策略 一节。补充说明你无需单独安装gatsby-script包它自gatsby4.15.0起已作为 Gatsby 主包的一部分对外可用见 packages/gatsby-script/README.md。两种脚本形态带 src 的脚本与内联脚本Script组件支持两种类型的脚本。带 src 的脚本Scripts with sources通过src属性指定脚本地址即可Script srchttps://my-example-script /Script组件会以src的值作为去重依据如果在同一页面上引入了两个src相同的脚本只会有一个被加载。这一行为在源码中有明确实现——gatsby-script.tsx 中通过scriptCache一个Setstring记录已经注入过 DOM 的脚本键injectScript在注入前会先检查scriptCache.has(scriptKey)命中则直接返回null避免重复注入。如果出于某种原因你确实需要在同一页面上加载两个相同src的脚本可以为它们分别提供唯一的id属性组件就会尝试加载两份Script idfirst-unique-id srchttps://my-example-script / Script idsecond-unique-id srchttps://my-example-script /从源码看scriptKey id || srcgatsby-script.tsx因此指定了不同的id后两个脚本会被视为不同的键而同时加载。内联脚本Inline scripts内联脚本必须携带唯一的id属性且可以通过以下两种方式定义通过 React 特殊的dangerouslySetInnerHTML属性通过模板字符串children。两种写法如下Script idfirst-unique-id dangerouslySetInnerHTML{{ __html: alert(Hello world) }} / Script idsecond-unique-id{alert(Hello world)}/Script从功能上讲这两种定义内联脚本的方式是等价的。源码中的resolveInlineScript也印证了这一点它优先取dangerouslySetInnerHTML.__html否则取childrenfunction resolveInlineScript(props: ScriptProps): string { const { dangerouslySetInnerHTML, children } props || {} const { __html: dangerousHTML } dangerouslySetInnerHTML || {} return (dangerousHTML as string) || children }加载策略Strategies通过strategy属性声明加载策略。可用的加载策略共有三种策略说明post-hydrate默认页面水合完成后加载idle页面进入空闲状态后加载off-main-thread实验性通过 Partytown 在 Web Worker 中、主线程之外加载对应的 JSX 写法Script srchttps://my-example-script strategypost-hydrate / Script srchttps://my-example-script strategyidle / Script srchttps://my-example-script strategyoff-main-thread /在 TSX 文件中也可以使用 Gatsby 导出的ScriptStrategy枚举import { Script, ScriptStrategy } from gatsby Script srchttps://my-example-script strategy{ScriptStrategy.postHydrate} / Script srchttps://my-example-script strategy{ScriptStrategy.idle} / Script srchttps://my-example-script strategy{ScriptStrategy.offMainThread} /在源码中ScriptStrategy枚举定义于 gatsby-script.tsxexport enum ScriptStrategy { postHydrate post-hydrate, idle idle, offMainThread off-main-thread, }组件内部通过switch (strategy)分发不同的注入逻辑gatsby-script.tsxpost-hydrate直接调用injectScriptidle将注入逻辑包装在requestIdleCallback中off-main-thread则将脚本属性收集进按页面维护的映射表中由 Gatsby 在服务端/构建期统一处理。Post hydrate 策略默认post-hydrate是默认加载策略当你不指定strategy属性时即采用此策略。该策略的优势在于你可以声明脚本在水合hydration之后才开始加载。水合是页面变得可交互的关键阶段如果使用普通script标签即使加了async或defer脚本仍可能与负责水合的框架 JavaScript 并行加载从而影响 Total Blocking Time 等关键 Web 指标。而使用post-hydrate策略可以确保脚本不干扰页面达到可交互状态为用户带来更好的体验。post-hydrate适合需要让脚本尽早加载、但又不想影响站点“可交互时间”的场景。Idle 策略idle策略与post-hydrate类似同样在水合之后加载区别在于idle会告诉浏览器在主线程空闲时才加载脚本。也就是说如果页面正在执行其他关键任务例如 DOM 操作或其他占用主线程的计算脚本会等这些工作完成后再开始加载。idle策略适合希望脚本以“不与其他主线程工作竞争”的方式加载的场景。从实现上看Gatsby 在浏览器环境优先使用原生的requestIdleCallback并在不支持的环境下回退到基于setTimeout的 shim见 packages/gatsby-script/src/request-idle-callback-shim.tsexport const requestIdleCallback (typeof self ! undefined self.requestIdleCallback self.requestIdleCallback.bind(window)) || function (cb: IdleRequestCallback): number { const start Date.now() return setTimeout(function () { cb({ didTimeout: false, timeRemaining: function () { return Math.max(0, 50 - (Date.now() - start)) }, }) }, 1) as unknown as number }Off main thread 策略实验性与前两种策略不同off-main-thread通过 Partytown 在 Web Worker 中加载脚本。这意味着脚本求值的负担不再由主线程承担主线程得以腾出来处理其他关键任务。注意由于 Partytown 目前仍处于beta阶段off-main-thread策略被视为实验性功能。它受某些限制约束并且根据你的使用场景可能比其他加载策略需要更多的配置。以下示例使用off-main-thread策略加载 Google Analytics 4process.env.GTAG是你定义在.env.production与.env.development文件中的 GA4 标识符import { Script } from gatsby Script src{https://www.googletagmanager.com/gtag/js?id${process.env.GTAG}} strategyoff-main-thread / Script idgtag-config strategyoff-main-thread forward{[gtag]} { window.dataLayer window.dataLayer || []; function gtag(){dataLayer.push(arguments)}; gtag(js, new Date()); gtag(config, ${process.env.GTAG}, { page_path: location ? location.pathname location.search location.hash : undefined }) } /Script事件转发Forward collectionGatsby 会收集页面上的所有off-main-thread脚本并自动将各脚本通过forward属性声明的 Partytown 转发事件 合并为每个页面的一份统一配置Script src{https://www.googletagmanager.com/gtag/js?id${process.env.GTAG}} strategyoff-main-thread forward{[dataLayer.push]} /forward是Script组件中唯一由组件本身处理的 Partytown 专属属性。在源码中ScriptProps接口为forward?: Arraystringgatsby-script.tsx且forward不在handledProps集合中因此会被resolveAttributes透传到最终的script typetext/partytown元素上。代理配置Proxy configuration所有提供给off-main-thread策略的 URL都会被 Gatsby 代理到/__third-party-proxy?url${YOUR_URL}。原因在于许多第三方脚本需要代理才能在 Partytown 中正常工作Gatsby 因此内置了代理功能以简化这一过程。为保证代理安全你必须在 Gatsby 配置中通过partytownProxiedURLs键声明允许代理的绝对 URL。如果不这样做请求将返回 404。针对上面的 Google Analytics 示例配置如下import dotenv from dotenv dotenv.config({ path: .env.${process.env.NODE_ENV}, }) module.exports { siteMetadata: { title: Gatsby, }, partytownProxiedURLs: [ https://www.googletagmanager.com/gtag/js?id${process.env.GTAG} ], }这部分能力在gatsby develop、gatsby serve以及 Gatsby Cloud 上开箱即用。其底层实现位于 packages/gatsby/src/internal-plugins/partytown/代理路径常量定义于 proxy.tsexport const thirdPartyProxyPath /__third-party-proxy代理中间件通过filter校验请求的url查询参数是否命中partytownProxiedURLs白名单未命中则直接拒绝转发在 gatsby-node.ts 中Gatsby 会针对partytownProxiedURLs中的每个 URL通过createRedirect动作创建从/__third-party-proxy?url...到目标 URL、状态码为 200 的重定向同时在onCreateDevServer中把代理挂载到开发服务器配置项的校验与类型声明分别见 joi-schemas/joi.tspartytownProxiedURLs: Joi.array().items(Joi.string())与 packages/gatsby/index.d.ts。其他托管平台需要支持 Gatsby 的createRedirect动作才能把/__third-party-proxy?url${YOUR_URL}的请求以 200 状态码重写到YOUR_URL。你需要与托管服务商确认其是否支持该能力。自定义 URL 解析Resolving URLs你可以利用 Partytown 的 vanilla config 来处理off-main-thread脚本中 Partytown 专属的行为。其中resolveUrl选项允许你修改由 Partytown 处理的 URL。resolveUrl的典型使用场景是标签管理器脚本例如 Google Tag Manager。这类脚本与 Partytown 配合较为困难因为它们内部包含的其他脚本会发起其他请求这些请求是否需要被代理取决于 CORS 设置。此时可以用resolveUrl处理这些子脚本的 URL。以下示例使用 Google Tag ManagerGTM加载 Google AnalyticsUniversal Analytics注意此示例假设你已在 Google Tag Manager 后台配置好使用 Universal Analytics。首先加载 GTM 脚本并发送初始化事件process.env.GTM是你的 GTM 标识符定义在.env.production与.env.development文件中import { Script } from gatsby Script src{https://www.googletagmanager.com/gtm.js?id${process.env.GTM}} strategyoff-main-thread forward{[dataLayer.push]} / Script idgtm-init strategyoff-main-thread { window.dataLayer window.dataLayer || [] window.dataLayer.push({ gtm.start: new Date().getTime(), event: gtm.js }) } /Script然后在 Partytown 的 vanilla config 中定义resolveUrl处理由 GTM 加载的 Google Analytics 脚本import React from react export const onRenderBody ({ setHeadComponents }) { setHeadComponents([ script keypartytown-vanilla-config dangerouslySetInnerHTML{{ __html: partytown { resolveUrl(url, location) { if (url.hostname.includes(google-analytics)) { // Use a secure connection if (url?.protocol http:) { url new URL(url.href.replace(http, https)) } // Point to our proxied URL const proxyUrl new URL(location.origin /__third-party-proxy) proxyUrl.searchParams.append(url, url) return proxyUrl } return url } }, }} /, ]) }最后把 Google Analytics 的 URL 加入partytownProxiedURLs让 Gatsby 知道该 URL 是允许代理的安全地址import dotenv from dotenv dotenv.config({ path: .env.${process.env.NODE_ENV}, }) module.exports { siteMetadata: { title: Gatsby, }, partytownProxiedURLs: [ https://www.googletagmanager.com/gtm.js?id${process.env.GTM}, https://www.google-analytics.com/analytics.js, ] }至此Google Tag Manager 与 Google Analytics 脚本都应能在你的站点中成功加载。调试Debugging同样借助 Partytown 的 vanilla config你可以为off-main-thread脚本开启调试模式import React from react export const onRenderBody ({ setHeadComponents }) { setHeadComponents([ script keypartytown-vanilla-config dangerouslySetInnerHTML{{ __html: partytown { debug: true }, }} /, ]) }你可能需要把开发者工具的日志级别调到 verbose才能在控制台看到额外的日志输出。限制Limitations由于依赖 Partytown使用off-main-thread策略的脚本还必须了解 Partytown 文档中列出的权衡与限制。该策略虽然强大但未必是所有场景的最佳方案。以下限制需要 Partytown 上游做出变更才能解除onLoad与onError回调不受支持脚本仅在 SSR服务端渲染导航时加载例如普通a标签跳转而不会在 CSR客户端渲染导航时加载例如 GatsbyLink跳转。此外off-main-thread策略不能在wrapRootElementAPI 中使用——因为脚本收集依赖 location provider。请改用wrapPageElementAPI。这一限制与源码实现密切相关GatsbyScript组件内部通过useLocation()获取当前pathname并把off-main-thread脚本的属性写入 collected-scripts-by-page.tsx 中按pathname组织的 MapcollectedScriptsByPage.set(pathname, attributes)见 gatsby-script.tsx供 Gatsby 在渲染页面 HTML 时取出并输出。在 Gatsby SSR 与 Browser API 中使用Script组件还可以用于以下 Gatsby SSR 与 Gatsby Browser APIwrapPageElementwrapRootElement注意如果你使用了这些 API建议同时在 Gatsby SSR 与 Gatsby Browser 中实现。常见的模式是定义一个单一函数在两个文件中分别导入使用。以下示例在 Gatsby SSR 和 Browser 中通过wrapPageElement使用Script且不重复代码import React from react import { Script } from gatsby export const wrapPageElement ({ element }) { return ( {element} Script srchttps://my-example-script / / ) }export { wrapPageElement } from ./gatsby-sharedexport { wrapPageElement } from ./gatsby-sharedonLoad与onError回调使用post-hydrate或idle策略加载的带 src 的脚本支持两个回调onLoad— 脚本加载完成后调用onError— 脚本加载失败时调用。注意内联脚本以及使用off-main-thread策略的脚本不支持onLoad与onError回调。使用示例Script srchttps://my-example-script onLoad{() console.log(success)} onError{() console.log(sadness)} /重复脚本即id或src属性相同的脚本即使没有被注入 DOM也会执行其onLoad与onError回调。这一行为在源码中通过scriptCallbackCache一个Mapstring, { load?, error? }实现gatsby-script.tsx回调注册时会查询缓存若对应事件已发生过缓存了event则立即用缓存的事件重放回调injectScript则为真实注入的脚本挂载事件监听并在事件触发时通过onEventCallback把事件写入缓存供后续重复脚本的回调使用。依赖加载Loading scripts dependentlyonLoad与onError回调还支持实现脚本的依赖加载。以下示例展示了如何先加载第一个脚本、再加载第二个import React, { useState } from react import { Script } from gatsby function MyPage() { const [loaded, setLoaded] useState(false) return ( Script srchttps://my-example-script onLoad{() setLoaded(true)} / {loaded Script srchttps://my-other-example-script /} / ) } export default Page实战参考官方示例与组件属性速查官方示例站点仓库中的 examples/using-gatsby-script/ 提供了一个完整的实战参考值得对照研读在 src/pages/index.tsx 中同时使用了字符串字面量与ScriptStrategy枚举两种方式声明策略并覆盖了post-hydrate、idle、off-main-thread三种策略以及内联脚本的两种写法在 gatsby-config.ts 中把marked模块的 CDN 地址加入partytownProxiedURLs白名单注释明确说明这是加载off-main-thread脚本所必需的否则请求会 404import type { GatsbyConfig } from gatsby const config: GatsbyConfig { siteMetadata: { title: using-gatsby-script, siteUrl: https://www.yourdomain.tld, }, plugins: [], /** * Add the CDN URL for the marked module to the Partytown proxy allowlist * so we can load it with the off-main-thread strategy. * * This is required, otherwise the request will 404. */ partytownProxiedURLs: [https://cdn.jsdelivr.net/npm/marked/marked.min.js], } export default config组件属性速查根据源码中 ScriptProps 接口的定义Script组件支持以下关键属性属性类型说明srcstring远程脚本地址同时作为去重键与id二选一idstring唯一标识内联脚本必须提供也用于区分同src的重复脚本strategyScriptStrategy或对应字符串字面量加载策略默认post-hydratechildrenstring内联脚本的模板字符串写法dangerouslySetInnerHTML{ __html: string }内联脚本的 React 写法onLoad/onError(event) void加载成功/失败回调仅带src且策略为post-hydrate/idle时支持forwardArraystringPartytown 事件转发配置仅off-main-thread其他属性透传src、strategy、dangerouslySetInnerHTML、children、onLoad、onError之外的所有属性如crossOrigin、data-*等都会透传到最终渲染出的script元素上见handledProps与resolveAttributes总结与选型建议GatsbyScript组件为第三方脚本管理提供了从“零配置高性能”到“最大灵活度”的完整梯度默认场景直接用Script src... /post-hydrate策略保证脚本不干扰水合与可交互时间低优先级脚本对分析、埋点等可延后的脚本使用idle策略在空闲时加载重型第三方脚本对标签管理器、分析类脚本可尝试实验性的off-main-thread策略把求值负担移出主线程——但务必按本文步骤配置partytownProxiedURLs白名单与必要的代理/URL 解析并留意其在 SSR 导航与回调支持上的限制脚本依赖管理利用onLoad/onError回调实现脚本间的依赖加载。对于其他托管平台若需要使用off-main-thread策略请务必确认平台支持 Gatsby 的createRedirect动作参考 packages/gatsby/src/internal-plugins/partytown/gatsby-node.ts 中的重定向实现否则代理请求将无法正确转发。结合本文给出的源码证据与官方示例你可以在生产环境中安全、高效地接入各类第三方脚本。【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考