Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Last active October 18, 2016 16:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mindplay-dk/90507eb164e74bac7bbbf9abc97a04ee to your computer and use it in GitHub Desktop.
Save mindplay-dk/90507eb164e74bac7bbbf9abc97a04ee to your computer and use it in GitHub Desktop.
Asset Scheme

Asset Scheme

This document describes a convention for a file-system structure and URL scheme to support the distribution and delivery of static front-end assets, such as Javascript and CSS files, as components of a vendored (e.g. Composer) package.

Static assets are a fundamental component of web-applications. Such assets are commonly a dependency of controllers, middleware, front-controllers, and other server-side components, and therefore should have a consistent file-system structure. Such assets are also commonly a dependency of other client-side components, and therefore also need to have a predictable, consistent URL scheme.

The scheme defined by this specification aims to be simple enough that it can be supported, with minimal effort, either with or without a framework, under any server daemon, including Apache, NGINX, and the simple HTTP-server that is built into the CLI version of PHP.

The creation of static assets from from other resources (such as SASS, LESS, TypeScript or ES6 scripts being transpiled to ES5, etc.) is beyond the scope of this specification, which deals strictly with the delivery, not the creation, of static assets.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

1. Specification

In the examples below, for the sake of discussion, we will assume a typical Composer project structure as follows:

composer.json
webroot/
  index.php
vendor/
  a/
    b/

In this typical structure, vendor packages are installed (per Composer defaults) into a vendor folder in the root of the project, with packages such as b grouped into parent folders by vendor names such as a.

As is typical of most projects, a folder has been named and designated as webroot, from which the index.php script, by means of server configuration, has been routed as the front-controller; e.g. a script that gets dispatched for any requested URL for which the path could not be mapped directly to a file in the webroot folder.

Components following this specification MUST NOT make any assumptions about the name or location of a public, web-accessible web-root folder, only that a designated web-root folder exists.

Likewise, components MUST NOT make any assumptions about the structure of a vendor-folder or about the package installation paths - only that, for any given vendor and package name pair, a designated package installation folder exists.

1.1. File System Structure

A package a/b, which provides one or more static assets, MUST confine all of these assets to a single folder, assets, at the root of the package.

For example:

composer.json
assets/
  css/
    main.css
  js/
    main.js

A package MAY organize it's assets into any hierarchy of subfolders, grouped as found necessary or appropriate by the package vendor.

A package such as this, when installed by a project using the default Composer settings, might result in a directory structure such as:

composer.json
webroot/
  index.php
vendor/
  a/
    b/
      assets/
        css/
          main.css
        js/
          main.js

Again, this is an example only - in practice, the package installation paths may vary, depending on, for example, Composer installers or other installation scripts.

A project that contains a designated web-root folder SHOULD NOT contain a sub-folder named assets, UNLESS such a folder is used exclusively for symbolic links to vendor-supplied assets folders, e.g. created by an installation script, and if so, MUST use a physical directory structure matching the URL Scheme described below.

1.2. Asset URL Scheme

A project that delivers vendor-supplied assets in response to HTTP requests MUST reserve the URL path prefix /assets/{vendor}/{package}/ exclusively for the delivery of static assets of any given package provided by any given vendor.

Delivery of assets for an installed package a/b, per the example above, would therefore effectively result in the following valid URL paths:

/assets/a/b/js/main.js
/assets/a/b/css/main.css

This specification does not stipulate how this is implemented. Examples include: proprietary web-server configuration files, e.g. Apache, NGINX, or other web-server software, front controllers, middleware, symbolic links created by installation scripts, or any other means.

A component responsible for the delivery of vendor-supplied assets:

  1. MUST respond with the correct MIME-type for all common asset file-types according to media-types defined by IANA.

  2. MUST respond with a 3xx success status code for any applicable GET or HEAD request, such as 302 Found or 304 Not Modified, as appropriate.

  3. MUST respond with a 405 Method Not Allowed status code for requests with an applicable path and any HTTP method other than GET or HEAD.

A component responsible for the delivery of vendor-supplied assets SHOULD respond with the appropriate cache-headers, such as ETag or Last-Modified, e.g. in accordance with RFC2616 section 13.


Meta

Q: Why a single assets folder, as opposed to a map?

A: Because it's simpler. A map would require more than a standard - it would require at least a configuration file format specification, and/or at least interfaces, possibly even an implementation and/or directions detailed enough that they would basically be pseudo-code.

