Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a2d7a95

Browse filesBrowse files
committed
Automatically preload assets that are required by other preloaded assets
1 parent c00f3b3 commit a2d7a95
Copy full SHA for a2d7a95

File tree

Expand file treeCollapse file tree

10 files changed

+164
-36
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+164
-36
lines changed
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\AssetMapper;
13+
14+
/**
15+
* Represents a dependency that a MappedAsset has.
16+
*/
17+
class AssetDependency
18+
{
19+
public function __construct(
20+
public readonly MappedAsset $asset,
21+
/**
22+
* @var bool Whether this dependency is immediately needed.
23+
*/
24+
public readonly bool $isLazy,
25+
)
26+
{
27+
}
28+
}

‎src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class JavaScriptImportPathCompiler implements AssetCompilerInterface
2121
{
2222
use AssetCompilerPathResolverTrait;
2323

24-
private const IMPORT_PATTERN = '/(?:import(?:\s+\w+)?\s*(?:from)?\s*|\bimport\()\s*[\'"](\.\/[^\'"]+|(\.\.\/)+[^\'"]+)[\'"]\s*[;\)]?/m';
24+
// https://regex101.com/r/VFdR4H/1
25+
private const IMPORT_PATTERN = '/(?:import\s+(?:(?:\*\s+as\s+\w+|[\w\s{},*]+)\s+from\s+)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)+[^\'"`]+)[\'"`]\s*[;\)]?/m';
2526

2627
public function compile(string $content, MappedAsset $asset, AssetMapper $assetMapper): string
2728
{
@@ -45,7 +46,9 @@ public function compile(string $content, MappedAsset $asset, AssetMapper $assetM
4546
return $matches[0];
4647
}
4748

48-
$asset->addDependency($dependentAsset);
49+
$isLazy = str_contains($matches[0], 'import(');
50+
51+
$asset->addDependency($dependentAsset, $isLazy);
4952

5053
if ($resolvedPath === $finalResolvedPath) {
5154
// no change to the path, so just return the original import

‎src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php
+7-2Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\AssetMapper\ImportMap;
1313

14+
use Symfony\Component\AssetMapper\AssetDependency;
1415
use Symfony\Component\AssetMapper\AssetMapper;
1516
use Symfony\Component\AssetMapper\MappedAsset;
1617
use Symfony\Component\HttpClient\HttpClient;
@@ -374,8 +375,12 @@ private function convertEntriesToImports(array $entries): array
374375
$this->modulesToPreload[] = $path;
375376
}
376377

377-
$dependencyImportMapEntries = array_map(function (MappedAsset $asset) {
378-
return new ImportMapEntry($asset->getPublicPathWithoutDigest(), $asset->getLogicalPath());
378+
$dependencyImportMapEntries = array_map(function (AssetDependency $dependency) {
379+
return new ImportMapEntry(
380+
$dependency->asset->getPublicPathWithoutDigest(),
381+
$dependency->asset->getLogicalPath(),
382+
preload: !$dependency->isLazy,
383+
);
379384
}, $dependencies);
380385
$imports = array_merge($imports, $this->convertEntriesToImports($dependencyImportMapEntries));
381386
}

‎src/Symfony/Component/AssetMapper/MappedAsset.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/MappedAsset.php
+11-4Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
namespace Symfony\Component\AssetMapper;
1313

14+
/**
15+
* Represents a single asset in the asset mapper system.
16+
*
17+
* @author Ryan Weaver <ryan@symfonycasts.com>
18+
*/
1419
class MappedAsset
1520
{
1621

@@ -20,7 +25,7 @@ class MappedAsset
2025
private string $digest;
2126
private bool $isPredigested;
2227
private ?string $mimeType;
23-
/** @var MappedAsset[] */
28+
/** @var AssetDependency[] */
2429
private array $dependencies = [];
2530

2631
public function __construct(private string $logicalPath)
@@ -90,6 +95,9 @@ public function getMimeType(): string
9095
return $this->mimeType;
9196
}
9297

98+
/**
99+
* @return AssetDependency[]
100+
*/
93101
public function getDependencies(): array
94102
{
95103
return $this->dependencies;
@@ -141,14 +149,13 @@ public function setContent(string $content): void
141149
$this->content = $content;
142150
}
143151

144-
public function addDependency(MappedAsset $asset): void
152+
public function addDependency(MappedAsset $asset, bool $isLazy = false): void
145153
{
146-
$this->dependencies[] = $asset;
154+
$this->dependencies[] = new AssetDependency($asset, $isLazy);
147155
}
148156

149157
public function getPublicPathWithoutDigest(): string
150158
{
151-
// TODO: should we pass this in instead of trying to calculate it?
152159
if ($this->isPredigested) {
153160
return $this->publicPath;
154161
}

‎src/Symfony/Component/AssetMapper/Tests/Compiler/CssAssetUrlCompilerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Tests/Compiler/CssAssetUrlCompilerTest.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\AssetMapper\Tests\Compiler;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\AssetMapper\AssetDependency;
1516
use Symfony\Component\AssetMapper\AssetMapper;
1617
use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler;
1718
use Symfony\Component\AssetMapper\MappedAsset;
@@ -46,7 +47,7 @@ public function testCompile(string $sourceLogicalName, string $input, string $ex
4647
$compiler = new CssAssetUrlCompiler();
4748
$asset = new MappedAsset($sourceLogicalName);
4849
$this->assertSame($expectedOutput, $compiler->compile($input, $asset, $assetMapper));
49-
$assetDependencyLogicalPaths = array_map(fn (MappedAsset $asset) => $asset->getLogicalPath(), $asset->getDependencies());
50+
$assetDependencyLogicalPaths = array_map(fn (AssetDependency $dependency) => $dependency->asset->getLogicalPath(), $asset->getDependencies());
5051
$this->assertSame($expectedDependencies, $assetDependencyLogicalPaths);
5152
}
5253

‎src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php
+95-21Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\AssetMapper\Tests\Compiler;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\AssetMapper\AssetDependency;
1516
use Symfony\Component\AssetMapper\AssetMapper;
1617
use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler;
1718
use Symfony\Component\AssetMapper\MappedAsset;
@@ -43,20 +44,23 @@ public function testCompile(string $sourceLogicalName, string $input, string $ex
4344

4445
$compiler = new JavaScriptImportPathCompiler();
4546
$this->assertSame($expectedOutput, $compiler->compile($input, $asset, $assetMapper));
46-
$assetDependencyLogicalPaths = array_map(fn (MappedAsset $asset) => $asset->getLogicalPath(), $asset->getDependencies());
47-
$this->assertEquals($expectedDependencies, $assetDependencyLogicalPaths);
47+
$actualDependencies = [];
48+
foreach ($asset->getDependencies() as $dependency) {
49+
$actualDependencies[$dependency->asset->getLogicalPath()] = $dependency->isLazy;
50+
}
51+
$this->assertEquals($expectedDependencies, $actualDependencies);
4852
}
4953

5054
public function provideCompileTests(): iterable
5155
{
52-
yield 'simple_double_quotes' => [
56+
yield 'dynamic_simple_double_quotes' => [
5357
'sourceLogicalName' => 'app.js',
5458
'input' => 'import("./other.js");',
5559
'expectedOutput' => 'import("./other.js");',
56-
'expectedDependencies' => ['other.js']
60+
'expectedDependencies' => ['other.js' => true]
5761
];
5862

59-
yield 'simple_multiline' => [
63+
yield 'dynamic_simple_multiline' => [
6064
'sourceLogicalName' => 'app.js',
6165
'input' => <<<EOF
6266
const fun;
@@ -68,63 +72,133 @@ public function provideCompileTests(): iterable
6872
import("./other.js");
6973
EOF
7074
,
71-
'expectedDependencies' => ['other.js']
75+
'expectedDependencies' => ['other.js' => true]
7276
];
7377

74-
yield 'simple_single_quotes' => [
78+
yield 'dynamic_simple_single_quotes' => [
7579
'sourceLogicalName' => 'app.js',
7680
'input' => 'import(\'./other.js\');',
7781
'expectedOutput' => 'import(\'./other.js\');',
78-
'expectedDependencies' => ['other.js']
82+
'expectedDependencies' => ['other.js' => true]
7983
];
8084

81-
yield 'resolves_without_extension' => [
85+
yield 'dynamic_simple_tick_quotes' => [
86+
'sourceLogicalName' => 'app.js',
87+
'input' => 'import(`./other`);',
88+
'expectedOutput' => 'import(`./other.js`);',
89+
'expectedDependencies' => ['other.js' => true]
90+
];
91+
92+
yield 'dynamic_resolves_without_extension' => [
8293
'sourceLogicalName' => 'app.js',
8394
'input' => 'import("./other");',
8495
'expectedOutput' => 'import("./other.js");',
85-
'expectedDependencies' => ['other.js'],
96+
'expectedDependencies' => ['other.js' => true],
8697
];
8798

88-
yield 'resolves_multiple' => [
99+
yield 'dynamic_resolves_multiple' => [
89100
'sourceLogicalName' => 'app.js',
90101
'input' => 'import("./other.js"); import("./subdir/foo.js");',
91102
'expectedOutput' => 'import("./other.js"); import("./subdir/foo.js");',
92-
'expectedDependencies' => ['other.js', 'subdir/foo.js'],
103+
'expectedDependencies' => ['other.js' => true, 'subdir/foo.js' => true],
93104
];
94105

95-
yield 'avoid_resolving_non_relative_imports' => [
106+
yield 'dynamic_avoid_resolving_non_relative_imports' => [
96107
'sourceLogicalName' => 'app.js',
97108
'input' => 'import("other.js");',
98109
'expectedOutput' => 'import("other.js");',
99110
'expectedDependencies' => [],
100111
];
101112

102-
yield 'resolves_dynamic_imports_later_in_file' => [
113+
yield 'dynamic_resolves_dynamic_imports_later_in_file' => [
103114
'sourceLogicalName' => 'app.js',
104115
'input' => 'console.log("Hello test!") import("./subdir/foo.js").then(() => console.log("inside promise!"));',
105116
'expectedOutput' => 'console.log("Hello test!") import("./subdir/foo.js").then(() => console.log("inside promise!"));',
106-
'expectedDependencies' => ['subdir/foo.js'],
117+
'expectedDependencies' => ['subdir/foo.js' => true],
107118
];
108119

109-
yield 'correctly_moves_to_higher_directories' => [
120+
yield 'dynamic_correctly_moves_to_higher_directories' => [
110121
'sourceLogicalName' => 'subdir/app.js',
111122
'input' => 'import("../other.js");',
112123
'expectedOutput' => 'import("../other.js");',
113-
'expectedDependencies' => ['other.js'],
124+
'expectedDependencies' => ['other.js' => true],
114125
];
115126

116-
yield 'correctly_moves_to_higher_directories_and_adds_extension' => [
127+
yield 'dynamic_correctly_moves_to_higher_directories_and_adds_extension' => [
117128
'sourceLogicalName' => 'subdir/app.js',
118129
'input' => 'import("../other");',
119130
'expectedOutput' => 'import("../other.js");',
120-
'expectedDependencies' => ['other.js'],
131+
'expectedDependencies' => ['other.js' => true],
121132
];
122133

123-
yield 'resolves_the_index_file_in_directory' => [
134+
yield 'dynamic_resolves_the_index_file_in_directory' => [
124135
'sourceLogicalName' => 'app.js',
125136
'input' => 'import("./dir_with_index");',
126137
'expectedOutput' => 'import("./dir_with_index/index.js");',
127-
'expectedDependencies' => ['dir_with_index/index.js'],
138+
'expectedDependencies' => ['dir_with_index/index.js' => true],
139+
];
140+
141+
yield 'static_named_import_double_quotes' => [
142+
'sourceLogicalName' => 'app.js',
143+
'input' => 'import { myFunction } from "./other";',
144+
'expectedOutput' => 'import { myFunction } from "./other.js";',
145+
'expectedDependencies' => ['other.js' => false],
146+
];
147+
148+
yield 'static_named_import_single_quotes' => [
149+
'sourceLogicalName' => 'app.js',
150+
'input' => 'import { myFunction } from \'./other\';',
151+
'expectedOutput' => 'import { myFunction } from \'./other.js\';',
152+
'expectedDependencies' => ['other.js' => false],
153+
];
154+
155+
yield 'static_default_import' => [
156+
'sourceLogicalName' => 'app.js',
157+
'input' => 'import myFunction from "./other";',
158+
'expectedOutput' => 'import myFunction from "./other.js";',
159+
'expectedDependencies' => ['other.js' => false],
160+
];
161+
162+
yield 'static_default_and_named_import' => [
163+
'sourceLogicalName' => 'app.js',
164+
'input' => 'import myFunction, { helperFunction } from "./other";',
165+
'expectedOutput' => 'import myFunction, { helperFunction } from "./other.js";',
166+
'expectedDependencies' => ['other.js' => false],
167+
];
168+
169+
yield 'static_import_everything' => [
170+
'sourceLogicalName' => 'app.js',
171+
'input' => 'import * as myModule from "./other";',
172+
'expectedOutput' => 'import * as myModule from "./other.js";',
173+
'expectedDependencies' => ['other.js' => false],
174+
];
175+
176+
yield 'static_import_just_for_side_effects' => [
177+
'sourceLogicalName' => 'app.js',
178+
'input' => 'import "./other";',
179+
'expectedOutput' => 'import "./other.js";',
180+
'expectedDependencies' => ['other.js' => false],
181+
];
182+
183+
yield 'mix_of_static_and_dynamic_imports' => [
184+
'sourceLogicalName' => 'app.js',
185+
'input' => 'import "./other"; import("./subdir/foo.js");',
186+
'expectedOutput' => 'import "./other.js"; import("./subdir/foo.js");',
187+
'expectedDependencies' => ['other.js' => false, 'subdir/foo.js' => true],
188+
];
189+
190+
yield 'extra_import_word_does_not_cause_issues' => [
191+
'sourceLogicalName' => 'app.js',
192+
'input' => "// about to do an import\nimport('./other');",
193+
'expectedOutput' => "// about to do an import\nimport('./other.js');",
194+
'expectedDependencies' => ['other.js' => true],
195+
];
196+
197+
yield 'import_on_one_line_then_module_name_on_next_is_ok' => [
198+
'sourceLogicalName' => 'app.js',
199+
'input' => "import \n './other';",
200+
'expectedOutput' => "import \n './other.js';",
201+
'expectedDependencies' => ['other.js' => false],
128202
];
129203
}
130204
}

‎src/Symfony/Component/AssetMapper/Tests/Compiler/SourceMappingUrlsCompilerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Tests/Compiler/SourceMappingUrlsCompilerTest.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\AssetMapper\Tests\Compiler;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\AssetMapper\AssetDependency;
1516
use Symfony\Component\AssetMapper\AssetMapper;
1617
use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler;
1718
use Symfony\Component\AssetMapper\MappedAsset;
@@ -46,7 +47,7 @@ public function testCompile(string $sourceLogicalName, string $input, string $ex
4647
$compiler = new SourceMappingUrlsCompiler();
4748
$asset = new MappedAsset($sourceLogicalName);
4849
$this->assertSame($expectedOutput, $compiler->compile($input, $asset, $assetMapper));
49-
$assetDependencyLogicalPaths = array_map(fn (MappedAsset $asset) => $asset->getLogicalPath(), $asset->getDependencies());
50+
$assetDependencyLogicalPaths = array_map(fn (AssetDependency $dependency) => $dependency->asset->getLogicalPath(), $asset->getDependencies());
5051
$this->assertSame($expectedDependencies, $assetDependencyLogicalPaths);
5152
}
5253

‎src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Symfony\Component\HttpClient\MockHttpClient;
1414
use Symfony\Component\HttpClient\Response\MockResponse;
1515
use Symfony\Component\Mime\MimeTypesInterface;
16-
use Symfony\Contracts\HttpClient\HttpClientInterface;
1716

1817
class ImportMapManagerTest extends TestCase
1918
{
@@ -42,7 +41,10 @@ public function testGetModulesToPreload()
4241
);
4342
$this->assertEquals([
4443
'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js',
45-
'/assets/app-2ed6cde5b51e6acf8a94964885ce35f4.js',
44+
'/assets/app-ea9ebe6156adc038aba53164e2be0867.js',
45+
// these are non-lazily imported from app.js
46+
'/assets/pizza/index-b3fb5ee31adaf5e1b32d28edf1ab8e7a.js',
47+
'/assets/popcorn-c0778b84ef9893592385aebc95a2896e.js',
4648
], $manager->getModulesToPreload());
4749
}
4850

@@ -55,9 +57,10 @@ public function testGetImportMapJson()
5557
$this->assertEquals(['imports' => [
5658
'@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js',
5759
'lodash' => '/assets/vendor/lodash-ad7bd7bf42edd09654255a82b9027810.js',
58-
'app' => '/assets/app-2ed6cde5b51e6acf8a94964885ce35f4.js',
60+
'app' => '/assets/app-ea9ebe6156adc038aba53164e2be0867.js',
5961
'/assets/pizza/index.js' => '/assets/pizza/index-b3fb5ee31adaf5e1b32d28edf1ab8e7a.js',
6062
'/assets/popcorn.js' => '/assets/popcorn-c0778b84ef9893592385aebc95a2896e.js',
63+
'/assets/imported_async.js' => '/assets/imported_async-8f0cd418bfeb0cf63826e09a4474a81c.js',
6164
'other_app' => '/assets/namespaced_assets2/app2-344d0d513d424647e7d8a394ffe5e4b5.js',
6265
]], json_decode($manager->getImportMapJson(), true));
6366
}
+7-2Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
import './pizza';
2-
import './popcorn';
1+
import
2+
'./pizza';
3+
import Popcorn from './popcorn';
4+
5+
import('./imported_async').then(() => {
6+
console.log('async import done');
7+
});
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('imported_async.js');

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.