-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(@suspensive/react-image): initialize (#353)
Co-author: @tooooo1 # Overview <!-- A clear and concise description of what this pr is about. --> This package is experimental package to use img tag with React Suspense ## PR Checklist - [x] I did below actions if need 1. I read the [Contributing Guide](https://github.com/suspensive/react/blob/main/CONTRIBUTING.md) 2. I added documents and tests. --------- Co-authored-by: Chung-il Jung <[email protected]>
- Loading branch information
Showing
29 changed files
with
612 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@suspensive/react-image": patch | ||
--- | ||
|
||
feat(react-image): initialize @suspensive/react-image |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** @type {import('eslint').Linter.Config} */ | ||
module.exports = { | ||
root: true, | ||
extends: ['@suspensive/eslint-config/react-ts'], | ||
ignorePatterns: ['*.js*', 'dist', 'coverage'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# 라이브러리 소개 | ||
|
||
[![npm version](https://img.shields.io/npm/v/@suspensive/react-image?color=000&labelColor=000&logo=npm&label=)](https://www.npmjs.com/package/@suspensive/react-image) | ||
[![npm](https://img.shields.io/npm/dm/@suspensive/react-image?color=000&labelColor=000)](https://www.npmjs.com/package/@suspensive/react-image) | ||
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@suspensive/react-image?color=000&labelColor=000)](https://www.npmjs.com/package/@suspensive/react-image) | ||
|
||
## 설치하기 | ||
|
||
@suspensive/react-image 는 npm에 있습니다. 최신 안정버전을 설치하기 위해 아래 커맨드를 실행하세요 | ||
|
||
```shell npm2yarn | ||
npm install @suspensive/react-image | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Introduction | ||
|
||
[![npm version](https://img.shields.io/npm/v/@suspensive/react-image?color=000&labelColor=000&logo=npm&label=)](https://www.npmjs.com/package/@suspensive/react-image) | ||
[![npm](https://img.shields.io/npm/dm/@suspensive/react-image?color=000&labelColor=000)](https://www.npmjs.com/package/@suspensive/react-image) | ||
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@suspensive/react-image?color=000&labelColor=000)](https://www.npmjs.com/package/@suspensive/react-image) | ||
|
||
## Installation | ||
|
||
@suspensive/react-image lives in npm. To install the latest stable version, run the following command | ||
|
||
```shell npm2yarn | ||
npm install @suspensive/react-image | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
{ | ||
"name": "@suspensive/react-image", | ||
"version": "0.0.0", | ||
"description": "Useful image interfaces for React Suspense", | ||
"keywords": [ | ||
"suspensive", | ||
"react" | ||
], | ||
"homepage": "https://suspensive.org", | ||
"bugs": "https://github.com/suspensive/react/issues", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/suspensive/react.git", | ||
"directory": "packages/react-image" | ||
}, | ||
"funding": { | ||
"type": "github", | ||
"url": "https://github.com/sponsors/manudeli" | ||
}, | ||
"license": "MIT", | ||
"author": { | ||
"name": "Jonghyeon Ko", | ||
"email": "[email protected]" | ||
}, | ||
"sideEffects": false, | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"main": "dist/index.cjs", | ||
"module": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"scripts": { | ||
"build": "tsup", | ||
"build:watch": "tsup --watch", | ||
"clean": "rimraf ./dist && rimraf ./coverage", | ||
"lint": "eslint \"**/*.ts*\"", | ||
"lint:attw": "attw --pack", | ||
"lint:pub": "publint --strict", | ||
"prepack": "pnpm build", | ||
"test": "vitest run --coverage", | ||
"test:watch": "vitest --ui --coverage.enabled=true", | ||
"type:check": "tsc --noEmit" | ||
}, | ||
"dependencies": { | ||
"use-sync-external-store": "^1.2.0" | ||
}, | ||
"devDependencies": { | ||
"@suspensive/package-json-name": "workspace:*", | ||
"@types/react": "^18.2.38", | ||
"@types/react-dom": "^18.2.15", | ||
"@types/use-sync-external-store": "^0.0.6", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8 || ^17 || ^18" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type { ComponentProps } from 'react' | ||
import { forwardRef } from 'react' | ||
import { useLoad } from './Load' | ||
|
||
type ImageProps = ComponentProps<'img'> | ||
export const Image = forwardRef<HTMLImageElement, ImageProps>(function Image({ src, ...props }, ref) { | ||
if (typeof src !== 'string') { | ||
throw new Error('Image of @suspensive/react-image requires src') | ||
} | ||
const loaded = useLoad({ src }) | ||
return <img ref={ref} src={loaded.src} {...props} /> | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { describe, expect, it } from 'vitest' | ||
import { load } from './Load' | ||
|
||
describe('load', () => { | ||
it('should load image by src', async () => { | ||
const loadedImage = await load('src/assets/test.png') | ||
expect(loadedImage.src).toBe('http://localhost:5173/src/assets/test.png') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { createElement } from 'react' | ||
import type { FunctionComponent } from 'react' | ||
import { useSyncExternalStore } from 'use-sync-external-store/shim' | ||
|
||
/** | ||
* Loads an image from the given source URL. | ||
* @param src - The source URL of the image to load. | ||
* @returns A Promise that resolves to the loaded image. | ||
* @throws Will reject the promise if the image fails to load. | ||
*/ | ||
export const load = (src: HTMLImageElement['src']) => | ||
new Promise<HTMLImageElement>((resolve, reject) => { | ||
const image = new Image() | ||
image.onload = () => resolve(image) | ||
image.onerror = () => reject() | ||
image.src = src | ||
}) | ||
|
||
type LoadSrc = Parameters<typeof load>[0] | ||
|
||
type LoadState<TLoadSrc extends LoadSrc> = { | ||
src: TLoadSrc | ||
promise?: Promise<unknown> | ||
error?: unknown | ||
} | ||
|
||
type Notify = (...args: unknown[]) => unknown | ||
class LoadClient { | ||
private loadCache = new Map<LoadSrc, LoadState<LoadSrc>>() | ||
private notifiesMap = new Map<LoadSrc, Notify[]>() | ||
|
||
attach(src: LoadSrc, notify: Notify) { | ||
const notifies = this.notifiesMap.get(src) | ||
this.notifiesMap.set(src, [...(notifies ?? []), notify]) | ||
|
||
return { | ||
detach: () => this.detach(src, notify), | ||
} | ||
} | ||
|
||
detach(src: LoadSrc, notify: Notify) { | ||
const notifies = this.notifiesMap.get(src) | ||
if (notifies) { | ||
this.notifiesMap.set( | ||
src, | ||
notifies.filter((item) => item !== notify) | ||
) | ||
} | ||
} | ||
|
||
load<TLoadSrc extends LoadSrc>(src: TLoadSrc) { | ||
const loadState = this.loadCache.get(src) | ||
|
||
if (loadState?.error) { | ||
throw loadState.error | ||
} | ||
if (loadState?.src) { | ||
return loadState as LoadState<TLoadSrc> | ||
} | ||
if (loadState?.promise) { | ||
throw loadState.promise | ||
} | ||
|
||
const newLoadState: LoadState<TLoadSrc> = { | ||
src, | ||
promise: load(src) | ||
.then((image) => (newLoadState.src = image.src as TLoadSrc)) | ||
.catch(() => (newLoadState.error = `${src}: load error`)), | ||
} | ||
|
||
this.loadCache.set(src, newLoadState) | ||
throw newLoadState.promise | ||
} | ||
|
||
private notify(src: LoadSrc) { | ||
const notifies = this.notifiesMap.get(src) | ||
if (notifies) { | ||
for (const notify of notifies) { | ||
notify() | ||
} | ||
} | ||
} | ||
} | ||
|
||
const loadClient = new LoadClient() | ||
|
||
type UseLoadOptions<TLoadSrc extends LoadSrc> = { | ||
src: TLoadSrc | ||
} | ||
export const useLoad = <TLoadSrc extends LoadSrc>(options: UseLoadOptions<TLoadSrc>) => | ||
useSyncExternalStore( | ||
(onStoreChange) => loadClient.attach(options.src, onStoreChange).detach, | ||
() => loadClient.load<TLoadSrc>(options.src), | ||
() => loadClient.load<TLoadSrc>(options.src) | ||
) | ||
|
||
type LoadProps<TLoadSrc extends LoadSrc> = { | ||
src: TLoadSrc | ||
children: FunctionComponent<LoadState<TLoadSrc>> | ||
} | ||
export const Load = <TLoadSrc extends LoadSrc>({ src, children }: LoadProps<TLoadSrc>) => | ||
createElement(children, useLoad({ src })) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Image } from './Image' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "@suspensive/tsconfig/react-library.json", | ||
"include": ["."], | ||
"compilerOptions": { | ||
"types": ["node", "@testing-library/jest-dom"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { options } from '@suspensive/tsup' | ||
import { defineConfig } from 'tsup' | ||
|
||
export default defineConfig(options) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import packageJsonName from '@suspensive/package-json-name' | ||
import { defineConfig } from 'vitest/config' | ||
|
||
export default defineConfig({ | ||
test: { | ||
name: packageJsonName(), | ||
dir: './src', | ||
environment: 'jsdom', | ||
globals: true, | ||
coverage: { | ||
provider: 'istanbul', | ||
}, | ||
browser: { | ||
enabled: true, | ||
headless: true, | ||
provider: 'playwright', | ||
name: 'chromium', | ||
}, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.