Added assets hashing after build, added favicon

This commit is contained in:
Igor Scheller 2021-08-10 11:19:58 +02:00
parent 59e54fc1f5
commit b842466b3a
14 changed files with 2063 additions and 1905 deletions

View File

@ -22,6 +22,7 @@ return [
\Engelsystem\Http\ResponseServiceProvider::class, \Engelsystem\Http\ResponseServiceProvider::class,
\Engelsystem\Http\Psr7ServiceProvider::class, \Engelsystem\Http\Psr7ServiceProvider::class,
\Engelsystem\Helpers\AuthenticatorServiceProvider::class, \Engelsystem\Helpers\AuthenticatorServiceProvider::class,
\Engelsystem\Helpers\AssetsServiceProvider::class,
\Engelsystem\Renderer\TwigServiceProvider::class, \Engelsystem\Renderer\TwigServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class, \Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class, \Engelsystem\Middleware\RequestHandlerServiceProvider::class,
@ -33,7 +34,7 @@ return [
\Engelsystem\Helpers\VersionServiceProvider::class, \Engelsystem\Helpers\VersionServiceProvider::class,
\Engelsystem\Mail\MailerServiceProvider::class, \Engelsystem\Mail\MailerServiceProvider::class,
\Engelsystem\Http\HttpClientServiceProvider::class, \Engelsystem\Http\HttpClientServiceProvider::class,
\Engelsystem\Helpers\DumpServerServiceProvider::class \Engelsystem\Helpers\DumpServerServiceProvider::class,
], ],
// Application middleware // Application middleware

View File

@ -39,6 +39,7 @@
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.1.2", "terser-webpack-plugin": "^5.1.2",
"webpack": "^5.37.1", "webpack": "^5.37.1",
"webpack-cli": "^4.7.0" "webpack-cli": "^4.7.0",
"webpack-manifest-plugin": "^5.0.0"
} }
} }

View File

@ -8,6 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="icon" href="{{ asset('assets/angel.svg') }}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/theme' ~ themeId ~ '.css') }}"/> <link rel="stylesheet" type="text/css" href="{{ asset('assets/theme' ~ themeId ~ '.css') }}"/>
<script type="text/javascript" src="{{ asset('assets/vendor.js') }}"></script> <script type="text/javascript" src="{{ asset('assets/vendor.js') }}"></script>

View File

@ -109,7 +109,9 @@ class Application extends Container
$this->instance('path', $appPath); $this->instance('path', $appPath);
$this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config'); $this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config');
$this->instance('path.resources', $appPath . DIRECTORY_SEPARATOR . 'resources'); $this->instance('path.resources', $appPath . DIRECTORY_SEPARATOR . 'resources');
$this->instance('path.public', $appPath . DIRECTORY_SEPARATOR . 'public');
$this->instance('path.assets', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'assets'); $this->instance('path.assets', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'assets');
$this->instance('path.assets.public', $this->get('path.public') . DIRECTORY_SEPARATOR . 'assets');
$this->instance('path.lang', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'lang'); $this->instance('path.lang', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'lang');
$this->instance('path.views', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'views'); $this->instance('path.views', $this->get('path.resources') . DIRECTORY_SEPARATOR . 'views');
$this->instance('path.storage', $appPath . DIRECTORY_SEPARATOR . 'storage'); $this->instance('path.storage', $appPath . DIRECTORY_SEPARATOR . 'storage');

38
src/Helpers/Assets.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace Engelsystem\Helpers;
class Assets
{
/** @var string */
protected string $assetsPath;
/** @var string */
protected string $manifestFile = 'manifest.json';
/**
* @param string $assetsPath Directory containing assets
*/
public function __construct(string $assetsPath)
{
$this->assetsPath = $assetsPath;
}
/**
* @param string $asset
* @return string
*/
public function getAssetPath(string $asset): string
{
$manifest = $this->assetsPath . DIRECTORY_SEPARATOR . $this->manifestFile;
if (is_readable($manifest)) {
$manifest = json_decode(file_get_contents($manifest), true);
if (isset($manifest[$asset])) {
$asset = $manifest[$asset];
}
}
return $asset;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Engelsystem\Helpers;
use Engelsystem\Container\ServiceProvider;
class AssetsServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(Assets::class)
->needs('$assetsPath')
->give($this->app->get('path.assets.public'));
}
}

