Script File Executing After Inline Script With CDN

2020-06-12 05:58发布

问题:

I am having an issue with HTML injection into an already loaded DOM where the inline javascript is being loaded after the script file is downloaded. From what I know this should not be async and the inline script should execute after the script file. This works if the domain name is the same as the calling page, but using a CDN or even a subdomain does the same thing. Is there something I should do to rework how I am calling these? I swear this worked before as I had the CDN on for over a week but maybe I never caught this issue.

Console

Loading Inline Script
VM1400:3 Uncaught TypeError: Cannot read property 'init' of undefined(anonymous function) 
app.members.event.js?v=204&_=1453644424985:5 Loading Script File
app.members.event.js?v=204&_=1453644424985:71 Finished Script File

Javascript

<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript">
console.log('Loading Inline Script');
    app.viewModel.members.event.init();
console.log('Finished Inline Script');

回答1:

One way is to use jquery's getScript() function.

But preferably, you may use native javascript to load the script file and then run the inline script.

Maybe i have not understood the question clearly.

Edit: This is a quote from the HTML5 spec regarding script elements.

If the element has a src content attribute, run these substeps:

Let src be the value of the element's src attribute.

If src is the empty string, queue a task to fire a simple event named error at the element, and abort these steps.

Resolve src relative to the element.

If the previous step failed, queue a task to fire a simple event named error at the element, and abort these steps.

Do a potentially CORS-enabled fetch of the resulting absolute URL, with the mode being the current state of the element's crossorigin content attribute, the origin being the origin of the script element's Document, and the default origin behaviour set to taint.

The resource obtained in this fashion can be either CORS-same-origin or CORS-cross-origin. This only affects how error reporting happens.

For performance reasons, user agents may start fetching the script (as defined above) as soon as the src attribute is set, instead, in the hope that the element will be inserted into the document (and that the crossorigin attribute won't change value in the meantime). Either way, once the element is inserted into the document, the load must have started as described in this step. If the UA performs such prefetching, but the element is never inserted in the document, or the src attribute is dynamically changed, or the crossorigin attribute is dynamically changed, then the user agent will not execute the script so obtained, and the fetching process will have been effectively wasted.

Then, the first of the following options that describes the situation must be followed:

If the element has a src attribute, and the element has a defer attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element must be added to the end of the list of scripts that will execute when the document has finished parsing associated with the Document of the parser that created the element.

The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script.

If the element has a src attribute, and the element has been flagged as "parser-inserted", and the element does not have an async attribute The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.)

The task that the networking task source places on the task queue once the fetching algorithm has completed must set the element's "ready to be parser-executed" flag. The parser will handle executing the script.

If the element does not have a src attribute, and the element has been flagged as "parser-inserted", and either the parser that created the script is an XML parser or it's an HTML parser whose script nesting level is not greater than one, and the Document of the HTML parser or XML parser that created the script element has a style sheet that is blocking scripts The element is the pending parsing-blocking script of the Document of the parser that created the element. (There can only be one such script per Document at a time.)

Set the element's "ready to be parser-executed" flag. The parser will handle executing the script.

If the element has a src attribute, does not have an async attribute, and does not have the "force-async" flag set The element must be added to the end of the list of scripts that will execute in order as soon as possible associated with the Document of the script element at the time the prepare a script algorithm started.

The task that the networking task source places on the task queue once the fetching algorithm has completed must run the following steps:

If the element is not now the first element in the list of scripts that will execute in order as soon as possible to which it was added above, then mark the element as ready but abort these steps without executing the script yet.

Execution: Execute the script block corresponding to the first script element in this list of scripts that will execute in order as soon as possible.

Remove the first element from this list of scripts that will execute in order as soon as possible.

If this list of scripts that will execute in order as soon as possible is still not empty and the first entry has already been marked as ready, then jump back to the step labeled execution.

If the element has a src attribute The element must be added to the set of scripts that will execute as soon as possible of the Document of the script element at the time the prepare a script algorithm started.

The task that the networking task source places on the task queue once the fetching algorithm has completed must execute the script block and then remove the element from the set of scripts that will execute as soon as possible.

Otherwise The user agent must immediately execute the script block, even if other scripts are already executing. Fetching an external script must delay the load event of the element's document until the task that is queued by the networking task source once the resource has been fetched (defined above) has been run.

