Skip to main content

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

info

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
info

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.tsx
getComponent={() => import("./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.tsx
getComponent={() => import("./MySkiaComponent")}
fallback={<Text>Loading Skia...</Text>}
/>
);
}

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 (
<WithSkiaWeb
opts={{ 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 (
<WithSkiaWeb
opts={{ 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

Unplanned

  • ImageSvg

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 and path node modules, achievable via the node polyfill plugin.
  • Update aliases for react-native-reanimated and react-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 availability
new (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 modules
new 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 availability
new (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 modules
new 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.