shikijs / shiki

A beautiful yet powerful syntax highlighter
http://shiki.style/
MIT License
10.11k stars 370 forks source link

I need a function that only outputs the class #752

Closed XIYO closed 1 month ago

XIYO commented 2 months ago

Clear and concise description of the problem

Light and dark themes are not suitable for environments without JavaScript. If additional styling is needed, custom styles will eventually have to be written.

Suggested solution

When using theme: none, Shiki does not apply inline styles, so it’s necessary to create classes for Shiki. You can then solve styling issues by directly importing the necessary styles:

import 'shiki/theme/~~~.css';

Alternative

highlightjs

Additional context

Hello,

I find Shiki's transform functionality incredibly impressive. However, after using it, I noticed a few areas that could benefit from improvement:

  1. There is no built-in feature to style and display line numbers.
  2. Applying custom styles is somewhat challenging.

This brings me to the following idea:

image

I’ve always felt that a title is necessary for code blocks.

For instance, with syntax like:

data-title-index.js
const a = 1;

When transformed, it could output something like:

<pre data-title="index.js"><code></code></pre>
<style>
  pre::before { content: attr(data-title); }
</style>

This would make it easier to implement such features. Currently, managing styling is difficult due to the need for dual styling.

Although creating classes for theme: none to output seems beyond my current capabilities, I am able to contribute by adding data-set via transform.

Validations

Contributes

fuma-nama commented 2 months ago

Light and dark themes are not suitable for environments without JavaScript. If additional styling is needed, custom styles will eventually have to be written.

Why? Styling with CSS doesn't require JavaScript at all. And you can use @media (prefers-color-scheme: dark) to implement dark mode support without JavaScript.


Also, I think your "Additional context" isn't quite related to your issue:

If you use Shiki to highlight Markdown/MDX code blocks, why don't use parseMetaString from Rehype Shiki to parse meta string and add these properties? (e.g. title)

Properties in the meta object will be added to pre element via data attributes and you can handle that with a custom pre component, or using CSS.

/**
 * Custom meta string values
 */
const metaValues = [
  {
    name: 'title',
    regex: /title="(?<value>[^"]*)"/,
  },
];

export const rehypeCodeDefaultOptions: RehypeCodeOptions = {
  parseMetaString(meta) {
    const map: Record<string, string> = {};

    for (const value of metaValues) {
      const result = value.regex.exec(meta);

      if (result) {
        map[value.name] = result[1];
      }
    }

    return map;
  },
};
XIYO commented 2 months ago

It seems that my initial explanation was a bit complex. To clarify:

I believe it would be better to give users the option to choose inline styles for code, as it provides more flexibility.

Currently, it’s possible to make everything work purely with CSS by creating a theme.json file where you can input values like "breadcrumb.background": "light-dark(#282A36, #181818)". This way, a single theme can support both light and dark modes, potentially being referred to as dracula-light-dark.

Consider this design:

<figure>
  <div>index.js</div>
  <pre><code>~~</code></pre>
  <figcaption>caption</figcaption>
</figure>

When writing code, there are times when you need a title or some element that indicates which file the code belongs to. However, if you want the style to share the same color as <pre>, you would need to declare the CSS separately, using the background color value from the JSON file where the Shiki theme is set.

But if both the code style and the decorative style are declared in an external CSS file (using classes), you can avoid the mistake of forgetting to update the decorative style when changing the Shiki theme.

The final result can still be designed to appear as intended, but I’ve created this issue because I believe it would be helpful to have the option to output styles as classes. If this suggestion doesn’t align with the project’s direction, please feel free to disregard it.

As I’m not a native English speaker, I used GPT to translate my message. If anything is unclear or seems off, please let me know, and I will revise it to be more specific and clear.

fuma-nama commented 2 months ago

Guess you're just looking for:

Same as above, add title to the data attributes of pre with parseMetaString:

/**
 * Custom meta string values
 */
const metaValues = [
  {
    name: 'title',
    regex: /title="(?<value>[^"]*)"/,
  },
];

export const rehypeCodeDefaultOptions: RehypeCodeOptions = {
  parseMetaString(meta) {
    const map: Record<string, string> = {};

    for (const value of metaValues) {
      const result = value.regex.exec(meta);

      if (result) {
        map[value.name] = result[1];
      }
    }

    return map;
  },
};

So it's up to you on how to handle the output hast tree.

With React/Vue, you can use hast-util-to-jsx-runtime to render a custom code block component by overriding pre. It can read the props from pre which solved your problem.

With MDX.js, you can simply override the pre element on MDX components same as using hast-util-to-jsx-runtime.

If you want a full example, I built a codeblock for my projects. You can basically see all props are passed to CodeBlock and handled by itself.

If you only need HTML, maybe a Rehype plugin or Shiki transformer will be required to transform the hast tree. But TLDR; Personally, I would just suggest to use MDX, vanilla React, and Vue

XIYO commented 1 month ago

Since I'm using a translation tool, the meaning might be slightly altered. I strive to convey the content in a straightforward manner, but if the context seems off, please let me know, and I will rephrase it to better communicate my intention.

First of all, thank you for taking an interest in my issue and for your response.

Here’s what I initially intended:

  1. When using the value class:none, I expected that classes would be added to the output (or that styling could be controlled externally).
  2. I wanted a feature where, if a js data-title="hi" value is used in the code block’s meta, it would output as <pre data-title="hi">.

The intention behind point 1 was to control styling externally rather than using inline styles. As for point 2, based on the overall response, it seems that this functionality is already possible, and I was able to achieve the desired result after reading your reply (though I did need to write additional styles, which increased the management points, either by creating a shiki.css or adding code to the existing root styles).

The overall intention of my question was to ask for a feature that allows styling to be managed through a single point by using class-based styling instead of inline styles.

Thank you for reading.

P.S. Since I don’t fully understand how Shiki works internally, I might be making an incorrect request. If my explanation is unclear, I would be happy to illustrate it with diagrams. If my issue is out of line with the direction Shiki is heading, please feel free to close it at any time.

fuma-nama commented 1 month ago

When using the value class:none, I expected that classes would be added to the output (or that styling could be controlled externally).

You can enable dual themes for this, pass defaultColor: false and themes: { light: theme }. Check https://github.com/shikijs/shiki/issues/742

This makes Shiki to add CSS variables instead of inline styles, you can do further styling with it.

XIYO commented 1 month ago

Although it wasn't exactly what I initially requested, I found a way to control the styles from a single CSS file, as mentioned in this documentation: https://shiki.style/guide/theme-colors#css-variables-theme (though it’s not an official feature 🥲).

Thank you for taking the time to respond. Wishing you a great day!