First plugin
Creating your first extension for anisun.
Manifest
Every extensions should have a metadata file, which is also called manifest. You can name it however you want, place it wherever you want, but you must use a JSON format.
Manifest type:
type ManifestType = {
logo: string; // required
name: string; // required
url: string; // required
pages: Array<string>; // required
version: string; // required
author: string; // required
displayName?: string;
areStyles?: boolean;
};
logo
- a link for the extension logo.name
- an extension name.url
- an extension bundled source code link.pages
- an array of custom page names, where plugins will be able to show content.version
- plugin's version.author
- plugin's author(s).displayName
- an extension name, which will be shown in extensions loader. Optional.areStyles
- whentrue
, plugin will be loaded in the app's layout (to apply CSS styles or change UI nodes globally). Optional.
This is an example of Manifest:
{
"logo": "https://anime.tatar/roxy-example-plugin-logo.png",
"name": "css-styles-example-svelte",
"displayName": "Example Svelte Plugin",
"url": "https://raw.githubusercontent.com/notwindstone/anisun-svelte-css-styles-extension/refs/heads/main/dist/bundle.js",
"pages": [],
"version": "semver-1.0.0",
"author": "windstone",
"areStyles": true
}
Key notes
If you are going to make an extension with stylesheets that are gonna apply to the website UI, then you need to add a .garbageCollectorClearMe{display:none;}
class to the top of your css code. This is needed because your plugin will create stylesheets every route change, which might eventually (especially if your stylesheets are big af) fuck user's website performance (until he refreshes the page, but that's still not really convenient).
About root IDs:
extensions-css-loader-id
- loads in the layout, theoretically should always be on every page.extensions-root-id
- exists only on the anime page.extensions-root-page-id
- exists only on the custom pages.
React
Optimizing dependencies
A small problem
WARNING
Shared dependencies are not fully bundled packages. They have only those exported elements, which anisun uses itself. That means a shared React bundle will not have a useId
hook, but will have useState
, useEffect
, useMemo
, etc. If you need something that is not in the shared dependency, consider bundling the whole package to your extension output code.
How to enable optimization
INFO
If you are using a starter kit, then skip this section, because dependency sharing is enabled by default.
First, install a @paciolan/remote-component
package using your package manager.
Second, create a remote-component.config.js
file in the root of your project and fill it with the next content:
/**
* Dependencies for Remote Components
*/
module.exports = {
resolve: {
react: require("react"),
"react-dom/client": require("react-dom/client"),
},
};
Now you just need to load this file in the webpack configuration (webpack.config.js
):
// ...other imports
const remoteComponentConfig = require("./remote-component.config").resolve;
const externals = Object.keys(remoteComponentConfig).reduce(
(obj, key) => ({ ...obj, [key]: key }),
{}
);
module.exports = {
// ...other options
externals: {
...externals,
"remote-component.config.js": "remote-component.config.js",
},
}
How to disable optimization
Comment out or remove your externals
option in the webpack configuration:
// ...other code
module.exports = {
// ...other options
externals: {
// ...externals,
// "remote-component.config.js": "remote-component.config.js",
},
}
Coding
Finally, the actual coding part! Let's make an extension that will get MAL ID from pathname, fetch a trailer data with that ID from Anilist and show the data to user. Also, we will implement a custom page with a simple settings.
To be written
export default function Component() {
// some code...
return (
<div className="react-extension-body" />
);
}
Vue
To be written
<template>
<div class="vue-extension-body">
</div>
</template>
<script setup lang="ts">
// some code
</script>
Svelte
Let's make a style extension that will modify the UI of anisun. I'm gonna use Needy Girl Overdose assets for the styling part.
If you made it until here, this means that you already initialized your project. So I'm gonna clean up some unused code in the starting src/
folder:
.
└─ src
├─ assets
│ └─ svelte.svg
├─ lib
│ └─ Counter.svelte
├─ app.css
├─ main.ts
├─ App.svelte
└─ vite-env.d.ts // auto-generated, don't touch
Now we need to change main.ts
file. There we must remove export default app
keywords, because we are not returning a React component. Instead, we should manually mount into the DOM. But to do so, we must ensure that the mounting element actually exists (to prevent unexpected errors from happening):
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
if (document.getElementById('extensions-css-loader-id')) {
mount(App, {
target: document.getElementById('extensions-css-loader-id')!,
});
}
const app = mount(App, {
target: document.getElementById('app')!,
})
export default app
Let's open App.svelte
. This file by default should have a lot of gibberish code, so remove it and write:
<div class="EXTENSION-NAME-OR-OTHER-UNIQUE-CLASS-svelte-body"></div>
Now, the styling part. I'm gonna open app.css
file, remove everything and make some new styles.
First of all, I add a garbageCollectorClearMe
class at the top of the file.
Now I want to show an image on the website background, considering that anisun has two theme schemes: dark
and light
. As I remember, TailwindCSS (my project relies on it) uses data-*
attributes for this particular thing, but I have changed the dark/light theme implementation some time ago. Because of it, I just need to specify .dark
and .light
classes to handle both themes.
.garbageCollectorClearMe {
display: none;
}
.light {
background: linear-gradient( rgba(255, 255, 255, 0.87), rgba(255, 255, 255, 0.87) ), url("https://cdn.dynamicwallpaper.club/wallpapers/jqc5oqev0br/thumbs/1600/11.jpg");
}
.dark {
background: linear-gradient( rgba(0, 0, 0, 0.87), rgba(0, 0, 0, 0.87) ), url("https://cdn.dynamicwallpaper.club/wallpapers/jqc5oqev0br/thumbs/1600/11.jpg");
}
I tested the website and... the hero card looked kinda out of the place. I decided to hide it for the desktop screens, but show it on the mobile phones with a border to match background and make black to pink transition not quite sharp.
/* previous styles */
.hero__poster-image {
display: none;
@media screen and (max-width: 640px) {
display: block;
}
}
.hero__poster-shadow {
display: none;
@media screen and (max-width: 640px) {
display: block;
border-bottom: 2px solid palevioletred;
}
}
.scrollableCardsShadow {
display: none;
}
Now the plugin is done. Source code is available at github.
Other frameworks
Give it a try yourself without my guide!
Building
Run bun run build
or npm run build
or whatever the command is for your building process.