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

feat: dynamic import retry plugin [KM-865] #2

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

2eha0
Copy link

@2eha0 2eha0 commented Jan 7, 2025

Summary

  • add dynamic import retry plugin
  • add e2e tests and vitest settings
  • modified CI to support playwright caches

KM-865

@2eha0 2eha0 force-pushed the feat/dynmamic-import-retry branch 8 times, most recently from 77c17e6 to 9ddda00 Compare January 7, 2025 08:37
Copy link
Member

@adamdehaven adamdehaven left a comment

Choose a reason for hiding this comment

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

I left an initial review; the biggest issue is please back out the changes turning this into a mono repo.

Separately, it would be great if you could please extensively comment the code so that maintenance and future updates are easier.

package.json Outdated Show resolved Hide resolved
playground/vitestGlobalSetup.ts Show resolved Hide resolved
@@ -0,0 +1,350 @@
import type * as http from 'node:http'
Copy link
Member

Choose a reason for hiding this comment

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

question: I believe most of this was taken from one of the existing vite repos. Are we actually using the entirety of the config or is there any unneeded code here for our purpose?

Copy link
Author

Choose a reason for hiding this comment

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

I removed most of code that I thought they were not necessary for us.

playground/vue/.gitignore Outdated Show resolved Hide resolved
playground/vitestSetup.ts Outdated Show resolved Hide resolved
Comment on lines 1 to 19
{
"name": "@kong-vite-plugin/vue",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"vue-tsc": "^2.2.0"
}
}
Copy link
Member

Choose a reason for hiding this comment

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

issue: why does the playground need its own package.json file? This isn't a monorepo. Please move dependencies to the root

Copy link
Author

Choose a reason for hiding this comment

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

We can create multiple projects under the playground folder, package.json for each one is necessary, otherwise, how can we manually run those demo projects from the root level?

Copy link
Member

Choose a reason for hiding this comment

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

Can you please write up how you envision the testing working?

I’m not sure it makes sense to have fully-standalone applications for testing purposes.

If we need to change to a mono repository then I’d first want to restructure things before merging this PR

Copy link
Member

Choose a reason for hiding this comment

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

If each package is independent and needs an entire app sandbox I’d almost rather delete this repository and just utilize public-ui-components instead; it’s already set up and would essentially just need a different build command.

For dependencies in the current structure, playground dependencies would be devDependencies in the root package.json

Copy link
Author

Choose a reason for hiding this comment

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

My initial thought was that we might need multiple applications to test different cases, similar to https://github.com/vitejs/vite-plugin-vue/tree/main/playground.

However, we can start simple by writing all use cases in a single project. If more complex scenarios arise, we can refactor it into a monorepo as needed.

