Problem/Motivation

Keeping libraries up to date is a manual process. Need to visit each library and check if there was a new release.

Proposed resolution

Introduce an automated way to check library status and update dependencies.

Workflow

The proposed workflow to update a library is:

yarn outdated
yarn upgrade X Y Z
yarn run rollup # command name subject to change.

Details

The goal is to provide a predictable experience on usage, minification, and debugging.

Overview

Rollup is used because it's easy to use and there is not much configuration necessary. It's also suited to package individual files/libraries whereas webpack is more turned towards handling a single project as a whole. Because Drupal is using it's own library/dependency handling it's easier to have individual files that are aggregated later.

To manage a library there are 4 steps:

  1. yarn add -D <library> Add the package as a dev dependency with yarn, if a package doesn't exists in npm, use the git repository directly (needed for farbtastic)
  2. Configure the rollup file by declaring 2 steps:
    1. Step 1: is to bring the unimified version of the library into the assets/vendor/<library>/<library>.js file. This is done in rollup with a module import, rollup has a way of declaring 'virtual' module, that can declared inline. In short we can do the equivalent of include "file"; with
      {
        input: "entry",
        plugins: [
          // avoid the need for a dedicated file somewhere on disk.
          virtual({ entry: 'import "<library>";' }),
          // Does the actual code 'include'
          resolve(),
        ],
        output: { file: 'assets/vendor/<library>/<library>.js },
      }
        

      Now depending on the library format for umd files a simple import "<library>" is needed, when the library is a proper JS module we need to be more explicit with import Lib from "<library>"; export default Lib; so that the second step works (this is the case for sortable, js-cookies, once).

    2. Step 2: is process (transform is needed + minifiy) the previous file and generate the corresponding map file. The transform is to serve the library in a format appropiate for ES5 browsers (umd or iife). If the file was already in a umd format, there is little to do (just need to change some terser configuration to prevent ending up with additional global variables). If the library was in a JS module format, it is transformed into a iife (not a umd because it's smaller and I haven't seen that format really used for anything on the browser side lately, trivial to change though.). An example of the code transformation happening for JS module to iife.
      // 1. A JS module in a 'api.js' file
      const a = true;
      const api = () => a;
      export default api;  
       
      // 2. contents of 'api.js' transformed into an iife
      var api = function () {
        const a = true;
        const api = () => a;
        
        return api;
      }();
       
      // 3. contents of the 'api.js' iife is minified with terser
      var api=function(){"use strict";return()=>true}();
      //# sourceMappingURL=api.min.js.map
      
        

      in the rollup file it's presented like this:

      {
        input: 'assets/vendor/<library>/<library>.js',
        // minify code
        plugins: [terser()],
        output: {
          file: 'assets/vendor/<library>/<library>.min.js',
          // Generate a sourcemap for debuggability.
          sourcemap: true,
          // Do not copy the unminifed source into the map file, we already have it in the <library>.js file.
          sourcemapExcludeSources: true,
          format: 'iife', // needed only for JS modules
          name: 'api',  // needed only for JS modules
        },
      }
      
      
  3. Declare the library in core.libraries.yml
  4. Profit

Because changing dependencies file names can be disruptive there are some inconsistencies (underscore-min.js instead of underscore.min.js, farbtastic.js having the minified version of the library, etc.).

Minification/Debugging

The reason we don't use the minified version of the library is to make it possible to offer a consistent debugging experience.

Problem

In core map files are not always available and some behave 'strangely'. Because we manually copy/pasted library code in an arbitrary folder that doesn't follow the library package structure some strange things can happen. Let's take jquery-form as an example:

// Extract of the structure for the jquery-form package.
package.json
dist/jquery.form.min.js
dist/jquery.form.min.js.map
src/jquery.form.js

// Folder structure in drupal/core directory
assets/vendor/jquery-form/jquery.form.min.js
assets/vendor/jquery-form/jquery.form.min.js.map

With the map file pointing to ../src/jquery.form.js it gives us an experience where the jquery.form.js file appears to be under assets/vendor/src/jquery.form.js (it is even worst for the popper lib which appears directly under assets/src!). There are also libraries with map files that are not referenced, and not shown in the dev tools, which makes them useless.

Messy folder structure of 'virtual' js dependencies file sources

Solution

When we generate the map file everything behaves as expected:

Cleaned up folder structure of minified and source files of js dependencies

Remaining tasks

  • Feedback
  • Dependency evaluation
  • File size comparison

User interface changes

None

Release notes snippet

Issue fork drupal-3185289

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

nod_ created an issue. See original summary.

nod_’s picture

Status: Active » Needs review
StatusFileSize
new1.89 MB

Will post details later.

nod_’s picture

Title: Use yarn to manage library dependencies » Use package.json and rollup to manage third party JS libraries
andypost’s picture

Issue tags: +Needs change record

Great change!

nod_’s picture

StatusFileSize
new2.1 MB
new6.49 KB

the only thing we can't automate that easily are the jquery ui part and ckeditor. definitely possible to automate but not like this.

attaching the rollup config file by itself for easier review, that's where everything happens.

nod_’s picture

Issue summary: View changes
StatusFileSize
new33.25 KB
new38.99 KB
nod_’s picture

Issue summary: View changes
nod_’s picture

Issue summary: View changes
nod_’s picture

Issue summary: View changes
nod_’s picture

Issue summary: View changes
xjm’s picture

The test fail from Dispatcher is:


1) Drupal\BuildTests\Composer\ComposerValidateTest::testValidateComposer with data set #10 ('/var/www/html/core/node_modul...r.json')
COMMAND: composer validate --strict --no-check-all /var/www/html/core/node_modules/ckeditor4/composer.json
OUTPUT: 
ERROR: /var/www/html/core/node_modules/ckeditor4/composer.json is valid, but with a few warnings
See https://getcomposer.org/doc/04-schema.md for details on the schema
License "GPL-2.0+" is a deprecated SPDX license identifier, use "GPL-2.0-or-later" instead
License "LGPL-2.1+" is a deprecated SPDX license identifier, use "LGPL-2.1-or-later" instead


