├── .eslintrc
├── .github
└── workflows
│ └── buildDeploy.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── UPGRADE.md
├── build.js
├── dev
├── ErrorBoundry.tsx
├── app.tsx
├── index.html
├── index.js
└── sw.js
├── jest.config.cjs
├── jest.setup.ts
├── package-lock.json
├── package.json
├── renovate.json
├── src
├── Img.tsx
├── imagePromiseFactory.ts
├── index.test.jsx
├── index.tsx
└── useImage.tsx
└── tsconfig.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": ["eslint:recommended", "plugin:react/recommended"],
8 | "parser": "babel-eslint",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "ecmaFeatures": {
13 | "jsx": true
14 | }
15 | },
16 | "settings": {
17 | "react": {
18 | "version": "detect"
19 | }
20 | },
21 | "plugins": ["react", "react-hooks"],
22 | "globals": {
23 | "test": true,
24 | "expect": true
25 | },
26 | "rules": {
27 | "linebreak-style": ["error", "unix"],
28 | "react-hooks/rules-of-hooks": "error",
29 | "react-hooks/exhaustive-deps": "warn"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/buildDeploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 |
3 | on: push
4 |
5 | env:
6 | CI: true
7 | NODE_VER: 20
8 | NETLIFY_SITE_ID: 89194942-bd48-4c23-a181-7e489c17eabc
9 |
10 | jobs:
11 | lint:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: ${{ env.NODE_VER }}
19 |
20 | - uses: bahmutov/npm-install@v1
21 |
22 | - name: prettier
23 | run: npx prettier '**/*.{js?(on|x),ts?(x),y?(a)ml,graphql,md,css}' --check
24 |
25 | - name: typescript
26 | run: npx tsc --noEmit
27 |
28 | build:
29 | runs-on: ubuntu-latest
30 |
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-node@v4
34 | with:
35 | node-version: ${{ env.NODE_VER }}
36 | - uses: bahmutov/npm-install@v1
37 |
38 | - name: build
39 | run: npm run build
40 |
41 | - name: build package
42 | run: npm version --no-git-tag-version 0.0.0-${{ github.sha }} && npm pack
43 |
44 | - name: Upload build artifact
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: build
48 | path: dist
49 |
50 | - name: Upload package
51 | uses: actions/upload-artifact@v4
52 | with:
53 | name: react-image-0.0.0-${{ github.sha }}.tgz
54 | path: react-image-0.0.0-${{ github.sha }}.tgz
55 |
56 | test:
57 | runs-on: ubuntu-latest
58 | needs: build
59 |
60 | steps:
61 | - uses: actions/checkout@v4
62 | - uses: actions/setup-node@v4
63 | with:
64 | node-version: ${{ env.NODE_VER }}
65 | - uses: bahmutov/npm-install@v1
66 |
67 | - name: test
68 | run: npm test
69 |
70 | - name: download build artifact
71 | uses: actions/download-artifact@v4
72 | with:
73 | name: build
74 | path: dist
75 |
76 | - name: test built libs
77 | run: npm run test:dist
78 |
79 | - name: Setup env vars
80 | run: |
81 | printf -v SHORT_COMMIT_MESSAGE '%q ' `git log --oneline -n 1 HEAD --format=%B`
82 | echo "GIT_SHORT_COMMIT_MESSAGE=$SHORT_COMMIT_MESSAGE" | tee -a $GITHUB_ENV
83 | echo "GIT_SHORT_REVISION=$(echo ${GITHUB_SHA} | cut -c1-7)" | tee -a $GITHUB_ENV
84 |
85 | - name: Deploy visual tests to Netlify
86 | uses: nwtgck/actions-netlify@v3
87 | id: deploy-to-netlify
88 | with:
89 | publish-dir: dist/dev
90 | github-token: ${{ secrets.GITHUB_TOKEN }}
91 | deploy-message: '[${{ github.ref_name }}@${{ env.GIT_SHORT_REVISION }}] ${{ env.GIT_SHORT_COMMIT_MESSAGE }}'
92 | enable-pull-request-comment: false
93 | enable-commit-comment: false
94 | enable-commit-status: false
95 | # Use custom alias for non master branch Deploy Previews only
96 | alias: ${{ github.ref_name != 'master' && env.RUN_NAME || '' }}
97 | github-deployment-environment: '[${{ github.ref_name }}] visual tests'
98 | fails-without-credentials: true
99 | production-deploy: ${{ github.ref_name == 'master' }}
100 | env:
101 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
102 | NETLIFY_SITE_ID: ${{ env.NETLIFY_SITE_ID }}
103 |
104 | deploy:
105 | runs-on: ubuntu-latest
106 | needs: [lint, test]
107 | if: ${{ github.ref_name == 'master' }}
108 |
109 | steps:
110 | - uses: actions/checkout@v4
111 | - uses: actions/setup-node@v4
112 | with:
113 | node-version: ${{ env.NODE_VER }}
114 | - uses: bahmutov/npm-install@v1
115 |
116 | - name: setup .npmrc
117 | run: npm config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN
118 | env:
119 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
120 |
121 | - name: download build artifact
122 | uses: actions/download-artifact@v4
123 | with:
124 | name: build
125 | path: dist
126 |
127 | - name: check if package is newer than the published version
128 | id: checkPublish
129 | run: echo "::set-output name=shouldPublish::`npm run -s isNewerThanPublished`"
130 |
131 | - name: publish release package if needed
132 | if: steps.checkPublish.outputs.shouldPublish == 'true'
133 | run: npm publish
134 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .env
4 | build/
5 | npm-debug.log
6 | disc/
7 | coverage/
8 | umd/
9 | dist/
10 | tags
11 | *.d.ts
12 | *.tgz
13 | jsSrc/
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | semi: false,
3 | singleQuote: true,
4 | bracketSpacing: false
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # next
2 |
3 | - BREAKING: removed umd build
4 | - BREAKING: Img can now accept a ref
5 |
6 | # 4.1.0
7 |
8 | - Add support for React 18
9 |
10 | # 4.0.3
11 |
12 | - Update peerDependencies in package.json
13 |
14 | # 4.0.2
15 |
16 | - add IE support
17 |
18 | # 4.0.1
19 |
20 | - export types ImgProps/useImageProps
21 |
22 | # 4.0.0
23 |
24 | - BREAKING: all exports are now named exports
25 |
26 | # 3.0.3
27 |
28 | - build hooks for umd
29 |
30 | # 3.0.2
31 |
32 | - dont include typescript libs in build modules
33 | - include esm modules
34 |
35 | # 3.0.1
36 |
37 | - include missing files
38 |
39 | # 3.0.0
40 |
41 | - move to typescript
42 | - add useImage hook
43 | - allow for an image loader to be injected
44 | - BREAKING: requires react 16.8 or higher
45 |
46 | # 2.4.0
47 |
48 | - fix: TS Interface Error for 'src' attribute. Related to issue: #260
49 |
50 | # 2.3.0
51 |
52 | - fix: typescript declarations
53 |
54 | # 2.2.2
55 |
56 | - add: typescript declarations
57 |
58 | # 2.2.1
59 |
60 | - fix: Removes warnings of unsafe lifecycle methods from console due to react 16.9 update.
61 |
62 | # 2.2.0
63 |
64 | - fix:Use correct case for crossOrigin and ensure prop is used both for the initial image fetch and in the final `` element
65 |
66 | # 2.1.3
67 |
68 | - fix: nullify callbacks before removing - #237
69 |
70 | # 2.1.2
71 |
72 | - fix: don't call handlers multiple times, fixes: #236
73 |
74 | # 2.1.1
75 |
76 | - fix: unset incorrect prop in https://github.com/mbrevda/react-image/pull/223
77 |
78 | # 2.1.0
79 |
80 | - Add: abort image download on unmount https://github.com/mbrevda/react-image/pull/223
81 |
82 | # 2.0.0
83 |
84 | - build: move to rollup
85 | - Fix: Don't return a bool from constructor https://github.com/mbrevda/react-image/pull/220
86 |
87 | # 1.5.1
88 |
89 | - update babel loader to v7
90 |
91 | # 1.5.0
92 |
93 | - Add: `loaderContainer`/`unloaderContainer` (#208, #211). Thanks @eedrah!
94 | - Test: test built libs
95 |
96 | # 1.4.1
97 |
98 | - Fix: strip dev-specific code when compiling
99 |
100 | # 1.4.0
101 |
102 | - Add: `container` props
103 | - Fix: issue deleting `src` prop in Safari (#87)
104 | - Add: `babel-runtime` as peer dep for https://pnpm.js.org/ (#199, #200). Thanks @vjpr!
105 | - Add: (crude) demo including transitions
106 |
107 | # 1.3.1
108 |
109 | - bug: Don't pass decode prop to underlying ``
110 |
111 | # 1.3.0
112 |
113 | - Use img.decode() by default where available
114 |
115 | # 1.2.0
116 |
117 | - Add support for React 16
118 |
119 | # 1.0.1
120 |
121 | - move to new prop-types package
122 | - add 100% test coverage
123 |
124 | # 1.0.0
125 |
126 | - Renamed to react-image
127 |
128 | # 0.6.3
129 |
130 | - Housekeeping: update dependencies
131 | - Add recipes
132 |
133 | # 0.6.2
134 |
135 | - Fix Readme formatting
136 |
137 | # 0.6.1
138 |
139 | - Start iteration at current location
140 |
141 | # 0.6.0
142 |
143 | - Add a cache so that we don't attempt the same image twice (per page load)
144 |
145 | # 0.5.0
146 |
147 | - Fix issue where index would overshoot available sources
148 | - Don't try setting state if `this.i` was already destroyed, which probably means that we have been unmounted
149 |
150 | # 0.4.2
151 |
152 | - Remove Browsierfy config
153 |
154 | # 0.4.1
155 |
156 | - Revert 0.4.0
157 |
158 | # 0.3.0
159 |
160 | - Don't overshoot sourceList when state.currentIndex
161 | - Ensure state has been set before trying to load images when new props are delivered
162 |
163 | # 0.2.0
164 |
165 | - Restart the loading process when src prop changes
166 |
167 | # 0.1.0
168 |
169 | - Don't use until we know the image can be rendered. This will prevent the "jumping"
170 | when loading an image and the preloader is displayed at the same time as the image
171 |
172 | # 0.0.11
173 |
174 | - Don't require `src` to be set
175 |
176 | # 0.0.10
177 |
178 | - Made react a peer depends
179 |
180 | # 0.0.8
181 |
182 | - Return `null` instead of false from React component. Thanks @tikotzky!
183 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to React Image
2 |
3 | You can contribute to `react-image` in these ways:
4 |
5 | - Reporting a bug
6 | - Discussing the current state of the code
7 | - Submitting a fix
8 | - Proposing new features
9 |
10 | ## Reporting Bugs
11 |
12 | You encountered a bug? Report it by [opening a new issue](https://github.com/mbrevda/react-image/issues) on repository!
13 |
14 | ## Proposing Changes
15 |
16 | Pull requests are the best way to propose changes to the codebase and contribute. Follow this guide to send your PR:
17 |
18 | 1. Fork the repo, clone it and create your branch from `master`.
19 | 2. Commit the changes in created branch.
20 | 3. [Submit a pull request (referencing the issue)!](https://github.com/mbrevda/react-image/pulls)
21 |
22 | ## License
23 |
24 | `react-image` is available under the MIT License
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Moshe Brevda
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Image 🏝 🏖 🏜
2 |
3 | [](https://www.npmjs.com/package/react-image)
4 | [](https://www.npmjs.com/package/react-image)
5 | [](https://www.npmjs.com/package/react-image)
6 | [](https://www.npmjs.com/package/react-image)
7 | [](https://snyk.io/test/github/mbrevda/react-image)
8 |
9 | **React Image** is an `` tag replacement and hook for [React.js](https://facebook.github.io/react/), supporting fallback to alternate sources when loading an image fails.
10 |
11 | **React Image** allows one or more images to be used as fallback images in the event that the browser couldn't load the previous image. When using the component, you can specify any React element to be used before an image is loaded (i.e. a spinner) or in the event that the specified image(s) could not be loaded. When using the hook this can be achieved by wrapping the component with [``](https://reactjs.org/docs/react-api.html#reactsuspense) and specifying the `fallback` prop.
12 |
13 | **React Image** uses the `useImage` hook internally which encapsulates all the image loading logic. This hook works with React Suspense by default and will suspend painting until the image is downloaded and decoded by the browser.
14 |
15 | ## Getting started
16 |
17 | 1. To include the code locally in ES6, CommonJS, or UMD format, install `react-image` using npm:
18 |
19 | ```
20 | npm install react-image --save
21 | ```
22 |
23 | 2. To include the code globally from a cdn:
24 |
25 | ```html
26 |
27 | ```
28 |
29 | ## Dependencies
30 |
31 | `react-image` has no external dependencies, aside from a version of `react` and `react-dom` which support hooks and `@babel/runtime`.
32 |
33 | ## Documentation
34 |
35 | You can use the standalone component, documented below, or the `useImage` hook.
36 |
37 | ### useImage():
38 |
39 | The `useImage` hook allows for incorporating `react-image`'s logic in any component. When using the hook, the component can be wrapped in `` to keep it from rendering until the image is ready. Specify the `fallback` prop to show a spinner or any other component to the user while the browser is loading. The hook will throw an error if it fails to find any images. You can wrap your component with an [Error Boundary](https://reactjs.org/docs/code-splitting.html#error-boundaries) to catch this scenario and do/show something.
40 |
41 | Example usage:
42 |
43 | ```js
44 | import React, {Suspense} from 'react'
45 | import {useImage} from 'react-image'
46 |
47 | function MyImageComponent() {
48 | const {src} = useImage({
49 | srcList: 'https://www.example.com/foo.jpg',
50 | })
51 |
52 | return
53 | }
54 |
55 | export default function MyComponent() {
56 | return (
57 |
58 |
59 |
60 | )
61 | }
62 | ```
63 |
64 | ### `useImage` API:
65 |
66 | - `srcList`: a string or array of strings. `useImage` will try loading these one at a time and returns after the first one is successfully loaded
67 |
68 | - `imgPromise`: a promise that accepts a url and returns a promise which resolves if the image is successfully loaded or rejects if the image doesn't load. You can inject an alternative implementation for advanced custom behaviour such as logging errors or dealing with servers that return an image with a 404 header
69 |
70 | - `useSuspense`: boolean. By default, `useImage` will tell React to suspend rendering until an image is downloaded. Suspense can be disabled by setting this to false.
71 |
72 | **returns:**
73 |
74 | - `src`: the resolved image address
75 | - `isLoading`: the currently loading status. Note: this is never true when using Suspense
76 | - `error`: any errors ecountered, if any
77 |
78 | ### Standalone component (legacy)
79 |
80 | When possible, you should use the `useImage` hook. This provides for greater flexibility and provides support for React Suspense.
81 |
82 | Include `react-image` in your component:
83 |
84 | ```js
85 | import {Img} from 'react-image'
86 | ```
87 |
88 | and set a source for the image:
89 |
90 | ```js
91 | const myComponent = () =>
92 | ```
93 |
94 | will resolve to:
95 |
96 | ```js
97 |
98 | ```
99 |
100 | If the image cannot be loaded, **`` will not be rendered**, preventing a "broken" image from showing.
101 |
102 | ### Multiple fallback images:
103 |
104 | When `src` is specified as an array, `react-image` will attempt to load all the images specified in the array, starting at the first and continuing until an image has been successfully loaded.
105 |
106 | ```js
107 | const myComponent = () => (
108 |
111 | )
112 | ```
113 |
114 | If an image has previously been attempted unsuccessfully, `react-image` will not retry loading it again until the page is reloaded.
115 |
116 | ### Show a "spinner" or other element before the image is loaded:
117 |
118 | ```js
119 | const myComponent = () => (
120 |
124 | )
125 | ```
126 |
127 | If an image was previously loaded successfully (since the last time the page was loaded), the loader will not be shown and the image will be rendered immediately instead.
128 |
129 | ### Show a fallback element if none of the images could be loaded:
130 |
131 | ```js
132 | const myComponent = () => (
133 |
137 | )
138 | ```
139 |
140 | ### NOTE:
141 |
142 | The following options only apply to the `` component, not to the `useImage` hook. When using the hook you can inject a custom image resolver with custom behaviour as required.
143 |
144 | ### Decode before paint
145 |
146 | By default and when supported by the browser, `react-image` uses [`Image.decode()`](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode) to decode the image and only render it when it's fully ready to be painted. While this doesn't matter much for vector images (such as svg's) which are rendered immediately, decoding the image before painting prevents the browser from hanging or flashing while the image is decoded. If this behaviour is undesirable, it can be disabled by setting the `decode` prop to `false`:
147 |
148 | ```js
149 | const myComponent = () => (
150 |
151 | )
152 | ```
153 |
154 | ### Loading images with a CORS policy
155 |
156 | When loading images from another domain with a [CORS policy](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes), you may find you need to use the `crossorigin` attribute. For example:
157 |
158 | ```js
159 | const myComponent = () => (
160 |
161 | )
162 | ```
163 |
164 | ### Animations and other advanced uses
165 |
166 | A wrapper element `container` can be used to facilitate higher level operations which are beyond the scope of this project. `container` takes a single property, `children` which is whatever is passed in by **React Image** (i.e. the final `` or the loaders).
167 |
168 | For example, to animate the display of the image (and animate out the loader) a wrapper can be set:
169 |
170 | ```js
171 | {
174 | return
{children}
175 | }}
176 | />
177 | ```
178 |
179 | By default, the loader and unloader components will also be wrapped by the `container` component. These can be set independently by passing a container via `loaderContainer` or `unloaderContainer`. To disable the loader or unloader from being wrapped, pass a noop to `loaderContainer` or `unloaderContainer` (like `unloaderContainer={img => img}`).
180 |
181 | ## Recipes
182 |
183 | ### Delay rendering until element is visible (lazy rendering)
184 |
185 | By definition, **React Image** will try loading images right away. This may be undesirable in some situations, such as when the page has many images. As with any react element, rendering can be delayed until the image is actually visible in the viewport using popular libraries such as [`react-visibility-sensor`](https://www.npmjs.com/package/react-visibility-sensor). Here is a quick sample (psudocode/untested!):
186 |
187 | ```js
188 | import {Img} from 'react-image'
189 | import VisibilitySensor from 'react-visibility-sensor'
190 |
191 | const myComponent = () =>
192 |
193 |
194 |
195 | ```
196 |
197 | Note: it is not necessary to use **React Image** to prevent loading of images past "the fold" (i.e. not currently visible in the window). Instead just use the native HTML `` element and the `loading="lazy"` prop. See more [here](https://addyosmani.com/blog/lazy-loading/).
198 |
199 | ### Animate image loading
200 |
201 | see above
202 |
203 | ## License
204 |
205 | `react-image` is available under the MIT License
206 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # 4.0.0
2 |
3 | All upgrade are now named exports, so:
4 |
5 | ```js
6 | import Img from 'react-image'
7 | ```
8 |
9 | needs to be changed to:
10 |
11 | ```js
12 | import {Img} from 'react-image'
13 | ```
14 |
15 | # 3.0.0
16 |
17 | This version requires a version of react that supports hook (16.8 or greater)
18 |
19 | # 1.0.0
20 |
21 | For users of the original `react-image` only: please note props and behaviors changes for this release:
22 |
23 | - `srcSet` is not supported
24 | - `onLoad` & `onError` callbacks are currently private
25 | - `lazy` has been removed from the core lib. To lazy load your images, see the recipes section [here](https://github.com/mbrevda/react-image#delay-rendering-until-element-is-visible)
26 |
27 | If you have a need for any of these params, feel free to send a PR. You can also open an issue to discuss your use case.
28 |
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | import {rm} from 'node:fs/promises'
2 | import url from 'node:url'
3 | import {context, build} from 'esbuild'
4 | import open from 'open'
5 |
6 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
7 | const distOutdir = './dist/dev'
8 |
9 | const buildOpts = {
10 | entryPoints: ['./src/index.tsx', './src/Img.tsx', './src/useImage.tsx'],
11 | external: ['react', 'react-dom'],
12 | bundle: true,
13 | splitting: true,
14 | outdir: './dist/esm',
15 | format: 'esm',
16 | sourcemap: false,
17 | minify: true,
18 | jsxDev: false, // MODE === 'dev',
19 | jsx: 'automatic',
20 | }
21 |
22 | const devBuildOpts = {
23 | entryPoints: ['./dev/app.tsx', './dev/index.html', './dev/sw.js'],
24 | bundle: true,
25 | splitting: true,
26 | outdir: distOutdir,
27 | format: 'esm',
28 | sourcemap: true,
29 | minify: process.env.NODE_ENV !== 'development',
30 | jsxDev: true,
31 | jsx: 'automatic',
32 | loader: {'.html': 'copy'},
33 | }
34 |
35 | await rm('./dist', {recursive: true, force: true})
36 |
37 | if (process.env.NODE_ENV !== 'development') {
38 | // build esm version
39 | await Promise.all([
40 | build(buildOpts),
41 |
42 | // build cjs version
43 | build({
44 | ...buildOpts,
45 | format: 'cjs',
46 | outdir: './dist/cjs',
47 | splitting: false,
48 | }),
49 |
50 | // build dev site
51 | build(devBuildOpts),
52 | ])
53 | } else {
54 | const ctx = await context(devBuildOpts)
55 | await ctx.watch()
56 | let {port} = await ctx.serve({servedir: distOutdir})
57 | open(`http://localhost:${port}`)
58 | await ctx.dispose()
59 | }
60 |
61 | process.on('unhandledRejection', console.error)
62 | process.on('uncaughtException', console.error)
63 |
--------------------------------------------------------------------------------
/dev/ErrorBoundry.tsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 |
3 | export interface ErrorBoundary {
4 | props: {
5 | children: React.ReactNode
6 | onError?: React.ReactNode
7 | }
8 | }
9 | export class ErrorBoundary extends Component implements ErrorBoundary {
10 | state: {
11 | hasError: boolean
12 | error: Error | null
13 | }
14 | onError: React.ReactNode
15 |
16 | constructor(props) {
17 | super(props)
18 | this.state = {hasError: false, error: null}
19 | this.onError = props.onError
20 | }
21 |
22 | static getDerivedStateFromError(error) {
23 | // Update state so the next render will show the fallback UI.
24 | return {hasError: error, error}
25 | }
26 |
27 | render() {
28 | if (this.state.hasError) {
29 | if (this.onError) return this.onError
30 | // You can render any custom fallback UI
31 | return Something went wrong. {this.state.error?.message}
32 | }
33 |
34 | return this.props.children
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/dev/app.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Suspense,
3 | useState,
4 | useEffect,
5 | useRef,
6 | useLayoutEffect,
7 | } from 'react'
8 | import {createRoot} from 'react-dom/client'
9 | import {Img, useImage} from '../src/index'
10 | import {ErrorBoundary} from './ErrorBoundry'
11 |
12 | navigator.serviceWorker.register('/sw.js', {scope: './'})
13 | new EventSource('/esbuild').addEventListener('change', () => location.reload())
14 |
15 | const randSeconds = (min, max) =>
16 | Math.floor(Math.random() * (max - min + 1) + min)
17 |
18 | function Timer({delay}) {
19 | const [startTime, setStartTime] = useState(Date.now())
20 | const [elapsedTime, setElapsedTime] = useState(0)
21 | const maxTimeReached = elapsedTime / 1000 > delay
22 | const remainingTime = delay - Math.trunc(elapsedTime / 1000)
23 |
24 | useEffect(() => {
25 | if (maxTimeReached) return
26 | const timer = setTimeout(() => setElapsedTime(Date.now() - startTime), 1000)
27 | return () => clearTimeout(timer)
28 | }, [elapsedTime])
29 |
30 | useEffect(() => {
31 | setStartTime(Date.now())
32 | setElapsedTime(0)
33 | }, [delay])
34 |
35 | return (
36 |
60 | Test will load on page load. For a test to pass, one or more images
61 | should show in a green box or the text "✅ test passed" should show.
62 | Note that test are delayed by a random amount of time.
63 |
64 | {!maxTimeReached ? (
65 |
Elapsed seconds: {Math.trunc(elapsedTime / 1000)}
66 | ) : (
67 | <>
68 |
Max time elapsed!
69 | All images should be loaded at this point
70 |
71 | >
72 | )}
73 |
74 |
219 | This test will load an image and then switch sources after 1 second. It
220 | should then rerender with the new source. To manually confirm, ensure
221 | the loaded image's source is the second item in the Src list
222 |