View File

@ -2,20 +2,27 @@
namespace Engelsystem\Renderer\Twig\Extensions; namespace Engelsystem\Renderer\Twig\Extensions;
use Engelsystem\Helpers\Assets as AssetsProvider;
use Engelsystem\Http\UrlGeneratorInterface; use Engelsystem\Http\UrlGeneratorInterface;
use Illuminate\Support\Str;
use Twig\Extension\AbstractExtension as TwigExtension; use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\TwigFunction; use Twig\TwigFunction;
class Assets extends TwigExtension class Assets extends TwigExtension
{ {
/** @var AssetsProvider */
protected $assets;
/** @var UrlGeneratorInterface */ /** @var UrlGeneratorInterface */
protected $urlGenerator; protected $urlGenerator;
/** /**
* @param AssetsProvider $assets
* @param UrlGeneratorInterface $urlGenerator * @param UrlGeneratorInterface $urlGenerator
*/ */
public function __construct(UrlGeneratorInterface $urlGenerator) public function __construct(AssetsProvider $assets, UrlGeneratorInterface $urlGenerator)
{ {
$this->assets = $assets;
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
} }
@ -36,6 +43,10 @@ class Assets extends TwigExtension
public function getAsset(string $path): string public function getAsset(string $path): string
{ {
$path = ltrim($path, '/'); $path = ltrim($path, '/');
if (Str::startsWith($path, 'assets/')) {
$asset = Str::replaceFirst('assets/', '', $path);
$path = 'assets/' . $this->assets->getAssetPath($asset);
}
return $this->urlGenerator->to('/' . $path); return $this->urlGenerator->to('/' . $path);
} }

View File

@ -55,6 +55,8 @@ class ApplicationTest extends TestCase
$this->assertTrue($app->has('path.cache')); $this->assertTrue($app->has('path.cache'));
$this->assertTrue($app->has('path.cache.routes')); $this->assertTrue($app->has('path.cache.routes'));
$this->assertTrue($app->has('path.cache.views')); $this->assertTrue($app->has('path.cache.views'));
$this->assertTrue($app->has('path.public'));
$this->assertTrue($app->has('path.assets.public'));
$this->assertEquals(realpath('.'), $app->path()); $this->assertEquals(realpath('.'), $app->path());
$this->assertEquals(realpath('.') . '/config', $app->get('path.config')); $this->assertEquals(realpath('.') . '/config', $app->get('path.config'));

View File

@ -0,0 +1,25 @@
<?php
namespace Engelsystem\Test\Unit\Helpers;
use Engelsystem\Application;
use Engelsystem\Helpers\Assets;
use Engelsystem\Helpers\AssetsServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
class AssetsServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Helpers\AssetsServiceProvider::register
*/
public function testRegister()
{
$app = new Application();
$app->instance('path.assets.public', '/tmp');
$serviceProvider = new AssetsServiceProvider($app);
$serviceProvider->register();
$this->assertArrayHasKey(Assets::class, $app->contextual);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Helpers;
use Engelsystem\Helpers\Assets;
use Engelsystem\Test\Unit\TestCase;
class AssetsTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Assets::__construct
* @covers \Engelsystem\Helpers\Assets::getAssetPath
*/
public function testGetAssetPath()
{
$assets = new Assets('/foo/bar');
$this->assertEquals('lorem.bar', $assets->getAssetPath('lorem.bar'));
$assets = new Assets(__DIR__ . '/Stub/files');
$this->assertEquals('something.xyz', $assets->getAssetPath('something.xyz'));
$this->assertEquals('lorem-hashed.ipsum', $assets->getAssetPath('foo.bar'));
}
}

View File

@ -0,0 +1,3 @@
{
"foo.bar": "lorem-hashed.ipsum"
}

View File

@ -2,6 +2,7 @@
namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions; namespace Engelsystem\Test\Unit\Renderer\Twig\Extensions;
use Engelsystem\Helpers\Assets as AssetsProvider;
use Engelsystem\Http\UrlGenerator; use Engelsystem\Http\UrlGenerator;
use Engelsystem\Renderer\Twig\Extensions\Assets; use Engelsystem\Renderer\Twig\Extensions\Assets;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
@ -14,10 +15,12 @@ class AssetsTest extends ExtensionTest
*/ */
public function testGetFunctions() public function testGetFunctions()
{ {
/** @var UrlGenerator|MockObject $urlGenerator */ /** @var UrlGenerator&MockObject $urlGenerator */
$urlGenerator = $this->createMock(UrlGenerator::class); $urlGenerator = $this->createMock(UrlGenerator::class);
/** @var AssetsProvider&MockObject $assets */
$assets = $this->createMock(AssetsProvider::class);
$extension = new Assets($urlGenerator); $extension = new Assets($assets, $urlGenerator);
$functions = $extension->getFunctions(); $functions = $extension->getFunctions();
$this->assertExtensionExists('asset', [$extension, 'getAsset'], $functions); $this->assertExtensionExists('asset', [$extension, 'getAsset'], $functions);
@ -28,20 +31,35 @@ class AssetsTest extends ExtensionTest
*/ */
public function testGetAsset() public function testGetAsset()
{ {
/** @var UrlGenerator|MockObject $urlGenerator */ /** @var UrlGenerator&MockObject $urlGenerator */
$urlGenerator = $this->createMock(UrlGenerator::class); $urlGenerator = $this->createMock(UrlGenerator::class);
/** @var AssetsProvider&MockObject $assets */
$assets = $this->createMock(AssetsProvider::class);
$urlGenerator->expects($this->exactly(2)) $urlGenerator->expects($this->exactly(4))
->method('to') ->method('to')
->with('/assets/foo.css') ->withConsecutive(['/test.png'], ['/assets/foo.css'], ['/assets/bar.css'], ['/assets/lorem-hashed.js'])
->willReturn('https://foo.bar/project/assets/foo.css'); ->willReturnCallback(function ($path) {
return 'https://foo.bar/project' . $path;
});
$extension = new Assets($urlGenerator); $assets->expects($this->exactly(3))
->method('getAssetPath')
->withConsecutive(['foo.css'], ['bar.css'], ['lorem.js'])
->willReturnOnConsecutiveCalls('foo.css', 'bar.css', 'lorem-hashed.js');
$extension = new Assets($assets, $urlGenerator);
$return = $extension->getAsset('test.png');
$this->assertEquals('https://foo.bar/project/test.png', $return);
$return = $extension->getAsset('assets/foo.css'); $return = $extension->getAsset('assets/foo.css');
$this->assertEquals('https://foo.bar/project/assets/foo.css', $return); $this->assertEquals('https://foo.bar/project/assets/foo.css', $return);
$return = $extension->getAsset('/assets/foo.css'); $return = $extension->getAsset('/assets/bar.css');
$this->assertEquals('https://foo.bar/project/assets/foo.css', $return); $this->assertEquals('https://foo.bar/project/assets/bar.css', $return);
$return = $extension->getAsset('assets/lorem.js');
$this->assertEquals('https://foo.bar/project/assets/lorem-hashed.js', $return);
} }
} }

View File

@ -4,6 +4,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const nodeEnv = (process.env.NODE_ENV || 'development').trim(); const nodeEnv = (process.env.NODE_ENV || 'development').trim();
const {WebpackManifestPlugin} = require('webpack-manifest-plugin');
const fs = require('fs'); const fs = require('fs');
// eslint-disable-next-line // eslint-disable-next-line
@ -18,9 +19,10 @@ const plugins = [
}, },
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '[name].css', filename: '[name]-[contenthash].css',
chunkFilename: '[id]-[hash].css', chunkFilename: '[id]-[contenthash].css',
}), }),
new WebpackManifestPlugin({}),
]; ];
let themeFileNameRegex = /theme\d+/; let themeFileNameRegex = /theme\d+/;
@ -50,7 +52,7 @@ module.exports = {
}, },
output: { output: {
path: path.resolve('public/assets'), path: path.resolve('public/assets'),
filename: '[name].js', filename: '[name]-[contenthash].js',
publicPath: '', publicPath: '',
clean: true, clean: true,
}, },

3796
yarn.lock

File diff suppressed because it is too large Load Diff