This post features a really simple skia graphics but details instructions for installing react native skia on the expo go and expo web. Guide builds on top of the expo router tabs demo app and saves you time and frustration.
Intros, what’s what?
Skia is a powerful open source graphics engine and library. It’s widely used in systems like Google Chrome, Android, iOS and many others.
Expo is a platform for building native apps for Android, iOS and the web with javascript/typescript and React Native. When devs go with expo instead of react native, they usually choose managed workflow. Managed workflow means most or all of the native code is hidden from you, you don’t need to worry about that, just type your React code and you’re done.
Expo Go is a mobile client app which lets you open and test projects while developing them. When you make changes to your code, those changes are immediately visible in the Expo Go app on your mobile device without needing a full reload of the app, preserving the current state. I believe other word for this feature is hot reload.
Expo Router is a file-based router for React Native and web applications. It allows you to manage navigation between screens in your app. Architecture of the routes and layout is very similar, if not the same as next.js app router.
Requirements
Configured Android SDK on your machine. Take a look at windowsor linuxinstructions if you need step by step guide (also recorded on Youtube).
Initialize the project
You can skip this step if you already have setup an expo project. I’m using the expo tabs template which includes a basic tab navigator setup, providing a structured layout and navigation system right from the start.
npx create-expo-app@latest --template tabs@50
Install Skia
When installing dependencies on expo, usually you would install with npx expo install
instead of npm
or yarn
. Reason for this approach is compatibility and automatic configuration. When issuing expo install, expo will pull the version which is known to work correctly with the version of sdk you are using. Also, expo will sometimes modify the native code or configuration files accordingly.
In an ideal world, right?
From my experience, this is not always the case. In the case of skia as authors mention:
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
.https://shopify.github.io/react-native-skia/docs/getting-started/web/#expo
Version of sdk 50 will install 0.1.221 which does not work without an additional patch.
My solution is to use a newer version and ignore expo-doctor warnings.
npm i --save @shopify/react-native-skia@0.1.240
npx expo install react-native-reanimated
Skia depends on react-native-reanimated
, which is not installed without explicitly declaring it in package.json
.
Configure Skia
React native skia does not work without canvaskit.wasm, which needs to be stored somewhere in the expo app. Since I’m using the expo router setup, static files can be stored in public directory.
Adding ”postinstall”: “npx setup-skia-web public”
makes the wasm file accessible.
There’s one fun error that occurs after the setup. I’ve lost an hour trying to debug and polyfill:
The package at “node_modules\canvaskit-wasm\bin\full\canvaskit.js” attempted to import the Node standard library module “fs”.
It failed because the native React runtime does not include the Node standard library.https://docs.expo.dev/workflow/using-libraries/#using-third-party-libraries
Self explanatory message, react native needs a workaround for standard node library like fs and path.
React Native does not include Node.js’s standard library. Usual solution would be to polyfill the standard library. But the real culprit here is the canvaskit-wasm
module setup and not the lack of the standard Node library. Thanks to kimchouard, there’s a workaround for this issue (just copying the piece here to see what’s going on):
/**
* Original code by https://github.com/kimchouard/rn-skia-metro-web-example/blob/main/path-fs-canvaskit-postinstall.js
*/
const fs = require('fs');
const path = require('path');
const packageJsonPath = path.join(__dirname, 'node_modules', 'canvaskit-wasm', 'package.json');
const packageJson = require(packageJsonPath);
packageJson.browser = {
fs: false,
path: false,
os: false,
};
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
canvaskit-wasm has no place using fs in native and web since we point to wasm file and also run npx setup-skia-web public
as part of postinstall.
Create or modify metro.config.js and add the following:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname, {
isCSSEnabled: true,
});
config.resolver.assetExts.push('wasm');
config.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
});
module.exports = config;
This code customizes the Metro bundler configuration by adding support for WebAssembly (.wasm) files as assets. It also improves performance by inlining require()
calls and disables experimental import features. I don’t know why it doesn’t work without custom transform rules.
Making it work for the Web
This is where LoadSkiaWeb
comes handy. I’d recommend adding this code in the root _layout if you need it accessible in your entire app.
_layout.ts
useEffect(() => {
if (Platform.OS === 'web') {
LoadSkiaWeb({ locateFile: () => '/canvaskit.wasm' })
.then(() => {
setLoaded(true);
})
.catch((err) => console.error(err));
} else {
setLoaded(true);
}
}, []);
Conclusion
Sometimes expo acts fishy for no apparent reason (like the Cannot read properties of undefined (reading ‘Matrix’)
). Then the only solution is to clear the bundler cache and reinstall/rebuild the app. Anyway, all source code to install and configure react native skia on web and on native is available at my github repo. Also I recorded a YT tutorial to make it easier to follow the setup.
Top comments (0)