From 59d8c310898e439dca257103a9892778a98747f2 Mon Sep 17 00:00:00 2001 From: Willem Leuverink Date: Thu, 18 Jan 2024 23:36:35 +0100 Subject: [PATCH] allow minification to be configurable --- config/bundle.php | 14 +++++++++- docs/advanced-usage.md | 4 ++- src/BundleManager.php | 4 +++ src/Bundlers/Bun.php | 18 +++++++++---- src/Components/Import.php | 35 +++++++++++++++---------- src/Contracts/Bundler.php | 8 +++++- tests/Browser/AlpineIntegrationTest.php | 13 ++++----- tests/Feature/IntegrationTest.php | 22 ++++++++++++++++ 8 files changed, 90 insertions(+), 28 deletions(-) diff --git a/config/bundle.php b/config/bundle.php index 3549c58..971cf85 100644 --- a/config/bundle.php +++ b/config/bundle.php @@ -23,7 +23,7 @@ | loading. Here you can tweak it's internal timout in ms. | */ - 'import_resolution_timeout' => env('BUNDLE_IMPORT_RESOLUTION_TIMOUT', 500), + 'import_resolution_timeout' => env('BUNDLE_IMPORT_RESOLUTION_TIMEOUT', 500), /* |-------------------------------------------------------------------------- @@ -49,6 +49,18 @@ */ 'sourcemaps_enabled' => env('BUNDLE_SOURCEMAPS_ENABLED', false), + /* + |-------------------------------------------------------------------------- + | Minification + |-------------------------------------------------------------------------- + | + | All code is minified by default. This can make issues harder to debug. + | Using sourcemaps should relieve this issue. But in case you need it; + | Simply disable the minification option to below to stop minifing. + | + */ + 'minify' => env('BUNDLE_MINIFY', true), + /* |-------------------------------------------------------------------------- | Build paths (glob patterns) diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 7652387..aa9b461 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -78,9 +78,11 @@ Since Bundle's core is included with the first `` that you load you > We like to explore ways to inject Bundle's core on every page. This way the `_import()` function does not have to be wrapped in a `DOMContentLoaded` listener. Check out our [roadmap](https://laravel-bundle.dev/roadmap.html#roadmap) to see what else we're cooking up. +The import resolution time may be configured milliseconds by updating the config file or via an env variable `BUNDLE_IMPORT_RESOLUTION_TIMEOUT` + ## Minification - +All code is minified by default. This can make issues harder to debug at times. Using sourcemaps should relieve this issue. But in case you need it you can disable minification by updating the config file or via an env variable `BUNDLE_MINIFICATION`. ## Sourcemaps diff --git a/src/BundleManager.php b/src/BundleManager.php index e11a796..5c27304 100644 --- a/src/BundleManager.php +++ b/src/BundleManager.php @@ -45,6 +45,7 @@ public function bundle(string $script): SplFileInfo try { $processed = $this->bundler->build( sourcemaps: $this->config()->get('sourcemaps_enabled'), + minify: $this->config()->get('minify'), inputPath: $this->tempDisk()->path(''), outputPath: $this->buildDisk()->path(''), fileName: $file, @@ -128,6 +129,9 @@ public static function fake(): MockInterface { $mock = Mockery::mock(BundleManagerContract::class, fn ($mock) => $mock ->makePartial() + ->shouldReceive('config') + ->andReturn(new ConfigRepository([])) + ->shouldReceive('bundle') ->andReturn(new SplFileInfo(base_path('composer.json'))) // Just a file we know exists. It won't be touched ->atLeast()->once() diff --git a/src/Bundlers/Bun.php b/src/Bundlers/Bun.php index f8eef1f..494e9d7 100644 --- a/src/Bundlers/Bun.php +++ b/src/Bundlers/Bun.php @@ -12,11 +12,17 @@ class Bun implements Bundler { use Constructable; - public function build(string $inputPath, string $outputPath, string $fileName, bool $sourcemaps = false): SplFileInfo - { + public function build( + string $inputPath, + string $outputPath, + string $fileName, + bool $sourcemaps = false, + bool $minify = true + ): SplFileInfo { + $path = base_path('node_modules/.bin/'); $options = [ - // '--tsconfig-override' => base_path('jsconfig.json'), // Disable enforcing this. custom config is optional. + // '--splitting', // Breaks relative paths to imports from resources/js (TODO: Experiment more after writing tests) '--chunk-naming' => 'chunks/[name]-[hash].[ext]', // Not in use without --splitting '--asset-naming' => 'assets/[name]-[hash].[ext]', // Not in use without --splitting '--entrypoints' => $inputPath . $fileName, @@ -24,13 +30,15 @@ public function build(string $inputPath, string $outputPath, string $fileName, b '--outdir' => $outputPath, '--target' => 'browser', '--root' => $inputPath, - // '--splitting', // Breaks relative paths to imports from resources/js (TODO: Experiment more after writing tests) '--format' => 'esm', - '--minify', // Only in production? $sourcemaps ? '--sourcemap=external' : '--sourcemap=none', + + $minify + ? '--minify' + : '', ]; Process::run("{$path}bun build {$this->args($options)}") diff --git a/src/Components/Import.php b/src/Components/Import.php index 0420ed0..2869dd0 100644 --- a/src/Components/Import.php +++ b/src/Components/Import.php @@ -5,17 +5,15 @@ use Illuminate\View\Component; use Leuverink\Bundle\BundleManager; use Leuverink\Bundle\Exceptions\BundlingFailedException; +use Leuverink\Bundle\Contracts\BundleManager as BundleManagerContract; class Import extends Component { - public BundleManager $manager; - public function __construct( public string $module, public ?string $as = null, public bool $inline = false ) { - $this->manager = BundleManager::new(); } public function render() @@ -27,6 +25,24 @@ public function render() } } + /** Builds the core JavaScript & packages it up in a bundle */ + protected function bundle() + { + $js = $this->core(); + + // Render script tag with bundled code + return view('x-import::script', [ + 'bundle' => $this->manager()->bundle($js), + ]); + } + + /** Get an instance of the BundleManager */ + protected function manager(): BundleManagerContract + { + return BundleManager::new(); + } + + /** Determines wherether to raise a console error or throw a PHP exception */ protected function raiseConsoleErrorOrException(BundlingFailedException $e) { if (app()->hasDebugModeEnabled()) { @@ -42,19 +58,10 @@ protected function raiseConsoleErrorOrException(BundlingFailedException $e) HTML; } - protected function bundle() - { - $js = $this->core(); - - // Render script tag with bundled code - return view('x-import::script', [ - 'bundle' => $this->manager->bundle($js), - ]); - } - + /** Builds Bundle's core JavaScript */ protected function core(): string { - $timeout = $this->manager->config()->get('import_resolution_timeout'); + $timeout = $this->manager()->config()->get('import_resolution_timeout'); return <<< JS // First make sure window.x_import_modules exists diff --git a/src/Contracts/Bundler.php b/src/Contracts/Bundler.php index 02c7897..0021372 100644 --- a/src/Contracts/Bundler.php +++ b/src/Contracts/Bundler.php @@ -6,5 +6,11 @@ interface Bundler { - public function build(string $inputPath, string $outputPath, string $fileName, bool $sourcemaps = false): SplFileInfo; + public function build( + string $inputPath, + string $outputPath, + string $fileName, + bool $sourcemaps = false, + bool $minify = true + ): SplFileInfo; } diff --git a/tests/Browser/AlpineIntegrationTest.php b/tests/Browser/AlpineIntegrationTest.php index 7206ffb..04f6c43 100644 --- a/tests/Browser/AlpineIntegrationTest.php +++ b/tests/Browser/AlpineIntegrationTest.php @@ -10,7 +10,7 @@ class AlpineIntegrationTest extends DuskTestCase { /** @test */ - public function it_can_bootstrap_alpine_via_import() + public function it_can_bootstrap_alpine_via_iife_import() { $browser = $this->blade(<<< 'HTML' @@ -31,7 +31,7 @@ public function it_can_bootstrap_alpine_via_import() } /** @test */ - public function it_can_bootstrap_plugins_via_import() + public function it_can_bootstrap_plugins_via_iife_import() { $browser = $this->blade(<<< 'HTML' @@ -74,12 +74,12 @@ public function it_can_use_other_imports_inside_x_init_directive() $el.innerHTML = filtered[0].name " > - HTML); + HTML)->pause(20); // Doesn't raise console errors $this->assertEmpty($browser->driver->manage()->getLog('browser')); - $browser->assertSee('Fello World!'); + $browser->assertSeeIn('#component', 'Fello World!'); } @@ -92,6 +92,7 @@ public function it_can_use_other_imports_inside_x_data_directive()
- HTML); + HTML)->pause(20); // Doesn't raise console errors $this->assertEmpty($browser->driver->manage()->getLog('browser')); - $browser->assertSee('Gello World!'); + $browser->assertSeeIn('#component', 'Gello World!'); } } diff --git a/tests/Feature/IntegrationTest.php b/tests/Feature/IntegrationTest.php index 10d8f5a..eda174e 100644 --- a/tests/Feature/IntegrationTest.php +++ b/tests/Feature/IntegrationTest.php @@ -139,6 +139,28 @@ ->toContain('type="module"'); }); +// Easiest way to verify minification is to check if the line count is below a certain threshold +it('minifies code when minification enabled', function () { + $lineThreshold = 10; + config()->set('bundle.minify', true); + + $script = Blade::renderComponent(new Import('~/output-to-id', 'foo', inline: true)); + + expect(substr_count($script, "\n")) + ->toBeLessThan($lineThreshold); +}); + +// Easiest way to verify minification is to check if the line count is above a certain threshold +it('doesnt minify code when minification disabled', function () { + $lineThreshold = 10; + config()->set('bundle.minify', false); + + $script = Blade::renderComponent(new Import('~/output-to-id', 'foo', inline: true)); + + expect(substr_count($script, "\n")) + ->toBeGreaterThan($lineThreshold); +}); + it('serves bundles over http', function () { $js = <<< 'JS' const filter = await import('~/output-to-id')