-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
@emotion/server crash on import in Worker environment #2446
Comments
With #2819 we are allowing ourselves to add a special entrypoint for workers. So we are able to fix this if somebody would be willing to provide a fix for this. That being said I think that @nicksrandall has already been testing this branch with Cloudflare Workers and he said that it's all working. So perhaps there isn't anything to be done here once this lands. On the other hand, this PR doesn't really remove dependency on the packages mentioned in this issue so maybe there is still something to be worked on here. |
It's still not possible to use @nicksrandall, I see #2819 did not add a worker condition to the In our case, it's the only apparent blocker to using Emotion + SSR in a worker environment. |
@aaronadamsCA it might be an oversight - could you tell me how does it crash right now in the worker env? There is a chance that we depend on node-specific stuff there. In such a case, we'd have to provide an alternative implementation for workers |
@Andarist You guessed right!
|
Actually - I wonder if the scope of this issue could be to fix Emotion Server in worker environments, and then the scope of #2781 (blocked by this issue) could be to add any specific worker environment capabilities. My understanding is that What seems odd, then, is that I think this would be well worth fixing soon, as I haven't found a viable workaround. Right now we get a massive FOUC (flash of unstyled content) on landing/reload; we can solve that with a loading indicator, but a big part of the value of SSR workers is a correct first render, and I'd really like to get that working. |
This comment was marked as outdated.
This comment was marked as outdated.
The current implementation shouldn't be reused in workers as-is as conceptually it returns a node stream:
Even the method's name mention this - it's I'm all for providing an alternative implementation of this that would work with web streams. For the time being, we could just have an additional Note that this whole thing is mostly needed only if you are using |
This comment was marked as outdated.
This comment was marked as outdated.
Ah, ye - I can see that. We need to restructure our package a little bit or provide different bundles for specific environments. This might require some changes in our bundling solution (https://github.com/preconstruct/preconstruct/) to make it all work.
It's on the "roadmap". However, lately, I don't get as much free time for OSS as I used to have and I'm not certain when I will be able to actually work on this. |
We probably can't do that because this would taint the whole function - it would have to become |
Good point. |
I'd prefer not to mix ESM with require - it's a can of worms that I prefer closed ;p |
Our eventual solution was to replace
|
@aaronadamsCA thanks! This works like charm! |
@aaronadamsCA Thanks! I'm using Chakra-UI in a Remix project (specifically a Hydrogen Shopify app) and had the same issue. The workaround is very helpful. |
@aaronadamsCA great job. Only thing I could get to work after messing around for hours w/ Miniflare (CloudFlare local dev tool), Chakra-UI, and Remix configurations and examples. This has fixed it for local dev at least. Will let you know if it has problems with deployng. |
For those using Typescript, setting up the paths object to look like below allows for additional dropins for situations like this:
Anything in the root level lib directory can be imported this way. Just make sure to include an index w/ an export. |
For others who might need a little extra guidance and building on the work of @aaronadamsCA
/app/vendor/@emotion/server/index.tsimport type { EmotionCache } from "@emotion/utils";
function createExtractCriticalToChunks(cache: EmotionCache) {
return function (html: string) {
const RGX = new RegExp(`${cache.key}-([a-zA-Z0-9-_]+)`, "gm");
const o: {
html: string;
styles: { key: string; ids: string[]; css: string | boolean }[];
} = { html, styles: [] };
let match;
const ids: { [key: string]: boolean} = {};
while ((match = RGX.exec(html)) !== null) {
if (ids[match[1]] === undefined) {
ids[match[1]] = true;
}
}
const regularCssIds: string[] = [];
let regularCss = "";
Object.keys(cache.inserted).forEach((id) => {
if (
(ids[id] !== undefined ||
cache.registered[`${cache.key}-${id}`] === undefined) &&
cache.inserted[id] !== true
) {
if (cache.registered[`${cache.key}-${id}`]) {
regularCssIds.push(id);
regularCss += cache.inserted[id];
} else {
o.styles.push({
key: `${cache.key}-global`,
ids: [id],
css: cache.inserted[id],
});
}
}
});
o.styles.push({ key: cache.key, ids: regularCssIds, css: regularCss });
return o;
};
}
function generateStyleTag(cssKey: string, ids: string, styles: string | boolean, nonceString: string) {
return `<style data-emotion="${cssKey} ${ids}"${nonceString}>${styles}</style>`;
}
function createConstructStyleTagsFromChunks(cache: EmotionCache, nonceString: string) {
return function (criticalData: ReturnType<ReturnType<typeof createExtractCriticalToChunks>>) {
let styleTagsString = "";
criticalData.styles.forEach((item) => {
styleTagsString += generateStyleTag(
item.key,
item.ids.join(" "),
item.css,
nonceString
);
});
return styleTagsString;
};
}
export function createEmotionServer(cache: EmotionCache) {
if (cache.compat !== true) {
cache.compat = true;
}
const nonceString =
cache.nonce !== undefined ? ` nonce="${cache.nonce}"` : "";
return {
extractCriticalToChunks: createExtractCriticalToChunks(cache),
constructStyleTagsFromChunks: createConstructStyleTagsFromChunks(
cache,
nonceString
),
};
}
/app.entry.server.tsx//... existing imports
import createEmotionCache from '@emotion/cache';
import { createEmotionServer } from '~/vendor/@emotion/server';
export default function handleRequest(
) {
const cache = createEmotionCache({ key: 'css' });
const { extractCriticalToChunks } = createEmotionServer(cache);
//... if using MUI boilerplate you might have MuiRemixServer function
const html = ReactDOMServer.renderToString(/* existing component */);
const { styles } = extractCriticalToChunks(html);
//... injecting styles
//... returns
} |
So from my understanding currently the only workaround is by serving the content as a string with |
I'm wondering the same thing, but I guess not, since the stream won't be complete unless yoyu use the I'm going to play around with following two returns from the stream to see if there is anything that can be done to pipe it
This is to see if we can achieve something simular to the pipe() used here |
I'm open to reviewing a PR implementing a fix for this. I don't have enough context to work on the fix myself at the moment though. |
I see that the solution in the example you mention has access to the stream node module (imports PassThrough from stream and uses @emotion/server which relies on the stream package), while on worker environment is not always available. |
Current behavior:
Looks like
@emotion/server
always assumes a Node.js environment. However, it is now possible to do SSR in Workers environment (such as Cloudflare Workers). This package breaks on import because some dependencies (html-tokenize) try to access Buffer, Stream, and other Node-only APIs.I've tried to get some of the problems fixed: LinusU/buffer-from#13
However,
html-tokenize
have other issues.To reproduce:
Run
import createEmotionServer from '@emotion/server/create-instance'
in a worker or a browser environment.It cannot be reproduced in Codesandbox because looks like they add some Node API polyfills (Buffer, etc).
Expected behavior:
It should not crash on import. More specifically, it would be great if there was an ESM build or a way to import only the necessary functions. For example, importing
createExtractCriticalToChunks
andcreateConstructStyleTagsFromChunks
, and leaving outcreateRenderStylesToStream
, which is the one causing problems and not needed for this use case.Right now I have this code, which works in Node but not in workers.
The text was updated successfully, but these errors were encountered: