diff --git a/.gitignore b/.gitignore index 657c315552ed4e9a89623814ed54e2d81903f062..1e6c8cdcecef6cdad04fdea14e744b760cb2816e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ atlassian* /lib/internal/flex/varien/.project /lib/internal/flex/varien/.settings /node_modules +/.grunt /pub/media/*.* !/pub/media/.htaccess diff --git a/Gruntfile.js b/Gruntfile.js index 5c391d89ab09b865dcddb1cd207b1d429c49e52f..097358c045354b708dcaf0fba0648cab1c0ff435 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -175,14 +175,17 @@ module.exports = function (grunt) { }, all: { cmd: function () { - var command = '', + var command = [], cmdPlus = (/^win/.test(process.platform) == true) ? ' & ' : ' && ', themes = Object.keys(theme), i = 0; for (i; i < themes.length; i++) { - command += combo.collector(themes[i]) + cmdPlus; + command.push(combo.collector(themes[i])); } + + command = command.join(cmdPlus); return 'echo ' + command; + } } }, diff --git a/dev/tools/Magento/Tools/Webdev/Collector.php b/dev/tools/Magento/Tools/Webdev/Collector.php new file mode 100644 index 0000000000000000000000000000000000000000..20751f245cb8c049bc85b7e5ea8057caa30a6cf5 --- /dev/null +++ b/dev/tools/Magento/Tools/Webdev/Collector.php @@ -0,0 +1,119 @@ +<?php +/** + * {license_notice} + * + * @copyright {copyright} + * @license {license_link} + */ + +namespace Magento\Tools\Webdev; + +use Magento\Framework\Test\Utility\Files; +use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\App\View\Deployment\Version; + +/** + * A service for Collecting less sources + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Collector +{ + /** + * @var \Magento\Framework\Less\FileGenerator + */ + protected $fileGenerator; + + /** + * @var \Magento\Framework\App\State + */ + protected $_appState; + + /** @var ObjectManagerFactory */ + private $omFactory; + + /** @var \Magento\Tools\View\Deployer\Log */ + private $logger; + + /** @var \Magento\Framework\View\Asset\Repository */ + private $assetRepo; + + /** @var \Magento\Framework\View\Asset\Source */ + private $assetSource; + + /** @var \Magento\Framework\App\View\Asset\Publisher */ + private $assetPublisher; + + /** + * @param \Magento\Framework\View\Asset\Source $assetSource + * @param \Magento\Framework\App\State $_appState + * @param \Magento\Tools\View\Deployer\Log $logger + * @param \Magento\Framework\Less\FileGenerator $fileGenerator + */ + public function __construct( + \Magento\Framework\View\Asset\Source $assetSource, + \Magento\Framework\App\State $_appState, + \Magento\Tools\View\Deployer\Log $logger, + \Magento\Framework\Less\FileGenerator $fileGenerator + ) { + $this->logger = $logger; + $this->fileGenerator = $fileGenerator; + $this->assetSource = $assetSource; + $this->_appState = $_appState; + } + + /** + * @param ObjectManagerFactory $omFactory + * @param $locale + * @param $area + * @param $theme + * @param array $files + */ + public function tree(ObjectManagerFactory $omFactory, $locale, $area, $theme, array $files) + { + $this->omFactory = $omFactory; + $this->logger->logMessage("Gathering {$area}/{$locale}/{$theme} sources."); + $this->emulateApplicationArea($area); + $this->_appState->setAreaCode($area); + foreach ($files as $file ) { + $file .= '.less'; + $this->logger->logMessage("Gathering {$file} sources."); + $asset = $this->assetRepo->createAsset( + $file, + ['area' => $area, 'theme' => $theme, 'locale' => $locale] + ); + $sourceFile = $this->assetSource->findSource($asset); + $content = file_get_contents($sourceFile); + $chain = new \Magento\Framework\View\Asset\PreProcessor\Chain( + $asset, + $content, + 'less' + ); + $this->fileGenerator->generateLessFileTree($chain); + $this->logger->logMessage("Done"); + } + return; + } + + /** + * Emulate application area and various services that are necessary for populating files + * + * @param string $areaCode + * @return void + */ + private function emulateApplicationArea($areaCode) + { + $objectManager = $this->omFactory->create( + [\Magento\Framework\App\State::PARAM_MODE => \Magento\Framework\App\State::MODE_DEFAULT] + ); + /** @var \Magento\Framework\App\State $appState */ + $appState = $objectManager->get('Magento\Framework\App\State'); + $appState->setAreaCode($areaCode); + /** @var \Magento\Framework\App\ObjectManager\ConfigLoader $configLoader */ + $configLoader = $objectManager->get('Magento\Framework\App\ObjectManager\ConfigLoader'); + $objectManager->configure($configLoader->load($areaCode)); + $this->assetRepo = $objectManager->get('Magento\Framework\View\Asset\Repository'); + $this->assetPublisher = $objectManager->get('Magento\Framework\App\View\Asset\Publisher'); + } + +} diff --git a/dev/tools/Magento/Tools/Webdev/less.php b/dev/tools/Magento/Tools/Webdev/less.php new file mode 100644 index 0000000000000000000000000000000000000000..9e6ff961785cd21017bdb92fbd9bdab6e2d065bb --- /dev/null +++ b/dev/tools/Magento/Tools/Webdev/less.php @@ -0,0 +1,106 @@ +<?php +/** + * A script for static view files partial pre-processing (when application in developer mode) and publication + * + * This tool is quite similar to deploy one (dev/tools/Magento/Tools/View/deploy.php), except this one perform only + * less files pre-processing and publishing within particular area, locale, theme and files. + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Framework\App\State; +use Magento\Tools\View\Deployer\Log; + +require __DIR__ . '/../../../bootstrap.php'; + + +try { + $opt = new Zend_Console_Getopt( + [ + 'locale=s' => 'locale, default: en_US', + 'area=s' => 'area, one of (frontend|adminhtml|doc), default: frontend', + 'theme=s' => 'theme in format Vendor/theme, default: Magento/blank', + 'files=s' => 'files to pre-process (accept more than one file type as comma-separate values), default: css/styles-m', + 'setup' => 'perform setup actions', + 'help|h' => 'show help', + 'verbose|v' => 'provide extra output', + ] + ); + + $opt->parse(); + + // Parse and validate all options + + if ($opt->getOption('help')) { + echo $opt->getUsageMessage(); + exit(0); + } + + if ($opt->getOption('setup')) { + echo "Setting up... \n"; + + if (!file_exists(BP . '/Gruntfile.js')) { + copy(BP . '/dev/tools/Magento/Tools/Webdev/Gruntfile.js.example', BP . '/Gruntfile.js'); + echo file_exists(BP . '/Gruntfile.js') ? "Created " . BP . "/Gruntfile.js \n" : ''; + } + + if (!file_exists(BP . '/package.json')) { + copy(BP . '/dev/tools/Magento/Tools/Webdev/package.json.example', BP . '/package.json'); + echo file_exists(BP . '/package.json') ? "Created " . BP . "/package.json \n" : ''; + } + + $cmdSeparator = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? ' & ' : ' && '; + + $cmd = 'cd ' . BP . $cmdSeparator . 'npm install' . $cmdSeparator . 'grunt --no-color refresh'; + + passthru($cmd); + exit(0); + } + + $locale = $opt->getOption('locale') ?: 'en_US'; + + if (!preg_match('/^[a-z]{2}_[A-Z]{2}$/', $locale)) { + throw new \Zend_Console_Getopt_Exception('Invalid locale format'); + } + + $area = $opt->getOption('area') ?: 'frontend'; + $theme = $opt->getOption('theme') ?: 'Magento/blank'; + + if (isset($options['theme'])) { + $theme = $options['theme']; + } + + $files = explode(',', $opt->getOption('files') ?: 'css/styles-m'); + + + if ($opt->getOption('verbose')) { + $verbosity = Log::ERROR | Log::DEBUG; + } else { + $verbosity = Log::ERROR; + } + + // Run actual application logic: + + $bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER); + + // Initialize object manager + $magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); + $magentoObjectManagerFactory->create($_SERVER); + + $objectManager = $magentoObjectManagerFactory->create([State::PARAM_MODE => State::MODE_DEFAULT]); + + $logger = new Log($verbosity); + + /** @var \Magento\Tools\Webdev\Collector $collector */ + $collector = $objectManager->create('Magento\Tools\Webdev\Collector', ['logger' => $logger]); + + $collector->tree($magentoObjectManagerFactory, $locale, $area, $theme, $files); + +} catch (Zend_Console_Getopt_Exception $e) { + echo $e->getUsageMessage(); + echo 'Please, use quotes(") for wrapping strings.' . "\n"; + exit(1); +} catch (Exception $e) { + fwrite(STDERR, "Execution failed with exception: " . $e->getMessage()); + throw($e); +} diff --git a/lib/internal/Magento/Framework/Less/FileGenerator.php b/lib/internal/Magento/Framework/Less/FileGenerator.php index a72f6dea6cb70ce2e6ce8a4aeda486af14d9a6de..41f4829c49aa224373588016a5b17a2808a0c7f1 100644 --- a/lib/internal/Magento/Framework/Less/FileGenerator.php +++ b/lib/internal/Magento/Framework/Less/FileGenerator.php @@ -27,6 +27,11 @@ class FileGenerator */ const LOCK_FILE = 'less.lock'; + /** + * Styling mode + */ + const STYLING_MODE = true; + /** * @var string */ @@ -37,11 +42,21 @@ class FileGenerator */ protected $tmpDirectory; + /** + * @var \Magento\Framework\View\Filesystem + */ + protected $_filesystem; + /** * @var \Magento\Framework\View\Asset\Repository */ private $assetRepo; + /** + * @var \Magento\Framework\View\Asset\Source + */ + private $assetSource; + /** * @var \Magento\Framework\Less\PreProcessor\Instruction\MagentoImport */ @@ -57,16 +72,21 @@ class FileGenerator * @param \Magento\Framework\View\Asset\Repository $assetRepo * @param \Magento\Framework\Less\PreProcessor\Instruction\MagentoImport $magentoImportProcessor * @param \Magento\Framework\Less\PreProcessor\Instruction\Import $importProcessor + * @param \Magento\Framework\View\Asset\Source $assetSource */ public function __construct( \Magento\Framework\Filesystem $filesystem, \Magento\Framework\View\Asset\Repository $assetRepo, \Magento\Framework\Less\PreProcessor\Instruction\MagentoImport $magentoImportProcessor, - \Magento\Framework\Less\PreProcessor\Instruction\Import $importProcessor + \Magento\Framework\Less\PreProcessor\Instruction\Import $importProcessor, + \Magento\Framework\View\Asset\Source $assetSource ) { $this->tmpDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->pubDirectory = $filesystem->getDirectoryWrite(DirectoryList::PUB); $this->lessDirectory = DirectoryList::TMP_MATERIALIZATION_DIR . '/' . self::TMP_LESS_DIR; $this->assetRepo = $assetRepo; + $this->assetSource = $assetSource; + $this->magentoImportProcessor = $magentoImportProcessor; $this->importProcessor = $importProcessor; } @@ -98,6 +118,10 @@ class FileGenerator $this->generateRelatedFiles(); $lessRelativePath = preg_replace('#\.css$#', '.less', $chain->getAsset()->getPath()); $tmpFilePath = $this->createFile($lessRelativePath, $chain->getContent()); + + if (self::STYLING_MODE) { + $this->createFileMain($lessRelativePath, $chain->getContent()); + } $this->tmpDirectory->delete($lockFilePath); return $tmpFilePath; } @@ -148,7 +172,12 @@ class FileGenerator protected function generateRelatedFile($relatedFileId, LocalInterface $asset) { $relatedAsset = $this->assetRepo->createRelated($relatedFileId, $asset); + $relatedAsset->getFilePath(); + $this->createFile($relatedAsset->getPath(), $relatedAsset->getContent()); + if (self::STYLING_MODE) { + $this->createSymlink($relatedAsset); + } } /** @@ -167,4 +196,38 @@ class FileGenerator } return $this->tmpDirectory->getAbsolutePath($filePath); } + + /** + * @param $relativePath + * @param $contents + */ + protected function createFileMain($relativePath, $contents) + { + $filePath = '/static/' . $relativePath; + $contents .= '@urls-resolved: true;' . PHP_EOL . PHP_EOL; + $this->pubDirectory->writeFile($filePath, $contents); + return; + } + + /** + * @param LocalInterface $relatedAsset + */ + protected function createSymLink(LocalInterface $relatedAsset) + { + $linkBase = '/static/'; + $linkDir = $linkBase . str_replace(pathinfo($relatedAsset->getPath())['basename'], '', $relatedAsset->getPath()); + if (strpos($relatedAsset->getSourceFile(),'view_preprocessed') !== false) { + $linkTarget = $this->assetSource->findSource($relatedAsset); + } else { + $linkTarget = $relatedAsset->getSourceFile(); + } + $link = $this->pubDirectory->getAbsolutePath($linkBase . $relatedAsset->getPath()); + if (!$this->pubDirectory->isExist($linkDir)) { + $this->pubDirectory->create($linkDir); + } + if (!file_exists($link)) { + symlink($linkTarget, $link); + } + return; + } } diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php index f2004fabcb19101aca372000a08bdd1afd864b2e..d9f041f307c75473691d6d29d5dbaacfecaa4a95 100644 --- a/lib/internal/Magento/Framework/View/Asset/Source.php +++ b/lib/internal/Magento/Framework/View/Asset/Source.php @@ -163,6 +163,15 @@ class Source return $result; } + /** + * @param LocalInterface $asset + * @return bool|string + */ + public function findSource(LocalInterface $asset) + { + return $this->findSourceFile($asset); + } + /** * Infer a content type from the specified path *