JavaScript - How to make ES6 imports work in browser?
Asked Answered
C

1

9

I'm starting a new project in which I'd like to have ES6 style modules, however, I can't get it to run in a browser. I'm using Chrome.

I isolated the issue into very few lines of code.

Here are my 2 TypeScript files:

app.ts

import { Component } from './component';

var component: Component = new Component();

component.ts

export class Component {

}

Here's how they compile to JavaScript:

app.js

import { Component } from './component';
var component = new Component();

component.js

export class Component {
}

My index.html only contains a script tag. I tried a few variations, but none of them worked.

index.html #1

<script src="src/app.js" type="module"></script>

The script is not loaded. (No request in network tab)

index.html #2

<script src="src/app.js" type=module></script>

The script is not loaded. (No request in network tab)

index.html #3

<script src="src/app.js"></script>

Uncaught SyntaxError: Unexpected token {

I'm using tsc to transpile TypeScript via Visual Studio Code.

tasks.json

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "typescript",
            "tsconfig": "tsconfig.json",
            "problemMatcher": [
                "$tsc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "reveal": "silent"
            }
        }
    ]
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "sourceMap": false,
    "removeComments": false,
    "noImplicitReturns": true,
    "noImplicitAny": true,
    "preserveConstEnums": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "outDir": "../src/"
  },
  "exclude": [
    "logs",
    "node_modules"
  ]
}
Cirillo answered 27/10, 2018 at 15:10 Comment(9)
It's simple: they don'tBernat
Are you sure? If so, what am I misunderstanding in here? contentful.com/blog/2017/04/04/…Cirillo
Yes you can use modules directly in the latest browsers, without a bundler! You should transpile ts to js files without defining any specific module system. Then you can load modules in the browser with <script type=‘module’ src=‘thing.js’>Ianiana
Ok I'm doing it now and the js looks cleaner, but the script still isn't loaded. I added more info at the bottom of the original question.Cirillo
have you tried writing a minimum constructor? TS classesWiseacre
I just tried this if this is what you meant, same results: export class Component { constructor() { } }Cirillo
In your transpiled app.js change your import statement to the following: import { Component } from './component.js'; Note: the file extension .js has been added to the Module Specifier. The ECMA-262 Imports Syntax only defines the syntax for modules and not the specific mechanism(s) for loading them. So the requirement for with or without a .js suffix may vary per implementation. However the inclusion of .js file extension is required in Safari browser, and most probably in Chrome too. Also, utilize <script src="src/app.js" type="module"></script> in index.htmlLateritious
Also worth noting that Chrome supports .mjs file extension in the Module Specifier too (as noted here), however the .mjs file extension does not work with the latest Safari browser.Lateritious
I tried this as well. It's true depending on your environment, but it wouldn't be the point here since no request to the script is even made, it's not like I have a request with a bad path which would've been easy to debug. See my last comment to ymz.Cirillo
D
12

To be honest - I think this is a good question because JS is widely use in both server-side and client-side application, which contributes to the already existing confusion among developers

It's clear that your TS code is written as server-side code (probably on Node.js). Trying to run it (as is) on client-side is... well.. tricky. The reason: The syntax you are using in your code suppose to run on server-side (not on client-side). Is there a workaround? Well... yes!

The good news:

JS ES6 does have a native module loader! (check MDN)

The bad ones:

  • Syntax is different from Node.js module loader syntax (when exporting a module)
  • Support is very partial (modern browsers only)

Some additional notes:

  • The common syntax of modules loading is associated with a third-party library called require js (https://requirejs.org/). You can use this library in client side projects but you have to install it and configure it properly (the docs are pretty clear about how to do that)
  • You can always use a task runner like grunt (https://gruntjs.com/) or similar projects to assist you to minify and unify all your code to a single file in production. Why you ask? It will clearly help you ease the load when a client fetch you website (less files are better in terms of network traffic)

As you see, there is no quick or simple answer to your question.. but it may be a good starting point to improve your knowledge and building better modern web apps.

UPDATE

As requested, I created a little sandbox demo that shows how to use ES6 native module (https://codesandbox.io/s/oj2rwm9v35)

index.html

<html>
<body>
    <div id="app"></div>
    <script type="module" src="src/primary.js"></script>
</body>
</html>

primary.js

import test from "./test";

test();

test.js

export default function test() {
  document.querySelector("#app").textContent = "Hello JS module!";
}
Drollery answered 27/10, 2018 at 18:16 Comment(5)
Thanks for the answer. Is it still relevant to the updated question description?Cirillo
I believe it is.. your options are: use native ES6 modules (and change the way you export your functions), install require (and configure it properly) or simply decide to use a task runner to help you convert TS to a single minified JS file that will be suitable to client-side environmentDrollery
I'm trying to use native ES6 modules. What's the difference between what I'm doing there and how it's supposed to be? The following import: import { Component } from './component'; seems to be the correct syntax. Also changing to import { Component } from './component.js'; doesn't make a difference. How come app.js isn't even loaded? I couldn't really understand from your answer, it'd be great if you could elaborate on that point.Cirillo
Thanks for the elaboration, so I don't think the problem is with the code itself. Does this work for you when you open it in your local file system? For me it doesn't. Both your code and mine work only when ran on a server. Any idea why?Cirillo
You are right. It works when running from localhost rather than from local file system. Also note that I defined a main module (which behaves like app module) and tried to load more modules from there - this approach (defining your app as a module, load it from a script tag and load other modules from this app module) should do the trick.. in my opinion at leastDrollery

© 2022 - 2024 — McMap. All rights reserved.