Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using React within a dynamically loaded es module

I have been loading a native ES Module which can be simplified to src/test.tsx:

export default class Test {
    constructor() {
        console.log('loaded');
    }
}

I can load this in my browser and initialize it great, a la:

import('http://127.0.0.1:8085/').then(m => { 
  const instance = new m.default();
});

However.. if I want to add any external dependency, in this case React, I can't seem to figure out how to target es6 and also bundle React, with tsc. So my dist file contains import React from 'react'; for which the browser has no idea how to resolve, and produces:

(index):1 Uncaught (in promise) TypeError: Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../".

My tsconfig looks like:

{
    "compilerOptions": {
        "baseUrl": ".",
        "rootDir": "src",
        "module": "es6",
        "target": "es2015",
        "lib": ["es6", "dom"],
        "declaration": true,
        "jsx": "react",
        "outDir": "dist",
        "strict": true,
        "noImplicitAny": false,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "experimentalDecorators": true,
        "moduleResolution": "node",
        "skipLibCheck": true
    },
    "include": ["./src/**/*"]
}

Running node v9.5.0, typescript v2.9.2

I have also attempted to use webpack to bundle everything but cannot figure out how to produce a dynamically importable module that way

like image 338
Martin Avatar asked Jul 20 '18 13:07

Martin


People also ask

How do you dynamically import a module in react JS?

To load dynamically a module call import(path) as a function with an argument indicating the specifier (aka path) to a module. const module = await import(path) returns a promise that resolves to an object containing the components of the imported module. } = await import(path);

How do you dynamically load component in React?

In React, dynamically importing a component is easy—you invoke React. lazy with the standard dynamic import syntax and specify a fallback UI. When the component renders for the first time, React will load that module and swap it in.

Do I need to import React in every component?

You no longer need to import React from "react" . Starting from the release 17 of React, JSX is automatically transformed without using React. createElement . However, other exports like hooks must be imported.


1 Answers

The problem

After building your typescript files, at the top of your components you are going to have some import statements like the following (assuming you are targeting es2015)

import React from "react";

The browser natively does not know how to resolve "react", for that reason it thinks that you have mistaken it for another relative path such as './react.js', and thus the error

(index):1 Uncaught (in promise) TypeError: Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../".

currently there is a proposal import-maps that will enable browsers to natively support theses import statements. Chrome is experimenting with this, and eventually browser javascript will have to provide a way to resolve imported modules.

The solution

You can either do a runtime solution or a compile time solution:

Runtime solution

I'm gonna use systemjs since it is already supported by typescript out of the box, you wanna change "module" to "system" inside compilerOptions, like

 "compilerOptions": {
   ....
   "module":"system"
  }

This will make typescript compile to a systemjs compatible code, in your root index html file you wanna include the systemjs file, and u also have to tell systemjs where to look for these modules

// root index.html
 <script src="https://unpkg.com/[email protected]/dist/system.js"></script>
 <script type="systemjs-importmap">
      {
        // for each library you add u have to include it here
        // documentation is here [systemjs import maps][3]d
        "imports": {
          "react": "https://unpkg.com/react@16/umd/react.production.min.js",
          "react-dom": "https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"
        }
      }
  </script>

and later you can dynamically load the needed module

// load your module later
System.import("./dist/index.js").catch()

compile time solution

You can use webpack, parcel or any other javascript bundler that will resolve these modules for you at compile time, and give you a bundle that has these modules already bundled within it.

I recommend a compile time solution, as it provides you with much more than just resolving modules, you can use css preprocessors, css modules, base64 inline images, and many things more.

like image 191
ehab Avatar answered Oct 20 '22 00:10

ehab