From using a similar approach at work for a while, we learned that that being able to symlink vendored asset folders into the project's public asset folder is really useful, for a couple of reasons:

  1. It enables you to run an installation script once, and the continue to add more files to the vendor packages, since every file in the symlinked folder becomes automatically available.

  2. A map cannot be interpreted or resolved at design-time, by the browser, or by an IDE (things like source-maps of relative paths fall apart, since the relative public URLs do not map directly to physical files - having to run a build/deploy/install script after every change is cumbersome.)

  3. The length or appearance of asset URLs is typically completely irrelevant - about as irrelevant as the physical file-system structure is to Composer packages. For the most part, no person will ever see the URL of your CSS or JS files - shorter or neater URLs have no practical value. Having a simple, predictable URL structure that prevents collissions, has great value.

Q: What about compilers, minifiers, build tools...?

A: Create of static assets is beyond the scope of this specification, which deals solely with the delivery of static assets.

Your (SASS, LESS, CoffeeScript, etc.) source files assets aren't "static assets" in the context of this specification - they require tools for processing, compilation, minification, and so on, which, in the context of this specification, makes them "dynamic assets".

Build tools, compilers and other complex pipelines (such as Grunt, WebPack, etc.) deal with the creation of static assets - the subject of this specification is limited to the delivery of static assets created with or without such tools.

Q: What about other resources like templates, configuration files...?

A: This specification deals only with the delivery of public, static assets.

Locating other server-side resources, such as templates or configuration-files, is an entirely different problem - even for resources with the same file-type, such as images, some images (such as CSS dependencies) may be public and static, while others (such as photos being resized on-the-fly) may be private resources requiring some form of server-side processing; the latter is outside the scope of this specification.

@pmjones
Copy link

pmjones commented Oct 17, 2016

The directory structure reminds me of the Solar packaging scheme. See A.6.4. at the end: http://solarphp.com/manual/appendix-standards.system

@pmjones
Copy link

pmjones commented Oct 17, 2016

@harikt also has a packaging system similar to this: https://github.com/harikt/psr7-asset

@xtuc
Copy link

xtuc commented Oct 17, 2016

@mindplay-dk Hi,

I guess most of the application written in PHP uses Composer.

NPM downloads its content in a given folder. You can of course modify this folder.

From my point of view the PSR should just explain that behavior.

When packages are downloaded, assets are put (moved, copied, symlinked, ...) in that folder.

The PHP framework (if any) should deal with it or as with NPM the developper must deal with it.

A use case: If you have a React front-end, you might use some PHP packages assets. I would use a tool (grunt, gulp, webpack, …) instead of serving statically.

Symfony3 drops its support for Assetic, the asset pipeline. The reason is that other tools are better for that. This is sad because assets pipeline are very usefull. Rails one is still very good.

I rarely ran into these kind of problem. I explained my point of view as asked on the ML.

@kelunik
Copy link

kelunik commented Oct 17, 2016

MUST respond with a 405 Method Not Allowed status code for requests with an applicable path and any HTTP method other than GET.

This misses HEAD.

@mindplay-dk
Copy link
Author

@xtuc I personally view build tools as design-time tools. When I use compilers and minifiers etc., I run them at design-time, and check the resulting static assets into source-control, so that they application is ready to deploy and run without run-time dependencies on development tools like compilers and minifiers.

That's a personal point of view though, and this pattern doesn't prevent that - you could choose to tie in your build-tools for on-the-fly processing via middleware or a front-controller, as a middle-layer between your source-files and the actual (virtual) assets.

I don't think this idea conflicts with those?

@mindplay-dk
Copy link
Author