playground/vue/tsconfig.json Outdated Show resolved Hide resolved
pnpm-workspace.yaml Outdated Show resolved Hide resolved
Comment on lines 7 to 18
/*
Copyright 2024 Carl-Erik Kopseng

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
Copy link
Member

@adamdehaven adamdehaven Jan 7, 2025

Choose a reason for hiding this comment

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

is this a requirement of an external lib? If yes, can you please add a link to the source?

Also, could this be extracted into its own file and imported where needed rather than inlined into our code?

Copy link
Author

Choose a reason for hiding this comment

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

link added.

No, the code in this function shouldn't have external references, I put detailed reason in comments.

@CLAassistant
Copy link

CLAassistant commented Jan 8, 2025

CLA assistant check
All committers have signed the CLA.

@2eha0 2eha0 force-pushed the feat/dynmamic-import-retry branch from c0a326e to 73dc74e Compare January 9, 2025 09:41
Copy link
Member

@adamdehaven adamdehaven left a comment

Choose a reason for hiding this comment

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

Left some initial feedback.

There is a large lack of comments in the code that would be helpful for review. Please thoroughly comment the code.

id: playwright-cache
uses: actions/cache@v4
with:
key: playwright-bin-v1
Copy link
Member

Choose a reason for hiding this comment

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

Should we tie the cache key here to a dynamic string that includes the playwright dependency version from package.json?

Copy link
Author

Choose a reason for hiding this comment

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

Good point! Updated.

.gitignore Outdated Show resolved Hide resolved
"baseUrl": ".",
"outDir": "dist",
"declaration": false,
"declarationDir":null
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"declarationDir":null
"declarationDir": null

Choose a reason for hiding this comment

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

@adamdehaven Maybe we should enforce this with our shared ESLint config?

Copy link
Author

Choose a reason for hiding this comment

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

done

Comment on lines 48 to 49
const newError = new Error(`[preload-css-retried] ${e.payload.message}`)
throw newError
Copy link

Choose a reason for hiding this comment

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

Suggested change
const newError = new Error(`[preload-css-retried] ${e.payload.message}`)
throw newError
throw new Error(`[preload-css-retried] ${e.payload.message}`)

Copy link
Author

Choose a reason for hiding this comment

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

Updated

"baseUrl": ".",
"outDir": "dist",
"declaration": false,
"declarationDir":null

Choose a reason for hiding this comment

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

@adamdehaven Maybe we should enforce this with our shared ESLint config?

src/plugin-dynamic-import-retry/README.md Show resolved Hide resolved
@2eha0 2eha0 force-pushed the feat/dynmamic-import-retry branch from 1f0c3c4 to 7cc82d4 Compare January 13, 2025 03:48
@adamdehaven
Copy link
Member

@2eha0 why are you force pushing the branch? You’re obliterating the commit history and it’s impossible to see the changes between commits

@adamdehaven
Copy link
Member

Separate from the changes in the PR, have we done any benchmarking to determine if implementing this plugin in a host application has any performance / timing impacts in the build, or the actual imports themselves?

@2eha0
Copy link
Author

2eha0 commented Jan 14, 2025

Runtime cost

This plugin will only work if the module fails to load, which is just one more js function call for the normal case and has no impact on performance.

the pseudocode:

function importWraper(importFn) {
  try {
    await importFn() // <- the normal case
  } catch () {
   // do retry... // <- only run here on error
  }
}

importWraper(() => import('xxxx')) // original: () => import('xxx')

Comparison of time spent during build

There's no discernible difference.

With out the plugin With the plugin
1m 18s 1m 22s

https://github.com/Kong/konnect-ui-apps/actions/runs/12758610182/job/35561049472
image

https://github.com/Kong/konnect-ui-apps/actions/runs/12760560701/job/35566279787?pr=5332
image

@Justineo
Copy link

I think the performance impact should be rather limited and acceptable.

@2eha0 2eha0 marked this pull request as ready for review January 15, 2025 02:24
@adamdehaven
Copy link
Member

We should also investigate how this plugin could impact usage of something like defineAsyncComponent where the import, loading, and error state is handled as part of the implementation: https://vuejs.org/guide/components/async.html#loading-and-error-states

@adamdehaven
Copy link
Member

Does this plugin, by default, run only against the source code in the repository, or does it also run against all node_modules, etc.?

My gut is telling me we would want to exclude node_modules by default since other packages may have their own internal optimizations, desired behavior, etc.

We could then potentially allow opting in based on a package name, similar to how rollup marks externals or Vite does optimizeDeps

Comment on lines 26 to 27
| include | string \| RegExp \| (string \| RegExp)[] | Files to include, default is `\.(js\|ts\|vue\|tsx)$`. |
| exclude | string \| RegExp \| (string \| RegExp)[] | Files to exclude. |
Copy link
Member

Choose a reason for hiding this comment

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

We should likely exclude node_modules by default, and then allow opting-in where needed.

We should discuss any other desired default configurations

Choose a reason for hiding this comment

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

I thought about it again today, and I still can’t figure out in what scenarios we would prefer to display an error instead of retrying and then falling back to an exception state when a resource chunk fails to load. In cases like defineAsyncComponent with a timeout set, as I understand it, retrying wouldn’t extend the wait time—the timeout would still display an error as expected. If we were to use a dynamic import for something like “feature detection”, it doesn’t seem likely that the distinction would be based on whether the imported module resides in node_modules. Can you think of any practical examples where retrying would negatively impact the original logic?

Copy link
Member

Choose a reason for hiding this comment

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

I'm more suggesting it may be worth testing so we know how it will behave rather than assuming

Choose a reason for hiding this comment

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

Sure. Maybe we can add a test case for defineAsyncComponent? @2eha0

Copy link
Author

Choose a reason for hiding this comment

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

test cases for defineAsyncComponent have been added.

package.json Show resolved Hide resolved
@adamdehaven
Copy link
Member

When the plugin retries the import, does it typically always succeed?

I started wondering if the issue is actually rooted in the way we invalidate the cache on AWS. The first request after the cache is invalidated may fail, but would trigger AWS to put the requested file back in the cache. A subsequent request would then have a cache hit, and would successfully download the chunk (similar to SWR behavior).

Could we validate this isn't the case in the deployed MFE?

@2eha0
Copy link
Author

2eha0 commented Jan 22, 2025

I don’t think this is how the CDN is supposed to work; resources should always be served, even without caches. If it does, our Prod regression tests should always fail.

Something I feel strange about AWS is sometimes I get 403 when I download JS files, I think it might be related to our settings in AWS?

image

@Justineo
Copy link

I started wondering if the issue is actually rooted in the way we invalidate the cache on AWS. The first request after the cache is invalidated may fail, but would trigger AWS to put the requested file back in the cache. A subsequent request would then have a cache hit, and would successfully download the chunk (similar to SWR behavior).

I believe AWS would not reject requests during revalidation unless the origin is unavailable itself. Additionally, I believe that our JavaScript files shouldn’t invalidate at all, as they all have content hashes in the filenames.

@adamdehaven
Copy link
Member

adamdehaven commented Jan 22, 2025

I believe that our JavaScript files shouldn’t invalidate at all, as they all have content hashes in the filenames.

We manually invalidate the cache for some paths, although not JS that I recall (example)

- name: Set Playwright path
id: playwright-path
shell: bash
run: echo "PLAYWRIGHT_BROWSERS_PATH=/home/runner/.cache/ms-playwright" >> $GITHUB_OUTPUT
Copy link
Member

Choose a reason for hiding this comment

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

Two issues:

  • I believe you actually need to expose this variable to the $GITHUB_ENV and then pass this env variable to the step that runs the install command so that Playwright will utilize it during the install
  • I believe the path should be this:
Suggested change
run: echo "PLAYWRIGHT_BROWSERS_PATH=/home/runner/.cache/ms-playwright" >> $GITHUB_OUTPUT
run: echo "PLAYWRIGHT_BROWSERS_PATH=/home/ubuntu/.cache/ms-playwright" >> $GITHUB_ENV

Copy link
Author

Choose a reason for hiding this comment

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

Done, and I also replaced /home to $HOME

id: playwright-install
shell: bash
# does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved
run: pnpm playwright install chromium
Copy link
Member

Choose a reason for hiding this comment

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

You need to pass the PLAYWRIGHT_BROWSERS_PATH here as an env variable

@ValeryG
Copy link

ValeryG commented Feb 5, 2025

I don’t think this is how the CDN is supposed to work; resources should always be served, even without caches. If it does, our Prod regression tests should always fail.

Something I feel strange about AWS is sometimes I get 403 when I download JS files, I think it might be related to our settings in AWS?

@2eha0 , 403 is getting returned when non-existing asset is requested: eg: https://cloud.konghq.tech/signin/assets/bad-asset-name.js
image
if you see this happening for assets that are valid and should be returned with 200, please give me an example of those requests.

@ValeryG
Copy link

ValeryG commented Feb 5, 2025

I started wondering if the issue is actually rooted in the way we invalidate the cache on AWS. The first request after the cache is invalidated may fail, but would trigger AWS to put the requested file back in the cache. A subsequent request would then have a cache hit, and would successfully download the chunk (similar to SWR behavior).

I believe AWS would not reject requests during revalidation unless the origin is unavailable itself. Additionally, I believe that our JavaScript files shouldn’t invalidate at all, as they all have content hashes in the filenames.

That is correct, if asset is NOT on the edge, request is going to origin , grabs asset from there , spits it back to the requestor and sticks it into CDN. There is never the case when error is returned when asset is on the origin, but not on the edge.

@ValeryG
Copy link

ValeryG commented Feb 5, 2025

question: let's assume this is enabled for MFE. Is there any way to see in the datadog:

  • how many times retry needed to be invoked
  • how many times the logic of retry safes the world and asset request actual recovers

This allows us to evaluate the usefulness of this plugin :)

@Justineo
Copy link

Justineo commented Feb 5, 2025

@2eha0 had added two widgets to our DD dashboard (one for JS errors and the other for CSS errors) for current experiments in gateway-manager MFE. @ValeryG

Once the plugin is published, we’ll install it in gateway-manager first and monitor the data for a while before enabling it for the other MFEs.

@adamdehaven
Copy link
Member

@2eha0 had added two widgets to our DD dashboard (one for JS errors and the other for CSS errors) for current experiments in gateway-manager MFE. @ValeryG

Once the plugin is published, we’ll install it in gateway-manager first and monitor the data for a while before enabling it for the other MFEs.

image

I still feel like we may be over-optimizing here TBH.

@Justineo
Copy link

Justineo commented Feb 5, 2025

This isn’t exactly a major pain point, but it’s at least a nice-to-have. The experiment has shown that it is indeed effective, and the community does have similar needs: whatwg/html#6768.

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.

5 participants