Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Third Party Capital Discussion #54

Closed
harlan-zw opened this issue May 9, 2024 · 2 comments
Closed

Third Party Capital Discussion #54

harlan-zw opened this issue May 9, 2024 · 2 comments

Comments

@harlan-zw
Copy link
Collaborator

harlan-zw commented May 9, 2024

Background

Third-party capital was being used previously to generate some of the static data needed for a subset of the registry scripts (youtube, google maps, google analytics, google tag manager).

The runtime dependency has since been removed in a PR for several reasons below. We still use the exported types for the scripts.

Runtime Overhead

Third-party-capital has quite a hefty runtime bundle size relatively. Consider that the useScript wrapper from Unhead and Nuxt itself is only around ~2kb, which I think is too high and would be exploring further ways to bring it down. For Nuxt Scripts to be maximally useful it needs to be as minimal as possible.

The minified bundled size for each export with comparisons for current size:

  • Google Analytics: 1.7KB (vs 393B)
  • Google Tag Manager 1.6KB (vs 384B)
  • Google Maps 1.6KB minified (n/a as the implementation is different to TPC)
  • Youtube 1.7KB minified (n/a as the implementation is different to TPC)

Convulated implementation / no implementation types

TPC abstracts away the implementation details inside of a JSON file that has custom parsing, this makes it quite difficult to debug the behaviour of the code without digging into the entire code base implementation.

import { GoogleAnalytics } from 'third-party-capital'

const schema = GoogleAnalytics({ /* options */)
// schema is generically typed
const scripts = schema.scripts 
// scripts is an array, no way to know which script is the load of the script verse the bootstrap setup

Because the implementation is not obvious, it adds a disconnect between how the the use() and the stubs are implemented versus the rest of the script.

For example for SSR in GTM, we need to stub dataLayer as an array so we can push to it.

   scriptOptions: {
      use() {
        return { dataLayer: window.dataLayer, google_tag_manager: window.google_tag_manager }
      },
      // allow dataLayer to be accessed on the server
      stub: import.meta.client
        ? undefined
        : ({ fn }) => {
            return fn === 'dataLayer' ? [] : undefined
          },
    },

Leveraging Nuxt Tree Shaking for Micro-Optimizations

Since Nuxt is a SSR framework, there are certain optimizations we can opt-in to that environment agnostic packages can't, as they have no control over the SSR lifecycle.

if (import.meta.server) {
  // offload logic to the server instead of having the client do it
  // - gets treeshaken out of the client-build
}

A simple example of a micro-optimization is not passing any code that only the client needs to use. For example, we need to bootstrap the window for most third-party scripts, in Nuxt we can easily leverage tree-shaking to make sure this code isn't in the server bundle.

// packages publish code like this to be environment agnostic
if (typeof window !== 'undefined') {

}
// in nuxt we can just treeshake as we have control over rollup
if (import.meta.client) {
  window.myLib = {}
}

For something concrete, we can consider not running head injection tasks on the client when we can just do it on the server, reducing the time to hydrate the client.

We can see this in the ScriptYouTubePlayer component.

if (import.meta.server) {
  useHead({
    link: [
      {
        rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
        href: 'https://i.ytimg.com',
      },
      props.aboveTheFold
        // we can preload the placeholder image
        ? {
            rel: 'preload',
            as: 'image',
            href: placeholder.value,
          }
        : {},
    ],
  })
}

To achieve this with TPC would be quite difficult and would unlikely to work even if we can tree shake.

Maintanence DX

Nuxt Scripts is providing out-of-the-box support for 16 scripts, having 4 of them being configured differently through an external dependency we don't have control over will make maintenance harder in the short and long term.

I'd also like to see the repo more active generally, the following remain open and unanswered.

Reimplementing TPC

I'm happy to reconsider adding TPC within the runtime if the above can be solved or some other value can be provided that justifies the constraints.

@huang-julien
Copy link
Member

I think the way of integrating TPC into nuxt-scripts is to do it at build-time. Not at nuxt-script bundle-time but at users build-time by having TPC as a peerdep and external within nuxt scripts.

The idea is to have TPC kept as external but still added in the registry.
It will be up to users to update TPC to have the latest version.
We could also check TPC version within nuxt-script module setup time and warn the user that a new version of TPC is available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants