Skip to content

Commit

Permalink
Merge pull request #5 from gwleuverink/major-refactors
Browse files Browse the repository at this point in the history
Major refactors
  • Loading branch information
gwleuverink authored Jan 15, 2024
2 parents 453eee5 + 354cf7e commit e5359a3
Show file tree
Hide file tree
Showing 19 changed files with 208 additions and 60 deletions.
60 changes: 54 additions & 6 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ You should apply this with consideration. You will save up on requests, but doin
</script>
```

## Per method exports

If a module supports per method exports, like `lodash` does, it is recomended to import the single method instead of the whole module & only retrieving the desired export later.

```html
<x-import module="lodash/filter" as="filter" /> <!-- 25kb -->
<!-- as opposed to -->
<x-import module="lodash" as="lodash" /> <!-- 78kb -->
```

## Path rewriting for local modules

If you want to import modules from any other directory than `node_modules`, you may add a `jsconfig.json` file to your project root with all your path aliases.
Expand All @@ -33,7 +43,7 @@ If you want to import modules from any other directory than `node_modules`, you
}
```

Consider the following example script in your `resources/js` directory:
Consider the following example script `resources/js/alert.js`:

```javascript
export default function alertProxy(message) {
Expand All @@ -53,16 +63,54 @@ In order to use this script directly in your blade views, you simply need to imp
</script>
```

## Per method exports
## Self evaluation functions

If a module supports per method exports, like `lodash` does, it is recomended to import the single method instead of the whole module & only retrieving the desired export later.
You can use this mechanism to immediatly execute some code or to bootstrap & import other libraries.

Consider the following example the following file `resources/js/immediately-invoked.js`

```javascript
export default (() => {
alert('Hello World!)
})();
```
Then in your template you can use the `<x-import />` component to evaluate this function. Without the need of calling the `_import()` function. Note this only works with a [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE){:target="\_blank"}
```html
<x-import module="lodash/filter" as="filter" /> <!-- 25kb -->
<!-- as opposed to -->
<x-import module="lodash" as="lodash" /> <!-- 78kb -->
<!-- User will be alerted with 'Hello World' -->
<x-import module="~/immediately-invoked" as="foo" />
```
This can be used in a variety of creative ways. For example for swapping out Laravel's default `bootstrap.js` to a need-only approach.

```javascript
import axios from "axios";
export default (async () => {
window.axios = axios;
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
})();
```

```html
<x-import module="~/bootstrap/axios" as="axios" />
<script type="module">
// axios available, since it was attached to the window inside the IIFE
axios.get("/user?ID=12345").then(function (response) {
console.log(response);
});
</script>
```

Note that your consuming script still needs to be of type="module" otherwise `window.axios` will be undefined

{: .alert }

