10000 [AssetMapper] Fixing bug where existing importmap.json would poison f… · symfony/symfony@bd3c42d · GitHub
[go: up one dir, main page]

Skip to content

Commit bd3c42d

Browse files
committed
[AssetMapper] Fixing bug where existing importmap.json would poison future compile
Also, fixing bug where the "preload" information also needed dumping.
1 parent 70b40fc commit bd3c42d

File tree

6 files changed

+116
-23
lines changed

6 files changed

+116
-23
lines changed

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

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,54 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6666
throw new InvalidArgumentException(sprintf('The public directory "%s" does not exist.', $publicDir));
6767
}
6868

69+
$outputDir = $publicDir.$this->assetMapper->getPublicPrefix();
6970
if ($input->getOption('clean')) {
70-
$outputDir = $publicDir.$this->assetMapper->getPublicPrefix();
7171
$io->comment(sprintf('Cleaning <info>%s</info>', $outputDir));
7272
$this->filesystem->remove($outputDir);
7373
$this->filesystem->mkdir($outputDir);
7474
}
7575

76+
$manifestPath = $publicDir.$this->assetMapper->getPublicPrefix().AssetMapper::MANIFEST_FILE_NAME;
77+
if (file_exists($manifestPath)) {
78+
$this->filesystem->remove($manifestPath);
79+
}
80+
$manifest = $this->createManifestAndWriteFiles($io, $publicDir);
81+
$this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT));
82+
$io->comment(sprintf('Manifest written to <info>%s</info>', $manifestPath));
83+
84+
$importMapPath = $outputDir.ImportMapManager::IMPORT_MAP_FILE_NAME;
85+
if (file_exists($importMapPath)) {
86+
$this->filesystem->remove($importMapPath);
87+
}
88+
$this->filesystem->dumpFile($importMapPath, $this->importMapManager->getImportMapJson());
89+
90+
$importMapPreloadPath = $outputDir.ImportMapManager::IMPORT_MAP_PRELOAD_FILE_NAME;
91+
if (file_exists($importMapPreloadPath)) {
92+
$this->filesystem->remove($importMapPreloadPath);
93+
}
94+
$this->filesystem->dumpFile(
95+
$importMapPreloadPath,
96+
json_encode($this->importMapManager->getModulesToPreload(), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)
97+
);
98+
$io->comment(sprintf('Import map written to <info>%s</info> and <info>%s</info> for quick importmap dumping onto the page.', $this->shortenPath($importMapPath), $this->shortenPath($importMapPreloadPath)));
99+
100+
if ($this->isDebug) {
101+
$io->warning(sprintf(
102+
'You are compiling assets in development. Symfony will not serve any changed assets until you delete the "%s" directory.',
103+
$this->shortenPath($outputDir)
104+
));
105+
}
106+
107+
return self::SUCCESS;
108+
}
109+
110+
private function shortenPath(string $path): string
111+
{
112+
return str_replace($this->projectDir.'/', '', $path);
113+
}
114+
115+
private function createManifestAndWriteFiles(SymfonyStyle $io, string $publicDir): array
116+
{
76117
$allAssets = $this->assetMapper->allAssets();
77118

78119
$io->comment(sprintf('Compiling <info>%d</info> assets to <info>%s%s</info>', \count($allAssets), $publicDir, $this->assetMapper->getPublicPrefix()));
@@ -88,23 +129,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
88129
$this->filesystem->dumpFile($targetPath, $asset->getContent());
89130
$manifest[$asset->logicalPath] = $asset->getPublicPath();
90131
}
132+
ksort($manifest);
91133

92-
$manifestPath = $publicDir.$this->assetMapper->getPublicPrefix().AssetMapper::MANIFEST_FILE_NAME;
93-
$this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT));
94-
$io->comment(sprintf('Manifest written to <info>%s</info>', $manifestPath));
95-
96-
$importMapPath = $publicDir.$this->assetMapper->getPublicPrefix().ImportMapManager::IMPORT_MAP_FILE_NAME;
97-
$this->filesystem->dumpFile($importMapPath, $this->importMapManager->getImportMapJson());
98-
$io->comment(sprintf('Import map written to <info>%s</info>', $importMapPath));
99-
100-
if ($this->isDebug) {
101-
$io->warning(sprintf(
102-
'You are compiling assets in development. Symfony will not serve any changed assets until you delete %s and %s.',
103-
$manifestPath,
104-
$importMapPath
105-
));
106-
}
107-
108-
return self::SUCCESS;
134+
return $manifest;
109135
}
110136
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ImportMapManager
4949
*/
5050
private const PACKAGE_PATTERN = '/^(?:https?:\/\/[\w\.-]+\/)?(?:(?<registry>\w+):)?(?<package>(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*)(?:@(?<version>[\w\._-]+))?(?:(?<subpath>\/.*))?$/';
5151
public const IMPORT_MAP_FILE_NAME = 'importmap.json';
52+
public const IMPORT_MAP_PRELOAD_FILE_NAME = 'importmap.preload.json';
5253

