Javascript NPM Modules
Since v0.9.8, Spicetify injects extension with file extension .mjs
as a script with type="module" and automatically symlink node_modules
folder found in user's Extensions folder to zlink
app.
In Javascript module, Javascript would work just the same as normal script but now you can use import
to include other Javascript files. Click here for details.
Node Package Manager (NPM) is a commandline app bundled with NodeJS. You can it use to download and install hundred of utilities packages to ease your development process. Since Spicetify symlinks node_modules
to Spotify main app folder, all packages files are mutually linked and available for you to use in your extension. Simply just use import
:
import './node_modules/package_name/file.js';
Careful! Javascript file you import has to be supported by Browser, not NodeJS. In other words, that Javascript should not use require
function to include other packages or NodeJS API. Whenever you decide to use a package, check its readme page to see if its author already export distributed file as ES6 Module.
Example​
For Japanese studying purpose, I'm developing an extension that show Romaji form of Japanese track titles or artist names.
The idea is when I right click at track name and choose Show Romaji:
Result should show as a notification:
To translate Japanese text to Romaji, I use a package named kuroshiro. Luckily, this package will export distribution files as ES6 Module. This is quite important because package itself relies on other utilities packages too. When it is complied as ES6 Module, everything is transpiled to Browser supported Javascript and combined in one file. Moreover, kuroshiro also needs kuroshiro-analyzer-kuromoji package to be usable, which relies on dictionaries gzip files. You can see there is no easy way to utiltise both packages and their external files if we use traditional Javascript extension.
Following are steps to make and install this extension from scratch:
- First, change director to user's
Extensions
folder:
- Windows, in Powershell:
cd "$(dirname "$(spicetify -c)")/Extensions"
- Linux and MacOS, in Bash:
cd "$(dirname "$(spicetify -c)")/Extensions"
- Now install needed packages with NPM
npm install kuroshiro kuroshiro-analyzer-kuromoji
Go to user's Extensions
folder, you can see node_modules
folder is created and contains all installed packages files. Next, go to kuroshiro
and kuroshiro-analyzer-kuromoji
folder to locate ES6 Module distribution files.
- After that I can comfortably include both of them in my extension. Following is extension code:
romaji.mjs:
import './node_modules/kuroshiro/dist/kuroshiro.min.js';
import './node_modules/kuroshiro-analyzer-kuromoji/dist/kuroshiro-analyzer-kuromoji.min.js';
const kuroshiro = new Kuroshiro.default();
kuroshiro.init(new KuromojiAnalyzer());
function converter(input) {
return kuroshiro.convert(input, {
to: 'romaji',
mode: 'spaced',
romajiSystem: 'passport',
});
}
const fetchAlbum = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri.split(':')[2]}/desktop`);
return res.name;
};
const fetchShow = async (uri) => {
const res = await Spicetify.CosmosAsync.get(
`sp://core-show/v1/shows/${uri.split(':')[2]}?responseFormat=protobufJson`,
{
policy: { list: { index: true } },
}
);
return res.header.showMetadata.name;
};
const fetchArtist = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`hm://artist/v1/${uri.split(':')[2]}/desktop?format=json`);
return res.info.name;
};
const fetchTrack = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/tracks/${uri.split(':')[2]}`);
return res.name;
};
const fetchEpisode = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/episodes/${uri.split(':')[2]}`);
return res.name;
};
const fetchPlaylist = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/metadata`, {
policy: { name: true },
});
return res.metadata.name;
};
async function showRomaji([uri]) {
const type = uri.split(':')[1];
let name;
switch (type) {
case Spicetify.URI.Type.TRACK:
name = await fetchTrack(uri);
break;
case Spicetify.URI.Type.ALBUM:
name = await fetchAlbum(uri);
break;
case Spicetify.URI.Type.ARTIST:
name = await fetchArtist(uri);
break;
case Spicetify.URI.Type.SHOW:
name = await fetchShow(uri);
break;
case Spicetify.URI.Type.EPISODE:
name = await fetchEpisode(uri);
break;
case Spicetify.URI.Type.PLAYLIST:
case Spicetify.URI.Type.PLAYLIST_V2:
name = await fetchPlaylist(uri);
break;
}
if (Kuroshiro.default.Util.hasJapanese(name)) {
name = await converter(name);
name = name.replace(/(^|\s)\S/g, (t) => t.toUpperCase());
}
Spicetify.showNotification(name);
}
new Spicetify.ContextMenu.Item(`Show Romaji`, showRomaji).register();
Save this file to Extensions
folder.
- Finally, push my extension to Spotify:
spicetify config extensions romaji.mjs
spicetify apply