feat: self-hosting existing google fonts (#8540)

This commit is contained in:
Marcel Mraz 2024-09-24 17:30:21 +02:00 committed by GitHub
parent 6dfa18414a
commit a80cb5896a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 47 additions and 200 deletions

View file

@ -1,7 +1,6 @@
const { build } = require("esbuild");
const { sassPlugin } = require("esbuild-sass-plugin");
const { externalGlobalPlugin } = require("esbuild-plugin-external-global");
const { woff2BrowserPlugin } = require("./woff2/woff2-esbuild-plugins");
// Will be used later for treeshaking
//const fs = require("fs");
@ -45,13 +44,15 @@ const browserConfig = {
format: "esm",
plugins: [
sassPlugin(),
woff2BrowserPlugin(),
externalGlobalPlugin({
react: "React",
"react-dom": "ReactDOM",
}),
],
splitting: true,
loader: {
".woff2": "file",
},
};
const createESMBrowserBuild = async () => {
// Development unminified build with source maps
@ -100,9 +101,10 @@ const rawConfig = {
entryPoints: ["index.tsx"],
bundle: true,
format: "esm",
plugins: [sassPlugin(), woff2BrowserPlugin()],
plugins: [sassPlugin()],
loader: {
".json": "copy",
".woff2": "file",
},
packages: "external",
};

View file

@ -1,17 +1,17 @@
const fs = require("fs");
const { build } = require("esbuild");
const { sassPlugin } = require("esbuild-sass-plugin");
const {
woff2BrowserPlugin,
woff2ServerPlugin,
} = require("./woff2/woff2-esbuild-plugins");
const { woff2ServerPlugin } = require("./woff2/woff2-esbuild-plugins");
const browserConfig = {
entryPoints: ["index.ts"],
bundle: true,
format: "esm",
plugins: [sassPlugin(), woff2BrowserPlugin()],
plugins: [sassPlugin()],
assetNames: "assets/[name]",
loader: {
".woff2": "file",
},
};
// Will be used later for treeshaking

View file

@ -2,45 +2,9 @@ const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const which = require("which");
const fetch = require("node-fetch");
const wawoff = require("wawoff2");
const { Font } = require("fonteditor-core");
/**
* Custom esbuild plugin to convert url woff2 imports into a text.
* Other woff2 imports are handled by a "file" loader.
*
* @returns {import("esbuild").Plugin}
*/
module.exports.woff2BrowserPlugin = () => {
return {
name: "woff2BrowserPlugin",
setup(build) {
build.initialOptions.loader = {
".woff2": "file",
...build.initialOptions.loader,
};
build.onResolve({ filter: /^https:\/\/.+?\.woff2$/ }, (args) => {
return {
path: args.path,
namespace: "woff2BrowserPlugin",
};
});
build.onLoad(
{ filter: /.*/, namespace: "woff2BrowserPlugin" },
async (args) => {
return {
contents: args.path,
loader: "text",
};
},
);
},
};
};
/**
* Custom esbuild plugin to:
* 1. inline all woff2 (url and relative imports) as base64 for server-side use cases (no need for additional font fetch; works in both esm and commonjs)
@ -53,27 +17,6 @@ module.exports.woff2BrowserPlugin = () => {
* @returns {import("esbuild").Plugin}
*/
module.exports.woff2ServerPlugin = (options = {}) => {
// google CDN fails time to time, so let's retry
async function fetchRetry(url, options = {}, retries = 0, delay = 1000) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`Status: ${response.status}, ${await response.json()}`);
}
return response;
} catch (e) {
if (retries > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
return fetchRetry(url, options, retries - 1, delay * 2);
}
console.error(`Couldn't fetch: ${url}, error: ${e.message}`);
throw e;
}
}
return {
name: "woff2ServerPlugin",
setup(build) {
@ -82,9 +25,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
const fonts = new Map();
build.onResolve({ filter: /\.woff2$/ }, (args) => {
const resolvedPath = args.path.startsWith("http")
? args.path // url
: path.resolve(args.resolveDir, args.path); // absolute path
const resolvedPath = path.resolve(args.resolveDir, args.path);
return {
path: resolvedPath,
@ -101,9 +42,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
// read local woff2 as a buffer (WARN: `readFileSync` does not work!)
woff2Buffer = await fs.promises.readFile(args.path);
} else {
// fetch remote woff2 as a buffer (i.e. from a cdn)
const response = await fetchRetry(args.path, {}, 3);
woff2Buffer = await response.buffer();
throw new Error(`Font path has to be absolute! "${args.path}"`);
}
// google's brotli decompression into snft

View file

@ -1,15 +1,13 @@
// `EXCALIDRAW_ASSET_PATH` as a SSOT
const OSS_FONTS_CDN =
"https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/oss/";
/**
* Custom vite plugin to convert url woff2 imports into a text.
* Other woff2 imports are automatically served and resolved as a file uri.
* Custom vite plugin for auto-prefixing `EXCALIDRAW_ASSET_PATH` woff2 fonts in `excalidraw-app`.
*
* @returns {import("vite").PluginOption}
*/
module.exports.woff2BrowserPlugin = () => {
// for now limited to woff2 only, might be extended to any assets in the future
const regex = /^https:\/\/.+?\.woff2$/;
let isDev;
return {
@ -18,34 +16,9 @@ module.exports.woff2BrowserPlugin = () => {
config(_, { command }) {
isDev = command === "serve";
},
resolveId(source) {
if (!regex.test(source)) {
return null;
}
// getting the url to the dependency tree
return source;
},
load(id) {
if (!regex.test(id)) {
return null;
}
// loading the url as string
return `export default "${id}"`;
},
// necessary for dev as vite / rollup does skips https imports in serve (~dev) mode
// aka dev mode equivalent of "export default x" above (resolveId + load)
transform(code, id) {
// treat https woff2 imports as a text
if (isDev && id.endsWith("/excalidraw/fonts/index.ts")) {
return code.replaceAll(
/import\s+(\w+)\s+from\s+(["']https:\/\/.+?\.woff2["'])/g,
`const $1 = $2`,
);
}
// use CDN for Assistant
// using copy / replace as fonts defined in the `.css` don't have to be manually copied over (vite/rollup does this automatically),
// but at the same time can't be easily prefixed with the `EXCALIDRAW_ASSET_PATH` only for the `excalidraw-app`
if (!isDev && id.endsWith("/excalidraw/fonts/assets/fonts.css")) {
return `/* WARN: The following content is generated during excalidraw-app build */
@ -90,7 +63,6 @@ module.exports.woff2BrowserPlugin = () => {
}`;
}
// using EXCALIDRAW_ASSET_PATH as a SSOT
if (!isDev && id.endsWith("excalidraw-app/index.html")) {
return code.replace(
"<!-- PLACEHOLDER:EXCALIDRAW_APP_FONTS -->",
@ -110,9 +82,10 @@ module.exports.woff2BrowserPlugin = () => {
type="font/woff2"
crossorigin="anonymous"
/>
<!-- For Nunito only preload the latin range, which should be good enough for now -->
<link
rel="preload"
href="${OSS_FONTS_CDN}Virgil-Regular-hO16qHwV.woff2"
href="${OSS_FONTS_CDN}Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg-DqUjjPte.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
@ -124,6 +97,13 @@ module.exports.woff2BrowserPlugin = () => {
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="${OSS_FONTS_CDN}Virgil-Regular-hO16qHwV.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
`,
);
}