diff --git a/config/static_caching.php b/config/static_caching.php index 46766739505..2af89331ea4 100644 --- a/config/static_caching.php +++ b/config/static_caching.php @@ -142,6 +142,23 @@ \Statamic\StaticCaching\Replacers\NoCacheReplacer::class, ], + /* + |-------------------------------------------------------------------------- + | Script Delivery + |-------------------------------------------------------------------------- + | + | Full measure static caching injects small ' + : ''; + } + public function shouldOutputJs(): bool { return $this->shouldOutputJs; diff --git a/src/StaticCaching/NoCache/ScriptController.php b/src/StaticCaching/NoCache/ScriptController.php new file mode 100644 index 00000000000..e3ec0a111b7 --- /dev/null +++ b/src/StaticCaching/NoCache/ScriptController.php @@ -0,0 +1,37 @@ +response($this->cacher()->getNocacheJs()); + } + + public function csrf(): Response + { + return $this->response($this->cacher()->getCsrfTokenJs()); + } + + private function cacher(): FileCacher + { + $cacher = app(Cacher::class); + + abort_unless($cacher instanceof FileCacher, 404); + + return $cacher; + } + + private function response(string $js): Response + { + return response($js) + ->header('Content-Type', 'application/javascript') + ->header('Cache-Control', 'public, max-age=3600') + ->setEtag(md5($js)); + } +} diff --git a/src/StaticCaching/Replacers/CsrfTokenReplacer.php b/src/StaticCaching/Replacers/CsrfTokenReplacer.php index 485b14564f1..27cb0f8cc0d 100644 --- a/src/StaticCaching/Replacers/CsrfTokenReplacer.php +++ b/src/StaticCaching/Replacers/CsrfTokenReplacer.php @@ -81,9 +81,7 @@ private function modifyFullMeasureResponse(Response $response) Str::position($contents, ''), ])->filter()->min(); - $js = ""; - - $contents = Str::substrReplace($contents, $js, $insertBefore, 0); + $contents = Str::substrReplace($contents, $cacher->getCsrfScript(), $insertBefore, 0); $response->setContent($contents); } diff --git a/src/StaticCaching/Replacers/NoCacheReplacer.php b/src/StaticCaching/Replacers/NoCacheReplacer.php index f7ca32fd97f..3d67e3711e2 100644 --- a/src/StaticCaching/Replacers/NoCacheReplacer.php +++ b/src/StaticCaching/Replacers/NoCacheReplacer.php @@ -94,8 +94,7 @@ private function modifyFullMeasureResponse(Response $response) $contents = $response->getContent(); if ($cacher->shouldOutputJs()) { - $js = $cacher->getNocacheJs(); - $contents = str_replace('', '', $contents); + $contents = str_replace('', $cacher->getNocacheScript().'', $contents); } $contents = str_replace('NOCACHE_PLACEHOLDER', $cacher->getNocachePlaceholder(), $contents); diff --git a/tests/StaticCaching/ExternalScriptDeliveryTest.php b/tests/StaticCaching/ExternalScriptDeliveryTest.php new file mode 100644 index 00000000000..6884c2205ae --- /dev/null +++ b/tests/StaticCaching/ExternalScriptDeliveryTest.php @@ -0,0 +1,76 @@ +set('statamic.static_caching.strategy', 'full'); + $app['config']->set('statamic.static_caching.strategies.full.path', $this->dir = __DIR__.'/static'); + $app['config']->set('statamic.static_caching.script_delivery', 'external'); + + File::delete($this->dir); + } + + public function tearDown(): void + { + File::delete($this->dir); + parent::tearDown(); + } + + #[Test] + public function it_references_the_csrf_and_nocache_scripts_instead_of_inlining_them() + { + $this->withFakeViews(); + $this->viewShouldReturnRaw('layout', '{{ template_content }}'); + $this->viewShouldReturnRaw('default', '{{ csrf_token }}'); + + $this->createPage('about'); + + $expected = 'STATAMIC_CSRF_TOKEN'; + + $response = $this->get('/about')->assertOk(); + + $this->assertEquals($expected, $response->getContent()); + $this->assertStringNotContainsString('(function()', $response->getContent()); + $this->assertEquals($expected, file_get_contents($this->dir.'/about_.html')); + } + + #[Test] + public function the_scripts_are_served_from_routes() + { + $nocache = $this->get('/!/nocache.js')->assertOk(); + $this->assertStringContainsString('application/javascript', $nocache->headers->get('content-type')); + $this->assertEquals(app(Cacher::class)->getNocacheJs(), $nocache->getContent()); + $this->assertStringContainsString("fetch('/!/nocache'", $nocache->getContent()); + + $csrf = $this->get('/!/csrf.js')->assertOk(); + $this->assertStringContainsString('application/javascript', $csrf->headers->get('content-type')); + $this->assertEquals(app(Cacher::class)->getCsrfTokenJs(), $csrf->getContent()); + } + + #[Test] + public function the_routes_are_registered_only_in_external_mode() + { + $this->assertTrue(Route::has('statamic.nocache.js')); + $this->assertTrue(Route::has('statamic.csrf.js')); + } +} diff --git a/tests/StaticCaching/NocacheRouteTest.php b/tests/StaticCaching/NocacheRouteTest.php index f9ae48ecd34..f87de014120 100644 --- a/tests/StaticCaching/NocacheRouteTest.php +++ b/tests/StaticCaching/NocacheRouteTest.php @@ -61,4 +61,15 @@ public function url_is_required() ->postJson('/!/nocache') ->assertJsonValidationErrorFor('url'); } + + #[Test] + public function the_script_routes_are_not_registered_unless_script_delivery_is_external() + { + // Defaults to "inline", so the routes shouldn't exist. + $this->assertFalse(\Illuminate\Support\Facades\Route::has('statamic.nocache.js')); + $this->assertFalse(\Illuminate\Support\Facades\Route::has('statamic.csrf.js')); + + $this->get('/!/nocache.js')->assertNotFound(); + $this->get('/!/csrf.js')->assertNotFound(); + } }