@pmjones @harikt would your systems be able to support this standard? (I'm not asking if you would, merely trying to assert if this standard can be met by existing related tools.)

@judgej
Copy link

judgej commented Oct 17, 2016

I don't think the URL scheme should be absolute, as it makes assumptions about the application structure and location:

/assets/a/b/js/main.js
/assets/a/b/css/main.css

The paths should be relative to [presumably] the application root path, which may or may not be "/".

Doing this will also ensure absolute paths are not coded into the stylesheet and JS assets, making it more robust.

@xtuc
Copy link

xtuc commented Oct 17, 2016

@mindplay-dk For convenience I also build my assets locally and add them into git. I'm sure this is not good practice.

Assets shouldn't be public by default. Webpack for example, only the resulting assets should be public. This is anyways the developer's decision.

@pmjones The restrictif directory structure must be implemented (omitting the specification part) by each package owner. I don't think that a good idea.

@mindplay-dk
Copy link
Author

@xtuc

For convenience I also build my assets locally and add them into git. I'm sure this is not good practice.

I think that's very good practice. You should not have run-time dependencies on design-time tools.

Assets shouldn't be public by default. Webpack for example, only the resulting assets should be public. This is anyways the developer's decision.

Of course, but these are not "public static assets", which is what this standard covers.

Other resources, such as SASS, LESS, Typescript etc. files should be put somewhere else, and build from there into assets.

@mindplay-dk
Copy link
Author

@judgej

I don't think the URL scheme should be absolute, as it makes assumptions about the application structure and location

I thought about that, and I believe it's actually okay to make assumptions about the locations of static assets.

A Composer package, for any given project, can be installed only once - this isn't node/npm, and namespace in PHP are global, which prevents you from installing more than one version of the same package.

In other words, the application structure is absolute - each vendor package installed by a project can be uniquely identified with two pieces of information: vendor name and package name.

My goal is to keep things simple by observing the same limitation when it comes to assets: the path to an asset provided by any vendor package installed by a project should be determined by those two pieces of information only.

Introducing additional variables creates complexity, and adds no value - you still can only have one copy of the same package.

By the way, it's expected that the root project itself, if it provides any static assets, also does so following the same convention - the root package must have a vendor/package name, and must deliver it's assets the same way. This ensure the paths do not change depending on whether a package is installed as a dependency or is currently the root project - so the asset paths will be consistent and predictable in either case.

Doing this will also ensure absolute paths are not coded into the stylesheet and JS assets, making it more robust.

A practical reason for the consistent, predictable URLs, is dynamic loaders - if you use a dynamic AMD, SystemJS (or other module format) loader, you can configure this once, for your entire project, using a single convention. This can eliminate the need to hardcode absolute paths, and/or add more configuration for every new package you add on.

With that said, if somebody prefers the simplicity of hardcoding absolute paths, I don't want to dictate to anyone that they should be using something more complex. If you prefer to keep it simple, that should be your choice.

@harikt
Copy link

harikt commented Oct 17, 2016

@mindplay-dk yes, this is some what similar or resembles https://github.com/harikt/psr7-asset . Some things are missing like E-Tag , Last-Modified etc.

@mindplay-dk
Copy link
Author

@harikt looks like cache headers are missing either way? Likely sending assets with every request is not desirable in production ;-)

@theofidry
Copy link

theofidry commented Oct 17, 2016

Unless I'm missing something @mindplay-dk, isn't it managing assets something a tool such as Puli would be more appropriate than Composer? It stills need some polishing, but I believe it broke down the problem a lot and I hardly see Composer doing that job instead of a dedicated tool.

@dracony
Copy link

dracony commented Oct 17, 2016

Is there any need for this actually? Today it's much easier to manage web stuff with tools like gulp that play nicely with bower etc.
The only example of when PHP packages include assets seem to be numerous framework bundles, and those are not interpolable at all.

Any links tp projects that would have benefited from such kind of generalization?

@mindplay-dk
Copy link
Author

@theofidry

isn't it managing assets something a tool such as Puli would be more appropriate than Composer?

What's "appropriate" depends entirely on your needs.

This spec isn't about asset management - only about delivery.

In a sense, this is about avoiding asset management, so, no, this isn't anything like Puli - in a sense it's the opposite of Puli.

I hardly see Composer doing that job instead of a dedicated tool.

Agreed, and that's not the goal - if what you need/want is an asset manager, this isn't that.

@dracony

Is there any need for this actually?

I need it. We use a very similar pattern at work, so we need it, yes.

I think others could find a need for it as well - delivering assets to the client is pretty fundamental. In many cases, there is no real justification or need for anything more complex, so yeah, I believe there's a very real need.

Today it's much easier to manage web stuff with tools like gulp that play nicely with bower etc.

You're free to use npm, gulp, bower, etc. - on at least one project, we do use gulp alongside this pattern. In that project, we use gulp internally within that project, e.g. offline, and the static assets are emitted by gulp into a folder that gets checked in.

The only example of when PHP packages include assets seem to be numerous framework bundles

This is part of my motivation. We have assets that are a direct dependency on controllers/middleware/other server-side components, and we need a means of shipping those with the package.

Any links tp projects that would have benefited from such kind of generalization?

Most of our internal projects would - it's all proprietary, none of it open-source, so I can't provide an example.

But again, that's my motivation - the reason people don't generally ship their front-end dependencies with their Composer packages, is because there's no simple, practical, standardized way to do that. Basically, all means of doing so are proprietary "bundles", package managers, etc.

That, and/or running through some kind of server-side front-controller, middleware, etc. for every asset, which is neither simple nor fast.

In a nutshell, the only thing I'm trying to accomplish with this PSR, is provide a means of including client-side dependencies with my server-side packages. I'm not trying to replace your build tools, package managers, assets managers, or anything else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment