diff --git a/README.md b/README.md
index b48709f40..a6ac9dda0 100644
--- a/README.md
+++ b/README.md
@@ -71,10 +71,38 @@ Supports:
- `lang="scss"`: load as the `.scss` extension
- `lang="sass"`: load as the `.sass` extension (no brackets; indent-style)
-### Autoprefixer
+#### Autoprefixer
We also automatically add browser prefixes using [Autoprefixer][autoprefixer]. By default, Astro loads the default values, but you may also specify your own by placing a [Browserslist][browserslist] file in your project root.
+#### Tailwind
+
+Astro can be configured to use [Tailwind][tailwind] easily! Install the dependencies:
+
+```
+npm install @tailwindcss/jit tailwindcss
+```
+
+And also create a `tailwind.config.js` in your project root:
+
+```
+module.exports = {
+ // your options here
+}
+```
+
+_Note: a Tailwind config file is currently required to enable Tailwind in Astro, even if you use the default options._
+
+Then write Tailwind in your project just like you‘re used to:
+
+```astro
+
+```
+
## 🚀 Build & Deployment
Add a `build` npm script to your `/package.json` file:
@@ -100,3 +128,4 @@ Now upload the contents of `/_site_` to your favorite static site host.
[browserslist]: https://github.com/browserslist/browserslist
[sass]: https://sass-lang.com/
[svelte]: https://svelte.dev
+[tailwind]: https://tailwindcss.com
diff --git a/src/@types/postcss-modules.d.ts b/src/@types/postcss-modules.d.ts
deleted file mode 100644
index 4035404bd..000000000
--- a/src/@types/postcss-modules.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// don’t need types; just a plugin
-declare module 'postcss-modules';
diff --git a/src/@types/tailwind.d.ts b/src/@types/tailwind.d.ts
new file mode 100644
index 000000000..99ae97419
--- /dev/null
+++ b/src/@types/tailwind.d.ts
@@ -0,0 +1,3 @@
+// we shouldn‘t have this as a dependency for Astro, but we may dynamically import it if a user requests it, so let TS know about it
+declare module 'tailwindcss';
+declare module '@tailwindcss/jit';
diff --git a/src/compiler/optimize/styles.ts b/src/compiler/optimize/styles.ts
index 72781fefe..65b429fef 100644
--- a/src/compiler/optimize/styles.ts
+++ b/src/compiler/optimize/styles.ts
@@ -1,16 +1,26 @@
import crypto from 'crypto';
+import fs from 'fs';
import path from 'path';
import autoprefixer from 'autoprefixer';
-import postcss from 'postcss';
+import esbuild from 'esbuild';
+import postcss, { Plugin } from 'postcss';
import findUp from 'find-up';
import sass from 'sass';
-import { RuntimeMode } from '../../@types/astro';
-import { OptimizeOptions, Optimizer } from '../../@types/optimizer';
+import type { RuntimeMode } from '../../@types/astro';
+import type { OptimizeOptions, Optimizer } from '../../@types/optimizer';
import type { TemplateNode } from '../../parser/interfaces';
+import { debug } from '../../logger.js';
import astroScopedStyles, { NEVER_SCOPED_TAGS } from './postcss-scoped-styles/index.js';
type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
+declare global {
+ interface ImportMeta {
+ /** https://nodejs.org/api/esm.html#esm_import_meta_resolve_specifier_parent */
+ resolve(specifier: string, parent?: string): Promise;
+ }
+}
+
const getStyleType: Map = new Map([
['.css', 'css'],
['.pcss', 'postcss'],
@@ -42,8 +52,15 @@ export interface StyleTransformResult {
type: StyleType;
}
-// cache node_modules resolutions for each run. saves looking up the same directory over and over again. blown away on exit.
-const nodeModulesMiniCache = new Map();
+interface StylesMiniCache {
+ nodeModules: Map; // filename: node_modules location
+ tailwindEnabled?: boolean; // cache once per-run
+}
+
+/** Simple cache that only exists in memory per-run. Prevents the same lookups from happening over and over again within the same build or dev server session. */
+const miniCache: StylesMiniCache = {
+ nodeModules: new Map(),
+};
export interface TransformStyleOptions {
type?: string;
@@ -72,17 +89,18 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode
let includePaths: string[] = [path.dirname(filename)];
// include node_modules to includePaths (allows @use-ing node modules, if it can be located)
- const cachedNodeModulesDir = nodeModulesMiniCache.get(filename);
+ const cachedNodeModulesDir = miniCache.nodeModules.get(filename);
if (cachedNodeModulesDir) {
includePaths.push(cachedNodeModulesDir);
} else {
const nodeModulesDir = await findUp('node_modules', { type: 'directory', cwd: path.dirname(filename) });
if (nodeModulesDir) {
- nodeModulesMiniCache.set(filename, nodeModulesDir);
+ miniCache.nodeModules.set(filename, nodeModulesDir);
includePaths.push(nodeModulesDir);
}
}
+ // 1. Preprocess (currently only Sass supported)
let css = '';
switch (styleType) {
case 'css': {
@@ -91,13 +109,7 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode
}
case 'sass':
case 'scss': {
- css = sass
- .renderSync({
- outputStyle: mode === 'production' ? 'compressed' : undefined,
- data: code,
- includePaths,
- })
- .css.toString('utf8');
+ css = sass.renderSync({ data: code, includePaths }).css.toString('utf8');
break;
}
default: {
@@ -105,7 +117,28 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode
}
}
- css = await postcss([astroScopedStyles({ className: scopedClass }), autoprefixer()])
+ // 2. Post-process (PostCSS)
+ const postcssPlugins: Plugin[] = [];
+
+ // 2a. Tailwind (only if project uses Tailwind)
+ if (miniCache.tailwindEnabled) {
+ try {
+ const { default: tailwindcss } = await import('@tailwindcss/jit');
+ postcssPlugins.push(tailwindcss());
+ } catch (err) {
+ console.error(err);
+ throw new Error(`tailwindcss not installed. Try running \`npm install tailwindcss\` and trying again.`);
+ }
+ }
+
+ // 2b. Astro scoped styles (always on)
+ postcssPlugins.push(astroScopedStyles({ className: scopedClass }));
+
+ // 2c. Autoprefixer (always on)
+ postcssPlugins.push(autoprefixer());
+
+ // 2e. Run PostCSS
+ css = await postcss(postcssPlugins)
.process(css, { from: filename, to: undefined })
.then((result) => result.css);
@@ -118,6 +151,21 @@ export default function optimizeStyles({ compileOptions, filename, fileID }: Opt
const styleTransformPromises: Promise[] = []; // async style transform results to be finished in finalize();
const scopedClass = `astro-${hashFromFilename(fileID)}`; // this *should* generate same hash from fileID every time
+ // find Tailwind config, if first run (cache for subsequent runs)
+ if (miniCache.tailwindEnabled === undefined) {
+ const tailwindNames = ['tailwind.config.js', 'tailwind.config.mjs'];
+ for (const loc of tailwindNames) {
+ const tailwindLoc = path.join(compileOptions.astroConfig.projectRoot.pathname, loc);
+ if (fs.existsSync(tailwindLoc)) {
+ miniCache.tailwindEnabled = true; // Success! We have a Tailwind config file.
+ debug(compileOptions.logging, 'tailwind', 'Found config. Enabling.');
+ break;
+ }
+ }
+ if (miniCache.tailwindEnabled !== true) miniCache.tailwindEnabled = false; // We couldn‘t find one; mark as false
+ debug(compileOptions.logging, 'tailwind', 'No config found. Skipping.');
+ }
+
return {
visitors: {
html: {