Bundle.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Deploy\Service;
  7. use Magento\Deploy\Config\BundleConfig;
  8. use Magento\Deploy\Package\BundleInterface;
  9. use Magento\Deploy\Package\BundleInterfaceFactory;
  10. use Magento\Framework\App\Filesystem\DirectoryList;
  11. use Magento\Framework\Filesystem;
  12. use Magento\Framework\App\Utility\Files;
  13. use Magento\Framework\View\Asset\Repository;
  14. use Magento\Framework\View\Asset\RepositoryMap;
  15. /**
  16. * Deploy bundled static files service
  17. *
  18. * Read all static files from deployed packages and generate bundles. Bundle Factory can be configured to use
  19. * bundle format different from RequireJS used out of the box
  20. */
  21. class Bundle
  22. {
  23. /**
  24. * Path to package subdirectory where bundle files are located
  25. */
  26. const BUNDLE_JS_DIR = 'js/bundle';
  27. /**
  28. * Matched file extension name for JavaScript files
  29. */
  30. const ASSET_TYPE_JS = 'js';
  31. /**
  32. * Matched file extension name for template files
  33. */
  34. const ASSET_TYPE_HTML = 'html';
  35. /**
  36. * Public static directory writable interface
  37. *
  38. * @var Filesystem\Directory\WriteInterface
  39. */
  40. private $pubStaticDir;
  41. /**
  42. * Factory for Bundle object
  43. *
  44. * @see BundleInterface
  45. * @var BundleInterfaceFactory
  46. */
  47. private $bundleFactory;
  48. /**
  49. * Utility class for collecting files by specific pattern and location
  50. *
  51. * @var Files
  52. */
  53. private $utilityFiles;
  54. /**
  55. * Cached data about files which must be excluded from bundling
  56. *
  57. * @var array
  58. */
  59. private $excludedCache = [];
  60. /**
  61. * List of supported types of static files
  62. *
  63. * @var array
  64. * */
  65. public static $availableTypes = [
  66. self::ASSET_TYPE_JS,
  67. self::ASSET_TYPE_HTML
  68. ];
  69. /**
  70. * Bundle constructor
  71. *
  72. * @param Filesystem $filesystem
  73. * @param BundleInterfaceFactory $bundleFactory
  74. * @param BundleConfig $bundleConfig
  75. * @param Files $files
  76. */
  77. public function __construct(
  78. Filesystem $filesystem,
  79. BundleInterfaceFactory $bundleFactory,
  80. BundleConfig $bundleConfig,
  81. Files $files
  82. ) {
  83. $this->pubStaticDir = $filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW);
  84. $this->bundleFactory = $bundleFactory;
  85. $this->bundleConfig = $bundleConfig;
  86. $this->utilityFiles = $files;
  87. }
  88. /**
  89. * Deploy bundles for the given area, theme and locale
  90. *
  91. * @param string $area
  92. * @param string $theme
  93. * @param string $locale
  94. * @return void
  95. */
  96. public function deploy($area, $theme, $locale)
  97. {
  98. $bundle = $this->bundleFactory->create([
  99. 'area' => $area,
  100. 'theme' => $theme,
  101. 'locale' => $locale
  102. ]);
  103. // delete all previously created bundle files
  104. $bundle->clear();
  105. $files = [];
  106. $mapFilePath = $area . '/' . $theme . '/' . $locale . '/' . RepositoryMap::RESULT_MAP_NAME;
  107. if ($this->pubStaticDir->isFile($mapFilePath)) {
  108. // map file is available in compact mode, so no need to scan filesystem one more time
  109. $resultMap = $this->pubStaticDir->readFile($mapFilePath);
  110. if ($resultMap) {
  111. $files = json_decode($resultMap, true);
  112. }
  113. } else {
  114. $packageDir = $this->pubStaticDir->getAbsolutePath($area . '/' . $theme . '/' . $locale);
  115. $files = $this->utilityFiles->getFiles([$packageDir], '*.*');
  116. }
  117. foreach ($files as $filePath => $sourcePath) {
  118. if (is_array($sourcePath)) {
  119. $filePath = str_replace(Repository::FILE_ID_SEPARATOR, '/', $filePath);
  120. $sourcePath = $sourcePath['area']
  121. . '/' . $sourcePath['theme']
  122. . '/' . $sourcePath['locale']
  123. . '/' . $filePath;
  124. } else {
  125. $sourcePath = str_replace('\\', '/', $sourcePath);
  126. $sourcePath = $this->pubStaticDir->getRelativePath($sourcePath);
  127. $filePath = substr($sourcePath, strlen($area . '/' . $theme . '/' . $locale) + 1);
  128. }
  129. $contentType = pathinfo($filePath, PATHINFO_EXTENSION);
  130. if (!in_array($contentType, self::$availableTypes)) {
  131. continue;
  132. }
  133. if ($this->hasMinVersion($filePath) || $this->isExcluded($filePath, $area, $theme)) {
  134. continue;
  135. }
  136. $bundle->addFile($filePath, $sourcePath, $contentType);
  137. }
  138. $bundle->flush();
  139. }
  140. /**
  141. * Check if file is minified version or there is a minified version of the file
  142. *
  143. * @param string $filePath
  144. * @return bool
  145. */
  146. private function hasMinVersion($filePath)
  147. {
  148. if (in_array($filePath, $this->excludedCache)) {
  149. return true;
  150. }
  151. $info = pathinfo($filePath);
  152. if (strpos($filePath, '.min.') !== false) {
  153. $this->excludedCache[] = str_replace(".min.{$info['extension']}", ".{$info['extension']}", $filePath);
  154. } else {
  155. $pathToMinVersion = $info['dirname'] . '/' . $info['filename'] . '.min.' . $info['extension'];
  156. if ($this->pubStaticDir->isExist($pathToMinVersion)) {
  157. $this->excludedCache[] = $filePath;
  158. return true;
  159. }
  160. }
  161. return false;
  162. }
  163. /**
  164. * Check if file is in exclude list
  165. *
  166. * @param string $filePath
  167. * @param string $area
  168. * @param string $theme
  169. * @return bool
  170. */
  171. private function isExcluded($filePath, $area, $theme)
  172. {
  173. $excludedFiles = $this->bundleConfig->getExcludedFiles($area, $theme);
  174. foreach ($excludedFiles as $excludedFileId) {
  175. $excludedFilePath = $this->prepareExcludePath($excludedFileId);
  176. if ($excludedFilePath === $filePath) {
  177. return true;
  178. }
  179. }
  180. $excludedDirs = $this->bundleConfig->getExcludedDirectories($area, $theme);
  181. foreach ($excludedDirs as $directoryId) {
  182. $directoryPath = $this->prepareExcludePath($directoryId);
  183. if (strpos($filePath, $directoryPath) === 0) {
  184. return true;
  185. }
  186. }
  187. return false;
  188. }
  189. /**
  190. * Get excluded path
  191. *
  192. * @param string $path
  193. * @return array|bool
  194. */
  195. private function prepareExcludePath($path)
  196. {
  197. if (strpos($path, Repository::FILE_ID_SEPARATOR) !== false) {
  198. list($excludedModule, $excludedPath) = explode(Repository::FILE_ID_SEPARATOR, $path);
  199. if ($excludedModule == 'Lib') {
  200. return $excludedPath;
  201. } else {
  202. return $excludedModule . '/' . $excludedPath;
  203. }
  204. }
  205. return $path;
  206. }
  207. }