├── .gitignore
├── .php_cs.dist
├── .travis.yml
├── .travis
├── after_script.php
├── before_install.php
├── common.php
├── install.php
└── script.php
├── CONTRIBUTING.md
├── Command
├── Options
│ ├── BaseOptionHelper.php
│ ├── ChoiceOptionHelper.php
│ └── SimpleOptionHelper.php
└── SetupCommand.php
├── DependencyInjection
├── AssetExtensionLoader.php
├── Compiler
│ └── AssetCompilerPass.php
├── Configuration.php
└── RjFrontendExtension.php
├── EventListener
└── InjectLiveReloadListener.php
├── LICENSE
├── Manifest
├── Loader
│ ├── AbstractManifestLoader.php
│ ├── CachedManifestLoader.php
│ ├── JsonManifestLoader.php
│ └── ManifestLoaderInterface.php
└── Manifest.php
├── Package
└── FallbackPackage.php
├── README.md
├── Resources
├── blueprints
│ ├── bower.json.php
│ ├── images
│ │ └── .keep
│ ├── pipelines
│ │ └── gulp
│ │ │ ├── gulpfile.js.php
│ │ │ └── package.json.php
│ ├── scripts
│ │ ├── coffee
│ │ │ └── app.coffee
│ │ └── js
│ │ │ └── app.js
│ └── stylesheets
│ │ ├── less
│ │ ├── app.less.php
│ │ └── vendor.less.php
│ │ ├── none
│ │ └── app.css.php
│ │ └── sass
│ │ ├── app.scss.php
│ │ └── vendor.scss.php
├── config
│ ├── asset.yml
│ ├── commands.yml
│ ├── console.yml
│ ├── fallback.yml
│ ├── livereload.yml
│ ├── manifest.yml
│ └── version_strategy.yml
└── doc
│ ├── _static
│ └── custom.css
│ ├── bower.rst
│ ├── conf.py
│ ├── deployment.rst
│ ├── directory-structure.rst
│ ├── index.rst
│ ├── referencing-assets.rst
│ └── setup.rst
├── RjFrontendBundle.php
├── Tests
├── Command
│ └── SetupCommandTest.php
├── DependencyInjection
│ ├── Compiler
│ │ ├── BaseCompilerPassTest.php
│ │ └── Packages
│ │ │ └── AssetCompilerPassTest.php
│ ├── ConfigurationTest.php
│ ├── RjFrontendExtensionAssetTest.php
│ ├── RjFrontendExtensionBaseTest.php
│ └── RjFrontendExtensionTest.php
├── EventListener
│ └── InjectLiveReloadListenerTest.php
├── Functional
│ ├── BaseTestCase.php
│ ├── InjectLivereloadTest.php
│ ├── PackagesTest.php
│ └── TestApp
│ │ ├── TestBundle
│ │ ├── Controller
│ │ │ ├── InjectLivereloadController.php
│ │ │ └── PackagesController.php
│ │ ├── Resources
│ │ │ └── views
│ │ │ │ └── Packages
│ │ │ │ ├── custom.html.php
│ │ │ │ ├── default.html.php
│ │ │ │ └── fallback.html.php
│ │ └── TestBundle.php
│ │ ├── app
│ │ ├── AppKernel.php
│ │ └── config
│ │ │ ├── config.yml
│ │ │ └── routing.yml
│ │ └── web
│ │ └── assets
│ │ └── manifest.json
├── Manifest
│ ├── Loader
│ │ ├── CachedManifestLoaderTest.php
│ │ └── JsonManifestLoaderTest.php
│ └── ManifestTest.php
├── Package
│ └── FallbackPackageTest.php
└── VersionStrategy
│ └── ManifestVersionStrategyTest.php
├── Util
└── Util.php
├── VersionStrategy
├── EmptyVersionStrategy.php
└── ManifestVersionStrategy.php
├── composer.json
├── phpunit.xml.dist
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /bin/
3 | /venv/
4 | /Resources/doc/_build/
5 | composer.lock
6 | .php_cs.cache
7 |
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | in(__DIR__)
5 | ->depth('> 0')
6 | ->exclude('venv')
7 | ->exclude('Resources')
8 | ;
9 |
10 | return PhpCsFixer\Config::create()
11 | ->setRules([
12 | '@Symfony' => true,
13 | 'array_syntax' => ['syntax' => 'short'],
14 | ])
15 | ->setFinder($finder)
16 | ;
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | branches:
4 | only:
5 | - master
6 |
7 | cache:
8 | directories:
9 | - vendor
10 | - venv
11 |
12 | before_install: ./.travis/before_install.php
13 | install: ./.travis/install.php
14 | script: ./.travis/script.php
15 | after_script: ./.travis/after_script.php
16 |
17 | env:
18 | global:
19 | - SYMFONY_DEPRECATIONS_HELPER=weak
20 |
21 | matrix:
22 | include:
23 | # old PHP versions
24 | - php: 5.4
25 | env: SYMFONY_VERSION=2.8.* # Symfony 3 doesn't support PHP 5.4
26 | - php: 5.6
27 | env: SYMFONY_VERSION=3.4.*
28 | # current PHP with all non-EOLed Symfony versions
29 | - php: 7.2
30 | env: SYMFONY_VERSION=2.7.*
31 | - php: 7.2
32 | env: SYMFONY_VERSION=2.8.*
33 | - php: 7.2
34 | env: SYMFONY_VERSION=3.2.*
35 | - php: 7.2
36 | env: SYMFONY_VERSION=3.3.*
37 | - php: 7.2
38 | env: SYMFONY_VERSION=3.4.*
39 | - php: 7.2
40 | env: SYMFONY_VERSION=dev-master
41 | allow_failures:
42 | - env: SYMFONY_VERSION=dev-master
43 |
--------------------------------------------------------------------------------
/.travis/after_script.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | command = $command;
57 | $this->input = $input;
58 | $this->output = $output;
59 | }
60 |
61 | /**
62 | * @param array $allowedValues
63 | *
64 | * @return $this
65 | */
66 | public function setAllowedValues(array $allowedValues)
67 | {
68 | $this->allowedValues = $allowedValues;
69 |
70 | return $this;
71 | }
72 |
73 | /**
74 | * @param string $errorMessage
75 | *
76 | * @return $this
77 | */
78 | public function setErrorMessage($errorMessage)
79 | {
80 | $this->errorMessage = $errorMessage;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * @param mixed $defaultValue
87 | *
88 | * @return $this
89 | */
90 | public function setDefaultValue($defaultValue)
91 | {
92 | $this->defaultValue = $defaultValue;
93 |
94 | return $this;
95 | }
96 |
97 | /**
98 | * @param string $name
99 | * @param string $question
100 | *
101 | * @return string|bool
102 | */
103 | public function setOption($name, $question)
104 | {
105 | $selection = $this->input->getOption($name);
106 | $allowed = $this->allowedValues;
107 | $error = $this->errorMessage;
108 |
109 | if (null !== $selection && !empty($allowed) && !in_array($selection, $allowed)) {
110 | $this->output->writeln(sprintf("$error", $selection));
111 | $selection = null;
112 | }
113 |
114 | if (null === $selection) {
115 | $selection = $this->ask($this->getQuestion($question));
116 | }
117 |
118 | if ($selection === 'false') {
119 | $selection = false;
120 | }
121 |
122 | if ($selection === 'true') {
123 | $selection = true;
124 | }
125 |
126 | $this->input->setOption($name, $selection);
127 |
128 | return $selection;
129 | }
130 |
131 | /**
132 | * @param Question $question
133 | *
134 | * @return mixed
135 | */
136 | private function ask(Question $question)
137 | {
138 | return $this->command->getHelper('question')->ask($this->input, $this->output, $question);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Command/Options/ChoiceOptionHelper.php:
--------------------------------------------------------------------------------
1 | $question", $this->allowedValues, $this->defaultValue);
15 | $question->setErrorMessage($this->errorMessage);
16 |
17 | return $question;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Command/Options/SimpleOptionHelper.php:
--------------------------------------------------------------------------------
1 | $question", $this->defaultValue);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Command/SetupCommand.php:
--------------------------------------------------------------------------------
1 | templating = new PhpEngine(
36 | new TemplateNameParser(),
37 | new FilesystemLoader([__DIR__.'/../Resources/blueprints/%name%'])
38 | );
39 | }
40 |
41 | /**
42 | * @param string $path
43 | */
44 | public function setRootDir($path)
45 | {
46 | $this->rootDir = $path;
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | protected function configure()
53 | {
54 | $this
55 | ->setName('rj_frontend:setup')
56 | ->setDescription('Generate the configuration for the asset pipeline')
57 | ->addOption(
58 | 'dry-run',
59 | null,
60 | InputOption::VALUE_NONE,
61 | 'Output which commands would have been run instead of running them'
62 | )
63 | ->addOption(
64 | 'force',
65 | null,
66 | InputOption::VALUE_NONE,
67 | 'Force execution'
68 | )
69 | ->addOption(
70 | 'src-dir',
71 | null,
72 | InputOption::VALUE_REQUIRED,
73 | 'Path to the directory containing the source assets [e.g. '.$this->getDefaultOption('src-dir').']'
74 | )
75 | ->addOption(
76 | 'dest-dir',
77 | null,
78 | InputOption::VALUE_REQUIRED,
79 | 'Path to the directory containing the compiled assets [e.g. '.$this->getDefaultOption('dest-dir').']'
80 | )
81 | ->addOption(
82 | 'pipeline',
83 | null,
84 | InputOption::VALUE_REQUIRED,
85 | 'Asset pipeline to use [only gulp is available at the moment]'
86 | )
87 | ->addOption(
88 | 'csspre',
89 | null,
90 | InputOption::VALUE_REQUIRED,
91 | 'CSS preprocessor to use [sass, less or none]'
92 | )
93 | ->addOption(
94 | 'coffee',
95 | null,
96 | InputOption::VALUE_REQUIRED,
97 | 'Use the CoffeeScript compiler [true or false]'
98 | )
99 | ;
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | protected function interact(InputInterface $input, OutputInterface $output)
106 | {
107 | $simpleOptionHelper = new SimpleOptionHelper($this, $input, $output);
108 | $choiceOptionHelper = new ChoiceOptionHelper($this, $input, $output);
109 |
110 | $simpleOptionHelper
111 | ->setDefaultValue($this->getDefaultOption('src-dir'))
112 | ->setOption(
113 | 'src-dir',
114 | 'Path to the directory containing the source assets [default is '.$this->getDefaultOption('src-dir').']'
115 | )
116 | ;
117 |
118 | $simpleOptionHelper
119 | ->setDefaultValue($this->getDefaultOption('dest-dir'))
120 | ->setOption(
121 | 'dest-dir',
122 | 'Path to the directory containing the compiled assets [default is '.$this->getDefaultOption('dest-dir').']'
123 | )
124 | ;
125 |
126 | $choiceOptionHelper
127 | ->setAllowedValues(['gulp'])
128 | ->setErrorMessage('%s is not a supported asset pipeline')
129 | ->setOption(
130 | 'pipeline',
131 | 'Asset pipeline to use [only gulp is available at the moment]'
132 | )
133 | ;
134 |
135 | $choiceOptionHelper
136 | ->setAllowedValues(['sass', 'less', 'none'])
137 | ->setErrorMessage('%s is not a supported CSS preprocessor')
138 | ->setOption(
139 | 'csspre',
140 | 'CSS preprocessor to use [default is '.$this->getDefaultOption('csspre').']'
141 | )
142 | ;
143 |
144 | $choiceOptionHelper
145 | ->setAllowedValues(['false', 'true'])
146 | ->setErrorMessage('%s is not a supported value for --coffee. Use either true or false')
147 | ->setOption(
148 | 'coffee',
149 | 'Whether to use the CoffeeScript compiler [default is '.$this->getDefaultOption('coffee').']'
150 | )
151 | ;
152 |
153 | $output->writeln('');
154 | }
155 |
156 | /**
157 | * {@inheritdoc}
158 | */
159 | protected function execute(InputInterface $input, OutputInterface $output)
160 | {
161 | $this->processOptions($input);
162 |
163 | $output->writeln('Selected options are:');
164 | $output->writeln('src-dir: '.$input->getOption('src-dir'));
165 | $output->writeln('dest-dir: '.$input->getOption('dest-dir'));
166 | $output->writeln('pipeline: '.$input->getOption('pipeline'));
167 | $output->writeln('csspre: '.$input->getOption('csspre'));
168 | $output->writeln('coffee: '.($input->getOption('coffee') ? 'true' : 'false'));
169 |
170 | if (!preg_match('|web/.+|', $input->getOption('dest-dir'))) {
171 | throw new \InvalidArgumentException("'dest-dir' must be a directory under web/");
172 | }
173 |
174 | $output->writeln('');
175 | $this->createSourceTree($input, $output);
176 | $this->createBuildFile($input, $output);
177 | $this->createPackageJson($input, $output);
178 | $this->createBowerJson($input, $output);
179 |
180 | $output->writeln('');
181 | $output->writeln('All files generated.');
182 | $output->writeln('');
183 | $output->writeln('Make sure to install dependencies:');
184 | $output->writeln('');
185 | $output->writeln(' npm install');
186 | $output->writeln('');
187 | }
188 |
189 | /**
190 | * @param InputInterface $input
191 | * @param OutputInterface $output
192 | */
193 | private function createSourceTree(InputInterface $input, OutputInterface $output)
194 | {
195 | $blueprints = __DIR__.'/../Resources/blueprints';
196 | $dryRun = $input->getOption('dry-run');
197 | $base = $input->getOption('src-dir');
198 |
199 | $output->writeln($dryRun
200 | ? 'Would have created directory tree for source assets:'
201 | : 'Creating directory tree for source assets:'
202 | );
203 |
204 | $blueprintDir = "$blueprints/images";
205 | $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/images");
206 |
207 | $blueprintDir = "$blueprints/stylesheets/".$input->getOption('csspre');
208 | $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/stylesheets");
209 |
210 | $blueprintDir = "$blueprints/scripts/";
211 | $blueprintDir .= $input->getOption('coffee') ? 'coffee' : 'js';
212 | $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/scripts");
213 |
214 | $output->writeln('');
215 | }
216 |
217 | /**
218 | * @param InputInterface $input
219 | * @param OutputInterface $output
220 | */
221 | private function createBuildFile(InputInterface $input, OutputInterface $output)
222 | {
223 | $files = [
224 | 'gulp' => 'gulp/gulpfile.js',
225 | ];
226 |
227 | $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
228 | }
229 |
230 | /**
231 | * @param InputInterface $input
232 | * @param OutputInterface $output
233 | */
234 | private function createPackageJson(InputInterface $input, OutputInterface $output)
235 | {
236 | $files = [
237 | 'gulp' => 'gulp/package.json',
238 | ];
239 |
240 | $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
241 | }
242 |
243 | /**
244 | * @param InputInterface $input
245 | * @param OutputInterface $output
246 | */
247 | private function createBowerJson(InputInterface $input, OutputInterface $output)
248 | {
249 | $this->createFileFromTemplate($input, $output, 'bower.json');
250 | }
251 |
252 | /**
253 | * @param InputInterface $input
254 | * @param OutputInterface $output
255 | * @param string $blueprintDir
256 | * @param string $targetDir
257 | */
258 | private function createDirFromBlueprint(InputInterface $input, OutputInterface $output, $blueprintDir, $targetDir)
259 | {
260 | $dryRun = $input->getOption('dry-run');
261 |
262 | if (!$dryRun && !file_exists($targetDir)) {
263 | mkdir($targetDir, 0777, true);
264 | }
265 |
266 | foreach (preg_grep('/^\.?\w+/', scandir($blueprintDir)) as $entry) {
267 | $target = $entry;
268 |
269 | $isPhpTemplate = substr($entry, strrpos($entry, '.')) === '.php';
270 | if ($isPhpTemplate) {
271 | $entry = str_replace('.php', '', $entry);
272 | $target = str_replace('.php', '', $target);
273 | }
274 |
275 | $entry = $blueprintDir.'/'.$entry;
276 | $target = $targetDir.'/'.$target;
277 |
278 | if (!$dryRun) {
279 | if ($isPhpTemplate) {
280 | $this->renderTemplate($input, $output, $entry, $target);
281 | } else {
282 | if (file_exists($target) && !$input->getOption('force')) {
283 | $output->writeln(
284 | "$target already exists. Run this command with --force to overwrite
285 | ");
286 |
287 | continue;
288 | }
289 |
290 | copy($entry, $target);
291 | }
292 | }
293 |
294 | $output->writeln($target);
295 | }
296 | }
297 |
298 | /**
299 | * @param InputInterface $input
300 | * @param OutputInterface $output
301 | * @param string $file
302 | */
303 | private function createFileFromTemplate(InputInterface $input, OutputInterface $output, $file)
304 | {
305 | $dryRun = $input->getOption('dry-run');
306 |
307 | $targetFile = basename($file);
308 | if (!empty($this->rootDir)) {
309 | $targetFile = $this->rootDir.'/'.$targetFile;
310 | }
311 |
312 | $output->writeln($dryRun
313 | ? "Would have created file $targetFile"
314 | : "Creating file $targetFile"
315 | );
316 |
317 | if ($dryRun) {
318 | return;
319 | }
320 |
321 | $this->renderTemplate($input, $output, $file, $targetFile);
322 | }
323 |
324 | /**
325 | * @param InputInterface $input
326 | * @param OutputInterface $output
327 | * @param string $file
328 | * @param string $target
329 | */
330 | private function renderTemplate(InputInterface $input, OutputInterface $output, $file, $target)
331 | {
332 | if (file_exists($target) && !$input->getOption('force')) {
333 | $output->writeln(
334 | "$target already exists. Run this command with --force to overwrite"
335 | );
336 | }
337 |
338 | switch ($input->getOption('csspre')) {
339 | case 'sass':
340 | $stylesheetExtension = 'scss';
341 | break;
342 | case 'less':
343 | $stylesheetExtension = 'less';
344 | break;
345 | default:
346 | $stylesheetExtension = 'css';
347 | break;
348 | }
349 |
350 | file_put_contents($target, $this->templating->render("$file.php", [
351 | 'projectName' => basename(getcwd()),
352 | 'srcDir' => $input->getOption('src-dir'),
353 | 'destDir' => $input->getOption('dest-dir'),
354 | 'prefix' => str_replace('web/', '', $input->getOption('dest-dir')),
355 | 'coffee' => $input->getOption('coffee'),
356 | 'cssPre' => $input->getOption('csspre'),
357 | 'stylesheetExtension' => $stylesheetExtension,
358 | ]));
359 | }
360 |
361 | /**
362 | * @param InputInterface $input
363 | */
364 | private function processOptions(InputInterface $input)
365 | {
366 | foreach ($input->getOptions() as $name => $value) {
367 | if (!$input->isInteractive() && $value === null) {
368 | $value = $this->getDefaultOption($name);
369 | }
370 |
371 | if ($value === 'true') {
372 | $value = true;
373 | } elseif ($value === 'false') {
374 | $value = false;
375 | }
376 |
377 | $input->setOption($name, $value);
378 | }
379 | }
380 |
381 | /**
382 | * @param string $name
383 | *
384 | * @return string
385 | */
386 | private function getDefaultOption($name)
387 | {
388 | $defaults = [
389 | 'src-dir' => empty($this->rootDir) ? 'app/Resources' : $this->rootDir.'/app/Resources',
390 | 'dest-dir' => empty($this->rootDir) ? 'web/assets' : $this->rootDir.'/web/assets',
391 | 'pipeline' => 'gulp',
392 | 'csspre' => 'sass',
393 | 'coffee' => 'false',
394 | ];
395 |
396 | return $defaults[$name];
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/DependencyInjection/AssetExtensionLoader.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
38 | $this->container = $container;
39 | }
40 |
41 | /**
42 | * @param array $config
43 | * @param LoaderInterface $loader
44 | */
45 | public function load(array $config, LoaderInterface $loader)
46 | {
47 | $loader->load('asset.yml');
48 |
49 | if ($config['override_default_package']) {
50 | $loader->load('fallback.yml');
51 |
52 | $defaultPackage = $this->createPackage('default', [
53 | 'prefix' => $config['prefix'],
54 | 'manifest' => $config['manifest'],
55 | ]);
56 |
57 | $defaultPackageId = $this->getPackageId('default');
58 | $this->container->setDefinition($defaultPackageId, $defaultPackage);
59 |
60 | $this->container->getDefinition($this->namespaceService('package.fallback'))
61 | ->addArgument($config['fallback_patterns'])
62 | ->addArgument(new Reference($defaultPackageId));
63 | }
64 |
65 | foreach ($config['packages'] as $name => $packageConfig) {
66 | $packageTag = $this->namespaceService('package.asset');
67 | $package = $this->createPackage($name, $packageConfig)
68 | ->addTag($packageTag, ['alias' => $name]);
69 |
70 | $this->container->setDefinition($this->getPackageId($name), $package);
71 | }
72 | }
73 |
74 | /**
75 | * @param string $name
76 | * @param array $config
77 | *
78 | * @return Definition
79 | */
80 | private function createPackage($name, array $config)
81 | {
82 | $prefixes = $config['prefix'];
83 | $isUrl = Util::containsUrl($prefixes);
84 |
85 | $packageDefinition = $isUrl
86 | ? new ChildDefinition($this->namespaceService('asset.package.url'))
87 | : new ChildDefinition($this->namespaceService('asset.package.path'))
88 | ;
89 |
90 | if ($config['manifest']['enabled']) {
91 | $versionStrategy = $this->createManifestVersionStrategy($name, $config['manifest']);
92 | } else {
93 | $versionStrategy = new Reference($this->namespaceService('version_strategy.empty'));
94 | }
95 |
96 | return $packageDefinition
97 | ->addArgument($isUrl ? $prefixes : $prefixes[0])
98 | ->addArgument($versionStrategy)
99 | ->setPublic(false);
100 | }
101 |
102 | /**
103 | * @param string $name
104 | *
105 | * @return string
106 | */
107 | private function getPackageId($name)
108 | {
109 | return $this->namespaceService("_package.$name");
110 | }
111 |
112 | /**
113 | * @param string $packageName
114 | * @param array $config
115 | *
116 | * @return Reference
117 | */
118 | private function createManifestVersionStrategy($packageName, $config)
119 | {
120 | $loader = new ChildDefinition($this->namespaceService('manifest.loader.'.$config['format']));
121 | $loader
122 | ->addArgument($config['path'])
123 | ->addArgument($config['root_key'])
124 | ;
125 |
126 | $loaderId = $this->namespaceService("_package.$packageName.manifest_loader");
127 | $this->container->setDefinition($loaderId, $loader);
128 |
129 | $cachedLoader = new ChildDefinition($this->namespaceService('manifest.loader.cached'));
130 | $cachedLoader->addArgument(new Reference($loaderId));
131 |
132 | $cachedLoaderId = $this->namespaceService("_package.$packageName.manifest_loader_cached");
133 | $this->container->setDefinition($cachedLoaderId, $cachedLoader);
134 |
135 | $versionStrategy = new ChildDefinition($this->namespaceService('version_strategy.manifest'));
136 | $versionStrategy->addArgument(new Reference($cachedLoaderId));
137 |
138 | $versionStrategyId = $this->namespaceService("_package.$packageName.version_strategy");
139 | $this->container->setDefinition($versionStrategyId, $versionStrategy);
140 |
141 | return new Reference($versionStrategyId);
142 | }
143 |
144 | /**
145 | * @param string $id
146 | *
147 | * @return string
148 | */
149 | private function namespaceService($id)
150 | {
151 | return $this->alias.'.'.$id;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/AssetCompilerPass.php:
--------------------------------------------------------------------------------
1 | getRegisteredPackages($container);
19 |
20 | foreach ($this->getTaggedPackages($container) as $id => $tags) {
21 | if (empty($tags) || !isset($tags[0]['alias'])) {
22 | throw new \LogicException(
23 | "The tag for the service with id '$id' must define an 'alias' attribute"
24 | );
25 | }
26 |
27 | $packageName = $tags[0]['alias'];
28 |
29 | if (isset($registeredPackages[$packageName])) {
30 | throw new \LogicException(
31 | "A package named '$packageName' has already been registered"
32 | );
33 | }
34 |
35 | if (isset($packages[$packageName])) {
36 | throw new \LogicException(
37 | "Multiple packages were found with alias '$packageName'. Package alias' must be unique"
38 | );
39 | }
40 |
41 | $packages[$packageName] = $id;
42 | }
43 |
44 | $this->addPackages($packages, $container);
45 |
46 | if ($container->hasDefinition($this->namespaceService('package.fallback'))) {
47 | $this->setDefaultPackage($container);
48 | }
49 | }
50 |
51 | /**
52 | * @param ContainerBuilder $container
53 | *
54 | * @return Definition[]
55 | */
56 | private function getTaggedPackages(ContainerBuilder $container)
57 | {
58 | return $container->findTaggedServiceIds($this->namespaceService('package.asset'));
59 | }
60 |
61 | /**
62 | * @param ContainerBuilder $container
63 | *
64 | * @return Definition
65 | */
66 | private function getPackagesService(ContainerBuilder $container)
67 | {
68 | if (!$container->hasDefinition('assets.packages')) {
69 | throw new \LogicException('The Asset component is not registered in the container');
70 | }
71 |
72 | return $container->getDefinition('assets.packages');
73 | }
74 |
75 | /**
76 | * @param Definition[] $packages
77 | * @param ContainerBuilder $container
78 | */
79 | private function addPackages($packages, ContainerBuilder $container)
80 | {
81 | $packagesService = $this->getPackagesService($container);
82 |
83 | foreach ($packages as $name => $id) {
84 | $packagesService->addMethodCall(
85 | 'addPackage',
86 | [$name, new Reference($id)]
87 | );
88 | }
89 | }
90 |
91 | /**
92 | * @param ContainerBuilder $container
93 | */
94 | private function setDefaultPackage(ContainerBuilder $container)
95 | {
96 | $packagesService = $this->getPackagesService($container);
97 | $defaultPackage = $this->getRegisteredDefaultPackage($container);
98 | $fallbackPackageId = $this->namespaceService('package.fallback');
99 |
100 | $container->getDefinition($fallbackPackageId)->addMethodCall('setFallback', [$defaultPackage]);
101 |
102 | $packagesService->replaceArgument(0, new Reference($fallbackPackageId));
103 | }
104 |
105 | /**
106 | * Retrieve packages that have already been registered.
107 | *
108 | * @param ContainerBuilder $container
109 | *
110 | * @return array with the packages' name as keys
111 | */
112 | private function getRegisteredPackages(ContainerBuilder $container)
113 | {
114 | $arguments = $this->getPackagesService($container)->getArguments();
115 |
116 | if (!isset($arguments[1]) || count($arguments[1]) < 2) {
117 | return [];
118 | }
119 |
120 | $argPackages = $arguments[1];
121 |
122 | $packages = [];
123 | $argCount = count($argPackages);
124 | for ($i = 0; $i < $argCount; ++$i) {
125 | $packages[$argPackages[$i]] = $argPackages[++$i];
126 | }
127 |
128 | return $packages;
129 | }
130 |
131 | /**
132 | * @param ContainerBuilder $container
133 | *
134 | * @return Definition|null
135 | */
136 | private function getRegisteredDefaultPackage(ContainerBuilder $container)
137 | {
138 | $arguments = $this->getPackagesService($container)->getArguments();
139 |
140 | if (!isset($arguments[0])) {
141 | return null;
142 | }
143 |
144 | return $arguments[0];
145 | }
146 |
147 | /**
148 | * @param string $id
149 | *
150 | * @return string
151 | */
152 | private function namespaceService($id)
153 | {
154 | return "rj_frontend.$id";
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | kernelRootDir = $kernelRootDir;
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function getConfigTreeBuilder()
32 | {
33 | $self = $this;
34 |
35 | return $this->createRoot('rj_frontend', 'array')
36 | ->children()
37 | ->booleanNode('override_default_package')->defaultTrue()->end()
38 | ->arrayNode('fallback_patterns')
39 | ->prototype('scalar')->end()
40 | ->defaultValue(['.*bundles\/.*'])
41 | ->end()
42 | ->append($this->addLivereloadSection())
43 | ->append($this->addPackagePrefixSection(self::DEFAULT_PREFIX))
44 | ->append($this->addPackageManifestSection())
45 | ->arrayNode('packages')
46 | ->useAttributeAsKey('name')
47 | ->prototype('array')
48 | ->children()
49 | ->append($this->addPackagePrefixSection())
50 | ->append($this->addPackageManifestSection())
51 | ->end()
52 | ->beforeNormalization()
53 | ->ifTrue(function ($config) use ($self) {
54 | return $self->mustApplyManifestDefaultPath($config);
55 | })
56 | ->then(function ($config) use ($self) {
57 | return $self->applyManifestDefaultPath($config);
58 | })
59 | ->end()
60 | ->end()
61 | ->validate()
62 | ->ifTrue(function ($config) {
63 | return in_array('default', array_keys($config));
64 | })
65 | ->thenInvalid("'default' is a reserved package name")
66 | ->end()
67 | ->end()
68 | ->end()
69 | ->beforeNormalization()
70 | ->ifTrue(function ($config) use ($self) {
71 | return $self->mustApplyManifestDefaultPath($config);
72 | })
73 | ->then(function ($config) use ($self) {
74 | return $self->applyManifestDefaultPath($config);
75 | })
76 | ->end()
77 | ->end();
78 | }
79 |
80 | private function addLivereloadSection()
81 | {
82 | return $this->createRoot('livereload')
83 | ->canBeDisabled()
84 | ->children()
85 | ->scalarNode('url')
86 | ->defaultValue('//localhost:35729/livereload.js')
87 | ->end()
88 | ->end()
89 | ;
90 | }
91 |
92 | private function addPackagePrefixSection($defaultValue = null)
93 | {
94 | $node = $this->createRoot('prefix')
95 | ->prototype('scalar')->end()
96 | ->defaultValue([$defaultValue])
97 | ->requiresAtLeastOneElement()
98 | ->beforeNormalization()
99 | ->ifString()
100 | ->then(function ($v) { return [$v]; })
101 | ->end()
102 | ->validate()
103 | ->ifTrue(function ($prefixes) {
104 | return Util::containsUrl($prefixes)
105 | && Util::containsNotUrl($prefixes);
106 | })
107 | ->thenInvalid('Packages cannot have both URL and path prefixes')
108 | ->end()
109 | ->validate()
110 | ->ifTrue(function ($prefixes) {
111 | return count($prefixes) > 1
112 | && Util::containsNotUrl($prefixes);
113 | })
114 | ->thenInvalid('Packages can only have one path prefix')
115 | ->end()
116 | ;
117 |
118 | return $defaultValue === null
119 | ? $node->isRequired()
120 | : $node
121 | ;
122 | }
123 |
124 | private function addPackageManifestSection()
125 | {
126 | return $this->createRoot('manifest')
127 | ->canBeEnabled()
128 | ->children()
129 | ->scalarNode('format')
130 | ->defaultValue('json')
131 | ->validate()
132 | ->ifNotInArray(['json'])
133 | ->thenInvalid('For the moment only JSON manifest files are supported')
134 | ->end()
135 | ->end()
136 | ->scalarNode('path')->isRequired()->end()
137 | ->scalarNode('root_key')->defaultNull()->end()
138 | ->end()
139 | ->beforeNormalization()
140 | ->ifString()
141 | ->then(function ($v) { return ['enabled' => true, 'path' => $v]; })
142 | ->end()
143 | ;
144 | }
145 |
146 | /**
147 | * Returns true if the manifest's path has not been defined AND:
148 | * - a prefix has not been defined
149 | * - OR if a prefix has been defined, it's not a URL.
150 | *
151 | * Note that the manifest's configuration can be a string, in which case it
152 | * represents the path to the manifest file.
153 | *
154 | * This method is public because of the inability to use $this in closures
155 | * in PHP 5.3.
156 | *
157 | * @param array $config
158 | *
159 | * @return bool
160 | */
161 | public function mustApplyManifestDefaultPath(array $config)
162 | {
163 | return isset($config['manifest']) &&
164 | !is_string($config['manifest']) &&
165 | !isset($config['manifest']['path']) &&
166 | (!isset($config['prefix']) || !Util::containsUrl($config['prefix']))
167 | ;
168 | }
169 |
170 | /**
171 | * Apply a default manifest path computed from the defined prefix.
172 | *
173 | * After calling this method, the manifest's path will be
174 | * %kernel.root_dir%/../web/$prefix/manifest.json, where $prefix is the
175 | * configured prefix.
176 | *
177 | * Note that this method is used for both the default package's config and
178 | * for each custom package's config.
179 | *
180 | * This method is public because of the inability to use $this in closures
181 | * in PHP 5.3
182 | *
183 | * @param array $config
184 | *
185 | * @return array
186 | */
187 | public function applyManifestDefaultPath(array $config)
188 | {
189 | $prefix = isset($config['prefix']) ? $config['prefix'] : self::DEFAULT_PREFIX;
190 |
191 | if (is_array($prefix)) {
192 | $prefix = $prefix[0];
193 | }
194 |
195 | if (!is_array($config['manifest'])) {
196 | $config['manifest'] = ['enabled' => true];
197 | }
198 |
199 | $config['manifest']['path'] = implode('/', [
200 | $this->kernelRootDir,
201 | '..',
202 | 'web',
203 | $prefix,
204 | 'manifest.json',
205 | ]);
206 |
207 | return $config;
208 | }
209 |
210 | /**
211 | * @param string $root
212 | * @param string|null $type
213 | *
214 | * @return ArrayNodeDefinition|NodeDefinition
215 | */
216 | private function createRoot($root, $type = null)
217 | {
218 | $treeBuilder = new TreeBuilder();
219 |
220 | if ($type !== null) {
221 | return $treeBuilder->root($root, $type);
222 | }
223 |
224 | return $treeBuilder->root($root);
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/DependencyInjection/RjFrontendExtension.php:
--------------------------------------------------------------------------------
1 | getConfiguration([], $container);
19 | $config = $this->processConfiguration($configuration, $configs);
20 |
21 | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/'));
22 | $loader->load('console.yml');
23 | $loader->load('version_strategy.yml');
24 | $loader->load('manifest.yml');
25 |
26 | if (version_compare(Kernel::VERSION, '3.3.0', '>=')) {
27 | $loader->load('commands.yml');
28 | }
29 |
30 | if ($config['livereload']['enabled']) {
31 | $loader->load('livereload.yml');
32 | $container->getDefinition($this->namespaceService('livereload.listener'))
33 | ->addArgument($config['livereload']['url']);
34 | }
35 |
36 | $assetExtensionLoader = new AssetExtensionLoader($this->getAlias(), $container);
37 | $assetExtensionLoader->load($config, $loader);
38 | }
39 |
40 | /**
41 | * @param array $config
42 | * @param ContainerBuilder $container
43 | *
44 | * @return Configuration
45 | */
46 | public function getConfiguration(array $config, ContainerBuilder $container)
47 | {
48 | return new Configuration($container->getParameter('kernel.root_dir'));
49 | }
50 |
51 | /**
52 | * @param string $id
53 | *
54 | * @return string
55 | */
56 | private function namespaceService($id)
57 | {
58 | return $this->getAlias().'.'.$id;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/EventListener/InjectLiveReloadListener.php:
--------------------------------------------------------------------------------
1 | url = $url;
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function onKernelResponse(FilterResponseEvent $event)
29 | {
30 | if (!$this->shouldInject($event)) {
31 | return;
32 | }
33 |
34 | $response = $event->getResponse();
35 | $content = $response->getContent();
36 |
37 | $pos = strripos($content, '