<?php
/**
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2021 The s9e authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use RuntimeException;
use s9e\TextFormatter\Configurator\JavaScript\OnlineMinifier;
class ClosureCompilerService extends OnlineMinifier
{
/**
* @var string Closure Compiler's compilation level
*/
public $compilationLevel = 'ADVANCED_OPTIMIZATIONS';
/**
* @var bool Whether to exclude Closure Compiler's default externs
*/
public $excludeDefaultExterns = true;
/**
* @var string Externs used for compilation
*/
public $externs;
/**
* @var string Closure Compiler Service's URL
*/
public $url = 'https://closure-compiler.appspot.com/compile';
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->externs = file_get_contents(__DIR__ . '/../externs.service.js');
}
/**
* {@inheritdoc}
*/
public function getCacheDifferentiator()
{
$key = [$this->compilationLevel, $this->excludeDefaultExterns];
if ($this->excludeDefaultExterns)
{
$key[] = $this->externs;
}
return $key;
}
/**
* Compile given JavaScript source via the Closure Compiler Service
*
* @param string $src JavaScript source
* @return string Compiled source
*/
public function minify($src)
{
$body = $this->generateRequestBody($src);
$response = $this->query($body);
if ($response === false)
{
throw new RuntimeException('Could not contact the Closure Compiler service');
}
return $this->decodeResponse($response);
}
/**
* Decode the response returned by the Closure Compiler service
*
* @param string $response Response body
* @return string Minified code
*/
protected function decodeResponse($response)
{
$response = json_decode($response, true);
if (is_null($response))
{
throw new RuntimeException('Closure Compiler service returned invalid JSON: ' . json_last_error_msg());
}
if (isset($response['serverErrors'][0]))
{
$error = $response['serverErrors'][0];
throw new RuntimeException('Server error ' . $error['code'] . ': ' . $error['error']);
}
if (isset($response['errors'][0]))
{
$error = $response['errors'][0];
throw new RuntimeException('Compilation error: ' . $error['error']);
}
return $response['compiledCode'];
}
/**
* Generate the request body for given code
*
* @param string $src JavaScript source
* @return string Compiled source
*/
protected function generateRequestBody($src)
{
$params = [
'compilation_level' => $this->compilationLevel,
'js_code' => $src,
'output_format' => 'json',
'output_info' => 'compiled_code'
];
// Add our custom externs if default externs are disabled
if ($this->excludeDefaultExterns && $this->compilationLevel === 'ADVANCED_OPTIMIZATIONS')
{
$params['exclude_default_externs'] = 'true';
$params['js_externs'] = $this->externs;
}
// Add dupe variables by hand
$body = http_build_query($params) . '&output_info=errors';
return $body;
}
/**
* Query the Closure Compiler service
*
* @param string $body Request body
* @return string|false Response body, or FALSE
*/
protected function query($body)
{
return $this->httpClient->post(
$this->url,
['headers' => ['Content-Type: application/x-www-form-urlencoded']],
$body
);
}
}