Failed asserting that 1 matches expected 0.
nod_’s picture

StatusFileSize
new2.1 MB

Seems like a problem that composer looks into the node_modules folder.

In any case, removed the ckeditor4 package, we can't do anything with, it was just so we get a warning that it's outdated. jquery ui and ckeditor will stay manually managed for now.

nod_’s picture

StatusFileSize
new2.1 MB

didn't like the version bump on the js-cookie library. didn't pay too much attention to the lib version between the libraries.yml file and package.json, just want to get feedback on the tools, process, etc. we can sort out the "details" later.

lauriii’s picture

Awesome work! Thank you for working on this @nod_! This would make maintaining our dependency updates so much easier.

Few comments/questions:

  1. The patch adds the dependencies as dev dependencies which I guess makes sense because we don't want those dependencies to be loaded on production. I think it would be great to be able to identify our dev dependencies from production dependencies. Should we at least group them in the dev dependencies?
  2. We should make sure that information on license is kept on both source and minified files
  3. Wondering why once, CKEditor, Modernizr and jQuery Joyride aren't using the new process
  4. Once we agree on a solution, it would be great to write a bit of documentation on how libraries should be added and managed, as well as decisions such as which module format we use. We should also write dependency evaluation on rollup.
nod_’s picture

StatusFileSize
new2.2 MB
  1. We don't control the order of the deps in the package.json file, it's handled by yarn. As soon as you do a yarn add/remove everything is ordered alphabetically so we can't do that. They are dev deps so that's where they should be.
  2. done, had to special case joyride because it's not following conventions
  3. I forgot jquery-once, and joyride. Fixed. We can't manage CKEditor and modernizr as easily because we build a drupal-specific version for each of them. And neither use rollup to do their bundling, we'll need to keep that manually for now. Even adding CKEditor to be warned when a release is out cause problems for us, see #11.
  4. For the doc, the steps are clear enough in the IS? what's missing you think? For the decisions I'll wait until a few more people had a change to review this.
xjm’s picture

Adding some tags and bumping priority; this issue is a big deal.

catch’s picture

This is not really my area at all, but it sounds like an excellent plan!

Leaving ckeditor and modernizr out for now seems fine - it'd be worth opening a follow-up to see if we can somehow integrate them later (maybe as part of ckeditor 5 migration or afterwards).

wim leers’s picture

@lauriii just pointed me here.

WOAHHHHHHHHHHHHHHHHHHHHHHHH! 🤩🥳

Epic work, @nod_! 👏

it'd be worth opening a follow-up to see if we can somehow integrate them later (maybe as part of ckeditor 5 migration or afterwards).

