Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ npm install astro

TODO: astro boilerplate

### 💧 Partial Hydration

By default, Astro outputs zero client-side JS. If you'd like to include an interactive component in the client output, you may use any of the following techniques.

- `MyComponent:load` will render `MyComponent` on page load
- `MyComponent:idle` will use `requestIdleCallback` to render `MyComponent` as soon as main thread is free
- `MyComponent:visible` will use an `IntersectionObserver` to render `MyComponent` when the element enters the viewport

## 🧞 Development

Add a `dev` npm script to your `/package.json` file:
Expand All @@ -36,6 +28,53 @@ Then run:
npm run dev
```

### 💧 Partial Hydration

By default, Astro outputs zero client-side JS. If you'd like to include an interactive component in the client output, you may use any of the following techniques.

- `MyComponent:load` will render `MyComponent` on page load
- `MyComponent:idle` will use `requestIdleCallback` to render `MyComponent` as soon as main thread is free
- `MyComponent:visible` will use an `IntersectionObserver` to render `MyComponent` when the element enters the viewport

### 💅 Styling

If you‘ve used [Svelte][svelte]’s styles before, Astro works almost the same way. In any `.astro` file, start writing styles in a `<style>` tag like so:

```astro
<style>
.scoped {
font-weight: bold;
}
</style>

<div class="scoped">I’m a scoped style</div>
```

#### Sass

Astro also supports [Sass][sass] out-of-the-box; no configuration needed:

```astro
<style lang="scss">
@use "../tokens" as *;

.title {
color: $color.gray;
}
</style>

<h1 class="title">Title</h1>
```

Supports:

- `lang="scss"`: load as the `.scss` extension
- `lang="sass"`: load as the `.sass` extension (no brackets; indent-style)

### 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.

## 🚀 Build & Deployment

Add a `build` npm script to your `/package.json` file:
Expand All @@ -56,3 +95,8 @@ npm run build
```

Now upload the contents of `/_site_` to your favorite static site host.

[autoprefixer]: https://github.com/postcss/autoprefixer
[browserslist]: https://github.com/browserslist/browserslist
[sass]: https://sass-lang.com/
[svelte]: https://svelte.dev
2 changes: 1 addition & 1 deletion examples/snowpack/astro/components/Nav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export let version: string = '3.1.2';
color: $white;
background-color: $dark-blue;

body.is-nav-open & {
:global(body.is-nav-open) & {
height: $nav-height * 2;
}

Expand Down
17 changes: 12 additions & 5 deletions examples/snowpack/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"micromark-extension-gfm": "^0.3.3",
"node-fetch": "^2.6.1",
"postcss": "^8.2.8",
"postcss-modules": "^4.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"sass": "^1.32.8",
Expand Down
73 changes: 73 additions & 0 deletions src/compiler/optimize/postcss-astro-scoped/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Plugin } from 'postcss';

interface AstroScopedOptions {
className: string;
}

interface Selector {
start: number;
end: number;
value: string;
}

const CSS_SEPARATORS = new Set([' ', ',', '+', '>', '~']);

export function scopeSelectors(selector: string, className: string) {
Comment thread
drwpow marked this conversation as resolved.
const c = className.replace(/^\.?/, '.'); // make sure class always has leading '.'
const selectors: Selector[] = [];
let ss = selector; // final output

// Pass 1: parse selector string; extract top-level selectors
let start = 0;
let lastValue = '';
for (let n = 0; n < ss.length; n++) {
const isEnd = n === selector.length - 1;
if (isEnd || CSS_SEPARATORS.has(selector[n])) {
lastValue = selector.substring(start, isEnd ? undefined : n);
if (!lastValue) continue;
selectors.push({ start, end: isEnd ? n + 1 : n, value: lastValue });
start = n + 1;
}
}

// Pass 2: starting from end, transform selectors w/ scoped class
for (let i = selectors.length - 1; i >= 0; i--) {
const { start, end, value } = selectors[i];
const head = ss.substring(0, start);
const tail = ss.substring(end);

// replace '*' with className
if (value === '*') {
ss = head + c + tail;
continue;
}

// leave :global() alone!
if (value.startsWith(':global(')) {
ss = head + ss.substring(start, end).replace(':global(', '').replace(')', '') + tail;
continue;
}

// scope everything else
let newSelector = ss.substring(start, end);
const pseudoIndex = newSelector.indexOf(':');
if (pseudoIndex > 0) {
// if there‘s a pseudoclass (:focus)
ss = head + newSelector.substring(start, pseudoIndex) + c + newSelector.substr(pseudoIndex) + tail;
} else {
ss = head + newSelector + c + tail;
}
}

return ss;
}

/** PostCSS Scope plugin */
export default function astroScopedStyles(options: AstroScopedOptions): Plugin {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could maybe be its own PostCSS plugin eventually? But local for now.

return {
postcssPlugin: 'Astro Scoped Styles',
Comment thread
drwpow marked this conversation as resolved.
Outdated
Rule(rule) {
rule.selector = scopeSelectors(rule.selector, options.className);
},
};
}
Loading