GoogleChromeLabs / houdini.how

A community-driven gathering place for CSS Houdini worklets and resources.
https://houdini.how
Apache License 2.0
152 stars 39 forks source link

Load worklets using CSS.paintWorklet.addModule #138

Closed bramus closed 3 years ago

bramus commented 3 years ago

I'm currently trying to add css-houdin-voronoi to the list of demo worklets (see this separate branch). While the addition of the JSON is straightforward, I am however greeted with an "Uncaught ReferenceError: Voronoi is not defined" error.

I took me some time to figure out but found that this is caused by the way how worklets are loaded, in combination with how css-houdini-voronoi is structured.

  1. The worklets get injected as scripts tags: https://github.com/GoogleChromeLabs/houdini.how/blob/main/public/components/Demo/index.js#L56
  2. As those scripts call registerPaint (normally fromo CSS.paintWorklet), this method is being manually implemented in the window scope: https://github.com/GoogleChromeLabs/houdini.how/blob/main/public/index.js#L19
    • css-houdini-voronoi uses an extra library which the painter uses. This extra function/class is placed adjacent to the registerPaint call found inside the worklet.js file.

As here on houdini.how the window.registerPaint is being called from within the script loaded worklet.js, and that registerPaint calling CSS.paintWorklet.addModule() with only the painter, the painter no longer has access to the extra library which css-houdini-voronoi requires.

This PR fixes that behavior by simply loading the paint worklets using CSS.paintWorklet.addModule. As that's also a promise, nothing else in the logic of the Demo component needs to change.

⚠️ However, if merging this PR would be considered: I've noticed the leaf demo breaks on this change. It spits out a "Uncaught ReferenceError: CSS is not defined". That's because that worklet checks for registerPaint being available in CSS before calling it. I don't think this is customary for a worklet to do so? Furthermore that demo calls window.CSS.paintWorklet.addModule(workletBlob); itself, which also doesn't seem customary. All other paint worklet demos — including the css-houdin-voronoi one which I want to add — work fine. Fixed with an extra commit.

ConradSollitt commented 3 years ago

For reference, in case it helps with a decision on the issue.

The reason for the leaf demo not working after the code switch is that it internally the leaf demo is building a URL blob and calling window.CSS.paintWorklet.addModule with the blob.

https://unpkg.com/houdini-leaf@1.0.6/houdini-leaf.js

const worklet = `
  ...
  class LeafPainter {}
  registerPaint('leaf', LeafPainter);
  ...
`;
const workletBlob = URL.createObjectURL(new Blob([worklet], { type: 'application/javascript' }));
window.CSS.paintWorklet.addModule(workletBlob);

It appears the reason for the design is so the end user can just include a script tag to load the worklet rather than call the worklet API:

https://www.npmjs.com/package/houdini-leaf

<script src="https://unpkg.com/houdini-leaf"></script>

I did some testing locally with both css-houdin-voronoi and houdini-leaf and found I could get them both working (and all other demos) using the proposed pull request and pointing the Leaf demo from the build version to the worklet version:

File: https://github.com/GoogleChromeLabs/houdini.how/blob/main/worklets/houdini-leaf.json Before: "cdnUrl": "https://unpkg.com/houdini-leaf", After: "cdnUrl": "https://unpkg.com/houdini-leaf@1.0.6/worklet.js",

The build script for houdini-leaf is here: https://github.com/zhenyanghua/houdini/blob/master/leaf/build.mjs

Additionally I tried this on the original coded script.type = 'module' but that broke both demos.

bramus commented 3 years ago

@ConradSollitt Upon inspecting the houdini-leaf package and your information I've updated this PR so that the leaf worklet itself will be loaded from https://unpkg.com/houdini-leaf/worklet.js

una commented 3 years ago

Hi, thanks for this PR! Would it be possible to include all dependancies in the file? For example, how iamvdo's conic gradient worklet "injects" the TinyColor library?

bramus commented 3 years ago

Hi Una,

All dependencies for css-houdini-voronoi are included in its file, similar to how iamvdo's conic-gradient script does it: they are simply inlined. Therefore his code is also affected by the bug this PR fixes: when loading his paint worklet, you'll get back a "Uncaught ReferenceError: ConicalGradient is not defined" — a message similar to the one seen in css-houdini-voronoi.

You can test this yourself by including this worklet JSON definition:

{
  "workletName": "conic-gradient",
  "packageName": "conic-gradient",
  "cdnUrl": "https://codepen.io/bramus/pen/3545348be3100ddc57bb4716cfaa8d52.js",
  "tags": ["paint"],
  "usage": {
    "background": "paint(conic-gradient)"
  },
  "customProps": {
  },
  "author": {
      "name": "Vincent De Oliveira",
      "url": "https://twitter.com/iamvdo",
      "image": "/assets/images/iamvdo.jpg"
  }
}

(Note: I'm using a slightly altered version of his worklet here hosted on CodePen. The changed version includes a check for window being undefined or not + a fallback to default colors to use)


The problem is that only the worklet class from his paint.js gets loaded. All the rest of his code — e.g. the dependencies — gets stripped due to the loading mechanism currently in use. You can see this in this screenshot from DevTools: only the registerPaint call is there, all the rest is gone.

Screenshot 2021-01-08 at 21 11 18

👉 This PR fixes this behavior, making sure the whole code from the worklet is added as a module, by using a simple CSS.paintWorklet.addModule(url).

When including the JSON above with the branch from this PR, iamvdo's script (and my css-houdini-voronoi along with that) work as expected 🎉

Screenshot 2021-01-08 at 21 50 07
developit commented 3 years ago

Hi all!

One suggestion I have: it would be good to retain the old behavior under a property (like cdnUrlType:'script') on each Worklet's configuration. This would allow worklets available only as main-thread JS with addModule(blob) to still be used. The default can definitely be to load these URLs via addModule() - the script loading code I had in here was very much a hack.

I would suggest something like:

[
  {
    "workletName": "conic-gradient",
    "packageName": "conic-gradient",
#   // This URL is assumed to be a Paint Worklet module. It gets loaded via CSS.paintWorklet.addModule():
!   "cdnUrl": "https://codepen.io/bramus/pen/3545348be3100ddc57bb4716cfaa8d52.js",
    "tags": ["paint"] // etc
  },
  {
    "workletName": "other",
    "packageName": "other",
+   // If this is set to "script", the URL will be loaded via <script src> on the main thread:
+   "cdnUrlType": "script",
!   "cdnUrl": "https://unpkg.com/foo-worklet/index.js",
#   // ^ this file is one that would be calling CSS.registerProperty()/CSS.paintWorklet.addModule()
    "tags": ["paint"] // etc
  }
]

Also: with the above in place, this stuff should be safe to remove.

bramus commented 3 years ago

@developit That would allow one to fix it indeed. If I interpret correct, the default would then be "worklet" as a value for cdnUrlType?

developit commented 3 years ago

@bramus yup, exactly!

bramus commented 3 years ago

@developit I've adjusted this PR to reflect your proposed changes:

una commented 3 years ago

Awesome, thanks all :) I like this solution!