If you only have a few contrib modules, you don’t have to alias Drupal core’s version: it is possible to provide alternative package info about Drupal 9 incompatible modules instead of what the package’s composer.json actually contains, and Composer will evaluate this replacement info.

⇩ Jump to the steps.

I learned this this trick from Wim Leers, who found it in the Markdown module’s issue queue, especially here: #3103679. And this is actually a documented Composer feature.

Steps to follow

  1. Visit Drupal.org packagist: https://packages.drupal.org/8/packages.json.
  2. Search for the most recent provider url (now it is provider-2020-2), copy its hash and open the corresponding relative URL: https://packages.drupal.org/8/drupal/provider-2020-2$[actual_provider_hash].json
  3. Locate spamspan and with its hash, open https://packages.drupal.org/8/drupal/spamspan$[actual_spamspan_hash].json
  4. Pick the version you want to use, and copy its info. For 1.1.0, this is:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    
    {
      "keywords": "Seeking co-maintainer(s),Content Display",
      "homepage": "http:\/\/drupal.org\/project\/spamspan",
      "version": "1.1.0",
      "version_normalized": "1.1.0.0",
      "license": "GPL-2.0+",
      "authors": [
        {
          "name": "lakka",
          "homepage": "https:\/\/www.drupal.org\/user\/63367"
        },
        {
          "name": "peterx",
          "homepage": "https:\/\/www.drupal.org\/user\/23516"
        },
        {
          "name": "vitalie",
          "homepage": "https:\/\/www.drupal.org\/user\/175134"
        }
      ],
      "support": {
        "source": "https:\/\/git.drupalcode.org\/project\/spamspan"
      },
      "source":{
        "type": "git",
        "url": "https:\/\/git.drupalcode.org\/project\/spamspan.git",
        "reference": "8.x-1.1"
      },
      "dist":{
        "type": "zip",
        "url": "https:\/\/ftp.drupal.org\/files\/projects\/spamspan-8.x-1.1.zip",
        "reference": "8.x-1.1",
        "shasum": "2197256c5b7fb0a7f11c24465b49c1a2e2314c05"
      },
      "type":"drupal-module",
      "uid": "spamspan-3107836",
      "name": "drupal\/spamspan",
      "extra": {
        "drupal": {
          "version": "8.x-1.1",
          "datestamp": "1579718452",
          "security-coverage": {
            "status": "covered",
            "message": "Covered by Drupal\u0027s security advisory policy"
          }
        }
      },
      "description": "The SpamSpan module obfuscates email addresses to help prevent spambots from collecting them. It implements the technique at http:\/\/www.spamspan.com.",
      "require": {
        "drupal\/core": "~8.1"
      }
    }
    
  5. Note the last lines! It requires drupal/core ~8.1: so Drupal 9 cannot fit. Change this version constraint to ~8.1 || ^9. Or to ^9.

  6. You only have to define a new, custom package repository in your Drupal 9 project’s root composer.json. This repository should be inserted before the official Drupal composer repository, with one minor change: keywords should be a list, not a string.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    {
      "name": "drupal/recommended-project", 
      "type": "drupal-project",
      "repositories": [
        {
          "type": "package",
          "package": {
             "keywords": ["Seeking co-maintainer(s)", "Content Display"],
             "homepage": "http:\/\/drupal.org\/project\/spamspan",
             "version": "1.1.0",
             "version_normalized": "1.1.0.0",
             []
          }
        },
        {
          "type": "composer",
          "url": "https://packages.drupal.org/8"
        }
      ],
      []
    }
    

You’re done!