The Great Graphite Migration, Derek Joins Fat Fighters, and a Leaderboard for Your Dependency Tree

Issue #6112 October 20266 min read
The Great Graphite Migration, Derek Joins Fat Fighters, and a Leaderboard for Your Dependency Tree

Get the next issue first

Subscribe to the weekly newsletter and the 5-day guide covering the story of React Native, where it is now, and what that means for our future.

The Great Graphite Migration

Out on the open plains of React Native, a great change is underway...

William Candillon (@wcandillon) has released React Native Skia 3.0, and he announced it with a stream called Hello Graphite.

The renderer now runs on WebGPU (WebGL is a browser version of OpenGL, which is how web pages draw 3D graphics).

OpenGL, the herd leader for decades, has been driven out of the group.

OpenGL is the old graphics API that phones have relied on to draw every pixel on screen for as long as anyone can remember.

Skia is the 2D graphics engine used by Chrome, Android and Flutter, and it is the engine of choice for some of the mightiest apps on the planet.

React Native Skia brings that engine to React Native, so you can draw shapes, text, images and shaders onto a <Canvas> using ordinary React components.

Observe, here, a small shader, drawn with Skia’s web build (CanvasKit).

const effect = CanvasKit.RuntimeEffect.Make(`
  uniform float2 size;
  uniform float time;
  half4 main(float2 xy) {
    float d = length(xy / size - 0.5);
    float rings = sin(d * 40.0 - time * 4.0) * 0.5 + 0.5;
    half3 orange = half3(0.973, 0.659, 0.137);
    return half4(orange * rings + half3(0.08) * (1.0 - rings), 1.0);
  }`);
paint.setShader(effect.makeShader([W, H, t]));
canvas.drawRect(CanvasKit.XYWHRect(0, 0, W, H), paint);
canvas.drawCircle(W / 2, H / 2, 60 + 30 * Math.sin(t * Math.PI), stroke);

And there they are, rippling rings surrounding a pulsing circle, the wild rings of a wild developer.

1.skia-shader-rings.gif

So what changed in 3.0?

Skia’s backend, the part that converts “draw a circle” into GPU instructions, was once Ganesh, which is not the Hindu god, though it was also based on OpenGL.

Version 3.0 replaces Ganesh with Graphite.

Graphite runs on Dawn, Google’s WebGPU implementation, and Dawn talks to Metal on Apple devices and to Vulkan on Android.

As William points out, Graphite fixes the stability and performance problems of the OpenGL design, which had been struggling on the savanna for years.

But why should a developer care?

Because the first win is multithreading by default. Drawings can be recorded on any thread, and only the finished frame is allowed to reach the UI thread.

The second win is that Skia and React Native WebGPU now share the same GPU device.

A texture, an image held in GPU memory, passes between them without being copied, which is nothing short of a miracle in the GPU world.

The docs include a recipe for drawing a three.js scene inside a Skia canvas.

const device = importDevice(Skia.getNativeDevice());
const target = makeOffscreenTarget(device, width, height);
const renderer = new THREE.WebGPURenderer({
  canvas: target.context.canvas,
  context: target.context,
  device,
});
await renderer.init();
renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
  const previous = image.value;
  // Same GPU device, so the frame is wrapped, not copied
  image.value = Skia.Image.MakeImageFromGPUTexture(target.texture);
  previous?.dispose();
});

The scene arrives as a plain <Image> inside your <Canvas>, ready for blurs, shaders and text to be stacked on top.

Video and camera frames receive the same zero-copy treatment, which is kinder to the battery, and the battery is the most vulnerable creature in the whole ecosystem.

The community did not wait for permission.

Paul Nguyen (@paulNguyenJS) combined Skia v3, React Native WebGPU and Reanimated 4 to turn the Dynamic Island into a UFO.

2.dynamic-island-ufo.gif

Where did @shopify go?

Version 3 is published as react-native-skia, while @shopify/react-native-skia stops at 2.14, like a pride of lions that has walked off the map.

The repository now lives on William’s GitHub, and the drawing API is unchanged, so most apps only need to rename imports and bump platform versions.

OpenGL had a long reign on the savanna...

But the Graphite herd has moved on, and nobody is going back to the water hole for it.

Goodbye, OpenGL.

👉 React Native Skia


3.woman-yelling-cat-works-on-my-pixel-meme.jpg

Derek, Welcome to Fat Fighters

You asked React Native for a View...

And it sent 448 files, 80,000 lines of JavaScript, a Slider, a DatePicker, a Switch, and a Modal your app never used.

We didn’t break up with my exes to be dragging all that extra luggage around.

So come on now, that is a lot to carry for a single View.

Davyd NRB, who goes by @tell_me_mur on X, wrote a Babel plugin called babel-plugin-react-native-direct-imports, which rewrites each react-native import so that importing View pulls in only the View file.

Right, so what does that actually mean?

The plugin changes every react-native import so that it points straight at the file where that component is defined.

Let’s look at the problem first, shall we?

When you write import { View } from 'react-native', you are importing from react-native/index.js, which is a barrel (one file that re-exports everything in the package).

That barrel uses CommonJS, with a lazy require() getter for each public API, so bundlers cannot tree-shake the barrel, which means they cannot drop the parts of React Native that your app never touches.

