- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.8k
Image: support ImageSource with headers #2442
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: master
Are you sure you want to change the base?
Conversation
| This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 616975e: 
 | 
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.
Leaving some notes on the code changes
There's a breaking change that might be introduced or reverted - it's regarding the onLoad prop and the parameter structure used - I've opened a thread on the relevant source code chunk
        
          
                packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | It would have been a good idea to ask about this before writing the PR, as there are numerous other plans around Image. There's this PR for crossOrigin support #2207 This milestone https://github.com/necolas/react-native-web/milestone/18 And a parallel effort to support W3C props on Image facebook/react-native#34424 | 
| Hi @necolas This PR is following the original idea to support headers via XHR, though I've opted to use  Overall I think the changes here are compatible with #2207 A big chunk of the changes comes from added tests The rest of the changes are small cleanups and moving existing logic - I can revert some of these namely  If there are any problems, I'm happy to discuss and modify the PR | 
| Please resolve the conflicts with master | 
| There are no conflicts with  There would be conflicts for the PR that happens to be merged last due to the changes to  Since the other PR just moves the function without changing the logic, it might be easier to do the same after this PR is merged | 
| If you could rebase/squash your 20 commits into just a few, that would help too. Thanks | 
27f7163    to
    7ecabef      
    Compare
  
    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.
Squashed existing commits to allow rebasing
        
          
                packages/react-native-web/src/exports/Image/__tests__/index-test.js
              
                Outdated
          
            Show resolved
            Hide resolved
        
      7ecabef    to
    8324905      
    Compare
  
    
      
        
              This comment was marked as off-topic.
        
        
      
    
  This comment was marked as off-topic.
8324905    to
    2d82778      
    Compare
  
    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.
PR Updated using an alternative strategy to load images with headers resulting in less changes overall
When a source with headers is used, we use a (kind of) a HOC component that fetches the source and then passes a local uri created with URL.createObjectURL to the original Image component
| nativeEvent.source = { | ||
| uri: image.src, | ||
| width: image.naturalWidth, | ||
| height: image.naturalHeight | ||
| }; | 
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.
Attach source to nativeEvent so onLoad matches the RN Image interface https://reactnative.dev/docs/image#onload
| @necolas any chance you or a fellow maintainer can check out these changes? We're fine to wait since, as you mentioned before, you have many updates planned - we're just hoping to get a possible timeline when this could get reviewed. | 
| It looks like the author is still working on the patch. It needs unit tests and the suggestion to make changes to make testing easier makes sense to me. | 
| What's next for this PR? The PR in the fork had no new unit tests and this one hasn't been updated for 2 weeks | 
| I haven't synced my latest changes here, and I still need to write some unit tests | 
8533f2d    to
    081216d      
    Compare
  
    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.
Ready for Review ✅
| jest.useFakeTimers(); | ||
| ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => { | ||
| onLoad(); | ||
| }); | 
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.
Fake timers no longer needed to pass this test
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.
Actually the fake timers are breaking the (new) tests that use Promises
For some reason unless we remove all calls to jest.useFakeTimers() - return waitFor(() => ...) does not work
| function raiseOnErrorEvent(uri, { onError, onLoadEnd }) { | ||
| if (onError) { | ||
| onError({ | ||
| nativeEvent: { | ||
| error: `Failed to load resource ${uri} (404)` | ||
| } | ||
| }); | ||
| } | ||
| if (onLoadEnd) onLoadEnd(); | ||
| } | 
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 was extracted for reuse out of the original Image component hook
(We reuse this in case loading with headers fails)
| /** | ||
| * This component handles specifically loading an image source with headers | ||
| * default source is never loaded using headers | ||
| */ | ||
| const ImageWithHeaders: ImageComponent = React.forwardRef((props, ref) => { | ||
| // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource` | ||
| const nextSource: ImageSource = props.source; | 
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.
Perhaps there's a way to declare ImageWithHeaders as an Image component where the source prop is exactly type ImageSource, but I don't know exactly how
I guess I need to declare ImageWithHeaderProp type that spreads the default Image prop type but changes the source. Should I do that?
| export type ImageProps = {| | ||
| ...$Exact<ViewProps>, | 
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 had Flow errors after my changes
Following the error messages and suggested fixes led to these changes
| type ImageBackgroundProps = { | ||
| type ImageBackgroundProps = {| | ||
| ...ImageProps, | ||
| imageRef?: any, | ||
| imageStyle?: $PropertyType<ImageProps, 'style'>, | ||
| style?: $PropertyType<ViewProps, 'style'> | ||
| }; | ||
| |}; | 
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 had Flow errors after my changes
Following the error messages and suggested fixes led to these changes
| Hi, please could you rebase this. Now that 0.19 is done, the 0.20 release will be focused on Image changes. | 
081216d    to
    616975e      
    Compare
  
    | ✅ Rebased | 
| return <BaseImage ref={ref} {...propsToPass} />; | ||
| }); | ||
|  | ||
| // $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet | 
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.
When this old $FlowIgnore is removed, there is a new type error:
Cannot assign React.forwardRef(...) to ImageWithStatics because property getSize is missing in AbstractComponent [1] but
exists in ImageStatics [2]. [incompatible-type]
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 I've seen the original error the comment references at some point
Could it be the error was fixed by a flow version update, but now we have this other error?
Extend ImageLoader functionality to be able to work with image sources containing headers We preserve the existing strategy that works with image.src for cases where source is just an uri with no headers When sources contain headers we make a fetch request and then render a local url for the downloaded blob (URL.createObjectURL) Fix #1019 Close #2442
| This PR was merged into the 0.20-dev branch #2508 | 
Extend ImageLoader functionality to be able to work with image sources containing headers We preserve the existing strategy that works with image.src for cases where source is just an uri with no headers When sources contain headers we make a fetch request and then render a local url for the downloaded blob (URL.createObjectURL) Fix #1019 Fix #2268 Close #2442
Extend ImageLoader functionality to be able to work with image sources containing headers We preserve the existing strategy that works with image.src for cases where source is just an uri with no headers When sources contain headers we make a fetch request and then render a local url for the downloaded blob (URL.createObjectURL) Fix #1019 Fix #2268 Close #2442

Details
Extend
ImageLoaderfunctionality to be able to work with image sources containing headersWe preserve the existing strategy that works with
image.srcfor cases wheresourceis just an
uriwith no headersWhen
sourcecontain headers we make afetchrequest and then render a local url for thedownloaded blob (
URL.createObjectURL)Fixed Issues
Test Strategy
Verify existing Image functionality has no regressions
npm run dev -w react-native-webandnpm run dev -w react-native-web-examplesImage: http://localhost:3000/imagemasterbranch. You can switch back and forth and verify the image are loading the same wayVerify Images with headers can be loaded
npm run dev -w react-native-webandnpm run dev -w react-native-web-examplesImage: http://localhost:3000/imagesourceWithHeadersherepackages/react-native-web-examples/pages/image/index.jsand try to load images from a server that expects a GET request with a headerExample Project Screenshot
The code is also tested in a standalone project and it loads images with headers successfully