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="scss"`: load as the `.scss` extension
|
||||||
- `lang="sass"`: load as the `.sass` extension (no brackets; indent-style)
|
- `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.
|
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
|
## 🚀 Build & Deployment
|
||||||
|
|
||||||
Add a `build` npm script to your `/package.json` file:
|
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
|
[browserslist]: https://github.com/browserslist/browserslist
|
||||||
[sass]: https://sass-lang.com/
|
[sass]: https://sass-lang.com/
|
||||||
[svelte]: https://svelte.dev
|
[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 crypto from 'crypto';
|
||||||
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import autoprefixer from 'autoprefixer';
|
import autoprefixer from 'autoprefixer';
|
||||||
import postcss from 'postcss';
|
import esbuild from 'esbuild';
|
||||||
|
import postcss, { Plugin } from 'postcss';
|
||||||
import findUp from 'find-up';
|
import findUp from 'find-up';
|
||||||
import sass from 'sass';
|
import sass from 'sass';
|
||||||
import { RuntimeMode } from '../../@types/astro';
|
import type { RuntimeMode } from '../../@types/astro';
|
||||||
import { OptimizeOptions, Optimizer } from '../../@types/optimizer';
|
import type { OptimizeOptions, Optimizer } from '../../@types/optimizer';
|
||||||
import type { TemplateNode } from '../../parser/interfaces';
|
import type { TemplateNode } from '../../parser/interfaces';
|
||||||
|
import { debug } from '../../logger.js';
|
||||||
import astroScopedStyles, { NEVER_SCOPED_TAGS } from './postcss-scoped-styles/index.js';
|
import astroScopedStyles, { NEVER_SCOPED_TAGS } from './postcss-scoped-styles/index.js';
|
||||||
|
|
||||||
type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
|
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([
|
const getStyleType: Map<string, StyleType> = new Map([
|
||||||
['.css', 'css'],
|
['.css', 'css'],
|
||||||
['.pcss', 'postcss'],
|
['.pcss', 'postcss'],
|
||||||
|
@ -42,8 +52,15 @@ export interface StyleTransformResult {
|
||||||
type: StyleType;
|
type: StyleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache node_modules resolutions for each run. saves looking up the same directory over and over again. blown away on exit.
|
interface StylesMiniCache {
|
||||||
const nodeModulesMiniCache = new Map<string, string>();
|
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 {
|
export interface TransformStyleOptions {
|
||||||
type?: string;
|
type?: string;
|
||||||
|
@ -72,17 +89,18 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode
|
||||||
let includePaths: string[] = [path.dirname(filename)];
|
let includePaths: string[] = [path.dirname(filename)];
|
||||||
|
|
||||||
// include node_modules to includePaths (allows @use-ing node modules, if it can be located)
|
// 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) {
|
if (cachedNodeModulesDir) {
|
||||||
includePaths.push(cachedNodeModulesDir);
|
includePaths.push(cachedNodeModulesDir);
|
||||||
} else {
|
} else {
|
||||||
const nodeModulesDir = await findUp('node_modules', { type: 'directory', cwd: path.dirname(filename) });
|
const nodeModulesDir = await findUp('node_modules', { type: 'directory', cwd: path.dirname(filename) });
|
||||||
if (nodeModulesDir) {
|
if (nodeModulesDir) {
|
||||||
nodeModulesMiniCache.set(filename, nodeModulesDir);
|
miniCache.nodeModules.set(filename, nodeModulesDir);
|
||||||
includePaths.push(nodeModulesDir);
|
includePaths.push(nodeModulesDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. Preprocess (currently only Sass supported)
|
||||||
let css = '';
|
let css = '';
|
||||||
switch (styleType) {
|
switch (styleType) {
|
||||||
case 'css': {
|
case 'css': {
|
||||||
|
@ -91,13 +109,7 @@ async function transformStyle(code: string, { type, filename, scopedClass, mode
|
||||||
}
|
}
|
||||||
case 'sass':
|
case 'sass':
|
||||||
case 'scss': {
|
case 'scss': {
|
||||||
css = sass
|
css = sass.renderSync({ data: code, includePaths }).css.toString('utf8');
|
||||||
.renderSync({
|
|
||||||
outputStyle: mode === 'production' ? 'compressed' : undefined,
|
|
||||||
data: code,
|
|
||||||
includePaths,
|
|
||||||
})
|
|
||||||
.css.toString('utf8');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
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 })
|
.process(css, { from: filename, to: undefined })
|
||||||
.then((result) => result.css);
|
.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 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
|
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 {
|
return {
|
||||||
visitors: {
|
visitors: {
|
||||||
html: {
|
html: {
|
||||||
|
|
Loading…
Reference in a new issue