diff options
-rw-r--r-- | .changeset/chatty-teachers-sit.md | 5 | ||||
-rw-r--r-- | .changeset/config.json | 2 | ||||
-rw-r--r-- | .changeset/pre.json | 35 | ||||
-rw-r--r-- | .changeset/quick-ads-exercise.md | 10 | ||||
-rw-r--r-- | .changeset/small-ties-sort.md | 50 | ||||
-rw-r--r-- | CONTRIBUTING.md | 10 | ||||
-rwxr-xr-x | packages/astro/astro.js | 2 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/render/util.ts | 21 | ||||
-rw-r--r-- | packages/astro/test/astro-attrs.test.js | 38 | ||||
-rw-r--r-- | packages/astro/test/astro-component-code.test.js | 8 | ||||
-rw-r--r-- | packages/astro/test/fixtures/astro-attrs/src/pages/index.astro | 35 | ||||
-rw-r--r-- | packages/integrations/mdx/test/mdx-vite-env-vars.test.js | 4 | ||||
-rw-r--r-- | packages/markdown/remark/src/shiki.ts | 30 |
13 files changed, 174 insertions, 76 deletions
diff --git a/.changeset/chatty-teachers-sit.md b/.changeset/chatty-teachers-sit.md new file mode 100644 index 000000000..9e4fd89b4 --- /dev/null +++ b/.changeset/chatty-teachers-sit.md @@ -0,0 +1,5 @@ +--- +"astro": major +--- + +The lowest version of Node supported by Astro is now Node v18.17.1 and higher. diff --git a/.changeset/config.json b/.changeset/config.json index 030941db2..7920f52b8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -4,7 +4,7 @@ "commit": false, "linked": [], "access": "public", - "baseBranch": "main", + "baseBranch": "next", "updateInternalDependencies": "patch", "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..f239e523d --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,35 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "astro": "4.13.1", + "@astrojs/prism": "3.1.0", + "@astrojs/rss": "4.0.7", + "create-astro": "4.8.1", + "@astrojs/db": "0.12.0", + "@astrojs/alpinejs": "0.4.0", + "@astrojs/cloudflare": "0.0.0", + "@astrojs/lit": "4.3.0", + "@astrojs/markdoc": "0.11.3", + "@astrojs/mdx": "3.1.3", + "@astrojs/netlify": "0.0.0", + "@astrojs/node": "8.3.2", + "@astrojs/partytown": "2.1.1", + "@astrojs/preact": "3.5.1", + "@astrojs/react": "3.6.1", + "@astrojs/sitemap": "3.1.6", + "@astrojs/solid-js": "4.4.0", + "@astrojs/svelte": "5.7.0", + "@astrojs/tailwind": "5.1.0", + "@astrojs/vercel": "7.7.2", + "@astrojs/vue": "4.5.0", + "@astrojs/web-vitals": "1.0.0", + "@astrojs/internal-helpers": "0.4.1", + "@astrojs/markdown-remark": "5.2.0", + "@astrojs/studio": "0.1.1", + "@astrojs/telemetry": "3.1.0", + "@astrojs/underscore-redirects": "0.3.4", + "@astrojs/upgrade": "0.3.1" + }, + "changesets": [] +} diff --git a/.changeset/quick-ads-exercise.md b/.changeset/quick-ads-exercise.md new file mode 100644 index 000000000..dd4285a4c --- /dev/null +++ b/.changeset/quick-ads-exercise.md @@ -0,0 +1,10 @@ +--- +'@astrojs/markdown-remark': major +--- + +Renames the following CSS variables theme color token names to better align with the Shiki v1 defaults: + +- `--astro-code-color-text` => `--astro-code-foreground` +- `--astro-code-color-background` => `--astro-code-background` + +You can perform a global find and replace in your project to migrate to the new token names. diff --git a/.changeset/small-ties-sort.md b/.changeset/small-ties-sort.md new file mode 100644 index 000000000..e3f3d67eb --- /dev/null +++ b/.changeset/small-ties-sort.md @@ -0,0 +1,50 @@ +--- +'astro': major +--- + +Fixes attribute rendering for non-[boolean HTML attributes](https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML) with boolean values to match proper attribute handling in browsers. + +Previously, non-boolean attributes may not have included their values when rendered to HTML. In Astro v5.0, the values are now explicitly rendered as `="true"` or `="false"` + +In the following `.astro` examples, only `allowfullscreen` is a boolean attribute: + +```astro +<!-- src/pages/index.astro --> +<!-- `allowfullscreen` is a boolean attribute --> +<p allowfullscreen={true}></p> +<p allowfullscreen={false}></p> + +<!-- `inherit` is *not* a boolean attribute --> +<p inherit={true}></p> +<p inherit={false}></p> + +<!-- `data-*` attributes are not boolean attributes --> +<p data-light={true}></p> +<p data-light={false}></p> +``` + +Astro v5.0 now preserves the full data attribute with its value when rendering the HTML of non-boolean attributes: + +```diff + <p allowfullscreen></p> + <p></p> + + <p inherit="true"></p> +- <p inherit></p> ++ <p inherit="false"></p> + +- <p data-light></p> ++ <p data-light="true"></p> +- <p></p> ++ <p data-light="false"></p> +``` + +If you rely on attribute values, for example to locate elements or to conditionally render, update your code to match the new non-boolean attribute values: + +```diff +- el.getAttribute('inherit') === '' ++ el.getAttribute('inherit') === 'false' + +- el.hasAttribute('data-light') ++ el.dataset.light === 'true' +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f2e63786..caaa9e726 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -369,20 +369,22 @@ Full documentation: https://github.com/changesets/changesets/blob/main/docs/prer ### Entering prerelease mode -If you have gotten permission from the core contributors, you can enter into prerelease mode by following the following steps: +If you have gotten permission from the core contributors, you can enter into prerelease mode with the following steps: - Run: `pnpm exec changeset pre enter next` in the project root +- Update `.changeset/config.json` with `"baseBranch": "next"` (for easier changesets creation) - Create a new PR from the changes created by this command -- Review, approve, and more the PR to enter prerelease mode. +- Review, approve, and merge the PR to enter prerelease mode. - If successful, The "[ci] release" PR (if one exists) will now say "[ci] release (next)". ### Exiting prerelease mode -Exiting prerelease mode should happen once an experimental release is ready to go from `npm install astro@next` to `npm install astro`. Only a core contributor run these steps. These steps should be run before +Exiting prerelease mode should happen once an experimental release is ready to go from `npm install astro@next` to `npm install astro`. Only a core contributor can run these steps: - Run: `pnpm exec changeset pre exit` in the project root +- Update `.changeset/config.json` with `"baseBranch": "main"` - Create a new PR from the changes created by this command. -- Review, approve, and more the PR to enter prerelease mode. +- Review, approve, and merge the PR to enter prerelease mode. - If successful, The "[ci] release (next)" PR (if one exists) will now say "[ci] release". ### Releasing `astro@latest` while in prerelease mode diff --git a/packages/astro/astro.js b/packages/astro/astro.js index 2000ca566..a02e60b76 100755 --- a/packages/astro/astro.js +++ b/packages/astro/astro.js @@ -12,7 +12,7 @@ const CI_INSTRUCTIONS = { }; // Hardcode supported Node.js version so we don't have to read differently in CJS & ESM. -const engines = '>=18.14.1'; +const engines = '>=18.17.1'; const skipSemverCheckIfAbove = 19; /** `astro *` */ diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts index 5dc821aa5..019bf9a40 100644 --- a/packages/astro/src/runtime/server/render/util.ts +++ b/packages/astro/src/runtime/server/render/util.ts @@ -8,9 +8,6 @@ export const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; const htmlBooleanAttributes = /^(?:allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i; -const htmlEnumAttributes = /^(?:contenteditable|draggable|spellcheck|value)$/i; -// Note: SVG is case-sensitive! -const svgEnumAttributes = /^(?:autoReverse|externalResourcesRequired|focusable|preserveAlpha)$/i; const AMPERSAND_REGEX = /&/g; const DOUBLE_QUOTE_REGEX = /"/g; @@ -67,13 +64,6 @@ export function addAttribute(value: any, key: string, shouldEscape = true) { return ''; } - if (value === false) { - if (htmlEnumAttributes.test(key) || svgEnumAttributes.test(key)) { - return markHTMLString(` ${key}="false"`); - } - return ''; - } - // compiler directives cannot be applied dynamically, log a warning and ignore. if (STATIC_DIRECTIVES.has(key)) { // eslint-disable-next-line no-console @@ -115,11 +105,16 @@ Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the } // Boolean values only need the key - if (value === true && (key.startsWith('data-') || htmlBooleanAttributes.test(key))) { + if (htmlBooleanAttributes.test(key)) { + return markHTMLString(value ? ` ${key}` : ''); + } + + // Other attributes with an empty string value can omit rendering the value + if (value === '') { return markHTMLString(` ${key}`); - } else { - return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`); } + + return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`); } // Adds support for `<Component {...value} /> diff --git a/packages/astro/test/astro-attrs.test.js b/packages/astro/test/astro-attrs.test.js index 2e020f8ea..a981a5b15 100644 --- a/packages/astro/test/astro-attrs.test.js +++ b/packages/astro/test/astro-attrs.test.js @@ -16,21 +16,41 @@ describe('Attributes', async () => { const $ = cheerio.load(html); const attrs = { - 'false-str': { attribute: 'attr', value: 'false' }, - 'true-str': { attribute: 'attr', value: 'true' }, - false: { attribute: 'attr', value: undefined }, - true: { attribute: 'attr', value: 'true' }, - empty: { attribute: 'attr', value: '' }, + 'boolean-attr-true': { attribute: 'allowfullscreen', value: '' }, + 'boolean-attr-false': { attribute: 'allowfullscreen', value: undefined }, + 'boolean-attr-string-truthy': { attribute: 'allowfullscreen', value: '' }, + 'boolean-attr-string-falsy': { attribute: 'allowfullscreen', value: undefined }, + 'boolean-attr-number-truthy': { attribute: 'allowfullscreen', value: '' }, + 'boolean-attr-number-falsy': { attribute: 'allowfullscreen', value: undefined }, + 'data-attr-true': { attribute: 'data-foobar', value: 'true' }, + 'data-attr-false': { attribute: 'data-foobar', value: 'false' }, + 'data-attr-string-truthy': { attribute: 'data-foobar', value: 'foo' }, + 'data-attr-string-falsy': { attribute: 'data-foobar', value: '' }, + 'data-attr-number-truthy': { attribute: 'data-foobar', value: '1' }, + 'data-attr-number-falsy': { attribute: 'data-foobar', value: '0' }, + 'normal-attr-true': { attribute: 'foobar', value: 'true' }, + 'normal-attr-false': { attribute: 'foobar', value: 'false' }, + 'normal-attr-string-truthy': { attribute: 'foobar', value: 'foo' }, + 'normal-attr-string-falsy': { attribute: 'foobar', value: '' }, + 'normal-attr-number-truthy': { attribute: 'foobar', value: '1' }, + 'normal-attr-number-falsy': { attribute: 'foobar', value: '0' }, null: { attribute: 'attr', value: undefined }, undefined: { attribute: 'attr', value: undefined }, - 'html-boolean': { attribute: 'async', value: 'async' }, - 'html-boolean-true': { attribute: 'async', value: 'async' }, - 'html-boolean-false': { attribute: 'async', value: undefined }, 'html-enum': { attribute: 'draggable', value: 'true' }, 'html-enum-true': { attribute: 'draggable', value: 'true' }, 'html-enum-false': { attribute: 'draggable', value: 'false' }, }; + assert.ok(!/allowfullscreen=/.test(html), 'boolean attributes should not have values'); + assert.ok( + !/id="data-attr-string-falsy"\s+data-foobar=/.test(html), + "data attributes should not have values if it's an empty string" + ); + assert.ok( + !/id="normal-attr-string-falsy"\s+data-foobar=/.test(html), + "normal attributes should not have values if it's an empty string" + ); + // cheerio will unescape the values, so checking that the url rendered unescaped to begin with has to be done manually assert.equal( html.includes('https://example.com/api/og?title=hello&description=somedescription'), @@ -46,7 +66,7 @@ describe('Attributes', async () => { for (const id of Object.keys(attrs)) { const { attribute, value } = attrs[id]; const attr = $(`#${id}`).attr(attribute); - assert.equal(attr, value); + assert.equal(attr, value, `Expected ${attribute} to be ${value} for #${id}`); } }); diff --git a/packages/astro/test/astro-component-code.test.js b/packages/astro/test/astro-component-code.test.js index 6124e6054..bc8b5f172 100644 --- a/packages/astro/test/astro-component-code.test.js +++ b/packages/astro/test/astro-component-code.test.js @@ -88,13 +88,13 @@ describe('<Code>', () => { .map((i, f) => (f.attribs ? f.attribs.style : 'no style found')) .toArray(), [ - 'background-color:var(--astro-code-color-background);color:var(--astro-code-color-text); overflow-x: auto;', + 'background-color:var(--astro-code-background);color:var(--astro-code-foreground); overflow-x: auto;', 'color:var(--astro-code-token-constant)', 'color:var(--astro-code-token-function)', - 'color:var(--astro-code-color-text)', + 'color:var(--astro-code-foreground)', 'color:var(--astro-code-token-string-expression)', - 'color:var(--astro-code-color-text)', - ], + 'color:var(--astro-code-foreground)', + ] ); }); diff --git a/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro b/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro index 7ac96635f..8f2576650 100644 --- a/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro +++ b/packages/astro/test/fixtures/astro-attrs/src/pages/index.astro @@ -1,19 +1,30 @@ -<span id="false-str" attr="false" /> -<span id="true-str" attr="true" /> -<span id="true" attr={true} /> -<span id="false" attr={false} /> -<span id="empty" attr="" /> +<span id="boolean-attr-true" allowfullscreen={true} /> +<span id="boolean-attr-false" allowfullscreen={false} /> +<span id="boolean-attr-string-truthy" allowfullscreen={'foo'} /> +<span id="boolean-attr-string-falsy" allowfullscreen={''} /> +<span id="boolean-attr-number-truthy" allowfullscreen={1} /> +<span id="boolean-attr-number-falsy" allowfullscreen={0} /> + +<span id="data-attr-true" data-foobar={true} /> +<span id="data-attr-false" data-foobar={false} /> +<span id="data-attr-string-truthy" data-foobar={'foo'} /> +<span id="data-attr-string-falsy" data-foobar={''} /> +<span id="data-attr-number-truthy" data-foobar={1} /> +<span id="data-attr-number-falsy" data-foobar={0} /> + +<span id="normal-attr-true" foobar={true} /> +<span id="normal-attr-false" foobar={false} /> +<span id="normal-attr-string-truthy" foobar={'foo'} /> +<span id="normal-attr-string-falsy" foobar={''} /> +<span id="normal-attr-number-truthy" foobar={1} /> +<span id="normal-attr-number-falsy" foobar={0} /> + <span id="null" attr={null} /> <span id="undefined" attr={undefined} /> + <span id="url" attr={"https://example.com/api/og?title=hello&description=somedescription"}/> <span id="code" attr={"cmd: echo \"foo\" && echo \"bar\" > /tmp/hello.txt"} /> -<!-- - Per HTML spec, some attributes should be treated as booleans - These should always render <span async /> or <span /> (without a string value) ---> -<span id='html-boolean' async /> -<span id='html-boolean-true' async={true} /> -<span id='html-boolean-false' async={false} /> + <!-- Other attributes should be treated as string enums These should always render <span draggable="true" /> or <span draggable="false" /> diff --git a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js index 80a9b1cec..213386ceb 100644 --- a/packages/integrations/mdx/test/mdx-vite-env-vars.test.js +++ b/packages/integrations/mdx/test/mdx-vite-env-vars.test.js @@ -57,8 +57,8 @@ describe('MDX - Vite env vars', () => { const dataAttrDump = document.querySelector('[data-env-dump]'); assert.notEqual(dataAttrDump, null); - assert.notEqual(dataAttrDump.getAttribute('data-env-prod'), null); - assert.equal(dataAttrDump.getAttribute('data-env-dev'), null); + assert.equal(dataAttrDump.getAttribute('data-env-prod'), 'true'); + assert.equal(dataAttrDump.getAttribute('data-env-dev'), 'false'); assert.equal(dataAttrDump.getAttribute('data-env-base-url'), '/'); assert.equal(dataAttrDump.getAttribute('data-env-mode'), 'production'); }); diff --git a/packages/markdown/remark/src/shiki.ts b/packages/markdown/remark/src/shiki.ts index 0028ec927..011431e50 100644 --- a/packages/markdown/remark/src/shiki.ts +++ b/packages/markdown/remark/src/shiki.ts @@ -5,7 +5,6 @@ import { getHighlighter, isSpecialLang, } from 'shiki'; -import { visit } from 'unist-util-visit'; import type { ShikiConfig } from './types.js'; export interface ShikiHighlighter { @@ -23,16 +22,6 @@ export interface ShikiHighlighter { ): Promise<string>; } -// TODO: Remove this special replacement in Astro 5 -const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = { - '--astro-code-foreground': '--astro-code-color-text', - '--astro-code-background': '--astro-code-color-background', -}; -const COLOR_REPLACEMENT_REGEX = new RegExp( - `${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')}`, - 'g', -); - let _cssVariablesTheme: ReturnType<typeof createCssVariablesTheme>; const cssVariablesTheme = () => _cssVariablesTheme ?? @@ -145,21 +134,6 @@ export async function createShikiHighlighter({ return node.children[0] as typeof node; } }, - root(node) { - if (Object.values(themes).length) { - return; - } - - const themeName = typeof theme === 'string' ? theme : theme.name; - if (themeName === 'css-variables') { - // Replace special color tokens to CSS variables - visit(node as any, 'element', (child) => { - if (child.properties?.style) { - child.properties.style = replaceCssVariables(child.properties.style); - } - }); - } - }, }, ...transformers, ], @@ -171,7 +145,3 @@ export async function createShikiHighlighter({ function normalizePropAsString(value: Properties[string]): string | null { return Array.isArray(value) ? value.join(' ') : (value as string | null); } - -function replaceCssVariables(str: string) { - return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match); -} |