Extending patternslib/mockup in Plone 6 classic

I wanted to share my experience working with patternslib/mockup and extending it for Plone 6 classic. I try to lay out my understanding of how a bunch of different code packages work in this post, but I was not a part of their development, so any corrections are welcome.

For me there was a lot of confusion on the difference between patternslib and mockup and other similarly named packages, and what was in Plone and what wasn't, and I needed to understand all of that before knowing how I was supposed to extend it. Ultimately, my use case was to be able to use pat-inject and pat-autosubmit to make a form that would automatically load ajax content from a form when changed. Just testing blindly in Plone 6, I saw that pat-inject worked and pat-autosubmit did not - and no clear understanding why.

My understanding is that patternslib https://patternslib.com/ is the fundamental js toolkit involved here. It's used by Plone but it is not Plone-specific itself. It essentially has a core with a registry system and then a collection of individual patterns. Mockup is the Plone implementation of the patternslib registry core and, importantly, only some of its patterns. In addition it registers several new patterns for Plone. In Plone 4 there was an implementation as plone.mockup but this is antiquated in favor of mockup. Sometime in Plone 5 (5.2 I think? or was this not until 6?), the distribution resources created by mockup were moved to plone.staticresources. So although it's used by Plone you won't actually see it in your buildout/venv anymore.

Tthe patternslib docs and demos were great, but in order to get an understanding of which patterns from it were actually in Plone already and how to add the ones that weren't, I needed to understand webpack.

I am certainly no js expert but my understanding of js dependency management is that historically it's been a mess. Prior to ES6, you could use tools like requirejs to set up defines and requires for dependencies. I remember taking a training with Nathan van Gheem where he said if you learn one thing from the training it should be when to use define and when to use require. But I also learned that I hate requirejs. Fortunately, by the time of Plone 6 support of old browsers has progressed to the point where they could use ES6 exclusively with import statements. To convert everything to Es6, Plone packages generally use the webpack tool. This does mean that a lot of old guides that show how to do things in requirejs are not relevant to Plone 6.

I am new to webpack, but getting started guide gave me the understanding where I could then look at mockup as well as packages like plone.formwidget.geolocation and collective.z3cforms.datagridfield and see how they worked. The important files are package.json and webpack.config.js. package.json is really just a general npm project config file but it's used by webpack. The webpack.config.js is custom config code to tell webpack what exactly it should do. Importantly it defines an entry point to some js where you actually see some import statements.

Following that import path on mockup led to patterns.js which added a lot of clarity to me. I can see now that although patternslib is a requirement for mockup, the entirety of that package isn't actually included. It's really just the registry core and only a handful of the patterns. And importantly I can see here that inject.js is included and not auto-submit.js, so that explains what I saw in testing originally. Now I just need to know to register auto-submit.js.

For this I was guided to look at plone.formwidgets.geolocation and how it registers additional patterns with the patternslib core. Here they are registering a pattern from https://github.com/Patternslib/pat-leaflet whereas I need one from the base patternslib project, but that doesn't really matter - the process to add them is the same. My webpack.config.js looks almost exactly the same as this with just minor changes to entry point locations/etc. I should point out here that an important part of the registration is module federation. This handles shared dependencies like patternslib core and jquery and to be honest it's still mostly black magic to me how exactly it does that.

At this point I just needed to run webpack and set the dist output location to a directory I have set up as a Plone browser directory. Then I put that location in the registry/bundles.xml and install. A quick test shows it working! (One final gotcha, it's pat-autosubmit but the file is auto-submit.js with the hyphen, ugh)

            <form action="@@search#search-results-wrapper" data-pat-inject="target: #search-results" class="pat-inject pat-autosubmit">
                <input type="text" name="SearchableText" />

            </form>
            <div id="search-results">
            </div>

Thank you to @thet, @yurj, @petschki, @MrTango for guidance in discord. I'd like to contribute some documentation to the Plone 6 classic ui - I don't think there's anything about patternslib or how to do any of this in there. And while it's by no means the only js toolkit you can put into Plone, it's certainly a good one and actually pretty easy to integrate other patterns or add new ones.

One thing I am not clear on is how to have webpack output to e.g. static, while also letting that directory contain other resources that need to be browser accessible. For instance, I have some less/css files that are transpiled by another tool and also some images. When I run webpack it actually replaces the entirety of the contents of static. Can I configure webpack to not do that, or should I be processing all of those files through webpack somehow too?

8 Likes

Nice write up !

I think this is not possible due to webpacks chunking method which automatically splits the bundled JS code into chunks. Each time you change some dependencies, the chunks get recompiled partly or fully with different uuids and the old ones are removed. Nothing you do have to care about in reality, but it assumes that the output path can be wiped completely by webpack.

I usually solve this with a subfolder structure within my package's ++resource++ directory... for example ++resource++my.package/bundle folder which is the output for webpack and a ++resource++my.package/static folder for custom JS code which can be registered to RR separately.

1 Like

Ah, that makes sense. I did see that webpack had some config options related to clearing but I understand now we do want it to be able to wipe a directory out completely.

Thanks @Esoth , I learned a lot from your post.

1 Like