loading...
Cover image for Loading scripts on your webpage

Loading scripts on your webpage

jochemstoel profile image Jochem Stoel ・1 min read

I missed my train and have a hour to fill so let's talk about something simple, loading scripts on a webpage.

As a bundle

A super easy way to enforce all your scripts being loaded is to bundle them all into one single file. This however is expensive (stupid) because you can not import only the ones you need this way.

As scripts document

As little ducklings we were taught to place scripts in the document head as they are loaded first thing immediately when loading the webpage.

<html>
    <head>
        <title></title>
        <script src="main.js"></script>
        <script src="util.js"></script>
        <script src="events.js"></script>
        <script src="speech.js"></script>
    </head>
    <body>
    <h1>Page Title</h1>
    </body>
</html>

A script element inside the document head can not access HTML elements declared after the script because when the script is being loaded, the target element does not exist yet. In other words, in the above example you can not access the h1 element from main.js. This is why often ducklings want their scripts to be loaded after the rest of the page already has.

As scripts at end of body

If you want to execute your scripts after the document has loaded then simply put them at the end of your body.
It became common practice at some point to do this because it would speed up loading of your page. What they mean by this is that the page images and stylesheets will already have been loaded. The script tags do not block/delay them. This is much better for the user.

<html>
    <head>
        <title></title>
    </head>
    <body>
    <h1>Page Title</h1>
    <script src="main.js"></script>
    <script src="util.js"></script>
    <script src="events.js"></script>
    <script src="speech.js"></script>
    </body>
</html>

It is very possible to access elements on the page from scripts declared in the page head but you have to wait for an event that tells you the page has loaded. In the old days one assigned an onload attribute to the page body.

<body onload="method()"></body>

Using jQuery

Everybody knows the following.

$(document).ready(function() {
  // the page has finished loading
}

Vanilla

This is almost the same as above but without jQuery.

document.addEventListener('DOMContentLoaded', event => {
  // the page has finished loading
})

Injecting them programmatically

You can imitate some sort of require function by injecting scripts into your head. This really not as scary as it sounds.

function importScript(src) {
    let script = document.createElement('script')
    script.setAttribute('src', src)
    document.head.appendChild(script)
}

importScript('main.js')
importScript('util.js')
importScript('events.js')
importScript('speech.js')

With async function

Some people insist on wrapping mutliple promises into an asynchronous function.

// imagine the same import function but one that implements a Promise.
document.addEventListener('DOMContentLoaded', async event => {
  window.main = await importScript('main.js')
  window.util= await importScript('util.js')
  window.events= await importScript('events.js')
  window.speech = await importScript('speech.js')
}

As modules

It is now 2018, the ducklings have become swans and we can import modules using an extra attribute in our script tags. The functional programmers are bent on this and they are probably the reason it is spreading to Node.

<script type="module">
  import * as util from './util.js'
  util.default()
  util.extra()
</script>
<script type="module">
  import { display, extra } from './main.js' // that too
  display.message()
  extra()
</script>

A friend of mine asked why Node doesn't simply use export. This is because Node was there a lot earlier than when the export keyword was introduced. :P

The import keyword used as a function enables dynamic imports. It returns a Promise that resolves whatever the script exports. Using import like this does not require the type="module" attribute of your script tag.

import('./util').then(module => window.util = module).catch(console.error)

Because import makes promises, we can await it in the DOMContentLoaded event as long as our event handler function is async.

document.addEventListener('DOMContentLoaded', async event => {
    window.util = await import('./util')
})

To load multiple scripts, simply iterate an Array

For some reason you might want a resolver function to import scripts by identifier (not full path) and why not a context object that in this case defaults to window. What you see below is not ideal but you get the point.

let libs = ['main', 'utils', 'session']
const init = async (context = window) => libs.forEach(async lib => context[lib] = await import(init.resolve(lib)))
init.resolve = lib => `./js/${lib}.js`


init(window) // libs are now properties of window
init({}) // initialize on empty object
init({
    utils: 'abc'
}) // utils is overwritten

Using RequireJS

Personally I never understood why anyone would think that this is what they need in life. It never solved any problems for me. However because of its reputation it needs to be included.

requirejs(["util"], util => {
    //This function is called when util.js is loaded.
    window.util = util
})

Acquire

A simplification of requirejs that evaluates the responseText of a XMLHttpRequest in its own context that contains a module identifier. There was no fetch at the time. There were no module scripts or import/export keywords. Acquire supports both synchronous and asynchronous with a single function call but a synchronous XMLHttpRequest is perhaps the most deprecated thing you can do period.

If you liked this article then come back here more often on dev.to

Posted on Nov 8 '18 by:

jochemstoel profile

Jochem Stoel

@jochemstoel

Pellentesque nec neque ex. Aliquam at quam vitae lacus convallis pulvinar. Mauris vitae ullamcorper lacus. Cras nisi dui, faucibus non dolor quis, volutpat euismod massa. Donec et pulvinar erat.

Discussion

markdown guide
 

You wrote that it is done that way in Vanilla JS :

document.addEventListener('DOMContentLoaded', event => {
// the page has finished loading
})

Isn't this plain JS ?

 

Vanilla JS is plain JS, some more info: stackoverflow.com/a/20435744/7446162

If you enable all the "modules" the download size is still 0 bytes: vanilla-js.com/
Vanilla JS

 

Haha okay I get it.

I guess the joke couldn't have worked on me because I don't use Javascript libraries

 

So sweet, I never expected that question.

 

Potentially relevant (and proven quite useful): You can use <script defer="defer"> to load scripts after a page has loaded. (Beware that some of your scripts may still be asynchronous, so it is not guaranteed that all HTML elements can be accessed yet.)