Reputation: 1644
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
Upvotes: 23
Views: 15738
Reputation: 61
also my VScode is complaining with warnings I do not know what to do with them
Now there is a tailwindcss extension for VS Code
creating a "professional" workflow with esbuild and tailwindcss with react, typescript and all the goodies
esbuild
can handle the building of CSS files by default, but if you want to work with TailwindCSS, which adds extra steps of modification to your original CSS file, I believe it would be a better choice to separate the CSS building process from your esbuild
workflow.
For example, you can edit your build script to add the logic of building CSS files (with PostCSS
and TailwindCSS
. I'm also using lightningcss
for minification):
import { Buffer } from 'node:buffer';
import { open, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import * as esbuild from 'esbuild';
import postcss from 'postcss';
import tailwindcss from 'tailwindcss';
import { transform as lightningTransform } from 'lightningcss';
// Build CSS
const INPUT_CSS_FILE = join('src', 'input.css');
const OUTPUT_CSS_FILE = join('build', 'output.css');
const tailwindConfig = { // or import from tailwind.config.js
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
const postcssInstance = postcss()
.use(tailwindcss(tailwindConfig));
const f = await open(INPUT_CSS_FILE, 'r');
f.readFile()
.then((buf) => postcssInstance.process(buf, { from: INPUT_CSS_FILE, to: OUTPUT_CSS_FILE }))
.then((rs) => rs.css)
.then((css) => lightningTransform({
code: Buffer.from(css),
minify: true,
}))
.then(({ code }) => writeFile(OUTPUT_CSS_FILE, code))
.finally(() => f.close());
// Build JS
const JS_ENTRY_POINT = join('src', 'index.jsx');
const OUTPUT_JS_FILE = join('build', 'js', 'out.js');
await esbuild.build({
format: 'esm',
entryPoints: [JS_ENTRY_POINT],
loader: { '.css': 'empty' }, // turn off default css building by esbuild
bundle: true,
outfile: OUTPUT_JS_FILE,
minify: true,
define: { "process.env.NODE_ENV": "\"production\"" },
plugins: [],
});
Upvotes: 5
Reputation: 3177
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.
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:*"
},
}
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.
Upvotes: 20