diff --git a/src/assets/javascripts/index.ts b/src/assets/javascripts/bundle.ts similarity index 100% rename from src/assets/javascripts/index.ts rename to src/assets/javascripts/bundle.ts diff --git a/src/assets/javascripts/workers/search.ts b/src/assets/javascripts/workers/search.ts new file mode 100644 index 000000000..db4099387 --- /dev/null +++ b/src/assets/javascripts/workers/search.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016-2021 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +import "~/integrations/search/worker/main" diff --git a/src/base.html b/src/base.html index 927e116d1..110ca287c 100644 --- a/src/base.html +++ b/src/base.html @@ -355,7 +355,7 @@ "base": base_url, "features": features, "translations": {}, - "search": "assets/javascripts/worker/search.js" | url, + "search": "assets/javascripts/workers/search.js" | url, } -%} diff --git a/src/overrides/assets/javascripts/index.ts b/src/overrides/assets/javascripts/bundle.ts similarity index 100% rename from src/overrides/assets/javascripts/index.ts rename to src/overrides/assets/javascripts/bundle.ts diff --git a/tools/copy/index.ts b/tools/copy/index.ts index c0709f754..aa5fe8154 100644 --- a/tools/copy/index.ts +++ b/tools/copy/index.ts @@ -34,11 +34,12 @@ import { mkdir, resolve } from "../resolve" /** * Copy transform function * - * @param content - Content + * @param data - File data + * @param name - File name * - * @returns Transformed content + * @returns Transformed file data */ -type CopyTransformFn = (content: string) => Promise +type CopyTransformFn = (data: string, name: string) => Promise /* ------------------------------------------------------------------------- */ @@ -71,8 +72,8 @@ export function copy( ? from(fs.copyFile(src, out)) : from(fs.readFile(src, "utf8")) .pipe( - switchMap(content => transform(content)), - switchMap(content => fs.writeFile(out, content)) + switchMap(data => transform(data, src)), + switchMap(data => fs.writeFile(out, data)) ) ), mapTo(out) diff --git a/tools/index.ts b/tools/index.ts index 65f314dd5..48a27dfff 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -22,8 +22,9 @@ import { minify as minhtml } from "html-minifier" import * as path from "path" -import { concat, merge } from "rxjs" -import { concatMap } from "rxjs/operators" +import { concat, defer, merge } from "rxjs" +import { concatMap, tap } from "rxjs/operators" +import { extendDefaultPlugins, optimize } from "svgo" import { copyAll } from "./copy" import { base, resolve } from "./resolve" @@ -48,6 +49,26 @@ function ext(file: string, extension: string): string { return file.replace(path.extname(file), extension) } +/** + * Optimize SVG data + * + * This function will just pass-through non-SVG data, which makes the pipeline + * much simpler, as we can reuse it for the license texts. + * + * @param data - SVG data + * + * @returns Minified SVG data + */ +function minsvg(data: string): string { + const result = optimize(data, { + plugins: extendDefaultPlugins([ + { name: "removeDimensions", active: true }, + { name: "removeViewBox", active: false } + ]) + }) + return result.data || data +} + /* ---------------------------------------------------------------------------- * Program * ------------------------------------------------------------------------- */ @@ -59,28 +80,30 @@ const dependencies$ = concat( ...["*.svg", "../LICENSE"] .map(pattern => copyAll(pattern, { src: "node_modules/@mdi/svg/svg", - out: `${base}/.icons/material` + out: `${base}/.icons/material`, + ...process.argv.includes("--optimize") && { + transform: async data => minsvg(data) + } })), /* Copy GitHub octicons */ ...["*.svg", "../../LICENSE"] .map(pattern => copyAll(pattern, { src: "node_modules/@primer/octicons/build/svg", - out: `${base}/.icons/octicons` + out: `${base}/.icons/octicons`, + ...process.argv.includes("--optimize") && { + transform: async data => minsvg(data) + } })), /* Copy FontAwesome icons */ ...["**/*.svg", "../LICENSE.txt"] .map(pattern => copyAll(pattern, { src: "node_modules/@fortawesome/fontawesome-free/svgs", - out: `${base}/.icons/fontawesome` - })), - - /* Copy Lunr.js search stemmers and segmenter */ - ...["min/*.js", "tinyseg.js"] - .map(pattern => copyAll(pattern, { - src: "node_modules/lunr-languages", - out: `${base}/assets/javascripts/lunr` + out: `${base}/.icons/fontawesome`, + ...process.argv.includes("--optimize") && { + transform: async data => minsvg(data) + } })) ) @@ -98,7 +121,7 @@ const assets$ = concat( copyAll("**/*.html", { src: "src", out: base, - transform: async content => { + transform: async data => { const metadata = require("../package.json") const banner = "{#-\n" + @@ -106,7 +129,7 @@ const assets$ = concat( "-#}\n" /* Normalize line feeds and minify HTML */ - const html = content.replace(/\r\n/gm, "\n") + const html = data.replace(/\r\n/gm, "\n") return banner + minhtml(html, { collapseBooleanAttributes: true, includeAutoGeneratedTags: false, @@ -136,36 +159,38 @@ const stylesheets$ = resolve("**/[!_]*.scss", { cwd: "src" }) })) ) -/* Transform scripts with ESBuild */ -const javascripts$ = merge( +/* Transform stylesheets with SASS and PostCSS */ +const javascripts$ = resolve("**/{bundle,search}.ts", { cwd: "src" }) + .pipe( + concatMap(file => transformScript({ + src: `src/${file}`, + out: ext(`${base}/${file}`, ".js") + })) + ) - /* Transform application */ - transformScript({ - src: "src/assets/javascripts/index.ts", - out: `${base}/assets/javascripts/bundle.js` - }), +/* Add content hashes to files and replace occurrences */ +const manifest$ = defer(() => resolve("**/*.{css,js}", {cwd: base }) + .pipe(tap(console.log))) - /* Transform application overrides */ - transformScript({ - src: "src/overrides/assets/javascripts/index.ts", - out: `${base}/overrides/assets/javascripts/bundle.js` - }), +/* Copy Lunr.js search stemmers and segmenter */ +const stemmers$ = ["min/*.js", "tinyseg.js"] + .map(pattern => copyAll(pattern, { + src: "node_modules/lunr-languages", + out: `${base}/assets/javascripts/lunr` + })) - /* Transform search worker */ - transformScript({ - src: "src/assets/javascripts/integrations/search/worker/main/index.ts", - out: `${base}/assets/javascripts/worker/search.js` - }) -) +/* ------------------------------------------------------------------------- */ -/* Compile everything */ +/* Put everything together */ concat( dependencies$, merge( assets$, stylesheets$, javascripts$ - ) + ), + manifest$, + stemmers$ ) .subscribe() // .subscribe(console.log) diff --git a/typings/svgo/index.d.ts b/typings/svgo/index.d.ts new file mode 100644 index 000000000..7fb7bce32 --- /dev/null +++ b/typings/svgo/index.d.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016-2021 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* ---------------------------------------------------------------------------- + * Types + * ------------------------------------------------------------------------- */ + +declare module "svgo" { + + /** + * Plugin + */ + interface Plugin { + name: string + active: boolean + } + + /** + * Optimization configuration + */ + interface OptimizeConfig { + plugins: Plugin[] + } + + /** + * Optimization result + */ + interface OptimizeResult { + data: string + } + + /** + * Optimize SVG + * + * @param data - SVG data + * + * @returns Optimization result + */ + function optimize(data: string, config: OptimizeConfig): OptimizeResult + + /** + * Extend the list of default plugins + * + * @param plugins - Plugins + * + * @returns Plugins + */ + function extendDefaultPlugins(plugins: Plugin[]): Plugin[] +}