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

refactor(plugin-gtag): update gtag plugin to modern SPA recommendations #8143

Merged
merged 14 commits into from
Oct 21, 2022
7 changes: 7 additions & 0 deletions packages/docusaurus-plugin-google-gtag/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
"description": "Global Site Tag (gtag.js) plugin for Docusaurus.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": "./lib/index.js",
"./utils": {
"type": "./lib/utils.d.ts",
"default": "./lib/utils.js"
}
},
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch"
Expand Down
12 changes: 7 additions & 5 deletions packages/docusaurus-plugin-google-gtag/src/gtag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ const clientModule: ClientModule = {
setTimeout(() => {
// Always refer to the variable on window in case it gets overridden
// elsewhere.
window.gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href,
page_path: location.pathname + location.search + location.hash,
});
// See https://developers.google.com/analytics/devguides/collection/gtagjs/single-page-applications
window.gtag(
lanegoolsby marked this conversation as resolved.
Show resolved Hide resolved
'set',
'page_path',
location.pathname + location.search + location.hash,
);
slorber marked this conversation as resolved.
Show resolved Hide resolved
window.gtag('event', 'page_view');
});
}
},
Expand Down
14 changes: 8 additions & 6 deletions packages/docusaurus-plugin-google-gtag/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
interface Window {
gtag: (
command: string,
fields: string,
params: {
page_title?: string;
page_location?: string;
page_path?: string;
},
action: string,
params?:
| string
| {
event_category?: string;
event_label?: string;
value?: string;
},
) => void;
}
8 changes: 8 additions & 0 deletions packages/docusaurus-plugin-google-gtag/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export {useGoogleAnalytics, type Event} from './utils/useGoogleAnalytics';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {useCallback} from 'react';

export interface Event {
action: string;
event_category?: string;
event_label?: string;
value?: string;
}

export function useGoogleAnalytics(): {
sendEvent: (event: Event) => void;
} {
const sendEvent = useCallback((event: Event) => {
window.gtag('event', event.action, event);
}, []);

return {sendEvent};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand the value of this abstraction 🤷‍♂️

What prevents someone to just do this directly in their code? (which would give them even more flexibility)

function onClick() {
  const event = {
      action: 'some action',
      event_category: 'come category',
      event_label: 'some label',
      value:'some value'
  }

  window.gtag('event', event.action, event);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can, I'm just an old school OOO programmer and will always provide strong types. :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can agree with that, but the types should rather be provided by ambient definitions from official gtag TS bindings preferably.

If they don't provide those, I'd rather create our own types only, and not add an extra runtime api/hook

For example:

interface Window {
  gtag: ...
}

Isn't this good enough to provide strong types for our users? Do we really need the extra client api/hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can agree with that, but the types should rather be provided by ambient definitions from official gtag TS bindings preferably.

If they don't provide those, I'd rather create our own types only, and not add an extra runtime api/hook

There are gtag types published to DefinitelyTyped. Are you suggesting I get rid of the Event interface and replace it with something exported from there?

For example:

interface Window {
  gtag: ...
}

Isn't this good enough to provide strong types for our users? Do we really need the extra client api/hook?

I couldn't get that approach to work with my code. Its possible I was doing something wrong, but I always got a syntax error that gtag does not exist on the Window object.

In fact, the reason that I don't reference the Event interface in src/types.d.ts is because if I try to use an import statement in that file I get the same syntax error.

The onClick approach you suggested above does indeed work, but it requires using a ts-ignore statement because gtag is not global. Our internal code analyzers get very angry when we do stuff like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS - there is an unofficial React gtag package available. Its basically a port of the popular ReactGA plugin (that only works with old GA3 sites).

I thought about going that route instead but it seemed like a much bigger lift that this approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ever import a third-party package, it should be either the official one of the DT one.

This one looks appropriate to me:

As far as I know adding a @types/* deps to the package should be enough for TS to find its ambient typedefs

If that's not the case you may be able to use /// directive:

https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html

/// <reference types="@types/gtag.js" />

Our internal code analyzers get very angry when we do stuff like that.

On the Docusaurus repo we only care about providing a setup that TS agree with, not all the analyzers in the world 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"rootDir": "src",
"outDir": "lib"
},
"include": ["src/gtag.ts", "src/options.ts", "src/*.d.ts"],
"include": ["src/gtag.ts", "src/options.ts", "src/*.d.ts", "src/utils/*"],
"exclude": ["**/__tests__/**"]
}