Search code examples
sveltekit

How to properly export & implement non-svelte-components (tailwind config, base css) in a Svelte Component Library


I've built a Svelte component library using ShadCN-Svelte. I'm in the process of extracting it out from the application where I first started tinkering into its own repository so that it can be installed and used in multiple applications.

I created a new repo for the library. For simplicity let's say my company's name is XYZ and the design system is called ABC, so I'm creating @XYZ/ABC. I created the project using npm create svelte@latest ABC, selecting the option for a component library (as opposed to an app).

Error message

npm run build of an app importing/using the design system fails with this error message:

[vite:css] [postcss] /app/node_modules/@XYZ/ABC/dist/ABC.pcss:2:1: The border-border class does not exist. If border-border is a custom class, make sure it is defined within a @layer directive.

What I know for sure

This error started happening when I added import '@XYZ/ABC/ABC.pcss'; to the root layout of the application. I added this line in an attempt to pull in the ShadCN CSS custom properties (a good portion of the theme work on the component library) as well as some custom CSS I've added around it.

The border-border reference in the error message is pretty clearly a reference to the CSS created in Step 5 of ShadCN-Svelte setup and the color named "border" in step 4.

Things I've tried

  1. I had to add "./ABC.pcss": "./dist/ABC.pcss", to package.json exports (see package.json below) before the app would recognize and import the pcss file.
  2. Since it appears that border-border is not recognized, my theory is that the tailwind configuration is not loaded (it's not aware of the color named border in the tailwind config). I've been trying to figure out the best way to merge my design system's tailwind config into the app's tailwind config. I copied my tailwind.config.ts into src/lib so that it will be available in the dist folder after build+publish, and then tried to import and extend it from the app's tailwind config like so:
import type { Config } from 'tailwindcss';
import abcConfig from '@XYZ/ABC/dist/tailwind.config.js';

const config = Object.assign({}, abcConfig, {
    darkMode: 'class',
    content: ['./src/**/*.{html,js,svelte,ts}'],

    theme: {
        extend: {}
    },

    plugins: []
}) satisfies Config;

export default config;

It's worth noting that something's weird with the pathing for my newly exported tailwind.config.js (built from a .ts)... Note the two ways I'm exporting it in package.json below. Regardless of that configuration, if my import line does not have the /dist/ then VSCode/TS show the error:

Cannot find module '@XYZ/ABC/tailwind.config.js' or its corresponding type declarations. There are types at '/Users/atcodes/DEV/XYZ/my-app/node_modules/@XYZ/ABC/dist/tailwind.config.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'. ts(2307)

... However, the app build (npm run build) seems to accept this just fine (both with and without /dist/). Before I added those two exports to package.json, I was getting the build-time error:

[vite:css] [postcss] Package subpath './dist/tailwind.config.js' is not defined by "exports" in /app/node_modules/@XYZ/ABC/package.json

So to the best of my understanding, the build is behaving correctly and TS/VSCode are misreporting that error.

However, this has not fixed the border-border issue. I'm still getting the same border-border error message at build time, so now I'm stumped.

Update: One last thing that I tried: Instead of importing/merging it, if I just copy the tailwind config from the design system into the app, the build errors are resolved; so it's definitely something to do with the tailwind config.

package.json

{
    "name": "@XYZ/ABC",
    "version": "0.0.1-alpha.11",
    "license": "UNLICENSED",
    "scripts": {
        "dev": "vite dev",
        "build": "vite build && npm run package",
        "preview": "vite preview",
        "package": "svelte-kit sync && svelte-package && publint",
        "prepublishOnly": "npm run package",
        "test": "npm run test:integration && npm run test:unit",
        "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
        "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
        "lint": "prettier --check . && eslint .",
        "format": "prettier --write .",
        "test:integration": "playwright test",
        "test:unit": "vitest"
    },
    "exports": {
        ".": {
            "types": "./dist/index.d.ts",
            "svelte": "./dist/index.js"
        },
        "./ABC.pcss": "./dist/ABC.pcss",
        "./tailwind.config.js": "./dist/tailwind.config.js",
        "./dist/tailwind.config.js": "./dist/tailwind.config.js"
    },
    "files": [
        "dist",
        "!dist/**/*.test.*",
        "!dist/**/*.spec.*"
    ],
    "peerDependencies": {
        "svelte": "^4.0.0"
    },
    "devDependencies": {
        "@internationalized/date": "^3.5.1",
        "@playwright/test": "^1.28.1",
        "@sveltejs/adapter-auto": "^3.0.0",
        "@sveltejs/kit": "^2.0.0",
        "@sveltejs/package": "^2.0.0",
        "@sveltejs/vite-plugin-svelte": "^3.0.0",
        "@types/eslint": "8.56.0",
        "@typescript-eslint/eslint-plugin": "^6.0.0",
        "@typescript-eslint/parser": "^6.0.0",
        "autoprefixer": "^10.4.16",
        "clsx": "^2.1.0",
        "cmdk-sv": "^0.0.13",
        "eslint": "^8.56.0",
        "eslint-config-prettier": "^9.1.0",
        "eslint-plugin-svelte": "^2.35.1",
        "formsnap": "^0.4.2",
        "lucide-svelte": "^0.314.0",
        "postcss": "^8.4.33",
        "postcss-load-config": "^5.0.2",
        "prettier": "^3.1.1",
        "prettier-plugin-svelte": "^3.1.2",
        "prettier-plugin-tailwindcss": "^0.5.9",
        "publint": "^0.1.9",
        "svelte": "^4.2.7",
        "svelte-check": "^3.6.0",
        "tailwind-merge": "^2.2.1",
        "tailwind-variants": "^0.1.20",
        "tailwindcss": "^3.4.1",
        "tslib": "^2.4.1",
        "typescript": "^5.0.0",
        "vite": "^5.0.11",
        "vitest": "^1.2.0"
    },
    "svelte": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "type": "module",
    "dependencies": {
        "bits-ui": "^0.14.0",
        "md5-hex": "^5.0.0",
        "mode-watcher": "^0.1.2",
        "svelte-sonner": "^0.3.11"
    }
}

Solution

  • The problem was that my Tailwind.config.ts (as converted to .js by the SvelteKit build step) would not run in the consuming application.

    Given that the problem went away when I copied the tailwind config into the app rather than trying to import it, I figured there had to be some problem with the import. I added a test.js file to my app and put this code in it:

    import config from '@XYZ/ABC/dist/tailwind.config.js';
    console.log(config);
    

    Running this started pointing out problems:

    Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/atcodes/DEV/XYZ/my-app/node_modules/tailwindcss/defaultTheme' imported from /Users/atcodes/DEV/XYZ/my-app/node_modules/@XYZ/ABC/dist/tailwind.config.js Did you mean to import tailwindcss/defaultTheme.js?

    💡 Right! SvelteKit allows us to import from extension-less files, but this doesn't work out of the box. I updated my library to add the recommended .js suffix, then got this error:

    file:///Users/atcodes/DEV/XYZ/my-app/node_modules/@XYZ/ABC/dist/tailwind.config.js:1
    import { fontFamily } from 'tailwindcss/defaultTheme.js';
             ^^^^^^^^^^
    

    SyntaxError: Named export 'fontFamily' not found. The requested module 'tailwindcss/defaultTheme.js' is a CommonJS module, which may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, for example using:

    import pkg from 'tailwindcss/defaultTheme.js';
    const { fontFamily } = pkg;
    

    I took that advice and updated the tailwind.config.ts file to import fontFamily that way:

    import pkg from 'tailwindcss/defaultTheme.js';
    const { fontFamily } = pkg;
    

    And now it imports correctly and all works as intended.