From e2ed3081eba69364fdbca26791a062682308c98a Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 20:31:52 +0200 Subject: [PATCH 1/9] make web entry compatible --- bundle.js | 36 +++++++++++++++++++++++++++++++++ package/src/web/WithSkiaWeb.tsx | 8 +------- 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 bundle.js diff --git a/bundle.js b/bundle.js new file mode 100644 index 0000000000..3c23f139d3 --- /dev/null +++ b/bundle.js @@ -0,0 +1,36 @@ +// package/src/web/LoadSkiaWeb.tsx +import CanvasKitInit from "canvaskit-wasm/bin/full/canvaskit"; +var ckSharedPromise; +var LoadSkiaWeb = async (opts) => { + if (global.CanvasKit !== undefined) { + return; + } + ckSharedPromise = ckSharedPromise ?? CanvasKitInit(opts); + const CanvasKit = await ckSharedPromise; + global.CanvasKit = CanvasKit; +}; +var LoadSkia = LoadSkiaWeb; +// package/src/web/WithSkiaWeb.tsx +import {useMemo, lazy, Suspense} from "react"; +import { +jsxDEV +} from "react/jsx-dev-runtime"; +var WithSkiaWeb = ({ + getComponent, + fallback, + opts +}) => { + const Inner = useMemo(() => lazy(async () => { + await LoadSkiaWeb(opts); + return getComponent(); + }), [getComponent, opts]); + return jsxDEV(Suspense, { + fallback: fallback ?? null, + children: jsxDEV(Inner, {}, undefined, false, undefined, this) + }, undefined, false, undefined, this); +}; +export { + WithSkiaWeb, + LoadSkiaWeb, + LoadSkia +}; diff --git a/package/src/web/WithSkiaWeb.tsx b/package/src/web/WithSkiaWeb.tsx index 1a940481e4..d215421f83 100644 --- a/package/src/web/WithSkiaWeb.tsx +++ b/package/src/web/WithSkiaWeb.tsx @@ -21,13 +21,7 @@ export const WithSkiaWeb = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (): any => lazy(async () => { - if (Platform.OS === "web") { - await LoadSkiaWeb(opts); - } else { - console.warn( - " is only necessary on web. Consider not using on native." - ); - } + await LoadSkiaWeb(opts); return getComponent(); }), [getComponent, opts] From be58062f7c9ade6fe845cdbc244f7dfcc52bda4d Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 21:19:42 +0200 Subject: [PATCH 2/9] figure it out --- bundle.js | 36 -------- package.json | 3 +- package/build.ts | 87 +++++++++++++++++++ package/src/Platform/Platform.web.tsx | 21 +---- .../Platform/ResolveAssetWithNoDependency.tsx | 11 +++ .../Platform/ResolveAssetWithRNDependency.tsx | 18 ++++ package/src/skia/web/JsiSkDataFactory.ts | 2 +- package/src/web/WithSkiaWeb.tsx | 2 - package/tsconfig.json | 3 +- yarn.lock | 41 +++++++++ 10 files changed, 164 insertions(+), 60 deletions(-) delete mode 100644 bundle.js create mode 100644 package/build.ts create mode 100644 package/src/Platform/ResolveAssetWithNoDependency.tsx create mode 100644 package/src/Platform/ResolveAssetWithRNDependency.tsx diff --git a/bundle.js b/bundle.js deleted file mode 100644 index 3c23f139d3..0000000000 --- a/bundle.js +++ /dev/null @@ -1,36 +0,0 @@ -// package/src/web/LoadSkiaWeb.tsx -import CanvasKitInit from "canvaskit-wasm/bin/full/canvaskit"; -var ckSharedPromise; -var LoadSkiaWeb = async (opts) => { - if (global.CanvasKit !== undefined) { - return; - } - ckSharedPromise = ckSharedPromise ?? CanvasKitInit(opts); - const CanvasKit = await ckSharedPromise; - global.CanvasKit = CanvasKit; -}; -var LoadSkia = LoadSkiaWeb; -// package/src/web/WithSkiaWeb.tsx -import {useMemo, lazy, Suspense} from "react"; -import { -jsxDEV -} from "react/jsx-dev-runtime"; -var WithSkiaWeb = ({ - getComponent, - fallback, - opts -}) => { - const Inner = useMemo(() => lazy(async () => { - await LoadSkiaWeb(opts); - return getComponent(); - }), [getComponent, opts]); - return jsxDEV(Suspense, { - fallback: fallback ?? null, - children: jsxDEV(Inner, {}, undefined, false, undefined, this) - }, undefined, false, undefined, this); -}; -export { - WithSkiaWeb, - LoadSkiaWeb, - LoadSkia -}; diff --git a/package.json b/package.json index 07de86c0d2..3451e09956 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "example": "example" }, "devDependencies": { + "@types/bun": "1.1.0", "@types/node": "16.11.7", "clang-format": "1.6.0", "rimraf": "3.0.2", @@ -22,7 +23,7 @@ "copy-skia-include-headers": "yarn rimraf ./package/cpp/skia/include/ && cp -a ./externals/skia/include/. ./package/cpp/skia/include", "copy-skia-module-headers": "ts-node ./scripts/copy-skia-module-headers.ts", "copy-skia-headers": "yarn copy-skia-include-headers && yarn copy-skia-module-headers", - "build": "yarn build-skia && yarn copy-skia-headers", + "build": "yarn build-skia && yarn copy-skia-headers && bun build.ts", "clang-format": "yarn clang-format-ios && yarn clang-format-android && yarn clang-format-common", "clang-format-ios": "find package/ios/ -iname *.h -o -iname *.mm -o -iname *.cpp | xargs clang-format -i", "clang-format-android": "find package/android/cpp/ -iname *.h -o -iname *.m -o -iname *.cpp | xargs clang-format -i", diff --git a/package/build.ts b/package/build.ts new file mode 100644 index 0000000000..5069ba06bd --- /dev/null +++ b/package/build.ts @@ -0,0 +1,87 @@ +import { build, BunPlugin } from "bun"; +import pathModule from "path"; + +export const bundleSkia = async ( + noReactNativeDependency: boolean, + output: string +) => { + const reactNativePlugin: BunPlugin = { + name: "Resolve .web.ts first", + setup(build) { + build.onResolve( + { filter: /.*/ }, + async ({ importer, namespace, path }) => { + // Don't resolve the file with the RN dependency if building for web + if (noReactNativeDependency) { + path = path.replace( + /ResolveAssetWithRNDependency/, + "ResolveAssetWithNoDependency" + ); + } + const resolved = pathModule.resolve(importer, "..", path); + + // First resolve web.ts + const extensions = [".web.ts", ".web.tsx", ".ts", ".tsx"]; + const resolvedWithExtensions = extensions.map( + (ext) => resolved + ext + ); + + // Override with .web.tsx if it exists + for (const resolvedWithExtension of resolvedWithExtensions) { + if (await Bun.file(resolvedWithExtension).exists()) { + return Promise.resolve({ + namespace, + path: resolvedWithExtension, + }); + } + } + return undefined; + } + ); + }, + }; + + const outputs = await build({ + plugins: [reactNativePlugin], + entrypoints: ["./src/index.ts"], + // Don't bundle these dependencies + external: [ + "react-native", + "canvaskit-wasm", + "react", + "scheduler", + "react-reconciler", + ], + }); + + if (!outputs.success) { + console.error(outputs.logs); + throw new Error("Build failed"); + } + const bundled = outputs.outputs[0]; + Bun.write(output, await bundled.text()); +}; + +const bundleSkiaWeb = async () => { + const outputs = await build({ + entrypoints: ["./src/web/index.ts"], + // Don't bundle these dependencies + external: [ + "react-native", + "canvaskit-wasm", + "react", + "scheduler", + "react-reconciler", + ], + }); + if (!outputs.success) { + console.error(outputs.logs); + throw new Error("Build failed"); + } + const bundled = outputs.outputs[0]; + Bun.write("webindex.js", await bundled.text()); +}; + +await bundleSkia(true, "web.js"); +await bundleSkia(false, "react-native-web.js"); +await bundleSkiaWeb(); diff --git a/package/src/Platform/Platform.web.tsx b/package/src/Platform/Platform.web.tsx index 0177371a6b..8a3c04583b 100644 --- a/package/src/Platform/Platform.web.tsx +++ b/package/src/Platform/Platform.web.tsx @@ -1,9 +1,7 @@ import type { RefObject, CSSProperties } from "react"; import React, { useLayoutEffect, useMemo, useRef } from "react"; import type { LayoutChangeEvent, ViewComponent, ViewProps } from "react-native"; - -import type { DataModule } from "../skia/types"; -import { isRNModule } from "../skia/types"; +import { resolveAsset } from "./ResolveAssetWithRNDependency"; import type { IPlatform } from "./IPlatform"; @@ -127,22 +125,7 @@ const View = (({ children, onLayout, style: rawStyle }: ViewProps) => { export const Platform: IPlatform = { OS: "web", PixelRatio: typeof window !== "undefined" ? window.devicePixelRatio : 1, // window is not defined on node - resolveAsset: (source: DataModule) => { - if (isRNModule(source)) { - if (typeof source === "number" && typeof require === "function") { - const { - getAssetByID, - } = require("react-native/Libraries/Image/AssetRegistry"); - const { httpServerLocation, name, type } = getAssetByID(source); - const uri = `${httpServerLocation}/${name}.${type}`; - return uri; - } - throw new Error( - "Asset source is a number - this is not supported on the web" - ); - } - return source.default; - }, + resolveAsset: resolveAsset, findNodeHandle: () => { throw new Error("findNodeHandle is not supported on the web"); }, diff --git a/package/src/Platform/ResolveAssetWithNoDependency.tsx b/package/src/Platform/ResolveAssetWithNoDependency.tsx new file mode 100644 index 0000000000..7192e318a4 --- /dev/null +++ b/package/src/Platform/ResolveAssetWithNoDependency.tsx @@ -0,0 +1,11 @@ +import { DataModule } from "../skia"; +import type { resolveAsset as original } from "./ResolveAssetWithRNDependency"; + +export const resolveAsset: typeof original = (source: DataModule) => { + if (typeof source === "number") { + throw new Error( + "Asset loading is not implemented in pure web - use React Native Web implementation" + ); + } + return source.default; +}; diff --git a/package/src/Platform/ResolveAssetWithRNDependency.tsx b/package/src/Platform/ResolveAssetWithRNDependency.tsx new file mode 100644 index 0000000000..679fe7335b --- /dev/null +++ b/package/src/Platform/ResolveAssetWithRNDependency.tsx @@ -0,0 +1,18 @@ +import { DataModule, isRNModule } from "../skia/types"; + +export const resolveAsset = (source: DataModule) => { + if (isRNModule(source)) { + if (typeof source === "number" && typeof require === "function") { + const { + getAssetByID, + } = require("react-native/Libraries/Image/AssetRegistry"); + const { httpServerLocation, name, type } = getAssetByID(source); + const uri = `${httpServerLocation}/${name}.${type}`; + return uri; + } + throw new Error( + "Asset source is a number - this is not supported on the web" + ); + } + return source.default; +}; diff --git a/package/src/skia/web/JsiSkDataFactory.ts b/package/src/skia/web/JsiSkDataFactory.ts index 662de472b1..1041a97318 100644 --- a/package/src/skia/web/JsiSkDataFactory.ts +++ b/package/src/skia/web/JsiSkDataFactory.ts @@ -21,7 +21,7 @@ export class JsiSkDataFactory extends Host implements DataFactory { * @param bytes An array of bytes representing the data */ fromBytes(bytes: Uint8Array) { - return new JsiSkData(this.CanvasKit, bytes); + return new JsiSkData(this.CanvasKit, bytes as ArrayBuffer); } /** * Creates a new Data object from a base64 encoded string. diff --git a/package/src/web/WithSkiaWeb.tsx b/package/src/web/WithSkiaWeb.tsx index d215421f83..3eb1bb385c 100644 --- a/package/src/web/WithSkiaWeb.tsx +++ b/package/src/web/WithSkiaWeb.tsx @@ -1,8 +1,6 @@ import type { ComponentProps, ComponentType } from "react"; import React, { useMemo, lazy, Suspense } from "react"; -import { Platform } from "../Platform"; - import { LoadSkiaWeb } from "./LoadSkiaWeb"; interface WithSkiaProps { diff --git a/package/tsconfig.json b/package/tsconfig.json index e4e4c1126d..ae60cba562 100644 --- a/package/tsconfig.json +++ b/package/tsconfig.json @@ -34,6 +34,7 @@ "metro.config.js", "jest.config.js", "lib", - "scripts" + "scripts", + "build.ts" ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2d9f2a24eb..5970b9f373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,11 +34,39 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/bun@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/bun/-/bun-1.1.0.tgz#227060a9b97a74213430f06ed1423aa943c75bca" + integrity sha512-QGK0yU4jh0OK1A7DyhPkQuKjHQCC5jSJa3dpWIEhHv/rPfb6zLfdArc4/uUUZBMTcjilsafRXnPWO+1owb572Q== + dependencies: + bun-types "1.1.0" + +"@types/node@*": + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + dependencies: + undici-types "~5.26.4" + "@types/node@16.11.7": version "16.11.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@~20.11.3": + version "20.11.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" + integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + dependencies: + undici-types "~5.26.4" + +"@types/ws@~8.5.10": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + acorn-walk@^8.1.1: version "8.3.2" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" @@ -72,6 +100,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +bun-types@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/bun-types/-/bun-types-1.1.0.tgz#2a0bcabda1e7e6f7be94f622be5fc9e3bd279796" + integrity sha512-GhMDD7TosdJzQPGUOcQD5PZshvXVxDfwGAZs2dq+eSaPsRn3iUCzvpFlsg7Q51bXVzLAUs+FWHlnmpgZ5UggIg== + dependencies: + "@types/node" "~20.11.3" + "@types/ws" "~8.5.10" + clang-format@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.6.0.tgz#48fac4387712aeeae0f47b5d72f639f3fd95f4b6" @@ -218,6 +254,11 @@ typescript@5.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 0c5dd828bcf652a03df51c17fd258c66fca31c3c Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 21:38:19 +0200 Subject: [PATCH 3/9] exports --- package.json | 2 +- package/build.ts | 35 ++++++++---------------- package/package.json | 2 +- package/react-native-web.js | 3 ++ package/src/skia/web/JsiSkDataFactory.ts | 3 +- package/src/web/for-bundling.ts | 3 ++ package/web.js | 3 ++ 7 files changed, 24 insertions(+), 27 deletions(-) create mode 100644 package/react-native-web.js create mode 100644 package/src/web/for-bundling.ts create mode 100644 package/web.js diff --git a/package.json b/package.json index 3451e09956..7741852483 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "copy-skia-include-headers": "yarn rimraf ./package/cpp/skia/include/ && cp -a ./externals/skia/include/. ./package/cpp/skia/include", "copy-skia-module-headers": "ts-node ./scripts/copy-skia-module-headers.ts", "copy-skia-headers": "yarn copy-skia-include-headers && yarn copy-skia-module-headers", - "build": "yarn build-skia && yarn copy-skia-headers && bun build.ts", + "build": "yarn build-skia && yarn copy-skia-headers", "clang-format": "yarn clang-format-ios && yarn clang-format-android && yarn clang-format-common", "clang-format-ios": "find package/ios/ -iname *.h -o -iname *.mm -o -iname *.cpp | xargs clang-format -i", "clang-format-android": "find package/android/cpp/ -iname *.h -o -iname *.m -o -iname *.cpp | xargs clang-format -i", diff --git a/package/build.ts b/package/build.ts index 5069ba06bd..4ae7b5bf57 100644 --- a/package/build.ts +++ b/package/build.ts @@ -43,7 +43,7 @@ export const bundleSkia = async ( const outputs = await build({ plugins: [reactNativePlugin], - entrypoints: ["./src/index.ts"], + entrypoints: ["./src/web/for-bundling.ts"], // Don't bundle these dependencies external: [ "react-native", @@ -62,26 +62,13 @@ export const bundleSkia = async ( Bun.write(output, await bundled.text()); }; -const bundleSkiaWeb = async () => { - const outputs = await build({ - entrypoints: ["./src/web/index.ts"], - // Don't bundle these dependencies - external: [ - "react-native", - "canvaskit-wasm", - "react", - "scheduler", - "react-reconciler", - ], - }); - if (!outputs.success) { - console.error(outputs.logs); - throw new Error("Build failed"); - } - const bundled = outputs.outputs[0]; - Bun.write("webindex.js", await bundled.text()); -}; - -await bundleSkia(true, "web.js"); -await bundleSkia(false, "react-native-web.js"); -await bundleSkiaWeb(); +await bundleSkia(true, "lib/web/pure.js"); +await bundleSkia(false, "lib/web/react-native-web.js"); +Bun.write( + "lib/web/pure.d.ts", + 'export * from "../typescript/src/web/for-bundling";' +); +Bun.write( + "lib/web/react-native-web.d.ts", + 'export * from "../typescript/src/web/for-bundling";' +); diff --git a/package/package.json b/package/package.json index 64058082d4..8102bec19c 100644 --- a/package/package.json +++ b/package/package.json @@ -48,7 +48,7 @@ "lint": "eslint . --ext .ts,.tsx --max-warnings 0 --cache", "test": "jest", "e2e": "E2E=true yarn test -i e2e", - "build": "bob build && merge-dirs lib/typescript/src lib/commonjs && merge-dirs lib/typescript/src lib/module", + "build": "bob build && merge-dirs lib/typescript/src lib/commonjs && merge-dirs lib/typescript/src lib/module && bun build.ts", "release": "semantic-release" }, "repository": { diff --git a/package/react-native-web.js b/package/react-native-web.js new file mode 100644 index 0000000000..e4611fe041 --- /dev/null +++ b/package/react-native-web.js @@ -0,0 +1,3 @@ +// For maximum backwards compatibility, not using "exports" field +// and using a commonjs export +module.exports = require("./lib/web/react-native-web"); diff --git a/package/src/skia/web/JsiSkDataFactory.ts b/package/src/skia/web/JsiSkDataFactory.ts index 1041a97318..6926e8c1bd 100644 --- a/package/src/skia/web/JsiSkDataFactory.ts +++ b/package/src/skia/web/JsiSkDataFactory.ts @@ -21,7 +21,8 @@ export class JsiSkDataFactory extends Host implements DataFactory { * @param bytes An array of bytes representing the data */ fromBytes(bytes: Uint8Array) { - return new JsiSkData(this.CanvasKit, bytes as ArrayBuffer); + // FIXME: Bun type error, might be resolved with a newer version + return new JsiSkData(this.CanvasKit, bytes as unknown as ArrayBuffer); } /** * Creates a new Data object from a base64 encoded string. diff --git a/package/src/web/for-bundling.ts b/package/src/web/for-bundling.ts new file mode 100644 index 0000000000..1bb3e99676 --- /dev/null +++ b/package/src/web/for-bundling.ts @@ -0,0 +1,3 @@ +export * from "./LoadSkiaWeb"; +export * from "./WithSkiaWeb"; +export * from "../index"; diff --git a/package/web.js b/package/web.js new file mode 100644 index 0000000000..3fb56870f4 --- /dev/null +++ b/package/web.js @@ -0,0 +1,3 @@ +// For maximum backwards compatibility, not using "exports" field +// and using a commonjs export +module.exports = require("./lib/web/pure"); From 057e2c3db5a51dd66a4a8511708131f41538fa23 Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 21:46:37 +0200 Subject: [PATCH 4/9] explain what is happening --- package/build.ts | 13 ++++++++++++- .../src/Platform/ResolveAssetWithNoDependency.tsx | 3 +++ .../src/Platform/ResolveAssetWithRNDependency.tsx | 3 +++ package/src/web/for-bundling.ts | 8 ++++++++ package/web.js | 5 +++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/package/build.ts b/package/build.ts index 4ae7b5bf57..1e09bd9933 100644 --- a/package/build.ts +++ b/package/build.ts @@ -1,3 +1,13 @@ +// Execute this file with `bun build.ts` + +// Generates two files: +// `@Shopify/react-native-skia/web` -> `lib/web/pure.js` +// -> A version of RN Skia that has no React Native dependency +// and therefore no Webpack override is necessary +// `@Shopify/react-native-skia/react-native-web` -> lib/web/react-native-web.js +// -> A version of RN Skia for the web that has a React Native dependency +// that can be overriden with Webpack in order to support React Native-style assets + import { build, BunPlugin } from "bun"; import pathModule from "path"; @@ -11,7 +21,8 @@ export const bundleSkia = async ( build.onResolve( { filter: /.*/ }, async ({ importer, namespace, path }) => { - // Don't resolve the file with the RN dependency if building for web + // If there should be no react-native dependency, + // override it with a no-dependency version if (noReactNativeDependency) { path = path.replace( /ResolveAssetWithRNDependency/, diff --git a/package/src/Platform/ResolveAssetWithNoDependency.tsx b/package/src/Platform/ResolveAssetWithNoDependency.tsx index 7192e318a4..73e467c552 100644 --- a/package/src/Platform/ResolveAssetWithNoDependency.tsx +++ b/package/src/Platform/ResolveAssetWithNoDependency.tsx @@ -1,3 +1,6 @@ +// In `package/build.ts`, this file will replace `ResolveAssetWithRNDependency.tsx` +// in order to remove React Native dependencies. + import { DataModule } from "../skia"; import type { resolveAsset as original } from "./ResolveAssetWithRNDependency"; diff --git a/package/src/Platform/ResolveAssetWithRNDependency.tsx b/package/src/Platform/ResolveAssetWithRNDependency.tsx index 679fe7335b..e0d3806ea9 100644 --- a/package/src/Platform/ResolveAssetWithRNDependency.tsx +++ b/package/src/Platform/ResolveAssetWithRNDependency.tsx @@ -1,3 +1,6 @@ +// In `package/build.ts`, this file will be replaced with +// `ResolveAssetWithNoDependency.tsx` in order to remove React Native dependencies. + import { DataModule, isRNModule } from "../skia/types"; export const resolveAsset = (source: DataModule) => { diff --git a/package/src/web/for-bundling.ts b/package/src/web/for-bundling.ts index 1bb3e99676..a2ad7e1cfc 100644 --- a/package/src/web/for-bundling.ts +++ b/package/src/web/for-bundling.ts @@ -1,3 +1,11 @@ +// This file can get bundled with package/build.ts +// and two versions will get generated: +// @Shopify/react-native-skia/web -> A Pure JS version with no webpack override necessary +// @Shopify/react-native-skia/react-native-web -> +// A React Native Web version +// that supports React Native assets but +// but needs a react-native-web Webpack override + export * from "./LoadSkiaWeb"; export * from "./WithSkiaWeb"; export * from "../index"; diff --git a/package/web.js b/package/web.js index 3fb56870f4..eb4abb286b 100644 --- a/package/web.js +++ b/package/web.js @@ -1,3 +1,8 @@ +// The entry point for `@Shopify/react-native-skia/web` +// A version of RN Skia that requires no Webpack override +// The `lib/web/pure` file gets generated at build time +// using `bun build.ts` + // For maximum backwards compatibility, not using "exports" field // and using a commonjs export module.exports = require("./lib/web/pure"); From ff426c66edd39836199a35a51a2b4ff2364508a4 Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 22:04:05 +0200 Subject: [PATCH 5/9] awesome --- package/build.ts | 35 +++++++++++++++---- .../reanimated/ReanimatedProxyPure.ts | 15 ++++++++ .../external/reanimated/reanimatedStatus.ts | 20 +++++++++++ .../reanimated/reanimatedStatusPure.ts | 5 +++ .../src/external/reanimated/renderHelpers.ts | 18 ++-------- 5 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 package/src/external/reanimated/ReanimatedProxyPure.ts create mode 100644 package/src/external/reanimated/reanimatedStatus.ts create mode 100644 package/src/external/reanimated/reanimatedStatusPure.ts diff --git a/package/build.ts b/package/build.ts index 1e09bd9933..9710257cda 100644 --- a/package/build.ts +++ b/package/build.ts @@ -25,9 +25,11 @@ export const bundleSkia = async ( // override it with a no-dependency version if (noReactNativeDependency) { path = path.replace( - /ResolveAssetWithRNDependency/, + "ResolveAssetWithRNDependency", "ResolveAssetWithNoDependency" ); + path = path.replace("ReanimatedProxy", "ReanimatedProxyPure"); + path = path.replace("reanimatedStatus", "reanimatedStatusPure"); } const resolved = pathModule.resolve(importer, "..", path); @@ -62,6 +64,7 @@ export const bundleSkia = async ( "react", "scheduler", "react-reconciler", + "react-native-reanimated", ], }); @@ -69,17 +72,35 @@ export const bundleSkia = async ( console.error(outputs.logs); throw new Error("Build failed"); } - const bundled = outputs.outputs[0]; - Bun.write(output, await bundled.text()); + return outputs.outputs[0].text(); }; -await bundleSkia(true, "lib/web/pure.js"); -await bundleSkia(false, "lib/web/react-native-web.js"); -Bun.write( +const PURE_VERSION = "lib/web/pure.js"; +const REACT_NATIVE_WEB_VERSION = "lib/web/react-native-web.js"; + +// 1. Bundle a pure version with no React Native dependencies +const pureVersion = await bundleSkia(true, PURE_VERSION); +await Bun.write(PURE_VERSION, pureVersion); +// Test pure version: Should not import React Native dependencies +// or react-native-reanimated +if ( + pureVersion.includes(`__require("react-native`) || + pureVersion.includes(`from "react-native`) +) { + throw new Error("Pure version should not include React Native dependencies"); +} + +// 2. Bundle a version with React Native dependencies +const rnwVersion = await bundleSkia(false, REACT_NATIVE_WEB_VERSION); +// Test RNW version: Should import React Native dependencies +await Bun.write(REACT_NATIVE_WEB_VERSION, rnwVersion); + +// 3. Map the types to the correct files +await Bun.write( "lib/web/pure.d.ts", 'export * from "../typescript/src/web/for-bundling";' ); -Bun.write( +await Bun.write( "lib/web/react-native-web.d.ts", 'export * from "../typescript/src/web/for-bundling";' ); diff --git a/package/src/external/reanimated/ReanimatedProxyPure.ts b/package/src/external/reanimated/ReanimatedProxyPure.ts new file mode 100644 index 0000000000..19b912fe4a --- /dev/null +++ b/package/src/external/reanimated/ReanimatedProxyPure.ts @@ -0,0 +1,15 @@ +import type * as ReanimatedT from "react-native-reanimated"; +import { + createModuleProxy, + OptionalDependencyNotInstalledError, +} from "../ModuleProxy"; + +import original from "./ReanimatedProxy"; +type TReanimated = typeof ReanimatedT; + +const Reanimated: typeof original = createModuleProxy(() => { + throw new OptionalDependencyNotInstalledError("react-native-reanimated"); +}); + +// eslint-disable-next-line import/no-default-export +export default Reanimated; diff --git a/package/src/external/reanimated/reanimatedStatus.ts b/package/src/external/reanimated/reanimatedStatus.ts new file mode 100644 index 0000000000..1667ff522b --- /dev/null +++ b/package/src/external/reanimated/reanimatedStatus.ts @@ -0,0 +1,20 @@ +export const getReanimatedStatus = () => { + let HAS_REANIMATED = false; + let HAS_REANIMATED_3 = false; + try { + require("react-native-reanimated"); + HAS_REANIMATED = true; + const reanimatedVersion = + require("react-native-reanimated/package.json").version; + if ( + reanimatedVersion && + (reanimatedVersion >= "3.0.0" || reanimatedVersion.includes("3.0.0-")) + ) { + HAS_REANIMATED_3 = true; + } + } catch (e) { + HAS_REANIMATED = false; + } + + return { HAS_REANIMATED, HAS_REANIMATED_3 }; +}; diff --git a/package/src/external/reanimated/reanimatedStatusPure.ts b/package/src/external/reanimated/reanimatedStatusPure.ts new file mode 100644 index 0000000000..4b36c2d7c9 --- /dev/null +++ b/package/src/external/reanimated/reanimatedStatusPure.ts @@ -0,0 +1,5 @@ +import { getReanimatedStatus as original } from "./reanimatedStatus"; + +export const getReanimatedStatus: typeof original = () => { + return { HAS_REANIMATED: false, HAS_REANIMATED_3: false }; +}; diff --git a/package/src/external/reanimated/renderHelpers.ts b/package/src/external/reanimated/renderHelpers.ts index d9003a6a51..9920efdf71 100644 --- a/package/src/external/reanimated/renderHelpers.ts +++ b/package/src/external/reanimated/renderHelpers.ts @@ -5,23 +5,9 @@ import type { AnimatedProps } from "../../renderer/processors"; import type { Node } from "../../dom/types"; import Rea from "./ReanimatedProxy"; +import { getReanimatedStatus } from "./reanimatedStatus"; -let HAS_REANIMATED = false; -let HAS_REANIMATED_3 = false; -try { - require("react-native-reanimated"); - HAS_REANIMATED = true; - const reanimatedVersion = - require("react-native-reanimated/package.json").version; - if ( - reanimatedVersion && - (reanimatedVersion >= "3.0.0" || reanimatedVersion.includes("3.0.0-")) - ) { - HAS_REANIMATED_3 = true; - } -} catch (e) { - HAS_REANIMATED = false; -} +const { HAS_REANIMATED, HAS_REANIMATED_3 } = getReanimatedStatus(); const _bindings = new WeakMap, unknown>(); From b7a16b30412edf81fb9356ff32557a93cb670219 Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 22:04:55 +0200 Subject: [PATCH 6/9] add bun to pipeline --- .github/workflows/build-npm.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-npm.yml b/.github/workflows/build-npm.yml index ed81f8ed43..5c888a8f8d 100644 --- a/.github/workflows/build-npm.yml +++ b/.github/workflows/build-npm.yml @@ -18,8 +18,11 @@ jobs: - uses: actions/setup-node@v3 with: node-version: "lts/*" - cache: 'yarn' - cache-dependency-path: 'package/yarn.lock' + cache: "yarn" + cache-dependency-path: "package/yarn.lock" + - uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.1.3 - name: Install root node dependencies run: yarn From 907af0dac5d40f867154116f4ebb75bdc21c6a0a Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 22:10:36 +0200 Subject: [PATCH 7/9] fix linting --- package/.eslintrc | 1 + package/src/Platform/Platform.web.tsx | 2 +- package/src/Platform/ResolveAssetWithNoDependency.tsx | 3 ++- package/src/Platform/ResolveAssetWithRNDependency.tsx | 3 ++- package/src/external/reanimated/ReanimatedProxyPure.ts | 3 ++- package/src/external/reanimated/reanimatedStatusPure.ts | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package/.eslintrc b/package/.eslintrc index b8629a1d80..26cf69b443 100644 --- a/package/.eslintrc +++ b/package/.eslintrc @@ -4,6 +4,7 @@ "parserOptions": { "project": "./tsconfig.json" }, + "ignorePatterns": "build.ts", "rules": { "prefer-destructuring": [ "error", diff --git a/package/src/Platform/Platform.web.tsx b/package/src/Platform/Platform.web.tsx index 8a3c04583b..41ad2a776b 100644 --- a/package/src/Platform/Platform.web.tsx +++ b/package/src/Platform/Platform.web.tsx @@ -1,8 +1,8 @@ import type { RefObject, CSSProperties } from "react"; import React, { useLayoutEffect, useMemo, useRef } from "react"; import type { LayoutChangeEvent, ViewComponent, ViewProps } from "react-native"; -import { resolveAsset } from "./ResolveAssetWithRNDependency"; +import { resolveAsset } from "./ResolveAssetWithRNDependency"; import type { IPlatform } from "./IPlatform"; // eslint-disable-next-line max-len diff --git a/package/src/Platform/ResolveAssetWithNoDependency.tsx b/package/src/Platform/ResolveAssetWithNoDependency.tsx index 73e467c552..7dc5e01037 100644 --- a/package/src/Platform/ResolveAssetWithNoDependency.tsx +++ b/package/src/Platform/ResolveAssetWithNoDependency.tsx @@ -1,7 +1,8 @@ // In `package/build.ts`, this file will replace `ResolveAssetWithRNDependency.tsx` // in order to remove React Native dependencies. -import { DataModule } from "../skia"; +import type { DataModule } from "../skia"; + import type { resolveAsset as original } from "./ResolveAssetWithRNDependency"; export const resolveAsset: typeof original = (source: DataModule) => { diff --git a/package/src/Platform/ResolveAssetWithRNDependency.tsx b/package/src/Platform/ResolveAssetWithRNDependency.tsx index e0d3806ea9..15b13ead4c 100644 --- a/package/src/Platform/ResolveAssetWithRNDependency.tsx +++ b/package/src/Platform/ResolveAssetWithRNDependency.tsx @@ -1,7 +1,8 @@ // In `package/build.ts`, this file will be replaced with // `ResolveAssetWithNoDependency.tsx` in order to remove React Native dependencies. -import { DataModule, isRNModule } from "../skia/types"; +import type { DataModule } from "../skia/types"; +import { isRNModule } from "../skia/types"; export const resolveAsset = (source: DataModule) => { if (isRNModule(source)) { diff --git a/package/src/external/reanimated/ReanimatedProxyPure.ts b/package/src/external/reanimated/ReanimatedProxyPure.ts index 19b912fe4a..73d166c0fb 100644 --- a/package/src/external/reanimated/ReanimatedProxyPure.ts +++ b/package/src/external/reanimated/ReanimatedProxyPure.ts @@ -1,10 +1,11 @@ import type * as ReanimatedT from "react-native-reanimated"; + import { createModuleProxy, OptionalDependencyNotInstalledError, } from "../ModuleProxy"; -import original from "./ReanimatedProxy"; +import type original from "./ReanimatedProxy"; type TReanimated = typeof ReanimatedT; const Reanimated: typeof original = createModuleProxy(() => { diff --git a/package/src/external/reanimated/reanimatedStatusPure.ts b/package/src/external/reanimated/reanimatedStatusPure.ts index 4b36c2d7c9..7dcbd7c145 100644 --- a/package/src/external/reanimated/reanimatedStatusPure.ts +++ b/package/src/external/reanimated/reanimatedStatusPure.ts @@ -1,4 +1,4 @@ -import { getReanimatedStatus as original } from "./reanimatedStatus"; +import type { getReanimatedStatus as original } from "./reanimatedStatus"; export const getReanimatedStatus: typeof original = () => { return { HAS_REANIMATED: false, HAS_REANIMATED_3: false }; From 55b6f604a1201ee0258424551614a64cd535c885 Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Sat, 20 Apr 2024 22:27:17 +0200 Subject: [PATCH 8/9] Update package.json --- package/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/package.json b/package/package.json index 8102bec19c..38151525cf 100644 --- a/package/package.json +++ b/package/package.json @@ -27,6 +27,8 @@ "android/src/**", "libs/android/**", "index.js", + "web.js", + "react-native-web.js", "jestSetup.js", "jestSetup.mjs", "jestEnv.mjs", From 8b07b9f53bb1a612af544e654d16f747ba7b6f4e Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Thu, 6 Jun 2024 09:40:10 +0200 Subject: [PATCH 9/9] fix bun bundling --- .github/workflows/build-npm.yml | 2 +- package/build.ts | 4 ++++ package/package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-npm.yml b/.github/workflows/build-npm.yml index 5c888a8f8d..25fd33d9aa 100644 --- a/.github/workflows/build-npm.yml +++ b/.github/workflows/build-npm.yml @@ -22,7 +22,7 @@ jobs: cache-dependency-path: "package/yarn.lock" - uses: oven-sh/setup-bun@v1 with: - bun-version: 1.1.3 + bun-version: 1.1.12 - name: Install root node dependencies run: yarn diff --git a/package/build.ts b/package/build.ts index 9710257cda..e0a3f7c989 100644 --- a/package/build.ts +++ b/package/build.ts @@ -11,6 +11,10 @@ import { build, BunPlugin } from "bun"; import pathModule from "path"; +if (process.env.NODE_ENV !== "production") { + throw new Error("This script needs to be run with NODE_ENV=production."); +} + export const bundleSkia = async ( noReactNativeDependency: boolean, output: string diff --git a/package/package.json b/package/package.json index 38151525cf..27f3e303ae 100644 --- a/package/package.json +++ b/package/package.json @@ -50,7 +50,7 @@ "lint": "eslint . --ext .ts,.tsx --max-warnings 0 --cache", "test": "jest", "e2e": "E2E=true yarn test -i e2e", - "build": "bob build && merge-dirs lib/typescript/src lib/commonjs && merge-dirs lib/typescript/src lib/module && bun build.ts", + "build": "bob build && merge-dirs lib/typescript/src lib/commonjs && merge-dirs lib/typescript/src lib/module && NODE_ENV=production bun build.ts", "release": "semantic-release" }, "repository": {