Web Support
React Native Skia runs in a web browser thanks to CanvasKit, a WebAssembly build of Skia. The WebAssembly file is loaded asynchronously and has a size of 2.9MB gzipped. While this is a substantial file size, you have control over the user experience: you can decide when to load Skia and how the loading experience should be.
We provide direct integrations with Expo and Remotion. Below you will also find the manual installation steps to run the module on any React Native Web projects.
You can use React Native Skia without React Native Web.
Expo
Using React Native Skia on Expo web is reasonably straightforward. We provide a script that will work well with the setup:
bash
$ expo install @shopify/react-native-skia$ yarn setup-skia-web
bash
$ expo install @shopify/react-native-skia$ yarn setup-skia-web
You need to run yarn setup-skia-web
everytime you upgrade @shopify/react-native-skia
.
Consider adding it to your postinstall
script.
Once you are done, you need to pick your strategy to Load Skia.
If you are loading CanvasKit from a CDN, you don't need to run the setup-skia-web
script. If you are not using react-native-reanimated
, webpack will output a warning because React Native Skia refers to Reanimated. We describe how to fix this warning here.
Snack
To use React Native Skia on snack, you will need to use code-splitting method.
Remotion
To use React Native Skia with Remotion, please follow the following installation steps.
Manual Webpack Installation
To run React Native Skia on Web, you need to do three things:
- Make sure that the WebAssembly file is available from the build system.
- Configure the build system to resolve the following two node modules:
fs
andpath
. One way to do it is to use the node polyfill plugin. - If you are not using the
react-native-reanimated
, Webpack will throw a warning since React Native Skia refers to that module.
So following is an example of a Webpack v5 configuration that supports React Native Skia. These three steps can easily be adapted to your build system.
tsx
import fs from "fs";import { sources } from "webpack";import NodePolyfillPlugin from "node-polyfill-webpack-plugin";const newConfiguration = {...currentConfiguration,plugins: [...currentConfiguration.plugins,// 1. Make the wasm file available to the build systemnew (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)) {// Skip emitting the asset again because it's immutablereturn;}compilation.emitAsset("/canvaskit.wasm",new sources.RawSource(await fs.promises.readFile(src)));});});}})(),// 2. Polyfill fs and path module from nodenew NodePolyfillPlugin()],externals: {...currentConfiguration.externals,// 3. Avoid warning if reanimated is not present"react-native-reanimated": "require('react-native-reanimated')","react-native-reanimated/package.json":"require('react-native-reanimated/package.json')",},}
tsx
import fs from "fs";import { sources } from "webpack";import NodePolyfillPlugin from "node-polyfill-webpack-plugin";const newConfiguration = {...currentConfiguration,plugins: [...currentConfiguration.plugins,// 1. Make the wasm file available to the build systemnew (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)) {// Skip emitting the asset again because it's immutablereturn;}compilation.emitAsset("/canvaskit.wasm",new sources.RawSource(await fs.promises.readFile(src)));});});}})(),// 2. Polyfill fs and path module from nodenew NodePolyfillPlugin()],externals: {...currentConfiguration.externals,// 3. Avoid warning if reanimated is not present"react-native-reanimated": "require('react-native-reanimated')","react-native-reanimated/package.json":"require('react-native-reanimated/package.json')",},}
Last, you need to load Skia.
Loading Skia
You need to have Skia fully loaded and initialized before importing the Skia module. There are two ways you can control the way Skia should load:
- With
<WithSkiaWeb />
: using code-splitting to defer loading the components that import Skia. - With
LoadSkiaWeb()
: deferring the root component registration until Skia is loaded.
Using Code-Splitting
We provide a <WithSkiaWeb>
component which leverages code splitting. In the example below, we load Skia before loading the MySkiaComponent
component.
tsx
import React from 'react';import { Text } from "react-native";// Notice the import path `@shopify/react-native-skia/lib/module/web`// This is important only to pull the code responsible for loading Skia.// @ts-expect-errorimport { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";export default function App() {return (<WithSkiaWebgetComponent={() => import("./MySkiaComponent")}fallback={<Text>Loading Skia...</Text>} />}/>);}
tsx
import React from 'react';import { Text } from "react-native";// Notice the import path `@shopify/react-native-skia/lib/module/web`// This is important only to pull the code responsible for loading Skia.// @ts-expect-errorimport { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";export default function App() {return (<WithSkiaWebgetComponent={() => import("./MySkiaComponent")}fallback={<Text>Loading Skia...</Text>} />}/>);}
Using Defered Component Registration
We provide a LoadSkiaWeb()
function you can use to load Skia before starting the React app.
This is the approach we use for Remotion, for instance.
The following is an example of an index.web.js
file.
tsx
// Notice the import path `@shopify/react-native-skia/lib/module/web`// This is important only to pull the code responsible for loading Skia.// @ts-expect-errorimport { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";LoadSkiaWeb().then(async () => {const App = (await import("./src/App")).default;AppRegistry.registerComponent("Example", () => App);});
tsx
// Notice the import path `@shopify/react-native-skia/lib/module/web`// This is important only to pull the code responsible for loading Skia.// @ts-expect-errorimport { 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
Another option is to load CanvasKit from a CDN. This is useful to load Skia Web without having to modify your build steps. It is imperative that you are using the exact same version of CanvasKit as the one used by React Native Skia.
In the example below we load CanvasKit from a CDN using code-splitting.
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")});}
Below is the same example but using defered 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
Below are the React Native Skia APIs which are not yet supported on React Native Web. If you are interested to use one of these features, please make a feature request on GitHub.
Unsupported
PathEffectFactory.MakeSum()
PathEffectFactory.MakeCompose()
PathFactory.MakeFromText()
ShaderFilter
Unplanned
ImageSvg