diff --git a/.gitignore b/.gitignore index c8a3c4254..f90d75d56 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ test.log tunnel.log /static /well-known +/.well-known-cache /**/tmp-* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b6e358687..384525655 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,7 @@ default: - node_modules - .stack - .stack-work + - .well-known-cache variables: STACK_ROOT: "${CI_PROJECT_DIR}/.stack" diff --git a/config/favicon.json b/config/favicon.json index 2bb896654..f72c235d6 100644 --- a/config/favicon.json +++ b/config/favicon.json @@ -72,6 +72,5 @@ }, "settings": { "html_code_file": true - }, - "versioning": false + } } diff --git a/package-lock.json b/package-lock.json index c315ea0a7..65112246e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4752,6 +4752,18 @@ "yargs": "12.0.1" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, "date-format": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", @@ -4767,6 +4779,28 @@ "ms": "^2.1.1" } }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "log4js": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.3.tgz", @@ -4785,6 +4819,52 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", @@ -4797,6 +4877,15 @@ "readable-stream": "^2.3.0" } }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "^1.4.1" + } + }, "ws": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", @@ -8626,14 +8715,22 @@ } }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + } } }, "fs-minipass": { @@ -9836,39 +9933,13 @@ "dev": true }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - } } }, "has": { @@ -15634,9 +15705,9 @@ "dev": true }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "object-assign": { @@ -17327,6 +17398,12 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -17797,31 +17874,31 @@ } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" }, "dependencies": { "qs": { @@ -17832,6 +17909,27 @@ } } }, + "request-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -19093,6 +19191,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -19154,6 +19258,17 @@ "ms": "^2.1.1" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19747,11 +19862,12 @@ "dev": true }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" }, "dependencies": { @@ -20545,6 +20661,19 @@ "lodash": ">=3.5 <5", "object.entries": "^1.1.0", "tapable": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "webpack-plugin-hash-output": { diff --git a/package.json b/package.json index 3549fb585..de7a3eb42 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "css-loader": "^2.1.1", "eslint": "^5.16.0", "file-loader": "^5.0.2", + "fs-extra": "^8.1.0", "glob": "^7.1.6", "html-webpack-plugin": "^3.2.0", "husky": "^2.7.0", @@ -96,6 +97,8 @@ "postcss-preset-env": "^6.7.0", "real-favicon-webpack-plugin": "^0.2.3", "remove-files-webpack-plugin": "^1.1.3", + "request": "^2.88.0", + "request-promise": "^4.2.5", "resolve-url-loader": "^3.1.1", "sass": "^1.23.7", "sass-loader": "^7.3.1", diff --git a/shell.nix b/shell.nix index 76e71b9ec..08c6dde7c 100644 --- a/shell.nix +++ b/shell.nix @@ -19,7 +19,7 @@ let ''; override = oldAttrs: { - nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]); + nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-13_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]); shellHook = '' export PROMPT_INFO="${oldAttrs.name}" diff --git a/webpack.config.js b/webpack.config.js index 24e9c7aef..01ce06558 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,9 +2,10 @@ const webpack = require('webpack'); const path = require('path'); const tmp = require('tmp'); tmp.setGracefulCleanup(); -const fs = require('fs'); +const fs = require('fs-extra'); const glob = require('glob'); const { execSync } = require('child_process'); +const request = require('request-promise'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); @@ -17,244 +18,316 @@ const HashOutput = require('webpack-plugin-hash-output'); const postcssPresetEnv = require('postcss-preset-env'); const RemovePlugin = require('remove-files-webpack-plugin'); const RealFaviconPlugin = require('real-favicon-webpack-plugin'); +const crypto = require('crypto'); const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.'); const packageVersion = require('./package.json').version; -module.exports = { - module: { - rules: [ - { - loader: 'babel-loader', +async function webpackConfig() { + let faviconApiVersion = undefined; - options: { - plugins: ['syntax-dynamic-import'], + try { + const faviconApiChangelog = await request({ + method: 'GET', + uri: 'https://realfavicongenerator.net/api/versions', + headers: { + 'Accept': '*/*' + }, + json: true + }); + faviconApiVersion = faviconApiChangelog.filter(vObj => vObj.relevance.automated_update).slice(-1)[0].version; + } catch(e) { + console.error(e); + } + + return { + module: { + rules: [ + { + loader: 'babel-loader', - presets: [ - [ - '@babel/preset-env', - { - modules: false, - targets: { - edge: "17", - firefox: "50", - chrome: "60", - safari: "11.1", - ie: "11", - }, - useBuiltIns: "usage", - corejs: 3 - } + options: { + plugins: ['syntax-dynamic-import'], + + presets: [ + [ + '@babel/preset-env', + { + modules: false, + targets: { + edge: "17", + firefox: "50", + chrome: "60", + safari: "11.1", + ie: "11", + }, + useBuiltIns: "usage", + corejs: 3 + } + ] ] - ] - }, - test: /\.js$/i, - exclude: /node_modules/, - }, - { - test: /\.css$/i, - use: [ MiniCssExtractPlugin.loader, - { loader: 'css-loader', options: { sourceMap: true }}, - { loader: 'postcss-loader', options: { - sourceMap: true, - plugins: () => [ postcssPresetEnv ] - }}, - { loader: 'resolve-url-loader', options: { sourceMap: true }} - ] - }, - { - test: /\.s(c|a)ss$/i, - use: [ MiniCssExtractPlugin.loader, - { loader: 'css-loader', options: { sourceMap: true }}, - { loader: 'postcss-loader', options: { - sourceMap: true, - plugins: () => [ postcssPresetEnv ] - }}, - { loader: 'resolve-url-loader', options: { sourceMap: true }}, - { loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: true }} - ] - }, - { - test: /\.(woff(2)?|ttf|eot|svg)(\?.*)?$/i, - use: [ - { - loader: 'file-loader', - options: { - name: '[contenthash].[ext]', - esModule: false - } - } - ] - } - ] - }, - - entry: { - main: [ path.resolve(__dirname, 'frontend/src', 'polyfill.js'), - path.resolve(__dirname, 'frontend/src', 'main.js') - ] - }, - - plugins: [ - new HashOutput({ - validateOutput: true, - validateOutputRegex: /static\/wp-[^\/]\// - }), - new MiniCssExtractPlugin({ - // Options similar to the same options in webpackOptions.output - // all options are optional - filename: '[chunkhash].css', - chunkFilename: '[chunkhash].css', - ignoreOrder: false, // Enable to remove warnings about conflicting order - }), - new webpack.NamedChunksPlugin((chunk) => { - if (chunk.name) { - return chunk.name; - } - let modules = chunk.modules || [chunk.entryModule]; - return modules.map(m => path.relative(m.context, m.request)).join("_"); - }), - new webpack.NamedModulesPlugin(), - new ManifestPlugin({ - fileName: path.resolve(__dirname, 'config', 'webpack.yml'), - publicPath: `wp-${webpackVersion}/`, - generate: (seed, files, entrypoints) => Object.keys(entrypoints).reduce((acc, fs) => ({...acc, [fs]: files.filter(file => entrypoints[fs].filter(basename => !(/\.map$/.test(basename))).some(basename => file.path.endsWith(basename))).filter(file => file.isInitial).map(file => file.path)}), {}), - serialize: yaml.safeDump - }), - new CleanWebpackPlugin({ - cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'), - path.resolve(__dirname, 'well-known'), - ] - }), - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - new CopyPlugin([ - { from: 'assets/lmu/sigillum.svg', to: path.resolve(__dirname, 'static', 'img/lmu/sigillum.svg') }, - ]), - new webpack.DefinePlugin({ - VERSION: JSON.stringify(packageVersion) - }), - ...(() => { - const faviconJson = require('./config/favicon.json'); - const langs = new Set(); - function findLangs(json) { - if (json && json._i18n) { - Object.keys(json).forEach(key => { - if (key !== '_i18n') { - langs.add(key); - } - }) - } else if (Array.isArray(json)) { - json.forEach(elem => findLangs(elem)); - } else if (typeof json === 'object') { - Object.keys(json).forEach(key => findLangs(json[key])); - } - } - findLangs(faviconJson); - - function selectLang(lang, json) { - if (json && json._i18n) { - return json[lang]; - } else if (Array.isArray(json)) { - return json.map(elem => selectLang(lang, elem)); - } else if (typeof json === 'object') { - return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)])); - } else { - return json; - } - } - - const langJsons = {}; - Array.from(langs).forEach(lang => { - langJsons[lang] = selectLang(lang, faviconJson); - }); - - return Array.from(langs).map(lang => { - const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" }); - fs.writeSync(tmpobj.fd, JSON.stringify(langJsons[lang])); - fs.close(tmpobj.fd); - - return [ - new RealFaviconPlugin({ - faviconJson: `./${tmpobj.name}`, - outputPath: path.resolve(__dirname, 'well-known', lang), - inject: false - }), - new CopyPlugin([ - { from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') }, - ]) - ]; - }).flat(1); - })(), - { apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => { - const imgFiles = glob.sync(path.resolve(__dirname, 'well-known', '**', '*.@(png)')); - const imgFilesArgs = Array.from(imgFiles).join(" "); - execSync(`exiftool -overwrite_original -all= ${imgFilesArgs}`, { stdio: 'inherit' }); - }) - } - ], - - output: { - chunkFilename: '[chunkhash].js', - filename: '[chunkhash].js', - path: path.resolve(__dirname, 'static', `wp-${webpackVersion}`), - publicPath: `/static/res/wp-${webpackVersion}/`, - hashFunction: 'shake256', - hashDigestLength: 36 - }, - - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - cache: true, - parallel: true, - sourceMap: true - }), - new OptimizeCSSAssetsPlugin({ - cssProcessorOptions: { - map: { - inline: false - } - } - }) - ], - runtimeChunk: 'single', - splitChunks: { - chunks: 'all', - maxInitialRequests: Infinity, - maxAsyncRequests: Infinity, - minSize: 0, - minChunks: 1, - cacheGroups: { - vendor: { - test(module, chunk) { - return module.context.match(/[\\/]node_modules[\\/]/); }, - name(module, chunks, cacheGroupKey) { - const moduleFileName = module.identifier().split('/').reduceRight(item => item); - const allChunksNames = chunks.map((item) => item.name).join('~'); - const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; - - return `${cacheGroupKey}-${packageName}-${allChunksNames}-${moduleFileName}`; - }, - priority: -10 + test: /\.js$/i, + exclude: /node_modules/, }, - default: { - priority: -20, - minChunks: 1 + { + test: /\.css$/i, + use: [ MiniCssExtractPlugin.loader, + { loader: 'css-loader', options: { sourceMap: true }}, + { loader: 'postcss-loader', options: { + sourceMap: true, + plugins: () => [ postcssPresetEnv ] + }}, + { loader: 'resolve-url-loader', options: { sourceMap: true }} + ] + }, + { + test: /\.s(c|a)ss$/i, + use: [ MiniCssExtractPlugin.loader, + { loader: 'css-loader', options: { sourceMap: true }}, + { loader: 'postcss-loader', options: { + sourceMap: true, + plugins: () => [ postcssPresetEnv ] + }}, + { loader: 'resolve-url-loader', options: { sourceMap: true }}, + { loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: true }} + ] + }, + { + test: /\.(woff(2)?|ttf|eot|svg)(\?.*)?$/i, + use: [ + { + loader: 'file-loader', + options: { + name: '[contenthash].[ext]', + esModule: false + } + } + ] } - } + ] }, - moduleIds: 'hashed' - }, - mode: 'production', + entry: { + main: [ path.resolve(__dirname, 'frontend/src', 'polyfill.js'), + path.resolve(__dirname, 'frontend/src', 'main.js') + ] + }, - recordsPath: path.join(__dirname, 'records.json'), + plugins: [ + new HashOutput({ + validateOutput: true, + validateOutputRegex: /static\/wp-[^\/]\// + }), + new MiniCssExtractPlugin({ + // Options similar to the same options in webpackOptions.output + // all options are optional + filename: '[chunkhash].css', + chunkFilename: '[chunkhash].css', + ignoreOrder: false, // Enable to remove warnings about conflicting order + }), + new webpack.NamedChunksPlugin((chunk) => { + if (chunk.name) { + return chunk.name; + } + let modules = chunk.modules || [chunk.entryModule]; + return modules.map(m => path.relative(m.context, m.request)).join("_"); + }), + new webpack.NamedModulesPlugin(), + new ManifestPlugin({ + fileName: path.resolve(__dirname, 'config', 'webpack.yml'), + publicPath: `wp-${webpackVersion}/`, + generate: (seed, files, entrypoints) => Object.keys(entrypoints).reduce((acc, fs) => ({...acc, [fs]: files.filter(file => entrypoints[fs].filter(basename => !(/\.map$/.test(basename))).some(basename => file.path.endsWith(basename))).filter(file => file.isInitial).map(file => file.path)}), {}), + serialize: yaml.safeDump + }), + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'), + path.resolve(__dirname, 'well-known'), + ] + }), + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new CopyPlugin([ + { from: 'assets/lmu/sigillum.svg', to: path.resolve(__dirname, 'static', 'img/lmu/sigillum.svg') }, + ]), + new webpack.DefinePlugin({ + VERSION: JSON.stringify(packageVersion) + }), + ...(() => { + const faviconJson = require('./config/favicon.json'); + const langs = new Set(); + function findLangs(json) { + if (json && json._i18n) { + Object.keys(json).forEach(key => { + if (key !== '_i18n') { + langs.add(key); + } + }) + } else if (Array.isArray(json)) { + json.forEach(elem => findLangs(elem)); + } else if (typeof json === 'object') { + Object.keys(json).forEach(key => findLangs(json[key])); + } + } + findLangs(faviconJson); - performance: { - assetFilter: (assetFilename) => !(/\.(map|svg|ttf|eot)$/.test(assetFilename)) - }, + function selectLang(lang, json) { + if (json && json._i18n) { + return json[lang]; + } else if (Array.isArray(json)) { + return json.map(elem => selectLang(lang, elem)); + } else if (typeof json === 'object') { + return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)])); + } else { + return json; + } + } - devtool: 'source-map' -}; + const langJsons = {}; + Array.from(langs).forEach(lang => { + langJsons[lang] = selectLang(lang, faviconJson); + }); + + const cacheHash = crypto.createHash('sha256'); + cacheHash.update(JSON.stringify(langJsons)); + + const cacheFiles = new Set([ + ...(Array.from(langs).map(lang => path.resolve(__dirname, langJsons[lang].masterPicture))), + path.resolve(__dirname, 'config/robots.txt') + ]); + + for (const cacheFile of cacheFiles) { + cacheHash.update(fs.readFileSync(cacheFile)); + } + + const cacheDigest = cacheHash.copy().digest('hex'); + + let cachedVersion = undefined; + + const versionFile = path.resolve(__dirname, '.well-known-cache', `${cacheDigest}.version`); + try { + if (fs.existsSync(versionFile)) { + cachedVersion = fs.readFileSync(versionFile, 'utf8'); + } + } catch (e) { + console.error(e); + } + + if (faviconApiVersion) { + cacheHash.update(faviconApiVersion); + } + const versionDigest = cacheHash.digest('hex'); + + return Array.from(langs).map(lang => { + const faviconConfig = { versioning: { param_name: 'v', param_value: versionDigest.substr(0,10) }, ...langJsons[lang] }; + + const cacheDirectory = path.resolve(__dirname, '.well-known-cache', `${cacheDigest}-${lang}`); + + if (fs.existsSync(cacheDirectory) && (!faviconApiVersion || faviconApiVersion === cachedVersion)) { + console.log(`Using cached well-known from ${cacheDirectory} for ${lang}`); + return [ + new CopyPlugin([ + { from: cacheDirectory, to: path.resolve(__dirname, 'well-known', lang) } + ]) + ]; + } else { + const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" }); + fs.writeSync(tmpobj.fd, JSON.stringify(faviconConfig)); + fs.close(tmpobj.fd); + + return [ + new RealFaviconPlugin({ + faviconJson: `./${tmpobj.name}`, + outputPath: path.resolve(__dirname, 'well-known', lang), + inject: false + }), + new CopyPlugin([ + { from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') }, + ]), + { apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => { + const imgFiles = glob.sync(path.resolve(__dirname, 'well-known', lang, '*.@(png)')); + const imgFilesArgs = Array.from(imgFiles).join(" "); + execSync(`exiftool -overwrite_original -all= ${imgFilesArgs}`, { stdio: 'inherit' }); + }) + }, + { apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => { + fs.ensureDirSync(__dirname, '.well-known-cache'); + fs.copySync(path.resolve(__dirname, 'well-known', lang), cacheDirectory); + if (!fs.existsSync(versionFile)) { + fs.writeFileSync(versionFile, faviconApiVersion, { encoding: 'utf8' }); + } + }) + } + ]; + } + }).flat(1); + })() + ], + + output: { + chunkFilename: '[chunkhash].js', + filename: '[chunkhash].js', + path: path.resolve(__dirname, 'static', `wp-${webpackVersion}`), + publicPath: `/static/res/wp-${webpackVersion}/`, + hashFunction: 'shake256', + hashDigestLength: 36 + }, + + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + cache: true, + parallel: true, + sourceMap: true + }), + new OptimizeCSSAssetsPlugin({ + cssProcessorOptions: { + map: { + inline: false + } + } + }) + ], + runtimeChunk: 'single', + splitChunks: { + chunks: 'all', + maxInitialRequests: Infinity, + maxAsyncRequests: Infinity, + minSize: 0, + minChunks: 1, + cacheGroups: { + vendor: { + test(module, chunk) { + return module.context.match(/[\\/]node_modules[\\/]/); + }, + name(module, chunks, cacheGroupKey) { + const moduleFileName = module.identifier().split('/').reduceRight(item => item); + const allChunksNames = chunks.map((item) => item.name).join('~'); + const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; + + return `${cacheGroupKey}-${packageName}-${allChunksNames}-${moduleFileName}`; + }, + priority: -10 + }, + default: { + priority: -20, + minChunks: 1 + } + } + }, + moduleIds: 'hashed' + }, + + mode: 'production', + + recordsPath: path.join(__dirname, 'records.json'), + + performance: { + assetFilter: (assetFilename) => !(/\.(map|svg|ttf|eot)$/.test(assetFilename)) + }, + + devtool: 'source-map' + }; +} + +module.exports = webpackConfig;