Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compile): Enable multiple roots for a standalone module graph #17663

Merged
merged 9 commits into from
Mar 18, 2023

Conversation

andreubotella
Copy link
Contributor

@andreubotella andreubotella commented Feb 5, 2023

This change will enable dynamic imports and web workers to use modules not reachable from the main module, by passing a list of extra side module roots as options to deno compile.


This PR builds on top of #17657, which adds support for web workers to deno compile.

The option name --extra-side-modules is up for bikeshedding, as well as the exact way to specify such list of modules. I suspect this list could be added to deno.json.

Closes #17908

@bartlomieju bartlomieju added this to the 1.31 milestone Feb 6, 2023
Copy link
Collaborator

@aapoalas aapoalas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpicks and bikesheddings aside, looks good to me and really useful.

Statically analysable dynamic imports being picked up into the module graph automatically would be a really good feature to get working, as it would make both deno compile and Deno Deploy users quite happy indeed. For Workers I don't think it would work though, since it's possible to override the Worker name can be overridden both by local shadowing and globally by overriding globalThis.Worker.

cli/args/flags.rs Show resolved Hide resolved
cli/tests/testdata/compile/dynamic_imports/main.js Outdated Show resolved Hide resolved
cli/tests/testdata/compile/workers/not_in_module_map.js Outdated Show resolved Hide resolved
Copy link
Member

@dsherret dsherret left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to do it without needing --extra-side-modules.

Edit: let's wait on denoland/eszip#115

@andreubotella
Copy link
Contributor Author

We should be able to do it without needing --extra-side-modules.

Some flag would still be needed for non-analyzable dynamic imports, and for workers.

@dsherret
Copy link
Member

Sorry, I read too fast and missed about workers. I think that's reasonable for workers and in that case maybe we can just merge this without the analyzing of statically analyzable dynamic imports. For some reason, all our walking/traversing of the graph in the CLI skips over statically analyzable dynamic imports, but they're in the ModuleGraph. I'll review this now.

@aapoalas
Copy link
Collaborator

aapoalas commented Feb 11, 2023

I agree with @andreubotella that the flag would be useful for non-analysable imports but... It's actually also possible to do that without the flag:

const EXTRA_SIDE_MODULES = [
  () => import("./side-module-1.ts"),
  () => import("../root-side-module.ts"),
];

It's probably a matter of taste which is more elegant: Use the existing language features that we have, or add a configuration flag or option.

@andreubotella
Copy link
Contributor Author

andreubotella commented Feb 11, 2023

Sorry, I read too fast and missed about workers. I think that's reasonable for workers and in that case maybe we can just merge this without the analyzing of statically analyzable dynamic imports. For some reason, all our walking/traversing of the graph in the CLI skips over statically analyzable dynamic imports, but they're in the ModuleGraph. I'll review this now.

Thanks. You should review commit 4d8e192, since the previous ones come from #17657. I built this PR off of that one not because the actual code changes need to be based on that PR, but because that way I can add to one of the worker tests from that PR.

I agree with @andreubotella that the flag would be useful for non-analysable imports but... It's actually also possible to do that without the flag:

const EXTRA_SIDE_MODULES = [
  () => import("./side-module-1.ts"),
  () => import("../root-side-module.ts"),
];

It's probably a matter of taste which is more elegant: Use the existing language features that we have, or add a configuration flag or option.

This seems like a hard-to-explain non-obvious hack, if you're reading the code. It feels a lot cleaner to me to say "deno compile will try to detect dynamic imports, but it can't always, so use this flag just in case".

@nayeemrmn
Copy link
Collaborator

For some reason, all our walking/traversing of the graph in the CLI skips over statically analyzable dynamic imports

Not sure if you're talking about somewhere specific, but the logic of skipping over (even statically analyzable) dynamic imports exists for example in startup graph validation because we don't want to elevate dynamic module slot errors until the runtime import of that module happens.

@dsherret
Copy link
Member

