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

Call GPP via code splitting #4447

Merged
merged 21 commits into from
Dec 1, 2023
Merged

Conversation

allisonking
Copy link
Contributor

@allisonking allisonking commented Nov 20, 2023

Closes https://ethyca.atlassian.net/browse/PROD-1395

Description Of Changes

Integrates GPP as a dynamically imported JS package. This means it isn't called until it is needed so isn't included in the initial bundle size. This is also a good proof of concept for how we can implement code splitting in our bundle to split out our UI code from our core initialization code.

Caveats

This works pretty well, but there are some warnings in the Privacy Center logs which say Critical dependency: the request of a dependency is an expression. After some googling, this appears to be a warning that is okay to ignore: vercel/next.js#10633

One way to remove the warning is to force the variable where we cast the GPP bundle parameter to a string but this actually causes the app not to compile. It tries to import typescript files which it can't read, and I haven't figured out why that might be. So I left the warning in, though I can't say I totally understand it.

Code Changes

  • Set up a new route in privacy center called /gpp-ext
  • Set up the gpp code as a separate output of rollup which copies to a place the PC expects (in order to host /gpp-ext
  • New environment variable FIDES_PRIVACY_CENTER__IS_GPP_ENABLED to include the GPP bundle dynamically. In the future, this will be done through a config on the backend, but to get this going, I added the env var. We should remove it down the line, similar to what we did for TCF.
  • Cypress tests

Steps to Confirm

  • Run privacy center via turbo dev. This will build the new gpp-ext.js output and you should see a block along the lines of
fides-js:build: src/extensions/gpp.ts → dist/gpp-ext.js...
fides-js:build: ✅ gpp-ext.js gzipped size passed maximum size checks (2.86 KB < 15 KB)
fides-js:build: ┌──────────────────────────────────┐
fides-js:build: │                                  │
fides-js:build: │   Destination: dist/gpp-ext.js   │
fides-js:build: │   Bundle Size:  11.04 KB         │
fides-js:build: │   Minified Size:  10.94 KB       │
fides-js:build: │   Gzipped Size:  2.86 KB         │
fides-js:build: │                                  │
fides-js:build: └──────────────────────────────────┘
fides-js:build: copied:
fides-js:build:   dist/gpp-ext.js → ../privacy-center/public/lib/gpp-ext.js
fides-js:build: created dist/gpp-ext.js in 122ms
  • Make a note of the gzipped size of the fides-tcf.js bundle. It should be around 40.45 KB
  • Now visit localhost:3000/fides-js-demo.html?geolocation=fr-id to get the TCF overlay
  • Open up the console and try to ping the GPP API via __gpp('ping', (data) => {console.log({data})}). You should get an error that the function __gpp is not defined. This is expected since the bundle wasn't included!

image

  • Now stop your privacy center and restart it with FIDES_PRIVACY_CENTER__IS_GPP_ENABLED=true
  • Check the gzipped size of the fides-tcf.js bundle. It should be the same as it was before (40.45 KB).
  • Now visit localhost:3000/fides-js-demo.html?geolocation=fr-id to get the TCF overlay
  • Open up the console and try to ping the GPP API via __gpp('ping', (data) => {console.log({data})}). This time you should get a data object back!

image

  • In the Network tab, you should also see a new call to gpp-ext.js

image

Tada, GPP without increasing the initial bundle size!

Pre-Merge Checklist

  • All CI Pipelines Succeeded
  • Issue Requirements are Met
  • Update CHANGELOG.md

Copy link

vercel bot commented Nov 20, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
fides-plus-nightly ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 1, 2023 9:16pm

Copy link

cypress bot commented Nov 20, 2023

Passing run #5445 ↗︎

0 4 0 0 Flakiness 0

Details:

Merge d358613 into 6ebfb5d...
Project: fides Commit: bf52fc29c9 ℹ️
Status: Passed Duration: 00:33 💡
Started: Dec 1, 2023 9:24 PM Ended: Dec 1, 2023 9:25 PM

Review all test suite changes for PR #4447 ↗︎


export const setupExtensions = (options: FidesOptions) => {
if (options.gppEnabled) {
import(GPP_EXT_PATH).catch((e) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the dynamic importing step!

Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice. We'll need to test this on a real web server to see the gotchas though - I'd imagine a relative path like this might be stymied when the script tag is a third-party source?

For example, if we're on example.com and we do this:

<script src="https://ethyca.com/path/to/fides.js" />

Does this know how to import from "https://ethyca.com/path/to/gpp-ext.js", or is it going to try to use "https://example.com/gpp-ext.js"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm yeah I think it'll try to use example.com/gpp-ext.js. how do we want to treat this? should we try to force it to look relative, or allow the whole path to be configurable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Realistically, we'll want to make it configurable, and then test out what works well in our staging/etc

@allisonking allisonking marked this pull request as ready for review November 21, 2023 16:12
Copy link
Contributor

@NevilleS NevilleS left a comment

Choose a reason for hiding this comment

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

This is super promising! I have some nitpicky comments about filenames (mostly!) but my main question is: will this work once it's deployed? 😄

clients/fides-js/rollup.config.mjs Outdated Show resolved Hide resolved

export const setupExtensions = (options: FidesOptions) => {
if (options.gppEnabled) {
import(GPP_EXT_PATH).catch((e) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice. We'll need to test this on a real web server to see the gotchas though - I'd imagine a relative path like this might be stymied when the script tag is a third-party source?

For example, if we're on example.com and we do this:

<script src="https://ethyca.com/path/to/fides.js" />

Does this know how to import from "https://ethyca.com/path/to/gpp-ext.js", or is it going to try to use "https://example.com/gpp-ext.js"?

clients/fides-js/src/lib/consent-types.ts Show resolved Hide resolved
clients/fides-js/src/lib/initialize.ts Outdated Show resolved Hide resolved
clients/privacy-center/next.config.js Outdated Show resolved Hide resolved
Comment on lines 27 to 31
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const gppJsFile = "public/lib/gpp-ext.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW: I feel like this an API route here is more complicated than we'd want here, just because next.js will happily serve static files... but at the same time, it's fairly painful to force cache headers on them...

In other words, a potentially simpler way to solve this is:

  1. copy that fides-gpp-ext.js file into public/ instead of public/lib
  2. use the default hosting of static files from next
  3. add the required headers in the next.config.js or similar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh good point, that makes things a lot simpler!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm actually there seem to be some open issues about adding cache headers in the public folder: vercel/next.js#22319

will look into some of the workarounds on monday...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

since I couldn't change the cache control header via the public/ folder, I kept this as an endpoint that just serves the file (+ a cache header). I did make the changes to the filenames so the rollup file is easier to follow now though. if you have ideas for how to make caching work on public/ then we can come back to this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup, no problem!

@allisonking
Copy link
Contributor Author

I'm running into some issues merging main because of this bit that was just introduced in a separate PR:

if (!window.Fides.options.fidesTcfGdprApplies) {
// A `null` TC string means gdpr does not apply
return null;
}

This PR also has GPP calling the same function, however I'm unclear about how to get the GPP extension file to also have access to window.Fides. I know it has something to do with the rollup output here

output: [
{
// Intended for browser <script> tag - defines `Fides` global. Also supports UMD loaders.
file: `${outputFileName}.js`,
name: isExtension ? undefined : "Fides",
format: isExtension ? undefined : "umd",
sourcemap: IS_DEV,
},
],

But I'm not sure the right way to tell the GPP extension to also use the same window.Fides from one of the base fides.js files

@allisonking
Copy link
Contributor Author

I'm running into some issues merging main because of this bit that was just introduced in a separate PR:

Figured it out! I just needed to make sure my GPP ext also had the same window declaration in order to access the Fides object.

// Call extensions
// DEFER(PROD#1439): This is likely too late for the GPP stub.
// We should move stub code out to the base package and call it right away instead.
await setupExtensions(options);
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 just changed this to await because otherwise FidesInitialized might be called before the extension is ready to listen for it. I'm not sure if this is okay though in terms of initialization times—the first FidesInitialized from an existing cookie will already have fired, so this is counting on the second one to fire. It would happen after the UI initializes, which might be far enough away from initial load times?

Copy link
Contributor

Choose a reason for hiding this comment

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

For the stub initialization, it'd definitely be too late.

Copy link
Contributor

@NevilleS NevilleS left a comment

Choose a reason for hiding this comment

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

Overall: I'd say ship it! I think you should add a config option here though to experiment with the import path.

// Call extensions
// DEFER(PROD#1439): This is likely too late for the GPP stub.
// We should move stub code out to the base package and call it right away instead.
await setupExtensions(options);
Copy link
Contributor

Choose a reason for hiding this comment

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

For the stub initialization, it'd definitely be too late.


export const setupExtensions = (options: FidesOptions) => {
if (options.gppEnabled) {
import(GPP_EXT_PATH).catch((e) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Realistically, we'll want to make it configurable, and then test out what works well in our staging/etc

Comment on lines 27 to 31
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const gppJsFile = "public/lib/gpp-ext.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

Yup, no problem!

* The path where the GPP extension bundle is located,
* likely served by the privacy center
*/
const GPP_EXT_PATH = "/fides-ext-gpp.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

I do this we should make this configurable, especially as a runtime override option. That'd give a lot of flexibility for testing this out in different environments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! 17b8fa1

Running FIDES_PRIVACY_CENTER__IS_GPP_ENABLED=true FIDES_PRIVACY_CENTER__GPP_EXTENSION_PATH="/test.js" turbo dev will try to import the GPP path from /test.js and you'll get an error in the console. Running without the env var will default to /fides-ext-gpp.js and things should work!

@allisonking allisonking merged commit fc490d6 into main Dec 1, 2023
13 checks passed
@allisonking allisonking deleted the aking/prod-1395/code-splitting-poc branch December 1, 2023 22:28
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

Successfully merging this pull request may close these issues.

2 participants