Skip to content

[experiment] implement tc39 Defer Module Evaluation #16567

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Jack-Works
Copy link
Contributor

@Jack-Works Jack-Works commented Dec 14, 2022

Does this PR introduce a breaking change?

no?

What needs to be documented once your changes are merged?

Defer import

This implements the semantics of the TC39 proposal Deferring Module Evaluation.

New options:

experiments.deferImport: true

To use the native syntax, install acorn-import-defer as a peer dependency.

// index.mjs
import defer * as ns from './mod.mjs'

function some_work() {
    console.log('deferred import value', ns.f)
}
console.log('entry runs')
setTimeout(some_work, 1000)

// mod.mjs
export function f() {
}
console.log('init cost!')

Result:

entry runs
// 1000ms later
init cost!
deferred import value, function f() {}

Other things to notice

Alternative syntax

import defer * as ns from './mod.mjs'

can be written as

import * as ns from /* webpackDefer: true */ './mod.mjs'

I suggest putting the magic comment after the from keyword, which is compatible with TypeScript, Babel, SWC, and Flow if the necessary option to preserve comments is enabled.

Please do not put it after the "import" keyword (like import /* webpackDefer: true */ * as ns from './mod.mjs'), the filesystem cache is broken, and I don't know why.

ESBuild is not compatible, please read evanw/esbuild#1439 and evanw/esbuild#309. Esbuild may support this feature ad-hoc like other webpack magic comments.

@alexander-akait
Copy link
Member

Yeah, we can implement this under the experimental flag, feel free to ping when it's ready

@webpack-bot
Copy link
Contributor

For maintainers only:

  • This needs to be documented (issue in webpack/webpack.js.org will be filed when merged)
  • This needs to be backported to webpack 4 (issue will be created when merged)

@Jack-Works Jack-Works force-pushed the lazy-import branch 2 times, most recently from 7420ab0 to 32408f7 Compare December 16, 2022 14:21
@Jack-Works

This comment was marked as resolved.

@alexander-akait
Copy link
Member

Just update snaphots (-u):

FAIL test/StatsTestCases.basictest.js (11.925 s, 87 MB heap size)
  ● Console

@alexander-akait
Copy link
Member

Test suite failed to run

Unable to find JEST_STATE_SYMBOL in Symbol(jest-native-promise), Symbol(jest-native-now), Symbol(jest-native-read-file), Symbol(jest-native-write-file), Symbol(jest-native-exists-file), Symbol($$jest-matchers-object), Symbol(graceful-fs.queue)

Is it locally?

@Jack-Works

This comment was marked as resolved.

@alexander-akait
Copy link
Member

@Jack-Works Sounds like a bug in jest... do you use the same steps as in our CI file? Based on CI report you just need to update snapshot, no more

@alexander-akait
Copy link
Member

Can you update StatsTestCases using -u flag for jest, thank you

@Jack-Works

This comment was marked as resolved.

@alexander-akait
Copy link
Member

alexander-akait commented Dec 20, 2022

@Jack-Works What is node version? You can try to change, also just check you have the same versions as in the lock file

@Jack-Works

This comment was marked as resolved.

@Jack-Works

This comment was marked as resolved.

@alexander-akait
Copy link
Member

Looks like 18.0.0 is the first breaking Node version. I don't know what happened exactly.

Weird, I don't have problem with jest locally... Okay, we can try to update jest (maybe new versions resolve this problem)

Also can you use Node@16 to update snapshots?

@Jack-Works

This comment was marked as resolved.

@Jack-Works
Copy link
Contributor Author

I tried to add some test cases (6041d87) but it seems like does not work... Did I do right?

@alexander-akait
Copy link
Member

That will be a little pain, I need to adjust my setup (installing nvm-for-windows seems to require uninstalling all npm globals and the current node install), I'll use this as the last solution joy

You can use virtual machine here 😄

@Jack-Works

This comment was marked as resolved.

@Jack-Works

This comment was marked as resolved.

@Jack-Works Jack-Works force-pushed the lazy-import branch 2 times, most recently from 9161b0f to 5250fd6 Compare March 23, 2023 11:09
Copy link

codspeed-hq bot commented May 2, 2025

CodSpeed Performance Report

Merging #16567 will degrade performances by 82.72%

Comparing Jack-Works:lazy-import (2d3b95c) with main (d4f275d)

Summary

⚡ 1 improvements
❌ 3 regressions
✅ 129 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
benchmark "devtool-eval-source-map", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 43.2 ms 10.3 ms ×4.2
benchmark "future-defaults", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 10 ms 52.6 ms -81.08%
benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 27.4 ms 34.8 ms -21.25%
benchmark "many-modules-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 11.2 ms 64.7 ms -82.72%

Copy link

codspeed-hq bot commented May 2, 2025

CodSpeed Performance Report

Merging #16567 will degrade performances by 23.26%

Comparing Jack-Works:lazy-import (de9c023) with main (3f36f91)

Summary

❌ 1 regressions
✅ 44 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
benchmark "typescript-long", scenario '{"name":"mode-development","mode":"development"}' 27.8 s 36.2 s -23.26%

@nicolo-ribaudo
Copy link

The same test becomes 23% slower in development and 23% faster in production? 🤔

@Jack-Works
Copy link
Contributor Author

hi @alexander-akait I think it is ready now!

@@ -46,6 +46,8 @@ class ImportParserPlugin {
Array.from(enumerable, e => [e]);
parser.hooks.importCall.tap("ImportParserPlugin", expr => {
const param = parser.evaluateExpression(expr.source);
// TODO support deferImport
// this requires the evaluateExpression to support Object literals.
Copy link
Member

Choose a reason for hiding this comment

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

@Jack-Works So we don't support import('something', { defer: true })? Do we have this in spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Jack-Works So we don't support import('something', { defer: true })? Do we have this in spec?

No, the syntax becomes import.defer('something') (this comment is written in a very early stage), I'll update this soon.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you

* @this {TODO}
* @param {TODO} node node
* @returns {TODO} import.meta
*/

This comment was marked as resolved.

Copy link
Member

@alexander-akait alexander-akait left a comment

Choose a reason for hiding this comment

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

@Jack-Works Sorry for delay, there are two question, and we can merge (want to merge it this week), thank you for your work

Co-authored-by: Nicolo Ribaudo <hello@nicr.dev>
@Jack-Works
Copy link
Contributor Author

updated, but import.defer() is marked as not implemented. I may implement it in the future

@alexander-akait
Copy link
Member

@Jack-Works Just for information - any blockers for import.defer('something')?

@Jack-Works
Copy link
Contributor Author

@Jack-Works Just for information - any blockers for import.defer('something')?

Not yet, I didn't do any research on this because I'm focusing on other things right now. I emit this error to make the PR merge-ready (without a breaking import.defer that acts as import).

@nicolo-ribaudo
Copy link

Fyi, TypeScript just merged support for this :)

])},`,
`defineProperty: ${runtimeTemplate.basicFunction("_, name", [
init,
// TODO:
Copy link
Member

Choose a reason for hiding this comment

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

What we should do here in future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A Module Namespace object's defineProperty checks the new property descriptor. It only returns true when the new property descriptor is the same as the current one.

This behaviour is skipped since webpack's implementation (lib/runtime/MakeNamespaceObjectRuntimeModule.js) for normal namespace object is skipping this too.

environment: {
optionalChaining: false
}
},
Copy link
Member

Choose a reason for hiding this comment

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

Very weird, I will resolve it before merge, no need action from you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Ready for Merge
Development

Successfully merging this pull request may close these issues.

8 participants