diff --git a/.changeset/shiny-signs-agree.md b/.changeset/shiny-signs-agree.md new file mode 100644 index 0000000..900fad7 --- /dev/null +++ b/.changeset/shiny-signs-agree.md @@ -0,0 +1,8 @@ +--- +"react-native-youtube-bridge": patch +"@react-native-youtube-bridge/react": patch +"@react-native-youtube-bridge/core": patch +"@react-native-youtube-bridge/web": patch +--- + +chore: reformat code with oxfmt diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 6e42fec..1588cfd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -36,7 +36,7 @@ body: attributes: label: Library version description: What version of the library are you using? - placeholder: "x.x.x" + placeholder: 'x.x.x' validations: required: true - type: textarea @@ -61,7 +61,7 @@ body: - type: input id: reproducible-example attributes: - label: Reproducible example repository + label: Reproducible example repository description: Please provide a link to a repository on GitHub with a reproducible example. validations: required: true diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index e587b8c..9a0be73 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -13,10 +13,10 @@ runs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 10.11.0 + version: 10.29.3 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' cache: 'pnpm' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fce704..be56192 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,13 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uses: ./.github/actions/setup - name: Lint files - run: pnpm run lint + run: pnpm run lint:ci + + - name: Format check + run: pnpm run format:check - name: Build dependencies first run: | @@ -35,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uses: ./.github/actions/setup @@ -47,7 +50,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uses: ./.github/actions/setup diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4613fe1..e1b64f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,10 +25,10 @@ jobs: id: changesets uses: changesets/action@v1 with: - commit: "chore(release): version packages" - title: "chore(release): version packages" - version: "pnpm changeset version" - publish: "pnpm changeset publish" + commit: 'chore(release): version packages' + title: 'chore(release): version packages' + version: 'pnpm changeset version' + publish: 'pnpm changeset publish' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 7b6c9db..35865a5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,6 @@ # XDE .expo/ -# VSCode -.vscode/ -jsconfig.json - # Xcode # build/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..36e6dbe --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,19 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": ["lib", "node_modules", "ios", "android", "dist", "**/*.md"], + "experimentalSortImports": { + "groups": [ + ["side-effect"], + ["builtin"], + ["external", "type-external"], + ["internal", "type-internal"], + ["parent", "type-parent"], + ["sibling", "type-sibling"], + ["index", "type-index"] + ] + }, + "experimentalSortPackageJson": { + "sortScripts": true + }, + "singleQuote": true +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..38453e4 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,60 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "eslint", "import", "unicorn"], + "categories": { + "correctness": "error", + "perf": "error", + "nursery": "error", + "suspicious": "error" + }, + "rules": { + "react/react-in-jsx-scope": "off", + "import/no-unassigned-import": ["error", { "allow": ["**/*.css"] }], + "unicorn/require-post-message-target-origin": "off" + }, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {}, + "attributes": {} + }, + "react": { + "formComponents": [], + "linkComponents": [], + "version": null, + "componentWrapperFunctions": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "vitest": { + "typecheck": false + } + }, + "overrides": [ + { + "files": ["packages/**/src/__tests__/*.test.tsx"], + "plugins": ["jest"], + "env": { + "jest": true + } + } + ], + "env": { + "builtin": true, + "node": true, + "es6": true, + "browser": true + }, + "globals": { + "__DEV__": "readonly" + }, + "ignorePatterns": ["lib", "node_modules", "ios", "android", "dist"] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..99e2f7d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["oxc.oxc-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2aae66a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "always" + } +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 45d257b..8b4fcfd 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or advances of +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb46d02..3e08753 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,9 +88,9 @@ Our pre-commit hooks verify that your commit message matches this format when co ### Linting and tests -[biomejs](https://biomejs.dev/), [TypeScript](https://www.typescriptlang.org/) +[oxlint](https://oxc.rs/docs/guide/usage/linter.html), [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), [TypeScript](https://www.typescriptlang.org/) -We use [TypeScript](https://www.typescriptlang.org/) for type checking, [biomejs](https://biomejs.dev/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. +We use [TypeScript](https://www.typescriptlang.org/) for type checking, [oxlint](https://oxc.rs/docs/guide/usage/linter.html) for linting, [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) for formatting the code, and [Jest](https://jestjs.io/) for testing. Our pre-commit hooks verify that the linter and tests pass when committing. diff --git a/README-ko_kr.md b/README-ko_kr.md index e16e4ce..edecdf1 100644 --- a/README-ko_kr.md +++ b/README-ko_kr.md @@ -6,10 +6,11 @@ > **V1 사용자:** [V1 문서](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 마이그레이션 가이드](/packages/react-native-youtube-bridge/docs/migration-v2.md) ## 개요 -React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 필요합니다. -하지만 현재 지속적으로 유지보수되고 있는 React Native용 YouTube 플레이어 라이브러리가 없는 상황입니다. (가장 인기 있는 react-native-youtube-iframe의 [최근 릴리즈는 2023년 07월 02일](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) -`react-native-youtube-bridge`는 [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference)를 React Native에서 쉽게 사용할 수 있도록 도와주는 라이브러리입니다. +React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 필요합니다. +하지만 현재 지속적으로 유지보수되고 있는 React Native용 YouTube 플레이어 라이브러리가 없는 상황입니다. (가장 인기 있는 react-native-youtube-iframe의 [최근 릴리즈는 2023년 07월 02일](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge`는 [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference)를 React Native에서 쉽게 사용할 수 있도록 도와주는 라이브러리입니다. - ✅ TypeScript 지원 - ✅ iOS, Android, Web 플랫폼 지원 @@ -22,6 +23,7 @@ React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 - ✅ 유연한 렌더링 모드 (인라인 HTML & 웹뷰) ## 예제 + > 빠른 시작을 원하신다면 [예제](/example/)를 확인해보세요. - [웹 데모](https://react-native-youtube-bridge-example.pages.dev/) @@ -49,20 +51,19 @@ bun add react-native-youtube-bridge import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { - const videoIdOrUrl = 'AbZH7XWDW_k' + const videoIdOrUrl = 'AbZH7XWDW_k'; // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) const player = useYouTubePlayer(videoIdOrUrl); - return ( - - ); + return ; } ``` ### 이벤트 -YouTube iframe API의 상태 변화를 애플리케이션에 전달하기 위해 [이벤트](https://developers.google.com/youtube/iframe_api_reference#Events)를 발생시킵니다. + +YouTube iframe API의 상태 변화를 애플리케이션에 전달하기 위해 [이벤트](https://developers.google.com/youtube/iframe_api_reference#Events)를 발생시킵니다. `useYouTubeEvent` hook을 사용하여 완벽한 타입 추론을 지원하며, 두 가지 방법으로 이벤트를 쉽게 감지하여 사용할 수 있습니다. @@ -89,20 +90,20 @@ function App() { Alert.alert('Error', `Player error (${error.code}): ${error.message}`); }); - return ( - - ); + return ; } ``` `useYouTubeEvent` hook은 callback으로 값을 전달받는 방식과 state로 값을 바로 사용할 수 있는 두 가지 방법을 제공합니다. + 1. Callback 방식: 의존성에 따라 리렌더링이 필요한 경우 4번째 인자에 dependency array를 주입해주면 됩니다. 2. State 방식: 1. `progress` event의 경우 3번째 인자에 interval 값을 설정할 수 있습니다. (기본값: 1000ms) 2. 나머지 event의 경우 3번째 인자에 기본 값을 설정할 수 있습니다. ### 기능 -YouTube iframe API의 [함수들](https://developers.google.com/youtube/iframe_api_reference#Functions)을 `useYouTubePlayer`를 통해 반환된 player 인스턴스 메서드를 호출하여 음소거, 재생, 볼륨 조절 등 다양한 플레이어 기능을 제어할 수 있습니다. + +YouTube iframe API의 [함수들](https://developers.google.com/youtube/iframe_api_reference#Functions)을 `useYouTubePlayer`를 통해 반환된 player 인스턴스 메서드를 호출하여 음소거, 재생, 볼륨 조절 등 다양한 플레이어 기능을 제어할 수 있습니다. ```tsx import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; @@ -156,11 +157,12 @@ function App() { - ) + ); } ``` ### 초기 플레이어 매개변수 + YouTube 내장 플레이어의 [매개변수](https://developers.google.com/youtube/player_parameters#Parameters)를 설정하여 초기 재생 환경을 맞춤화할 수 있습니다. ```tsx @@ -175,13 +177,12 @@ function App() { muted: true, }); - return ( - - ); + return ; } ``` ### 스타일 + YouTube 플레이어의 스타일을 원하는 대로 커스터마이징할 수 있습니다. ```tsx @@ -199,19 +200,24 @@ function App() { aspectRatio: 16 / 9, }} // iOS, Android 플랫폼 지원 - webViewStyle={{ - // ... - }} + webViewStyle={ + { + // ... + } + } // iOS, Android 플랫폼 지원 - webViewProps={{ - // ... - }} + webViewProps={ + { + // ... + } + } /> - ) + ); } ``` ### 재생 진행률 추적 + - `useYouTubeEvent` hook을 사용하여 `progress` 이벤트의 리스너를 등록하여 재생 진행률을 추적할 수 있습니다. - 세 번째 인자에 interval 값을 설정하여 해당 간격(ms)마다 이벤트가 호출됩니다. - interval을 원치 않으면 `0`으로 설정하면 됩니다. @@ -224,15 +230,13 @@ function App() { const player = useYouTubePlayer(videoIdOrUrl); const progress = useYouTubeEvent(player, 'progress', progressInterval); - return ( - - ) + return ; } ``` ### 플레이어 렌더링 및 소스 설정 (ios, android) -**인라인 HTML vs 웹뷰 모드** +**인라인 HTML vs 웹뷰 모드** YouTube 플레이어 렌더링 방식을 제어하고 호환성을 위한 소스 URL을 설정합니다. 1. **인라인 HTML 모드** (`useInlineHtml: true`)는 앱 내에서 직접 HTML을 로드하여 플레이어를 렌더링합니다. (default) @@ -242,6 +246,7 @@ YouTube 플레이어 렌더링 방식을 제어하고 호환성을 위한 소스 > [!NOTE] > **webViewUrl 활용법** +> > - `useInlineHtml: true`인 경우: WebView source의 HTML `baseUrl`로 설정됩니다. > - `useInlineHtml: false`인 경우: WebView source의 `uri`를 override합니다. > @@ -280,14 +285,17 @@ export default CustomPlayerPage; > 자세한 내용은 [웹 플레이어 가이드](./packages/web/)를 참고해 주세요. ### YouTube oEmbed API -`useYoutubeOEmbed` 훅을 통해 YouTube 비디오의 메타데이터를 가져올 수 있습니다. + +`useYoutubeOEmbed` 훅을 통해 YouTube 비디오의 메타데이터를 가져올 수 있습니다. 이 훅은 YouTube URL만 지원합니다. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + 'https://www.youtube.com/watch?v=AbZH7XWDW_k', + ); if (isLoading) return Loading...; if (error) return Error: {error.message}; @@ -296,12 +304,12 @@ function App() { return ( <> {oEmbed.title} - - ) + ); } ``` diff --git a/README.md b/README.md index 2186645..dde7684 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ > **V1 users:** [V1 Documentation](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 Migration Guide](/packages/react-native-youtube-bridge/docs/migration-v2.md) ## Overview -Using a YouTube player in React Native requires complex setup and configuration. -However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) -`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. +Using a YouTube player in React Native requires complex setup and configuration. +However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. - ✅ TypeScript support - ✅ iOS, Android, and Web platform support @@ -50,21 +51,19 @@ bun add react-native-youtube-bridge import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { - const videoIdOrUrl = 'AbZH7XWDW_k' + const videoIdOrUrl = 'AbZH7XWDW_k'; // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) const player = useYouTubePlayer(videoIdOrUrl); - return ( - - ); + return ; } ``` ### Events -[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. +[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. The `useYouTubeEvent` hook provides complete type inference and allows you to easily detect and use events in two ways. @@ -91,9 +90,7 @@ function App() { Alert.alert('Error', `Player error (${error.code}): ${error.message}`); }); - return ( - - ); + return ; } ``` @@ -106,7 +103,7 @@ The `useYouTubeEvent` hook provides two ways to receive values: callback-based a ### Features -You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). +You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). ```tsx import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; @@ -160,7 +157,7 @@ function App() { - ) + ); } ``` @@ -180,9 +177,7 @@ function App() { muted: true, }); - return ( - - ); + return ; } ``` @@ -205,15 +200,19 @@ function App() { aspectRatio: 16 / 9, }} // iOS, Android platform support - webViewStyle={{ - // ... - }} + webViewStyle={ + { + // ... + } + } // iOS, Android platform support - webViewProps={{ - // ... - }} + webViewProps={ + { + // ... + } + } /> - ) + ); } ``` @@ -231,24 +230,23 @@ function App() { const player = useYouTubePlayer(videoIdOrUrl); const progress = useYouTubeEvent(player, 'progress', progressInterval); - return ( - - ) + return ; } ``` ### Player Rendering and Source Configuration (iOS, Android) -**Inline HTML vs WebView Mode** +**Inline HTML vs WebView Mode** Control the YouTube player rendering method and set source URLs for compatibility. 1. **Inline HTML mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) -2. **WebView mode** (`useInlineHtml: false`) loads an external player page. +2. **WebView mode** (`useInlineHtml: false`) loads an external player page. - The default URI is https://react-native-youtube-bridge.pages.dev. - To use your own custom player page as an external WebView, build your player with `@react-native-youtube-bridge/web` and set the URL in the `webViewUrl` property. For detailed implementation instructions, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). > [!NOTE] > **webViewUrl Usage** +> > - When `useInlineHtml: true`: Set as the HTML `baseUrl` of the WebView source. > - When `useInlineHtml: false`: Overrides the WebView source's `uri`. > @@ -288,14 +286,16 @@ export default CustomPlayerPage; ### YouTube oEmbed API -You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. +You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. This hook only supports YouTube URLs. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + 'https://www.youtube.com/watch?v=AbZH7XWDW_k', + ); if (isLoading) return Loading...; if (error) return Error: {error.message}; @@ -304,12 +304,12 @@ function App() { return ( <> {oEmbed.title} - - ) + ); } ``` diff --git a/biome.json b/biome.json deleted file mode 100644 index 663aa84..0000000 --- a/biome.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "include": ["src/**/*", "example/src/**/*", "packages/**/*"], - "ignoreUnknown": false, - "ignore": ["lib", "node_modules", "ios", "android", "dist"] - }, - "formatter": { - "enabled": true, - "indentStyle": "space" - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "warn" - } - } - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "lineWidth": 120 - } - } -} diff --git a/example/package.json b/example/package.json index af6f376..e5a2dec 100644 --- a/example/package.json +++ b/example/package.json @@ -1,11 +1,12 @@ { "name": "example", "version": "1.1.4", + "private": true, "main": "index.js", "scripts": { - "start": "expo start --clear", "android": "expo run:android", "ios": "expo run:ios", + "start": "expo start --clear", "web": "expo start --web --clear", "web:build": "expo export --platform web --clear" }, @@ -22,6 +23,5 @@ }, "devDependencies": { "@babel/core": "^7.20.0" - }, - "private": true + } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 6821781..6f13588 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,5 +1,14 @@ import { useCallback, useEffect, useState } from 'react'; -import { Alert, Platform, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { + Alert, + Platform, + SafeAreaView, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; import { PlayerState, YoutubeView, @@ -21,7 +30,9 @@ function App() { const [isMuted, setIsMuted] = useState(false); const [videoId, setVideoId] = useState('AbZH7XWDW_k'); const [progressInterval, setProgressInterval] = useState(1000); - const { oEmbed, isLoading, error } = useYoutubeOEmbed(`https://www.youtube.com/watch?v=${videoId}`); + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + `https://www.youtube.com/watch?v=${videoId}`, + ); const player = useYouTubePlayer(videoId, { autoplay: true, @@ -201,7 +212,10 @@ function App() { setProgressInterval(progressInterval === 0 ? 1000 : 0)} > {progressInterval}ms interval @@ -220,7 +234,10 @@ function App() { {isPlaying ? '⏸️ Pause' : '▶️ Play'} - player.stop()}> + player.stop()} + > ⏹️ Stop @@ -235,7 +252,10 @@ function App() { Volume control - + {isMuted ? '🔇 Muted' : '🔊 Unmuted'} diff --git a/lefthook.yml b/lefthook.yml index 6e04ff6..d711908 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,13 +2,16 @@ pre-commit: parallel: true commands: lint: - glob: "*.{js,ts,jsx,tsx}" - run: npx biome format --write {staged_files} + glob: '**/*.{js,ts,jsx,tsx}' + run: pnpm oxlint {staged_files} + format: + glob: '**/*.{js,ts,jsx,tsx}' + run: pnpm oxfmt {staged_files} types: - glob: "*.{js,ts,jsx,tsx}" + glob: '**/*.{js,ts,jsx,tsx}' run: pnpm run typecheck commit-msg: parallel: true commands: commitlint: - run: npx commitlint --edit + run: pnpm commitlint --edit diff --git a/package.json b/package.json index 87f5ddf..5a5f57c 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,41 @@ { "name": "@react-native-youtube-bridge/root", - "description": "🎥 Easy-to-use YouTube player for React Native with cross-platform support", "private": true, - "scripts": { - "example": "pnpm --filter example run", - "test": "jest", - "clear": "rimraf --glob **/dist", - "typecheck": "pnpm -r run typecheck", - "build": "pnpm -r run build", - "lint": "biome format packages/** --write", - "changeset": "changeset" - }, + "description": "🎥 Easy-to-use YouTube player for React Native with cross-platform support", "keywords": [ - "react-native", - "ios", "android", - "youtube", + "ios", + "react-native", "react-native-youtube", "react-native-youtube-bridge", - "youtube-iframe-api", - "react-native-youtube-iframe" + "react-native-youtube-iframe", + "youtube", + "youtube-iframe-api" ], + "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", + "bugs": { + "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + }, + "license": "MIT", + "author": "saseungmin (https://github.com/saseungmin)", "repository": { "type": "git", "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" }, - "author": "saseungmin (https://github.com/saseungmin)", - "license": "MIT", - "bugs": { - "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + "scripts": { + "build": "pnpm -r run build", + "changeset": "changeset", + "clear": "rimraf --glob **/dist", + "example": "pnpm --filter example run", + "format": "oxfmt", + "format:check": "oxfmt --check", + "lint": "oxlint", + "lint:ci": "oxlint --format=github", + "lint:fix": "oxlint --fix", + "test": "jest", + "typecheck": "pnpm -r run typecheck" }, - "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", "devDependencies": { - "@biomejs/biome": "1.9.4", "@changesets/cli": "^2.29.4", "@commitlint/config-conventional": "^19.6.0", "@evilmartians/lefthook": "^1.5.0", @@ -40,15 +43,20 @@ "@types/node": "^22.0.0", "commitlint": "^19.6.1", "jest": "^29.7.0", + "oxfmt": "^0.32.0", + "oxlint": "^1.47.0", "typescript": "^5.8.3" }, - "packageManager": "pnpm@10.11.0", "commitlint": { "extends": [ "@commitlint/config-conventional" ] }, "jest": { - "modulePathIgnorePatterns": ["lib", "node_modules"] - } + "modulePathIgnorePatterns": [ + "lib", + "node_modules" + ] + }, + "packageManager": "pnpm@10.29.3" } diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e719f69..7ddc8d3 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -79,7 +79,7 @@ ```jsx // Imperative, ref-based API - const playerRef = useRef < PlayerControls > null; + const playerRef = useRef(null); (https://github.com/saseungmin)", + "repository": { + "type": "git", + "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" + }, + "files": [ + "dist", + "package.json" + ], "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -12,38 +36,14 @@ "require": "./dist/index.js" } }, - "files": [ - "dist", - "package.json" - ], + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsdown", "dev": "tsdown --watch ./src", "typecheck": "tsc --noEmit" }, - "keywords": [ - "react-native", - "ios", - "android", - "youtube", - "react-native-youtube", - "react-native-youtube-bridge", - "youtube-iframe-api", - "react-native-youtube-iframe" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" - }, - "author": "saseungmin (https://github.com/saseungmin)", - "license": "MIT", - "bugs": { - "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" - }, - "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", - "publishConfig": { - "access": "public" - }, "devDependencies": { "tsdown": "^0.12.8" } diff --git a/packages/core/src/types/iframe.ts b/packages/core/src/types/iframe.ts index aa79091..3a0066c 100644 --- a/packages/core/src/types/iframe.ts +++ b/packages/core/src/types/iframe.ts @@ -1,6 +1,7 @@ -import type { PlaybackQuality, PlayerInfo, PlayerState } from '.'; import type { ERROR_CODES } from '../constants'; +import type { PlaybackQuality, PlayerInfo, PlayerState } from '.'; + export interface IframeApiType { Player: { new (elementId: string, options: Options): YouTubePlayer }; } @@ -119,14 +120,22 @@ export interface YouTubePlayer { endSeconds?: number | undefined; suggestedQuality?: string | undefined; }): Promise; - cueVideoByUrl(mediaContentUrl: string, startSeconds?: number, suggestedQuality?: string): Promise; + cueVideoByUrl( + mediaContentUrl: string, + startSeconds?: number, + suggestedQuality?: string, + ): Promise; cueVideoByUrl(video: { mediaContentUrl: string; startSeconds?: number | undefined; endSeconds?: number | undefined; suggestedQuality?: string | undefined; }): Promise; - loadVideoByUrl(mediaContentUrl: string, startSeconds?: number, suggestedQuality?: string): Promise; + loadVideoByUrl( + mediaContentUrl: string, + startSeconds?: number, + suggestedQuality?: string, + ): Promise; loadVideoByUrl(video: { mediaContentUrl: string; startSeconds?: number | undefined; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index e04767f..94b6a62 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -12,7 +12,11 @@ type YoutubeErrorMessage = (typeof ERROR_CODES)[YoutubeErrorCode]; * const player = useYouTubePlayer({ url: 'https://www.youtube.com/watch?v=AbZH7XWDW_k' }); * ``` */ -export type YoutubeSource = string | { videoId: string | undefined } | { url: string | undefined } | undefined; +export type YoutubeSource = + | string + | { videoId: string | undefined } + | { url: string | undefined } + | undefined; export type ProgressData = { /** diff --git a/packages/react-native-youtube-bridge/CHANGELOG.md b/packages/react-native-youtube-bridge/CHANGELOG.md index c9fec47..c40a617 100644 --- a/packages/react-native-youtube-bridge/CHANGELOG.md +++ b/packages/react-native-youtube-bridge/CHANGELOG.md @@ -121,7 +121,7 @@ ```jsx // Imperative, ref-based API - const playerRef = useRef < PlayerControls > null; + const playerRef = useRef(null); **V1 사용자:** [V1 문서](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 마이그레이션 가이드](/packages/react-native-youtube-bridge/docs/migration-v2.md) ## 개요 -React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 필요합니다. -하지만 현재 지속적으로 유지보수되고 있는 React Native용 YouTube 플레이어 라이브러리가 없는 상황입니다. (가장 인기 있는 react-native-youtube-iframe의 [최근 릴리즈는 2023년 07월 02일](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) -`react-native-youtube-bridge`는 [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference)를 React Native에서 쉽게 사용할 수 있도록 도와주는 라이브러리입니다. +React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 필요합니다. +하지만 현재 지속적으로 유지보수되고 있는 React Native용 YouTube 플레이어 라이브러리가 없는 상황입니다. (가장 인기 있는 react-native-youtube-iframe의 [최근 릴리즈는 2023년 07월 02일](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge`는 [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference)를 React Native에서 쉽게 사용할 수 있도록 도와주는 라이브러리입니다. - ✅ TypeScript 지원 - ✅ iOS, Android, Web 플랫폼 지원 @@ -22,6 +23,7 @@ React Native에서 YouTube 플레이어를 사용하려면 복잡한 설정이 - ✅ 유연한 렌더링 모드 (인라인 HTML & 웹뷰) ## 예제 + > 빠른 시작을 원하신다면 [예제](/example/)를 확인해보세요. - [웹 데모](https://react-native-youtube-bridge-example.pages.dev/) @@ -49,20 +51,19 @@ bun add react-native-youtube-bridge import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { - const videoIdOrUrl = 'AbZH7XWDW_k' + const videoIdOrUrl = 'AbZH7XWDW_k'; // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) const player = useYouTubePlayer(videoIdOrUrl); - return ( - - ); + return ; } ``` ### 이벤트 -YouTube iframe API의 상태 변화를 애플리케이션에 전달하기 위해 [이벤트](https://developers.google.com/youtube/iframe_api_reference#Events)를 발생시킵니다. + +YouTube iframe API의 상태 변화를 애플리케이션에 전달하기 위해 [이벤트](https://developers.google.com/youtube/iframe_api_reference#Events)를 발생시킵니다. `useYouTubeEvent` hook을 사용하여 완벽한 타입 추론을 지원하며, 두 가지 방법으로 이벤트를 쉽게 감지하여 사용할 수 있습니다. @@ -89,20 +90,20 @@ function App() { Alert.alert('Error', `Player error (${error.code}): ${error.message}`); }); - return ( - - ); + return ; } ``` `useYouTubeEvent` hook은 callback으로 값을 전달받는 방식과 state로 값을 바로 사용할 수 있는 두 가지 방법을 제공합니다. + 1. Callback 방식: 의존성에 따라 리렌더링이 필요한 경우 4번째 인자에 dependency array를 주입해주면 됩니다. 2. State 방식: 1. `progress` event의 경우 3번째 인자에 interval 값을 설정할 수 있습니다. (기본값: 1000ms) 2. 나머지 event의 경우 3번째 인자에 기본 값을 설정할 수 있습니다. ### 기능 -YouTube iframe API의 [함수들](https://developers.google.com/youtube/iframe_api_reference#Functions)을 `useYouTubePlayer`를 통해 반환된 player 인스턴스 메서드를 호출하여 음소거, 재생, 볼륨 조절 등 다양한 플레이어 기능을 제어할 수 있습니다. + +YouTube iframe API의 [함수들](https://developers.google.com/youtube/iframe_api_reference#Functions)을 `useYouTubePlayer`를 통해 반환된 player 인스턴스 메서드를 호출하여 음소거, 재생, 볼륨 조절 등 다양한 플레이어 기능을 제어할 수 있습니다. ```tsx import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; @@ -156,11 +157,12 @@ function App() { - ) + ); } ``` ### 초기 플레이어 매개변수 + YouTube 내장 플레이어의 [매개변수](https://developers.google.com/youtube/player_parameters#Parameters)를 설정하여 초기 재생 환경을 맞춤화할 수 있습니다. ```tsx @@ -175,13 +177,12 @@ function App() { muted: true, }); - return ( - - ); + return ; } ``` ### 스타일 + YouTube 플레이어의 스타일을 원하는 대로 커스터마이징할 수 있습니다. ```tsx @@ -199,19 +200,24 @@ function App() { aspectRatio: 16 / 9, }} // iOS, Android 플랫폼 지원 - webViewStyle={{ - // ... - }} + webViewStyle={ + { + // ... + } + } // iOS, Android 플랫폼 지원 - webViewProps={{ - // ... - }} + webViewProps={ + { + // ... + } + } /> - ) + ); } ``` ### 재생 진행률 추적 + - `useYouTubeEvent` hook을 사용하여 `progress` 이벤트의 리스너를 등록하여 재생 진행률을 추적할 수 있습니다. - 세 번째 인자에 interval 값을 설정하여 해당 간격(ms)마다 이벤트가 호출됩니다. - interval을 원치 않으면 `0`으로 설정하면 됩니다. @@ -224,15 +230,13 @@ function App() { const player = useYouTubePlayer(videoIdOrUrl); const progress = useYouTubeEvent(player, 'progress', progressInterval); - return ( - - ) + return ; } ``` ### 플레이어 렌더링 및 소스 설정 (ios, android) -**인라인 HTML vs 웹뷰 모드** +**인라인 HTML vs 웹뷰 모드** YouTube 플레이어 렌더링 방식을 제어하고 호환성을 위한 소스 URL을 설정합니다. 1. **인라인 HTML 모드** (`useInlineHtml: true`)는 앱 내에서 직접 HTML을 로드하여 플레이어를 렌더링합니다. (default) @@ -242,6 +246,7 @@ YouTube 플레이어 렌더링 방식을 제어하고 호환성을 위한 소스 > [!NOTE] > **webViewUrl 활용법** +> > - `useInlineHtml: true`인 경우: WebView source의 HTML `baseUrl`로 설정됩니다. > - `useInlineHtml: false`인 경우: WebView source의 `uri`를 override합니다. > @@ -280,14 +285,17 @@ export default CustomPlayerPage; > 자세한 내용은 [웹 플레이어 가이드](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web)를 참고해 주세요. ### YouTube oEmbed API -`useYoutubeOEmbed` 훅을 통해 YouTube 비디오의 메타데이터를 가져올 수 있습니다. + +`useYoutubeOEmbed` 훅을 통해 YouTube 비디오의 메타데이터를 가져올 수 있습니다. 이 훅은 YouTube URL만 지원합니다. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + 'https://www.youtube.com/watch?v=AbZH7XWDW_k', + ); if (isLoading) return Loading...; if (error) return Error: {error.message}; @@ -296,12 +304,12 @@ function App() { return ( <> {oEmbed.title} - - ) + ); } ``` diff --git a/packages/react-native-youtube-bridge/README.md b/packages/react-native-youtube-bridge/README.md index 652214a..b15ddc7 100644 --- a/packages/react-native-youtube-bridge/README.md +++ b/packages/react-native-youtube-bridge/README.md @@ -6,10 +6,11 @@ > **V1 users:** [V1 Documentation](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 Migration Guide](/packages/react-native-youtube-bridge/docs/migration-v2.md) ## Overview -Using a YouTube player in React Native requires complex setup and configuration. -However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) -`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. +Using a YouTube player in React Native requires complex setup and configuration. +However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. - ✅ TypeScript support - ✅ iOS, Android, and Web platform support @@ -50,21 +51,19 @@ bun add react-native-youtube-bridge import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { - const videoIdOrUrl = 'AbZH7XWDW_k' + const videoIdOrUrl = 'AbZH7XWDW_k'; // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) const player = useYouTubePlayer(videoIdOrUrl); - return ( - - ); + return ; } ``` ### Events -[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. +[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. The `useYouTubeEvent` hook provides complete type inference and allows you to easily detect and use events in two ways. @@ -91,9 +90,7 @@ function App() { Alert.alert('Error', `Player error (${error.code}): ${error.message}`); }); - return ( - - ); + return ; } ``` @@ -106,7 +103,7 @@ The `useYouTubeEvent` hook provides two ways to receive values: callback-based a ### Features -You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). +You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). ```tsx import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; @@ -160,7 +157,7 @@ function App() { - ) + ); } ``` @@ -180,9 +177,7 @@ function App() { muted: true, }); - return ( - - ); + return ; } ``` @@ -205,15 +200,19 @@ function App() { aspectRatio: 16 / 9, }} // iOS, Android platform support - webViewStyle={{ - // ... - }} + webViewStyle={ + { + // ... + } + } // iOS, Android platform support - webViewProps={{ - // ... - }} + webViewProps={ + { + // ... + } + } /> - ) + ); } ``` @@ -231,24 +230,23 @@ function App() { const player = useYouTubePlayer(videoIdOrUrl); const progress = useYouTubeEvent(player, 'progress', progressInterval); - return ( - - ) + return ; } ``` ### Player Rendering and Source Configuration (iOS, Android) -**Inline HTML vs WebView Mode** +**Inline HTML vs WebView Mode** Control the YouTube player rendering method and set source URLs for compatibility. 1. **Inline HTML mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) -2. **WebView mode** (`useInlineHtml: false`) loads an external player page. +2. **WebView mode** (`useInlineHtml: false`) loads an external player page. - The default URI is https://react-native-youtube-bridge.pages.dev. - To use your own custom player page as an external WebView, build your player with `@react-native-youtube-bridge/web` and set the URL in the `webViewUrl` property. For detailed implementation instructions, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). > [!NOTE] > **webViewUrl Usage** +> > - When `useInlineHtml: true`: Set as the HTML `baseUrl` of the WebView source. > - When `useInlineHtml: false`: Overrides the WebView source's `uri`. > @@ -288,14 +286,16 @@ export default CustomPlayerPage; ### YouTube oEmbed API -You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. +You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. This hook only supports YouTube URLs. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + 'https://www.youtube.com/watch?v=AbZH7XWDW_k', + ); if (isLoading) return Loading...; if (error) return Error: {error.message}; @@ -304,12 +304,12 @@ function App() { return ( <> {oEmbed.title} - - ) + ); } ``` diff --git a/packages/react-native-youtube-bridge/docs/migration-v2.md b/packages/react-native-youtube-bridge/docs/migration-v2.md index e69453a..f8b88dd 100644 --- a/packages/react-native-youtube-bridge/docs/migration-v2.md +++ b/packages/react-native-youtube-bridge/docs/migration-v2.md @@ -4,20 +4,21 @@ This guide will help you migrate from react-native-youtube-bridge v1 to v2. The ## Overview of Changes -| Aspect | v1 (Old) | v2 (New) | -|--------|----------|----------| -| **API Style** | Imperative (ref-based) | Declarative (hooks-based) | -| **Component** | `YoutubePlayer` | `YoutubeView` + `useYouTubePlayer` | -| **Event Handling** | Manual listeners + props | `useYouTubeEvent` hook | -| **State Management** | Manual `useState` | Automatic reactive updates | -| **Player Control** | `playerRef.current.method()` | `player.method()` | -| **Configuration** | Component props | Hook parameters | +| Aspect | v1 (Old) | v2 (New) | +| -------------------- | ---------------------------- | ---------------------------------- | +| **API Style** | Imperative (ref-based) | Declarative (hooks-based) | +| **Component** | `YoutubePlayer` | `YoutubeView` + `useYouTubePlayer` | +| **Event Handling** | Manual listeners + props | `useYouTubeEvent` hook | +| **State Management** | Manual `useState` | Automatic reactive updates | +| **Player Control** | `playerRef.current.method()` | `player.method()` | +| **Configuration** | Component props | Hook parameters | ## Step-by-Step Migration ### 1. Component Replacement **Before (v1):** + ```jsx import { YoutubePlayer } from 'react-native-youtube-bridge'; @@ -36,10 +37,11 @@ import { YoutubePlayer } from 'react-native-youtube-bridge'; onStateChange={handleStateChange} onProgress={handleProgress} onError={handleError} -/> +/>; ``` **After (v2):** + ```jsx import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; @@ -51,10 +53,7 @@ const player = useYouTubePlayer(videoId, { muted: true, }); - +; ``` ### 2. Event Handling Migration @@ -62,6 +61,7 @@ const player = useYouTubePlayer(videoId, { The `useYouTubeEvent` hook provides complete type inference and allows you to handle events in two ways: **callback-based** and **state-based**. **Before (v1):** + ```jsx // Manual event handlers and state management const [isPlaying, setIsPlaying] = useState(false); @@ -92,6 +92,7 @@ const handlePlaybackRateChange = useCallback((rate) => { ``` **After (v2):** + ```jsx // State-based event handling (reactive values) const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); @@ -125,31 +126,41 @@ useYouTubeEvent(player, 'error', (error) => { #### `useYouTubeEvent` Usage Patterns: 1. **State method** - Returns reactive values: + ```jsx // For progress events with custom interval const progress = useYouTubeEvent(player, 'progress', 1000); // 1000ms interval - + // For other events with default value const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); const state = useYouTubeEvent(player, 'stateChange'); ``` 2. **Callback method** - For side effects and actions: + ```jsx // Simple callback useYouTubeEvent(player, 'ready', (playerInfo) => { console.log('Player ready:', playerInfo); }); - + // With dependency array for re-rendering control - useYouTubeEvent(player, 'stateChange', (state) => { - // Handle state change - }, [/* dependencies */]); + useYouTubeEvent( + player, + 'stateChange', + (state) => { + // Handle state change + }, + [ + /* dependencies */ + ], + ); ``` ### 3. Player Control Migration **Before (v1):** + ```jsx const playerRef = useRef(null); @@ -171,6 +182,7 @@ const getPlayerInfo = async () => { ``` **After (v2):** + ```jsx const player = useYouTubePlayer(videoId, config); @@ -228,4 +240,5 @@ The v2 API is designed to be more intuitive and reduce boilerplate code while pr ## Previous Versions ### [1.x.x] - Legacy Version + See [v1 documentation](./v1.md) for the previous imperative API. diff --git a/packages/react-native-youtube-bridge/docs/v1.md b/packages/react-native-youtube-bridge/docs/v1.md index 89b224d..b562988 100644 --- a/packages/react-native-youtube-bridge/docs/v1.md +++ b/packages/react-native-youtube-bridge/docs/v1.md @@ -1,10 +1,11 @@ # React Native Youtube Bridge (Version 1) ## Overview -Using a YouTube player in React Native requires complex setup and configuration. -However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) -`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. +Using a YouTube player in React Native requires complex setup and configuration. +However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. - ✅ TypeScript support - ✅ iOS, Android, and Web platform support @@ -16,6 +17,7 @@ However, there are currently no actively maintained YouTube player libraries for - ✅ Flexible rendering modes (Inline HTML & WebView) ## Example + > For a quick start, check out the [example](/example/). - [Web Demo](https://react-native-youtube-bridge-example.pages.dev/) @@ -44,17 +46,18 @@ import { YoutubePlayer } from 'react-native-youtube-bridge'; function App() { return ( - - ) + ); } ``` ### Events -The library fires [events](https://developers.google.com/youtube/iframe_api_reference#Events) to notify your application of YouTube iframe API state changes. You can subscribe to these events using callback functions. + +The library fires [events](https://developers.google.com/youtube/iframe_api_reference#Events) to notify your application of YouTube iframe API state changes. You can subscribe to these events using callback functions. > 🔔 Note - Wrap callback functions with `useCallback` for performance optimization and to prevent abnormal behavior. @@ -95,12 +98,13 @@ function App() { onPlaybackQualityChange={handlePlaybackQualityChange} onAutoplayBlocked={handleAutoplayBlocked} /> - ) + ); } ``` ### Functions -You can control various player features like mute, play, volume, and more by calling YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions) through the `ref`. + +You can control various player features like mute, play, volume, and more by calling YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions) through the `ref`. ```tsx function App() { @@ -126,10 +130,7 @@ function App() { return ( - + - ) + ); } ``` ### Player Parameters + You can customize the playback environment by configuring YouTube embedded player [parameters](https://developers.google.com/youtube/player_parameters#Parameters). ```tsx @@ -175,11 +177,12 @@ function App() { muted: true, }} /> - ) + ); } ``` ### Styles + You can customize the YouTube player's styling to match your application's design. ```tsx @@ -197,19 +200,24 @@ function App() { aspectRatio: 16 / 9, }} // iOS, Android platform support - webViewStyle={{ - // ... - }} + webViewStyle={ + { + // ... + } + } // iOS, Android platform support - webViewProps={{ - // ... - }} + webViewProps={ + { + // ... + } + } /> - ) + ); } ``` ### Playback Progress Tracking + - If `progressInterval` is provided, the `onProgress` callback will be invoked at the specified interval (in milliseconds). - If `progressInterval` is `undefined`, `0`, or `null`, progress tracking is disabled and `onProgress` will not be called. @@ -221,19 +229,13 @@ function App() { setLoadedFraction(progress.loadedFraction); }, []); - return ( - - ) + return ; } ``` ### Player Rendering & Source Configuration (ios, android) -**Inline HTML vs WebView Mode** +**Inline HTML vs WebView Mode** Control YouTube player rendering method and configure source URLs for compatibility. 1. **Inline HTML Mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) @@ -243,6 +245,7 @@ Control YouTube player rendering method and configure source URLs for compatibil > [!NOTE] > **webViewUrl Usage** +> > - When `useInlineHtml: true`: Set as the `baseUrl` for WebView source HTML. > - When `useInlineHtml: false`: Overrides the WebView source `uri`. > @@ -281,6 +284,7 @@ export default CustomPlayerPage; > For more details, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). ### YouTube oEmbed API + Use the `useYoutubeOEmbed` hook to fetch YouTube video metadata. This hook only supports YouTube URLs. @@ -288,21 +292,23 @@ This hook only supports YouTube URLs. import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); - - if (isLoading) return Loading...; - if (error) return Error: {error.message}; - if (!oEmbed) return null; - - return ( - <> - {oEmbed.title} - - - ) + const { oEmbed, isLoading, error } = useYoutubeOEmbed( + 'https://www.youtube.com/watch?v=AbZH7XWDW_k', + ); + + if (isLoading) return Loading...; + if (error) return Error: {error.message}; + if (!oEmbed) return null; + + return ( + <> + {oEmbed.title} + + + ); } ``` diff --git a/packages/react-native-youtube-bridge/package.json b/packages/react-native-youtube-bridge/package.json index fa2b397..a3b1fc4 100644 --- a/packages/react-native-youtube-bridge/package.json +++ b/packages/react-native-youtube-bridge/package.json @@ -2,15 +2,25 @@ "name": "react-native-youtube-bridge", "version": "2.1.4", "description": "🎥 Easy-to-use YouTube player for React Native with cross-platform support", - "main": "./lib/module/index.js", - "types": "./lib/typescript/index.d.ts", - "exports": { - ".": { - "source": "./src/index.tsx", - "types": "./lib/typescript/index.d.ts", - "default": "./lib/module/index.js" - }, - "./package.json": "./package.json" + "keywords": [ + "android", + "ios", + "react-native", + "react-native-youtube", + "react-native-youtube-bridge", + "react-native-youtube-iframe", + "youtube", + "youtube-iframe-api" + ], + "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", + "bugs": { + "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + }, + "license": "MIT", + "author": "saseungmin (https://github.com/saseungmin)", + "repository": { + "type": "git", + "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" }, "files": [ "lib", @@ -31,35 +41,25 @@ "!**/__mocks__", "!**/.*" ], - "scripts": { - "test": "jest", - "typecheck": "tsc --noEmit", - "clean": "del-cli lib", - "build": "bob build" - }, - "keywords": [ - "react-native", - "ios", - "android", - "youtube", - "react-native-youtube", - "react-native-youtube-bridge", - "youtube-iframe-api", - "react-native-youtube-iframe" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" - }, - "author": "saseungmin (https://github.com/saseungmin)", - "license": "MIT", - "bugs": { - "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + "main": "./lib/module/index.js", + "types": "./lib/typescript/index.d.ts", + "exports": { + ".": { + "types": "./lib/typescript/index.d.ts", + "source": "./src/index.tsx", + "default": "./lib/module/index.js" + }, + "./package.json": "./package.json" }, - "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", "publishConfig": { "access": "public" }, + "scripts": { + "build": "bob build", + "clean": "del-cli lib", + "test": "jest", + "typecheck": "tsc --noEmit" + }, "dependencies": { "@react-native-youtube-bridge/core": "workspace:*", "@react-native-youtube-bridge/react": "workspace:*" @@ -80,10 +80,15 @@ "react-native-webview": ">=11.0.0" }, "jest": { - "preset": "react-native", "modulePathIgnorePatterns": [ "/lib/" - ] + ], + "preset": "react-native" + }, + "create-react-native-library": { + "languages": "js", + "type": "library", + "version": "0.50.3" }, "react-native-builder-bob": { "source": "src", @@ -102,10 +107,5 @@ } ] ] - }, - "create-react-native-library": { - "languages": "js", - "type": "library", - "version": "0.50.3" } } diff --git a/packages/react-native-youtube-bridge/src/YoutubeView.tsx b/packages/react-native-youtube-bridge/src/YoutubeView.tsx index 0dd473f..baa0d98 100644 --- a/packages/react-native-youtube-bridge/src/YoutubeView.tsx +++ b/packages/react-native-youtube-bridge/src/YoutubeView.tsx @@ -1,15 +1,18 @@ +import { type MessageData, MATCH_URL_YOUTUBE } from '@react-native-youtube-bridge/core'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { type DataDetectorTypes, Dimensions, StyleSheet, Linking } from 'react-native'; -import WebView, { type WebViewMessageEvent } from 'react-native-webview'; +import { WebView, type WebViewMessageEvent } from 'react-native-webview'; import type { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'; -import { type MessageData, MATCH_URL_YOUTUBE } from '@react-native-youtube-bridge/core'; -import YoutubeViewWrapper from './YoutubeViewWrapper'; import useCreateLocalPlayerHtml from './hooks/useCreateLocalPlayerHtml'; +import WebviewYoutubePlayerController from './modules/WebviewYoutubePlayerController'; +import { + INTERNAL_SET_CONTROLLER_INSTANCE, + INTERNAL_UPDATE_PROGRESS_INTERVAL, +} from './modules/YoutubePlayer'; import type { YoutubeViewProps } from './types/youtube'; import { getYoutubeWebViewUrl } from './utils/youtube'; -import WebviewYoutubePlayerController from './modules/WebviewYoutubePlayerController'; -import { INTERNAL_SET_CONTROLLER_INSTANCE, INTERNAL_UPDATE_PROGRESS_INTERVAL } from './modules/YoutubePlayer'; +import YoutubeViewWrapper from './YoutubeViewWrapper'; const { width: screenWidth } = Dimensions.get('window'); @@ -37,7 +40,6 @@ function YoutubeView({ const createPlayerHTML = useCreateLocalPlayerHtml({ videoId, useInlineHtml, ...playerVars }); const webViewUrl = getYoutubeWebViewUrl(videoId, useInlineHtml, playerVars, webViewBaseUrl); - // biome-ignore lint/correctness/useExhaustiveDependencies: webViewProps.source is intentionally excluded to prevent unnecessary re-renders const webViewSource = useMemo(() => { if (useInlineHtml) { const webViewBaseUrlWithSlash = @@ -50,10 +52,11 @@ function YoutubeView({ } if (webViewUrl) { - return { ...(webViewProps?.source ?? {}), uri: webViewUrl }; + return { ...webViewProps?.source, uri: webViewUrl }; } return undefined; + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps }, [useInlineHtml, createPlayerHTML, webViewBaseUrl, webViewUrl]); const handleMessage = useCallback( diff --git a/packages/react-native-youtube-bridge/src/YoutubeView.web.tsx b/packages/react-native-youtube-bridge/src/YoutubeView.web.tsx index 70d7a81..23cdc13 100644 --- a/packages/react-native-youtube-bridge/src/YoutubeView.web.tsx +++ b/packages/react-native-youtube-bridge/src/YoutubeView.web.tsx @@ -1,10 +1,13 @@ +import { WebYoutubePlayerController } from '@react-native-youtube-bridge/core'; import { useEffect, useRef, useState } from 'react'; import { useWindowDimensions } from 'react-native'; -import { WebYoutubePlayerController } from '@react-native-youtube-bridge/core'; -import { INTERNAL_SET_CONTROLLER_INSTANCE, INTERNAL_UPDATE_PROGRESS_INTERVAL } from './modules/YoutubePlayer'; -import YoutubeViewWrapper from './YoutubeViewWrapper'; +import { + INTERNAL_SET_CONTROLLER_INSTANCE, + INTERNAL_UPDATE_PROGRESS_INTERVAL, +} from './modules/YoutubePlayer'; import type { YoutubeViewProps } from './types/youtube'; +import YoutubeViewWrapper from './YoutubeViewWrapper'; function YoutubeView({ player, height, width, style, iframeStyle }: YoutubeViewProps) { const { width: screenWidth } = useWindowDimensions(); diff --git a/packages/react-native-youtube-bridge/src/YoutubeViewWrapper.tsx b/packages/react-native-youtube-bridge/src/YoutubeViewWrapper.tsx index 77437d7..075bf66 100644 --- a/packages/react-native-youtube-bridge/src/YoutubeViewWrapper.tsx +++ b/packages/react-native-youtube-bridge/src/YoutubeViewWrapper.tsx @@ -1,5 +1,11 @@ import type { ReactNode } from 'react'; -import { type DimensionValue, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'; +import { + type DimensionValue, + type StyleProp, + StyleSheet, + View, + type ViewStyle, +} from 'react-native'; type YoutubeViewWrapperProps = { children: ReactNode; diff --git a/packages/react-native-youtube-bridge/src/hooks/useCreateLocalPlayerHtml.ts b/packages/react-native-youtube-bridge/src/hooks/useCreateLocalPlayerHtml.ts index 0551bdc..c756e57 100644 --- a/packages/react-native-youtube-bridge/src/hooks/useCreateLocalPlayerHtml.ts +++ b/packages/react-native-youtube-bridge/src/hooks/useCreateLocalPlayerHtml.ts @@ -1,5 +1,10 @@ +import { + type YoutubePlayerVars, + escapeHtml, + safeNumber, + validateVideoId, +} from '@react-native-youtube-bridge/core'; import { useCallback } from 'react'; -import { type YoutubePlayerVars, escapeHtml, safeNumber, validateVideoId } from '@react-native-youtube-bridge/core'; import { youtubeIframeScripts } from './youtubeIframeScripts'; @@ -206,7 +211,19 @@ const useCreateLocalPlayerHtml = ({ `; - }, [videoId, origin, startTime, endTime, autoplay, controls, loop, muted, playsinline, rel, useInlineHtml]); + }, [ + videoId, + origin, + startTime, + endTime, + autoplay, + controls, + loop, + muted, + playsinline, + rel, + useInlineHtml, + ]); return createPlayerHTML; }; diff --git a/packages/react-native-youtube-bridge/src/hooks/useYouTubeEvent.ts b/packages/react-native-youtube-bridge/src/hooks/useYouTubeEvent.ts index baf545b..6713de7 100644 --- a/packages/react-native-youtube-bridge/src/hooks/useYouTubeEvent.ts +++ b/packages/react-native-youtube-bridge/src/hooks/useYouTubeEvent.ts @@ -1,5 +1,5 @@ -import { useEffect, useRef, useState } from 'react'; import type { EventCallback, YoutubePlayerEvents } from '@react-native-youtube-bridge/core'; +import { useEffect, useRef, useState } from 'react'; import type YoutubePlayer from '../modules/YoutubePlayer'; import { INTERNAL_SET_PROGRESS_INTERVAL } from '../modules/YoutubePlayer'; @@ -74,7 +74,10 @@ function useYouTubeEvent( function useYouTubeEvent( player: YoutubePlayer, eventType: T, - callbackOrThrottleOrDefaultValue?: EventCallback | YoutubePlayerEvents[T] | null, + callbackOrThrottleOrDefaultValue?: + | EventCallback + | YoutubePlayerEvents[T] + | null, deps?: React.DependencyList, ): YoutubePlayerEvents[T] | null | undefined { const isProgress = eventType === 'progress'; @@ -111,6 +114,7 @@ function useYouTubeEvent( if (isCallback) { callbackRef.current = callbackOrThrottleOrDefaultValue; } + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps }, [callbackOrThrottleOrDefaultValue, isCallback, ...(deps ?? [])]); useEffect(() => { @@ -119,7 +123,6 @@ function useYouTubeEvent( } }, [throttleMs, player]); - // biome-ignore lint/correctness/useExhaustiveDependencies: defaultValue is intentionally excluded to prevent unnecessary re-subscriptions useEffect(() => { if (!player) { return; @@ -140,6 +143,7 @@ function useYouTubeEvent( setData(defaultValue ?? null); unsubscribe(); }; + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps }, [player, eventType, isCallback]); return isCallback ? undefined : data; diff --git a/packages/react-native-youtube-bridge/src/hooks/useYouTubePlayer.ts b/packages/react-native-youtube-bridge/src/hooks/useYouTubePlayer.ts index 211f4fd..1c8234d 100644 --- a/packages/react-native-youtube-bridge/src/hooks/useYouTubePlayer.ts +++ b/packages/react-native-youtube-bridge/src/hooks/useYouTubePlayer.ts @@ -1,6 +1,10 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import type { YoutubeError, YoutubePlayerVars, YoutubeSource } from '@react-native-youtube-bridge/core'; +import type { + YoutubeError, + YoutubePlayerVars, + YoutubeSource, +} from '@react-native-youtube-bridge/core'; import { useYouTubeVideoId } from '@react-native-youtube-bridge/react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import YoutubePlayer from '../modules/YoutubePlayer'; @@ -52,7 +56,6 @@ const useYouTubePlayer = (source: YoutubeSource, config?: YoutubePlayerVars): Yo playerRef.current = new YoutubePlayer(videoId, config); } - // biome-ignore lint/correctness/useExhaustiveDependencies: videoId changes trigger re-creation const player = useMemo(() => { let newPlayer = playerRef.current; @@ -67,6 +70,7 @@ const useYouTubePlayer = (source: YoutubeSource, config?: YoutubePlayerVars): Yo isFastRefresh.current = true; return newPlayer; + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps }, [videoId]); useEffect(() => { diff --git a/packages/react-native-youtube-bridge/src/modules/WebviewYoutubePlayerController.ts b/packages/react-native-youtube-bridge/src/modules/WebviewYoutubePlayerController.ts index 582e57c..a4d460a 100644 --- a/packages/react-native-youtube-bridge/src/modules/WebviewYoutubePlayerController.ts +++ b/packages/react-native-youtube-bridge/src/modules/WebviewYoutubePlayerController.ts @@ -1,5 +1,5 @@ -import type WebView from 'react-native-webview'; import type { PlayerEvents } from '@react-native-youtube-bridge/core'; +import { type WebView } from 'react-native-webview'; class WebviewYoutubePlayerController { private webViewRef: React.RefObject; @@ -10,7 +10,9 @@ class WebviewYoutubePlayerController { this.webViewRef = webViewRef; } - static createInstance(webViewRef: React.RefObject): WebviewYoutubePlayerController { + static createInstance( + webViewRef: React.RefObject, + ): WebviewYoutubePlayerController { return new WebviewYoutubePlayerController(webViewRef); } diff --git a/packages/react-native-youtube-bridge/src/modules/YoutubePlayer.ts b/packages/react-native-youtube-bridge/src/modules/YoutubePlayer.ts index 342a5c3..b32645f 100644 --- a/packages/react-native-youtube-bridge/src/modules/YoutubePlayer.ts +++ b/packages/react-native-youtube-bridge/src/modules/YoutubePlayer.ts @@ -65,7 +65,10 @@ class YoutubePlayer { } } - subscribe(event: T, callback: EventCallback): () => void { + subscribe( + event: T, + callback: EventCallback, + ): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } diff --git a/packages/react-native-youtube-bridge/src/types/youtube.ts b/packages/react-native-youtube-bridge/src/types/youtube.ts index 20a382b..7ac56ae 100644 --- a/packages/react-native-youtube-bridge/src/types/youtube.ts +++ b/packages/react-native-youtube-bridge/src/types/youtube.ts @@ -2,6 +2,7 @@ import type { CSSProperties } from 'react'; import type { StyleProp, ViewStyle } from 'react-native'; import type { WebViewProps } from 'react-native-webview'; import type { WebViewSourceUri } from 'react-native-webview/lib/WebViewTypes'; + import type YoutubePlayer from '../modules/YoutubePlayer'; /** @@ -71,7 +72,10 @@ export type YoutubeViewProps = { * The props of the WebView. * @platform ios, android */ - webViewProps?: Omit & { + webViewProps?: Omit< + WebViewProps, + 'ref' | 'source' | 'style' | 'onMessage' | 'javaScriptEnabled' | 'onError' + > & { source?: Omit; }; }; diff --git a/packages/react-native-youtube-bridge/src/utils/youtube.ts b/packages/react-native-youtube-bridge/src/utils/youtube.ts index 6dc4b69..9f55122 100644 --- a/packages/react-native-youtube-bridge/src/utils/youtube.ts +++ b/packages/react-native-youtube-bridge/src/utils/youtube.ts @@ -1,4 +1,5 @@ import type { YoutubePlayerVars } from '@react-native-youtube-bridge/core'; + import { DEFAULT_EXTERNAL_WEB_URL } from './constants'; export const getYoutubeWebViewUrl = ( @@ -13,13 +14,18 @@ export const getYoutubeWebViewUrl = ( const baseUrl = webViewBaseUrl || DEFAULT_EXTERNAL_WEB_URL; - const { startTime, autoplay, controls, loop, muted, playsinline, rel, endTime, origin } = playerVars; + const { startTime, autoplay, controls, loop, muted, playsinline, rel, endTime, origin } = + playerVars; const url = new URL(baseUrl); url.searchParams.set('videoId', videoId); - startTime && url.searchParams.set('startTime', startTime.toString()); - endTime && url.searchParams.set('endTime', endTime.toString()); + if (startTime != null) { + url.searchParams.set('startTime', startTime.toString()); + } + if (endTime != null) { + url.searchParams.set('endTime', endTime.toString()); + } url.searchParams.set('origin', origin || baseUrl); url.searchParams.set('autoplay', autoplay ? 'true' : 'false'); url.searchParams.set('controls', controls ? 'true' : 'false'); diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index ee2a1dd..2ad40bd 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -79,7 +79,7 @@ ```jsx // Imperative, ref-based API - const playerRef = useRef < PlayerControls > null; + const playerRef = useRef(null); (https://github.com/saseungmin)", + "repository": { + "type": "git", + "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" + }, + "files": [ + "dist", + "package.json" + ], "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -12,44 +36,20 @@ "require": "./dist/index.js" } }, - "files": [ - "dist", - "package.json" - ], + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsdown", "dev": "tsdown --watch ./src", "typecheck": "tsc --noEmit" }, - "keywords": [ - "react-native", - "ios", - "android", - "youtube", - "react-native-youtube", - "react-native-youtube-bridge", - "youtube-iframe-api", - "react-native-youtube-iframe" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" - }, - "author": "saseungmin (https://github.com/saseungmin)", - "license": "MIT", - "bugs": { - "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" - }, - "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", - "publishConfig": { - "access": "public" - }, "dependencies": { "@react-native-youtube-bridge/core": "workspace:*" }, "devDependencies": { - "react": "19.0.0", "@types/react": "^19.0.12", + "react": "19.0.0", "tsdown": "^0.12.8" }, "peerDependencies": { diff --git a/packages/react/src/hooks/useYoutubeOEmbed.ts b/packages/react/src/hooks/useYoutubeOEmbed.ts index 7a04fac..4c864e1 100644 --- a/packages/react/src/hooks/useYoutubeOEmbed.ts +++ b/packages/react/src/hooks/useYoutubeOEmbed.ts @@ -39,9 +39,12 @@ const useYoutubeOEmbed = (url?: string) => { const fetchOEmbed = async () => { setIsLoading(true); try { - const response = await fetch(`https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(url)}`, { - signal: controller.signal, - }); + const response = await fetch( + `https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(url)}`, + { + signal: controller.signal, + }, + ); if (!response.ok) { throw new Error('Failed to fetch oEmbed'); diff --git a/packages/react/src/hooks/useYoutubeVideoId.ts b/packages/react/src/hooks/useYoutubeVideoId.ts index c4e1ae7..666abab 100644 --- a/packages/react/src/hooks/useYoutubeVideoId.ts +++ b/packages/react/src/hooks/useYoutubeVideoId.ts @@ -7,8 +7,10 @@ import { } from '@react-native-youtube-bridge/core'; import { useMemo } from 'react'; -const useYouTubeVideoId = (source: YoutubeSource, onError?: PlayerEvents['onError']): string | null | undefined => { - // biome-ignore lint/correctness/useExhaustiveDependencies: +const useYouTubeVideoId = ( + source: YoutubeSource, + onError?: PlayerEvents['onError'], +): string | null | undefined => { const sourceValue = useMemo(() => { if (!source) { return; @@ -28,6 +30,7 @@ const useYouTubeVideoId = (source: YoutubeSource, onError?: PlayerEvents['onErro return null; }, [ + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps typeof source === 'string' ? source : source && 'videoId' in source diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md index a3019e2..0530482 100644 --- a/packages/web/CHANGELOG.md +++ b/packages/web/CHANGELOG.md @@ -84,7 +84,7 @@ ```jsx // Imperative, ref-based API - const playerRef = useRef < PlayerControls > null; + const playerRef = useRef(null); {/* 방법 1: 인라인 HTML 사용 (기본값) */} - - + + {/* 방법 2: 외부 웹뷰 페이지 사용 */} - ) + ); } ``` diff --git a/packages/web/README.md b/packages/web/README.md index 089f6b9..9bd017c 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -17,11 +17,8 @@ function App() { return ( <> {/* Method 1: Using inline HTML (default) */} - - + + {/* Method 2: Using external WebView page */} - ) + ); } ``` diff --git a/packages/web/package.json b/packages/web/package.json index f91acf6..a69700a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,52 +2,52 @@ "name": "@react-native-youtube-bridge/web", "version": "2.1.4", "description": "Web implementation for react-native-youtube-bridge", - "main": "dist/web.mjs", - "exports": { - ".": { - "import": "./dist/web.mjs" - } - }, - "files": [ - "dist", - "package.json" - ], - "scripts": { - "dev": "vite", - "typecheck": "tsc --noEmit", - "build": "tsc -b && vite build" - }, "keywords": [ - "react-native", - "ios", "android", - "youtube", + "ios", + "react-native", "react-native-youtube", "react-native-youtube-bridge", - "youtube-iframe-api", - "react-native-youtube-iframe" + "react-native-youtube-iframe", + "youtube", + "youtube-iframe-api" ], + "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", + "bugs": { + "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + }, + "license": "MIT", + "author": "saseungmin (https://github.com/saseungmin)", "repository": { "type": "git", "url": "git+https://github.com/react-native-bridges/react-native-youtube-bridge.git" }, - "author": "saseungmin (https://github.com/saseungmin)", - "license": "MIT", - "bugs": { - "url": "https://github.com/react-native-bridges/react-native-youtube-bridge/issues" + "files": [ + "dist", + "package.json" + ], + "main": "dist/web.mjs", + "exports": { + ".": { + "import": "./dist/web.mjs" + } }, - "homepage": "https://github.com/react-native-bridges/react-native-youtube-bridge#readme", "publishConfig": { "access": "public" }, + "scripts": { + "build": "tsc -b && vite build", + "dev": "vite", + "typecheck": "tsc --noEmit" + }, "dependencies": { "@react-native-youtube-bridge/core": "workspace:*", "@react-native-youtube-bridge/react": "workspace:*" }, "devDependencies": { - "react": "19.0.0", "@types/react": "^19.0.12", "@vitejs/plugin-react-swc": "^3.9.0", + "react": "19.0.0", "vite": "^6.3.5" }, "peerDependencies": { diff --git a/packages/web/src/YoutubePlayer.tsx b/packages/web/src/YoutubePlayer.tsx index e8894c6..27550b4 100644 --- a/packages/web/src/YoutubePlayer.tsx +++ b/packages/web/src/YoutubePlayer.tsx @@ -1,13 +1,12 @@ -import { useEffect, useRef, useState } from 'react'; +import './YoutubePlayer.css'; -import { useYouTubeVideoId } from '@react-native-youtube-bridge/react'; import { WebYoutubePlayerController } from '@react-native-youtube-bridge/core'; +import { useYouTubeVideoId } from '@react-native-youtube-bridge/react'; +import { useEffect, useRef, useState } from 'react'; import { useWebView } from './hooks/useWebView'; import { parseTimeParam } from './utils'; -import './YoutubePlayer.css'; - function YoutubePlayer() { const urlParams = new URLSearchParams(window.location.search); const videoId = urlParams.get('videoId') ?? ''; diff --git a/packages/web/src/types/message.ts b/packages/web/src/types/message.ts index 92d36bc..e21c476 100644 --- a/packages/web/src/types/message.ts +++ b/packages/web/src/types/message.ts @@ -5,7 +5,9 @@ interface ReceivePlayerControls extends PlayerControls { updateProgressInterval: (interval: number) => void; } -export interface ReceiveMessage { +export interface ReceiveMessage< + K extends keyof ReceivePlayerControls = keyof ReceivePlayerControls, +> { command: K; args: Parameters; id?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f3a7f2..a450529 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: devDependencies: - '@biomejs/biome': - specifier: 1.9.4 - version: 1.9.4 '@changesets/cli': specifier: ^2.29.4 version: 2.29.5 @@ -32,6 +29,12 @@ importers: jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.15.32) + oxfmt: + specifier: ^0.32.0 + version: 0.32.0 + oxlint: + specifier: ^1.47.0 + version: 1.47.0 typescript: specifier: ^5.8.3 version: 5.8.3 @@ -867,59 +870,6 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - '@changesets/apply-release-plan@7.0.12': resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} @@ -1425,6 +1375,250 @@ packages: '@oxc-project/types@0.72.3': resolution: {integrity: sha512-CfAC4wrmMkUoISpQkFAIfMVvlPfQV3xg7ZlcqPXPOIMQhdKIId44G8W0mCPgtpWdFFAyJ+SFtiM+9vbyCkoVng==} + '@oxfmt/binding-android-arm-eabi@0.32.0': + resolution: {integrity: sha512-DpVyuVzgLH6/MvuB/YD3vXO9CN/o9EdRpA0zXwe/tagP6yfVSFkFWkPqTROdqp0mlzLH5Yl+/m+hOrcM601EbA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.32.0': + resolution: {integrity: sha512-w1cmNXf9zs0vKLuNgyUF3hZ9VUAS1hBmQGndYJv1OmcVqStBtRTRNxSWkWM0TMkrA9UbvIvM9gfN+ib4Wy6lkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.32.0': + resolution: {integrity: sha512-m6wQojz/hn94XdZugFPtdFbOvXbOSYEqPsR2gyLyID3BvcrC2QsJyT1o3gb4BZEGtZrG1NiKVGwDRLM0dHd2mg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.32.0': + resolution: {integrity: sha512-hN966Uh6r3Erkg2MvRcrJWaB6QpBzP15rxWK/QtkUyD47eItJLsAQ2Hrm88zMIpFZ3COXZLuN3hqgSlUtvB0Xw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.32.0': + resolution: {integrity: sha512-g5UZPGt8tJj263OfSiDGdS54HPa0KgFfspLVAUivVSdoOgsk6DkwVS9nO16xQTDztzBPGxTvrby8WuufF0g86Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.32.0': + resolution: {integrity: sha512-F4ZY83/PVQo9ZJhtzoMqbmjqEyTVEZjbaw4x1RhzdfUhddB41ZB2Vrt4eZi7b4a4TP85gjPRHgQBeO0c1jbtaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.32.0': + resolution: {integrity: sha512-olR37eG16Lzdj9OBSvuoT5RxzgM5xfQEHm1OEjB3M7Wm4KWa5TDWIT13Aiy74GvAN77Hq1+kUKcGVJ/0ynf75g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.32.0': + resolution: {integrity: sha512-eZhk6AIjRCDeLoXYBhMW7qq/R1YyVi+tGnGfc3kp7AZQrMsFaWtP/bgdCJCTNXMpbMwymtVz0qhSQvR5w2sKcg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.32.0': + resolution: {integrity: sha512-UYiqO9MlipntFbdbUKOIo84vuyzrK4TVIs7Etat91WNMFSW54F6OnHq08xa5ZM+K9+cyYMgQPXvYCopuP+LyKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.32.0': + resolution: {integrity: sha512-IDH/fxMv+HmKsMtsjEbXqhScCKDIYp38sgGEcn0QKeXMxrda67PPZA7HMfoUwEtFUG+jsO1XJxTrQsL+kQ90xQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.32.0': + resolution: {integrity: sha512-bQFGPDa0buYWJFeK2I7ah8wRZjrAgamaG2OAGv+Ua5UMYEnHxmHcv+r8lWUUrwP2oqQGvp1SB8JIVtBbYuAueQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.32.0': + resolution: {integrity: sha512-3vFp9DW1ItEKWltADzCFqG5N7rYFToT4ztlhg8wALoo2E2VhveLD88uAF4FF9AxD9NhgHDGmPCV+WZl/Qlj8cQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.32.0': + resolution: {integrity: sha512-Fub2y8S9ImuPzAzpbgkoz/EVTWFFBolxFZYCMRhRZc8cJZI2gl/NlZswqhvJd/U0Jopnwgm/OJ2x128vVzFFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.32.0': + resolution: {integrity: sha512-XufwsnV3BF81zO2ofZvhT4FFaMmLTzZEZnC9HpFz/quPeg9C948+kbLlZnsfjmp+1dUxKMCpfmRMqOfF4AOLsA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.32.0': + resolution: {integrity: sha512-u2f9tC2qYfikKmA2uGpnEJgManwmk0ZXWs5BB4ga4KDu2JNLdA3i634DGHeMLK9wY9+iRf3t7IYpgN3OVFrvDw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.32.0': + resolution: {integrity: sha512-5ZXb1wrdbZ1YFXuNXNUCePLlmLDy4sUt4evvzD4Cgumbup5wJgS9PIe5BOaLywUg9f1wTH6lwltj3oT7dFpIGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.32.0': + resolution: {integrity: sha512-IGSMm/Agq+IA0++aeAV/AGPfjcBdjrsajB5YpM3j7cMcwoYgUTi/k2YwAmsHH3ueZUE98pSM/Ise2J7HtyRjOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.32.0': + resolution: {integrity: sha512-H/9gsuqXmceWMsVoCPZhtJG2jLbnBeKr7xAXm2zuKpxLVF7/2n0eh7ocOLB6t+L1ARE76iORuUsRMnuGjj8FjQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.32.0': + resolution: {integrity: sha512-fF8VIOeligq+mA6KfKvWtFRXbf0EFy73TdR6ZnNejdJRM8VWN1e3QFhYgIwD7O8jBrQsd7EJbUpkAr/YlUOokg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.47.0': + resolution: {integrity: sha512-UHqo3te9K/fh29brCuQdHjN+kfpIi9cnTPABuD5S9wb9ykXYRGTOOMVuSV/CK43sOhU4wwb2nT1RVjcbrrQjFw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.47.0': + resolution: {integrity: sha512-xh02lsTF1TAkR+SZrRMYHR/xCx8Wg2MAHxJNdHVpAKELh9/yE9h4LJeqAOBbIb3YYn8o/D97U9VmkvkfJfrHfw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.47.0': + resolution: {integrity: sha512-OSOfNJqabOYbkyQDGT5pdoL+05qgyrmlQrvtCO58M4iKGEQ/xf3XkkKj7ws+hO+k8Y4VF4zGlBsJlwqy7qBcHA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.47.0': + resolution: {integrity: sha512-hP2bOI4IWNS+F6pVXWtRshSTuJ1qCRZgDgVUg6EBUqsRy+ExkEPJkx+YmIuxgdCduYK1LKptLNFuQLJP8voPbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.47.0': + resolution: {integrity: sha512-F55jIEH5xmGu7S661Uho8vGiLFk0bY3A/g4J8CTKiLJnYu/PSMZ2WxFoy5Hji6qvFuujrrM9Q8XXbMO0fKOYPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.47.0': + resolution: {integrity: sha512-wxmOn/wns/WKPXUC1fo5mu9pMZPVOu8hsynaVDrgmmXMdHKS7on6bA5cPauFFN9tJXNdsjW26AK9lpfu3IfHBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.47.0': + resolution: {integrity: sha512-KJTmVIA/GqRlM2K+ZROH30VMdydEU7bDTY35fNg3tOPzQRIs2deLZlY/9JWwdWo1F/9mIYmpbdCmPqtKhWNOPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.47.0': + resolution: {integrity: sha512-PF7ELcFg1GVlS0X0ZB6aWiXobjLrAKer3T8YEkwIoO8RwWiAMkL3n3gbleg895BuZkHVlJ2kPRUwfrhHrVkD1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.47.0': + resolution: {integrity: sha512-4BezLRO5cu0asf0Jp1gkrnn2OHiXrPPPEfBTxq1k5/yJ2zdGGTmZxHD2KF2voR23wb8Elyu3iQawXo7wvIZq0Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.47.0': + resolution: {integrity: sha512-aI5ds9jq2CPDOvjeapiIj48T/vlWp+f4prkxs+FVzrmVN9BWIj0eqeJ/hV8WgXg79HVMIz9PU6deI2ki09bR1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.47.0': + resolution: {integrity: sha512-mO7ycp9Elvgt5EdGkQHCwJA6878xvo9tk+vlMfT1qg++UjvOMB8INsOCQIOH2IKErF/8/P21LULkdIrocMw9xA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.47.0': + resolution: {integrity: sha512-24D0wsYT/7hDFn3Ow32m3/+QT/1ZwrUhShx4/wRDAmz11GQHOZ1k+/HBuK/MflebdnalmXWITcPEy4BWTi7TCA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.47.0': + resolution: {integrity: sha512-8tPzPne882mtML/uy3mApvdCyuVOpthJ7xUv3b67gVfz63hOOM/bwO0cysSkPyYYFDFRn6/FnUb7Jhmsesntvg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.47.0': + resolution: {integrity: sha512-q58pIyGIzeffEBhEgbRxLFHmHfV9m7g1RnkLiahQuEvyjKNiJcvdHOwKH2BdgZxdzc99Cs6hF5xTa86X40WzPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.47.0': + resolution: {integrity: sha512-e7DiLZtETZUCwTa4EEHg9G+7g3pY+afCWXvSeMG7m0TQ29UHHxMARPaEQUE4mfKgSqIWnJaUk2iZzRPMRdga5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.47.0': + resolution: {integrity: sha512-3AFPfQ0WKMleT/bKd7zsks3xoawtZA6E/wKf0DjwysH7wUiMMJkNKXOzYq1R/00G98JFgSU1AkrlOQrSdNNhlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.47.0': + resolution: {integrity: sha512-cLMVVM6TBxp+N7FldQJ2GQnkcLYEPGgiuEaXdvhgvSgODBk9ov3jed+khIXSAWtnFOW0wOnG3RjwqPh0rCuheA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.47.0': + resolution: {integrity: sha512-VpFOSzvTnld77/Edje3ZdHgZWnlTb5nVWXyTgjD3/DKF/6t5bRRbwn3z77zOdnGy44xAMvbyAwDNOSeOdVUmRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.47.0': + resolution: {integrity: sha512-+q8IWptxXx2HMTM6JluR67284t0h8X/oHJgqpxH1siowxPMqZeIpAcWCUq+tY+Rv2iQK8TUugjZnSBQAVV5CmA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1552,21 +1746,25 @@ packages: resolution: {integrity: sha512-d+qo1LZ/a3EcQW08byIIZy0PBthmG/7dr69pifmNIet/azWR8jbceQaRFFczVc/NwVV3fsZDCmjG8mgJzsNEAg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.15': resolution: {integrity: sha512-P1hbtYF+5ftJI2Ergs4iARbAk6Xd6WnTQb3CF9kjN3KfJTsRYdo5/fvU8Lz/gzhZVvkCXXH3NxDd9308UBO8cw==} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.15': resolution: {integrity: sha512-Q9NM9uMFN9cjcrW7gd9U087B5WzkEj9dQQHOgoENZSy+vYJYS2fINCIG40ljEVC6jXmVrJgUhJKv7elRZM1nng==} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.15': resolution: {integrity: sha512-1tuCWuR8gx9PyW2pxAx2ZqnOnwhoY6NWBVP6ZmrjCKQ16NclYc61BzegFXSdugCy8w1QpBPT8/c5oh2W4E5aeA==} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-wasm32-wasi@1.0.0-beta.15': resolution: {integrity: sha512-zrSeYrpTf27hRxMLh0qpkCoWgzRKG8EyR6o09Zt9xkqCOeE5tEK/S3jV1Nii9WSqVCWFRA+OYxKzMNoykV590g==} @@ -1628,56 +1826,67 @@ packages: resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.44.0': resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.44.0': resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.44.0': resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.44.0': resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.44.0': resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.44.0': resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.44.0': resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.44.0': resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.44.0': resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.44.0': resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} @@ -1726,24 +1935,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.12.6': resolution: {integrity: sha512-GZu3MnB/5qtBxKEH46hgVDaplEe4mp3ZmQ1O2UpFCv/u/Ji3Gar5w5g2wHCZoT5AOouAhP1bh7IAEqjG/fbVfg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.12.6': resolution: {integrity: sha512-WwJLQFzMW9ufVjM6k3le4HUgBFNunyt2oghjcgn2YjnKj0Ka2LrrBHCxfS7lgFSCQh/shib2wIlKXUnlTEWQJw==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.12.6': resolution: {integrity: sha512-rVGPNpI/sm8VVAhnB09Z/23OJP3ymouv6F4z4aYDbq/2JIwxqgpnl8gtMYP+Jw3XqabaFNjQmPiL15TvKCQaxQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.12.6': resolution: {integrity: sha512-EKDJ1+8vaIlJGMl2yvd2HklV4GNbpKKwNQcUQid6j91tFYz4/aByw+9vh/sDVG7ZNqdmdywSnLRo317UTt0zFg==} @@ -3339,24 +3552,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.27.0: resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.27.0: resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.27.0: resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.27.0: resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} @@ -3733,6 +3950,21 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + oxfmt@0.32.0: + resolution: {integrity: sha512-KArQhGzt/Y8M1eSAX98Y8DLtGYYDQhkR55THUPY5VNcpFQ+9nRZkL3ULXhagHMD2hIvjy8JSeEQEP5/yYJSrLA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint@1.47.0: + resolution: {integrity: sha512-v7xkK1iv1qdvTxJGclM97QzN8hHs5816AneFAQ0NGji1BMUquhiDAhXpMwp8+ls16uRVJtzVHxP9pAAXblDeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.11.2' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -4435,6 +4667,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -5662,41 +5898,6 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@biomejs/biome@1.9.4': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 - - '@biomejs/cli-darwin-arm64@1.9.4': - optional: true - - '@biomejs/cli-darwin-x64@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64@1.9.4': - optional: true - - '@biomejs/cli-linux-x64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-x64@1.9.4': - optional: true - - '@biomejs/cli-win32-arm64@1.9.4': - optional: true - - '@biomejs/cli-win32-x64@1.9.4': - optional: true - '@changesets/apply-release-plan@7.0.12': dependencies: '@changesets/config': 3.1.1 @@ -6545,6 +6746,120 @@ snapshots: '@oxc-project/types@0.72.3': {} + '@oxfmt/binding-android-arm-eabi@0.32.0': + optional: true + + '@oxfmt/binding-android-arm64@0.32.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.32.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.32.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.32.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.32.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.32.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.32.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.32.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.32.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.32.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.32.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.32.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.32.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.32.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.32.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.32.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.32.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.32.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.47.0': + optional: true + + '@oxlint/binding-android-arm64@1.47.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.47.0': + optional: true + + '@oxlint/binding-darwin-x64@1.47.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.47.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.47.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.47.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.47.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.47.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.47.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.47.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.47.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.47.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.47.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.47.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.47.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.47.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.47.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.47.0': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -9197,6 +9512,52 @@ snapshots: outdent@0.5.0: {} + oxfmt@0.32.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.32.0 + '@oxfmt/binding-android-arm64': 0.32.0 + '@oxfmt/binding-darwin-arm64': 0.32.0 + '@oxfmt/binding-darwin-x64': 0.32.0 + '@oxfmt/binding-freebsd-x64': 0.32.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.32.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.32.0 + '@oxfmt/binding-linux-arm64-gnu': 0.32.0 + '@oxfmt/binding-linux-arm64-musl': 0.32.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.32.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.32.0 + '@oxfmt/binding-linux-riscv64-musl': 0.32.0 + '@oxfmt/binding-linux-s390x-gnu': 0.32.0 + '@oxfmt/binding-linux-x64-gnu': 0.32.0 + '@oxfmt/binding-linux-x64-musl': 0.32.0 + '@oxfmt/binding-openharmony-arm64': 0.32.0 + '@oxfmt/binding-win32-arm64-msvc': 0.32.0 + '@oxfmt/binding-win32-ia32-msvc': 0.32.0 + '@oxfmt/binding-win32-x64-msvc': 0.32.0 + + oxlint@1.47.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.47.0 + '@oxlint/binding-android-arm64': 1.47.0 + '@oxlint/binding-darwin-arm64': 1.47.0 + '@oxlint/binding-darwin-x64': 1.47.0 + '@oxlint/binding-freebsd-x64': 1.47.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.47.0 + '@oxlint/binding-linux-arm-musleabihf': 1.47.0 + '@oxlint/binding-linux-arm64-gnu': 1.47.0 + '@oxlint/binding-linux-arm64-musl': 1.47.0 + '@oxlint/binding-linux-ppc64-gnu': 1.47.0 + '@oxlint/binding-linux-riscv64-gnu': 1.47.0 + '@oxlint/binding-linux-riscv64-musl': 1.47.0 + '@oxlint/binding-linux-s390x-gnu': 1.47.0 + '@oxlint/binding-linux-x64-gnu': 1.47.0 + '@oxlint/binding-linux-x64-musl': 1.47.0 + '@oxlint/binding-openharmony-arm64': 1.47.0 + '@oxlint/binding-win32-arm64-msvc': 1.47.0 + '@oxlint/binding-win32-ia32-msvc': 1.47.0 + '@oxlint/binding-win32-x64-msvc': 1.47.0 + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -9961,6 +10322,8 @@ snapshots: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 + tinypool@2.1.0: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2249fc1..4d17830 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,6 +4,3 @@ packages: onlyBuiltDependencies: - '@swc/core' - 'esbuild' -catalog: - 'typescript': ^5.8.3 - 'jest': ^29.7.0 diff --git a/web/bun.lock b/web/bun.lock index c28ac4f..ce66494 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "web", diff --git a/web/index.html b/web/index.html index 0a5f02f..0c8d1f3 100644 --- a/web/index.html +++ b/web/index.html @@ -3,8 +3,10 @@ - + React Native YouTube Bridge diff --git a/web/package.json b/web/package.json index ce5485b..05ec20f 100644 --- a/web/package.json +++ b/web/package.json @@ -1,11 +1,11 @@ { "name": "web", - "private": true, "version": "0.0.0", + "private": true, "type": "module", "scripts": { - "dev": "vite", "build": "vite build", + "dev": "vite", "preview": "vite preview" }, "dependencies": { @@ -18,5 +18,6 @@ "@vitejs/plugin-react-swc": "^3.9.0", "typescript": "~5.8.3", "vite": "^6.3.5" - } + }, + "packageManager": "bun@1.3.9" } diff --git a/web/src/main.tsx b/web/src/main.tsx index 3084387..2ead4bf 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,6 +1,8 @@ +import './index.css'; + import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import './index.css'; + import App from './App.tsx'; createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json index 64b7679..2eded36 100644 --- a/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -23,7 +23,7 @@ "noUncheckedSideEffectImports": true, "paths": { "@react-native-youtube-bridge/web": ["../packages/web/src/index.ts"] - }, + } }, "include": ["src"] } diff --git a/web/tsconfig.json b/web/tsconfig.json index 1ffef60..d32ff68 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/web/vite.config.ts b/web/vite.config.ts index e90d160..94c4ea3 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,20 +1,24 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' -import path from 'node:path' +import path from 'node:path'; + +import react from '@vitejs/plugin-react-swc'; +import { defineConfig } from 'vite'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], resolve: { alias: { - 'react': path.resolve(__dirname, './node_modules/react'), + react: path.resolve(__dirname, './node_modules/react'), 'react-dom': path.resolve(__dirname, './node_modules/react-dom'), '@react-native-youtube-bridge/web': path.resolve(__dirname, '../packages/web/src/index.ts'), '@react-native-youtube-bridge/core': path.resolve(__dirname, '../packages/core/src/index.ts'), - '@react-native-youtube-bridge/react': path.resolve(__dirname, '../packages/react/src/index.ts'), + '@react-native-youtube-bridge/react': path.resolve( + __dirname, + '../packages/react/src/index.ts', + ), }, }, optimizeDeps: { include: ['react', 'react-dom'], - } -}) + }, +});