Composer: Migrate an existing D8 site into a Composer-managed build

Migrating an existing D8 site into a composer-managed build requires building a composer.json file that includes all of the required packages for your existing site. Below is a step-by-step process for migrating an existing D8 site.

The structure of your repo should shift as follows:

Repo Structure

The key differences are the new top-level composer.json and composer.lock files, and the top-level location of the vendor directory.

Pre-Clean:

It will be easier to compare changes made by composer if you start with a site generally structured the way you want the site to end up.

Contrib Modules: Drupal best practice is to place contributed modules in docroot/modules/contrib. If your modules are not currently in that path, you should consider moving all contributed modules to docroot/modules/contrib/.

Custom Modules: Drupal best practice is to place custom modules in docroot/modules/custom. If your modules are not currently in that path, you should consider moving all contrib modules to docroot/modules/custom/.Themes: Drupal best practice is to reflect the same custom/contrib structure for themes as for modules.

Each one of these changes may require you to run drush cr to ensure your drupal site knows the new module/theme location. It is best that you do this first to reduce potential complications in migrating to composer.

Custom Code: If you have made any modifications to drupal core files or contributed module files, you must create patches for them and store them in a top level patches/ directory. Composer update will overwrite all core and contributed module files, but it is able to also apply patches after an update is applied. Creating these patches now will save you trouble later.

If you have modified the Drupal composer.json file located at docroot/composer.json, you must note these modifications and prepare to add them to the top-level composer.json file. This file will be overwritten, and patching it is unnecessary as you can simply add your requirements to the top-level composer.json file.

All of these changes should be committed, tagged, and, if possible, merged to master. This is your new, permanent, file structure.

Building the composer.json file

Start your work by creating a development branch. Do NOT do this on master. This branch should contain all code required to work on production.

Grab a working template:

https://github.com/acquia/acquia-ra-composer/blob/master/composer.json is a great template to use. Save the file at the top level of your repository: `repo/composer.json`. The instructions below presume you are using this composer.json file.

Building the require section:

In order to simplify the migration, it is strongly recommended that you install the EXACT version of Drupal and all contributed modules already running on your site. This allows you to test if the composer build is working without dealing with core or module updates. You may do this by ‘pinning’ your module to a specific version (described below). Using this method requires the additional final step of ‘unpinning’ all the modules once testing is complete.

To work with drupal, your composer.json file must require the following two packages:

    "require": {
        "drupal-composer/drupal-scaffold": "^2.0.0",
        "composer/installers": "^1.0"
    },

Drupal Core: Require drupal core by changing "drupal/core": "^8.3", to your current drupal version, i.e., "drupal/core": "8.3.1".

Specifying the entire version is called “pinning” a package. Pinning has the advantage of ensuring that this and only this version is installed, but it also prevents using the composer update drupal/core --with-dependencies command for updates. This should be ‘unpinned’ once testing of the composer migration is complete.

Contributed Modules: All modules required to run your site on production should be listed in the require section. Start with contributed modules.

Generate a list of all enabled contributed modules, which you can do with drush:

drush pml --status=Enabled --no-core

If you are running multisites, make sure you check all multisites so your list is complete. You should see something like this:

Package           Name                              Type    Version
 Administration    Admin Toolbar (admin_toolbar)   Module  8.x-1.19
 Chaos tool suite  Chaos tools (ctools)            Module  8.x-3.0-beta2   
 Other             Pathauto (pathauto)             Module  8.x-1.0-rc1
 Other             Token (token)                   Module  8.x-1.0-rc1
 Other             MTM Foundation (mtm_foundation) Theme
 Other             ZURB Foundation 6 (zurb_foundation)Theme

Each module and theme should be added to the require section. Using the example above, your require section should look something like this:

    "require": {
        "drupal-composer/drupal-scaffold": "^2.0.0",
        "drupal/core": "8.3.1",
        "drupal/admin_toolbar": "1.1.9",
        "drupal/ctools": "3.0.0-beta2",
        "drupal/pathauto": "1.0.0-rc1",
        "drupal/token": "1.0.0-rc1",
        "drupal/zurb_foundation": "6.x-dev",
        "composer/installers": "^1.0"
    },

