diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dc7816a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Shopware 5 editor configuration normalization +# http://editorconfig.org/ + +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = lf +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.ini] +insert_final_newline = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5287f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +vendor \ No newline at end of file diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..513940a --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,23 @@ +<?php +$finder = PhpCsFixer\Finder::create()->in(__DIR__); + +return PhpCsFixer\Config::create() + ->setUsingCache(false) + ->setRules( + [ + '@PSR2' => true, + '@Symfony' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'phpdoc_order' => true, + 'phpdoc_summary' => false, + 'blank_line_after_opening_tag' => false, + 'concat_space' => ['spacing' => 'one'], + 'array_syntax' => ['syntax' => 'short'], + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + 'binary_operator_spaces' => ['default' => null], + ] + ) + ->setFinder($finder); diff --git a/Commands/PurifyCssCommand.php b/Commands/PurifyCssCommand.php new file mode 100644 index 0000000..e6bb898 --- /dev/null +++ b/Commands/PurifyCssCommand.php @@ -0,0 +1,43 @@ +<?php + +namespace FroshPerformance\Commands; + +use Shopware\Commands\ShopwareCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class PurifyCssCommand extends ShopwareCommand +{ + public function execute(InputInterface $input, OutputInterface $output) + { + $purify = $this->container->get('frosh_performance.components.purify_css'); + $io = new SymfonyStyle($input, $output); + + if (!$purify->isRunnable()) { + $io->error('Please install first purify-css'); + exit(1); + } + + list($before, $after) = $this->container->get('frosh_performance.components.purify_css')->purify($input->getArgument('shopId')); + + $io->success(sprintf('Purified css from %s to %s', $this->humanFilesize($before), $this->humanFilesize($after))); + } + + protected function configure() + { + $this + ->setName('frosh:purify:css') + ->setDescription('Removes unused css styles') + ->addArgument('shopId', InputArgument::OPTIONAL, 'Shop id', 1); + } + + private function humanFilesize($bytes, $decimals = 2) + { + $sz = 'BKMGTP'; + $factor = floor((strlen($bytes) - 1) / 3); + + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]; + } +} diff --git a/Components/PurifyCss.php b/Components/PurifyCss.php new file mode 100644 index 0000000..770e966 --- /dev/null +++ b/Components/PurifyCss.php @@ -0,0 +1,109 @@ +<?php + +namespace FroshPerformance\Components; + +use Shopware\Components\Model\ModelManager; +use Shopware\Components\Theme\PathResolver; +use Shopware\Components\Theme\TimestampPersistor; +use Shopware\Models\Shop\Shop; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; + +class PurifyCss +{ + /** + * @var string + */ + private $kernelRootDir; + + /** + * @var PathResolver + */ + private $pathResolver; + + /** + * @var TimestampPersistor + */ + private $timestampPersistor; + + /** + * @var ModelManager + */ + private $manager; + + /** + * PurifyCss constructor. + * + * @param string $kernelRootDir + * @param TimestampPersistor $timestampPersistor + * @param PathResolver $pathResolver + * @param ModelManager $manager + */ + public function __construct($kernelRootDir, TimestampPersistor $timestampPersistor, PathResolver $pathResolver, ModelManager $manager) + { + $this->kernelRootDir = rtrim($kernelRootDir, '/'); + $this->timestampPersistor = $timestampPersistor; + $this->pathResolver = $pathResolver; + $this->manager = $manager; + } + + /** + * @return bool + */ + public function isRunnable() + { + $finder = new ExecutableFinder(); + + return (bool) $finder->find('purifycss'); + } + + /** + * @param int $shopId + * + * @return array + */ + public function purify($shopId) + { + $finder = new ExecutableFinder(); + $timestamp = $this->timestampPersistor->getCurrentTimestamp($shopId); + + $shop = $this->manager->find(Shop::class, $shopId); + + if (!$shop) { + throw new \RuntimeException(sprintf('Shop with id %d does not exist', $shopId)); + } + + $fileName = $this->pathResolver->buildTimestampName($timestamp, $shop, 'css'); + $filePath = $this->kernelRootDir . '/web/cache/' . $fileName; + + $beforeSize = filesize($filePath); + + $arguments = [ + $this->kernelRootDir . '/themes/**/*.js', + $this->kernelRootDir . '/themes/**/*.tpl', + $this->kernelRootDir . '/custom/**/*.js', + $this->kernelRootDir . '/custom/**/*.tpl', + $this->kernelRootDir . '/engine/Shopware/Plugins/**/*.tpl', + $this->kernelRootDir . '/engine/Shopware/Plugins/**/*.js', + '-m', + ]; + + array_unshift($arguments, $filePath); + array_unshift($arguments, $finder->find('purifycss')); + array_unshift($arguments, $finder->find('node')); + + $arguments[] = '-o'; + $arguments[] = $filePath; + + $process = new Process($arguments); + $process->run(); + + if ($process->getExitCode()) { + throw new \RuntimeException(sprintf('Purify failed with message %s and code %d', $process->getErrorOutput(), $process->getExitCode())); + } + + $afterSize = filesize($filePath); + + return [$beforeSize, $afterSize]; + } +} diff --git a/README.md b/README.md index 428100c..c2f2d02 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Performance Improvments for Shopware * Minify Html Output * Http 2 Server Push [#1397](https://github.com/shopware/shopware/pull/1397) * Caching of plugin configuration +* [Purify-CSS](https://github.com/purifycss/purifycss) Integration ## Requirements diff --git a/Resources/services.xml b/Resources/services.xml index a0a6f99..c729384 100644 --- a/Resources/services.xml +++ b/Resources/services.xml @@ -51,5 +51,16 @@ <argument type="service" id="frosh_performance.components.cached_config_reader.inner"/> <argument type="service" id="cache"/> </service> + + <service id="frosh_performance.commands.purify_css_command" class="FroshPerformance\Commands\PurifyCssCommand"> + <tag name="console.command" command="frosh:purify:css"/> + </service> + + <service id="frosh_performance.components.purify_css" class="FroshPerformance\Components\PurifyCss"> + <argument>%shopware.app.rootdir%</argument> + <argument type="service" id="theme_timestamp_persistor"/> + <argument type="service" id="theme_path_resolver"/> + <argument type="service" id="models"/> + </service> </services> </container> \ No newline at end of file