
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

This project is a dependency-free library that aims to provide a way to correctly read, format, structure, and display song lyrics.
Note: Support for fetching lyrics using a URL has been removed because it only supported public URLs.
Therefore, instead of refactoring to handle requests to private APIs, I opted to support only files. If a request is necessary, you can make it externally and then pass the returned file to Kashi!
The project is also exported using UMD, which means that all variables and classes such as the Kashi class is exposed globally, and can be used in any script imported after the library.
Note: You should change
X.Y.Zto the library version number according to your needs, this is just an example. It is recommended to always use the latest version.It is recommended that you pin to the latest stable version of the lib. However, if you always want to use the latest version, you can also use
latestinstead of a predefined version number. Be careful though, breaking changes may be introduced and your project may need to adapt to the changes.
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="https://unpkg.com/kashi@X.Y.Z/kashi.js" defer></script>
<!-- ... -->
</head>
<body>
<div id="kashi"></div>
</body>
</html>
"use strict";
new Kashi({
files. // Loaded from some input[type="file"] or anywhere else
container: document.getElementById("kashi"),
});
The HTML is very similar to the classic scripts version, just change the .js extension to .mjs (and maybe will be necessary to adds a type="module" too), which will result in something similar to this:
Note: Please read the previous section to get the details involved in importing and choosing a package version, they are the same here.
<script
src="https://unpkg.com/kashi@X.Y.Z/kashi.mjs"
type="module"
defer
></script>
import { Kashi } from "kashi";
// Usage is essentially the same as in the previous section
new Kashi({
files. // Loaded from some input[type="file"] or anywhere else
container: document.getElementById("kashi"),
});
Install the lib using your favorite package manager.
npm install kashi
Since the library was designed primarily to be used with vanilla JS, a helper component needs to be created to encapsulate Kashi's behavior and make it simple to reuse throughout the application.
Note: There is TypeScript support, and even if your project doesn't use the JS superset, it should help VSCode and other editors provide autocomplete/code suggestions.
import { memo, useEffect, useRef } from "react";
import { Kashi, KashiProps } from "kashi";
import { api } from "@/services/axios";
// Example using Vite, React and TypeScript
export const KashiWrapper = memo(
(props: Omit<KashiProps, "container"> & { url: string }) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
async function loadKashi() {
if (!ref.current) {
return;
}
if (!props.url) {
ref.current.innerHTML = "";
return;
}
try {
const { url, ...rest } = props;
const response = await api.get(url, { responseType: "blob" });
new Kashi({
...rest,
files: [response.data],
container: ref.current,
});
} catch (error) {
console.error("Error loading Kashi:", error);
}
}
loadKashi();
return () => {
// Required to avoid duplication when React is in Strict Mode
if (ref.current) {
ref.current.innerHTML = "";
}
};
}, [ref.current, props]);
return <div className="kashi-wrapper" ref={ref} />;
},
(prevProps, nextProps) => {
function compareArrays(arr1: unknown[], arr2: unknown[]) {
if (arr1.length !== arr2.length) {
return false;
}
return arr1.every((item, index) => item === arr2[index]);
}
function compareObjects(
obj1: Omit<KashiProps, "container">,
obj2: Omit<KashiProps, "container">,
) {
const keys1 = Object.keys(obj1) as Array<
keyof Omit<KashiProps, "container">
>;
const keys2 = Object.keys(obj2) as Array<
keyof Omit<KashiProps, "container">
>;
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = obj1[key];
const val2 = obj2[key];
if (Array.isArray(val1) && Array.isArray(val2)) {
if (!compareArrays(val1, val2)) {
return false;
}
} else if (val1 !== val2) {
return false;
}
}
return true;
}
return compareObjects(prevProps, nextProps);
},
);
You must pass some properties to Kashi to define what lyrics display and where. Here are its specifications:
| Property | Type | Default value | Is required? | Description |
|---|---|---|---|---|
files | Blob[] | - | Yes | Lyrics files |
container | HTMLDivElement | - | Yes | Element where the lyrics will be inserted |
emptyLineText | string | ... | No | Custom text for empty lines of the lyrics |
noLyricsText | string | - | No | Custom text for when there are no lyrics |
The div#kashi represents the container passed to Kashi where the song lyrics will be inserted.
Each line of lyrics present in the lrc files will be wrapped by a <div></div> tag and inserted into the container.
Here's an example:
<div id="kashi">
<div
data-time="00:17.55"
data-ms-time="17550"
data-empty="false"
data-aria-current="false"
>
<span>Telling myself, "I won't go there"</span>
<br />
<span> Dizendo a mim mesmo, "eu não vou lá" </span>
</div>
<div
data-time="00:21.24"
data-ms-time="21240"
data-empty="false"
data-aria-current="false"
>
<span>Oh, but I know that I won't care</span>
<br />
<span>Oh, mas eu sei que não vou me importar</span>
</div>
<!-- ... -->
</div>
The instance generated by Kashi has some public methods and attributes that can be used to query or change properties on the fly.
| Name | Type | Description |
|---|---|---|
files | Attribute | Returns the files from the current lyrics |
emptyLineText | Attribute | Returns the text set for empty lines |
noLyricsText | Attribute | Returns the text set for when there are no lyrics |
setFiles | Method | Function capable of changing the current lyrics files by passing the the new files |
setEmptyLineText | Method | Function capable of changing the text defined for empty lines |
setNoLyricsText | Method | Function capable of changing the text defined for when there are no lyrics |
subscribe | Method | Function capable of defining a callback to be executed when a given event is triggered |
unsubscribe | Method | Function capable of making a callback to stop listening to an event |
notify | Method | Function capable of triggering an event |
When creating a new instance using Kashi you will have access to the subscribe, unsubscribe and notify methods, these methods can be used respectively to listen for an event, stop listening for an event and manually trigger an event. Below is the list of events triggered internally:
| Event | Data | Trigger |
|---|---|---|
filesSet | { files: Blob[] } | When calling the setFiles method |
emptyLineTextSet | { emptyLineText: string } | When calling the setEmptyLineText method |
noLyricsTextSet | { noLyricsText: text } | When calling the setNoLyricsText method |
lyricLinesUpdated | { lyricLines: string[] } | When inserting/updating lyrics in HTML |
The first step is to clone the project, either via terminal or even by downloading the compressed file (.zip). After that, go ahead.
With the dependencies properly installed, still in the terminal, run npm start.
Create a simple demo project using vanilla HTML/JS and use the files in the dist folder for testing.
You can also create a demo project using React and use npm link.
Now you are running the project beautifully!
This project is under the GPL v3 license. See the LICENSE for more information.
Made with 💙 by lucasmc64 👋 Get in touch!
FAQs
Singing at the top of my lungs
We found that kashi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.