23

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!

Mahus
  • 595
  • 1
  • 6
  • 16
  • So, I ended up using a postcss loader, to bundle the assets, fonts and images, as base64 code. This is approx. 33% less effective than actual files, however, it improves ease of use, since the consuming party does not have to include any other assets, and can simply import the component from the package, done deal. – Mahus Nov 03 '20 at 00:37
  • Can you post the code snippet including the NPM package you used to solve this please @Mahus? I'm looking to base64 encode fonts included in my SCSS files. – JS_Dev Nov 26 '20 at 11:58
  • Here you go @JS_Dev! Let me know if this example is sufficient! – Mahus Dec 04 '20 at 10:44
  • Hi @Mahus, Thanks for posting the answer, I have the same issue, tried to solve it the above way, but the npm module at-import doesn't seem to exist anymore, npm page is there but it does not install or even there's no github link on the page, any work around for that? – Bmax Feb 22 '21 at 10:41
  • Think this might be what you are looking for: postcss-import, it is default exported, so you can name the imported variable as you prefer. https://github.com/postcss/postcss-import Bmax – Mahus Feb 24 '21 at 11:22

1 Answers1

4

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;
        },
      }),
    ],
  }),
]
elado
  • 8,510
  • 9
  • 51
  • 60