Not sure if you're talking about somewhere specific, but the logic of skipping over (even statically analyzable) dynamic imports exists for example in startup graph validation because we don't want to elevate dynamic module slot errors until the runtime import of that module happens.

Yeah, why is that done? I was thinking that maybe someone would be downloading code on the fly, but that wouldn't work. It seems to shift an error that could be spotted at startup to being surfaced later on. Anyway, in this case we can make it work by updating eszip to not skip over dynamic imports: denoland/eszip#115

@andreubotella andreubotella force-pushed the standalone-side-modules branch from d91c285 to 7db57ef Compare February 11, 2023 20:14
@nayeemrmn
Copy link
Collaborator

nayeemrmn commented Feb 11, 2023

Yeah, why is that done? I was thinking that maybe someone would be downloading code on the fly, but that wouldn't work. It seems to shift an error that could be spotted at startup to being surfaced later on.

Because the import might not happen, and even be conditioned on whether or not it would error:

if (fs.exists("plugin.ts")) {
  await import("./plugin.ts")
}

Yeah the eszip patch should be sufficient for the deno compile packaging issue

@dsherret
Copy link
Member

@nayeemrmn that makes sense. Thanks for the background!

@nayeemrmn
Copy link
Collaborator

Re flag name, the term 'side module' in this context seems too colloquial doesn't it? --imports, --include or even just --modules would be better IMO.

@aapoalas
Copy link
Collaborator

Re flag name, the term 'side module' in this context seems too colloquial doesn't it? --imports, --include or even just --modules would be better IMO.

I would vote for --import or --include.

@aapoalas
Copy link
Collaborator

Potentially related and weird errors in in WPT tests for workers:

/html/webappapis/atob/base64.any.worker.html


file result: ok. 380 passed; 0 failed; 0 expected failure; total 380

error: Uncaught (in worker "") TypeError: globalThis_.addEventListener is not a function
    at promiseRejectMacrotaskCallback (internal:cli/runtime/js/99_main.js:376:17)
----------------------------------------
/webidl/ecmascript-binding/global-immutable-prototype.any.worker.html

test stderr:
error: Uncaught (in worker "") TypeError: globalThis_.addEventListener is not a function
    at promiseRejectMacrotaskCallback (internal:cli/runtime/js/99_main.js:376:17)

file result: failed. runner failed during test

First one doesn't fail but it's a bit suspicious. Coming from the 99_main.js file:

    // Add a callback for "error" event - it will be dispatched
    // if error is thrown during dispatch of "unhandledrejection"
    // event.
    globalThis_.addEventListener("error", errorEventCb);

@dsherret
Copy link
Member

@aapoalas that one is #17405

cli/tests/integration/compile_tests.rs Outdated Show resolved Hide resolved
cli/args/flags.rs Outdated Show resolved Hide resolved
@bartlomieju bartlomieju removed this from the 1.31 milestone Feb 23, 2023
dsherret pushed a commit that referenced this pull request Mar 5, 2023
denoland/eszip#115 added support for statically-analyzed dynamic imports
in eszip, which made `deno compile` support dynamic imports starting
from #17858. This PR adds a test for it.

----

This test is adapted from PR #17663.

Closes #17908
@andreubotella
Copy link
Contributor Author

The dynamic_import test from this PR was split into #18017, and I've now updated the PR and added a new version of that test for dynamic imports that can't be statically analyzed. This is tested by reading the import path from the filesystem.

kt3k pushed a commit that referenced this pull request Mar 10, 2023
denoland/eszip#115 added support for statically-analyzed dynamic imports
in eszip, which made `deno compile` support dynamic imports starting
from #17858. This PR adds a test for it.

----

This test is adapted from PR #17663.

Closes #17908
@bartlomieju
Copy link
Member

Discussed during CLI team meeting, we are in favor of landing this PR and going with a flag appears to be a better solution - it's easier to explain and document than requiring statically analyzable dynamic imports. I'm sure we'll bike shed on the flag name a bit. Additional benefit is that it gives a clear way to move this option to the configuration file in the future.

