I am writing a react component library with typescript, sass and rollup, and I want it to be as standalone as possible.
Does anyone have a suggestion on how to best include assets (images and fonts) referenced in scss files?
One solution could be some sort of loader (for instance a postcss processor) replacing all image and font assets referenced in scss files with the base64 version.
Does anyone have an example where this has been done in an effective manner? Any solutions or suggestions would be highly appreciated 🙏🏻
My rollup config looks like this:
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "rollup-plugin-node-resolve";
import typescript from "rollup-plugin-typescript2";
import scss from 'rollup-plugin-scss'
import sass from "rollup-plugin-sass";
import commonjs from "rollup-plugin-commonjs";
import copy from "rollup-plugin-copy";
import url from '@rollup/plugin-url';
import packageJson from "./package.json";
export default {
input: "src/index.tsx",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true
},
{
file: packageJson.module,
format: "esm",
sourcemap: true
}
],
plugins: [
peerDepsExternal(),
resolve({
browser: true
}),
typescript({ objectHashIgnoreUnknownHack: true }),
commonjs({
include: ["node_modules/**"],
exclude: ["**/*.stories.js"],
namedExports: {
"node_modules/react/react.js": [
"Children",
"Component",
"PropTypes",
"createElement"
],
"node_modules/react-dom/index.js": ["render"]
}
}),
scss({
}),
sass({
insert: true
}),
copy({
targets: [
{
src: "src/variables.scss",
dest: "build",
rename: "variables.scss"
},
{
src: "src/typography.scss",
dest: "build",
rename: "typography.scss"
},
{
src: "src/assets",
dest: "build/",
},
]
})
]
};
UPDATE:
So what I did might be hacky, but it solved my problem.
I added a postcss-plugin in the plugins array in rollup.config.js, after a commonjs plugin.
postcss({
inject: true,
plugins: [
postcssInlineBase64({
baseDir: 'src/assets/',
}),
postcssUrl({
url: 'inline',
}),
atImport({
path: path.resolve(__dirname, '../'),
}),
],
}),
I also use storybook, which internally uses webpack, so I had to recreate the same in .storybook/webpack.config.js:
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
inject: true,
ident: 'postcss',
plugins: [
postcssInlineBase64({
baseDir: 'src/assets/',
}),
postCssUrl({ url: 'inline' }),
atImport({
path: path.resolve(__dirname, '../'),
}),
],
},
},
'sass-loader',
],
include: path.resolve(__dirname, '../'),
});
Now, when using the url directive in scss (or elsewhere probably), I can surroud any path with:
b64---<SOME_PATH>---
For instance:
@font-face {
font-family: 'Open Sans';
font-display: swap;
font-style: normal;
font-weight: 300;
src: url('b64---./fonts/open-sans/open-sans-v15-latin-ext_latin-300.woff2---') format('woff2'),
url('b64---./fonts/open-sans/open-sans-v15-latin-ext_latin-300.woff---') format('woff');
}
which makes post css bundle the asset as base64.
To anyone who might come across this post. Good luck! Hope this helps!
Got this to work using postcss-url asset function, with real image files instead of base64.
The idea is that the package extracts CSS into a separate file, and all the referenced assets in url()
are intercepted, hashed and put in a folder (_assets
), then postcss-url changes the original url into that folder.
Consumer apps (e.g. webpack with a tool like css-loader
) import the CSS
import 'pkg-name/dist/index.css'
where all url()
s look like background-image: url(_assets/<hash>.png)
and that loader is also responsible for bringing those assets into the bundle, and replace the url()
to a local url with the public path, e.g. url(/static/<app-hash>.png)
.
It does not use rollup built in emitFile
due to inability to access the plugin instance from that callback, although that would be ideal.
import fs from "fs-extra";
import path from "path";
import hasha from "hasha";
const IMAGES_RX = /\.(png|jpe?g|gif|webp|svg)$/;
// rollup config
plugins: [
postcss({
extract: true,
plugins: [
// extracts all url() assets into _assets folder, and replaces the url() to a relative path
// consumers of this package (e.g. webpack apps) will import the css and handle getting assets as well
postcssUrl({
url: (asset: any) => {
if (!IMAGES_RX.test(asset.url)) return asset.url;
const file = fs.readFileSync(asset.absolutePath);
const hash = hasha(file, { algorithm: "md5" });
const extname = path.extname(asset.absolutePath);
const hashedFileName = `${hash}${extname}`;
fs.ensureDirSync(path.join(OUT_DIR, "_assets"));
const hashedFilePath = path.join("_assets", hashedFileName);
fs.writeFileSync(path.join(OUT_DIR, hashedFilePath), file);
return hashedFilePath;
},
}),
],
}),
]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With