From this I think that your "external" file is loaded after the inline script block. I would therefore use the "getScript()" function from jquery to make sure that the script is loaded before the inline script block.



回答2:

I have two theories:

  1. Might be that there's something in the external script that's delaying the creation of the app.viewModel.members object (either a timeout or an event handler that takes a while to fulfil). This can be easily tested by setting a long timeout in your inline script (f.i. 5000+ms) and then checking if the models object exists.

  2. There's something funky going on when loading a same origin script.

In this case you could try is delay the execution of your inline script by doing the following:

<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
    app.viewModel.members.event.init();
});
</script>

or just put your inline code in an external .js file and call it with the 'deferred' flag:

<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204"></script>
<script type="text/javascript" src="{link-to-external-js-file}" defer></script>


回答3:

This is a common problem in the injection scenerio. It occurs due to the variable delays in script availability, as well as due to the parallel and differing implementations on various browsers.

There are 3 options, depending on whether the source code is available for editing or not, and if more than 2 dependencies exist between the script files.

Option 1. Using defer attribute in the script tag

This option can be used, if both scripts are remote (ie, not inline)

"defer" indicates to the browser that the script has to execute after the document has been parsed (quoted from MDN). This is applicable only for remote (not inline) scripts, that have the "src" attribute.

https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer

You can use it like below.

Defer is supported on major browsers, and I validated on Chrome, Firefox, Webkit on Tizen, and Safari:

https://developer.mozilla.org/en/docs/Web/HTML/Element/script#Browser_compatibility

To provide concrete example of the above cases, refer below. Note that the below have been validated on Firefox, Chrome, IE11, Safari on iPhone, and Webkit on Tizen.

Case 1:

Many Javascript files - All independent:

If there is no dependency, the "defer" attribute allows the HTML to be loaded quickly. The script takes over after downloading, without issues (assuming onload etc are taken care).

Case 2:

Two javascript files test1.js and test2.js - One dependent on the other:

If test2.js is dependent on the loading of test1.js, then the script tag for test2.js "only" should have the defer attribute.

This usage is shown in

http://www.gpupowered.org/loadtest/2_defer.html

Incorrect usage is shown in

http://www.gpupowered.org/loadtest/no_defer.html (Both scripts do not have defer tag - this fails) http://www.gpupowered.org/loadtest/all_defer.html (Both scripts having defer tag - this also fails)

Async usage that does not work is at,

http://gpupowered.org/loadtest/2_async.html (this fails)

Where does "defer" not meet the needs ?

If the functionality is split across several JS files (say n), and all "n-1" need to be downloaded before "n"th file can start processing some variables, even though the "defer" attribute might be present on all the script source tags, it is rendered irrelevant because the order in which they are received are indeterminate.

More background on defer and the various options for deferred loading (doesnot cover the multi defer case) http://www.html5rocks.com/en/tutorials/speed/script-loading/

https://developer.mozilla.org/en/docs/Web/HTML/Element/script

Option 2: Using state variables

This option can be used if some additional state variables can be added to both javascript source files.

The approach relies on a named variable in the dependent js file, and a named function in the js file that uses the dependent file. If the dependent file is not loaded at the time the user file is trying to access its functionality, it exits and will be called back when it is really loaded.

This is demonstrated in the below html file.

http://gpupowered.org/loadtest/variable.html (works correctly)

This option does not work if the loading needs to happen repetitively (ie, loading of multiple files of same name etc).

Option 3: Native script loader

In this case, there are multiple javascript files having dependencies between each other.

There is no solution for this case using defer or async or other specification provided tags. For the use-case I had in the remote labs in gpupowered.org, I had to implement my own native script loader using XMLHttpRequest, and the source for this is provided in the link below. This uses worker threads as some of the textures I have are fairly big. The callback function can be used to implement the dependency logic as per the application needs. For example, keep count of all loaded scripts and then trigger full execution etc.

https://github.com/prabindh/gpupowered.gl/blob/master/worker/worker_object_loader.js

The jquery script loader uses the HTTP request as well, though I have not checked if it uses a worker for loading. https://api.jquery.com/jquery.getscript/



回答4:

function onloadCallback(){
    app.viewModel.members.event.init();
}


回答5:

Use this:

<script type="text/javascript" src="https://test.azureedge.net/Areas/Directors/scripts/app.members.event.js?v=204&onload=onloadCallback"></script

<script type="text/javascript">function onloadCallback(){
app.viewModel.members.event.init();}</script>