NEW Make resources dir configurable (#8519)

* NEW Make resources dir configurable.

* Removing reference to old `resources` and updating doc #8519

* Rrtarget to 4.4 release.

* DOC Reference SS_RESOURCES_DIR in Environment doc.

* API Add a Resources method to SilverStripe\Core\Manifest\Module to read the resources-dir from composer.json

* Clean up reference to SS_RESOURCES_DIR env var

* Set default resources-dir

* Update test to use RESOURCES_DIR const in expected resource url method

* Correcting typos

Co-Authored-By: maxime-rainville <maxime@rainville.me>

* MINOR Correctubg minor typos

* DOCS Document the intricacies of exposing static assets.
This commit is contained in:
Maxime Rainville 2019-01-09 15:35:45 +13:00 committed by Aaron Carlino
parent 7c96feef37
commit 1e01deea39
19 changed files with 241 additions and 37 deletions

View File

@ -1372,3 +1372,7 @@ warnings:
'THIRDPARTY_DIR':
message: 'Path constants have been deprecated. Use the Requirements and ModuleResourceLoader APIs'
url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#module-paths'
'SilverStripe\Core\Manifest\ManifestFileFinder::RESOURCES_DIR':
message: 'Use global const RESOURCES_DIR'
url: 'https://docs.silverstripe.org/en/4/changelogs/4.4.0#resources-dir'
replacement: 'RESOURCES_DIR'

View File

@ -34,7 +34,7 @@
"psr/container-implementation": "1.0.0",
"silverstripe/config": "^1@dev",
"silverstripe/assets": "^1@dev",
"silverstripe/vendor-plugin": "^1.0",
"silverstripe/vendor-plugin": "^1.4",
"swiftmailer/swiftmailer": "~5.4",
"symfony/cache": "^3.3@dev",
"symfony/config": "^3.2",

View File

@ -11,7 +11,7 @@ Directory | Description
--------- | -----------
`public/` | Webserver public webroot
`public/assets/` | Images and other files uploaded via the SilverStripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS.
`public/resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location.
`public/_resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location (this can be altered by configuration).
`vendor/` | SilverStripe modules and other supporting libraries (the framework is in `vendor/silverstripe/framework`)
`themes/` | Standard theme installation location

View File

@ -11,6 +11,68 @@ The examples below are using certain folder naming conventions (CSS files in `cs
SilverStripe core modules like `cms` use a different naming convention (CSS and JavaScript files in `client/src/`).
The `Requirements` class can work with arbitrary file paths.
## Exposing static assets
Before requiring a static assets file in PHP code or in a template, those assets need to be "exposed". This process allows SilverStripe projects and SilverStripe modules to make static asset files available via the web server without directly exposing PHP files.
### Configuring your project "exposed" folders
Exposed assets are made available in your web root in a dedicated "resources" directory. Prior to SilverStripe 4.4, the name of this directory was hardcoded to `resources`. In SilverStripe 4.4 and above, the name of the resources directory can be configured by defining the `extra.resources-dir` key in your `composer.json`. SilverStripe projects created from `silverstripe/installer` 4.4 and above will automatically be configured to use `_resources` as their resource directory.
Each folder that needs to be exposed must be entered under the `extra.expose` key in your `composer.json` file. Module developers should use a path relative to the root of their module (don't include the "vendor/package-developer/package-name" path).
This is a sample `composer.json` configured to expose some assets.
```json
{
"name": "app/myproject",
"type": "silverstripe-project",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev"
},
"extra": {
"resources-dir": "_resources",
"expose": [
"app/client/dist",
"app/images"
]
}
}
```
Files contained inside the `app/client/dist` and `app/images` will be made publicly available under the `_resources` directory.
SilverStripe projects should not track the "resources" directory in their source control system.
### Exposing assets in the web root
SilverStripe projects ship with `silverstripe/vendor-plugin`. This composer plugin automatically tries to expose assets of modules after installation or after an update.
Developers can explicitly expose static assets by calling `composer vendor-expose`. This is necessary after updating your `resources-dir` or `expose` configuration in your `composer.json` file.
`composer vendor-expose` accepts an optional `method` argument (e.g.: `composer vendor-expose auto`). This controls how the files are exposed in the "resources" directory:
* `none` disables all symlink / copy,
* `copy` copy the exposed files,
* `symlink` create symlinks to the exposed folder,
* `junction` uses a junction (windows only),
* `auto` performs symlink (or junction on windows), but fail over to copy.
### Referencing exposed assets
When referencing exposed assets, you should not use their web server path. e.g.:
```php
// When referencing project files, use the same path defined in your `composer.json` file.
Requirements::javascript('app/client/dist/bundle.js');
// When referencing theme files, use a path relative to the root of your project
Requirements::javascript('themes/simple/javascript/script.js');
// When referencing files from a module, you need to prefix the path with the module name.
Requirements::javascript('silverstripe/admin:client/dist/js/bundle.js');
```
When rendered in HTML code, these URLs will be rewritten to their matching path inside the "resources" directory.
## Template Requirements API
**<my-module-dir>/templates/SomeTemplate.ss**
@ -179,7 +241,7 @@ is not appropriate. Normally a single backend is used for all site assets, so a
replaced. For instance, the below will set a new set of dependencies to write to `app/javascript/combined`
```yaml
```yml
---
Name: myrequirements
---

View File

@ -61,7 +61,7 @@ Note that SilverStripe modules have the following distinct characteristics:
- Any folder which should be exposed to the public webroot must be declared in the `extra.expose` config.
These paths will be automatically rewritten to public urls which don't directly serve files from the `vendor`
folder. For instance, `vendor/my-vendor/my-module/client` will be rewritten to
`resources/my-vendor/my-module/client`.
`_resources/my-vendor/my-module/client`.
- Any module which uses the folder expose feature must require `silverstripe/vendor-plugin` in order to
support automatic rewriting and linking. For more information on this plugin you can see the
[silverstripe/vendor-plugin github page](https://github.com/silverstripe/vendor-plugin).

View File

@ -1022,14 +1022,14 @@ If you are using a modified `index.php`, `.htaccess`, or `web.config`, you will
* `assets`
* Any `favicon` files
* Other common files that should be accssible in your project webroot (example: `robots.txt`)
* Delete the root `resources` directory if present.
* Delete the root `resources` or `_resources` directories if present.
* Run the following command `composer vendor-expose` to make static assets files accessible via the `public` directory.
If you are upgrading from SilverStripe 4.0 to SilverStripe 4.1 (or above), you'll need to update `index.php` before moving it to the public folder. You can get a copy of the generic `index.php` file from `vendor/silverstripe/recipe-core/public`. If you've made modifications to your `index.php` file, you'll need to replicate those into the new `public/index.php` file.
### Finalising the web root migration
You'll need to update your server configuration to point to the public directory rather than the root of your project.
Update your `.gitignore` file so `assets` and `resources` are still ignored when located under the `public` folder.
Update your `.gitignore` file so `assets` and `_resources` (or `resources` if using a pre SilverStripe 4.4 release) are still ignored when located under the `public` folder.
Your project should still be functional, although you may now be missing some static assets.
This is a good point to commit your changes to your source control system before moving on to the next step.

View File

@ -3,12 +3,31 @@
## Overview {#overview}
- [Correct PHP types are now returned from database queries](/developer_guides/model/sql_select#data-types)
- The name of the directory where vendor module resources are exposed can now be configured by defining a `extra.resources-dir` key in your `composer.json` file. If the key is not set, it will automatically default to `resources`. New projects will be preset to `_resources`. This will avoid potential conflict with SiteTree URL Segments.
- dev/build is now non-destructive for all Enums, not just ClassNames. This means your data won't be lost if you're switching between versions, but watch out for code that breaks when it sees an unrecognised value!
## Upgrading {#upgrading}
- dev/build is now non-destructive for all Enums, not just ClassNames. This means your data won't be lost if you're switching between versions, but watch out for code that breaks when it sees an unrecognised value!
### Adopting to new `_resources` directory
1. Update your `.gitignore` file to ignore the new `_resources` directory. This file is typically located in the root of your project or in the `public` folder.
2. Add a new `extra.resources-dir` key to your composer file.
```js
{
// ...
"extra": {
// ...
"resources-dir": "_resources"
}
}
```
3. Expose your vendor assets by running `composer vendor-expose`.
4. Remove the old `resources` folder. This folder will be located in the `public` folder if you have adopted the public web root, or in the root of your project if you haven't.
You may also need to update your server configuration if you have applied special conditions to the `resources` path.
## Changes to internal APIs
- `PDOQuery::__construct()` now has a 2nd argument. If you have subclassed PDOQuery and overridden __construct()
you may see an E_STRICT error
- The name of the directory where vendor module resources are exposed can now be configured by adding a `extra.resources-dir` key to your composer file. The new default in `silverstripe/installer` has been changed to `_resources` rather than `resources`. This allows you to use `resources` as a URL segment or a route.

View File

@ -133,15 +133,15 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
$exists = $resource->exists();
$absolutePath = $resource->getPath();
// Rewrite to resources with public directory
// Rewrite to _resources with public directory
if (Director::publicDir()) {
// All resources mapped directly to resources/
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
// All resources mapped directly to _resources/
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
} elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
// @todo Non-public dir support will be removed in 5.0, so remove this block there
// If there is no public folder, map to resources/ but trim leading vendor/ too (4.0 compat)
// If there is no public folder, map to _resources/ but trim leading vendor/ too (4.0 compat)
$relativePath = Path::join(
ManifestFileFinder::RESOURCES_DIR,
RESOURCES_DIR,
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
);
}
@ -167,10 +167,10 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
$absolutePath = Path::join(Director::baseFolder(), $relativePath);
$exists = file_exists($absolutePath);
// Rewrite vendor/ to resources/ folder
// Rewrite vendor/ to _resources/ folder
if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
$relativePath = Path::join(
ManifestFileFinder::RESOURCES_DIR,
RESOURCES_DIR,
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
);
}
@ -200,7 +200,7 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
/**
* Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
* public/resources/
* public/_resources/
*
* @param string $relativePath
* @return array List of [$exists, $absolutePath, $relativePath]
@ -217,11 +217,11 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
return [true, $publicPath, $relativePath];
}
// Fall back to private path (and assume expose will make this available to resources/)
// Fall back to private path (and assume expose will make this available to _resources/)
$privatePath = Path::join(Director::baseFolder(), $relativePath);
if (!$publicOnly && file_exists($privatePath)) {
// String is private but exposed to resources/, so rewrite to the symlinked base
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
// String is private but exposed to _resources/, so rewrite to the symlinked base
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
return [true, $privatePath, $relativePath];
}

View File

@ -22,7 +22,11 @@ class ManifestFileFinder extends FileFinder
const LANG_DIR = 'lang';
const TESTS_DIR = 'tests';
const VENDOR_DIR = 'vendor';
const RESOURCES_DIR = 'resources';
/**
* @deprecated 4.4.0:5.0.0 Use global `RESOURCES_DIR` instead.
*/
const RESOURCES_DIR = RESOURCES_DIR;
protected static $default_options = array(
'include_themes' => false,
@ -241,7 +245,7 @@ class ManifestFileFinder extends FileFinder
}
// Ignore these dirs in the root only
if ($depth === 1 && in_array($basename, [ASSETS_DIR, self::RESOURCES_DIR])) {
if ($depth === 1 && in_array($basename, [ASSETS_DIR, RESOURCES_DIR])) {
return true;
}

View File

@ -8,6 +8,10 @@ use Serializable;
use SilverStripe\Core\Path;
use SilverStripe\Dev\Deprecation;
/**
* Abstraction of a PHP Package. Can be used to retrieve information about SilverStripe modules, and other packages
* managed via composer, by reading their `composer.json` file.
*/
class Module implements Serializable
{
/**
@ -124,6 +128,20 @@ class Module implements Serializable
return basename($this->path);
}
/**
* Name of the resource directory where vendor resources should be exposed as defined by the `extra.resources-dir`
* key in the composer file. A blank string will will be returned if the key is undefined.
*
* Only applicable when reading the composer file for the main project.
* @return string
*/
public function getResourcesDir()
{
return isset($this->composerData['extra']['resources-dir'])
? $this->composerData['extra']['resources-dir']
: '';
}
/**
* Get base path for this module
*

View File

@ -49,7 +49,7 @@ class ModuleResource
/**
* Return the full filesystem path to this resource.
*
* Note: In the case that this resource is mapped to the `resources` folder, this will
* Note: In the case that this resource is mapped to the `_resources` folder, this will
* return the original rather than the copy / symlink.
*
* @return string Path with no trailing slash E.g. /var/www/module
@ -62,7 +62,7 @@ class ModuleResource
/**
* Get the path of this resource relative to the base path.
*
* Note: In the case that this resource is mapped to the `resources` folder, this will
* Note: In the case that this resource is mapped to the `_resources` folder, this will
* return the original rather than the copy / symlink.
*
* @return string Relative path (no leading /)
@ -81,7 +81,7 @@ class ModuleResource
* Public URL to this resource.
* Note: May be either absolute url, or root-relative url
*
* In the case that this resource is mapped to the `resources` folder this
* In the case that this resource is mapped to the `_resources` folder this
* will be the mapped url rather than the original path.
*
* @return string

View File

@ -54,9 +54,9 @@ class Installer
protected function installHeader()
{
$clientPath = PUBLIC_DIR
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
: 'resources/silverstripe/framework/src/Dev/Install/client';
$clientPath = RESOURCES_DIR . (PUBLIC_DIR
? '/vendor/silverstripe/framework/src/Dev/Install/client'
: '/silverstripe/framework/src/Dev/Install/client');
?>
<html>
<head>

View File

@ -106,9 +106,9 @@ if ($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) {
// Path to client resources (copied through silverstripe/vendor-plugin)
$base = rtrim(BASE_URL, '/') . '/';
$clientPath = PUBLIC_DIR
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
: 'resources/silverstripe/framework/src/Dev/Install/client';
$clientPath = RESOURCES_DIR . (PUBLIC_DIR
? '/vendor/silverstripe/framework/src/Dev/Install/client'
: '/silverstripe/framework/src/Dev/Install/client');
// If already installed, ensure the user clicked "reinstall"
$expectedArg = $alreadyInstalled ? 'reinstall' : 'go';

View File

@ -27,6 +27,7 @@ use SilverStripe\Core\TempFolder;
* - PUBLIC_PATH: Absolute path to webroot, e.g. "/var/www/project/public"
* - THIRDPARTY_DIR: Path relative to webroot, e.g. "framework/thirdparty"
* - THIRDPARTY_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/thirdparty"
* - RESOURCES_DIR: Name of the directory where vendor assets will be exposed, e.g. "_ressources"
*/
require_once __DIR__ . '/functions.php';
@ -198,3 +199,18 @@ if (!defined('TEMP_PATH')) {
if (!defined('TEMP_FOLDER')) {
define('TEMP_FOLDER', TEMP_PATH);
}
// Define the Ressource Dir constant that will be use to exposed vendor assets
if (!defined('RESOURCES_DIR')) {
$project = new SilverStripe\Core\Manifest\Module(BASE_PATH, BASE_PATH);
$resourcesDir = $project->getResourcesDir() ?: 'resources';
if (preg_match('/^[_\-a-z0-9]+$/i', $resourcesDir)) {
define('RESOURCES_DIR', $resourcesDir);
} else {
throw new LogicException(sprintf(
'Resources dir error: "%s" is not a valid resources directory name. Update the ' .
'`extra.resources-dir` key in your composer.json file',
$resourcesDir
));
}
}

View File

@ -36,7 +36,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/basemodule/client/file.js'
);
$this->assertEquals(
'/resources/basemodule/client/file.js?m=' . $mtime,
'/'. RESOURCES_DIR . '/basemodule/client/file.js?m=' . $mtime,
$generator->urlForResource('basemodule/client/file.js')
);
}
@ -49,7 +49,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
);
$this->assertEquals(
'/resources/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
'/'. RESOURCES_DIR . '/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
$generator->urlForResource('vendor/silverstripe/mymodule/client/style.css')
);
}
@ -72,7 +72,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
);
$this->assertEquals(
'/resources/basemodule/client/file.js?m=' . $mtime,
'/'. RESOURCES_DIR . '/basemodule/client/file.js?m=' . $mtime,
$generator->urlForResource('basemodule/client/file.js')
);
}
@ -89,7 +89,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
__DIR__ . '/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
);
$this->assertEquals(
'/resources/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
'/'. RESOURCES_DIR . '/vendor/silverstripe/mymodule/client/style.css?m=' . $mtime,
$generator->urlForResource($module->getResource('client/style.css'))
);
}

View File

@ -42,7 +42,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/module/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/module/client/script.js?m=',
$resource->getURL()
);
}
@ -60,7 +60,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/vendor/silverstripe/modulec/client/script.js?m=',
$resource->getURL()
);
}
@ -80,7 +80,7 @@ class ModuleResourceTest extends SapphireTest
$resource->getPath()
);
$this->assertStringStartsWith(
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
'/basefolder/'. RESOURCES_DIR . '/vendor/silverstripe/modulec/client/script.js?m=',
$resource->getURL()
);
}

View File

@ -0,0 +1,24 @@
<?php
namespace SilverStripe\Core\Tests\Manifest;
use SilverStripe\Control\Director;
use SilverStripe\Core\Manifest\Module;
use SilverStripe\Dev\SapphireTest;
class ModuleTest extends SapphireTest
{
public function testUnsetResourcesDir()
{
$path = __DIR__ . '/fixtures/ss-projects/withoutCustomResourcesDir';
$module = new Module($path, $path);
$this->assertEquals('', $module->getResourcesDir());
}
public function testResourcesDir()
{
$path = __DIR__ . '/fixtures/ss-projects/withCustomResourcesDir';
$module = new Module($path, $path);
$this->assertEquals('customised-resources-dir', $module->getResourcesDir());
}
}

View File

@ -0,0 +1,29 @@
{
"name": "silverstripe/ss44",
"type": "silverstripe-project",
"description": "Fake project using SS 4.4",
"homepage": "https://www.silverstripe.org",
"license": "BSD-3-Clause",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev as 4.4.0"
},
"extra": {
"project-files-installed": [
"app/.htaccess",
"app/_config.php",
"app/_config/mysite.yml",
"app/src/Page.php",
"app/src/PageController.php"
],
"public-files-installed": [
".htaccess",
"index.php",
"install-frameworkmissing.html",
"install.php",
"web.config"
],
"resources-dir": "customised-resources-dir"
},
"prefer-stable": true,
"minimum-stability": "dev"
}

View File

@ -0,0 +1,28 @@
{
"name": "silverstripe/ss44",
"type": "silverstripe-project",
"description": "Fake project using SS 4.4",
"homepage": "https://www.silverstripe.org",
"license": "BSD-3-Clause",
"require": {
"silverstripe/recipe-cms": "4.4.x-dev as 4.4.0"
},
"extra": {
"project-files-installed": [
"app/.htaccess",
"app/_config.php",
"app/_config/mysite.yml",
"app/src/Page.php",
"app/src/PageController.php"
],
"public-files-installed": [
".htaccess",
"index.php",
"install-frameworkmissing.html",
"install.php",
"web.config"
]
},
"prefer-stable": true,
"minimum-stability": "dev"
}