-
Notifications
You must be signed in to change notification settings - Fork 6
Sync dynamic import transformer #876
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
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 4c62690 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| options.env.NATIVE_LAZY_LOADING_TRANSFORMER, | ||
| ); | ||
| let syncDynamicImportConfig = | ||
| options.env.SYNC_DYNAMIC_IMPORT_CONFIG || undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth adding an integration test?
| unresolved_mark, | ||
| config.sync_dynamic_import_config.clone(), | ||
| )), | ||
| config.sync_dynamic_import_config.is_some()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also only allow this visitor if the context is Tesseract?
| ) -> Self { | ||
| let config: SyncDynamicImportConfig = json_string_config | ||
| .as_ref() | ||
| .and_then(|json_str| serde_json::from_str::<SyncDynamicImportConfig>(json_str).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This deserialisation will be done for every file - is there any performance issues with that or is this fairly fast? The alternative is to do it in loadConfig and pass an object in to the visitor, though that'll also occur for every file due to the way Transformer config currently works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call out, I assumed that serde_json would be faster than JSON.parse. For loadConfig could I store the result in global? Or does Rust/Native Atlaspack also a global or singleton I can store the result in for the Atlaspack instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main consideration for me, even if it's not faster, is that Matt is working on some caching stuff that will affect loadConfig - some of that will be moved to a new setup type method that does only run once. Doing it in loadConfig now will be closer to that final state than having it hidden away in the Rust visitor.
In terms of caching the result, I don't think there's currently any blessed/"safe" way if it's not already available in the config API (which mostly deals with loading config from files, rather than from the environment). You should probably use the config API though, and the invalidateOnEnvChange to register an invalidation for when that environment variable changes.
I guess you could consider using a [buildCache](https://github.com/atlassian-labs/atlaspack/blob/main/packages/core/build-cache/src/buildCache.ts#L3) - which provides a mechanism for a KV cache (a Map really), that gets cleared before each build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to summarise, now that I've thought about it:
- make sure you call
config.invalidateOnEnv('ENV_NAME')for any config you read fromoptions.envin the JSTransformer - Parse in JSTransformer::loadConfig as this will eventually be done once (it's also clearer what's happening than having it hidden deep in a visitor)
- Consider using a buildCache for caching the JSON.parse result (i.e. you could use the source string as the key, and the parsed config as the value) - this automatically gets cleared between builds.
| } | ||
| } | ||
|
|
||
| fn __override_resolve_module_path(&mut self, override_fn: Option<fn(&str) -> Option<String>>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this function named with a __?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's meant to be an internal method for allowing tests to pass in a mocked version of file resolving. Was hoping the double _ would symbolise that. I can remove if it's confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think generally in Rust a _ prefix denotes unused. If a method is only required for testing you can actually add an annotation that will only compile it in for test builds, e.g.:
#[cfg(test)]
fn override_resolve_module_path(..).. the method won't exist outside of tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the method even necessary instead of just directly setting the property where currently called?
- Add config cache - Add tests
| } | ||
| } | ||
|
|
||
| config.invalidateOnEnvChange('SYNC_DYNAMIC_IMPORT_CONFIG'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeking feedback on if this is the right place to call config.invalidateOnEnvChange, I'm not too familiar with the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is right? @mattcompiles, can you sanity check that for me? It's the config that gets invalidate when the env changes.. this shouldn't be asset.invalidateOnEnvChange in transform, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's right, it'll invalidate the Asset anyway though.
📊 Type Coverage ReportCoverage Comparison
Files with Most Type Issues (Top 15)
This report was generated by the Type Coverage GitHub Action |
📊 Benchmark Results✅ No significant performance changes detected. 📊 Benchmark ResultsOverall Performance
🔍 Detailed Phase AnalysisThree.js Real Repository (JS)
Three.js Real Repository (Native)
💾 Unified Memory AnalysisThree.js Real Repository (JS) Memory Statistics
Sample Counts: JS: 6, Native: 268 Three.js Real Repository (Native) Memory Statistics
Sample Counts: JS: 6, Native: 665 🖥️ Environment
|
| invariant(typeof config?.entrypoint_filepath_suffix === 'string'); | ||
| invariant(Array.isArray(config.actual_require_paths)); | ||
|
|
||
| configCache.set('SYNC_DYNAMIC_IMPORT_CONFIG', config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're reinventing cached config reading here a little. I think we should just use config.getConfigFrom rather than using a custom build cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, so you're suggesting we should put this config inside a separate file for getConfigFrom to read? Does this deviate from our upcoming dynamic config work where we want all config to be centralised?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I've misread this slightly. The env var you're passing is the config, not a file that contains it which was my confusion. Build caches are a little notorious for causing problems so be careful 😅
| let enableLazyLoadingTransformer = Boolean( | ||
| options.env.NATIVE_LAZY_LOADING_TRANSFORMER, | ||
| ); | ||
| let syncDynamicImportConfig: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this config a temporary solution or a permanent one? As all this code won't run when atlaspackV3 is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaning towards temporary until we have dynamic config, the reason why we're adding it to options.env is so we can feature flag the addition of config, where this was not possible with package.json. Are we planning on consuming atlaspackV3 soon? If so could you please point me to the transformer-js config equivalent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The V3 version of this file is located here: crates/atlaspack_plugin_transformer_js/src/js_transformer.rs
We're already using this for production Jira builds and we're rolling it out to more products over the next month.
| ) -> Self { | ||
| let config: SyncDynamicImportConfig = json_string_config | ||
| .as_ref() | ||
| .and_then(|json_str| serde_json::from_str::<SyncDynamicImportConfig>(json_str).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember, what was the reasoning for deserialising in Rust instead of JS? Because the latter would be simpler of course
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why GitHub marked this comment as outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code no longer exists, please check latest changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I can see it is gone, that means there are some dangling serde and probably other imports that can now be removed too.
| } | ||
| } | ||
|
|
||
| fn __override_resolve_module_path(&mut self, override_fn: Option<fn(&str) -> Option<String>>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the method even necessary instead of just directly setting the property where currently called?
| }) | ||
| } | ||
|
|
||
| fn create_dummy_promise(&self) -> Expr { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be able to use quote!() for these kinds of AST construction
| unresolved_mark, | ||
| &config.sync_dynamic_import_config, | ||
| )), | ||
| config.is_tesseract && config.sync_dynamic_import_config.is_some()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than pass is_tesseract all the way down the chain into Rust, you could mimic what was done with is_worker where the combined check is made in JS so Rust doesn't itself know anything.
- Support V3
Motivation
Provide native functionality parity for handling dynamic imports, mimicking its Babel counterpart.
Changes
Adds a transformer that converts dynamic imports to a
requirestatement for configured paths and EntryPoints, or a dummy noop Promise by default.Checklist
docs/folder