feat: Chore: Refactor ExpressiveCode system to better optimize it for future use (#30)

Co-authored-by: create-issue-branch[bot] <53036503+create-issue-branch[bot]@users.noreply.github.com>
Co-authored-by: Adam Matthiesen <amatthiesen@outlook.com>
This commit is contained in:
create-issue-branch[bot] 2024-03-08 21:54:41 -08:00 committed by GitHub
parent 5f8e99d33b
commit 1370875042
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1851 additions and 97 deletions

View File

@ -0,0 +1,10 @@
---
"@matthiesenxyz/astro-gists": patch
---
Changes:
- Internal refactor that will provide better future-proofing, this change also allowed the addition of EC's built in light/dark mode automations! Code Blocks will now be able to show both dark/light mode depending on the user's system preferences.
# NEW:
- adds light/darkmode support
- Themes can be defined by either single theme or an array (`['astroGists-dark','astroGists-light']`)

View File

@ -55,7 +55,8 @@ import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
+ integrations: [astroGist({
theme: 'github-dark' // OPTIONAL, if not set defaults to Astro's Houston, Only Available options are Shiki Bundled options
// This is the default options shown... dark is Astro's Houston-dark, and light is currently nightowl-light until a Houston-light is released.
theme: ['astroGists-dark', 'astroGists-light']
+ })]
});
```

View File

@ -1,35 +1,31 @@
---
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import { ExpressiveCode, loadShikiTheme, type BundledShikiTheme, ExpressiveCodeTheme } from 'expressive-code'
import { toHtml } from 'hast-util-to-html'
import fs from 'node:fs'
import config from "virtual:astro-gists/config";
import { getPageData } from './page-data'
import { getPageData } from './page-data';
import { engine } from '../index'
const jsoncString = fs.readFileSync(new URL(`../themes/houston.jsonc`, import.meta.url), 'utf-8')
const houston = ExpressiveCodeTheme.fromJSONString(jsoncString)
const theme = config.theme
const { code, raw_url, locale,
lang = '', meta = '', ...props
// Extract the code and other props from the Astro component
const {
code,
raw_url,
locale,
lang = '',
meta = '',
...props
} = Astro.props
const pageData = getPageData(Astro.request)
// Note: It's important to store the incremented index in a local variable immediately,
// as the `pageData` object is shared between all components on the current page
// and can be changed by other Code components during the `await` calls below
const groupIndex = ++pageData.blockGroupIndex
const engine = new ExpressiveCode({
themes: [theme ? await loadShikiTheme(theme as BundledShikiTheme) : houston],
plugins: [pluginLineNumbers()],
})
// Get the current block group index from the page data
const pageData = getPageData(Astro.request)
// Note: It's important to store the incremented index in a local variable immediately,
// as the `pageData` object is shared between all components on the current page
// and can be changed by other Code components during the `await` calls below
const groupIndex = ++pageData.blockGroupIndex
// Get the base and theme styles, and the JS modules from the ExpressiveCode engine
const baseStyles = await engine.getBaseStyles();
const themeStyles = await engine.getThemeStyles();
const jsModules = await engine.getJsModules();
// Render the code using the ExpressiveCode engine
const { renderedGroupAst, styles } = await engine.render({
language: lang, meta, locale, code,
parentDocument: {
@ -37,14 +33,17 @@ const { renderedGroupAst, styles } = await engine.render({
},
props, })
// Convert the rendered group AST to HTML
let htmlContent = toHtml(renderedGroupAst)
// Prepend the base and theme styles to the HTML content
const stylesToPrepend: string[] = []
stylesToPrepend.push(baseStyles, themeStyles, ...styles)
if (stylesToPrepend.length) {
htmlContent = `<style>${[...stylesToPrepend].join('').replace(/\.expressive-code/g, '.gist .expressive-code')}</style>${htmlContent}`
}
// Prepend the JS modules to the HTML content
const scriptsToPrepend: string[] = []
scriptsToPrepend.push(...jsModules)
if (scriptsToPrepend.length) {

View File

@ -0,0 +1,11 @@
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import { ExpressiveCode } from 'expressive-code'
import config from "virtual:astro-gists/config";
import { loadTheme } from "./theming";
export { default as Code } from './components/Code.astro'
export const engine = new ExpressiveCode({
themes: await loadTheme(config.theme),
plugins: [pluginLineNumbers()],
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
import { ExpressiveCodeTheme, loadShikiTheme, type BundledShikiTheme } from 'expressive-code'
import fs from 'node:fs'
function loadBundledThemeFromFile(theme: string) {
return fs.readFileSync(new URL(`./themes/${theme}.jsonc`, import.meta.url), 'utf-8');
}
const houstonDark = loadBundledThemeFromFile('houston-dark');
const nightOwlLight = loadBundledThemeFromFile('night-owl-light');
export type BundledThemeName = 'astroGists-dark' | 'astroGists-light';
export type ShikiThemeOrBundledThemeName = BundledShikiTheme | BundledThemeName;
export async function loadTheme(
themes: ShikiThemeOrBundledThemeName[] | undefined
): Promise<ExpressiveCodeTheme[]> {
const shikiThemes = themes && !Array.isArray(themes) ? [themes] : themes
const themesToLoad = (!shikiThemes || !shikiThemes.length) ? ['astroGists-dark', 'astroGists-light'] : shikiThemes;
const loadedThemes = await Promise.all(
themesToLoad.map(async (theme) => {
if (theme === 'astroGists-dark' || theme === 'astroGists-light') {
const bundledTheme = theme === 'astroGists-dark' ? houstonDark : nightOwlLight;
return customizeBundledTheme(ExpressiveCodeTheme.fromJSONString(bundledTheme));
}
return await loadShikiTheme(theme as BundledShikiTheme);
})
);
return loadedThemes;
}
function customizeBundledTheme(theme: ExpressiveCodeTheme) {
return theme;
}

View File

@ -1,6 +1,6 @@
---
import { getGistFile } from "../utils"
import CodeBlob from "./CodeBlob.astro"
import { getGistFile } from "../lib"
import { Code } from "../ExpressiveCode"
export interface Props {
/** REQUIRED: Used to define the desired GitHub Gist ID */
@ -26,7 +26,7 @@ const Gist = await getGistFile( gistId, filename);
---
{ Gist &&
<CodeBlob
<Code
wrap={WRAP} showLineNumbers={SLN}
title={Gist.filename} raw_url={Gist.raw_url}
code={ Gist.content ? Gist.content : "" }

View File

@ -1,6 +1,6 @@
---
import { getGistGroup } from "../utils"
import CodeBlob from "./CodeBlob.astro";
import { getGistGroup } from "../lib"
import { Code } from "../ExpressiveCode";
export interface Props {
/** REQUIRED: Used to define the desired GitHub Gist ID */
@ -29,7 +29,7 @@ const files = Gist.files;
{Object.keys(files).map((file) => {
const { content, filename, language, raw_url } = files[file];
return (
<CodeBlob
<Code
title={filename} wrap={WRAP}
code={content} raw_url={raw_url}
lang={language.toLowerCase()}

View File

@ -1,3 +1,19 @@
// Export Integration
import astroGist from "./integration";
export default astroGist;
// Export the user config schema
import { z } from "astro/zod";
import type { ShikiThemeOrBundledThemeName } from "./ExpressiveCode/theming"
export const optionsSchema = z.object({
/**
* Optional: Allows the user to change the default theme for the code blocks.
* @default ['astroGist-dark','astroGist-light']
*
* All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes).
*/
theme: z.custom<ShikiThemeOrBundledThemeName[]>().optional(),
});
export type astroGistsUserConfig = z.infer<typeof optionsSchema>

View File

@ -1,37 +1,10 @@
import { defineIntegration, createResolver } from "astro-integration-kit"
import { corePlugins } from "astro-integration-kit/plugins"
import { z } from "astro/zod";
import { loadEnv } from "vite";
import { type BundledShikiTheme } from 'expressive-code'
import { isThereAToken, TOKEN_MISSING_ERROR } from "./lib"
import { optionsSchema } from "./index"
// Load environment variables
const { GITHUB_PERSONAL_TOKEN } = loadEnv("all", process.cwd(), "GITHUB_");
export const optionsSchema = z.object({
/**
* Optional: Allows the user to change the default theme for the code blocks.
*
* All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes).
*/
theme: z.custom<BundledShikiTheme>().optional(),
});
export type UserConfig = z.infer<typeof optionsSchema>
/** Astro-Gist - An Astro.js integration for embedding GitHub Gists in your Astro.js project.
* @example
* import { defineConfig } from "astro/config";
* import astroGist from "@matthiesenxyz/astro-gists";
*
* export default defineConfig({
* integrations: [
* astroGist({
* // Optional: Change the default theme for the code blocks.
* // Default: `Astro Houston (Custom)` If you want to use the default theme, you can omit this option.
* theme: "github-dark"
* })
* ]
* });
/**
* Astro-Gist - An Astro.js integration for embedding GitHub Gists in your Astro.js project.
*/
export default defineIntegration({
name: "@matthiesenxyz/astro-gists",
@ -51,9 +24,7 @@ export default defineIntegration({
watchIntegration(resolve())
// Check for GITHUB_PERSONAL_TOKEN
if (!GITHUB_PERSONAL_TOKEN) {
configSetup.warn("GITHUB_PERSONAL_TOKEN not found. Please add it to your .env file. Without it, you will be limited to 60 requests per hour.")
}
if (!isThereAToken) {configSetup.warn(TOKEN_MISSING_ERROR)}
// Add virtual imports
addVirtualImports({

View File

@ -0,0 +1,30 @@
import { requestRetry } from './octokit';
// Get a Gist by ID
export const getGist = async (gistId: string) => {
/** @ts-ignore-error */
const { data: response } = await requestRetry('GET /gists/{gist_id}', { gist_id: gistId });
const data = response as {
// biome-ignore lint/suspicious/noExplicitAny: we don't know the shape of the data returned from the API
files: any; data: unknown
};
return data;
};
// Get a file from a Gist by ID and filename
export const getGistFile = async (
gistId: string,
filename: string
) => {
const gist = await getGist(gistId);
if (gist?.files) {
const file = gist.files[filename];
return file ? file : null;
}
return null;
};
export const getGistGroup = async (gistId: string) => {
const gist = await getGist(gistId);
return gist;
};

2
package/src/lib/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./gist-processor";
export * from "./octokit";

View File

@ -5,11 +5,20 @@ import type { Route, RequestParameters, OctokitResponse } from "@octokit/types"
// Load environment variables
const { GITHUB_PERSONAL_TOKEN } = loadEnv("all", process.cwd(), "GITHUB_");
export const isThereAToken = () => {
if (!GITHUB_PERSONAL_TOKEN) {
return false;
}
return true;
}
export const TOKEN_MISSING_ERROR = "GITHUB_PERSONAL_TOKEN not found. Please add it to your .env file. Without it, you will be limited to 60 requests per hour.";
// Create an Octokit instance
export const octokit = new Octokit({ auth: GITHUB_PERSONAL_TOKEN });
const octokit = new Octokit({ auth: GITHUB_PERSONAL_TOKEN });
// Retry requests if rate limited
async function requestRetry(route: Route, parameters: RequestParameters) {
export async function requestRetry(route: Route, parameters: RequestParameters) {
try {
const response: OctokitResponse<unknown, number> = await octokit.request(route, parameters);
return response;
@ -32,32 +41,3 @@ async function requestRetry(route: Route, parameters: RequestParameters) {
return Promise.reject(error);
}
}
// Get a Gist by ID
export const getGist = async (gistId: string) => {
/** @ts-ignore-error */
const { data: response } = await requestRetry('GET /gists/{gist_id}', { gist_id: gistId });
const data = response as {
// biome-ignore lint/suspicious/noExplicitAny: we don't know the shape of the data returned from the API
files: any; data: unknown
};
return data;
};
// Get a file from a Gist by ID and filename
export const getGistFile = async (
gistId: string,
filename: string
) => {
const gist = await getGist(gistId);
if (gist?.files) {
const file = gist.files[filename];
return file ? file : null;
}
return null;
};
export const getGistGroup = async (gistId: string) => {
const gist = await getGist(gistId);
return gist;
};

View File

@ -1,4 +1,4 @@
declare module "virtual:astro-gists/config" {
const Config: import("./src/integration").UserConfig;
const Config: import("./src/index").astroGistsUserConfig;
export default Config;
}