Mojimi
Mojimi

Reputation: 3161

Dynamically loading a module that is not in a script tag with type="module"?

Is it possible to use import without a script tag already in place for said module?

My problem is that I want to load modules dynamically based on a config file, for example :

Folder structure :

|-- main.js
|-- config.json.js
|-- modules
    |-- module1.js
    |-- module2.js
    |-- module3.js

Index.html head :

<script src="/config.json.js" type="module"></script>
<script src="/main.js"></script>

config.json.js :

export default {

  modules : ['module1', 'module3']

}

main.js :

import config from '/config.json.js'

//Loading modules defined in config
config.modules.forEach(moduleName => {
  import(`modules/${moduleName}`)
  .then( module => {
    console.log(`${module.name} loaded.`);
  )}
})

The above won't work as the modules haven't been defined in a script tag.

Is there any way I can achieve this using vanilla JS and keeping it clean?

Upvotes: 2

Views: 4486

Answers (2)

NVRM
NVRM

Reputation: 13067

Edit! Dynamic import has landed in Firefox 67+.

  (async () => {
    await import('./synth/BubbleSynth.js')
  })()

https://caniuse.com/#feat=es6-module-dynamic-import


Old answer:

To import further modules after DOM load, one not so clean but working way can be to create a new caller script of type module.

/* Example */
/* Loading BubbleSynth.js from «synth» folder*/
let dynamicModules = document.createElement("script")
dynamicModules.type = "module"
dynamicModules.innerText = "import * as bsynth from '../synth/BubbleSynth.js'"
/* One module script already exist. eq: «main.js», append after it */ 
document.querySelector("script[type='module']").parentElement.appendChild(dynamicModules)

Destroying the module caller script does not harm the past calls:

document.querySelectorAll("script[type='module']")[1].outerHTML = ""
// *BubbleSynth* is still in memory and is running

But appending a new module call to that script does not works. A new caller module script must be created.

As a function:

function dynModule(me){
  let dyn = document.createElement("script")
  dyn.type = "module"
  dyn.innerText = `import * as ${me} from '../synth/${me}.js'`
  document.querySelector("script[type='module']").parentElement.appendChild(dyn)
  document.querySelectorAll("script[type='module']")[1].outerHTML = ""
}

dynModule("BubbleSynth")

Upvotes: 2

Mrchief
Mrchief

Reputation: 76208

Yes you can, as long as your loader script is marked as module

<script type="module">
  const moduleSpecifier = './myModule.mjs';
  import(moduleSpecifier)
    .then((module) => {
      // do something
    });
</script>

Although, in your case, a simple forEach may not be enough. You may need Promise.all or similar if you want to wait for all the modules to load from your config.

const modules = config.modules.map(moduleName => import(`modules/${moduleName}`))

Promise.all(modules)
  .then(modules => {
    // modules is an array of all your loaded modules
    console.log('All modules loaded.');
  )}

Further reading:

Upvotes: 3

Related Questions