Loading scripts on your webpage

jochemstoel

Jochem Stoel

Posted on November 8, 2018

Loading scripts on your webpage

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

💖 💪 🙅 🚩
jochemstoel
Jochem Stoel

Posted on November 8, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related