scripting / Scripting-News

I'm starting to use GitHub for work on my blog. Why not? It's got good communication and collaboration tools. Why not hook it up to a blog?
115 stars 10 forks source link

Flow of ES6 modules #189

Open scripting opened 3 years ago

scripting commented 3 years ago

I had a modest goal for today, to understand how ES6 modules work and have a simple Hello World app that demonstrates.

I've tried to work my way through a bunch of tutorials, the only one that I found that has downloadable demo app is from Mozilla, and I downloaded them, and uploaded the simplest one to a test server, and ran from the browser, Chrome on a Mac.

It works. Here's a link.

http://hunter.scripting.com/moduletest/

Note that will probably not exist for a long time.

So I'm trying to figure out the flow. I set a breakpoint at the beginning of main.js, and it does run, and I stepped through it and it worked.

But here's the question -- how did it get to main.js?

I don't see a call to any script in there from index.html. In fact there is no JS code in index.html.

So that's the beginning. How does this flow? Help appreciated.

scripting commented 3 years ago

This may be one of those times where carefully writing the question helped me answer it.

This seems to be a basic rule.

Any code that calls into a module must itself be a module.

As far as I can see none of the tutorials states this limit.

Background

Normally, code.js would just be JavaScript code, and I'd have a routine in there called startup, which I'd call from the body of index.html. This is just a convention I established so all my web apps would work this way.

Well you can't import from a non-module, so that means this convention won't work in this context. code.js must be a module for it to even begin to participate in the module system. That seems like a wrong design decision, I've been using modules under different names since UCSD Pascal units in 1979. You want to be able to use a package/module/unit whatever you call it, from normal non-module code.

Also all the demos I've seen stack the modules, making it more complex. A Hello World must be as simple as possible to illustrate the idea. There are a lot of things I can't tell if they're part of a bigger demo and not related to modules, so I'll keep reducing my Hello World app until something stops working.

scripting commented 3 years ago

Annoyance -- I learned NPM modules, why did they do it differently here? I suspect they'll say they were first, but not in my life they weren't. I've built a whole system of modules. It's like having a vinyl record collection and having to create a whole new collection when cassette tapes come out. Ugh.

Take for example my daveutils package. A ton of routines, you call them like this:

const utils = require ("daveutils");
const whenstart = new Date ();
console.log (utils.secondsSince (whenstart));

I like that everything from the daveutils package is referred to as utils.something.

That doesn't seem to apply to ES6 modules.

I suppose you can create that effect.

tabatkins commented 3 years ago

If you use import * as canvas from './modules/canvas.js';, rather than explicitly indicating what you're importing, you'll get that behavior - canvas.create(), etc.

(Note all the import forms at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)

tabatkins commented 3 years ago

Well you can't import from a non-module, so that means this convention won't work in this context. code.js must be a module for it to even begin to participate in the module system. That seems like a wrong design decision, I've been using modules under different names since UCSD Pascal units in 1979. You want to be able to use a package/module/unit whatever you call it, from normal non-module code.

The reason for this restriction (iirc) is that it being a module lets you interact with the module import graph, which is important and would be a breaking behavior change in some cases for legacy scripts.

But you can manually handle the import graph, and thus use modules from non-module code, if you call the import("./module.js") function instead - it'll return a promise for the module, rather than pausing your script until the import resolves. (You can, of course, mimic the behavior by immediately awaiting the promise.) This'll resolve to an object similar to what you'd get from the import * as foo syntax, but with a special key for the default export as well.

scripting commented 3 years ago

First, thanks for all the help. :-)

As promised here's the Hello World app.

https://github.com/scripting/es6ModulesHelloWorld

Please continue to use this thread for any questions, comments.

allenwb commented 3 years ago

I don’t think putting code.js in the element has any effect. Module scripts are always loaded async. You could try some System.logs to see if you can detect any difference with where it goes. It is presumably defined in the HTML specification, maybe somebody else here knows for sure.

Modules initialize roughly bottom up in import order starting from roots modules (modules with no used exports) in the order they occur in the HTML file. Initialization of a module first initializes all of the modules it imports (skipping modules that have already been initialized) and then evaluating the body of the module. Do this recursively top-down resulting in bottom up execution of uninitialized module bodies.

One important thing to know that isn’t covered by your hello world: the bindings that are imported are read only to the importer. In your example, code.js can’t assign to secsSince but module.js can. If module.js changed the value of secsSince, that change would be visible to code.js.

scripting commented 3 years ago

The code works. You can try it, this link was in the readme file.

Allen I have no idea what the rest of it means.

allenwb commented 3 years ago

Yes, I know it works as written. I've fixed some typos and tried to clarify my previous comment.

scripting commented 3 years ago

Okay I think this is the line you're referring to.

document.getElementById ("idMessagePlace").innerHTML = "Number of seconds since March 12, 2020: " + secsSince ("March 12, 2020");

What is it doing that you disagree with>?

allenwb commented 3 years ago

No, that line is fine.
Clearly, it all works.

I was questioning why <script type="module" src="code.js"></script> was nested within <head> rather than <body>.

I understand that for this simple program it makes absolutely no difference where you put it. I mostly wanted to point out that for module scripts there are new rules for interpreting the script loading attributes. EG: from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

async HTML5 For classic scripts, if the async attribute is present, then the classic script will be fetched in parallel to parsing and evaluated as soon as it is available.

For module scripts, if the async attribute is present then the scripts and all their dependencies will be executed in the defer queue, therefore they will get fetched in parallel to parsing and evaluated as soon as they are available.

scripting commented 3 years ago

Okay now I understand what you were saying. I did that without any thought because that's where I always put my includes. I never put them in the body. No reason why.

I usually have a bit of code in the body that calls the startup function in code.js. But that depends on jQuery, and I didn't want to include that in this Hello World app. Minimal dependencies.

I think I found out somewhere in this exploration that I couldn't call a function in a module from the body.

Anyway I have my Hello World, I'm ready to move on. :-)

<script>
    $(document).ready (function () {
        startup ();
        });
    </script>
allenwb commented 3 years ago

I think I found out somewhere in this exploration that I couldn't call a function in a module from the body.

You can call it from a \ Githubissues.

  • Githubissues is a development platform for aggregating issues.