-
Notifications
You must be signed in to change notification settings - Fork 31
Fix Dropflow for Vercel serverless environments #29
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
Conversation
|
Hmm seems harmless enough, although it would be good to know what's doing it so we can document it in a comment. Can you grep Also, what about changing this to simply |
|
I can try to do more research. I know jsdom replaces URL, but I haven’t found evidence of it in use (yet).
Funny you should mention that. That’s the very first thing I tried. It did not work, however when I did, |
|
I did some more searching and I believe this issue is related to the use of NextJS and Webpack. There are some past discussions on the topic here: vercel/next.js#55523 EDIT: An interesting detail from those findings is that they said the simpler fixes didn't work in production, as it looks like it does some naive static analysis to extract and package the correct files when building. With that in mind, I think a solution like the one I have here might work, although they mentioned in the blog post at the end that they had to use |
|
Next.js is bundling dropflow for node and modifying the I'm cool with this change, just needs a comment so I don't forget! I also don't think you'd have the same problem in later versions of Node. I tried executing |
|
I am now confident this PR should not be merged as-is 😅 It took me some massaging, but eventually I jumped through all the hoops to get dropflow functioning on Vercel. Most of these issues I attribute to Vercel's platform and NextJS, and not dropflow. Although perhaps one thing could've been made clearer here. To summarize the changes I had to make:
After all of that, I was successfully able to use dropflow in Vercel. Here is the patch I made to dropflow for context: Expand to see changesDropflow Changesdiff --git a/dist/src/api-wasm-locator-node.js b/dist/src/api-wasm-locator-node.js
index 2212d683cc57c3282034e76f36524c840ec438b9..40ce2944fb8d749e2e736753eba1f2f3b8c4de6a 100644
--- a/dist/src/api-wasm-locator-node.js
+++ b/dist/src/api-wasm-locator-node.js
@@ -1,7 +1,14 @@
+import process from 'node:process';
import fs from 'node:fs';
+import path from 'node:path';
export const locatorFunction = {
value: async () => {
- return fs.readFileSync(new URL('../dropflow.wasm', import.meta.url));
+ return fs.readFileSync(
+ path.join(
+ process.cwd(),
+ '../../node_modules/dropflow/dist/dropflow.wasm'
+ )
+ );
}
};
export default function setBundleLocator(fn) {
diff --git a/dist/src/backend-node.js b/dist/src/backend-node.js
index c58bf7d5ea715d98b21162375fc2fd8e1fe92b2c..c4d6d24782fa64645ee2cd73aa5ccc4aa3186190 100644
--- a/dist/src/backend-node.js
+++ b/dist/src/backend-node.js
@@ -1,15 +1,23 @@
-import { fileURLToPath } from 'url';
+import { fileURLToPath } from "node:url";
const alreadyRegistered = new Set();
+
try {
- var canvas = await import('canvas');
-}
-catch (e) {
-}
+ var canvas = await import("canvas");
+} catch (e) {}
+try {
+ var napiRsCanvas = await import("@napi-rs/canvas");
+} catch (e) {}
+
export function registerPaintFont(match, buffer, url) {
- const filename = fileURLToPath(url);
- if (canvas?.registerFont && !alreadyRegistered.has(filename)) {
- const descriptor = match.toCssDescriptor();
- canvas.registerFont(filename, descriptor);
- alreadyRegistered.add(filename);
+ const filename = fileURLToPath(url);
+ if (!alreadyRegistered.has(filename)) {
+ const descriptor = match.toCssDescriptor();
+ if (canvas?.registerFont) {
+ canvas.registerFont(filename, descriptor);
+ }
+ if (napiRsCanvas?.GlobalFonts) {
+ napiRsCanvas.GlobalFonts.registerFromPath(filename, descriptor.family);
}
+ alreadyRegistered.add(filename);
+ }
}I think the Napi changes should be relatively safe to make, although I am left wondering if there is a much better way to solve these problems. The
Yea, my environments are all running on NodeJS 22, and one of the first things I tested was NodeJS 20, which gave me the same error in the original post above. It would be great if NodeJS 23 made these problems completely disappear. One thing I have been considering to solve this is to do something like this: export const locatorFunction = {
value: async () => {
if (process.env.NEXT_RUNTIME) {
return fs.readFileSync(
path.join(
process.cwd(),
'../../node_modules/dropflow/dist/dropflow.wasm'
)
);
}
// existing code goes here
}
};But I think we would still need a way to find the root node modules folder to avoid the monorepo issue of EDIT: I will experiment further with https://nextjs.org/docs/pages/api-reference/config/next-config-js/output and see if I can get this working without project specific modifications to dropflow. If I can't get import.meta.url working, I'll probably use the NEXT_RUNTIME env with process.cwd. |
|
I'm actually working on The concept of "environments" is to have flexible support for different canvases and ways to retrieve files. So when I'm done, you'll be able to write your own locator that uses I guess you would still need to make I'll post an update when I'm done! |
f33e526 to
7bf7610
Compare
|
That's awesome to hear, the changes to webpack for canvas and napi were minimal, so that's a fair trade off. The API change you're proposing is exactly what I was hoping for, looking forward to being able to hook into dropflow 🎉 For now I am unblocked using the solution I shared above, haven't found a way to workaround Vercel's limitations yet regarding |
|
@vinnymac I have documentation for environments now, which includes some samples for |
|
Thanks for letting me know, I will try to integrate this update into my work and see how it goes. Although at a glance reviewing the documentation it looks like exactly what I would need 👍🏼 |
|
It appears to work well for my needs. Only thing I am a little confused about is when I await This works: flow.fonts.add(font1).add(font2);
await Promise.all([font1.load(), font2.load()]);This does not: flow.fonts.add(font1).add(font2);
font1.load(); // adding await here and to the line below also does not help
font2.load();
await flow.fonts.ready;But other than that, this worked without a hitch 👍🏼 EDIT: I think the issue I described above may just be due to the fact that I'm using an ArrayBuffer, which is loaded immediately. |
|
Hmm are there any other steps to it? I tried writing a test for that but it passes, and so does every combination I can think of: it('resolves FontFaceSet.ready when buffers are sync loaded', async function () {
const f1 = new FontFace('f1', arrayBuffer());
const f2 = new FontFace('f2', arrayBuffer());
f1.load();
f2.load();
await flow.fonts.ready;
});But yeah, the |
|
Not quite sure why that happens in my case. But removing load and ready work just fine. I'll go ahead and close this. Appreciate your help, as the solution is now very robust and flexible. The only modifications I have to make to get dropflow working in Vercel are to externalize canvas libs and configure output tracing for the wasm file, which is much cleaner ✨ |
|
Great! Let me know if you run into anything else. |
Summary
In a local development environment I am working in, I experience an issue where one of the developer dependencies replaces the global URL. I am not sure who is specifically to blame there, but I did find that just changing the environment-node script to stop using URL allows me to use dropflow without any issues.
I don't believe this will cause any problems, so I figured I'd share my solution. If you'd rather not include this change, I can always just keep this as an internal patch on top of dropflow in my project.
The stack trace for the error looks like:
Let me know your thoughts 🙇🏼