Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable ESM and TS based config files #10785

Merged
merged 44 commits into from Mar 15, 2023

Conversation

RobinMalfait
Copy link
Contributor

@RobinMalfait RobinMalfait commented Mar 13, 2023

This PR adds support for loading and scaffolding ESM and TypeScript config files.

ESM and TS syntax support

Prior to this PR, your tailwind.config.js file had to be in CommonJS format. This was surprising to people using "type": "module" in their projects, and made it more difficult to do things like extract your theme tokens into a separate module that you could import into your tailwind.config.js as well as into your application code to maintain a single source of truth.

To make this work, we need to transpile ESM and TS config files on the fly (using jiti), since Tailwind itself isn't published in native ESM and of course there's no native TS support in Node.js. This means there is a performance penalty to using ESM and TS config files, which we're mitigating as much as possible by using Sucrase as the parser instead of Babel. The penalty on my machine is about ~10ms (~50ms using Babel) for one-off builds and the initial build in watch mode, and basically not measurable for incremental builds.

CLI improvements

This PR also adds --esm and --ts flags to the tailwindcss init CLI command to generate your config file using your preferred syntax:

npx tailwindcss init --esm

When running the init command with no flags, Tailwind will guess which syntax to use based on the "type" field in your package.json.

When using the --postcss flag, the generated postcss.config.js file will use the same syntax as your tailwind.config.js file, whether inferred or specified explicitly on the command line.


@natemoo-re and @innocenzi have been credited as co-authors for their efforts in earlier PRs:

@RobinMalfait RobinMalfait force-pushed the feat/esm-and-typescript-config-file branch 2 times, most recently from 1758789 to bb3c480 Compare March 15, 2023 17:56
@RobinMalfait RobinMalfait force-pushed the feat/esm-and-typescript-config-file branch from bb3c480 to 9bcf8ba Compare March 15, 2023 17:58
loadConfig.d.ts Show resolved Hide resolved
src/lib/load-config.ts Show resolved Hide resolved
integrations/io.js Outdated Show resolved Hide resolved
A little smaller but just for tests so doesn't matter too much here 👍
@adamwathan adamwathan merged commit 7e9a53f into master Mar 15, 2023
21 checks passed
@adamwathan adamwathan deleted the feat/esm-and-typescript-config-file branch March 15, 2023 21:04
@innocenzi
Copy link
Contributor

innocenzi commented Mar 15, 2023

Super happy we finally have this, great job guys ❤️

Tested with two medium projects:

  • Both with @tailwindcss/forms and a few other external plugins
  • Both greatly customized
  • One that uses two configs through @config

Everything works perfectly fine, migration is easy. Super nice.

I've noticed zIndex has type issues. I had the following:

zIndex: {
	'sticky-on-scroll': 20,
	'chat-bubble': 30,
	'navbar': 30,
	'navbar-dropdown': 25,
	'dialog-base': 35,
	'dialog-panel': 36,
	'notification': 50,
},

But I had to make the values strings instead of numbers. Using @type {import('tailwindcss').Config} did not catch that before.

The only thing I miss is a defineConfig util instead of having to add satisfies Config at the end of the exported object, which is subjectively less pretty. But it's minor.

Thank you for this! ❤️

@woss
Copy link

woss commented Mar 24, 2023

hi, has this been released? i'm trying to get the TS and ESM js config to work but it doesn't.

@RobinMalfait
Copy link
Contributor Author

@woss no not yet, it will be soon though!

If you want to know what's released and what isn't you can always take a look at the CHANGELOG.md file.

That said, you can already play with it using the insiders version: npm install tailwindcss@insiders.

@karlhorky
Copy link

karlhorky commented Mar 24, 2023

This looks great, thanks for this @RobinMalfait 🙌

Since the current version number is 3.2.7, I guess this will be probably released in a new minor (tailwindcss@3.3.0)?

@joshmanders
Copy link
Contributor

Thank you @RobinMalfait this is a long awaited feature I've been waiting for! Happy early birthday to me!

@klib19
Copy link

klib19 commented Mar 28, 2023

Thank you all for this feature! This is exactly what I needed for my project, I appreciate all the hard work!

One thing that is unclear to me and isn't addressed in the documentation, is how do I import the Tailwind colors into my ESM config file? None of these work

const colors = require('tailwindcss/colors')
import colors from "tailwindcss/colors";
import colors from "tailwindcss/colors.js";

@seb-jean
Copy link
Contributor

Hello,

How to add defaultTheme, plugin, etc with TS.

Like this?

const defaultTheme = require('tailwindcss/defaultTheme');
const plugin = require('tailwindcss/plugin');

@karlhorky
Copy link

karlhorky commented Mar 31, 2023

The more common style is import in TypeScript:

import defaultTheme from 'tailwindcss/defaultTheme';
import plugin from 'tailwindcss/plugin';

Often your editor / IDE can do this conversion for you like Convert 'require' to 'import' in VS Code (either use the lightbulb or hover over the three dots -> Quick Fixes -> "Convert 'require' to 'import'")

@RobinMalfait
Copy link
Contributor Author

@klib19 while I wouldn't recommend to mix everything. All three of those should just work:
image

If you are experiencing issues then I would recommend to open an issue with a minimal reproduction repo attached that shows the issue.


@seb-jean as @karlhorky mentioned, you would use the import syntax in this case 👍

@seb-jean
Copy link
Contributor

thanks @RobinMalfait

@pi0
Copy link

pi0 commented May 3, 2023

Hi. Jiti maintainer here. Excellent work and glad that tailwind is using jiti for TS/ESM support 🔥 Please let me know if there were any issues or possible improvements.

