Commit 73e5bfad authored by catch's avatar catch

Issue #2358981 by tadityar, tstoeckler, larowlan, mpdonadio, Devin Carlson:...

Issue #2358981 by tadityar, tstoeckler, larowlan, mpdonadio, Devin Carlson: Provide a mechanism for dynamic library declarations
parent e40dff31
......@@ -79,4 +79,11 @@ public function getLibraryByName($extension, $name) {
return isset($extension[$name]) ? $extension[$name] : FALSE;
}
/**
* {@inheritdoc}
*/
public function clearCachedDefinitions() {
$this->collector->clear();
}
}
......@@ -50,4 +50,9 @@ public function getLibrariesByExtension($extension);
*/
public function getLibraryByName($extension, $name);
/**
* Clears static and persistent library definition caches.
*/
public function clearCachedDefinitions();
}
......@@ -13,6 +13,7 @@
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
/**
* Parses library files to get extension data.
......@@ -77,15 +78,11 @@ public function buildByExtension($extension) {
$path = $this->drupalGetPath($extension_type, $extension);
}
$library_file = $path . '/' . $extension . '.libraries.yml';
if ($library_file && file_exists($this->root . '/' . $library_file)) {
$libraries = $this->parseLibraryInfo($extension, $library_file);
}
$libraries = $this->parseLibraryInfo($extension, $path);
foreach ($libraries as $id => &$library) {
if (!isset($library['js']) && !isset($library['css']) && !isset($library['drupalSettings'])) {
throw new IncompleteLibraryDefinitionException(sprintf("Incomplete library definition for '%s' in %s", $id, $library_file));
throw new IncompleteLibraryDefinitionException(sprintf("Incomplete library definition for definition '%s' in extension '%s'", $id, $extension));
}
$library += array('dependencies' => array(), 'js' => array(), 'css' => array());
......@@ -102,7 +99,7 @@ public function buildByExtension($extension) {
// If this is a 3rd party library, the license info is required.
if (isset($library['remote']) && !isset($library['license'])) {
throw new LibraryDefinitionMissingLicenseException(sprintf("Missing license information in library definition for '%s' in %s: it has a remote, but no license.", $id, $library_file));
throw new LibraryDefinitionMissingLicenseException(sprintf("Missing license information in library definition for definition '%s' extension '%s': it has a remote, but no license.", $id, $extension));
}
// Assign Drupal's license to libraries that don't have license info.
......@@ -209,8 +206,8 @@ public function buildByExtension($extension) {
*
* @param string $extension
* The name of the extension that registered a library.
* @param string $library_file
* The relative filename to the DRUPAL_ROOT of the wanted library file.
* @param string $path
* The relative path to the extension.
*
* @return array
* An array of parsed library data.
......@@ -218,14 +215,26 @@ public function buildByExtension($extension) {
* @throws \Drupal\Core\Asset\Exception\InvalidLibraryFileException
* Thrown when a parser exception got thrown.
*/
protected function parseLibraryInfo($extension, $library_file) {
try {
$libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file));
protected function parseLibraryInfo($extension, $path) {
$libraries = [];
$library_file = $path . '/' . $extension . '.libraries.yml';
if (file_exists($this->root . '/' . $library_file)) {
try {
$libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file));
}
catch (InvalidDataTypeException $e) {
// Rethrow a more helpful exception to provide context.
throw new InvalidLibraryFileException(sprintf('Invalid library definition in %s: %s', $library_file, $e->getMessage()), 0, $e);
}
}
catch (InvalidDataTypeException $e) {
// Rethrow a more helpful exception to provide context.
throw new InvalidLibraryFileException(sprintf('Invalid library definition in %s: %s', $library_file, $e->getMessage()), 0, $e);
// Allow modules to add dynamic library definitions.
$hook = 'library_info_build';
if ($this->moduleHandler->implementsHook($extension, $hook)) {
$libraries = NestedArray::mergeDeep($libraries, $this->moduleHandler->invoke($extension, $hook));
}
// Allow modules to alter the module's registered libraries.
$this->moduleHandler->alter('library_info', $libraries, $extension);
......
......@@ -374,6 +374,28 @@ function testLibraryAlter() {
$this->assertTrue(strpos($scripts, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.');
}
/**
* Dynamically defines an asset library and alters it.
*/
function testDynamicLibrary() {
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
$library_discovery = \Drupal::service('library.discovery');
// Retrieve a dynamic library definition.
// @see common_test_library_info_build()
\Drupal::state()->set('common_test.library_info_build_test', TRUE);
$library_discovery->clearCachedDefinitions();
$dynamic_library = $library_discovery->getLibraryByName('common_test', 'dynamic_library');
$this->assertTrue(is_array($dynamic_library));
if ($this->assertTrue(isset($dynamic_library['version']))) {
$this->assertIdentical('1.0', $dynamic_library['version']);
}
// Make sure the dynamic library definition could be altered.
// @see common_test_library_info_alter()
if ($this->assertTrue(isset($dynamic_library['dependencies']))) {
$this->assertIdentical(['core/jquery'], $dynamic_library['dependencies']);
}
}
/**
* Tests that multiple modules can implement libraries with the same name.
*
......
......@@ -164,6 +164,22 @@ function common_test_preprocess_common_test_render_element(&$variables) {
$variables['#attached']['library'][] = 'test/specific_preprocess';
}
/**
* Implements hook_library_info_build().
*/
function common_test_library_info_build() {
$libraries = [];
if (\Drupal::state()->get('common_test.library_info_build_test')) {
$libraries['dynamic_library'] = [
'version' => '1.0',
'css' => [
'common_test.css' => [],
],
];
}
return $libraries;
}
/**
* Implements hook_library_info_alter().
*/
......@@ -174,6 +190,13 @@ function common_test_library_info_alter(&$libraries, $module) {
// Make Farbtastic depend on jQuery Form to test library dependencies.
$libraries['jquery.farbtastic']['dependencies'][] = 'core/jquery.form';
}
// Alter the dynamically registered library definition.
if ($module == 'common_test' && isset($libraries['dynamic_library'])) {
$libraries['dynamic_library']['dependencies'] = [
'core/jquery',
];
}
}
/**
......
......@@ -721,6 +721,70 @@ function hook_js_alter(&$javascript) {
$javascript['core/assets/vendor/jquery/jquery.min.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js';
}
/**
* Add dynamic library definitions.
*
* Modules may implement this hook to add dynamic library definitions. Static
* libraries, which do not depend on any runtime information, should be declared
* in a modulename.libraries.yml file instead.
*
* @return array[]
* An array of library definitions to register, keyed by library ID. The
* library ID will be prefixed with the module name automatically.
*
* @see core.libraries.yml
* @see hook_library_info_alter()
*/
function hook_library_info_build() {
$libraries = [];
// Add a library whose information changes depending on certain conditions.
$libraries['mymodule.zombie'] = [
'dependencies' => [
'core/backbone',
],
];
if (Drupal::moduleHandler()->moduleExists('minifyzombies')) {
$libraries['mymodule.zombie'] += [
'js' => [
'mymodule.zombie.min.js' => [],
],
'css' => [
'mymodule.zombie.min.css' => [],
],
];
}
else {
$libraries['mymodule.zombie'] += [
'js' => [
'mymodule.zombie.js' => [],
],
'css' => [
'mymodule.zombie.css' => [],
],
];
}
// Add a library only if a certain condition is met. If code wants to
// integrate with this library it is safe to (try to) load it unconditionally
// without reproducing this check. If the library definition does not exist
// the library (of course) not be loaded but no notices or errors will be
// triggered.
if (Drupal::moduleHandler()->moduleExists('vampirize')) {
$libraries['mymodule.vampire'] = [
'js' => [
'js/vampire.js' => [],
],
'css' => [
'css/vampire.css',
],
'dependencies' => [
'core/jquery',
],
];
}
return $libraries;
}
/**
* Perform necessary alterations to the JavaScript settings (drupalSettings).
*
......
......@@ -161,7 +161,7 @@ public function testInvalidLibrariesFile() {
* Tests that an exception is thrown when no CSS/JS/setting is specified.
*
* @expectedException \Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException
* @expectedExceptionMessage Incomplete library definition for 'example' in core/tests/Drupal/Tests/Core/Asset/library_test_files/example_module_missing_information.libraries.yml
* @expectedExceptionMessage Incomplete library definition for definition 'example' in extension 'example_module_missing_information'
*
* @covers ::buildByExtension
*/
......@@ -400,7 +400,7 @@ public function testLibraryWithJavaScript() {
* Tests that an exception is thrown when license is missing when 3rd party.
*
* @expectedException \Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException
* @expectedExceptionMessage Missing license information in library definition for 'no-license-info-but-remote' in core/tests/Drupal/Tests/Core/Asset/library_test_files/licenses_missing_information.libraries.yml: it has a remote, but no license.
* @expectedExceptionMessage Missing license information in library definition for definition 'no-license-info-but-remote' extension 'licenses_missing_information': it has a remote, but no license.
*
* @covers ::buildByExtension
*/
......@@ -434,7 +434,6 @@ public function testLibraryWithLicenses() {
$libraries = $this->libraryDiscoveryParser->buildByExtension('licenses');
// For libraries without license info, the default license is applied.
$library = $libraries['no-license-info'];
$this->assertCount(1, $library['css']);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment