(Work on this is being coordinated in the
1762204-assets branch in sdboyer's github fork)
The goal of this issue is to introduce a new base architecture for declaring and managing assets (CSS/JS), via a set of our own Assetic-derived classes. The architecture should, once completed, be able to completely replace the drupal_add_css/js/library() system, which is already scoped for deletion:
This change is necessary because the current system is reliant on globals (
drupal_add_css/js()), which breaks page rendering encapsulation and can easily be cache-busty.
There are also some nice-to-haves that we can do with a proper approach to this problem.
- Develop an asset grouping and aggregation strategy that isn't predestined to blow chunks. And is easily swapped out by contrib. (cf. )
- Utilize Assetic to help with the above. Yay! ( )
- Allow the declaration of conditional assets (e.g., 'always give X css to users in Y role') in a first-class, introspectable fashion. (NOT in scope for the initial patch)
- Give us a clear point of control and reflection for, say, pushing javscript down into the footer.
This is a major hurdle on the path towards the goals of SCOTCH.
This patch slips in almost transparently underneath the existing
#attached mechanism for declaring assets - meaning, almost NO API CHANGE.. More on the sole API change below.
We need a whole new approach to the declaration, ordering, aggregating, and final placement of assets into templates. Out of the box, Assetic can help with certain mechanics of manipulating asset content: minification, SASS compilation (though making that work correctly is NOT in scope for this patch), but it missing necessary things needed at declaration, and is totally orthogonal to sorting. So, the first major component of this patch is constructing a series of interfaces and classes on top of the base API provided by Assetic in order to accommodate our added requirements.
To assist with declaration, this patch introduces our own family of classes for representing the individual assets themselves. Here's an overview of the main concepts:
- Asset - an individual asset, representing a local file, external file, or simply a string. There are classes representing each of those three, which share a base class,
BaseAsset. These implement our
AssetInterface, which is an extension of Assetic's
- Asset Metadata - these are classes that contain the sort of metadata we've historically set on assets - think
browsers, etc. They're injected into
AssetInterfaceobjects, which need them to function, but they're kept in a separate class in order to keep
AssetInterface's responsibility narrowly focused on representing, loading, dumping, etc., the asset itself.
- Asset Collection - a container that holds assets (that is, things implementing our
AssetInterface). These implement our
AssetCollectionInterface, and have the sort of methods you'd expect from a collection. They do NOT represent a logical asset that is renderable; they are just a container. Drupal's libraries have always been this way - they contain a list of assets, but are not themselves an asset. Indeed, the new
- Asset Aggregates - these are also a container for
AssetInterfaceobjects, and have some collection-style methods, but unlike collections, they themselves implement
Note that the distinction between aggregates and collections is not one made by Assetic. It has only an
AssetCollection, which is both a generalized container (like our collections) as well as a renderable (like our aggregates).
The other major player in this system is the
AssetCollector. Think of it like a factory on wheels: its job is to travel around and create asset objects and inject them into an
AssetCollectionInterface object that it holds. This lets us send the factory into code that we want to have declare assets, but prevents that code from having full access to the collection, which would allow it to change or remove assets already in the collection - not always appropriate.
weight metadata on assets is going away. This is the sole API change mentioned earlier. This is intentional: reliance on a linear weight ordering is one of the primary reasons Drupal's aggregates suck. The new system replaces it with a first-class way of handling ordering. There are two mechanisms: assets can declare dependencies on libraries - conceptually, the same as now. Assets can also declare that they come
after() another asset (not a library - an individual asset); unlike a dependency, this does not guarantee that the other asset will be present, BUT IF it is, then that ordering is guaranteed. This explicit ordering information lets us use a graph to find an ordering that allows for a minimal, stable set of aggregates - something we could never do with a linear weight order.
Now, gluing it all together. For this patch,
drupal_process_attached() uses an
AssetCollector to transform all array-style assets into their object equivalents, and stores them in an
AssetCollection. This still relies on
drupal_static(), so a global, but that can go away with some other patches that are being developed.
When it comes time to render out the HTML for the assets, we're quickly into the stack in introduced in
AssetCollection. There is some rejiggering of things - the responsibility for doing the sort is encompassed by
AssetGroupSorterInterface, which groupers (now known as aggregators) now call. Aggregators now produce an
AssetCollection mostly full of
AssetAggregates. The collection optimizer iterates over that, uses Assetic's mechanics for filtering, then passes them off to the dumper to write to disk.
Still a fair bit.
- Grunt work: adding brief docblocks to all of the new unit tests.
In doing on the above, any plain
Exceptions that are thrown should be converted to an appropriate more specific type. (some progress made)
ExternalAssetneed unit tests.
ExternalAssetneeds love. Right now it just throws exceptions if its
getLastModified()methods are called, but this is wrong - it needs to work, even if core doesn't use it.
Figure out a sane basis for
StringAssetto return a value from
- Deal with uniqueness in collections. That entails:
Deciding on a standard form for ids (e.g., files - absolute path? relative? what ifgoes in?)
- Figuring out if and when to allow changing ids, recalculating them, and when such state changes should be considered appropriate.
Make interfaces fluent, where possible and appropriate. Dependency resolution needs to be handled. This is big, and probably involves reviving the
AssetLibraryneeds unit tests.
AssetLibrarymaybe also needs some reconsideration of its metadata-as-exposed-get/set-methods interface. (followup?)
- We should have a look at serialization of collections, aggregates, etc. to see how not-breaky we can make it. (followup?)
AssetAggregatehas no test coverage; everything there needs to be written.
dump()can be skipped in a first pass, they are tricky.
AssetCollectorneeds an interface, and many of its methods still need to be documented.
AssetMetadataBagneeds an interface.
AssetMetadataBagalso really needs to be refactored. It was originally written to support a misunderstood use case - there's no need to keep track of "explicit" versus "default" values. The basic set of public methods on it are still probably useful, but the internals should be refactored to keep track of just a single array, rather than constantly coalescing between the two.
A factory should be introduced for making
AssetCollectorshould probably use it instead of directly calling
- Some thought needs to be given to the different types of metadata we track for assets, with specific mind to how "intrinsic" the datum actually is to the asset. This could factor significantly in contrib efforts to do sitewide optimal asset groupings. (definite followup)
drupal_process_attached()to utilize an
AssetCollectoras described in the summary.
Make a subclass of AssetCollection that behaves like a priority queue using natural case sort; using this as the de facto collection everywhere should improve group ordering reliability.(Actually implemented by just adding sort methods to
- All of the JS-related classes from
need to be reimplemented with the new system.
JsCollectionOptimizer. And tests.
JsOptimizer. And tests.
JsDumper. And tests.
JsCollectionAggregator. And tests.
- Write a
JsGraphSorter. And tests.
- Figure out how to separate out the two channels for header vs. footer js.
- All of the CSS-related classes from
, except the grouper, need to reimplemented with the new system.
CssOptimizer. And tests.
CssDumper. And tests.
Write aAnd tests.
- In doing the previous two items, we need to decide just how much we actually rely on Assetic's
dump()methods. We may let the behavior stay in there, but it's also an SRP violation, and can make for some awkward coupling.
Graph sorting needs to be moved out of(still needs a little tweaking, though)
CssCollectionGrouperNouveauxand into a standalone service, which the grouping classes utilize, but is also called directly when aggregation is disabled to prepare for rendering. Gotta sort there, too.
Graph sorting needs functional tests. The
AssetGraphhas unit tests, but we need black box tests that ensure the expected output, given dependencies, is correct.
- We need a new mechanism for injecting the rendered tags into the page - since we need to get rid of the global getters, too,
template_preprocess_html()can no longer call the globals to grab it. They need to be injected as variables to the theme function, instead. Maybe they needn't even pass through
Note - sdboyer reserves the right to pull the trigger on the last five. he's earned it :)
Further note - for now, we're punting on a thorny issue around supporting css/js preprocessors (so for SASS, LESS, CoffeeScript, etc.). It's not impossible, but it complicates the API a bit, has significant issues around best practices & third-party code, and we can readily do it later.
User interface changes
None, except that
weight doesn't do anything anymore on assets, and should not be used.
FAILED: [[SimpleTest]]: [MySQL] Unable to apply patch assets-1762204-176.patch. Unable to apply patch. See the log in the details link for more information.
FAILED: [[SimpleTest]]: [MySQL] Invalid PHP syntax in core/lib/Drupal/Component/ObjectState/FreezableTrait.php.
FAILED: [[SimpleTest]]: [MySQL] Drupal installation failed.