Module Paths: requiring composer/installers allows you to specify where composer should download packages of a particular type in the extras section:

   "extra": {
        "installer-paths": {
            "docroot/core": ["type:drupal-core"],
            "docroot/modules/contrib/{$name}": ["type:drupal-module"],
            "docroot/profiles/contrib/{$name}": ["type:drupal-profile"],
            "docroot/themes/contrib/{$name}": ["type:drupal-theme"],
            "docroot/libraries/{$name}": ["type:drupal-library"]
        }
    }

Drupal best practice is to place contributed modules in docroot/modules/contrib. Your current branch should already reflect the decision you made in cleanup. If your modules are not currently in that path, you need to modify the installer path by simply removing /contrib.

Libraries: If contrib modules require the manual addition of libraries (in other words, the module does not utilize a composer.json to download its required libraries), you may add them directly to your require section. For an example, see enyo/dropzone in both the require section as well as the installer-paths section of this sample template: https://github.com/acquia/acquia-ra-composer/blob/master/composer-templates/composer-libraries.json

Custom Modules and Themes: There are two ways to handle custom modules and themes:

  1. Circumvent composer entirely and directly commit your custom modules and themes to your repository, or,
  2. Create them as composer packages, ensure they can be downloaded via composer, and include them just like any other package.

The first is far simpler than the second, but if your theme or module is going to be used by more than one composer-built site, it may be more efficient and developer-friendly to create your custom code as discrete composer packages.

In either case, custom code should live in clearly demarcated custom folders:

docroot/modules/custom
docroot/themes/custom

This allows you to delete the contributed themes and modules by deleting the parent contributed folder entirely, and allow composer to rebuild it from scratch.

Delete!

At this point, the composer.json should be able to build your entire site (except for custom code you are adding directly). Go ahead and delete the following folders in their entirety (remember, you are on a development branch, you can always abandon ship):

docroot/core
docroot/modules/contrib
docroot/themes/contrib
docroot/vendor

Install

Run composer install.

This should install all the packages required in the composer.json in the proper folders, create a composer.lock file, and rewrite docroot/autoload.php to point to the new location of the vendor folder (this is what drupal-scaffold does).

Compare and Test:

At this point, your site should be working. You can use git diff to compare directories or particular files. Compare carefully! If you pinned your modules and core, there should be little difference in module or core files (except autoload.php). The entire vendor folder has moved, and it is likely that the packages in the vendor folder are different as there may be a more recent version than what is on your current site. This should be fine as the module itself is the same.

Make sure that you check all parts of your drupal site, noting if you need to run drush cr, or if modules appear to be missing.

Continue to delete, modify the `composer.json` and install until your site is fully working.

Commit and Merge:

At this point, you should be able to commit the composer.json, composer.lock, and all generated code (if you are using CI, this is a different process).

Remember that you are committing ‘pinned’ versions of your modules. This is to ensure that composer is installing the exact versions currently running your site. Your next step, either in this branch or after this branch, has been tested and merged to master, is to change all the versioning in the require section to using the ‘^’ and more open versioning:

  "require": {
       "drupal-composer/drupal-scaffold": "^2.0.0",
       "drupal/core": "^8.3",
       "drupal/admin_toolbar": "^1.1",
       "drupal/ctools": "^3.0",
       "drupal/pathauto": "^1.0",
       "drupal/token": "^1.0",
       "drupal/zurb_foundation": "^6.x",
       "composer/installers": "^1.0"
   },

At this point, if you were to run composer update drupal/core --with-dependencies, composer will update your site to Drupal 8.3.2 since in this example, we installed 8.3.1. This is what you want, but only after you have tested the migration with pinned versions.

Contact supportStill need assistance? Contact Acquia Support