diff --git a/README.md b/README.md
index 3ba7edc..0e50894 100644
--- a/README.md
+++ b/README.md
@@ -45,15 +45,15 @@ php artisan importmap:install
Next, we need to add the following component to our view or layout file:
```blade
-
+
```
Add that between your `
` tags. The `entrypoint` should be the "main" file, commonly the `resources/js/app.js` file, which will be mapped to the `app` module (use the module name, not the file).
-By default the `x-importmap-tags` component assumes your entrypoint module is `app`, which matches the existing `resources/js/app.js` file from Laravel's default scaffolding. You may want to customize the entrypoint, which you can do with the `entrypoint` prop:
+By default the `x-importmap::tags` component assumes your entrypoint module is `app`, which matches the existing `resources/js/app.js` file from Laravel's default scaffolding. You may want to customize the entrypoint, which you can do with the `entrypoint` prop:
```blade
-
+
```
The package will automatically map the `resources/js` folder to your `public/js` folder using Laravel's symlink feature. All you have to do after installing the package is run:
@@ -173,7 +173,7 @@ The version is added as a comment to your pin so you know which version was impo
### Preloading Modules
-To avoid the waterfall effect where the browser has to load one file after another before it can get to the deepest nested import, we support [modulepreload links](https://developers.google.com/web/updates/2017/12/modulepreload). Pinned modules can be preloaded by appending `preload: true` to the pin, like so:
+To avoid the waterfall effect where the browser has to load one file after another before it can get to the deepest nested import, we use [modulepreload links](https://developers.google.com/web/updates/2017/12/modulepreload) by default. If you don't want to preload a dependency, because you want to load it on-demand for efficiency, append `preload: false` to the pin.
```php
Importmap::pinAllFrom("resources/js/", to: "js/", preload: true);
@@ -186,6 +186,9 @@ Which will add the correct `links` tags to your head tag in the HTML document, l
```
+You may add the `AddLinkHeadersForPreloadedPins` middleware to the `web` routes group so these preloaded links are sent as a `Link` header.
+Add the `Tonysm\ImportmapLaravel\Http\Middleware\AddLinkHeadersForPreloadedPins` to the `web` route group so the preloaded modules are sent as the Link headers, which are used in [HTTP/2 Server Push](https://datatracker.ietf.org/doc/html/rfc7540#section-8.2) and [Resource Hints](https://html.spec.whatwg.org/#linkTypes) to push resources to the client as early as possible. Some web servers can pick up this `Link` header and convert them to [Early Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103) responses.
+
## Dependency Maintenance Commands
Maintaining a healthy dependency list can be tricky. Here are a couple of commands to help you with this task.
diff --git a/resources/views/tags.blade.php b/resources/views/components/tags.blade.php
similarity index 52%
rename from resources/views/tags.blade.php
rename to resources/views/components/tags.blade.php
index c61d39d..b632e40 100644
--- a/resources/views/tags.blade.php
+++ b/resources/views/components/tags.blade.php
@@ -1,3 +1,12 @@
+@props(['entrypoint' => 'app', 'nonce' => null, 'importmap' => null])
+
+@php
+ $resolver = new \Tonysm\ImportmapLaravel\AssetResolver();
+
+ $importmaps = $importmap?->asArray($resolver) ?? \Tonysm\ImportmapLaravel\Facades\Importmap::asArray($resolver);
+ $preloadedModules = $importmap?->preloadedModulePaths($resolver) ?? \Tonysm\ImportmapLaravel\Facades\Importmap::preloadedModulePaths($resolver);
+@endphp
+
diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php
index 33a95c7..dcc9031 100644
--- a/src/Commands/InstallCommand.php
+++ b/src/Commands/InstallCommand.php
@@ -63,7 +63,7 @@ private function deleteNpmRelatedFiles(): void
private function publishImportmapFile(): void
{
$this->displayTask('publishing the `routes/importmap.php` file', function () {
- File::copy(dirname(__DIR__, 2).join(DIRECTORY_SEPARATOR, ['', 'stubs', 'routes', 'importmap.php']), base_path(join(DIRECTORY_SEPARATOR, ['routes', 'importmap.php'])));
+ File::copy(dirname(__DIR__, 2).implode(DIRECTORY_SEPARATOR, ['', 'stubs', 'routes', 'importmap.php']), base_path(implode(DIRECTORY_SEPARATOR, ['routes', 'importmap.php'])));
return self::SUCCESS;
});
@@ -140,7 +140,7 @@ private function updateAppLayoutsUsingMix()
$file,
str_replace(
"",
- '',
+ '',
File::get($file),
),
));
@@ -157,7 +157,7 @@ private function updateAppLayoutsUsingVite()
$file,
preg_replace(
'/\@vite.*/',
- '',
+ '',
File::get($file),
),
))
@@ -182,7 +182,7 @@ private function appendImportmapTagsToLayoutsHead(): void
$file,
preg_replace(
'/(\s*)(<\/head>)/',
- "\\1 \n\\1\\2",
+ "\\1 \n\\1\\2",
File::get($file),
),
));
diff --git a/src/Http/Middleware/AddLinkHeadersForPreloadedPins.php b/src/Http/Middleware/AddLinkHeadersForPreloadedPins.php
new file mode 100644
index 0000000..43b0b79
--- /dev/null
+++ b/src/Http/Middleware/AddLinkHeadersForPreloadedPins.php
@@ -0,0 +1,31 @@
+assetsResolver)) {
+ $response->header('Link', collect($preloaded)
+ ->map(fn ($url) => "<{$url}>; rel=\"modulepreload\"")
+ ->join(', '));
+ }
+ });
+ }
+}
diff --git a/src/Importmap.php b/src/Importmap.php
index d62b8e7..5c227dc 100755
--- a/src/Importmap.php
+++ b/src/Importmap.php
@@ -20,12 +20,12 @@ public function __construct(public ?string $rootPath = null)
$this->directories = collect();
}
- public function pin(string $name, string $to = null, bool $preload = false)
+ public function pin(string $name, ?string $to = null, bool $preload = true)
{
$this->packages->add(new MappedFile($name, path: $to ?: "js/{$name}.js", preload: $preload));
}
- public function pinAllFrom(string $dir, string $under = null, string $to = null, bool $preload = false)
+ public function pinAllFrom(string $dir, ?string $under = null, ?string $to = null, bool $preload = true)
{
$this->directories->add(new MappedDirectory($dir, $under, $to, $preload));
}
diff --git a/src/ImportmapLaravelServiceProvider.php b/src/ImportmapLaravelServiceProvider.php
index 69593d0..4ccb11d 100644
--- a/src/ImportmapLaravelServiceProvider.php
+++ b/src/ImportmapLaravelServiceProvider.php
@@ -2,9 +2,9 @@
namespace Tonysm\ImportmapLaravel;
+use Illuminate\View\Compilers\BladeCompiler;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
-use Tonysm\ImportmapLaravel\View\Components;
class ImportmapLaravelServiceProvider extends PackageServiceProvider
{
@@ -19,7 +19,6 @@ public function configurePackage(Package $package): void
->name('importmap')
->hasConfigFile()
->hasViews()
- ->hasViewComponent('importmap', Components\Tags::class)
->hasCommand(Commands\InstallCommand::class)
->hasCommand(Commands\OptimizeCommand::class)
->hasCommand(Commands\ClearCacheCommand::class)
@@ -51,5 +50,14 @@ public function packageBooted()
public_path('js') => resource_path('js'),
]);
}
+
+ $this->configureComponents();
+ }
+
+ private function configureComponents()
+ {
+ $this->callAfterResolving('blade.compiler', function (BladeCompiler $blade) {
+ $blade->anonymousComponentPath(__DIR__.'/../resources/views/components', 'importmap');
+ });
}
}
diff --git a/src/View/Components/Tags.php b/src/View/Components/Tags.php
deleted file mode 100644
index 9222f2e..0000000
--- a/src/View/Components/Tags.php
+++ /dev/null
@@ -1,28 +0,0 @@
- $this->importmap?->asArray($resolver) ?? ImportmapFacade::asArray($resolver),
- 'preloadedModules' => $this->importmap?->preloadedModulePaths($resolver) ?? ImportmapFacade::preloadedModulePaths($resolver),
- ]);
- }
-}
diff --git a/tests/ImportmapTest.php b/tests/ImportmapTest.php
index f794ef5..b61c1b5 100644
--- a/tests/ImportmapTest.php
+++ b/tests/ImportmapTest.php
@@ -15,8 +15,9 @@ protected function setUp(): void
$this->map = new Importmap(rootPath: __DIR__.DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR);
- $this->map->pin('app');
- $this->map->pin('editor', to: 'js/rich_text.js');
+ $this->map->pin('app', preload: false);
+ $this->map->pin('editor', to: 'js/rich_text.js', preload: false);
+ $this->map->pin('not_there', to: 'js/nowhere.js', preload: false);
$this->map->pin('md5', to: 'https://cdn.skypack.dev/md5', preload: true);
$this->map->pinAllFrom('resources/js/controllers', under: 'controllers', to: 'js/controllers', preload: true);
@@ -90,6 +91,7 @@ public function preload_modules_are_included_in_preload_tags()
$this->assertStringContainsString('md5', $preloadingModulePaths);
$this->assertStringContainsString('hello_controller', $preloadingModulePaths);
+ $this->assertStringNotContainsString('not_there', $preloadingModulePaths);
$this->assertStringNotContainsString('app', $preloadingModulePaths);
}
}
diff --git a/tests/PreloadingWithLinkHeadersTest.php b/tests/PreloadingWithLinkHeadersTest.php
new file mode 100644
index 0000000..1e37f40
--- /dev/null
+++ b/tests/PreloadingWithLinkHeadersTest.php
@@ -0,0 +1,54 @@
+swap(Importmap::class, $map = new Importmap(rootPath: __DIR__.DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR));
+
+ $map->pin('app', preload: false);
+ $map->pin('editor', to: 'js/rich_text.js', preload: false);
+ $map->pinAllFrom('resources/js/', under: 'controllers', to: 'js/', preload: false);
+
+ $response = (new AddLinkHeadersForPreloadedPins())->handle(new Request(), function () {
+ return new Response('Hello World');
+ });
+
+ $this->assertNull($response->headers->get('Link'));
+ }
+
+ /** @test */
+ public function sets_link_header_when_pins_are_preloaded(): void
+ {
+ $this->swap(Importmap::class, $map = new Importmap(rootPath: __DIR__.DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR));
+
+ $map->pin('app', preload: true);
+ $map->pin('editor', to: 'js/rich_text.js', preload: false);
+ $map->pinAllFrom('resources/js/', under: 'controllers', to: 'js/', preload: true);
+
+ $resolver = new class () extends AssetResolver {
+ public function __invoke($module)
+ {
+ return 'http://localhost/'.str_replace(['.js'], ['-123123.js'], $module);
+ }
+ };
+
+ $response = (new AddLinkHeadersForPreloadedPins($resolver))->handle(new Request(), function () {
+ return new Response('Hello World');
+ });
+
+ $this->assertEquals(
+ '; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload", ; rel="modulepreload"',
+ $response->headers->get('Link'),
+ );
+ }
+}
diff --git a/tests/TagsComponentTest.php b/tests/TagsComponentTest.php
index 073a135..f2b623b 100644
--- a/tests/TagsComponentTest.php
+++ b/tests/TagsComponentTest.php
@@ -33,14 +33,14 @@ protected function setUp(): void
/** @test */
public function generates_tags_without_nonce()
{
- $this->blade('')
+ $this->blade('')
->assertSee('', escape: false);
}
/** @test */
public function uses_given_csp_nonce()
{
- $this->blade('')
+ $this->blade('')
->assertSee('', escape: false);
}
@@ -51,7 +51,7 @@ public function uses_custom_map()
$importmap->pin('foo', preload: true);
$importmap->pin('bar', preload: true);
- $this->blade('', ['importmap' => $importmap])
+ $this->blade('', ['importmap' => $importmap])
->assertSee('', escape: false)
->assertSee('', escape: false)
->assertDontSee('', escape: false);
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 15d69bf..c681f3d 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -35,6 +35,7 @@ protected function getPackageProviders($app)
public function getEnvironmentSetUp($app)
{
config()->set('database.default', 'testing');
+ config()->set('app.url', 'http://localhost');
/*
$migration = include __DIR__.'/../database/migrations/create_importmap-laravel_table.php.stub';
diff --git a/tests/fixtures/npm/single-quote-importmap.php b/tests/fixtures/npm/single-quote-importmap.php
index 53e0b2b..eeda064 100644
--- a/tests/fixtures/npm/single-quote-importmap.php
+++ b/tests/fixtures/npm/single-quote-importmap.php
@@ -3,4 +3,4 @@
use Tonysm\ImportmapLaravel\Facades\Importmap;
Importmap::pin('md5', to: 'https://cdn.skypack.dev/md5', preload: true);
-Importmap::pin('not_there', to: 'nowhere.js');
+Importmap::pin('not_there', to: 'nowhere.js', preload: false);