-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to extract StyleX CSS for Expo web? #34
Comments
We would need a proper Metro plugin to handle this correctly. I'm not sure what that process looks like, but in the meantime, I'm working on two stop-gap solutions:
|
@motiz88 Do you have any insight into how best to create a Metro plugin for StyleX? (Similar to the ones we have for other bundlers) |
Not too sure if this is the right approach but here's my attempt at css extraction for Expo Web if it helps. |
Thanks for giving this a go and including a write up! I might be interested in incorporating some version of this into the example app if it would help Expo provide something for StyleX out of the box soon. But we haven't had any input from Expo yet. Since Expo Web isn't really used for web-only apps, I might add a something else (like a Next app) so developers can see RSD working in a production setup with tooling they already know. I want to be able to highlight that RSD is also good option for web-only experiences that want to adopt StyleX and want to avoid exposing deprecated/legacy DOM APIs to their development team. |
Yes, please feel free to use my code as a starting point if needed. I wrote it from scratch but it's worth mentioning some ideas came from this stylex webpack plugin. I understand the point about having a Next.js example as well. Having one Expo example to showcase universal usage and another Next.js example for web usage should satisfy most users I believe. |
While this maybe true I think a platform agnostic package tied away from Expo is what anyone with any serious grease needs. |
If you're not using Expo/Metro (on web) then you don't need a Metro plugin for StyleX. RSD just works on bare React Native apps - that's what we use internally. The only people seriously using Metro on web today are Expo users. If anyone is trying to use RSD with Expo to target web, you might have more luck opening issues against Expo. Or switch Expo over to using webpack and integrate the StyleX webpack plugin |
I wrote something very crude based on the official Webpack plugin. Sharing here just in case it helps somebody. Create /* eslint-disable @typescript-eslint/no-var-requires */
const upstreamTransformer = require('@expo/metro-config/babel-transformer');
const svgTransformer = require('react-native-svg-transformer');
const path = require('path');
const babel = require('@babel/core');
const stylexBabelPlugin = require('@stylexjs/babel-plugin');
const fs = require('fs');
const importSources = [
'@stylexjs/stylex',
'react-strict-dom',
// This should be identical to the `importSources` you provide to StyleX plugin in your Babel config
];
// Whether you're okay with layers or not (falls back to `:not(#\#)` when false)
const useCSSLayers = true;
const stylexRules = {};
const getStyleXRules = () => {
if (Object.keys(stylexRules).length === 0) {
return null;
}
const allRules = Object.keys(stylexRules)
.map((filename) => stylexRules[filename])
.flat();
return stylexBabelPlugin.processStylexRules(allRules, useCSSLayers);
};
/**
* It's a very crude implementation for creating a CSS file out of StyleX styles on the build time.
* It has to be run on a single worker with this implementation to prevent overwriting the CSS file.
* Should only run for the release builds.
*
* Implemented based on the official Webpack plugin.
* See: https://github.com/facebook/stylex/blob/main/packages/webpack-plugin/src/index.js
*/
module.exports.transform = async ({ src, filename, options }) => {
if (importSources.some((source) => src.includes(source))) {
const res = upstreamTransformer.transform({ src, filename, options });
const { metadata } = res;
if (metadata.stylex != null && metadata.stylex.length > 0) {
stylexRules[filename] = metadata.stylex;
const style = getStyleXRules();
fs.writeFileSync('dist/stylex.css', style);
}
return res;
}
// just replace `svgTransformer` with `upstreamTransformer` if you don't need it.
return svgTransformer.transform({ src, filename, options });
}; Create an empty Add this to <link rel="preload" href="./stylex.css" as="style" />
<link rel="stylesheet" href="./stylex.css" /> Change your // Determine you're exporting somehow. Reading an env var should do it.
// I'm sure Expo is setting something automatically but I didn't bother looking into it.
const isExporting = process.env.EXPORTING;
const config = {
// ...
transformer: {
// You don't want stylex transformer to work on dev at all
babelTransformerPath: isExporting
? require.resolve('./metro-stylex-transformer')
: require.resolve('react-native-svg-transformer'),
},
// ...
} Use it: We're doing something outside Expo's asset resolution/handling here. We directly load the CSS inside the html so Expo doesn't rename it to something obscure etc. We created an empty CSS file which expo will copy to the The downside of this approach is that, it works on a single file synchronously. That means we can't allow any parallelism, every instance will override the file otherwise. That's why you have to set I had very limited time (a couple of hours) and this is what I came up with. I'll probably improve it whenever I find some time but ideally, this should be a part of Expo's asset process. EDIT: Realized that I somehow missed the other implementation shared above 😄 |
It looks like this will overwrite the CSS file for every JS file that is transformed. This will work as the styles are style being collected, but ideally it should be done once. A slightly better hack might be to use a throttled function that only runs once JS files are done transforming. |
I would strongly recommend against using a side-effectful Metro transformer (it breaks pretty fundamental assumptions in Metro re: caching), and I'd ask that we don't recommend such an approach officially. There isn't a plugin system as such in Metro, but there are plenty of existing extension points, and we can always refine them or expose new ones as needed to support StyleX / RSD in Metro projects. Happy to give pointers to anyone interested in hacking on this. (cc @robhogan for visibility) |
Thanks! It's good to note that this is a temporary hack and I explicitly pass |
I’ve developed another integration of RSD/StyleX with Expo Web using a custom PostCSS plugin. It supports Expo Web and works with upcoming universal RSC and Expo DOM Components features as well. I believe Babel + PostCSS Plugin is an overall more robust approach because it has built-in file dependency tracking for watch mode and does not have Metro cache invalidation issues. PostCSS is already deeply integrated with frameworks like Expo to allow for Fast Refresh. (It would be better if we have a proper official integration that doesn't rely on separate process though). I published a working PoC with explanation in the README here: Feedback is appreciated! |
Describe the feature request
The RSD example app uses Expo to target Android, iOS, and Web but does not support extracting styles to an external CSS file for web. For the purposes of the demo, we use Babel to process the styles on web but currently have to leave the runtime injection functions in the bundle.
The next step for improving Expo Web performance with StyleX would be to have a proper Metro integration that automatically extracts CSS and inserts the relevant
<link>
tag in the app shell. This could also help Expo become one of the easiest ways for React developers to replicate Meta's production setup on web, as other frameworks currently require developers to manually integrate and configure the relevant StyleX bundler plugin rather than working out-of-the-box.(Opinionated authoring experiences, like Tailwind syntax for web and native, could then also be built on top of StyleX to avoid the need for Tailwind tooling and provide the robust style-merging mechanics of StyleX)
cc @EvanBacon @nmn
The text was updated successfully, but these errors were encountered: