AssetConverter.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\web;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\Exception;
  11. /**
  12. * AssetConverter supports conversion of several popular script formats into JS or CSS scripts.
  13. *
  14. * It is used by [[AssetManager]] to convert files after they have been published.
  15. *
  16. * @author Qiang Xue <qiang.xue@gmail.com>
  17. * @since 2.0
  18. */
  19. class AssetConverter extends Component implements AssetConverterInterface
  20. {
  21. /**
  22. * @var array the commands that are used to perform the asset conversion.
  23. * The keys are the asset file extension names, and the values are the corresponding
  24. * target script types (either "css" or "js") and the commands used for the conversion.
  25. *
  26. * You may also use a [path alias](guide:concept-aliases) to specify the location of the command:
  27. *
  28. * ```php
  29. * [
  30. * 'styl' => ['css', '@app/node_modules/bin/stylus < {from} > {to}'],
  31. * ]
  32. * ```
  33. */
  34. public $commands = [
  35. 'less' => ['css', 'lessc {from} {to} --no-color --source-map'],
  36. 'scss' => ['css', 'sass {from} {to} --sourcemap'],
  37. 'sass' => ['css', 'sass {from} {to} --sourcemap'],
  38. 'styl' => ['css', 'stylus < {from} > {to}'],
  39. 'coffee' => ['js', 'coffee -p {from} > {to}'],
  40. 'ts' => ['js', 'tsc --out {to} {from}'],
  41. ];
  42. /**
  43. * @var bool whether the source asset file should be converted even if its result already exists.
  44. * You may want to set this to be `true` during the development stage to make sure the converted
  45. * assets are always up-to-date. Do not set this to true on production servers as it will
  46. * significantly degrade the performance.
  47. */
  48. public $forceConvert = false;
  49. /**
  50. * Converts a given asset file into a CSS or JS file.
  51. * @param string $asset the asset file path, relative to $basePath
  52. * @param string $basePath the directory the $asset is relative to.
  53. * @return string the converted asset file path, relative to $basePath.
  54. */
  55. public function convert($asset, $basePath)
  56. {
  57. $pos = strrpos($asset, '.');
  58. if ($pos !== false) {
  59. $ext = substr($asset, $pos + 1);
  60. if (isset($this->commands[$ext])) {
  61. list($ext, $command) = $this->commands[$ext];
  62. $result = substr($asset, 0, $pos + 1) . $ext;
  63. if ($this->forceConvert || @filemtime("$basePath/$result") < @filemtime("$basePath/$asset")) {
  64. $this->runCommand($command, $basePath, $asset, $result);
  65. }
  66. return $result;
  67. }
  68. }
  69. return $asset;
  70. }
  71. /**
  72. * Runs a command to convert asset files.
  73. * @param string $command the command to run. If prefixed with an `@` it will be treated as a [path alias](guide:concept-aliases).
  74. * @param string $basePath asset base path and command working directory
  75. * @param string $asset the name of the asset file
  76. * @param string $result the name of the file to be generated by the converter command
  77. * @return bool true on success, false on failure. Failures will be logged.
  78. * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
  79. * In production mode the error will be logged.
  80. */
  81. protected function runCommand($command, $basePath, $asset, $result)
  82. {
  83. $command = Yii::getAlias($command);
  84. $command = strtr($command, [
  85. '{from}' => escapeshellarg("$basePath/$asset"),
  86. '{to}' => escapeshellarg("$basePath/$result"),
  87. ]);
  88. $descriptor = [
  89. 1 => ['pipe', 'w'],
  90. 2 => ['pipe', 'w'],
  91. ];
  92. $pipes = [];
  93. $proc = proc_open($command, $descriptor, $pipes, $basePath);
  94. $stdout = stream_get_contents($pipes[1]);
  95. $stderr = stream_get_contents($pipes[2]);
  96. foreach ($pipes as $pipe) {
  97. fclose($pipe);
  98. }
  99. $status = proc_close($proc);
  100. if ($status === 0) {
  101. Yii::debug("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
  102. } elseif (YII_DEBUG) {
  103. throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
  104. } else {
  105. Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
  106. }
  107. return $status === 0;
  108. }
  109. }