3 final class CelerityResourceTransformer
extends Phobject
{
8 private $translateURICallback;
10 private $postprocessorKey;
13 public function setPostprocessorKey($postprocessor_key) {
14 $this->postprocessorKey
= $postprocessor_key;
18 public function getPostprocessorKey() {
19 return $this->postprocessorKey
;
22 public function setTranslateURICallback($translate_uricallback) {
23 $this->translateURICallback
= $translate_uricallback;
27 public function setMinify($minify) {
28 $this->minify
= $minify;
32 public function setCelerityMap(CelerityResourceMap
$celerity_map) {
33 $this->celerityMap
= $celerity_map;
37 public function setRawURIMap(array $raw_urimap) {
38 $this->rawURIMap
= $raw_urimap;
42 public function getRawURIMap() {
43 return $this->rawURIMap
;
47 * @phutil-external-symbol function jsShrink
49 public function transformResource($path, $data) {
50 $type = self
::getResourceType($path);
54 $data = $this->replaceCSSPrintRules($path, $data);
55 $data = $this->replaceCSSVariables($path, $data);
56 $data = preg_replace_callback(
57 '@url\s*\((\s*[\'"]?.*?)\)@s',
59 $this->translateURICallback
,
60 array($this, 'translateResourceURI')),
69 // Some resources won't survive minification (like d3.min.js), and are
70 // marked so as not to be minified.
71 if (strpos($data, '@'.'do-not-minify') !== false) {
78 $data = preg_replace('@/\*.*?\*/@s', '', $data);
79 // Remove whitespace around symbols.
80 $data = preg_replace('@\s*([{}:;,])\s*@', '\1', $data);
81 // Remove unnecessary semicolons.
82 $data = preg_replace('@;}@', '}', $data);
83 // Replace #rrggbb with #rgb when possible.
85 '@#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3@i',
92 // If `jsxmin` is available, use it. jsxmin is the Javelin minifier and
93 // produces the smallest output, but is complicated to build.
94 if (Filesystem
::binaryExists('jsxmin')) {
95 $future = new ExecFuture('jsxmin __DEV__:0');
96 $future->write($data);
97 list($err, $result) = $future->resolve();
104 // If `jsxmin` is not available, use `JsShrink`, which doesn't compress
105 // quite as well but is always available.
106 $root = dirname(phutil_get_library_root('phabricator'));
107 require_once $root.'/externals/JsShrink/jsShrink.php';
108 $data = jsShrink($data);
116 public static function getResourceType($path) {
117 return last(explode('.', $path));
120 public function translateResourceURI(array $matches) {
121 $uri = trim($matches[1], "'\" \r\t\n");
124 // If the resource URI has a query string or anchor, strip it off before
125 // we go looking for the resource. We'll stitch it back on later. This
126 // primarily affects FontAwesome.
128 $parts = preg_split('/(?=[?#])/', $uri, 2);
129 if (count($parts) == 2) {
134 $alternatives = array_unique(
140 foreach ($alternatives as $alternative) {
141 if ($this->rawURIMap
!== null) {
142 if (isset($this->rawURIMap
[$alternative])) {
143 $uri = $this->rawURIMap
[$alternative];
148 if ($this->celerityMap
) {
149 $resource_uri = $this->celerityMap
->getURIForName($alternative);
151 // Check if we can use a data URI for this resource. If not, just
152 // use a normal Celerity URI.
153 $data_uri = $this->generateDataURI($alternative);
157 $uri = $resource_uri;
164 return 'url('.$uri.$tail.')';
167 private function replaceCSSVariables($path, $data) {
168 $this->currentPath
= $path;
169 return preg_replace_callback(
171 array($this, 'replaceCSSVariable'),
175 private function replaceCSSPrintRules($path, $data) {
176 $this->currentPath
= $path;
177 return preg_replace_callback(
178 '/!print\s+(.+?{.+?})/s',
179 array($this, 'replaceCSSPrintRule'),
183 public function getCSSVariableMap() {
184 $postprocessor_key = $this->getPostprocessorKey();
185 $postprocessor = CelerityPostprocessor
::getPostprocessor(
188 if (!$postprocessor) {
189 $postprocessor = CelerityPostprocessor
::getPostprocessor(
190 CelerityDefaultPostprocessor
::POSTPROCESSOR_KEY
);
193 return $postprocessor->getVariables();
196 public function replaceCSSVariable($matches) {
197 if (!$this->variableMap
) {
198 $this->variableMap
= $this->getCSSVariableMap();
201 $var_name = $matches[1];
202 if (empty($this->variableMap
[$var_name])) {
203 $path = $this->currentPath
;
206 "CSS file '%s' has unknown variable '%s'.",
211 return $this->variableMap
[$var_name];
214 public function replaceCSSPrintRule($matches) {
218 $rules[] = '.printable '.$rule;
219 $rules[] = "@media print {\n ".str_replace("\n", "\n ", $rule)."\n}\n";
221 return implode("\n\n", $rules);
226 * Attempt to generate a data URI for a resource. We'll generate a data URI
227 * if the resource is a valid resource of an appropriate type, and is
228 * small enough. Otherwise, this method will return `null` and we'll end up
229 * using a normal URI instead.
231 * @param string Resource name to attempt to generate a data URI for.
232 * @return string|null Data URI, or null if we declined to generate one.
234 private function generateDataURI($resource_name) {
235 $ext = last(explode('.', $resource_name));
244 $type = 'image/jpeg';
250 // In IE8, 32KB is the maximum supported URI length.
251 $maximum_data_size = (1024 * 32);
253 $data = $this->celerityMap
->getResourceDataForName($resource_name);
254 if (strlen($data) >= $maximum_data_size) {
255 // If the data is already too large on its own, just bail before
260 $uri = 'data:'.$type.';base64,'.base64_encode($data);
261 if (strlen($uri) >= $maximum_data_size) {