+1

nod_’s picture

StatusFileSize
new1.64 MB
new1.66 MB

Some rollup related version updates.
Had some leftover map files I forgot to delete.
Removed the source from map files to avoid duplicating code all over the place (hence the smaller patch).

No changes in generated minified files after the update.

lauriii’s picture

Re #15.4: I think the issue summary is at least a good start for the documentation. Let's start by moving it to somewhere where it's easier to find in future.

I've not used rollup personally before but I took a look at their documentation and it looks good 👍 Removing the needs FEFM review tag because I don't have concerns on the approach.

nod_’s picture

Issue summary: View changes
GrandmaGlassesRopeMan’s picture

@_nod 👋

This is really nice. I wonder if it makes sense to keep the configuration for Rollup in a meta format? It seems like there is a lot going on `rollup.config.js` with lots of the entries being mostly repeated. Perhaps it could be automatically generated at runtime?

GrandmaGlassesRopeMan’s picture

I also considered if this could be generated automatically by looking through the filesystem, but I'm not sure if the virtual imports could be figured out easily.

nod_’s picture

That was the first thing I did actually. But then you run into issues like backbone or farbtastic file naming is not consistent with the rest of the libraries. Then when the library is umd you need to remove some optimisations in terser, for some libraries you need to reference a specific file, not the default import "<library>"; because the file is already minified in a format we don't want to be using.

At that point there is so much "non default" cases you end up remaking the rollup config and add a mapping layer in between. So I gave that up and went all-in with rollup. It's an internal dependency so we won't have to support a BC layer if we ever go to webpack or something else.

justafish’s picture

lauriii’s picture

StatusFileSize
new1.64 MB
new147.01 KB

Rerolled patch from #15 and created separate patch without the dist file changes to make it easier to review.

nod_’s picture

hiding files, use the merge request to review/update code.

nod_’s picture

xjm’s picture

There are some CSpell-related fails in the latest commits. The file should probably be added to the skipped files.

droplet’s picture

At this point, those new tools for a few committers only. As a developer, I don't want these vendor Dependencies in package.json. It's very bloated.

I suggested creating a new package `drupal-vendor-tools` for these new updates. (It's for CORE committers only, no BC. Break it anytime)

If the upstream packages provided min files, to re-compress it again is a bad idea IMO. Or you should re-run the tests again.

nod_’s picture

I'm not sure about the recompressing either. It should be safe enough but you're correct we should be testing it somehow if we do that. One issue I have with vendor provided minified files it's the map files paths like I showed in the issue summary, unless we add more folders inside the vendors directories it'll keep being messy. If we do that we might break libraries overrides because file path changed. Happy to hear suggestions :)

I think we will end up having a 'core-js' package that includes most of the stuff we need but I don't want to hold up this patch on this. This will make lib updates easier right away. People are not supposed to be using the core/package.json how would that be an issue to have more dependencies in it?

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

pasqualle’s picture

As I know webpack is needed to build CKEditor 5 plugins. I have a feeling we are trying to use 2 different JS module bundlers with Drupal.

related:
Drupal CKEditor plugin: https://git.drupalcode.org/project/ckeditor5/-/blob/1.0.x/README.md#drup...
No rollup for CKEditor: https://github.com/ckeditor/ckeditor5/issues/2140
Webpack5 for CKEditor: https://github.com/ckeditor/ckeditor5/issues/9799

nod_’s picture

Status: Needs review » Needs work

It's on my radar, I want to swap rollup with something else (most likely webpack) because webpack is used in symfony, most popular from the decoupled survey too. Personally I'd like something like esbuild but core is not really the place for tooling experiments.

The current patch has issues, like droplet said in #31 if we minify we should run the test suites all over again for each dependency. So better to avoid it. Most likely whatever we use will end up as a glorified copy/paste tool to extract dist files from packages and placing them where we need it.

I started to look at map files and see if we can force some path changes to fix part of the issue outlined in the US regarding debugging, if that's possible that would solve my main issue with pre-minified files. Ok that was way easier than expected, if we change the file path in the map file, it'll "just work", so yay.

having a dedicated package for core contributors/committers outside of drupal root is also a good idea. When we get back the drupal_js namespace we'll look into that.

nod_’s picture

Status: Needs work » Closed (won't fix)

actually don't need webpack/rollup, simple copy paste works just as well: #3219088: Use package.json to manage third party JS libraries