-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Improve template minifier #33016
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
blmage
wants to merge
13
commits into
magento:2.4-develop
Choose a base branch
from
blmage:template-minifier
base: 2.4-develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Improve template minifier #33016
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8d18437
Fix PHP code in template minifier test
blmage 7102e87
Improve detection of heredocs in minified templates
blmage e3a6ef7
Clean up PHP code using nikic/php-parser before minifying template HTML
blmage e0e5791
Remove redundant cleaning of PHP comments
blmage 59dfff2
Remove remaining single-line comments in <script> tags only
blmage 06f667b
Improve detection of commented PHP tags
blmage 5fbf10b
Do not break JS RegExps that look like comments
blmage 9062eb7
Handle edge cases when parsing templates
blmage 1bc9e69
Preserve all spaces inside <textarea> and <pre> tags
blmage 59be884
Comment on each step of the minification process
blmage 1ac88c3
Update template minifier tests
blmage 4e50eff
Remove unused variable
blmage 33d578c
Merge branch '2.4-develop' into template-minifier
Den4ik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/NodeVisitor.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Framework\View\Template\Html\Minifier\Php; | ||
|
||
use PhpParser\Comment; | ||
use PhpParser\Node; | ||
use PhpParser\NodeTraverser; | ||
use PhpParser\NodeVisitorAbstract; | ||
|
||
class NodeVisitor extends NodeVisitorAbstract | ||
{ | ||
/** | ||
* @var Node[] | ||
*/ | ||
private $stack = []; | ||
|
||
/** | ||
* @var ?Node | ||
*/ | ||
private $previous; | ||
|
||
public function beforeTraverse(array $nodes) | ||
{ | ||
$this->stack = []; | ||
$this->previous = null; | ||
} | ||
|
||
public function enterNode(Node $node) | ||
{ | ||
if ($node instanceof Node\Stmt) { | ||
// Mark isolated echo statements, to replace them later with short echo tags. | ||
$parent = empty($this->stack) ?: $this->stack[count($this->stack) - 1]; | ||
|
||
if ($node instanceof Node\Stmt\InlineHTML) { | ||
$node->setAttribute('parent', $parent); | ||
|
||
if ( | ||
($this->previous instanceof Node\Stmt\Echo_) | ||
&& ($previousHtmlStatement = $this->previous->getAttribute('previousHtmlStatement')) | ||
&& ($previousHtmlStatement->getAttribute('parent') === $parent) | ||
) { | ||
$this->previous->setAttribute('isSingleEchoStatement', true); | ||
$previousHtmlStatement->setAttribute('hasSingleEchoStatementNext', true); | ||
} | ||
} elseif ( | ||
($this->previous instanceof Node\Stmt\InlineHTML) | ||
&& ($this->previous->getAttribute('parent') === $parent) | ||
) { | ||
$node->setAttribute('previousHtmlStatement', $this->previous); | ||
} | ||
} | ||
|
||
$this->stack[] = $node; | ||
} | ||
|
||
public function leaveNode(Node $node) | ||
{ | ||
$this->previous = $node; | ||
|
||
array_pop($this->stack); | ||
|
||
// Remove nodes that only contain non-doc comments. | ||
if ($node instanceof Node\Stmt\Nop) { | ||
$comments = $node->getComments(); | ||
$isSuperfluousNode = true; | ||
|
||
foreach ($comments as $key => $comment) { | ||
if ($comment instanceof Comment\Doc) { | ||
$isSuperfluousNode = false; | ||
break; | ||
} | ||
} | ||
|
||
if ($isSuperfluousNode) { | ||
return NodeTraverser::REMOVE_NODE; | ||
} | ||
} | ||
} | ||
} |
132 changes: 132 additions & 0 deletions
132
lib/internal/Magento/Framework/View/Template/Html/Minifier/Php/PrettyPrinter.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Framework\View\Template\Html\Minifier\Php; | ||
|
||
use PhpParser\Comment; | ||
use PhpParser\Node; | ||
use PhpParser\PrettyPrinter\Standard; | ||
|
||
class PrettyPrinter extends Standard | ||
{ | ||
/** | ||
* @var string[] | ||
*/ | ||
private $delayedHeredocs = []; | ||
|
||
protected function resetState() | ||
{ | ||
$this->delayedHeredocs = []; | ||
$this->indentLevel = 0; | ||
$this->nl = ''; | ||
$this->origTokens = null; | ||
} | ||
|
||
protected function setIndentLevel(int $level) | ||
{ | ||
// Ignore indentation. | ||
} | ||
|
||
protected function indent() | ||
{ | ||
// Ignore indentation. | ||
} | ||
|
||
protected function outdent() | ||
{ | ||
// Ignore indentation. | ||
} | ||
|
||
/** | ||
* @param string $heredoc | ||
* @return string | ||
*/ | ||
private function getHeredocPlaceholder(string $heredoc): string | ||
{ | ||
$index = count($this->delayedHeredocs) + 1; | ||
|
||
$this->delayedHeredocs[$index] = $this->handleMagicTokens($heredoc); | ||
|
||
return '__MINIFIED_HEREDOC__' . $index; | ||
} | ||
|
||
protected function pScalar_String(Node\Scalar\String_ $node): string | ||
{ | ||
$result = parent::pScalar_String($node); | ||
|
||
return $node->getAttribute('kind') !== Node\Scalar\String_::KIND_HEREDOC | ||
? $result | ||
: $this->getHeredocPlaceholder($result); | ||
} | ||
|
||
protected function pScalar_Encapsed(Node\Scalar\Encapsed $node): string | ||
{ | ||
$result = parent::pScalar_Encapsed($node); | ||
|
||
return $node->getAttribute('kind') !== Node\Scalar\String_::KIND_HEREDOC | ||
? $result | ||
: $this->getHeredocPlaceholder($result); | ||
} | ||
|
||
protected function pCommaSeparated(array $nodes): string | ||
{ | ||
return $this->pImplode($nodes, ','); | ||
} | ||
|
||
protected function pComments(array $comments): string | ||
{ | ||
// Only preserve doc comments. | ||
foreach ($comments as $key => $comment) { | ||
if (!$comment instanceof Comment\Doc) { | ||
unset($comments[$key]); | ||
} | ||
} | ||
|
||
$formattedComments = []; | ||
|
||
foreach ($comments as $comment) { | ||
$formattedComments[] = str_replace("\n", '', $comment->getReformattedText()); | ||
} | ||
|
||
// Add a space between doc comments to avoid occurrences of "//" that could later be misinterpreted. | ||
return implode(' ', $formattedComments) . ' '; | ||
} | ||
|
||
protected function pExpr_Array(Node\Expr\Array_ $node): string | ||
{ | ||
$node->setAttribute('kind', Node\Expr\Array_::KIND_SHORT); | ||
|
||
return parent::pExpr_Array($node); | ||
} | ||
|
||
protected function pStmt_Echo(Node\Stmt\Echo_ $node): string | ||
{ | ||
$output = $this->pCommaSeparated($node->exprs); | ||
|
||
return $node->getAttribute('isSingleEchoStatement') | ||
? $output . ' ' | ||
: 'echo ' . $output . ';'; | ||
} | ||
|
||
protected function pStmt_InlineHTML(Node\Stmt\InlineHTML $node): string | ||
{ | ||
$newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : ''; | ||
|
||
return '?>' | ||
. $newline | ||
. $node->value | ||
. ($node->getAttribute('hasSingleEchoStatementNext') ? '<?= ' : '<?php '); | ||
} | ||
|
||
/** | ||
* @return string[] | ||
*/ | ||
public function getDelayedHeredocs(): array | ||
{ | ||
return $this->delayedHeredocs; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case doesn't it make more sense to just log a warning and return the unminified code instead of guessing that you could do a better job than the PHP parser library at minifying some mangled script using regex?