summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Drew Powers <1369770+drwpow@users.noreply.github.com> 2021-04-02 19:23:30 -0600
committerGravatar GitHub <noreply@github.com> 2021-04-02 19:23:30 -0600
commitaa333c2f297eae846580ba66304a9b4d88a7643b (patch)
tree6a574dda151e1ea366b895c8ea0364c7b4cde1ed /src
parentb58b493948ef7b6453ba8c4e94c4bd2fcaea8452 (diff)
downloadastro-aa333c2f297eae846580ba66304a9b4d88a7643b.tar.gz
astro-aa333c2f297eae846580ba66304a9b4d88a7643b.tar.zst
astro-aa333c2f297eae846580ba66304a9b4d88a7643b.zip
Add Tailwind support (#57)
Diffstat (limited to 'src')
-rw-r--r--src/@types/postcss-modules.d.ts2
-rw-r--r--src/@types/tailwind.d.ts3
-rw-r--r--src/compiler/optimize/styles.ts78
3 files changed, 66 insertions, 17 deletions
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<any>;
+ }
+}
+
const getStyleType: Map<string, StyleType> = 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<string, string>();
+interface StylesMiniCache {
+ nodeModules: Map<string, string>; // 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<string, string>(),
+};
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<StyleTransformResult>[] = []; // 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: {