dave izon
dave izon

Reputation: 81

Tailwind not being applied to library

I have created a library in angular which is styled using tailwind. This is then been push to NPM and then imported into a new project, but the css is not getting applied. I have referenced the node-module path in my tailwind.config.ts:

    content: [
    "./src/**/*.{html,ts}",
    './node_modules/components-name/**/*.{html,js,ts}'
  ],

What am i missing?

Tailwind is working if i apply it directly to the new application, it just doesn't work with the imported library.

Upvotes: 8

Views: 9219

Answers (3)

btx
btx

Reputation: 2487

Simplest solution that's proved working with Angular 16 (2023)

It works for HTML classes aswell as @apply.

Create another tailwind.config.js in your library root, e.g. ./projects/ngx-lib/tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
   content: ['./projects/ngx-lib/src/**/*.{html,ts}'],
};

The problem is, that we can not scope all Tailwind base-styles out of the box, so we need to fix this.

Create projects/ngx-tib/src/theme/tailwind-setup.scss and put in

@tailwind base;
@tailwind components;
@tailwind utilities;

Also create projects/ngx-tib/src/theme/tailwind.scss.

In your lib.component.scss, add:

ngx-lib {
   @import "../theme/tailwind";
}

In your package json, add this script and always run it before building the library:

"build:lib": "npm run build:lib:tailwind && ng build ngx-lib",
"build:lib:tailwind": "tailwind -c projects/ngx-lib/tailwind.config.js -i projects/ngx-lib/src/theme/tailwind-setup.scss -o projects/ngx-lib/src/theme/tailwind.scss",

In the end, the SCSS compiler will do the prefixing for us.

Development:

If you are importing the library to your demo app during development, you will need to add the library paths to the root project's tailwind.config.js:

content: [
   './src/**/*.{html,ts}',
   './projects/ngx-lib/src/**/*.{html,ts}'
],

Upvotes: 4

Tovar
Tovar

Reputation: 455

It's actually very easy, no need for precompiler hacks

Got this solution from the Tailwind docs, which suggest using the npx tailwind build tool (instead ofnpx tailwindcss-cli, like other solutions on the Internet suggest).

Step 1

In your library folder, have the Tailwind config content as if the origin is the base project folder:

module.exports = {
  content: [
    './projects/my-lib/**/*.{html,ts,css,scss}',
    './**/*.{html,ts,css,scss}',
  ],
};

Step 2

In step 3, a tailwind.scss file will be generated into the my-lib/src/lib folder, so (as suggested here) you have to include the tailwind.scss file in your component:

styleUrls: ['../tailwind.scss'].

(Careful with the path)

Step 3

Run this command

npx tailwindcss@latest -c ./projects/my-lib/tailwind.config.js -o ./projects/my-lib/src/lib/tailwind.scss 

before building the library. package.json

{
  "scripts": {
    "build:my-lib": "npx tailwindcss@latest -c ./projects/my-lib/tailwind.config.js -o ./projects/my-lib/src/lib/tailwind.scss & ng build my-lib"
  }
}

Upvotes: 3

Charly
Charly

Reputation: 1139

If you expect all depender apps to utilize tailwind, you can use tailwind classes in your library HTML and have them configure a content path of ./node_modules/my-lib/esm2020/**/*.mjs. It finds the inlined/escaped classes in the Ivy compiled files. esm2020 to scope the scan.


Update 11/30/22 - allowing the use of @apply in the library

@applys are not resolved in precompiled library code as these files are not processed in that lifecycle. As a workaround, you can pre-process your components to resolve @apply styles before building the library.

  1. Create a tailwind.config.js to use in the compilation
    • If your library project has a demo-app (highly suggest for impl testing), could utilize it's config file, unless you've got some crazy config in there. Since we're not rendering @tailwind components or anything, we won't get any excess styles

projects/my-lib/tailwind.config.js

module.exports = {
  content: [
    './projects/my-lib/**/*.{html,ts,css,scss}',
  ],
};

