Skip to content

Instantly share code, notes, and snippets.

@rhymeswithmogul
Last active December 1, 2021 12:45
Show Gist options
  • Save rhymeswithmogul/20d5cb31a4c90c3a21d9175b1564669d to your computer and use it in GitHub Desktop.
Save rhymeswithmogul/20d5cb31a4c90c3a21d9175b1564669d to your computer and use it in GitHub Desktop.
Generate statically-compressed assets for NGINX
#!/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