Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
background
When making A Web JS SDK (A), the methods of another Web JS SDK (B) are used internally. (A/B)
B usually provides both Script and NPM packages
Disadvantages introduced by using NPM
- Increase package volume
- If the SDK has already been introduced to the page by a Web application, then in theory it can be used directly and there is no need to complete another one
If THE SDK B includes the way script is introduced, and there are situations in which the target page might introduce B, then the case of using Script to introduce the dependent SDK is preferred: for example
- JQuery has already been introduced in the target page (as required by SDK A), so SDK A can use the existing JQuery directly
$
No need to create jQuery script - Usually, the page will be connected to infrastructure services such as SDK B, and SDK A also needs to report data through B
Derived demand
- When a function mounted on the window does not exist, the method is automatically completed by script or polyfill
- The caller still uses the SDK B documentation
window.sdkB(options)
Copy the code
The solution
Write a generic utility function that handles the derived requirements described above
The method definition is as follows
function patchWindowFun(
key: string,
value: string | Function, options? : { afterScriptLoad? :FunctionbeforeAppendScript? :FunctionalreadyExistCB? :Function
async? :booleandefer? :boolean
},
)
Copy the code
A total of three parameters can be passed:
key
: Specifies the attribute name of the judged method on the windowvalue
: non-existent value (function indicates that the method is used directly, string indicates that the method comes from an externally loaded JS resource)options
: is an optional configuration item for scenarios where external JS resource loading methods are usedafterScriptLoad
: Rollback after the resource is loadedbeforeAppendScript
: Rollback of resources before loadingalreadyExistCB
If the: method already exists to execute the callbackasync
: controls scriptasyncattributedefer
: controls scriptdeferattribute
Because most web SDK will need to invoke a particular function or method for initialization, solid provides afterScriptLoad beforeAppendScript, alreadyExistCB three hooks deal with different time to initialize
Method implementation
If the target attribute exists, the corresponding callback is performed without further processing
if (window[key]) {
alreadyExistCB && alreadyExistCB()
console.log(key, 'already exist')
return
}
Copy the code
The target attribute does not exist and is assigned directly if the passed method exists
// The function is assigned directly
if (typeof value === 'function') {
window[key] = value
return
}
Copy the code
The residual logic handles the case when a method is loaded from an external JS resource
Since script loading is mostly asynchronous, a method may have been called in the business code to temporarily create a method to collect the parameters passed in
let params = []
window[key] = function () {
params.push(arguments)}Copy the code
The following logic is used to handle script loading
After js resources are loaded, apply and forEach are used to correctly execute the parameters generated by calling the method in advance
const script = document.createElement('script') script.src = value script.async = !! defer script.defer = !!async
script.onload = function () {
afterScriptLoad && afterScriptLoad()
// Get rid of the original
params.forEach(param= > {
window[key].apply(this, param)
})
}
beforeAppendScript && beforeAppendScript()
document.body.append(script)
Copy the code
The complete source code is as follows
function patchWindowFun(
key: string,
value: string | Function, options? : { afterScriptLoad? :FunctionbeforeAppendScript? :FunctionalreadyExistCB? :Function
async? :booleandefer? :boolean
},
) {
// There is no processing
const { alreadyExistCB, afterScriptLoad, beforeAppendScript, defer, async } = options || {}
if (window[key]) {
alreadyExistCB && alreadyExistCB()
console.log(key, 'already exist')
return
}
// The function is assigned directly
if (typeof value === 'function') {
window[key] = value
return
}
// script url
if (typeof value === 'string') {
let params = []
window[key] = function () {
params.push(arguments)}const script = document.createElement('script') script.src = value script.async = !! defer script.defer = !!async
script.onload = function () {
afterScriptLoad && afterScriptLoad()
// Get rid of the original
params.forEach(param= > {
window[key].apply(this, param)
})
}
beforeAppendScript && beforeAppendScript()
document.body.append(script)
}
}
Copy the code
summary
The current method implementation is only applicable and the method invoked is relatively independent of normal interaction
If the business code depends on the return value of a method, asynchronous method loading through script will not be appropriate