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

[asset] Support fromModule in Snack web environment #14435

Merged
merged 8 commits into from
Oct 21, 2021

Conversation

IjzerenHein
Copy link
Contributor

@IjzerenHein IjzerenHein commented Sep 16, 2021

Why

See expo/snack#213

The Asset.fromModule method fails in the Snack environment due to various reasons. This PR makes some straightforward fixes to Asset.fromModule and adds some changes to support the Snack environment.

This is important to support examples such as ImageManipulator and all other examples that depend on Asset.fromModule on web.

How

  • Check for nativeExtensions var before using it (this is not available in the Snack env)
  • Clean up getScriptURL and _coerceLocalScriptURL which always returned null
  • Add support for Snack asset urls which have optional asset-types
  • Fix non-server urls which were always prefixed with file://

Test Plan

@expo-bot expo-bot added the bot: suggestions ExpoBot has some suggestions label Sep 16, 2021
@@ -37,7 +37,7 @@ function getScaledAssetPath(asset): string {
const scale = AssetSourceResolver.pickScale(asset.scales, getScale());
const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
const assetDir = getBasePath(asset);
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
return assetDir + '/' + asset.name + scaleSuffix + (asset.type ? `.${asset.type}` : '');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is needed to support Snack assets served from S3 (which don't have an extension)

Copy link
Contributor

Choose a reason for hiding this comment

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

so a trailing . crashes it? Or is the issue that we are trying to deal with some strange handling of null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not crashing it, but it produces the wrong url. asset.type is optional and when omitted this code produces the wrong url-suffix: .undefined

for reference, we've also been patching react-native-web (for as long as I can remember) in snack to support this:
https://github.com/expo/snack/blob/main/runtime/patches/react-native-web%2B0.13.18.patch#L10

Comment on lines -41 to -63
function _coerceLocalScriptURL(scriptURL: string | undefined | null): string | null {
if (scriptURL) {
if (scriptURL.startsWith('assets://')) {
// android: running from within assets, no offline path to use
return null;
}
scriptURL = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
if (!scriptURL.includes('://')) {
// Add file protocol in case we have an absolute file path and not a URL.
// This shouldn't really be necessary. scriptURL should be a URL.
scriptURL = 'file://' + scriptURL;
}
}
return null;
}

function getScriptURL(): string | null {
if (_scriptURL === undefined) {
_scriptURL = _coerceLocalScriptURL(getSourceCodeScriptURL());
}
return _scriptURL;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_coerceLocalScriptURL always returns null causing getScriptURL to also always return null.
@EvanBacon Do you know whether this was ever intentional?

@@ -84,7 +84,7 @@ export default class AssetSourceResolver {
return this.fromSource(getScaledAssetPath(this.asset));
}
scaledAssetURLNearBundle(): ResolvedAssetSource {
const path = this.jsbundleUrl || 'file://';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Prepending file:// to the web asset path actually breaks it. This is because getScaledAssetPath returns the httpServerLocation which already has a protocol prefix

@IjzerenHein IjzerenHein marked this pull request as ready for review September 16, 2021 14:21
Copy link
Contributor

@jkhales jkhales left a comment

Choose a reason for hiding this comment

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

LGTM, but I'm also not super familiar with the web stuff.

let _sourceCodeScriptURL: string | undefined | null;

function getSourceCodeScriptURL(): string | undefined | null {
if (_sourceCodeScriptURL) {
return _sourceCodeScriptURL;
}

let sourceCode = nativeExtensions && nativeExtensions.SourceCode;
let sourceCode = typeof nativeExtensions !== 'undefined' ? nativeExtensions.SourceCode : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let sourceCode = typeof nativeExtensions !== 'undefined' ? nativeExtensions.SourceCode : null;
let sourceCode = nativeExtensions?.SourceCode ?? null;

or did you want an undefined to leak through if SourceCode was undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The suggestion would break stuff as nativeExtensions is not a declared global in the Snack environment. The PR makes the change to not depend on this nativeExtensions global

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand the point about "declared global"?
Screen Shot 2021-10-07 at 8 42 05 AM

seems to make logical sense and to pass ts analysis in vscode. I also read through some documentation: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html

Can you explain this typescript danger you are seeing a bit more so I can learn to not make the mistake in the future?

Copy link
Contributor Author

@IjzerenHein IjzerenHein Oct 14, 2021

Choose a reason for hiding this comment

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

On native, nativeExtensions is injected by react-native into the global namespace. On web this global is however not available and produces the following runtime error, when accessed:

resolveAssetSource.web.ts:18 Uncaught (in promise) ReferenceError: nativeExtensions is not defined
    at u (resolveAssetSource.web.ts:18)
    at resolveAssetSource.web.ts:28
    ...

When using nativeExtensions?.SourceCode, it would still try to access the global nativeExtensions variable and produce the runtime error. By using typeof nativeExtensions !== 'undefined' you can check whether the global exists before accessing it. Another way of doing that is by using global.nativeExtensions to check whether the global exists.

This is the same kind of error that you get when for instance accessing the window global from your server rendered page.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anyway, many thanks for the review @jkhales 👍 👍

}
_sourceCodeScriptURL = sourceCode.scriptURL;
_sourceCodeScriptURL = sourceCode?.scriptURL;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
_sourceCodeScriptURL = sourceCode?.scriptURL;
return (nativeExtensions?.SourceCode ?? NativeModules?.SourceCode)?.scriptURL

Would let you get rid of 5 lines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would produce the same runtime error and also no longer cache the _sourceCodeScriptURL for subsequent calls

@IjzerenHein IjzerenHein force-pushed the @hein/asset/from-module-snack-web branch from eeda53f to c7d9eec Compare October 14, 2021 07:10
@expo-bot expo-bot added bot: passed checks ExpoBot has nothing to complain about and removed bot: suggestions ExpoBot has some suggestions labels Oct 14, 2021
@IjzerenHein IjzerenHein merged commit 7049ee5 into master Oct 21, 2021
@IjzerenHein IjzerenHein deleted the @hein/asset/from-module-snack-web branch October 21, 2021 11:21
brentvatne pushed a commit that referenced this pull request Oct 21, 2021
* [asset] Fix fromModule exception on web

* [asset] Support Snack web assets

* [asset] Support Snack web assets

* update build files

* fix

* update changelog

* Add nativeExtension typing

* Update build files
byCedric added a commit to expo/snack that referenced this pull request Oct 28, 2021
These changes ship with expo-asset by default now

expo/expo#14435
byCedric added a commit to expo/snack that referenced this pull request Oct 28, 2021
* [runtime] Upgrade to Expo SDK 43 stable

* [runtime] Remove expo-asset patch

These changes ship with expo-asset by default now

expo/expo#14435

* [runtime] Update web customization files with cli

* [snack-sdk] Remove bundled native modules for SDK 39

* [snack-sdk] Update bundled native modules for sdk 43

* [snack-sdk] Drop Expo SDK 39 from sdk

* [snack-sdk] Update tests and snapshots

* [snack-sdk] Add changelog message about sdk 43 upgrade

* [website] Update the visible SDK versions

* [website] Update tests and snapshots

* [snackager] Update tests and snapshots

* [snackager] Set bundler test timeout to 2 minutes

* [ci] Force integration tests to run in band without blocking fast tests
EvanBacon added a commit that referenced this pull request Dec 13, 2022
# Why

- fix expo/router#131 by using the correct web
base URL, previously was failing to resolve the origin using a number of
naive checks.
- drop all incorrect offline checks for web assets
- utilize the newer @react-native/assets/registry package
- reuse upstream types
- fix file system type error
- Possibly breaks web snack support introduced here
#14435
- ~~Also might break some assumptions about Expo Webpack, will need to
do some testing.~~ Webpack just ignores all of this code.


<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How

<!--
How did you build this feature or fix this bug and why?
-->

# Test Plan

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
This is required for changes to Expo modules.
-->

- [ ] Documentation is up to date to reflect these changes (eg:
https://docs.expo.dev and README.md).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
- [ ] This diff will work correctly for `expo prebuild` & EAS Build (eg:
updated a module plugin).

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot: passed checks ExpoBot has nothing to complain about
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants