Tuesday, January 30, 2024

Migration twind CSS to tailwind CSS

Upgrading to Deno Fresh 1.6 with Tailwind CSS Integration

The recent Fresh 1.6 release introduced an exciting update for developers: the addition of a Tailwind CSS plugin. This change, while seemingly minor, signifies a substantial shift in how styles can be managed and applied within Deno Fresh projects. Transitioning to this new setup took me several hours, but the benefits of using Tailwind CSS with Fresh are undeniable. Here's a comprehensive guide on how to make this upgrade smoothly.

Key Changes in the Upgrade Process

Switching from twind to tailwind

The first step involves updating the deno.json configuration. Replace the twind import with the new tailwindcss import as follows:

  • Before:

    {
      "imports": {
        "twind": "https://esm.sh/twind@0.16.19",
        "twind/": "https://esm.sh/twind@0.16.19/"
      }
    }
  • After:

    {
      "imports": {
        "tailwindcss": "npm:tailwindcss@3.3.5",
        "tailwindcss/": "npm:/tailwindcss@3.3.5/",
        "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js"
      }
    }

In your fresh.config.ts, replace the twindPlugin import with the tailwind plugin:

  • Before:

    import twindPlugin from "$fresh/plugins/twindv1.ts";
    import twindConfig from "./twind.config.ts";
    
    export default defineConfig({
      plugins: [twindPlugin(twindConfig)],
    });
  • After:

    import tailwind from "$fresh/plugins/tailwind.ts";
    
    export default defineConfig({
      plugins: [tailwind()],
    });

Updating the VSCode Environment

  • .vscode/extensions.json Changes: Replace "sastan.twind-intellisense" with "bradlc.vscode-tailwindcss" to reflect the new setup.

  • Addition of .vscode/tailwind.json: Create a new file named tailwind.json in the .vscode directory with default settings or specific configurations for your project.

{
  "version": 1.1,
  "atDirectives": [
    {
      "name": "@tailwind",
      "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
      "references": [
        {
          "name": "Tailwind Documentation",
          "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
        }
      ]
    },
    {
      "name": "@apply",
      "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
      "references": [
        {
          "name": "Tailwind Documentation",
          "url": "https://tailwindcss.com/docs/functions-and-directives#apply"
        }
      ]
    },
    {
      "name": "@responsive",
      "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n  .alert {\n    background-color: #E53E3E;\n  }\n}\n```\n",
      "references": [
        {
          "name": "Tailwind Documentation",
          "url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
        }
      ]
    },
    {
      "name": "@screen",
      "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n  /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n  /* ... */\n}\n```\n",
      "references": [
        {
          "name": "Tailwind Documentation",
          "url": "https://tailwindcss.com/docs/functions-and-directives#screen"
        }
      ]
    },
    {
      "name": "@variants",
      "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n   .btn-brand {\n    background-color: #3182CE;\n  }\n}\n```\n",
      "references": [
        {
          "name": "Tailwind Documentation",
          "url": "https://tailwindcss.com/docs/functions-and-directives#variants"
        }
      ]
    }
  ]
}
  • Updating settings.json: Ensure your VSCode settings accommodate the new Tailwind CSS setup by adding configurations that enable proper formatting and IntelliSense.
{
  "[typescriptreact]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[javascript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "css.customData": [
    ".vscode/tailwind.json"
  ]
}

Incorporating styles.css

Create a styles.css file that will be processed by the Tailwind plugin. Its default content should include:

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

Remember to import /styles.css in your project's common header, typically found in the root _app.tsx.

Ensuring Visibility of All Used Classes

When using dynamic class names, such as text-${color}-600, ensure that each variant is explicitly mentioned in your source code or included in the safelist within your tailwind.config.ts file to avoid purging by Tailwind.

Plugin Configuration and Migration

  • Migrating twind.config.ts to tailwind.config.ts: Begin by converting twind rules to Tailwind plugin syntax. Most theme.extend configurations can be transferred directly without modifications.

  • Adapting to Tailwind's Syntax: Convert any twind-specific string rules to Tailwind's @apply directive within the new styles.css.

Removing Grouping Variants

Tailwind CSS does not support twind's grouping variants syntax (dark:(bg-red-500 text-white)). These must be expanded to their full form, e.g., dark:bg-red-500 dark:text-white. To identify and replace these instances, I utilized a simple JavaScript script to search through the project files. This approach can be tailored to fit more complex requirements.

const allElements = document.querySelectorAll("*"); // Select all elements in the DOM
const allClasses = new Set(); // Use a Set to store unique class names
allElements.forEach((el) => {
  const classList = el.className;
  if (classList && typeof classList === "string") {
    const classes = classList.split(/\s+/); // Split class names by whitespace
    classes.forEach((className) => {
      if (className) allClasses.add(className); // Add each class name to the Set
    });
  }
});
console.log("Unique classes used in this page:");
console.log(Array.from(allClasses)); // Convert the Set to an Array for easy viewing
// Function to check if a class has styles
function hasStyles(className) {
  const tempElement = document.createElement("div");
  document.body.appendChild(tempElement);
  // Get the computed style for the element
  const style1 = JSON.stringify(window.getComputedStyle(tempElement));
  tempElement.className = className;
  const style2 = JSON.stringify(window.getComputedStyle(tempElement));
  // Clean up: remove the temporary element
  document.body.removeChild(tempElement);
  if (style1 === style2) {
    return false;
  }
  return true;
}
const classesWithoutStyles = [];
// Check each class in allClasses
allClasses.forEach((className) => {
  if (!hasStyles(className)) {
    classesWithoutStyles.push(className);
  }
});
console.log("Classes without styles:");
console.log(classesWithoutStyles);

By running this script on each page, I am able to swiftly identify any overlooked instances that utilize twind's unique syntax. This proactive approach ensures that no such patterns are missed during the transition to Tailwind CSS.

While there's room for enhancement in this script to accommodate more complex scenarios, it has proven to be sufficiently effective for my immediate needs. Its simplicity and efficiency in detecting specific syntax patterns make it a valuable tool in the upgrade process.

Conclusion

Upgrading to Deno Fresh 1.6 and integrating Tailwind CSS requires careful attention to detail but is ultimately rewarding. The process enhances your project's styling capabilities and streamlines development workflows. By following the steps outlined above, you can ensure a smooth transition to this powerful combination of technologies.