DavidWells / analytics

Lightweight analytics abstraction layer for tracking page views, custom events, & identifying visitors
https://getanalytics.io
MIT License
2.43k stars 247 forks source link

Feature: Support for a dataLayer-like queue of work #254

Open totalhack opened 2 years ago

totalhack commented 2 years ago

Have you considered adding support for queueing events pre-load of the analytics script similar to how google does with the dataLayer or how segment's analytics.js script did/does it?

Details on the segment approach here.

DavidWells commented 2 years ago

Hey @totalhack thanks for the link. That's pretty interesting 😃. Love that call before ready approach.

What's the use case you are looking to solve? Also how are you loading analytics library?

It looks like the approach segment takes would rely on analytics being global on the window.

One of the core differences for this library is the idea that analytics is always localized (if you are using it in a bundled app or using a non-global closure). The reasoning behind this is keeping analytics out window for anyone (including bad actors/extensions/etc) from calling methods can potentially polluting tracking calls.

The recommended way of consuming this lib would be as a bundled pkg outside of the global scope, but it is possible to attach to the window with the CDN links etc

totalhack commented 2 years ago

I think the main use case for this is just getting certain analytics calls fired as soon as possible rather than having to wait for an event that says the analytics JS has loaded and is ready (like DOMContentLoaded). With the queue you know the work will be done as soon as possible. There are probably a variety of use cases where that's important, particularly if any loads or updates on the page are dependent on analytics script/plugin setup.

My use case has some unique aspects -- I have a custom analytics plugin that has some other time-sensitive functionality I occasionally have to call on a page. For example: swapping a phone number out for a dynamic one for the session. Certain uses of GTM may suffer from a similar time-sensitivity.

I also am also using first-party server/HTTP cookies for tracking since it's more reliable against blockers and iOS than JS-based cookies or storage. I generate an on-page visit ID which is available client side as soon as the script loads, and that can be tied to the HTTP-cookie session ID on the back end. That client-side visit ID is sometimes needed for properly tracking interactions with other third party scripts/tags/iframes/redirects so it's another example of a dependency on the script loading.

I am bundling analytics with some other code and loading it via CDN so it's global anyway in my case. I had been avoiding loading it async or deferred due to aforementioned page dependencies, but I was just looking into delivering a separate bundle with polyfills only to older browsers per this general approach, or perhaps this offshoot. This introduces some challenges with timing/dependencies since the scripts become async or deferred depending on how you do it, so I wanted a way for the script to start processing work as soon as it loaded. My simpler workaround I'm attempting now is to trigger certain calls/features based on some global flags. But the queue approach is cleaner, more flexible, and more standard.

DavidWells commented 2 years ago

Ah super cool setup you have!

The first-party server/HTTP cookies is a good one. I'd love to see that setup if there's a repo to checkout. 😃

So there is a queuing system in place right now, here. It queues up events to wait until loaded function evaluates to true, after initialize functions run. But this isn't exactly what you want.

I've been meaning to replace the queue system with something a little more lightweight

There might be a way to provide an "opt-in" pre-load queue like you mention. It would need to be a global and thus making it opt-in would be ideal.

/*
 elsewhere the preload push stuff to global queue
*/

import Analytics from 'analytics'

const analytics = Analytics({
  app: 'app-name',
  dataLayer: 'nameOfGlobalVar',
  plugins: []
})

Then if dataLayer is set we can check it once analytics bootstraps...

Something like this might work... Need to think about it

totalhack commented 2 years ago

Some of the code is public, all is a WIP and subject to change, and it's likely lacking in best practices for organizing the code (JS is not my main language). But here is the main file, the page call in the zar plugin is where I override a session ID and client ID with backend values if they are present. The backend page endpoint is defined here.

For the queuing, any reason you decided on a setTimeout approach instead of generating a function call when an item was pushed? Check out this SO answer for what I'm thinking about.

Your preload queue suggestion looks reasonable. One thing I'd request is the ability to queue calls to custom plugin-specific functions too if possible, even though Analytics won't know what those are ahead of time.