@genesiscz
Copy link

genesiscz commented Sep 26, 2023

It would be really great if it was somehow documented in the official documentation on the website. We literally burnt a few hours trying to make this work. Here's my steps:

  • change postcss.config.js to postcss.config.mjs (we're not using type: module in package.json)
  • renamed tailwind.config.cjs to tailwind.config.ts
  • added import type { Config } from "tailwindcss"; and satisfies Config to tailwind.config.ts
  • I also needed to change all ResolvableTo from (theme) => { ...theme("colors") } to (plugin) => { ...plugin.theme("colors") } (but maybe it never worked? who knows)

Example:

-padding: (theme) => theme("spacing")
+padding: (plugin) => plugin.theme("spacing")
  • vite.config.ts:
    -- Deleted resolve.alias["tailwind.config.js"], optimizeDeps.include[path.resolve(__dirname, "src/tailwind.config.js")], build.commonJsOptions.include["tailwind.config.cjs"] (see diff below)
    -- Added css.postcss.plugins: [tailwindcss()] where tailwindcss is import tailwindcss from "tailwindcss";

Whole diff:

deleted file mode 100644
index 0a0fcca1..00000000
--- a/postcss.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import tailwind from "tailwindcss";
-import autoprefixer from "autoprefixer";
-import tailwindConfig from "tailwind.config";
-
-export default {
-  plugins: [tailwind(tailwindConfig), autoprefixer],
-};
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 00000000..4b8e31f8
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,9 @@
+export default {
+  plugins: {
+    tailwindcss: {
+      config: "src/tailwind.config.cjs",
+    },
+    autoprefixer: {},
+  },
+};
diff --git a/src/tailwind.theme.ts b/src/tailwind.theme.ts
index 7b344945..c496faa8 100644
--- a/src/tailwind.theme.ts
+++ b/src/tailwind.theme.ts
@@ -1,4 +1,9 @@
-import myConfig from "tailwind.config.cjs";
+import resolveConfig from "tailwindcss/resolveConfig";
+import tailwindConfig from "../tailwind.config";
 
-console.log(myConfig);
-export default myConfig;
+export const resolveTailwindConfig = () => {
+  return resolveConfig(tailwindConfig);
+};
+
+const twConfig = resolveConfig(tailwindConfig);
+export default twConfig;
diff --git a/src/tailwind.config.js b/tailwind.config.ts
similarity index 87%
rename from src/tailwind.config.js
rename to tailwind.config.ts
index cc038157..3c4cb6d3 100644
--- a/src/tailwind.config.js
+++ b/tailwind.config.ts
@@ -1,4 +1,5 @@
-/** @type {import('tailwindcss').Config} */
+import type { Config } from "tailwindcss";
+
 export default {
   content: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
   // ### Safelisting these classnames because, they were not updating in Title component when changing size prop
@@ -20,7 +21,8 @@ export default {
       xlg: ["3rem", "1.5"],
     },
     borderRadius: {
-      none: 0,
+      // prettier-ignore
+      'none': "0",
       sm: "0.4rem",
       md: "1.6rem",
       lg: "2.8rem",
@@ -69,11 +71,11 @@ export default {
     dropShadow: {
       sm: "(0px 20px 23px rgba(36, 28, 46, 0.1)",
     },
-    borderColor: (theme) => ({
-      ...theme("colors"),
-      DEFAULT: theme("colors.gray.200", "currentColor"),
+    borderColor: (plugin) => ({
+      ...plugin.theme("colors"),
+      DEFAULT: plugin.theme("colors.gray.200", "currentColor"),
     }),
-    padding: (theme) => theme("spacing"),
+    padding: (plugin) => plugin.theme("spacing"),
 
     borderSpacing: {
       13: "3.25rem",
@@ -140,16 +142,7 @@ export default {
         3: "3px",
       },
       width: {
        "1/7": "14.2857143%",
         "2/7": "28.5714286%",
         "3/7": "42.8571429%",
         "4/7": "57.1428571%",
@@ -158,4 +151,4 @@ export default {
       },
     },
   },
-};
+} satisfies Config;
diff --git a/vite.config.ts b/vite.config.ts
index 5f2371e3..c442d12c 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -7,11 +7,11 @@ import macrosPlugin from "vite-plugin-babel-macros";
-import postcss from "./postcss.config";
+import tailwindcss from "tailwindcss";
 
 export default defineConfig({
   envPrefix: "REACT_APP_",
@@ -29,11 +29,11 @@ export default defineConfig({
       https: require.resolve("rollup-plugin-node-builtins"),
       http2: require.resolve("rollup-plugin-node-builtins"),
       process: require.resolve("rollup-plugin-node-builtins"),
-      "tailwind.config.js": path.resolve(__dirname, "src/tailwind.config.js"),
     },
   },
+
   optimizeDeps: {
-    include: [path.resolve(__dirname, "src/tailwind.config.js")],
+    include: [],
     esbuildOptions: {
       define: {
         global: "globalThis",
@@ -50,7 +50,7 @@ export default defineConfig({
   },
   build: {
     commonjsOptions: {
-      include: ["src/tailwind.config.js", "node_modules/**"],
+      include: ["node_modules/**"],
 
       // Rollup doesn't build CommonJS modules out of the box. That's why this
       // option needs to be enabled
@@ -146,7 +146,9 @@ export default defineConfig({
     },
   },
   css: {
-    postcss,
+    postcss: {
+      plugins: [tailwindcss()],
+    },
   },
   plugins: [
     react(),

Hope that helps somebody!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet