Import a module from string variable
Asked Answered
D

9

33

I need to import a JavaScript module from an in memory variable. I know that this can be done using SystemJS and Webpack.

But nowhere can I find a good working example nor documentation for the same. The documentations mostly talks of dynamic import of .js files.

Basically I need to import the module like below

let moduleData = "export function doSomething(string) { return string + '-done'; };"
//Need code to import that moduleData into memory

If anyone can point to documentation that will be great

Dou answered 20/7, 2019 at 2:59 Comment(6)
Do you have babel in your toolchain already? If so, I guess you already have something like webpack.js.org/loaders/babel-loader where you can configure plugins. Now, I did not find any plugin suiting your needs. But using babel-generator to parse and generate code (babeljs.io/docs/en/babel-generator) and this docs github.com/jamiebuilds/babel-handbook you may write your own plugin.Orthogenic
This Q has the feel of an XY problem. I've used module loaders and module bundlers with JavaScript for years, and contributed to relevant projects, but never ever have I run into a problem that required a solution entailing importing a string as a module. There have been cases where I could have solved my problem by doing this, but there were better solutions available. The way the question is currently written though, we don't know the X part of the XY problem. Why must the module be imported from a string containing the source of the module?Unbidden
I agree with @Louis. This is an incredibly odd thing to ask, and explain why you need to do this could certainly help you find a solution.Firsthand
Basically we have an Angular App, where in we let users create Angular components including HTML Template, TS file and CSS. Once they type those we need to compile and load that in same Angular App. We have figured out how to merge and compile the HTML TS and CSS into a JS module, now the loading part is letDou
If you need to do this entirely within the browser (as you've indicated in comments on answers), I don't believe this is possible. Keep in mind that running arbitrary code is extremely dangerous as well.Firsthand
You can check this example, maybe it fit your expectation github.com/SaifJerbi/odessajs19Nevers
M
27

There are limitations in the import syntax that make it difficult to do if not impossible without using external libraries.

The closest I could get is by using the Dynamic Import syntax. Two examples follow: the first one is the original I wrote while the second one is an edit from a user that wanted to modernize it.

The original one:

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<script>
    var moduleData = "export function hello() { alert('hello'); };";
    var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
    
</script>
<script type="module">

    async function doimport() {
      const module = await import(b64moduleData);
      module.hello();
    }
    
    doimport();

</script>

</body>
</html>

The modernized one:

function doimport (str) {
  if (globalThis.URL.createObjectURL) {
    const blob = new Blob([str], { type: 'text/javascript' })
    const url = URL.createObjectURL(blob)
    const module = import(url)
    URL.revokeObjectURL(url) // GC objectURLs
    return module
  }
  
  const url = "data:text/javascript;base64," + btoa(moduleData)
  return import(url)
}

var moduleData = "export function hello() { console.log('hello') }"
var blob = new Blob(["export function hello() { console.log('world') }"])

doimport(moduleData).then(mod => mod.hello())

// Works with ArrayBuffers, TypedArrays, DataViews, Blob/Files 
// and strings too, that If URL.createObjectURL is supported.
doimport(blob).then(mod => mod.hello())

You will however notice that this has some limitations on the way the import code is constructed, which may not precisely match what you need. The simplest solution is probably to send the code of the module on the server for it to generate a temporary script to be then imported using a more conventional syntax.

Marlinemarlinespike answered 29/7, 2019 at 14:11 Comment(2)
Almost three years later, you saves my life! Thank you.Monolayer
It works as is using vanilla JS, but when using Webpack, I had to add /* webpackIgnore: true */ before the url like import(/* webpackIgnore: true */ url), otherwise Webpack throws an error saying module can't be found (see: github.com/webpack/webpack/issues/12731)Bayonet
M
9
let moduleData = await import("data:text/javascript,export function doSomething(str) { return str + '-done'; }");

and to test it

moduleData.doSomething('test');
Mackinaw answered 24/12, 2021 at 4:59 Comment(0)
B
3

Use nodejs flag --experimental-modules to use import ... from ...;

node --experimental-modules index.mjs
index.mjs:
import fs from 'fs';
let fileContents = "export function doSomething(string) { return string + '-done'; };"
let tempFileName = './.__temporaryFile.mjs';

fs.writeFileSync(tempFileName, fileContents);
console.log('Temporary mjs file was created!');

import(tempFileName)
    .then((loadedModule) => loadedModule.doSomething('It Works!'))
    .then(console.log)


Further reading here

How It Works:

  1. I first create the file with fs.writeFileSync
  2. then I use import method's promise to load module and
  3. pipe doSomething method call with "It Works!"
  4. and then log the result of doSomething.

Credits: https://mcmap.net/q/67988/-how-can-i-use-an-es6-import-in-node-js-duplicate, https://v8.dev/features/dynamic-import, @Louis

Ballentine answered 20/7, 2019 at 3:3 Comment(6)
eval is your solution here tough it is not recommended to use it. w3schools.com/js/js_best_practices.asp read some more about it hereTriumph
eval = evil good explanationRiancho
The objection "eval = evil" is not a good one when it comes to dealing with executing module code. The default mode of operation of SystemJS, Webpack, RequireJS and any other JS module loader I've used is to run the code with the exact same privileges as doing eval would. In other words, require("foo") is not any safer than fetching the source of the module foo and then evaling it. Some module loaders even have eval(sourceOfModule) as part of the module loading process. This being said, eval won't handle the OP's export statement and so it not enough to do what the OP wants.Unbidden
Post-edit, this answer is still not hitting the mark. Do a search for "es6 dynamic import". Static imports may be hoisted but dynamic imports are not hoisted. I don't think dynamic imports are implemented to allow passing to the import() call a module's source code, but there's could be a plugin for SystemJS or Webpack that allows it. Even if such plugin does not currently exist, I'm sure one could be developed. I would not call what the OP wants to do "impossible".Unbidden
@Unbidden yes you were right. I got the answer by searching google about the message. I will include the source as well. Thank you!Ballentine
Basically we have an Angular App, where in we let users create Angular components including HTML Template, TS file and CSS. Once they type those we need to compile and load that in same Angular App. We have figured out how to merge and compile the HTML TS and CSS into a JS module, now the loading part is leftDou
C
3

NodeJS:

    // Base64 encode is needed to handle line breaks without using ;
    const { Module } = await import(`data:text/javascript;base64,${Buffer.from(`export class Module { demo() { console.log('Hello World!') } }`).toString(`base64`)}`)
    let instance = new Module()
    instance.demo() // Hello World!
Charmer answered 3/6, 2022 at 20:40 Comment(0)
A
1

You can create component and Module on fly. But not from string. Here is an example:

name = "Dynamic Module on Fly";
const tmpCmp = Component({
  template:
    '<span (click)="doSomething()" >generated on the fly: {{name}}</span>'
})(
  class {
    doSomething(string) {
      console.log("log from function call");
      return string + "-done";
    }
  }
);
const tmpModule = NgModule({ declarations: [tmpCmp] })(class {});

this._compiler.compileModuleAndAllComponentsAsync(tmpModule).then(factories => {
  const f = factories.componentFactories[0];
  const cmpRef = this.container.createComponent(f);
  cmpRef.instance.name = "dynamic";
});

Here is the stackblitz

Afro answered 25/7, 2019 at 11:1 Comment(2)
Wont work We let the users create the module also on FlyDou
Basically we have an Angular App, where in we let users create Angular components including HTML Template, TS file and CSS. Once they type those we need to compile and load that in same Angular App. We have figured out how to merge and compile the HTML TS and CSS into a JS module, now the loading part is leftDou
T
0

Based on Feroz Ahmed answer, here is a specific example to dynamically load a Vue 3 component from the server via socket.io.

Server-side (Node + socket.io)

socket.on('give-me-component', (data: any, callback: any) => {

  const file = fs.readFileSync('path/to/js/file', 'utf-8');

  const buffer = Buffer.from(file).toString('base64');
          
  callback(`data:text/javascript;base64,${buf}`);

});

Client-side (Vue 3 + socket.io-client)

socket.emit('give-me-component', null, async (data: any) => {

  // Supposes that 'component' is, for example, 'ref({})' or 'shallowRef({})'
  component.value = defineAsyncComponent(async () => {

    const imp = await import(data);

    // ... get the named export you're interested in, or default.

    return imp['TheNamedExport_or_default'];

  });

});

... 

<template>
  <component :is="component" />
</template>
Tedious answered 7/4, 2022 at 16:48 Comment(0)
C
0

For some weird reason, the suggested way of dynamically import()-ing a "data" URL throws an error: TypeError: Invalid URL when done from an npm package code.

The same "data" URL import()s without any errors from the application code though.

Weird.

const module = await import(`data:text/javascript;base64,${Buffer.from(functionCode).toString(`base64`)}`)
Caliginous answered 9/1, 2023 at 16:27 Comment(0)
P
0

This will work natively

import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"' with { type: 'json' }; 

https://nodejs.org/api/esm.html#data-imports

Payload answered 23/10, 2023 at 20:46 Comment(0)
K
-7
<script type="module">
    import { myfun } from './myModule.js';
    myfun();
</script>

/* myModule.js  */
export function myfun() {
    console.log('this is my module function');
}
Kokura answered 20/7, 2019 at 3:8 Comment(1)
That is absolutely not dynamic.Firsthand

© 2022 - 2024 — McMap. All rights reserved.