17

What does it mean to use a plugin to a bundler to perform some work, I mean I have no experience with bundlers whatsoever yet and I wanted to learn about that by creating a "professional" workflow with esbuild and tailwindcss with react, typescript and all the goodies and I am stuck on connecting tailwind css to the eslint and the rest. I know that to run tailwind css the necessary lib to make it work is postcss, I have followed the tailwind css docs which says

npm install -D tailwindcss
npx tailwindcss init

It says nothing about postcss so I assume that esbuild should be reponsible for it, I assume that is has to bedone via plugin, there are two:

https://github.com/karolis-sh/esbuild-postcss npm i postcss esbuild-postcss -D

and

https://github.com/martonlederer/esbuild-plugin-postcss2 npm i -D esbuild-plugin-postcss2

Instalation process of the first one includes postcss and the second one does not, hovewer the second one seems to be newer and kind of "on top of" the first one. The problem is none of them is working... this is my esbuild config:


    const { build } = require("esbuild");
    
    build({
      publicPath: "http://127.0.0.1:7000/",
      entryPoints: ["src/app.tsx", "src/app.css"],
      outdir: "public",
      // external: ["react", "react-dom"], comented out -throws error cannot use import statement outside a module
      loader: {
        ".png": "file",
        ".jpg": "file",
        ".jpeg": "file",
        ".svg": "file",
        ".gif": "file",
      },
      assetNames: "assets/[name]-[hash]", //-[hash]
      chunkNames: "chunks/[name]-[hash]",
      entryNames: "[dir]/[name]", //-[hash]
      splitting: true,
      format: "esm",
      minify: true,
      bundle: true,
      sourcemap: "external",
      // target: ["es2020", "chrome58", "firefox57", "safari11", "edge16", "node12"],
      pure: ["console.log"],
      resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css", ".json"],
      inject: ["./process-shim.js", "./react-shim.js"],
      // watch: {
      //   onRebuild(error, result) {
      //     if (error) console.error("watch build failed:", error);
      //     else console.log("watch build succeeded:", result);
      //   },
      // },
    }).catch((error) => {
      console.error(`Build error: ${error}`);
      process.exit(1);
    });

and this is my package.json file:


    {
      "name": "real-world-app",
      "version": "1.0.0",
      "description": "this is not a package",
      "main": "src/app.js",
      "scripts": {
        "build": "node ./esbuild.config.js",
        "watch": "npm run build -- --watch",
        "start": "npm run css && node ./esbuild.serve.js -w ",
        "lint": "eslint --fix --debug --cache",
        "test": "jest",
        "css": "npx tailwindcss -i ./src/app.css -o ./public/app.css"
      },
      "keywords": [
        "conduit"
      ],
      "license": "ISC",
      "repository": {
        "type": "git",
        "url": "https://github.com/dziekonskik/real-world-app"
      },
      "dependencies": {
        "esbuild": "^0.14.2",
        "esbuild-darwin-64": "^0.14.2",
        "react": "^17.0.2",
        "react-dom": "^17.0.2"
      },
      "devDependencies": {
        "@babel/preset-env": "^7.16.5",
        "@babel/preset-react": "^7.16.5",
        "@babel/preset-typescript": "^7.16.5",
        "@testing-library/dom": "^8.11.1",
        "@testing-library/jest-dom": "^5.16.1",
        "@testing-library/react": "^12.1.2",
        "@testing-library/user-event": "^13.5.0",
        "@types/jest": "^27.0.3",
        "@types/react": "^17.0.37",
        "@types/react-dom": "^17.0.11",
        "@typescript-eslint/eslint-plugin": "^5.6.0",
        "@typescript-eslint/parser": "^5.6.0",
        "esbuild-serve": "^1.0.1",
        "eslint": "^8.4.1",
        "eslint-plugin-jest": "^25.3.0",
        "eslint-plugin-react": "^7.27.1",
        "eslint-plugin-testing-library": "^5.0.1",
        "jest": "^27.4.4",
        "ts-jest": "^27.1.2",
        "typescript": "^4.5.4"
      }
    }

As a development server i use package esbuild serve. When I run the css command i get kind of an output, hovewer it is more like css reset that the whole tailwind, and when I run npm run build as an output I get the copied directives

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

