Организуем библиотеки и патчи в drupal-проекте

Дубовской Александр
Team lead

Работа с патчами.

Если у вас более-менее стабильный набор патчей под проекты, имеет смысл выделить их в отдельный файл. Делается это довольно элементарно: с помощью https://github.com/cweagans/composer-patches .

Достаточно сделать: composer require cweagans/composer-patches, а потом добавить в свой composer.json:

"extra": {
        "patches-file": "composer.patches.json",
        "enable-patching": true
...

И создать файл composer.patches.json, в котором указать требуемые патчи, например:

{
  "patches": {
    "drupal/core": {
        "Fix images link problem": "https://www.drupal.org/files/issues/2018-09-11/edit_drupalimage-86x-2855521-27.patch"
    },
    "drupal/features": {
      "Fix features-import-all bug": "https://www.drupal.org/files/issues/2018-04-16/fix-features-import-all-2961007.patch",
      "Fix unicode error": "https://www.drupal.org/files/issues/2018-08-15/2992790-drupal-component-namespace-case.patch"
    },
    "drupal/colorbutton": {
      "issue:#2919123#comment-12393742": "https://www.drupal.org/files/issues/2919123.patch"
    },
    "drupal/ckeditor_font": {
      "issue#2900789#comment-12501946": "https://www.drupal.org/files/issues/ckeditor_font_8_1_0_libraries_integration_01.patch"
    }
  }  
}

Вуаля, при следующем composer update/install патчи будут применяться, а мы в git истории будем легко отслеживать именно правку файла с патчами.

 

Работа с библиотеками

Мы хотим складывать все js-зависимости (слайдеры, colorbox, и т.п.) в отдельную папку libraries и хранить это в composer-файле. Сделать это очень просто: сначала убедимся что стоит composer/installers. Для этого сделаем composer require composer/installers .

После чего в разделе "installer-paths" вашего composer.json укажите путь куда складывать библиотеки:

"installer-paths": {
            "core": ["type:drupal-core"],
            "modules/contrib/{$name}": ["type:drupal-module"],
            "modules/custom/{$name}": ["type:drupal-custom-module"],
            "modules/features/{$name}": ["type:drupal-custom-feature"],
            "profiles/contrib/{$name}": ["type:drupal-profile"],
            "themes/contrib/{$name}": ["type:drupal-theme"],
            "drush/contrib/{$name}": ["type:drupal-drush"],
            "themes/repository/{$name}": ["type:drupal-custom-theme"],
            "libraries/{$name}": ["type:drupal-library"]
        }

А именно: добавить строчку "libraries/{$name}": ["type:drupal-library"] .

Далее, т.к. библиотек много, нам бы хотелось хранить пути к ним в отдельном файле, для этого нам нужно убедиться что он стоит (вообще должен, для composer-based drupal-инсталляции он уже включен. Честно говоря не помню как было с ранними версиями 8.x ядра, но в новых уже есть): composer require wikimedia/composer-merge-plugin . Т.е. установить плагин https://github.com/wikimedia/composer-merge-plugin, он позволяет разделить composer.json файл на несколько составляющих (в т.ч. сделать файл с отдельным списком репозиториев). После чего, в composer.json нужно добавить:

        "merge-plugin": {
            "include": [
                "core/composer.json",
                "composer.libraries.json"
            ],
            "recurse": false,
            "replace": false,
            "merge-extra": false
        },

А именно, мы добавили "composer.libraries.json", в котором нужно указать все наши библиотеки. Пример нашего типового (да, он довольно большой, но может кому пригодится, т.к. там чаще всего используемый UI-контриб).

{
  "repositories": [{
      "type": "package",
      "package": {
        "name": "ckeditor/colorbutton",
        "type": "drupal-library",
        "version": "4.10.0",
        "dist": {
          "url": "https://download.ckeditor.com/colorbutton/releases/colorbutton_4.10.0.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/panelbutton",
        "type": "drupal-library",
        "version": "4.10.0",
        "dist": {
          "url": "https://download.ckeditor.com/panelbutton/releases/panelbutton_4.10.0.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/font",
        "type": "drupal-library",
        "version": "4.10.0",
        "dist": {
          "url": "https://download.ckeditor.com/font/releases/font_4.10.0.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "kenwheeler/slick",
        "type": "drupal-library",
        "version": "1.8.0",
        "dist": {
          "url": "https://github.com/kenwheeler/slick/archive/1.8.0.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "bgrins/spectrum",
        "type": "drupal-library",
        "version": "1.8.0",
        "dist": {
          "url": "https://github.com/bgrins/spectrum/archive/1.8.0.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "recurser/jquery-simple-color",
        "type": "drupal-library",
        "version": "1.2.2",
        "dist": {
          "url": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip",
          "type": "zip"
        }
      }
    },

    {
      "type": "package",
      "package": {
        "name": "algolia/places",
        "version": "1.9.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "algolia.places"
        },
        "dist": {
          "url": "https://registry.npmjs.org/places.js/-/places.js-1.9.0.tgz",
          "type": "tar"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/autogrow",
        "version": "4.10.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "ckeditor.autogrow"
        },
        "dist": {
          "url": "https://download.ckeditor.com/autogrow/releases/autogrow_4.10.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/codemirror",
        "version": "v1.17.5",
        "type": "drupal-library",
        "extra": {
          "installer-name": "ckeditor.codemirror"
        },
        "dist": {
          "url": "https://github.com/w8tcha/CKEditor-CodeMirror-Plugin/releases/download/v1.17.5/CKEditor-CodeMirror-Plugin.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/fakeobjects",
        "version": "4.10.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "ckeditor.fakeobjects"
        },
        "dist": {
          "url": "https://download.ckeditor.com/fakeobjects/releases/fakeobjects_4.10.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/image",
        "version": "4.10.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "ckeditor.image"
        },
        "dist": {
          "url": "https://download.ckeditor.com/image/releases/image_4.10.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/link",
        "version": "4.10.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "ckeditor.link"
        },
        "dist": {
          "url": "https://download.ckeditor.com/link/releases/link_4.10.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "codemirror/codemirror",
        "version": "5.39.2",
        "type": "drupal-library",
        "extra": {
          "installer-name": "codemirror"
        },
        "dist": {
          "url": "https://github.com/components/codemirror/archive/5.39.2.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/geocomplete",
        "version": "1.7.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.geocomplete"
        },
        "dist": {
          "url": "https://github.com/ubilabs/geocomplete/archive/1.7.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/icheck",
        "version": "1.0.2 ",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.icheck"
        },
        "dist": {
          "url": "https://github.com/fronteed/icheck/archive/1.0.2.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/inputmask",
        "version": "4.0.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.inputmask"
        },
        "dist": {
          "url": "https://github.com/RobinHerbots/jquery.inputmask/archive/4.0.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/intl-tel-input",
        "version": "13.0.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.intl-tel-input"
        },
        "dist": {
          "url": "https://github.com/jackocnr/intl-tel-input/archive/v13.0.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/rateit",
        "version": "1.1.1",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.rateit"
        },
        "dist": {
          "url": "https://github.com/gjunge/rateit.js/archive/1.1.1.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/select2",
        "version": "4.0.5",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.select2"
        },
        "dist": {
          "url": "https://github.com/select2/select2/archive/4.0.5.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/textcounter",
        "version": "0.8.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.textcounter"
        },
        "dist": {
          "url": "https://github.com/ractoon/jQuery-Text-Counter/archive/0.8.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jquery/timepicker",
        "version": "1.11.13",
        "type": "drupal-library",
        "extra": {
          "installer-name": "jquery.timepicker"
        },
        "dist": {
          "url": "https://github.com/jonthornton/jquery-timepicker/archive/1.11.13.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "progress-tracker/progress-tracker",
        "version": "1.4.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "progress-tracker"
        },
        "dist": {
          "url": "https://github.com/NigelOToole/progress-tracker/archive/v1.4.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "signature_pad/signature_pad",
        "version": "2.3.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "signature_pad"
        },
        "dist": {
          "url": "https://github.com/szimek/signature_pad/archive/v2.3.0.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "jackmoore/colorbox",
        "version": "2.3.0",
        "type": "drupal-library",
        "extra": {
          "installer-name": "colorbox"
        },
        "dist": {
          "url": "https://github.com/jackmoore/colorbox/archive/master.zip",
          "type": "zip"
        },
        "require": {
          "composer/installers": "~1.0"
        }
      }
    }
  ]
}

 

Обратите внимание, в некоторых указан параметр "extra": {  "installer-name": "algolia.places"  }, он сообщает, что нужно директорию в libraries назвать определенным образом. Т.е. у нас будет /libraries/algolia.places/ каталог, т.к. именно такой путь указан в drupal-модуле который требует эту библиотеку и именно в данный путь мы ее будем разворачивать. 

После чего нам достаточно указать все эти библиотеки как зависимости (установить т.е), набрав composer require ckeditor/colorbutton ckeditor/panelbutton (и все остальные). 

Можно разбить это на небольшие файлы и хранить в конкретных кастомных модулях. Например если вам для фичи нужна конкретная js-библиотека, логично хранить эту зависимость в composer.json фичи. Для этого, добавляем в глобальный composer.json:

"merge-plugin": {
            "include": [
                "core/composer.json",
                "composer.libraries.json",
                "modules/custom/*/composer.json",
                "modules/features/*/composer.json",
                "modules/repository/*/composer.json"
            ],
            "recurse": true,
            "replace": false,
            "merge-extra": true
        },

Теперь вы можете хранить зависимости внутри фичи, пример нашей фичи с настройками ckeditor:

{
  "name": "radon_features/ckeditor_editor_settings",
  "type": "drupal-custom-feature",
  "description": "Ckeditor default settings for editor role",
  "license": "GPL-2.0+",
  "homepage": "https://git.ra-don.com/radon_features/ckeditor_editor_settings",
  "minimum-stability": "dev",
  "require": { 
    "composer/installers": "^1.3",
    "radon_modules/ckawesome": "dev-master",
    "drupal/ckeditor_font": "^1.0",
    "drupal/ckeditor_tabletoolstoolbar": "^1.0",
    "drupal/ckeditor_tableselection": "^1.0",
    "drupal/ckeditor_tableresize": "^1.0",
    "drupal/ckeditor_uploadimage": "^1.3",
    "drupal/ckeditor_youtube": "^1.0",
    "drupal/colorbutton": "^1.0",
    "drupal/panelbutton": "^1.0",
    "ckeditor/autogrow": "^4.10",
    "ckeditor/codemirror": "^1.17",
    "ckeditor/colorbutton": "^4.10",
    "ckeditor/fakeobjects": "^4.10",
    "ckeditor/font": "^4.10",
    "ckeditor/image": "^4.10",
    "ckeditor/link": "^4.10",
    "ckeditor/panelbutton": "^4.10",
    "ckeditor/tableresize": "^4.7",
    "ckeditor/tableselection": "^4.7",
    "ckeditor/tabletoolstoolbar": "^0.0.1"
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
  },
  {
      "type": "composer",
      "url": "https://repo.ra-don.ru"
  },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/tabletoolstoolbar",
        "version": "0.0.1",
        "type": "drupal-library",
        "dist": {
          "url": "https://download.ckeditor.com/tabletoolstoolbar/releases/tabletoolstoolbar_0.0.1.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/tableselection",
        "version": "4.7.2",
        "type": "drupal-library",
        "dist": {
          "url": "https://download.ckeditor.com/tableselection/releases/tableselection_4.7.2.zip",
          "type": "zip"
        }
      }
    },
    {
      "type": "package",
      "package": {
        "name": "ckeditor/tableresize",
        "version": "4.7.2",
        "type": "drupal-library",
        "dist": {
          "url": "https://download.ckeditor.com/tableresize/releases/tableresize_4.7.2.zip",
          "type": "zip"
        }
      }
    }
  ]
}

Вариант с asset-packagist

Можно делать проще и не хранить все пути в composer.libraries.yml, а воспользоваться готовым репозиторием https://asset-packagist.org/ . Что для этого нужно:

  1. Убедиться что стоит installers extender:
    composer require oomphinc/composer-installers-extender

     

  2. Добавить репозиторий в раздел repositories вашего composer.json
    "repositories": {
        {
            "type": "composer",
            "url": "https://asset-packagist.org"
        }
    }

     

  3. Добавить нужные типы/пути в composer.json
    "extra": {
        "installer-types": [
            "npm-asset",
            "bower-asset"
        ],
        "installer-paths": {
            "web/libraries/{$name}": [
                "type:drupal-library",
                "type:npm-asset",
                "type:bower-asset"
            ]
        }
    }

    Примечание: вы тут же можете указать нужный каталог для распаковки (мало ли, разработчик модуля для drupal.org решил писать в нестандартный путь. Но вообще разработчику модуля хорошо бы при обнаружении написать об этом. Тем не менее возможность есть:
     

    "extra": {
        "installer-paths": {
            "web/libraries/chosen": ["npm-asset/chosen-js"],
            "web/libraries/{$name}": [
                "type:drupal-library",
                "type:npm-asset",
                "type:bower-asset"
            ]
        }
    }

    Для пакета npm-asset/shosen.js он будет поставлен в web/libraries/chosen
     

  4. Все, ставите нужную библиотеку как обычный пакет, указывая либо точную версию, либо минимальную:
     

    composer require npm-asset/chosen-js:1.8.0
    или
    composer require npm-asset/chosen-js:^1.8