So one View drags all of React Native into your bundle, PushNotificationIOS included, and you never once asked for PushNotificationIOS.

Now, what does the plugin actually do about it?

At build time, the plugin swaps the barrel import for direct requires of the underlying modules.

import { View, AppRegistry, Systrace } from 'react-native';

// ↓ becomes

const View = require('react-native/Libraries/Components/View/View').default;
const AppRegistry = require('react-native/Libraries/ReactNative/AppRegistry').AppRegistry;
const Systrace = require('react-native/Libraries/Performance/Systrace');

When we ran version 1.1.0 with a three-name import and the 0.87.1 map, the result was:

4.direct-imports-demo.gif

Now for the numbers, and yes, there are numbers.

The author built an iOS app that imports only ReactNativeVersion, then bundled it twice, once without the plugin and once with it.

Without the plugin, the bundle came to 711.93 KB.

With the plugin, the bundle shrank to 10.62 KB.

Here are the two bundle reports side by side.

5.bundle-before-after.jpg

It’s a toy app, so your real savings will be smaller, mostly because you’ll be importing more of React Native on purpose, hopefully.

Right, how do you get on the plan?

Install the plugin as a dev dependency, add it to your babel.config.js with the exact React Native version you have, and turn off the deep-import warnings in the preset.

module.exports = {
  presets: [
    ['module:@react-native/babel-preset', { disableDeepImportWarnings: true }],
  ],
  plugins: [
    process.env.NODE_ENV !== 'test' && [
      'react-native-direct-imports',
      { reactNativeVersion: require('react-native/package.json').version },
    ],
  ].filter(Boolean),
};

Then clear the Metro cache once, with either npx react-native start --reset-cache or npx expo start --clear.

Any catches?

Yes, and the catch is the upgrade.

The plugin writes React Native’s internal file paths into your build, and those paths change between releases.

Each version from 0.70.0 has its own map, and the build fails unless your React Native version matches one of those maps exactly.

So every React Native upgrade now needs two steps. Upgrade React Native, then upgrade babel-plugin-react-native-direct-imports to the matching version.

Skip the second step and the build fails with an error that never mentions the plugin, so you’ll spend an afternoon rereading your config.

Your bundle loses 700 KB, and your upgrade checklist gains a line.

Now, let’s all say it together…

Bye bye, fatty bundle!

👉 babel-plugin-react-native-direct-imports


6.scientist-myself-jsi-cpp-meme.jpg

📦 React Native 0.88.0-rc.4

TypeScript module augmentation can now extend generated props and style types, and an Android Pressable that went half-untappable after being disabled and re-enabled finally gets its fingers back... stable 0.88 is scheduled for October 12, so this might be the final dress rehearsal.

📦 Tamagui 3.0

A new C++ native runtime pushes theme changes straight to native views through Fabric without re-rendering React, plus flat style props, React Strict DOM and Tailwind classes... and the release notes politely suggest you hand the upgrade guide to your coding agent.

📦 React Native Skia 3.4

William Candillon (@wcandillon) moved Skia 3 onto Graphite, so Android now draws with Vulkan instead of OpenGL (minSdk is 26), and 3.4 brings Graphite and Dawn to tvOS... so your living room can finally render shaders nobody asked for.

📦 React Native Screens 4.29.0

iOS Tabs move to Apple's UITab API on iOS 26.1+, picking up a search tab, sidebar placement for iOS 27 and custom tab icons... while Android fragments learn to leave the room without crashing.

📦 React Native Testing Library 14.1

Maciej Jastrzębski (@mdj_dev) shipped userEvent.pullToRefresh(), accessibilityAction(), fireEvent.layout() and a warning when your test pokes a disabled element... plus React Native 0.88 and React 19.3 support.

📦 Unistyles 3.5

A stability release where theme changes finally reach the components they were skipping, reload memory leaks get plugged, and unfreezing a screen no longer blocks JS for whole seconds.

📦 EAS CLI 24.12

eas update:view --json now hands back signed source map and asset map URLs, and 24.11 added browser device login with resumable commands for chat agents... so even your coding agent has to sign in now.

📦 React Native Brownfield 5.3

npm create @callstack/react-native-brownfield scaffolds packaging targets into an existing React Native CLI project, and 5.2 added Expo SDK 58 and AGP 9 support for the apps that only let React Native in through the side door.

📦 munim-maps

Sheehan Munim (@sheehanmunim) put animated 3D cars, rockets, satellites and people on their actual floor onto Apple Maps with Nitro Modules... and it also speaks Google Maps, Mapbox, MapLibre and Cesium.

📦 react-native-inkpad

Mohamed Rayen Sbai (@WeskerTN) built a native ink canvas on androidx.ink and PencilKit with pen, marker, highlighter and eraser, and no WebView... coming from react-native-signature-canvas means changing one import.

📦 skia-diagrams

@ryuhzk renders Mermaid diagrams natively with Skia 3, with themes, animations and pan/zoom across 31 flowchart types... not a WebView in sight.

📦 React Native Directory

@Simek added a /stats page, a list of which packages depend on a package, and AGENTS.md previews... finally a leaderboard for your dependency tree.

7.bye61.gif

Join 1,700+ React Native developers

Subscribe to the weekly newsletter and the 5-day guide covering the story of React Native, where it is now, and what that means for our future.