Note the content path is still relative from project root as that's the context it's ran at

  1. Create precompiler process
    • Tailwind resolve into a new file (mostly so we don't mess things up accidentally locally)
    • Point component at the new file
import { readFile, writeFile } from "fs";
import { sync } from 'glob';
import { exec } from 'child_process';

const libRoot      = 'projects/my-lib/src/lib';
const tailwindConf = 'tailwind.config.js'; // may be apps/demo when using NX
const processedExt = '.precompiled.scss';
const styleRegex   = /styleUrls:\s*\[([^\]]+)]/;

// Find all `.scss` files and tailwind process them
sync(`${libRoot}/**/*.component.scss`).forEach(file => {
  const cssFile = file.replace(/\.scss$/, processedExt);
  exec(`npx tailwind -c ${tailwindConf} -i ${file} -o ${cssFile}`, (err, stdout, stderr) => {
    if (err) {
      console.error(stderr);
      throw err;
    }
  });
});

// .component.ts update
// Find all components with `styleUrls` and switch `.scss` extension to our precompiled file names
sync(`${libRoot}/**/*.component.ts`).forEach(file => {
  readFile(file, (err, data) => {
    if (err) throw err;
    const content = data.toString();
    const match = content.match(styleRegex);
    if (match) {
      const styleUrls = match[1]
        .split(',')
        .map(s => s.trim().replace('.scss', processedExt))
        .join(', ');

      writeFile(file, content.replace(styleRegex, `styleUrls: [${styleUrls}]`), (err) => {
        if (err) throw err;
      });
    }
  });
});

This should only be ran by your CI process and never committed. Also this could easily be switched to javascript instead of typescript

Other possible ways to do this (untested) without the .component.ts update:

  • Utilize environment.prod.ts's production: true flag to decide the style file to use
    • styleUrls: [ environment.prod ? 'my.component.precompiled.scss' : 'my.component.scss' ],
    • Gotta remember this for all new components
  • Change the tailwind compile to output to the same scss file
    • Less moving parts - I liked the separate file so I'd realize quickly if it were accidentally ran/committed
  1. Add CI precompile command to package.json
    • "build:ci": "node --require ts-node/register projects/my-lib/src/precompile.ts && npm run build:my-lib"
      • Very rough implementation - remove --require ts-node/register if converted to javascript

I use NX workspace, so I added a new target in the library's project.json:

"ci": {
  "executor": "nx:run-commands",
  "options": {
    "command": "node --require ts-node/register libs/my-lib/src/precompile.ts"
  }
},

and added a the package.json entry as:

"build": "nx run-many --all --target build",
"build:ci": "npx nx ci && npm run build",

allowing build to still be used locally.

  1. Build and Package/Release as normal
    • With @apply's resolved, all should flow well
    • If you used tailwind utility classes in HTML, be sure to see the very beginning of this answer

Tailwindless Depender

If you want applications to be able to utilize your library without them installing tailwind you could supply a stylesheet containing all the helper classes you used.

  1. Create a stylesheet to contain all used utilities

projects/my-lib/style.scss

@tailwind utilities;
  1. Add a postbuild to your package.json to produce the stylesheet, assuming you use npm run build to build the library.
    "postbuild": "npx tailwind -c projects/my-lib/tailwind.config.js -i projects/my-lib/style.scss -o dist/my-lib/style.scss",
  1. Direct depender projects to then include this compiled stylesheet:
@import 'my-lib/style.scss'

Note tailwind does not compile SCSS into CSS - need to run through a SASS processor if you want to supply CSS.

Downside of this is all utility classes used in all components are produced, even if the depender app doesn't use them (same happens for projects using tailwind, so not so bad). Also the depender project may produce duplicate utility classes if using tailwind itself.

Plus side is your library doesn't require the depender to have tailwind.

Note that you still need the above process to resolve @apply's - this only gathers the utility classes used in the HTML

Upvotes: 8

Related Questions