5354
private array $importMapEntries;
5455
private array $modulesToPreload;
@@ -125,9 +126,11 @@ private function buildImportMapJson(): void
125126
return;
126127
}
127128

128-
$dumpedPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_FILE_NAME;
129-
if (file_exists($dumpedPath)) {
130-
$this->json = file_get_contents($dumpedPath);
129+
$dumpedImportMapPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_FILE_NAME;
130+
$dumpedModulePreloadPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_PRELOAD_FILE_NAME;
131+
if (file_exists($dumpedImportMapPath) && file_exists($dumpedModulePreloadPath)) {
132+
$this->json = file_get_contents($dumpedImportMapPath);
133+
$this->modulesToPreload = json_decode(file_get_contents($dumpedModulePreloadPath), true, 512, \JSON_THROW_ON_ERROR);
131134

132135
return;
133136
}

src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,20 @@ public function testAssetsAreCompiled()
4040
{
4141
$application = new Application($this->kernel);
4242

43+
$targetBuildDir = $this->kernel->getProjectDir().'/public/assets';
44+
// put old "built" versions to make sure the system skips using these
45+
$this->filesystem->mkdir($targetBuildDir);
46+
file_put_contents($targetBuildDir.'/manifest.json', '{}');
47+
file_put_contents($targetBuildDir.'/importmap.json', '{"imports": {}}');
48+
file_put_contents($targetBuildDir.'/importmap.preload.json', '{}');
49+
4350
$command = $application->find('assetmap:compile');
4451
$tester = new CommandTester($command);
4552
$res = $tester->execute([]);
4653
$this->assertSame(0, $res);
4754
// match Compiling \d+ assets
4855
$this->assertMatchesRegularExpression('/Compiling \d+ assets/', $tester->getDisplay());
4956

50-
$targetBuildDir = $this->kernel->getProjectDir().'/public/assets';
5157
$this->assertFileExists($targetBuildDir.'/subdir/file5-f4fdc37375c7f5f2629c5659a0579967.js');
5258
$this->assertSame(<<<EOF
5359
import '../file4.js';
@@ -57,8 +63,35 @@ public function testAssetsAreCompiled()
5763

5864
$finder = new Finder();
5965
$finder->in($targetBuildDir)->files();
60-
$this->assertCount(9, $finder);
66+
$this->assertCount(10, $finder);
6167
$this->assertFileExists($targetBuildDir.'/manifest.json');
68+
69+
$this->assertSame([
70+
'already-abcdefVWXYZ0123456789.digested.css',
71+
'file1.css',
72+
'file2.js',
73+
'file3.css',
74+
'file4.js',
75+
'subdir/file5.js',
76+
'subdir/file6.js',
77+
], array_keys(json_decode(file_get_contents($targetBuildDir.'/manifest.json'), true)));
78+
6279
$this->assertFileExists($targetBuildDir.'/importmap.json');
80+
$actualImportMap = json_decode(file_get_contents($targetBuildDir.'/importmap.json'), true);
81+
$this->assertSame([
82+
'@hotwired/stimulus',
83+
'lodash',
84+
'file6',
85+
'/assets/subdir/file5.js', // imported by file6
86+
'/assets/file4.js', // imported by file5
87+
], array_keys($actualImportMap['imports']));
88+
89+
$this->assertFileExists($targetBuildDir.'/importmap.preload.json');
90+
$actualPreload = json_decode(file_get_contents($targetBuildDir.'/importmap.preload.json'), true);
91+
$this->assertCount(4, $actualPreload);
92+
$this->assertStringStartsWith('https://unpkg.com/@hotwired/stimulus', $actualPreload[0]);
93+
$this->assertStringStartsWith('/assets/subdir/file6-', $actualPreload[1]);
94+
$this->assertStringStartsWith('/assets/subdir/file5-', $actualPreload[2]);
95+
$this->assertStringStartsWith('/assets/file4-', $actualPreload[3]);
6396
}
6497
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ public function testGetImportMapJsonUsesDumpedFile()
8484
'@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js',
8585
'app' => '/assets/app-ea9ebe6156adc038aba53164e2be0867.js',
8686
]], json_decode($manager->getImportMapJson(), true));
87+
$this->assertEquals([
88+
'/assets/app-ea9ebe6156adc038aba53164e2be0867.js',
89+
], $manager->getModulesToPreload());
8790
}
8891

8992
/**
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
return [
13+
'@hotwired/stimulus' => [
14+
'url' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js',
15+
'preload' => true,
16+
],
17+
'lodash' => [
18+
'url' => 'https://ga.jspm.io/npm:lodash@4.17.21/lodash.js',
19+
'preload' => false,
20+
],
21+
'file6' => [
22+
'path' => 'subdir/file6.js',
23+
'preload' => true,
24+
],
25+
];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"/assets/app-ea9ebe6156adc038aba53164e2be0867.js"
3+
]

0 commit comments

Comments
 (0)
0