> Code splitting is [not supported](https://gwleuverink.github.io/bundle/caveats.html#code-splitting). Be careful when importing modules in your local scripts like this. When two script rely on the same dependency, it will be included in both bundles. This approach is meant to be used as a method to allow setup of more complex libraries. It is reccomended to place business level code inside your templates instead.

## Sourcemaps

Sourcemaps are disabled by default. You may enable this by setting `BUNDLE_SOURCEMAPS_ENABLED` to true in your env file or by publishing and updating the bundle config.
Expand Down
12 changes: 9 additions & 3 deletions src/Components/Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Import extends Component
{
public function __construct(
public string $module,
public string $as,
public ?string $as = null,
public bool $inline = false // TODO: Implement this
) {
}
Expand Down Expand Up @@ -50,11 +50,17 @@ protected function bundle()
// can retreive the module as a Promise
$js = <<< JS
if(!window.x_import_modules) window.x_import_modules = {}
window.x_import_modules.{$this->as} = import('{$this->module}')
'{$this->as}'
? window.x_import_modules['{$this->as}'] = import('{$this->module}') // Assign it under an alias
: import('{$this->module}') // Only import it (for IIFE no alias needed)
window._import = async function(alias, exportName = 'default') {
let module = await window.x_import_modules[alias]
return module[exportName]
return module[exportName] !== undefined
? module[exportName] // Return export if it exists
: module // Otherwise the entire module
}
JS;

Expand Down
4 changes: 2 additions & 2 deletions src/Components/views/import.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
@once("bundle:$as")
<!--[BUNDLE: {{ $as }} from '{{ $module }}']-->
@if ($inline)
<script data-bundle="{{ $as }}" {{ $attributes }}>
<script data-bundle="{{ $as }}" type="module" {{ $attributes }}>
{!! file_get_contents($bundle) !!}
</script>
@else
<script src="{{ route('bundle:import', $bundle->getFilename(), false) }}" data-bundle="{{ $as }}" {{ $attributes }}></script>
<script src="{{ route('bundle:import', $bundle->getFilename(), false) }}" data-bundle="{{ $as }}" type="module" {{ $attributes }}></script>
@endif
<!--[ENDBUNDLE]>-->
@else {{-- @once else clause --}}
Expand Down
28 changes: 14 additions & 14 deletions tests/Browser/ComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,32 @@ class ComponentTest extends DuskTestCase
public function it_renders_the_same_import_only_once()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" />
<x-import module="~/alert" as="alert" />
<x-import module="~/output-to-id" as="output" />
<x-import module="~/output-to-id" as="output" />
HTML)
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="alert"').length
document.querySelectorAll('script[data-bundle="output"').length
JS, 1);
}

/** @test */
public function it_renders_the_same_import_only_once_when_one_was_inlined()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" />
<x-import module="~/alert" as="alert" inline />
<x-import module="~/output-to-id" as="output" />
<x-import module="~/output-to-id" as="output" inline />
HTML)
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="alert"').length
document.querySelectorAll('script[data-bundle="output"').length
JS, 1);
}

/** @test */
public function it_renders_the_same_import_under_different_aliases()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="foo" />
<x-import module="~/alert" as="bar" />
<x-import module="~/output-to-id" as="foo" />
<x-import module="~/output-to-id" as="bar" />
HTML)
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="foo"').length
Expand All @@ -64,30 +64,30 @@ public function it_renders_the_same_import_under_different_aliases()
public function it_renders_script_inline_when_inline_prop_was_passed()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" inline />
<x-import module="~/output-to-id" as="output" inline />
HTML)
// Assert it doesn't render src attribute on the script tag
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="alert"')[0].hasAttribute('src')
document.querySelectorAll('script[data-bundle="output"')[0].hasAttribute('src')
JS, false)
// Assert script tag has content
->assertScript(<<< 'JS'
typeof document.querySelectorAll('script[data-bundle="alert"')[0].innerHTML
typeof document.querySelectorAll('script[data-bundle="output"')[0].innerHTML
JS, 'string');
}

public function it_doesnt_render_script_inline_by_default()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" />
<x-import module="~/output-to-id" as="output" />
HTML)
// Assert it renders src attribute on the script tag
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="alert"')[0].hasAttribute('src')
document.querySelectorAll('script[data-bundle="output"')[0].hasAttribute('src')
JS, true)
// Assert script tag has no content
->assertScript(<<< 'JS'
document.querySelectorAll('script[data-bundle="alert"')[0].innerHTML
document.querySelectorAll('script[data-bundle="output"')[0].innerHTML
JS, null);
}

Expand Down
43 changes: 37 additions & 6 deletions tests/Browser/LocalModuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,60 @@ class LocalModuleTest extends DuskTestCase
public function it_injects_import_and_import_function_on_the_window_object()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" />
<x-import module="~/output-to-id" as="output" />
HTML)
->assertScript('typeof window._import', 'function')
->assertScript('typeof window.x_import_modules', 'object');
}

/** @test */
public function it_evaluates_function_expressions()
{
$this->blade(<<< 'HTML'
<x-import module="~/function-is-evaluated" as="is-evaluated" defer />
HTML)
->assertScript('window.test_evaluated', true);
}

