Web Support
React Native Skia runs in the browser via CanvasKit, a WebAssembly (WASM) build of Skia. The CanvasKit WASM file, which is 2.9MB when gzipped, is loaded asynchronously. Despite its considerable size, it offers flexibility in determining when and how Skia loads, giving you full control over the user experience.
We support direct integration with Expo and Remotion. Additionally, you'll find manual installation steps for any webpack projects.
It should also be mentionned that React Native Skia can be used on projects without the need to install React Native Web.
Expo
Metro and expo-router support is available from v0.1.240 and onwards.
If you are using v0.1.221 (recommended version for Expo SDK 50), you can use this patch (using patch-package
.
Use the setup-skia-web
script to ensure that the canvaskit.wasm
file is accessible within your Expo project's public folder.
If you're loading CanvasKit from a CDN, running the setup-skia-web
script is unnecessary.
bash
$ npx expo install @shopify/react-native-skia$ yarn setup-skia-web
bash
$ npx expo install @shopify/react-native-skia$ yarn setup-skia-web
Run yarn setup-skia-web
each time you upgrade the @shopify/react-native-skia
package.
Consider incorporating it into your postinstall
script for convenience.
After setup, choose your method to Load Skia.
For projects using Expo Router, you can use code-splitting or deferred component registration.
If you wish to use deferred component registration with Expo Router, you need to create your own main
property in package.json
.
For instance, if you've created index.tsx
and index.web.tsx
in your root directory, update your package.json
accordingly:
- "main": "expo-router/entry", + "main": "index",
- "main": "expo-router/entry", + "main": "index",
Below is an example of index.web.tsx
:
tsx
import '@expo/metro-runtime';import { App } from 'expo-router/build/qualified-entry';import { renderRootComponent } from 'expo-router/build/renderRootComponent';import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';LoadSkiaWeb().then(async () => {renderRootComponent(App);});
tsx
import '@expo/metro-runtime';import { App } from 'expo-router/build/qualified-entry';import { renderRootComponent } from 'expo-router/build/renderRootComponent';import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';LoadSkiaWeb().then(async () => {renderRootComponent(App);});
For the index.tsx
file, directly invoke renderRootComponent(App)
.
Snack
Utilize the code-splitting method for incorporating React Native Skia on snack.
Remotion
Follow these installation steps to use React Native Skia with Remotion.
Loading Skia
Ensure Skia is fully loaded and initialized before importing the Skia module. Two methods facilitate Skia's loading:
<WithSkiaWeb />
for code-splitting, delaying the loading of Skia-importing components.LoadSkiaWeb()
to defer root component registration until Skia loads.
Using Code-Splitting
The <WithSkiaWeb>
component utilizes code splitting to preload Skia.
The following example demonstrates preloading Skia before rendering the MySkiaComponent
:
tsx
import React from 'react';import { Text } from "react-native";import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";export default function App() {return (<WithSkiaWeb// import() uses the default export of MySkiaComponent.tsxgetComponent={() => import("@/components/MySkiaComponent")}fallback={<Text>Loading Skia...</Text>}/>);}
tsx
import React from 'react';import { Text } from "react-native";import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";export default function App() {return (<WithSkiaWeb// import() uses the default export of MySkiaComponent.tsxgetComponent={() => import("@/components/MySkiaComponent")}fallback={<Text>Loading Skia...</Text>}/>);}
When using expo router in dev mode you cannot load components that are inside the app directory, as they will get evaluated by the router before CanvasKit is loaded. Make sure the component to load lies outside the 'app' directory.
Using Deferred Component Registration
The LoadSkiaWeb()
function facilitates Skia's loading prior to the React app's initiation.
Below is an index.web.js
example:
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";LoadSkiaWeb().then(async () => {const App = (await import("./src/App")).default;AppRegistry.registerComponent("Example", () => App);});
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";LoadSkiaWeb().then(async () => {const App = (await import("./src/App")).default;AppRegistry.registerComponent("Example", () => App);});
Using a CDN
Below, CanvasKit loads via code-splitting from a CDN. It is critical that the CDN-hosted CanvasKit version aligns with React Native Skia's requirements.
tsx
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";import { version } from 'canvaskit-wasm/package.json';export default function App() {return (<WithSkiaWebopts={{ locateFile: (file) => `https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}` }}getComponent={() => import("./MySkiaComponent")});}
tsx
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";import { version } from 'canvaskit-wasm/package.json';export default function App() {return (<WithSkiaWebopts={{ locateFile: (file) => `https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}` }}getComponent={() => import("./MySkiaComponent")});}
Alternatively, use deferred component registration:
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";import { version } from 'canvaskit-wasm/package.json';LoadSkiaWeb({locateFile: (file) => `https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}`}).then(async () => {const App = (await import("./src/App")).default;AppRegistry.registerComponent("Example", () => App);});
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";import { version } from 'canvaskit-wasm/package.json';LoadSkiaWeb({locateFile: (file) => `https://cdn.jsdelivr.net/npm/canvaskit-wasm@${version}/bin/full/${file}`}).then(async () => {const App = (await import("./src/App")).default;AppRegistry.registerComponent("Example", () => App);});
Unsupported Features
The following React Native Skia APIs are currently unsupported on React Native Web. To request these features, please submit a feature request on GitHub.
Unsupported
PathEffectFactory.MakeSum()
PathEffectFactory.MakeCompose()
PathFactory.MakeFromText()
ShaderFilter
Manual webpack Installation
To enable React Native Skia on Web using webpack, three key actions are required:
- Ensure the
canvaskit.wasm
file is accessible to the build system. - Configure the build system to resolve the
fs
andpath
node modules, achievable via the node polyfill plugin. - Update aliases for
react-native-reanimated
andreact-native/Libraries/Image/AssetRegistry
so webpack can do the bundle.
Here is an example webpack v5 configuration accommodating React Native Skia:
tsx
import fs from "fs";import { sources } from "webpack";import NodePolyfillPlugin from "node-polyfill-webpack-plugin";const newConfiguration = {...currentConfiguration,plugins: [...currentConfiguration.plugins,// 1. Ensure wasm file availabilitynew (class CopySkiaPlugin {apply(compiler) {compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {compilation.hooks.processAssets.tapPromise({name: "copy-skia",stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,},async () => {const src = require.resolve("canvaskit-wasm/bin/full/canvaskit.wasm");if (!compilation.getAsset(src)) {compilation.emitAsset("/canvaskit.wasm", new sources.RawSource(await fs.promises.readFile(src)));}});});}})(),// 2. Polyfill fs and path modulesnew NodePolyfillPlugin()],alias: {...currentConfiguration.alias,// 3. Suppress reanimated module warning// This assumes Reanimated is installed, if not you can use false."react-native-reanimated/package.json": require.resolve("react-native-reanimated/package.json"),"react-native-reanimated": require.resolve("react-native-reanimated"),"react-native/Libraries/Image/AssetRegistry": false,},}
tsx
import fs from "fs";import { sources } from "webpack";import NodePolyfillPlugin from "node-polyfill-webpack-plugin";const newConfiguration = {...currentConfiguration,plugins: [...currentConfiguration.plugins,// 1. Ensure wasm file availabilitynew (class CopySkiaPlugin {apply(compiler) {compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {compilation.hooks.processAssets.tapPromise({name: "copy-skia",stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,},async () => {const src = require.resolve("canvaskit-wasm/bin/full/canvaskit.wasm");if (!compilation.getAsset(src)) {compilation.emitAsset("/canvaskit.wasm", new sources.RawSource(await fs.promises.readFile(src)));}});});}})(),// 2. Polyfill fs and path modulesnew NodePolyfillPlugin()],alias: {...currentConfiguration.alias,// 3. Suppress reanimated module warning// This assumes Reanimated is installed, if not you can use false."react-native-reanimated/package.json": require.resolve("react-native-reanimated/package.json"),"react-native-reanimated": require.resolve("react-native-reanimated"),"react-native/Libraries/Image/AssetRegistry": false,},}
Finally, proceed to load Skia.