This change will enable dynamic imports and web workers to use modules
not reachable from the main module, by passing a list of extra side
module roots as options to `deno compile`.
@andreubotella andreubotella force-pushed the standalone-side-modules branch from c731dfd to 1a4d647 Compare March 14, 2023 19:13
@andreubotella
Copy link
Contributor Author

andreubotella commented Mar 14, 2023

Rebased to not depend or sit on top of on #17657 anymore.

andreubotella added a commit to andreubotella/deno that referenced this pull request Mar 14, 2023
This change enables the use of web workers in binaries produced by
`deno compile`. This requires the main module to be present in the
compiled module graph, which is why if it is not already, it must be
explicitly added as an additional root (see denoland#17663).
Copy link
Collaborator

@aapoalas aapoalas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add a help comment to the command line argument, otherwise looks ready for merge.

cli/tests/testdata/compile/dynamic_imports/import_path Outdated Show resolved Hide resolved
cli/args/flags.rs Outdated Show resolved Hide resolved
Comment on lines +915 to +918
"Includes an additional module in the compiled executable's module \
graph. Use this flag if a dynamically imported module or a web worker main \
module fails to load in the executable. This flag can be passed multiple \
times, to include multiple additional modules.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I do comma separated list of urls like: --side-module=https://deno.land/std/flags/mod.ts,https://deno.land/std/version.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commas are valid in URLs and filenames.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I thought we already supported comma separated lists in other flags, but maybe I'm wrong here

cli/graph_util.rs Show resolved Hide resolved
Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it locally with a bunch of dynamic imports and while having to specify --side-module= multiple times is not super nice it does the job very well. I'm sure this functionality will unblock many new use cases for users and I'm strongly in favor of landing it. We can try to further iterate on the flag name and potentially making it more ergonomic, but it seems worth landing as is already.

Awesome work as always @andreubotella, LGTM.

cli/args/flags.rs Outdated Show resolved Hide resolved
Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@dsherret dsherret left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM too once the timeout is lowered. Thanks for contributing this and the other worker PR! Will be nice to have.

@@ -908,6 +909,20 @@ fn compile_subcommand<'a>() -> Command<'a> {
runtime_args(Command::new("compile"), true, false)
.trailing_var_arg(true)
.arg(script_arg().required(true))
.arg(
Arg::new("side-module")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was flag name settled? Me and @aapoalas suggested --import which is less colloquial than --side-module.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - it's still up to debate/bike-shedding, but I'm fine changing the name in a follow up. I'm not sure I like --import better than --side-module, seems more ambigious.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--include ? Shorter is better here...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with --include too

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah --include was our next one #17663 (comment), sounds perfect if it might include assets as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok let's switch to --include

Copy link
Contributor Author

@andreubotella andreubotella Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm aware, including assets in the binary is a very different thing than including a module in the module graph. Though I guess you could reconstruct the original TS file from the source map.

In any case, if you agree that the flag name should be --include, I'm fine with that.

Copy link
Member

@littledivy littledivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tangential: the flag could potentially be reused to define file system assets (ffi dylib / game assets / worker source) to be included in the compiled binary? It is a very useful feature to have IMO.

@bartlomieju
Copy link
Member

Tangential: the flag could potentially be reused to define file system assets (ffi dylib / game assets / worker source) to be included in the compiled binary? It is a very useful feature to have IMO.

I'm not sure eszip is able to handle these kinds of files, but if it can, we should explore this possibility.

In any case, if you agree that the flag name should be --include, I'm fine with that.

@andreubotella let's go with --include.

@andreubotella
Copy link
Contributor Author

Flag renamed.

Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you @andreubotella!

@bartlomieju bartlomieju merged commit b64ec79 into denoland:main Mar 18, 2023
@andreubotella andreubotella deleted the standalone-side-modules branch March 18, 2023 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add integration test for deno compile with statically analyzable dynamic imports
8 participants