Skip to content

Commit

Permalink
New handling for define:vars scripts and styles (#3976)
Browse files Browse the repository at this point in the history
* feat: new handling for `define:vars` scripts and styles

* fix: handle new script hoisting pattern

* refactor: compiler handles sourcemaps

* chore: update to handle is:inline define:vars

* chore: bump compiler to latest

* chore: update define:vars tests

* fix: output of `define:vars` is not object style

* fix: appease ts

* chore: remove unused file

* chore: revert unecessary refactors

* chore: prefer sync `defineScriptVars`

* chore: add changeset

Co-authored-by: Nate Moore <nate@astro.build>
Co-authored-by: Okiki Ojo <okikio.dev@gmail.com>
  • Loading branch information
3 people authored Jul 22, 2022
1 parent 1666fdb commit fbef6a7
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-drinks-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix `define:vars` bugs with both `style` and `script`
2 changes: 1 addition & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
"@astrojs/compiler": "^0.20.0",
"@astrojs/compiler": "^0.21.0",
"@astrojs/language-server": "^0.20.0",
"@astrojs/markdown-remark": "^0.12.0",
"@astrojs/prism": "0.6.1",
Expand Down
45 changes: 19 additions & 26 deletions packages/astro/src/runtime/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,20 +213,6 @@ export async function renderComponent(
if (Component && (Component as any).isAstroComponentFactory) {
async function* renderAstroComponentInline(): AsyncGenerator<string, void, undefined> {
let iterable = await renderToIterable(result, Component as any, _props, slots);
// If this component added any define:vars styles and the head has already been
// sent out, we need to include those inline.
if (result.styles.size && alreadyHeadRenderedResults.has(result)) {
let styles = Array.from(result.styles);
result.styles.clear();
for (const style of styles) {
if ('define:vars' in style.props) {
// We only want to render the property value and not the full stylesheet
// which is bundled in the head.
style.children = '';
yield markHTMLString(renderElement('style', style));
}
}
}
yield* iterable;
}

Expand Down Expand Up @@ -583,7 +569,7 @@ Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the
}

// support object styles for better JSX compat
if (key === 'style' && typeof value === 'object') {
if (key === 'style' && !(value instanceof HTMLString) && typeof value === 'object') {
return markHTMLString(` ${key}="${toStyleString(value)}"`);
}

Expand Down Expand Up @@ -633,19 +619,31 @@ export function spreadAttributes(
}

// Adds CSS variables to an inline style tag
export function defineStyleVars(selector: string, vars: Record<any, any>) {
let output = '\n';
for (const [key, value] of Object.entries(vars)) {
output += ` --${key}: ${value};\n`;
export function defineStyleVars(defs: Record<any, any>|Record<any, any>[]) {
let output = '';
let arr = !Array.isArray(defs) ? [defs] : defs;
for (const vars of arr) {
for (const [key, value] of Object.entries(vars)) {
if (value || value === 0) {
output += `--${key}: ${value};`;
}
}
}
return markHTMLString(`${selector} {${output}}`);
return markHTMLString(output);
}

// converts (most) arbitrary strings to valid JS identifiers
const toIdent = (k: string) =>
k.trim().replace(/(?:(?<!^)\b\w|\s+|[^\w]+)/g, (match, index) => {
if (/[^\w]|\s/.test(match)) return '';
return index === 0 ? match : match.toUpperCase();
});

// Adds variables to an inline script.
export function defineScriptVars(vars: Record<any, any>) {
let output = '';
for (const [key, value] of Object.entries(vars)) {
output += `let ${key} = ${JSON.stringify(value)};\n`;
output += `let ${toIdent(key)} = ${JSON.stringify(value)};\n`;
}
return markHTMLString(output);
}
Expand Down Expand Up @@ -945,11 +943,6 @@ function renderElement(
const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props;
if (defineVars) {
if (name === 'style') {
if (props['is:global']) {
children = defineStyleVars(`:root`, defineVars) + '\n' + children;
} else {
children = defineStyleVars(`.astro-${astroId}`, defineVars) + '\n' + children;
}
delete props['is:global'];
delete props['is:scoped'];
}
Expand Down
35 changes: 26 additions & 9 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PluginContext } from 'rollup';
import type { PluginContext, SourceDescription } from 'rollup';
import type * as vite from 'vite';
import type { AstroConfig } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js';
Expand Down Expand Up @@ -175,17 +175,29 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}
}

return {
code:
hoistedScript.type === 'inline'
? hoistedScript.code!
: `import "${hoistedScript.src!}";`,
let result: SourceDescription & { meta: any } = {
code: '',
meta: {
vite: {
lang: 'ts',
},
},
lang: 'ts'
}
}
};

switch (hoistedScript.type) {
case 'inline': {
let { code, map } = hoistedScript
result.code = appendSourceMap(code, map);
break;
}
case 'external': {
const { src } = hoistedScript
result.code = `import "${src}"`;
break;
}
}

return result
}
default:
return null;
Expand Down Expand Up @@ -349,3 +361,8 @@ ${source}
},
};
}

