-
Notifications
You must be signed in to change notification settings - Fork 393
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
fix: add opf-load-once capability on js and css resources #19825
Changes from all commits
b63b192
55147db
d67a3ae
790e6a5
317831b
5c21bf1
4d7560e
89c7618
bddc97c
caab9fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,11 +22,12 @@ export class OpfResourceLoaderService { | |
protected document = inject(DOCUMENT); | ||
protected platformId = inject(PLATFORM_ID); | ||
|
||
protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; | ||
protected readonly CORS_DEFAULT_VALUE = 'anonymous'; | ||
protected readonly OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY = 'opf-load-once'; | ||
protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; | ||
|
||
protected embedStyles(embedOptions: { | ||
attributes?: OpfKeyValueMap[]; | ||
attributes?: { [key: string]: string }; | ||
src: string; | ||
sri?: string; | ||
callback?: EventListener; | ||
|
@@ -38,22 +39,18 @@ export class OpfResourceLoaderService { | |
link.href = src; | ||
link.rel = 'stylesheet'; | ||
link.type = 'text/css'; | ||
link.setAttribute(this.OPF_RESOURCE_ATTRIBUTE_KEY, 'true'); | ||
if (sri) { | ||
link.integrity = sri; | ||
const corsKeyvalue = attributes?.find( | ||
(attr) => attr.key === 'crossorigin' && !!attr.value?.length | ||
); | ||
link.crossOrigin = corsKeyvalue?.value ?? this.CORS_DEFAULT_VALUE; | ||
link.crossOrigin = attributes?.['crossorigin'] ?? this.CORS_DEFAULT_VALUE; | ||
delete attributes?.['crossorigin']; | ||
} | ||
if (attributes?.length) { | ||
attributes.forEach((attribute) => { | ||
const { key, value } = attribute; | ||
|
||
attributes && | ||
Object.keys(attributes)?.forEach((key) => { | ||
if (!(key in link)) { | ||
link.setAttribute(key, value); | ||
link.setAttribute(key, attributes[key as keyof object]); | ||
} | ||
}); | ||
} | ||
|
||
if (callback) { | ||
link.addEventListener('load', callback); | ||
|
@@ -74,39 +71,53 @@ export class OpfResourceLoaderService { | |
return this.scriptLoader.hasScript(src); | ||
} | ||
|
||
/** | ||
* Create attributes intended to script and link elements. | ||
* | ||
* Return attributes list including keyValueList and OPF specific attribute with below logic: | ||
* | ||
* 1. Resource loads only once: 'opf-load-once' key detected, no additional attribute added. | ||
* 2. Resource deleted at page/payment change: 'data-opf-resource' attribute is added. | ||
*/ | ||
|
||
FollowTheFlo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
protected createAttributesList(keyValueList?: OpfKeyValueMap[] | undefined): { | ||
[key: string]: string; | ||
} { | ||
const attributes: { [key: string]: string } = {}; | ||
keyValueList?.forEach((keyValue: OpfKeyValueMap) => { | ||
attributes[keyValue.key] = keyValue.value; | ||
}); | ||
if (attributes?.[this.OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY] === 'true') { | ||
attributes[this.OPF_RESOURCE_ATTRIBUTE_KEY] = 'true'; | ||
} | ||
delete attributes?.[this.OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY]; | ||
return attributes; | ||
} | ||
|
||
/** | ||
* Loads a script specified in the resource object. | ||
* | ||
* The returned Promise is resolved when the script is loaded or already present. | ||
* The returned Promise is rejected when a loading error occurs. | ||
*/ | ||
|
||
protected loadScript(resource: OpfDynamicScriptResource): Promise<void> { | ||
return new Promise((resolve, reject) => { | ||
const attributes: any = { | ||
const attributes: { [key: string]: string } = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we create type for it in a model if we cannot use: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mentioned above, we need both: |
||
type: 'text/javascript', | ||
[this.OPF_RESOURCE_ATTRIBUTE_KEY]: true, | ||
...this.createAttributesList(resource.attributes), | ||
}; | ||
|
||
if (resource?.sri) { | ||
attributes['integrity'] = resource.sri; | ||
const corsKeyvalue: OpfKeyValueMap | undefined = | ||
resource?.attributes?.find( | ||
(attr) => attr.key === 'crossorigin' && !!attr.value?.length | ||
); | ||
attributes['crossOrigin'] = | ||
corsKeyvalue?.value ?? this.CORS_DEFAULT_VALUE; | ||
} | ||
|
||
if (resource.attributes) { | ||
resource.attributes.forEach((attribute) => { | ||
attributes[attribute.key] = attribute.value; | ||
}); | ||
attributes?.['crossorigin'] ?? this.CORS_DEFAULT_VALUE; | ||
delete attributes?.['crossorigin']; | ||
} | ||
|
||
if (resource.url && !this.hasScript(resource.url)) { | ||
if (resource?.url && !this.hasScript(resource.url)) { | ||
this.scriptLoader.embedScript({ | ||
attributes, | ||
src: resource.url, | ||
attributes: attributes, | ||
callback: () => resolve(), | ||
errorCallback: () => reject(), | ||
disableKeyRestriction: true, | ||
|
@@ -123,11 +134,12 @@ export class OpfResourceLoaderService { | |
* The returned Promise is resolved when the stylesheet is loaded or already present. | ||
* The returned Promise is rejected when a loading error occurs. | ||
*/ | ||
|
||
protected loadStyles(resource: OpfDynamicScriptResource): Promise<void> { | ||
return new Promise((resolve, reject) => { | ||
if (resource.url && !this.hasStyles(resource.url)) { | ||
this.embedStyles({ | ||
attributes: resource?.attributes, | ||
attributes: this.createAttributesList(resource?.attributes), | ||
src: resource.url, | ||
sri: resource?.sri, | ||
callback: () => resolve(), | ||
|
@@ -167,6 +179,7 @@ export class OpfResourceLoaderService { | |
* The returned Promise is resolved when all resources are loaded. | ||
* The returned Promise is also resolved (not rejected!) immediately when any loading error occurs. | ||
*/ | ||
|
||
loadResources( | ||
scripts: OpfDynamicScriptResource[] = [], | ||
styles: OpfDynamicScriptResource[] = [] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it new type different than
OpfKeyValueMap[]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it was set as 'any'.
OpfKeyValueMap is {key:string,value:string}, we receive the list in this format from resource object (resource.attributes).
We perform a conversion to [key: string]: string to make handling easier and have same type as attributes in ScriptLoader service