Add Tailwind support (#57)
This commit is contained in:
parent
b58b493948
commit
aa333c2f29
4 changed files with 96 additions and 18 deletions
31
README.md
31
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
|
||||
<style>
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
</style>
|
||||
```
|
||||
|
||||
## 🚀 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
|
||||
|
|
2
src/@types/postcss-modules.d.ts
vendored
2
src/@types/postcss-modules.d.ts
vendored
|
@ -1,2 +0,0 @@
|
|||
// don’t need types; just a plugin
|
||||
declare module 'postcss-modules';
|
3
src/@types/tailwind.d.ts
vendored
Normal file
3
src/@types/tailwind.d.ts
vendored
Normal file
|
@ -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';
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue