Last active
December 1, 2021 12:45
-
-
Save rhymeswithmogul/20d5cb31a4c90c3a21d9175b1564669d to your computer and use it in GitHub Desktop.
Generate statically-compressed assets for NGINX
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
#!/usr/bin/env pwsh | |
<#PSScriptInfo | |
.VERSION 1.0.0 | |
.GUID ca796623-622f-44ad-89f0-111c147eba79 | |
.AUTHOR Colin Cogle | |
.COPYRIGHT © 2021 Colin Cogle. Licensed under the Affero GPL, version 3 or later. | |
.TAGS nginx, gzip, Brotli, compression | |
.LICENSEURI https://www.gnu.org/licenses/agpl-3.0.en.html | |
.PROJECTURI https://gist.github.com/rhymeswithmogul/20d5cb31a4c90c3a21d9175b1564669d | |
#> | |
<# | |
.SYNOPSIS | |
Creates statically-compressed versions of files. | |
.DESCRIPTION | |
This script iterates through a directory of items and compresses some or all of | |
them with Gzip and Brotli. If the compressed file is larger (which may happen | |
with already-compressed things like archives, images, or videos), then the | |
compressed file is deleted. | |
Pre-compressed files can be served by Web servers such as NGINX, as opposed to | |
having the server compress files on demand. Live compression often uses faster | |
versions of these algorithms to save resources and time. For example, NGINX's | |
http_gzip_module defaults to `gzip -6`, which provides worse compression than | |
`gzip -9` but is much faster to compress; likewise, turning Brotli up to 11 | |
will provide the smallest file size, but is too slow to do on demand unless you | |
have a powerful CPU in your server. | |
.PARAMETER InputObject | |
Supply the path to your web server content. On most platforms, this might be | |
/var/www/html or C:\html. You can specify multiple paths. | |
.PARAMETER Force | |
By default, this will only operate on an included list of file extensions that | |
are likely to be compressable. Include this parameter, -Force, and it will try | |
compressing everything. Note that this may consume a lot of CPU time with | |
minimal payoff. | |
.PARAMETER KeepLargerFiles | |
If a compressed file is larger than its uncompressed version, the compressed | |
file is deleted. Specify this switch to keep them anyway. Using this switch | |
might be good for debugging, because it defeats the purpose of compressing your | |
files in the first place. | |
.PARAMETER PathToGzip | |
If you would like to use a different gzip implementation (i.e., pigz), specify | |
its path here. Otherwise, the system default will be used. | |
.PARAMETER PathToBrotli | |
If you would like to use a different Brotli implementation, specify its path | |
here. Otherwise, the system default will be used. | |
.EXAMPLE | |
PS /home/colin> /usr/local/bin/Compress-Assets.ps1 /var/www/html -Force | |
.NOTES | |
This script should be re-run whenever any of your content changes. Otherwise, | |
web site visitors will receive older versions of your files. | |
To undo this, simply delete all .gz and .br files in and below the folder. | |
.LINK | |
https://gist.github.com/rhymeswithmogul/20d5cb31a4c90c3a21d9175b1564669d | |
.LINK | |
https://nginx.org/en/docs/http/ngx_http_gzip_static_module.html | |
.LINK | |
https://github.com/google/ngx_brotli | |
#> | |
#Requires -Version 7 | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory, Position=0, ValueFromPipeline)] | |
[ValidateNotNullOrEmpty()] | |
[ValidateScript({Test-Path -PathType Container $_}, ErrorMessage='The folder specified does not exist.')] | |
[Alias('FolderName', 'Name', 'Path')] | |
[String[]] $InputObject = '/var/www/html', | |
[Alias('CompressAll', 'IncludeAllFiles')] | |
[Switch] $Force, | |
[Switch] $KeepLargerFiles, | |
[ValidateScript({Test-Path -PathType Leaf $_}, ErrorMessage='The specified Gzip app does not exist.')] | |
[String] $PathToGzip = '/usr/bin/gzip', | |
[ValidateScript({Test-Path -PathType Leaf $_}, ErrorMessage='The specified Brotli app does not exist.')] | |
[String] $PathToBrotli = '/usr/bin/brotli' | |
) | |
Set-StrictMode -Version Latest | |
If ($KeepLargerFiles) { | |
Write-Warning -Message '-KeepLargerFiles was specified. Compressed files will be kept even if they are larger!' | |
} | |
$Include = '*' | |
If (-Not $Force) { | |
$Include = @( | |
'*.asc', '*.gpg', '*.cer', '*.crt', '*.pem', | |
'*.css', '*.scss', | |
'*.htm', '*.html', | |
'*.jrd', '*.rdf', 'host-meta' | |
'*.js', '*.map', '*.json', | |
'*.rss', '*.atom', | |
'*.svg', '*.xpm', '*.bmp', '*.dib', | |
'*.txt', '*.md', | |
'*.xml', '*.xsl', '*.xslt', '*.xsd' | |
) | |
} | |
#region Get apps | |
Write-Verbose "Using $PathToGzip for gzip." | |
Write-Verbose "Using $PathToBrotli for Brotli." | |
#endregion | |
#region Get files | |
Write-Progress -Activity 'Compressing files' -CurrentOperation 'Indexing the folder' | |
$files = Get-ChildItem -Path:$InputObject -Recurse -File -Include:$Include -Exclude:@('*.gz','*.br','.gitignore') -Force -ErrorAction Stop ` | |
| Where-Object {$_.FullName -NotMatch '.git'} | |
Write-Verbose "Found $($files.Count) files to try compressing." | |
#endregion | |
$done = 0 | |
$files | ForEach-Object { | |
Write-Progress -Activity 'Compressing files' -PercentComplete (100 * $done/$($files.Count)) -CurrentOperation "Compressing and testing $($_.Name)" | |
Write-Verbose "Working on $($_.FullName)" | |
#region Gzip | |
Start-Process -FilePath $PathToGzip -ArgumentList @('--best', '--keep', '--force', '--name', $_.FullName) -Wait | |
$CompressedGZFile = (Get-Item "$($_.FullName).gz") | |
If ($_.Size -gt $CompressedGZFile.Size) { | |
Write-Debug "$($_.FullName): $($_.Size) bytes. Gzipped: $($CompressedGZFile.Size) bytes ($([Math]::floor(100 * (1-($CompressedGZFile.Size/$_.Size))))% smaller)." | |
Write-Verbose "Compressed $_ with Gzip." | |
} | |
Else { | |
Write-Debug "$($_.FullName): $($_.Size) bytes. Gzipped: $($CompressedGZFile.Size) bytes ($([Math]::floor(100 * (1-($_.Size/$CompressedGZFile.Size))))% larger)." | |
If (-Not $KeepLargerFiles) { | |
Write-Verbose "Skipping Gzip compression for $_" | |
Remove-Item -Path $CompressedGZFile -Force | |
} | |
} | |
#endregion Gzip | |
#region Brotli | |
Start-Process -FilePath $PathToBrotli -ArgumentList @('--best', '--keep', '--force', "--output=`"$($_.FullName).br`"", $_.FullName) -Wait | |
$CompressedBrFile = (Get-Item "$($_.FullName).br") | |
If ($_.Size -gt $CompressedBrFile.Size) { | |
Write-Debug "$($_.FullName): $($_.Size) bytes. Brotli-compressed: $($CompressedBrFile.Size) bytes ($([Math]::floor(100 * (1-($CompressedBrFile.Size/$_.Size))))% smaller)." | |
Write-Verbose "Compressed $_ with Brotli." | |
} | |
Else { | |
Write-Debug "$($_.FullName): $($_.Size) bytes. Brotli-compressed: $($CompressedBrFile.Size) bytes ($([Math]::floor(100 * (1-($_.Size/$CompressedBrFile.Size))))% larger)." | |
If (-Not $KeepLargerFiles) { | |
Write-Verbose "Skipping Brotli compression for $_" | |
Remove-Item -Path $CompressedBrFile -Force | |
} | |
} | |
#endregion Brotli | |
#region Compare Gzip version and Brotli version | |
If (-Not $KeepLargerFiles ` | |
-and (Test-Path $CompressedGzFile) -and (Test-Path $CompressedBrFile) ` | |
-and ($CompressedGzFile.Size -lt $CompressedBrFile.Size) | |
) { | |
Write-Verbose "$_ is smaller with Gzip than Brotli. Deleting Brotli version." | |
Remove-Item -Path $CompressedBrFile -Force | |
} | |
#endregion | |
$done++ | |
} | |
Write-Progress -Activity 'Compressing files' -Completed |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment