Include assets in your NPM package with your favorite bundler!
Give us a star if you like this!
-
Automatic: Automatically bundle assets into your NPM package, no more making one file for each single asset file.
-
Metadata: Get the metadata of the asset (e.g. file name, file length, image width, height, etc.) without requesting the actual file (these data are written into the code when bundling).
-
Universal: Use the dist in most bundlers or frameworks (listed below).
-
Elegant: Include assets in your NPM package with just a few lines of code and use them in your source code easily.
-
Tree-Shaking: You can add assets as much as you want, and the consumer will only keep what they need.
-
Online CDN: you can optionally create a single site specially for assets, throw away the resources when bundling your application, and always fetch the resources from your CDN site when running the application.
Supported Library Bundlers to make the library with Assestant: Rollup, ... (more coming soon)
Supported Website Builders you can use the library made with Assestant: Vite, Next.js, Docusaurus (Docusaurus may need a few special configs when resolving SVG assets), ... (more coming soon)
This project is still in a stage of early development, so don't give expectation too high, and if there's something wrong, please report!
You may not always need Assestant when building your NPM package.
If you are building a website, or a web application, you may not need Assestant. You can use the bundler's built-in asset handling feature to include assets in your project. Only use Assestant when you are building a package that will be used by others.
If you are using CommonJS, instead of ESM, you may not need Assestant. Assestant is designed for ESM, and it may not work well, or even necessary, in CommonJS.
Additionally, Assestant has a certain amount of limitation, if your library or consumers are unable to meet these requirements, I guess you have to give up Assestant (or you can help us to break the limitations if possible).
The stuff in this section is kinda long, which includes the problem we have, the solution we provide, and the reason why we created this tool. If you're not interested in this, you can skip to the next section.
Have you ever tried to add assets (especially images, fonts and static files) to your NPM package? We have encountered this problem - when we're making our React control library, we may sometimes need images or icons to be included in the package. That is absolutely not as easy as it seems. We usually use Vite, Next.js and Docusaurus (the last two are based on Webpack) to build our final website / applications. If you have ever used these tools, you may know that there are a little bit of differences when coming to asset handling.
Let's use Vite and Next.js as examples. If you want to include an image in your package with Vite, you may need to import it in your source code like this:
import image from "path/to/image.png";
export const ImageComponent: React.FunctionComponent = () =>
{
return <img src={image} alt="Image" />;
};
As you can see, the image is imported as a module, and the default export is the URL of the image. In Next.js, you may use the same way to import the image, but things get unusual when using the imported image. The default export is no longer even a string, but an object. You may need to use image.src
to get the URL of the image like this:
import image from "path/to/image.png";
export const ImageComponent: React.FunctionComponent = () =>
{
return <img src={image.src} alt="Image" />;
};
Now you may wonder, that shouldn't be a big deal, right? Yeah I admit that you're right, but only if you are building just a website. When it comes to building a library that you don't know which bundler or framework the user will use, things get complicated. You may need to provide different ways to import the image, and distribute multiple branches of the package. Obviously, that sucks!
Initially, we used a middleware to handle the assets, for example, I have an image in public/icons/products/contoso.png
in my control library, I would create a file named public/icons/products/contoso.png.ts
(or js, whatever) in the same directory, and write the following code in it:
import ASSET from "../../public/icons/products/contoso.png"; // Rollup can only resolve relative paths
export default resolveAsset(ASSET);
function resolveAsset(asset: unknown): string
{
if (typeof asset === "string")
{
return asset;
}
if (typeof asset === "object")
{
if (typeof asset.src === "string") return asset.src;
if (typeof asset.default === "string") return asset.default;
}
throw new Error("Invalid asset type. There's nothin I can do with it.");
}
It's a good idea to do things like this (if we put the resolveAsset function in a shared file), but it's still not perfect. We need to create a file for each asset, and in this way we can't get the metadata of the asset (e.g. the width and height of an image, file name, length, etc.). So does that mean there's NO way to include assets in an NPM package elegantly?
Personally, I believe I'm not a man who gives up easily. I've been thinking about this for a long time, and finally, I came up with an idea - why not use a middleware, but more powerful and automatic? That's why I created Assestant. By parsing this name - Assets + Assistant, you may know what it does. It's an assistant that helps you to include assets in your NPM package, and it's powerful enough to handle the assets automatically. You don't need to create a file for each asset, and you can get the metadata of the asset easily. That's what Assestant does.
You can use Assestant with your favorite bundler (currently we only do Rollup, but we will support more bundlers in the future). It's easy to use, and it's powerful. You can include assets in your NPM package with just a few lines of code. And the dist can be used in most bundlers or frameworks.
There's no silver bullet in the world. Assestant is not perfect, and it has some limitations. For library builders (who build libraries that will be used by others), you may need to know these limitations before using Assestant:
-
ESM Recommeded: As mentioned above, Assestant is designed for ESM, we've not tested it in CommonJS. Additionally, to import the asset outside, you need to modify the
exports
field inpackage.json
to include the asset. This may not work well in CommonJS. -
Always Preserve Modules: Assestant will always preserve the folder structure of the asset. This is very important for tree-shaking. If everything are bundled into one single file, the tree-shaking may longer work (consumers will have to dist all the assets in your library even if they only use a tiny amount of them).
There are also a few limitations for comsumers who uses libraries made with Assestant:
-
Tree-shaking: The build tool of the final website is better to support tree-shaking, libraries made with Assestant usually includes all the assets anyone may need, but the final dist of your website should only contain the assets you really used.
-
Import Statement: Assestant can only import an asset with a ES import statement like
import ASSE from "../../a.png"
. It doesn't matter what type the imported ASSE variable is (mostly Assestant runtime will handle this correctly, but if you are using a build tool that doesn't work with Assestant, please submit an issue and we'll add support), but your build tool must support this pattern to use libraries made with Assestant.
Assestant needs your help to adapt more bundlers and build tools.
-
If you are using a bunder or build tool that is not supported by Assestant, please tell us right away by submitting an issue, and we will add its support as soon as possible! Our goal is to make Assestant work with every bundler.
-
PRs are always welcomed!