/** @test */
public function it_can_import_modules_inside_function_expressions()
{
$this->blade(<<< 'HTML'
<x-import module="~/function-is-evaluated" as="is-evaluated" defer />
HTML)
->assertScript('window.test_evaluated', true);
}

/** @test */
public function it_imports_from_local_resource_directory()
{
$this->blade(<<< 'HTML'
<x-import module="~/alert" as="alert" />
<x-import module="~/output-to-id" as="output" />
<script type="module">
var module = await _import('alert');
module('Hello World!')
var output = await _import('output');
output('output', 'Yello World!')
</script>
<div id="output"></div>
HTML)
->assertDialogOpened('Hello World!');
->assertSeeIn('#output', 'Yello World!');
}

/** @test */
public function it_canx_import_modules_per_method()
public function it_can_import_named_functions()
{
$this->blade(<<< 'HTML'
<x-import module="~/named-functions" as="helpers" />
<script type="module">
const outputBar = await _import('helpers', 'bar');
outputBar()
</script>
<div id="output"></div>
HTML)
->assertSeeIn('#output', 'Bar');
}
}
30 changes: 29 additions & 1 deletion tests/Browser/NodeModuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function it_imports_from_node_modules()
}

/** @test */
public function it_canx_import_modules_per_method()
public function it_can_import_modules_per_method()
{
$this->blade(<<< 'HTML'
<x-import module="lodash/filter" as="filter" />
Expand All @@ -72,4 +72,32 @@ public function it_canx_import_modules_per_method()
HTML)
->assertSeeIn('#output', 'Yello World!');
}

/** @test */
public function it_can_use_both_local_and_node_module_together_on_the_same_page()
{
$this->blade(<<< 'HTML'
<x-import module="~/output-to-id" as="output" />
<x-import module="lodash/filter" as="filter" />
<script type="module">
const filter = await _import('filter');
const output = await _import('output');
let data = [
{ 'name': 'Foo', 'active': false },
{ 'name': 'Wello World!', 'active': true }
];
// Filter only active
let filtered = filter(data, o => o.active)
output('output', filtered[0].name)
</script>
<div id="output"></div>
HTML)
->assertSeeIn('#output', 'Wello World!');
}
}
8 changes: 8 additions & 0 deletions tests/DuskTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public static function setUpBeforeClass(): void
parent::setUpBeforeClass();
}

protected function tearDown(): void
{
$this->artisan('bundle:clear');
$this->artisan('view:clear');

parent::tearDown();
}

protected function getBasePath()
{
// testbench-core skeleton is leading, due to test setup in testbench.yaml
Expand Down
12 changes: 3 additions & 9 deletions tests/Feature/Commands/BuildCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@

use Leuverink\Bundle\BundleManager;

it('scans glob patterns', function () {

})->todo();

it('generates a bundle', function () {
$manager = BundleManager::new();

// Scan the fixtures dir as build path
config()->set('bundle.build_paths', [
realpath(getcwd() . '/tests/Fixtures/build-command-resources'),
realpath(getcwd() . '/tests/Fixtures/resources'),
]);

// Make sure all cached scripts are cleared
Expand All @@ -30,7 +26,7 @@

// Scan the fixtures dir as build path
config()->set('bundle.build_paths', [
realpath(getcwd() . '/tests/Fixtures/build-command-resources'),
realpath(getcwd() . '/tests/Fixtures/resources'),
]);

// Make sure all cached scripts are cleared
Expand All @@ -49,7 +45,7 @@

// Scan the fixtures dir as build path
config()->set('bundle.build_paths', [
realpath(getcwd() . '/tests/Fixtures/build-command-resources/markdown'),
realpath(getcwd() . '/tests/Fixtures/resources/markdown'),
]);

// Make sure all cached scripts are cleared
Expand All @@ -58,7 +54,5 @@

// Execute build command
$this->artisan('bundle:build');

// Assert expected scripts are present
expect($manager->buildDisk()->allFiles())->toHaveCount(1);
});
Loading

0 comments on commit e5359a3

Please sign in to comment.