Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1aad46c

Browse files
committed
feat: add support for local files
1 parent 7fa1707 commit 1aad46c

File tree

13 files changed

+169
-9
lines changed

13 files changed

+169
-9
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,13 +2387,25 @@ private function addImportMapsSection(ArrayNodeDefinition $rootNode, callable $e
23872387
->defaultValue('%kernel.project_dir%/importmap.php')
23882388
->end()
23892389
->scalarNode('vendor_dir')
2390-
->info('The directory where to store Javascript vendors.')
2390+
->info('The directory where to store JavaScript vendors.')
23912391
->defaultValue('%kernel.project_dir%/public/vendor/')
23922392
->end()
23932393
->scalarNode('vendor_url')
2394-
->info('The base URL to access Javascript vendors.')
2394+
->info('The base URL to access JavaScript vendors.')
23952395
->defaultValue('/vendor/')
23962396
->end()
2397+
->scalarNode('javascript_dir')
2398+
->info('The directory where to store local JavaScript modules.')
2399+
->defaultValue('%kernel.project_dir%/javascript/')
2400+
->end()
2401+
->scalarNode('public_javascript_dir')
2402+
->info('The directory where to store the publicly exposed local JavaScript modules.')
2403+
->defaultValue('%kernel.project_dir%/public/javascript/')
2404+
->end()
2405+
->scalarNode('javascript_url')
2406+
->info('The base URL to access JavaScript modules.')
2407+
->defaultValue('/javascript/')
2408+
->end()
23972409
->enumNode('provider')
23982410
->info('The provider (CDN) to use.')
23992411
->values(class_exists(Provider::class) ? array_map(fn (Provider $p): string => $p->value, Provider::cases()) : [])

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
9494
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
9595
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
96+
use Symfony\Component\ImportMaps\Controller\ImportmapController;
9697
use Symfony\Component\ImportMaps\ImportMapManager;
9798
use Symfony\Component\ImportMaps\Provider;
9899
use Symfony\Component\Lock\LockFactory;
@@ -2980,13 +2981,20 @@ private function registerImportMapsConfiguration(array $config, ContainerBuilder
29802981
{
29812982
$loader->load('import_maps.php');
29822983

2984+
$container
2985+
->getDefinition(ImportmapController::class)
2986+
->replaceArgument(0, $config['javascript_dir']);
2987+
29832988
$container
29842989
->getDefinition(ImportMapManager::class)
29852990
->replaceArgument(0, $config['path'])
29862991
->replaceArgument(1, $config['vendor_dir'])
29872992
->replaceArgument(2, $config['vendor_url'])
2988-
->replaceArgument(3, Provider::from($config['provider']))
2989-
->replaceArgument(5, $config['api'])
2993+
->replaceArgument(3, $config['javascript_dir'])
2994+
->replaceArgument(4, $config['public_javascript_dir'])
2995+
->replaceArgument(5, $config['javascript_url'])
2996+
->replaceArgument(6, Provider::from($config['provider']))
2997+
->replaceArgument(8, $config['api'])
29902998
;
29912999
}
29923000

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,28 @@
1111
use Symfony\Component\ImportMaps\Command\RemoveCommand;
1212
use Symfony\Component\ImportMaps\Command\RequireCommand;
1313
use Symfony\Component\ImportMaps\Command\UpdateCommand;
14+
use Symfony\Component\ImportMaps\Controller\ImportmapController;
1415
use Symfony\Component\ImportMaps\ImportMapManager;
1516

1617
return static function (ContainerConfigurator $container): void {
1718
$container->services()
1819

20+
->set(ImportmapController::class)
21+
->args([
22+
abstract_arg('javascript directory'),
23+
service(ImportMapManager::class),
24+
service('filesystem'),
25+
])
26+
->public()
27+
1928
->set(ImportMapManager::class)
2029
->args([
2130
abstract_arg('importmap.php path'),
2231
abstract_arg('vendor directory'),
2332
abstract_arg('vendor URL'),
33+
abstract_arg('javascript directory'),
34+
abstract_arg('public javascript directory'),
35+
abstract_arg('javascript URL'),
2436
abstract_arg('provider'),
2537
service('http_client')->nullOnInvalid(),
2638
abstract_arg('JSPM API URL'),
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<routes xmlns="http://symfony.com/schema/routing"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
6+
7+
<route id="_importmap_controller" path="/javascript/{path}">
8+
<default key="_controller">Symfony\Component\ImportMaps\Controller\ImportmapController::handle</default>
9+
<requirement key="path">.*</requirement>
10+
</route>
11+
</routes>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\ImportMaps\Controller;
13+
14+
use Symfony\Component\Filesystem\Filesystem;
15+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
19+
use Symfony\Component\ImportMaps\ImportMapManager;
20+
21+
/**
22+
* Controller to use only in development mode.
23+
*
24+
* @author Kévin Dunglas <[email protected]>
25+
*/
26+
final class ImportmapController
27+
{
28+
public function __construct(
29+
private readonly string $javascriptsDir,
30+
private readonly ImportMapManager $importMapManager,
31+
private readonly Filesystem $filesystem = new Filesystem(),
32+
) {
33+
}
34+
35+
public function handle(string $path): Response
36+
{
37+
if (
38+
// prevent path traversing attacks
39+
!preg_match('/^([a-zA-Z0-9_@\/].*)\.(\w+)\.js$/', $path, $matches) ||
40+
!($mappedPath = $this->importMapManager->getImportMapArray()[$matches[1]]['path'] ?? null)
41+
) {
42+
throw new NotFoundHttpException();
43+
}
44+
45+
if (
46+
!$this->filesystem->exists($localPath = $this->javascriptsDir.$mappedPath)
47+
|| $matches[2] !== hash('xxh128', file_get_contents($localPath))
48+
) {
49+
throw new NotFoundHttpException();
50+
}
51+
52+
return new BinaryFileResponse($localPath, headers: ['Content-Type' => 'text/javascript']);
53+
}
54+
}

src/Symfony/Component/ImportMaps/ImportMapManager.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public function __construct(
4040
private readonly string $path = 'importmap.php',
4141
private readonly string $vendorDir = 'public/vendor/',
4242
private readonly string $vendorUrl = '/vendor/',
43+
private readonly string $javascriptDir = 'javascript/',
44+
private readonly string $publicJavascriptDir = 'public/javascript/',
45+
private readonly string $javascriptUrl = '/javascript/',
4346
private readonly Provider $provider = Provider::Jspm,
4447
private ?HttpClientInterface $httpClient = null,
4548
private readonly string $api = 'https://api.jspm.io',
@@ -64,13 +67,29 @@ public function getImportMap(): string
6467

6568
$importmap = ['imports' => []];
6669
foreach ($this->importMap as $package => $data) {
67-
$importmap['imports'][$package] = isset($data['digest']) ? $this->vendorUrl.$data['digest'] : $data['url'];
70+
if (isset($data['url'])) {
71+
$importmap['imports'][$package] = isset($data['digest']) ? $this->vendorUrl.$data['digest'] : $data['url'];
72+
73+
continue;
74+
}
75+
76+
if (isset($data['path'])) {
77+
$importmap['imports'][$package] = $this->javascriptUrl.$this->digestName($package, $data['path']);
78+
}
6879
}
6980

7081
// Use JSON_UNESCAPED_SLASHES | JSON_HEX_TAG to prevent XSS
7182
return json_encode($importmap, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG);
7283
}
7384

85+
// TODO: find a better name
86+
public function getImportMapArray(): array
87+
{
88+
$this->loadImportMap();
89+
90+
return $this->importMap;
91+
}
92+
7493
/**
7594
* Adds or updates packages.
7695
*
@@ -111,6 +130,17 @@ private function createImportMap(Env $env, ?Provider $provider, bool $update, ar
111130
$install = [];
112131
$packages = [];
113132
foreach ($this->importMap ?? [] as $name => $data) {
133+
if (isset($data['path'])) {
134+
$this->filesystem->mkdir($this->publicJavascriptDir);
135+
$this->filesystem->copy($this->javascriptDir.$data['path'], $this->publicJavascriptDir.$this->digestName($name, $data['path']));
136+
137+
continue;
138+
}
139+
140+
if (!$data['url']) {
141+
continue;
142+
}
143+
114144
$packages[$name] = new PackageOptions((bool) ($data['digest'] ?? false), $data['preload'] ?? false);
115145

116146
if (preg_match(self::PACKAGE_PATTERN, $data['url'], $matches)) {
@@ -184,7 +214,7 @@ private function jspmGenerate(Env $env, ?Provider $provider, array $install, arr
184214
continue;
185215
}
186216

187-
$this->importMap[$packageName]['digest'] = sprintf('%s-%s.js', $packageName, hash('xxh128', $url));
217+
$this->importMap[$packageName]['digest'] = sprintf('%s.%s.js', $packageName, hash('xxh128', $url));
188218
if ($this->importMap[$packageName]['digest'] === ($previousPackageData['digest'] ?? null)) {
189219
continue;
190220
}
@@ -206,4 +236,9 @@ private function removeIfExists(string $path): void
206236
$this->filesystem->remove($path);
207237
}
208238
}
239+
240+
private function digestName(string $package, string $path): string
241+
{
242+
return sprintf('%s.%s.js', $package, hash('xxh128', file_get_contents($this->javascriptDir.$path)));
243+
}
209244
}

src/Symfony/Component/ImportMaps/Tests/App/Kernel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ protected function configureContainer(ContainerConfigurator $container): void
6868

6969
protected function configureRoutes(RoutingConfigurator $routes): void
7070
{
71+
$routes->import('@FrameworkBundle/Resources/config/routing/importmap.xml');
7172
$routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt');
7273
$routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler');
7374

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
<?php
22

33
return [
4+
'application' => [
5+
'path' => 'application.js',
6+
],
7+
'controllers/hello_controller' => [
8+
'path' => 'controllers/hello_controller.js',
9+
],
410
'@hotwired/stimulus' => [
511
'url' => 'https://ga.jspm.io/npm:@hotwired/[email protected]/dist/stimulus.js',
6-
'digest' => '@hotwired/stimulus-997ad71d32657837726ccf3fe40b8b53.js',
12+
'digest' => '@hotwired/stimulus.997ad71d32657837726ccf3fe40b8b53.js',
713
],
814
];

src/Symfony/Component/ImportMaps/Tests/App/public/js/application.js renamed to src/Symfony/Component/ImportMaps/Tests/App/javascript/application.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Application } from "@hotwired/stimulus"
2-
import HelloController from "./controllers/hello_controller.js"
2+
import HelloController from "controllers/hello_controller"
33

44
const application = Application.start()
55

0 commit comments

Comments
 (0)