about which also my VScode is complaining with warnings I do not know what to do with them. Would you please explain how should I understand this entire process of bundling and using plugins on this example? What am i missing? Thanks a lot

seven
  • 1,183
  • 14
  • 33

1 Answers1

11

The easiest way to integrate Tailwind is by running it separately via their CLI, which is the method recommended by their docs. However, I also detail a way to integrate PostCSS with esbuild, and run Tailwind that way.

Method 1: Using the Tailwind CLI

This method has the advantage of being more straightforward and doesn't require PostCSS, or any esbuild config. I recommend this method if you don't absolutely need other PostCSS features.

To do this, follow their installation steps listed in the docs.

npm install -D tailwindcss

npx tailwindcss init

Don't forget to configure your content keys in your tailwind.config.js.

To build via their CLI, run the following command (you may need to update the path for your specific project).

npx tailwindcss -i ./src/app.css -o ./public/app.css --minify

You can also use npm-run-all to run your esbuild and Tailwind processes together.

npm I -D npm-run-all
{
"scripts": {
        "build:esbuild": "node ./esbuild.config.js",
        "build:css": "npx tailwindcss -i ./src/app.css -o ./public/app.css --minify",
        "build": "npm-run-all --parallel build:*"
      },
}

A similar setup can be used for watching your development server.

{
"scripts": {
        "build:esbuild": "node ./esbuild.config.js",
        "build:css": "npx tailwindcss -i ./src/app.css -o ./public/app.css --minify",
        "build": "npm-run-all --parallel build:*",
        "watch:esbuild": "node ./esbuild.serve.js",
        "watch:css": "npx tailwindcss -i ./src/app.css -o ./public/app.css --watch",
        "dev": "npm-run-all --parallel watch:*"
      },
}

Method 2: Using PostCSS

However, if you are writing custom CSS and require PostCSS features, or need to use esbuild to generate CSS for whatever reason, it is possible to run Tailwind as a PostCSS plugin to integrate with esbuild.

It doesn't seem like there is a standard esbuild plugin for PostCSS that's actively maintained, so I tried out a few. The one I settled on is esbuild-style-plugin which seemed to work the best.

To use this plugin with Tailwind, you'll need to install Tailwind (see their PostCSS install docs).

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init

Don't forget to configure your tailwind.config.js, but we actually won't need a postcss.config.js, since we'll configure that with esbuild-style-plugin.

Install it:

npm i -D esbuild-style-plugin

Your esbuild config will look something like this (feel free to add other options as part of your build):

const postCssPlugin = require('esbuild-style-plugin')

require('esbuild')
  .build({
    entryPoints: ['src/app.jsx', 'src/style.css'],
    outdir: 'public',
    bundle: true,
    minify: true,
    plugins: [
      postCssPlugin({
        postcss: {
          plugins: [require('tailwindcss'), require('autoprefixer')],
        },
      }),
    ],
  })
  .catch(() => {
    console.error(`Build error: ${error}`)
    process.exit(1)
  })

Notice how we're configuring our PostCSS plugins here. The plugin also has other options for CSS modules or other preprocessors (such as SASS or LESS).

One thing to note is that this plugin seems to also generate a .js file with the same name as your CSS file. This may cause issues if your CSS file is named the same as your JS file (such as app.tsx and app.css). In this case, you can either rename your CSS file, or import it in your JS file (with import './app.css' at the top of app.tsx. If you use this imports method, don't forget to remove app.css from your esbuild config's entrypoints.

I was able to get a barebones example (GitHub repo) of this working at the time of first posting this answer, but your specific setup may have unique quirks. I would recommend the first method if possible, since it does not depend on your bundler.

person_v1.32
  • 2,641
  • 1
  • 14
  • 27
  • Installing the Tailwind CLI also installs PostCSS. – Sơn Trần-Nguyễn Jul 07 '23 at 15:16
  • The first option results in a less than ideal development experience - when changing files the generated js could be ready before the css. If you have a browser/client that listen to this changes, it can cause it to refresh twice - once with the js changes, and another time for the css changes. – Shachar Har-Shuv Aug 25 '23 at 14:49