8000 [AssetMapper] Adding "path" option to importmap:require · weaverryan/symfony@a3a5472 · GitHub
[go: up one dir, main page]

Skip to content

Commit a3a5472

Browse files
weaverryannicolas-grekas
authored andcommitted
[AssetMapper] Adding "path" option to importmap:require
1 parent 3d6e0bd commit a3a5472

File tree

7 files changed

+115
-9
lines changed

7 files changed

+115
-9
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
->args([
150150
service('asset_mapper.importmap.manager'),
151151
service('asset_mapper'),
152+
param('kernel.project_dir'),
152153
])
153154
->tag('console.command')
154155

src/Symfony/Component/AssetMapper/Command/ImportMapRemoveCommand.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,18 @@ public function __construct(
3535

3636
protected function configure(): void
3737
{
38-
$this->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to remove');
38+
$this
39+
->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to remove')
40+
->setHelp(<<<'EOT'
41+
The <info>%command.name%</info> command removes packages from the <comment>importmap.php</comment>.
42+
If a package was downloaded into your app, the downloaded file will also be removed.
43+
44+
For example:
45+
46+
<info>php %command.full_name% lodash</info>
47+
EOT
48+
)
49+
;
3950
}
4051

4152
protected function execute(InputInterface $input, OutputInterface $output): int

src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,65 @@ final class ImportMapRequireCommand extends Command
3535
public function __construct(
3636
private readonly ImportMapManager $importMapManager,
3737
private readonly AssetMapperInterface $assetMapper,
38+
private readonly string $projectDir,
3839
) {
3940
parent::__construct();
4041
}
4142

4243
protected function configure(): void
4344
{
44-
$this->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to add');
45-
$this->addOption('download', 'd', InputOption::VALUE_NONE, 'Download packages locally');
46-
$this->addOption('preload', 'p', InputOption::VALUE_NONE, 'Preload packages');
45+
$this
46+
->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to add')
47+
->addOption('download', 'd', InputOption::VALUE_NONE, 'Download packages locally')
48+
->addOption('preload', 'p', InputOption::VALUE_NONE, 'Preload packages')
49+
->addOption('path', null, InputOption::VALUE_REQUIRED, 'The local path where the package lives relative to the project root')
50+
->setHelp(<<<'EOT'
51+
The <info>%command.name%</info> command adds packages to <comment>importmap.php</comment> usually
52+
by finding a CDN URL for the given package and version.
53+
54+
For example:
55+
56+
<info>php %command.full_name% lodash --preload</info>
57+
<info>php %command.full_name% "lodash@^4.15"</info>
58+
59+
The <info>preload</info> option will set the <info>preload</info> option in the importmap,
60+
which will tell the browser to preload the package. This should be used for all
61+
critical packages that are needed on page load.
62+
63+
The <info>download</info> option will download the package locally and point the
64+
importmap to it. Use this if you want to avoid using a CDN or if you want to
65+
ensure that the package is available even if the CDN is down.
66+
67+
Sometimes, a package may require other packages and multiple new items may be added
68+
to the import map.
69+
70+
You can also require multiple packages at once:
71+
72+
<info>php %command.full_name% "lodash@^4.15" "@hotwired/stimulus"</info>
73+
74+
EOT
75+
);
4776
}
4877

4978
protected function execute(InputInterface $input, OutputInterface $output): int
5079
{
5180
$io = new SymfonyStyle($input, $output);
5281

5382
$packageList = $input->getArgument('packages');
54-
if ($input->hasOption('path') && \count($packageList) > 1) {
55-
$io->error('The "--path" option can only be used when you require a single package.');
83+
$path = null;
84+
if ($input->hasOption('path')) {
85+
if (\count($packageList) > 1) {
86+
$io->error('The "--path" option can only be used when you require a single package.');
87+
88+
return Command::FAILURE;
89+
}
5690

57-
return Command::FAILURE;
91+
$path = $this->projectDir.'/'.$input->getOption('path');
92+
if (!is_file($path)) {
93+
$io->error(sprintf('The path "%s" does not exist.', $input->getOption('path')));
94+
95+
return Command::FAILURE;
96+
}
5897
}
5998

6099
$packages = [];
@@ -73,6 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
73112
$input->getOption('preload'),
74113
null,
75114
isset($parts['registry']) && $parts['registry'] ? $parts['registry'] : null,
115+
$path,
76116
);
77117
}
78118

src/Symfony/Component/AssetMapper/Command/ImportMapUpdateCommand.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ public function __construct(
3232
parent::__construct();
3333
}
3434

35+
protected function configure(): void
36+
{
37+
$this
38+
->setHelp(<<<'EOT'
39+
The <info>%command.name%</info> command will update all from the 3rd part packages
40+
in <comment>importmap.php</comment> to their latest version, including downloaded packages.
41+
42+
<info>php %command.full_name%</info>
43+
EOT
44+
);
45+
}
46+
3547
protected function execute(InputInterface $input, OutputInterface $output): int
3648
{
3749
$io = new SymfonyStyle($input, $output);

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,20 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr
221221

222222
$installData = [];
223223
$packageRequiresByName = [];
224+
$addedEntries = [];
224225
foreach ($packagesToRequire as $requireOptions) {
226+
if (null !== $requireOptions->path) {
227+
$newEntry = new ImportMapEntry(
228+
$requireOptions->packageName,
229+
$requireOptions->path,
230+
$requireOptions->preload,
231+
);
232+
$importMapEntries[$requireOptions->packageName] = $newEntry;
233+
$addedEntries[] = $newEntry;
234+
235+
continue;
236+
}
237+
225238
$constraint = $requireOptions->packageName;
226239
if (null !== $requireOptions->versionConstraint) {
227240
$constraint .= '@'.$requireOptions->versionConstraint;
@@ -233,6 +246,10 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr
233246
$packageRequiresByName[$requireOptions->packageName] = $requireOptions;
234247
}
235248

249+
if (!$installData) {
250+
return $addedEntries;
251+
}
252+
236253
$json = [
237254
'install' => $installData,
238255
'flattenScope' => true,
@@ -261,7 +278,6 @@ private function requirePackages(array $packagesToRequire, array &$importMapEntr
261278
// if we're requiring just one package, in case it has any peer deps, match the preload
262279
$defaultPreload = 1 === \count($packagesToRequire) ? $packagesToRequire[0]->preload : false;
263280

264-
$addedEntries = [];
265281
foreach ($response->toArray()['map']['imports'] as $packageName => $url) {
266282
$requireOptions = $packageRequiresByName[$packageName] ?? null;
267283
$importName = $requireOptions && $requireOptions->importName ? $requireOptions->importName : $packageName;
@@ -344,7 +360,16 @@ private function writeImportMapConfig(array $entries): void
344360
foreach ($entries as $entry) {
345361
$config = [];
346362
if ($entry->path) {
347-
$config[$entry->isDownloaded ? 'downloaded_to' : 'path'] = $entry->path;
363+
$path = $entry->path;
364+
// if the path is an absolute path, convert it to an asset path
365+
if (is_file($path)) {
366+
$asset = $this->assetMapper->getAssetFromSourcePath($path);
367+
if (null === $asset) {
368+
throw new \LogicException(sprintf('The "%s" importmap entry contains the path "%s" but it does not appear to be in any of your asset paths.', $entry->importName, $path));
369+
}
370+
$path = $asset->getLogicalPath();
371+
}
372+
$config[$entry->isDownloaded ? 'downloaded_to' : 'path'] = $path;
348373
}
349374
if ($entry->url) {
350375
$config['url'] = $entry->url;

src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function __construct(
2727
public readonly bool $preload = false,
2828
public readonly ?string $importName = null,
2929
public readonly ?string $registryName = null,
30+
public readonly ?string $path = null,
3031
) {
3132
}
3233
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ protected function setUp(): void
3838
if (!file_exists(__DIR__.'/../fixtures/importmaps_for_writing')) {
3939
$this->filesystem->mkdir(__DIR__.'/../fixtures/importmaps_for_writing');
4040
}
41+
if (!file_exists(__DIR__.'/../fixtures/importmaps_for_writing/assets')) {
42+
$this->filesystem->mkdir(__DIR__.'/../fixtures/importmaps_for_writing/assets');
43+
}
44+
file_put_contents(__DIR__.'/../fixtures/importmaps_for_writing/assets/some_file.js', '// some_file.js contents');
4145
}
4246

4347
protected function tearDown(): void
@@ -261,6 +265,18 @@ public static function getRequirePackageTests(): iterable
261265
],
262266
'expectedDownloadedFiles' => [],
263267
];
268+
269+
yield 'single_package_with_a_path' => [
270+
'packages' => [new PackageRequireOptions('some/module', path: __DIR__.'/../fixtures/importmaps_for_writing/assets/some_file.js')],
271+
'expectedInstallRequest' => [],
272+
'responseMap' => [],
273+
'expectedImportMap' => [
274+
'some/module' => [
275+
'path' => 'some_file.js',
276+
],
277+
],
278+
'expectedDownloadedFiles' => [],
279+
];
264280
}
265281

266282
public function testRemove()

0 commit comments

Comments
 (0)
0