From 38c397444f677798131b3170296fb71d4097255e Mon Sep 17 00:00:00 2001 From: Mikko Tiihonen Date: Sun, 18 Sep 2016 21:11:10 +0200 Subject: [PATCH] Precompress and optimize static files during build and serve them to the browsers. - optimize png images with pngquant (quality 95) and advpng - optimize svg images with svgo - create precompressed variants of js, css, svg files: gz using zopfli and br using brotli Currently using forked versions of expressjs/send/negotiator while waiting for the PRs to go through: - https://github.com/jshttp/negotiator/pull/49 - https://github.com/pillarjs/send/pull/108 --- Dockerfile | 24 ++++++++++++++++++++++ package.json | 11 ++++++---- precompress-file.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++ precompress.sh | 14 +++++++++++++ server/server.js | 2 +- 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100755 precompress-file.sh create mode 100755 precompress.sh diff --git a/Dockerfile b/Dockerfile index 6efad53e82..2162412b54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,12 +27,36 @@ ADD . ${WORK} # Build and set permissions for arbitary non-root users in OpenShift # (https://docs.openshift.org/latest/creating_images/guidelines.html#openshift-specific-guidelines) RUN \ + echo "deb http://ftp.de.debian.org/debian/ stretch main contrib non-free\ndeb-src http://ftp.de.debian.org/debian/ stretch main contrib non-free" | tee /etc/apt/sources.list.d/stretch.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + brotli \ + zopfli \ + advancecomp \ + pngquant && \ npm install && \ npm rebuild node-sass && \ npm run build && \ rm -rf static docs test /tmp/* && \ npm prune --production && \ npm cache clean && \ + apt-get purge --auto-remove -y \ + brotli \ + zopfli \ + advancecomp \ + pngquant \ + # also remove packages that native node module compilations required \ + g++ \ + cpp \ + gcc \ + make \ + autoconf \ + automake \ + libtool \ + lib*-dev && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ chmod -R a+rwX . && \ chown -R 9999:9999 ${WORK} diff --git a/package.json b/package.json index 5930dd7989..c788228bfe 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,13 @@ "test-visual-update": "gemini update test/visual/", "es-lint": "eslint .", "lint": "npm run es-lint", - "build": "NODE_ENV=production webpack --progress --color --display-error-details", - "win-build": "set NODE_ENV=production&& webpack --progress --color", + "build": "npm run webpack && npm run precompress", + "win-build": "npm run win-webpack", + "webpack": "NODE_ENV=production webpack --progress --color --display-error-details", + "win-webpack": "set NODE_ENV=production&& webpack --progress --color", "start": "NODE_ENV=production node --harmony server/server", - "win-start": "set NODE_ENV=production&& set CONFIG=hsl&& node --harmony server/server.js" + "win-start": "set NODE_ENV=production&& set CONFIG=hsl&& node --harmony server/server.js", + "precompress": "./precompress.sh _static" }, "main": "server.js", "repository": { @@ -59,7 +62,7 @@ "csscolorparser": "1.0.3", "debug": "2.3.3", "element-resize-detector": "1.1.9", - "express": "4.14.0", + "express": "gmokki/express#6eeb054", "fluxible": "1.2.0", "fluxible-action-utils": "0.2.4", "fluxible-addons-react": "0.2.8", diff --git a/precompress-file.sh b/precompress-file.sh new file mode 100755 index 0000000000..54f81f7ad2 --- /dev/null +++ b/precompress-file.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +if [ -x "$(command -v brotli)" ]; then + BROTLI=brotli +elif [ -x "$(command -v bro)" ]; then + BROTLI=bro +fi +if [ -x "$(command -v zopfli)" ]; then + ZOPFLI=zopfli +fi +if [ -x "$(command -v advpng)" ]; then + ADVPNG=advpng +fi + +# settings +ZOPFLI_ITERATIONS=15 +BROTLI_LEVEL=20 +PNG_QUALITY=95 +PNG_ZOPFLI_ITERATIONS=15 +SKIP_COMPRESS_REGEXP="\.map$" + +[ -f "$1" ] || exit 1 + +[[ "$1" =~ $SKIP_COMPRESS_REGEXP ]] && exit 0 + +TMPDIR="$(mktemp -d /tmp/precompress-XXXXXXXXXX)" +function cleanup { + rm -rf "$TMPDIR" +} +trap cleanup EXIT + +if [[ "$1" =~ \.png$ ]]; then + TMPFILE="$TMPDIR/image.png" + if pngquant --speed 1 --quality $PNG_QUALITY - < "$1" > "$TMPFILE"; then :; else + [[ $? != 99 ]] && exit 1 + fi + [ -n "$ADVPNG" ] && $ADVPNG --recompress --shrink-insane --iter=$PNG_ZOPFLI_ITERATIONS --quiet "$TMPFILE" || exit 1 + mv "$TMPFILE" "$1" || exit 2 +else + if [[ "$1" =~ \.svg$ ]]; then + export PATH+=:$(dirname "$0")/node_modules/.bin + svgo --multipass --disable removeUselessDefs --disable=cleanupIDs --config '{"useShortTags":false}' --output "$TMPDIR/file.svg" "$1" && + mv "$TMPDIR/file.svg" "$1" || exit 3 + fi + [ -n "$ZOPFLI" ] && $ZOPFLI -i$ZOPFLI_ITERATIONS "$1" -c > "$TMPDIR/file.gz" && mv "$TMPDIR/file.gz" "$1.gz" || exit 4 + [ -n "$BROTLI" ] && $BROTLI --quality $BROTLI_LEVEL --input "$1" --output "$TMPDIR/file.br" && mv "$TMPDIR/file.br" "$1.br" || exit 5 +fi + +exit 0 diff --git a/precompress.sh b/precompress.sh new file mode 100755 index 0000000000..80f73db69f --- /dev/null +++ b/precompress.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +if [ ! -d "$1" ]; then + exit 1 +fi + +if [ $(uname) == Linux ]; then + NUMCPUS=$(grep -c "^processor" /proc/cpuinfo) +else + NUMCPUS=$(sysctl -n hw.ncpu) +fi + +find "$1" -type f -and -not -name "*.gz" -and -not -name "*.br" -print0 | xargs -0 -n1 -P$NUMCPUS nice ./precompress-file.sh diff --git a/server/server.js b/server/server.js index 53e75efac8..ee9602237f 100644 --- a/server/server.js +++ b/server/server.js @@ -42,7 +42,7 @@ const app = express(); function setUpStaticFolders() { const staticFolder = path.join(process.cwd(), '_static'); // Sert cache for 1 week - app.use(config.APP_PATH, express.static(staticFolder, { maxAge: 604800000 })); + app.use(config.APP_PATH, express.static(staticFolder, { maxAge: 604800000, precompressed: [{encoding: 'br', extension: '.br'}, {encoding: 'gzip', extension: '.gz'}]})); } function setUpMiddleware() {