function appendSourceMap(content: string, map?: string) {
if (!map) return content;
return `${content}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from(map).toString('base64')}`
}
45 changes: 23 additions & 22 deletions packages/astro/test/astro-directives.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,36 @@ describe('Directives', async () => {
const html = await fixture.readFile('/define-vars/index.html');
const $ = cheerio.load(html);

expect($('script#inline')).to.have.lengthOf(1);
expect($('script#inline').toString()).to.include('let foo = "bar"');
expect($('script')).to.have.lengthOf(3);

let i = 0;
for (const script of $('script').toArray()) {
// Wrap script in scope ({}) to avoid redeclaration errors
expect($(script).text().at(0)).to.equal('{');
expect($(script).text().at(-1)).to.equal('}');
if (i < 2) {
// Inline defined variables
expect($(script).toString()).to.include('let foo = "bar"');
} else {
// Convert invalid keys to valid identifiers
expect($(script).toString()).to.include('let dashCase = "bar"');
}
i++;
}
});

it('Passes define:vars to style elements', async () => {
const html = await fixture.readFile('/define-vars/index.html');
const $ = cheerio.load(html);

expect($('style')).to.have.lengthOf(2);
expect($('style').toString()).to.include('--bg: white;');
expect($('style').toString()).to.include('--fg: black;');

const scopedClass = $('html')
.attr('class')
.split(' ')
.find((name) => /^astro-[A-Za-z0-9-]+/.test(name));

expect($($('style').get(0)).toString().replace(/\s+/g, '')).to.equal(
`<style>.${scopedClass}{--bg:white;--fg:black;}body{background:var(--bg);color:var(--fg)}</style>`
);

const scopedTitleClass = $('.title')
.attr('class')
.split(' ')
.find((name) => /^astro-[A-Za-z0-9-]+/.test(name));

expect($($('style').get(1)).toString().replace(/\s+/g, '')).to.equal(
`<style>.${scopedTitleClass}{--textColor:red;}</style>`
);

// Inject style attribute on top-level element in page
expect($('html').attr('style').toString()).to.include('--bg: white;');
expect($('html').attr('style').toString()).to.include('--fg: black;');

// Inject style attribute on top-level elements in component
expect($('h1').attr('style').toString()).to.include('--textColor: red;');
});

it('set:html', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
const textColor = 'red'
const textColor = 'red'
---

<h1 class="title">hello there</h1>

<style define:vars={{textColor: textColor}}>
<style define:vars={{ textColor: textColor }}>
.title {
color: var(--textColor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ let fg = 'black'

<html>
<head>
<style define:vars={{bg, fg}}>
body {
<style define:vars={{bg}}>
h1 {
background: var(--bg);
}
</style>
<style define:vars={{fg}}>
h1 {
color: var(--fg);
}
</style>
Expand All @@ -18,6 +22,12 @@ let fg = 'black'
<script id="inline" define:vars={{ foo }}>
console.log(foo);
</script>
<script id="inline-2" define:vars={{ foo }}>
console.log(foo);
</script>
<script id="inline-3" define:vars={{ 'dash-case': foo }}>
console.log(foo);
</script>

<Title />
</body>
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

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

0 comments on commit fbef6a7

Please sign in to comment.