From d16215e9f824eeea411e7453ec5adcbd7296993e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 18 Jun 2022 18:06:47 -0500 Subject: [PATCH 01/12] feat: add html package --- packages/astro/src/html/renderer.ts | 6 ++ packages/astro/src/html/server.ts | 23 ++++++ packages/integrations/html/package.json | 34 +++++++++ packages/integrations/html/readme.md | 1 + packages/integrations/html/src/index.ts | 38 ++++++++++ packages/integrations/html/src/server.ts | 13 ++++ packages/integrations/html/tsconfig.json | 10 +++ packages/webapi/mod.d.ts | 2 +- pnpm-lock.yaml | 95 ++++++++++++++++++++++-- 9 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 packages/astro/src/html/renderer.ts create mode 100644 packages/astro/src/html/server.ts create mode 100644 packages/integrations/html/package.json create mode 100644 packages/integrations/html/readme.md create mode 100644 packages/integrations/html/src/index.ts create mode 100644 packages/integrations/html/src/server.ts create mode 100644 packages/integrations/html/tsconfig.json diff --git a/packages/astro/src/html/renderer.ts b/packages/astro/src/html/renderer.ts new file mode 100644 index 000000000000..786bf5a79bfa --- /dev/null +++ b/packages/astro/src/html/renderer.ts @@ -0,0 +1,6 @@ +const renderer = { + name: 'astro:html', + serverEntrypoint: 'astro/html/server.js' +}; + +export default renderer; diff --git a/packages/astro/src/html/server.ts b/packages/astro/src/html/server.ts new file mode 100644 index 000000000000..c55b93fdfbba --- /dev/null +++ b/packages/astro/src/html/server.ts @@ -0,0 +1,23 @@ +export function check(Component: any) { + return !!Component['astro:html']; +} + +export async function renderToStaticMarkup(Component) +) { + const slots: Record = {}; + for (const [key, value] of Object.entries(slotted)) { + const name = slotName(key); + slots[name] = value; + } + + const { result } = this; + try { + const html = await renderJSX(result, jsx(Component, { ...props, ...slots, children })); + return { html }; + } catch (e) {} +} + +export default { + check, + renderToStaticMarkup, +}; diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json new file mode 100644 index 000000000000..dcad9af20e39 --- /dev/null +++ b/packages/integrations/html/package.json @@ -0,0 +1,34 @@ +{ + "name": "@astrojs/html", + "description": "Add HTML pages to your Astro site", + "version": "0.0.1", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/node" + }, + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./server.js": "./dist/server.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "build:ci": "astro-scripts build \"src/**/*.ts\"", + "dev": "astro-scripts dev \"src/**/*.ts\"" + }, + "devDependencies": { + "astro": "workspace:*", + "astro-scripts": "workspace:*" + }, + "dependencies": { + "@web/rollup-plugin-html": "^1.10.3", + "install": "^0.13.0" + } +} diff --git a/packages/integrations/html/readme.md b/packages/integrations/html/readme.md new file mode 100644 index 000000000000..a6e6e4ca903f --- /dev/null +++ b/packages/integrations/html/readme.md @@ -0,0 +1 @@ +# @astrojs/html diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts new file mode 100644 index 000000000000..763499a9abb3 --- /dev/null +++ b/packages/integrations/html/src/index.ts @@ -0,0 +1,38 @@ +import type { AstroIntegration, AstroRenderer } from 'astro'; + +function getRenderer(): AstroRenderer { + return { + name: '@astrojs/html', + serverEntrypoint: '@astrojs/html/server.js', + }; +} + +function getViteConfiguration() { + return { + optimizeDeps: { + exclude: ['@astrojs/html/server.js'], + }, + plugins: [{ + name: '@astrojs/html', + transform(code: string, id: string) { + if (!id.endsWith('.html')) return code; + return `export default { ['@astrojs/html']: true, code: ${JSON.stringify(code) }}` + } + }], + }; +} + +export default function createIntegration(): AstroIntegration { + return { + name: '@astrojs/html', + hooks: { + 'astro:config:setup': ({ addRenderer, updateConfig, addPageExtension }) => { + addRenderer( + getRenderer() + ) + updateConfig({ vite: getViteConfiguration() }); + addPageExtension('.html'); + }, + }, + }; +} diff --git a/packages/integrations/html/src/server.ts b/packages/integrations/html/src/server.ts new file mode 100644 index 000000000000..e2a83efa7aff --- /dev/null +++ b/packages/integrations/html/src/server.ts @@ -0,0 +1,13 @@ +function check(Component: any) { + return typeof Component === 'object' && Component['@astrojs/html']; +} + +async function renderToStaticMarkup(Component: { code: string }) { + const html = Component.code; + return { html }; +} + +export default { + check, + renderToStaticMarkup, +}; diff --git a/packages/integrations/html/tsconfig.json b/packages/integrations/html/tsconfig.json new file mode 100644 index 000000000000..44baf375c882 --- /dev/null +++ b/packages/integrations/html/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} diff --git a/packages/webapi/mod.d.ts b/packages/webapi/mod.d.ts index a3c49dc5c406..7150edbe716e 100644 --- a/packages/webapi/mod.d.ts +++ b/packages/webapi/mod.d.ts @@ -9,4 +9,4 @@ interface PolyfillOptions { override?: Record; -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0c342494d12..9085d2b82d42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2016,6 +2016,19 @@ importers: '@astrojs/deno': link:../../.. astro: link:../../../../../astro + packages/integrations/html: + specifiers: + '@web/rollup-plugin-html': ^1.10.3 + astro: workspace:* + astro-scripts: workspace:* + install: ^0.13.0 + dependencies: + '@web/rollup-plugin-html': 1.10.3 + install: 0.13.0 + devDependencies: + astro: link:../../astro + astro-scripts: link:../../../scripts + packages/integrations/image: specifiers: '@types/etag': ^1.8.1 @@ -4814,7 +4827,6 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.14 - dev: true /@jridgewell/sourcemap-codec/1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} @@ -8693,6 +8705,24 @@ packages: /@vue/shared/3.2.37: resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==} + /@web/parse5-utils/1.3.0: + resolution: {integrity: sha512-Pgkx3ECc8EgXSlS5EyrgzSOoUbM6P8OKS471HLAyvOBcP1NCBn0to4RN/OaKASGq8qa3j+lPX9H14uA5AHEnQg==} + engines: {node: '>=10.0.0'} + dependencies: + '@types/parse5': 6.0.3 + parse5: 6.0.1 + dev: false + + /@web/rollup-plugin-html/1.10.3: + resolution: {integrity: sha512-2RMIeKxpGtrcXiqPTgMVq5neGa5xa69MfNK860BHVMEO2N/MrHFuQNr1eNLsspcq2DL/xnymwC3w5hgjtlgxag==} + engines: {node: '>=12.0.0'} + dependencies: + '@web/parse5-utils': 1.3.0 + glob: 7.2.0 + html-minifier-terser: 6.1.0 + parse5: 6.0.1 + dev: false + /@webcomponents/template-shadowroot/0.1.0: resolution: {integrity: sha512-ry84Vft6xtRBbd4M/ptRodbOLodV5AD15TYhyRghCRgIcJJKmYmJ2v2BaaWxygENwh6Uq3zTfGPmlckKT/GXsQ==} dev: false @@ -9180,7 +9210,6 @@ packages: /buffer-from/1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true /buffer/5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -9240,6 +9269,13 @@ packages: engines: {node: '>=6'} dev: true + /camel-case/4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + dependencies: + pascal-case: 3.1.2 + tslib: 2.4.0 + dev: false + /camelcase-css/2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -9388,6 +9424,13 @@ packages: /ci-info/3.3.2: resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} + /clean-css/5.3.0: + resolution: {integrity: sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==} + engines: {node: '>= 10.0'} + dependencies: + source-map: 0.6.1 + dev: false + /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -9487,7 +9530,11 @@ packages: /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true + + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: false /common-ancestor-path/1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -9920,6 +9967,13 @@ packages: domhandler: 5.0.3 dev: true + /dot-case/3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.4.0 + dev: false + /dotenv/8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -10892,7 +10946,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /glob/7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -11194,7 +11247,6 @@ packages: /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - dev: true /hosted-git-info/2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -11215,6 +11267,20 @@ packages: /html-escaper/3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + /html-minifier-terser/6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.0 + commander: 8.3.0 + he: 1.2.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.14.1 + dev: false + /html-void-elements/2.0.1: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} dev: false @@ -11354,6 +11420,11 @@ packages: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} dev: false + /install/0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + dev: false + /internal-slot/1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} engines: {node: '>= 0.4'} @@ -13176,6 +13247,13 @@ packages: netmask: 2.0.2 dev: true + /param-case/3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + dependencies: + dot-case: 3.0.4 + tslib: 2.4.0 + dev: false + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -14193,6 +14271,11 @@ packages: '@jsdevtools/rehype-toc': 3.0.2 dev: true + /relateurl/0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + dev: false + /remark-code-titles/0.1.2: resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==} dependencies: @@ -14678,7 +14761,6 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -15119,7 +15201,6 @@ packages: acorn: 8.7.1 commander: 2.20.3 source-map-support: 0.5.21 - dev: true /test-exclude/6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} From 1aadedf6887ee4275881680bb10dbf93781aba6f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 29 Jun 2022 10:33:29 -0400 Subject: [PATCH 02/12] feat: support assets in HTML --- packages/integrations/html/package.json | 3 +- packages/integrations/html/src/assets.ts | 242 ++++++++++++++++++++ packages/integrations/html/src/index.ts | 26 ++- packages/integrations/html/src/server.ts | 6 +- packages/integrations/html/src/slots.ts | 11 + packages/integrations/html/src/transform.ts | 10 + pnpm-lock.yaml | 99 +------- 7 files changed, 292 insertions(+), 105 deletions(-) create mode 100644 packages/integrations/html/src/assets.ts create mode 100644 packages/integrations/html/src/slots.ts create mode 100644 packages/integrations/html/src/transform.ts diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json index dcad9af20e39..7d94d40dd994 100644 --- a/packages/integrations/html/package.json +++ b/packages/integrations/html/package.json @@ -28,7 +28,6 @@ "astro-scripts": "workspace:*" }, "dependencies": { - "@web/rollup-plugin-html": "^1.10.3", - "install": "^0.13.0" + "linkedom": "^0.14.12" } } diff --git a/packages/integrations/html/src/assets.ts b/packages/integrations/html/src/assets.ts new file mode 100644 index 000000000000..50747da79c1d --- /dev/null +++ b/packages/integrations/html/src/assets.ts @@ -0,0 +1,242 @@ +import { pathToFileURL } from 'node:url' + +export interface AssetSource { + tag: string; + srcAttributes?: string[]; + srcsetAttributes?: string[]; + filter?: (metadata: FilterMetadata) => boolean; +} + +export interface FilterMetadata { + attributes: Record; +} + +export const ASSET_PREFIX = '___ASSET___'; + +export function transformAssets(document: Document, id: string) { + // Import path to import name + // e.g. ./foo.png => ___ASSET___0 + const imports = new Map(); + + function addImport(el: Element, attr: string, value: string) { + let url = value.trim(); + + // Skip if url points to id, e.g. sprite sheets + if (url.startsWith('#')) return + + if (/^https?:\/\//.test(url)) return + + if (el.localName === 'script' || (el.localName === 'link' && el.getAttribute('rel') === 'stylesheet')) { + url = `${url}?url`; + } + + url = '/@fs/' + new URL('../' + url, `file://${pathToFileURL(id).pathname}/`).toString().slice('file://'.length); + let importName = '' + + if (imports.has(url)) { + importName = imports.get(url)! + } else { + importName = `${ASSET_PREFIX}${imports.size}`; + imports.set(url, importName) + } + + const newValue = el.getAttribute(attr)!.replace(value, `\${${importName}}`); + el.setAttribute(attr, newValue); + } + + const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, null); + + function check(node: Node) { + const el = node as Element; + const name = node.nodeName.toLowerCase(); + for (const source of DEFAULT_SOURCES) { + if (source.tag !== name) continue; + + function getAttributes() { + const attributes: Record = {} + for (const attr of el.attributes) { + attributes[attr.name] = attr.value; + } + return attributes; + } + + if (source.filter && !source.filter({ attributes: getAttributes() })) { + continue; + } + + // Check src + source.srcAttributes?.forEach((attr) => { + const value = el.getAttribute(attr); + if (!value) return; + addImport(el, attr, value); + }); + + // Check srcset + source.srcsetAttributes?.forEach((attr) => { + const value = el.getAttribute(attr); + if (!value) return; + const srcsetRegex = /\s*([^,\s]+).*?(?:,|$)\s*/gm; + let match: RegExpExecArray | null; + while ((match = srcsetRegex.exec(value))) { + addImport(el, attr, match[1]); + } + }); + } + } + + let currentNode: Node | null = walker.root; + while (currentNode) { + check(currentNode); + currentNode = walker.nextNode(); + } + + if (imports.size > 0) { + let importText = '' + for (const [path, importName] of imports.entries()) { + importText += `import ${importName} from "${path}";\n` + } + return importText; + } + + return ''; +} + +const ALLOWED_REL = [ + 'stylesheet', + 'icon', + 'shortcut icon', + 'mask-icon', + 'apple-touch-icon', + 'apple-touch-icon-precomposed', + 'apple-touch-startup-image', + 'manifest', + 'prefetch', + 'preload', +]; + +const ALLOWED_ITEMPROP = [ + 'image', + 'logo', + 'screenshot', + 'thumbnailurl', + 'contenturl', + 'downloadurl', + 'duringmedia', + 'embedurl', + 'installurl', + 'layoutimage', +]; + +const ALLOWED_META_NAME = [ + 'msapplication-tileimage', + 'msapplication-square70x70logo', + 'msapplication-square150x150logo', + 'msapplication-wide310x150logo', + 'msapplication-square310x310logo', + 'msapplication-config', + 'twitter:image', +]; + +const ALLOWED_META_PROPERTY = [ + 'og:image', + 'og:image:url', + 'og:image:secure_url', + 'og:audio', + 'og:audio:secure_url', + 'og:video', + 'og:video:secure_url', + 'vk:image', +]; + +const DEFAULT_SOURCES: AssetSource[] = [ + { + tag: 'script', + srcAttributes: ['src'], + }, + { + tag: 'audio', + srcAttributes: ['src'], + }, + { + tag: 'embed', + srcAttributes: ['src'], + }, + { + tag: 'img', + srcAttributes: ['src'], + srcsetAttributes: ['srcset'], + }, + { + tag: 'input', + srcAttributes: ['src'], + }, + { + tag: 'object', + srcAttributes: ['src'], + }, + { + tag: 'source', + srcAttributes: ['src'], + srcsetAttributes: ['srcset'], + }, + { + tag: 'track', + srcAttributes: ['src'], + }, + { + tag: 'video', + srcAttributes: ['poster', 'src'], + }, + { + tag: 'image', + srcAttributes: ['href', 'xlink:href'], + }, + { + tag: 'use', + srcAttributes: ['href', 'xlink:href'], + }, + { + tag: 'link', + srcAttributes: ['href'], + srcsetAttributes: ['imagesrcset'], + filter({ attributes }) { + if (attributes.rel && ALLOWED_REL.includes(attributes.rel.trim().toLowerCase())) { + return true; + } + + if ( + attributes.itemprop && + ALLOWED_ITEMPROP.includes(attributes.itemprop.trim().toLowerCase()) + ) { + return true; + } + + return false; + }, + }, + { + tag: 'meta', + srcAttributes: ['content'], + filter({ attributes }) { + if (attributes.name && ALLOWED_META_NAME.includes(attributes.name.trim().toLowerCase())) { + return true; + } + + if ( + attributes.property && + ALLOWED_META_PROPERTY.includes(attributes.property.trim().toLowerCase()) + ) { + return true; + } + + if ( + attributes.itemprop && + ALLOWED_ITEMPROP.includes(attributes.itemprop.trim().toLowerCase()) + ) { + return true; + } + + return false; + }, + }, +]; diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index 763499a9abb3..b6d0ff7777f1 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -1,4 +1,6 @@ import type { AstroIntegration, AstroRenderer } from 'astro'; +import { readFileSync } from 'fs'; +import { transform } from './transform.js'; function getRenderer(): AstroRenderer { return { @@ -12,13 +14,19 @@ function getViteConfiguration() { optimizeDeps: { exclude: ['@astrojs/html/server.js'], }, - plugins: [{ - name: '@astrojs/html', - transform(code: string, id: string) { - if (!id.endsWith('.html')) return code; - return `export default { ['@astrojs/html']: true, code: ${JSON.stringify(code) }}` + plugins: [ + { + name: '@astrojs/html', + options(options: any) { + options.plugins = options.plugins?.filter((p: any) => p.name !== 'vite:build-html'); + }, + transform(source: string, id: string) { + if (!id.endsWith('.html')) return; + const code = transform(source, id); + return { code }; + } } - }], + ], }; } @@ -27,12 +35,10 @@ export default function createIntegration(): AstroIntegration { name: '@astrojs/html', hooks: { 'astro:config:setup': ({ addRenderer, updateConfig, addPageExtension }) => { - addRenderer( - getRenderer() - ) + addRenderer(getRenderer()) updateConfig({ vite: getViteConfiguration() }); addPageExtension('.html'); - }, + } }, }; } diff --git a/packages/integrations/html/src/server.ts b/packages/integrations/html/src/server.ts index e2a83efa7aff..445b03a48239 100644 --- a/packages/integrations/html/src/server.ts +++ b/packages/integrations/html/src/server.ts @@ -1,9 +1,9 @@ function check(Component: any) { - return typeof Component === 'object' && Component['@astrojs/html']; + return Component && typeof Component === 'object' && Component['@astrojs/html']; } -async function renderToStaticMarkup(Component: { code: string }) { - const html = Component.code; +async function renderToStaticMarkup(Component: any, _: any, slots: Record) { + const html = Component.render({ slots }); return { html }; } diff --git a/packages/integrations/html/src/slots.ts b/packages/integrations/html/src/slots.ts new file mode 100644 index 000000000000..1e18af868a92 --- /dev/null +++ b/packages/integrations/html/src/slots.ts @@ -0,0 +1,11 @@ +export const SLOT_PREFIX = `___SLOTS___` +export function transformSlots(document: Document) { + for (const slot of document.querySelectorAll('slot')) { + if (slot.closest('template')) continue; + if (slot.hasAttribute('is:inline')) { + slot.removeAttribute('is:inline'); + continue; + } + slot.replaceWith(document.createTextNode(`\${${SLOT_PREFIX}['${slot.getAttribute('name') || 'default'}'] ?? ${JSON.stringify(slot.innerHTML)}}`)); + } +} diff --git a/packages/integrations/html/src/transform.ts b/packages/integrations/html/src/transform.ts new file mode 100644 index 000000000000..33c495da2993 --- /dev/null +++ b/packages/integrations/html/src/transform.ts @@ -0,0 +1,10 @@ +import { parseHTML } from 'linkedom' +import { transformSlots, SLOT_PREFIX } from './slots.js'; +import { transformAssets, ASSET_PREFIX } from './assets.js'; + +export function transform(code: string, id: string) { + const { document } = parseHTML(code) + transformSlots(document); + const importText = transformAssets(document, id); + return `${importText}\n\nconst ___CODE___ = ({ ['@astrojs/html']: true, render({ slots: ${SLOT_PREFIX} }) { return \`${document.toString().replace(/`/g, '\\`').replace(/\${(?!___(SLOTS|ASSET))/g, '\\${')}\` }});\nexport default ___CODE___;` +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9085d2b82d42..1bc9076bbb7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2018,13 +2018,11 @@ importers: packages/integrations/html: specifiers: - '@web/rollup-plugin-html': ^1.10.3 astro: workspace:* astro-scripts: workspace:* - install: ^0.13.0 + linkedom: ^0.14.12 dependencies: - '@web/rollup-plugin-html': 1.10.3 - install: 0.13.0 + linkedom: 0.14.12 devDependencies: astro: link:../../astro astro-scripts: link:../../../scripts @@ -4827,6 +4825,7 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.14 + dev: true /@jridgewell/sourcemap-codec/1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} @@ -8705,24 +8704,6 @@ packages: /@vue/shared/3.2.37: resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==} - /@web/parse5-utils/1.3.0: - resolution: {integrity: sha512-Pgkx3ECc8EgXSlS5EyrgzSOoUbM6P8OKS471HLAyvOBcP1NCBn0to4RN/OaKASGq8qa3j+lPX9H14uA5AHEnQg==} - engines: {node: '>=10.0.0'} - dependencies: - '@types/parse5': 6.0.3 - parse5: 6.0.1 - dev: false - - /@web/rollup-plugin-html/1.10.3: - resolution: {integrity: sha512-2RMIeKxpGtrcXiqPTgMVq5neGa5xa69MfNK860BHVMEO2N/MrHFuQNr1eNLsspcq2DL/xnymwC3w5hgjtlgxag==} - engines: {node: '>=12.0.0'} - dependencies: - '@web/parse5-utils': 1.3.0 - glob: 7.2.0 - html-minifier-terser: 6.1.0 - parse5: 6.0.1 - dev: false - /@webcomponents/template-shadowroot/0.1.0: resolution: {integrity: sha512-ry84Vft6xtRBbd4M/ptRodbOLodV5AD15TYhyRghCRgIcJJKmYmJ2v2BaaWxygENwh6Uq3zTfGPmlckKT/GXsQ==} dev: false @@ -9210,6 +9191,7 @@ packages: /buffer-from/1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true /buffer/5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -9269,13 +9251,6 @@ packages: engines: {node: '>=6'} dev: true - /camel-case/4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - dependencies: - pascal-case: 3.1.2 - tslib: 2.4.0 - dev: false - /camelcase-css/2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -9424,13 +9399,6 @@ packages: /ci-info/3.3.2: resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} - /clean-css/5.3.0: - resolution: {integrity: sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==} - engines: {node: '>= 10.0'} - dependencies: - source-map: 0.6.1 - dev: false - /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -9530,11 +9498,7 @@ packages: /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - /commander/8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - dev: false + dev: true /common-ancestor-path/1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -9648,7 +9612,6 @@ packages: domhandler: 5.0.3 domutils: 3.0.1 nth-check: 2.1.1 - dev: true /css-selector-parser/1.4.1: resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==} @@ -9656,7 +9619,6 @@ packages: /css-what/6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} - dev: true /cssdb/6.6.3: resolution: {integrity: sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA==} @@ -9669,7 +9631,6 @@ packages: /cssom/0.5.0: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - dev: true /csstype/2.6.20: resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==} @@ -9946,18 +9907,15 @@ packages: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.3.1 - dev: true /domelementtype/2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true /domhandler/5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} dependencies: domelementtype: 2.3.0 - dev: true /domutils/3.0.1: resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} @@ -9965,14 +9923,6 @@ packages: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - dev: true - - /dot-case/3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dependencies: - no-case: 3.0.4 - tslib: 2.4.0 - dev: false /dotenv/8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} @@ -10042,7 +9992,6 @@ packages: /entities/4.3.1: resolution: {integrity: sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==} engines: {node: '>=0.12'} - dev: true /eol/0.9.1: resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} @@ -10946,6 +10895,7 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true /glob/7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -11247,6 +11197,7 @@ packages: /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + dev: true /hosted-git-info/2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -11267,20 +11218,6 @@ packages: /html-escaper/3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - /html-minifier-terser/6.1.0: - resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} - engines: {node: '>=12'} - hasBin: true - dependencies: - camel-case: 4.1.2 - clean-css: 5.3.0 - commander: 8.3.0 - he: 1.2.0 - param-case: 3.0.4 - relateurl: 0.2.7 - terser: 5.14.1 - dev: false - /html-void-elements/2.0.1: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} dev: false @@ -11292,7 +11229,6 @@ packages: domhandler: 5.0.3 domutils: 3.0.1 entities: 4.3.1 - dev: true /http-errors/2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -11420,11 +11356,6 @@ packages: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} dev: false - /install/0.13.0: - resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} - engines: {node: '>= 0.10'} - dev: false - /internal-slot/1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} engines: {node: '>= 0.4'} @@ -11939,7 +11870,6 @@ packages: html-escaper: 3.0.3 htmlparser2: 8.0.1 uhyphen: 0.1.0 - dev: true /lit-element/3.2.1: resolution: {integrity: sha512-2PxyE9Yq9Jyo/YBK2anycaHcqo93YvB5D+24JxloPVqryW/BOXekne+jGsm0Ke3E5E2v7CDgkmpEmCAzYfrHCQ==} @@ -13247,13 +13177,6 @@ packages: netmask: 2.0.2 dev: true - /param-case/3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - dependencies: - dot-case: 3.0.4 - tslib: 2.4.0 - dev: false - /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -14271,11 +14194,6 @@ packages: '@jsdevtools/rehype-toc': 3.0.2 dev: true - /relateurl/0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} - dev: false - /remark-code-titles/0.1.2: resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==} dependencies: @@ -14761,6 +14679,7 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -15201,6 +15120,7 @@ packages: acorn: 8.7.1 commander: 2.20.3 source-map-support: 0.5.21 + dev: true /test-exclude/6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} @@ -15541,7 +15461,6 @@ packages: /uhyphen/0.1.0: resolution: {integrity: sha512-o0QVGuFg24FK765Qdd5kk0zU/U4dEsCtN/GSiwNI9i8xsSVtjIAOdTaVhLwZ1nrbWxFVMxNDDl+9fednsOMsBw==} - dev: true /unbox-primitive/1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} From 440d88dd7969b4fbbddc9f1507a593ce19e321af Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 5 Jul 2022 11:10:32 -0500 Subject: [PATCH 03/12] feat(html): upgrade html integration --- packages/integrations/html/readme.md | 1 - packages/integrations/html/src/assets.ts | 159 +++++++++----------- packages/integrations/html/src/escape.ts | 27 ++++ packages/integrations/html/src/index.ts | 5 +- packages/integrations/html/src/server.ts | 2 +- packages/integrations/html/src/slots.ts | 47 ++++-- packages/integrations/html/src/transform.ts | 42 ++++-- packages/integrations/html/src/utils.ts | 26 ++++ 8 files changed, 200 insertions(+), 109 deletions(-) delete mode 100644 packages/integrations/html/readme.md create mode 100644 packages/integrations/html/src/escape.ts create mode 100644 packages/integrations/html/src/utils.ts diff --git a/packages/integrations/html/readme.md b/packages/integrations/html/readme.md deleted file mode 100644 index a6e6e4ca903f..000000000000 --- a/packages/integrations/html/readme.md +++ /dev/null @@ -1 +0,0 @@ -# @astrojs/html diff --git a/packages/integrations/html/src/assets.ts b/packages/integrations/html/src/assets.ts index 50747da79c1d..aa48d3836ab9 100644 --- a/packages/integrations/html/src/assets.ts +++ b/packages/integrations/html/src/assets.ts @@ -1,106 +1,95 @@ import { pathToFileURL } from 'node:url' +import type { Plugin } from 'unified'; +import type { Root, RootContent, Element } from 'hast'; +import { visit } from 'unist-util-visit'; +import MagicString from 'magic-string'; -export interface AssetSource { - tag: string; - srcAttributes?: string[]; - srcsetAttributes?: string[]; - filter?: (metadata: FilterMetadata) => boolean; -} +import { replaceAttribute } from './utils.js'; -export interface FilterMetadata { - attributes: Record; -} +const rehypeAssets: Plugin<[{ s: MagicString, imports: Map }], Root> = ({ s, imports }) => { + return (tree, file) => { + const fileURL = new URL(file.path, 'file://'); + function addImport(el: Element, attr: string, value: string) { + let url = value.trim(); -export const ASSET_PREFIX = '___ASSET___'; + // Skip if url points to id, e.g. sprite sheets + if (url.startsWith('#')) return -export function transformAssets(document: Document, id: string) { - // Import path to import name - // e.g. ./foo.png => ___ASSET___0 - const imports = new Map(); + if (/^https?:\/\//.test(url)) return - function addImport(el: Element, attr: string, value: string) { - let url = value.trim(); - - // Skip if url points to id, e.g. sprite sheets - if (url.startsWith('#')) return - - if (/^https?:\/\//.test(url)) return - - if (el.localName === 'script' || (el.localName === 'link' && el.getAttribute('rel') === 'stylesheet')) { - url = `${url}?url`; - } + if (el.tagName === 'script') { + url = `${url}`; + } - url = '/@fs/' + new URL('../' + url, `file://${pathToFileURL(id).pathname}/`).toString().slice('file://'.length); - let importName = '' + if (url.startsWith('.')) { + url = '/@fs/' + new URL(url, fileURL).pathname.slice(1); + } + url += `?url` + let importName = '' + + if (imports.has(url)) { + importName = imports.get(url)! + } else { + importName = `${ASSET_PREFIX}${imports.size}`; + imports.set(url, importName) + } - if (imports.has(url)) { - importName = imports.get(url)! - } else { - importName = `${ASSET_PREFIX}${imports.size}`; - imports.set(url, importName) - } - - const newValue = el.getAttribute(attr)!.replace(value, `\${${importName}}`); - el.setAttribute(attr, newValue); + replaceAttribute(s, el, attr, `${attr}="\${${importName}}"`); } - const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, null); - function check(node: Node) { - const el = node as Element; - const name = node.nodeName.toLowerCase(); - for (const source of DEFAULT_SOURCES) { - if (source.tag !== name) continue; - - function getAttributes() { - const attributes: Record = {} - for (const attr of el.attributes) { - attributes[attr.name] = attr.value; + visit(tree, (node: Root | RootContent) => { + if (node.type !== 'element') return; + check(node); + function check(el: Element) { + const name = el.tagName; + for (const source of DEFAULT_SOURCES) { + if (source.tag !== name) continue; + + const attributes = el.properties ?? {}; + + if (source.filter && !source.filter({ attributes })) { + continue; + } + + // Check src + source.srcAttributes?.forEach((attr) => { + const value = attributes[attr]; + if (!value) return; + addImport(el, attr, value.toString()); + }); + + // Check srcset + source.srcsetAttributes?.forEach((attr) => { + const value = attributes[attr]; + if (!value) return; + const srcsetRegex = /\s*([^,\s]+).*?(?:,|$)\s*/gm; + let match: RegExpExecArray | null; + while ((match = srcsetRegex.exec(value.toString()))) { + addImport(el, attr, match[1]); + } + }); } - return attributes; } + }); + } +} - if (source.filter && !source.filter({ attributes: getAttributes() })) { - continue; - } - - // Check src - source.srcAttributes?.forEach((attr) => { - const value = el.getAttribute(attr); - if (!value) return; - addImport(el, attr, value); - }); - - // Check srcset - source.srcsetAttributes?.forEach((attr) => { - const value = el.getAttribute(attr); - if (!value) return; - const srcsetRegex = /\s*([^,\s]+).*?(?:,|$)\s*/gm; - let match: RegExpExecArray | null; - while ((match = srcsetRegex.exec(value))) { - addImport(el, attr, match[1]); - } - }); - } - } - - let currentNode: Node | null = walker.root; - while (currentNode) { - check(currentNode); - currentNode = walker.nextNode(); - } +export default rehypeAssets; - if (imports.size > 0) { - let importText = '' - for (const [path, importName] of imports.entries()) { - importText += `import ${importName} from "${path}";\n` - } - return importText; - } +export interface AssetSource { + tag: string; + srcAttributes?: string[]; + srcsetAttributes?: string[]; + filter?: (metadata: FilterMetadata) => boolean; +} - return ''; +export interface FilterMetadata { + attributes: Record; } +export const ASSET_PREFIX = '___ASSET___'; + const ALLOWED_REL = [ 'stylesheet', 'icon', diff --git a/packages/integrations/html/src/escape.ts b/packages/integrations/html/src/escape.ts new file mode 100644 index 000000000000..4ac836d2b62d --- /dev/null +++ b/packages/integrations/html/src/escape.ts @@ -0,0 +1,27 @@ +import type { Plugin } from 'unified'; +import type { Root, RootContent, Element } from 'hast'; +import type MagicString from 'magic-string'; +import { visit } from 'unist-util-visit'; + +import { replaceAttribute, needsEscape, escape } from './utils.js'; + +const rehypeEscape: Plugin<[{ s: MagicString }], Root> = ({ s }) => { + return (tree, file) => { + visit(tree, (node: Root | RootContent, index, parent) => { + if (node.type === 'text' || node.type === 'comment') { + if (needsEscape(node.value)) { + s.overwrite(node.position!.start.offset!, node.position!.end.offset!, escape(node.value)); + } + } else if (node.type === 'element') { + for (const [key, value] of Object.entries(node.properties ?? {})) { + const newKey = escape(key); + const newValue = needsEscape(value) ? escape(value) : value; + if (newKey === key && newValue === value) continue; + replaceAttribute(s, node, key, (value === '') ? newKey : `${newKey}="${newValue}"`); + } + } + }); + }; +}; + +export default rehypeEscape; diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index b6d0ff7777f1..50350afd0c59 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -20,10 +20,9 @@ function getViteConfiguration() { options(options: any) { options.plugins = options.plugins?.filter((p: any) => p.name !== 'vite:build-html'); }, - transform(source: string, id: string) { + async transform(source: string, id: string) { if (!id.endsWith('.html')) return; - const code = transform(source, id); - return { code }; + return await transform(source, id); } } ], diff --git a/packages/integrations/html/src/server.ts b/packages/integrations/html/src/server.ts index 445b03a48239..48059a7435f3 100644 --- a/packages/integrations/html/src/server.ts +++ b/packages/integrations/html/src/server.ts @@ -1,5 +1,5 @@ function check(Component: any) { - return Component && typeof Component === 'object' && Component['@astrojs/html']; + return Component && typeof Component === 'object' && Component['astro:html']; } async function renderToStaticMarkup(Component: any, _: any, slots: Record) { diff --git a/packages/integrations/html/src/slots.ts b/packages/integrations/html/src/slots.ts index 1e18af868a92..407253ded5d8 100644 --- a/packages/integrations/html/src/slots.ts +++ b/packages/integrations/html/src/slots.ts @@ -1,11 +1,38 @@ -export const SLOT_PREFIX = `___SLOTS___` -export function transformSlots(document: Document) { - for (const slot of document.querySelectorAll('slot')) { - if (slot.closest('template')) continue; - if (slot.hasAttribute('is:inline')) { - slot.removeAttribute('is:inline'); - continue; - } - slot.replaceWith(document.createTextNode(`\${${SLOT_PREFIX}['${slot.getAttribute('name') || 'default'}'] ?? ${JSON.stringify(slot.innerHTML)}}`)); - } +import type { Plugin } from 'unified'; +import type { Root, RootContent } from 'hast'; +import { visit } from 'unist-util-visit'; +import MagicString from 'magic-string'; +import { escape } from './utils.js'; + + +const rehypeSlots: Plugin<[{ s: MagicString }], Root> = ({ s }) => { + return (tree, file) => { + visit(tree, (node: Root | RootContent, index, parent) => { + if (node.type === 'element' && node.tagName === 'slot') { + if (typeof node.properties?.['is:inline'] !== 'undefined') return; + const name = node.properties?.['name'] ?? 'default'; + const start = node.position?.start.offset ?? 0; + const end = node.position?.end.offset ?? 0; + const first = node.children.at(0) ?? node; + const last = node.children.at(-1) ?? node; + const text = file.value.slice(first.position?.start.offset ?? 0, last.position?.end.offset ?? 0).toString(); + s.overwrite(start, end, `\${${SLOT_PREFIX}["${name}"] ?? \`${escape(text).trim()}\`}`) + } + }); + } } + +export default rehypeSlots; + +export const SLOT_PREFIX = `___SLOTS___`; + +// export function transformSlots(document: Document) { +// for (const slot of document.querySelectorAll('slot')) { +// if (slot.closest('template')) continue; +// if (slot.hasAttribute('is:inline')) { +// slot.removeAttribute('is:inline'); +// continue; +// } +// slot.replaceWith(document.createTextNode(`\${${SLOT_PREFIX}['${slot.getAttribute('name') || 'default'}'] ?? ${JSON.stringify(slot.innerHTML)}}`)); +// } +// } diff --git a/packages/integrations/html/src/transform.ts b/packages/integrations/html/src/transform.ts index 33c495da2993..0a42c920db71 100644 --- a/packages/integrations/html/src/transform.ts +++ b/packages/integrations/html/src/transform.ts @@ -1,10 +1,34 @@ -import { parseHTML } from 'linkedom' -import { transformSlots, SLOT_PREFIX } from './slots.js'; -import { transformAssets, ASSET_PREFIX } from './assets.js'; - -export function transform(code: string, id: string) { - const { document } = parseHTML(code) - transformSlots(document); - const importText = transformAssets(document, id); - return `${importText}\n\nconst ___CODE___ = ({ ['@astrojs/html']: true, render({ slots: ${SLOT_PREFIX} }) { return \`${document.toString().replace(/`/g, '\\`').replace(/\${(?!___(SLOTS|ASSET))/g, '\\${')}\` }});\nexport default ___CODE___;` +import MagicString from 'magic-string'; +import { rehype } from 'rehype'; +import { VFile } from 'vfile'; +import escape from './escape.js'; +import slots, { SLOT_PREFIX } from './slots.js'; +import assets from './assets.js'; + +export async function transform(code: string, id: string) { + const s = new MagicString(code, { filename: id }); + const imports = new Map(); + const parser = rehype() + .data('settings', { fragment: true }) + .use(escape, { s }) + .use(assets, { s, imports }) + .use(slots, { s }); + + const vfile = new VFile({ value: code, path: id }) + await parser.process(vfile) + s.prepend(`export default {\n\t"astro:html": true,\n\trender({ slots: ${SLOT_PREFIX} }) {\n\t\treturn \``); + s.append('`\n\t}\n}'); + + if (imports.size > 0) { + let importText = '' + for (const [path, importName] of imports.entries()) { + importText += `import ${importName} from "${path}";\n` + } + s.prepend(importText); + } + + return { + code: s.toString(), + map: s.generateMap() + } } diff --git a/packages/integrations/html/src/utils.ts b/packages/integrations/html/src/utils.ts new file mode 100644 index 000000000000..51b0e0030f08 --- /dev/null +++ b/packages/integrations/html/src/utils.ts @@ -0,0 +1,26 @@ +import type { Element } from 'hast'; +import MagicString from 'magic-string'; + +const splitAttrsTokenizer = /([a-z0-9_\:\-]*)\s*?=\s*?(['"]?)(.*?)\2\s+/gim; + +export function replaceAttribute(s: MagicString, node: Element, key: string, newValue: string) { + splitAttrsTokenizer.lastIndex = 0; + const text = s.original.slice(node.position?.start.offset ?? 0, node.position?.end.offset ?? 0).toString(); + const offset = text.indexOf(key); + if (offset === -1) return; + const start = node.position!.start.offset! + offset; + const tokens = text.slice(offset).split(splitAttrsTokenizer); + if (tokens[0].trim() === key) { + const end = start + key.length; + s.overwrite(start, end, newValue) + } else { + const end = start + `${key}=${tokens[2]}${tokens[3]}${tokens[2]}`.length; + s.overwrite(start, end, newValue) + } +} +export function needsEscape(value: any): value is string { + return typeof value === 'string' && (value.includes('`') || value.includes('${')); +} +export function escape(value: string) { + return value.replace(/`/g, '\\`').replace(/\$\{/g, '\\${'); +} From 89c8e3ffe94bd914f2e3035e43a920db7f5f33d7 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 13:40:22 -0500 Subject: [PATCH 04/12] feat(html): add `@astrojs/html` integration --- packages/astro/src/html/renderer.ts | 6 - packages/astro/src/html/server.ts | 23 -- packages/integrations/html/README.md | 49 ++++ packages/integrations/html/package.json | 25 +- packages/integrations/html/src/assets.ts | 231 ------------------ packages/integrations/html/src/index.ts | 10 +- .../html/src/{ => transform}/escape.ts | 4 +- .../src/{transform.ts => transform/index.ts} | 2 - .../html/src/{ => transform}/slots.ts | 13 +- .../html/src/{ => transform}/utils.ts | 5 +- .../integrations/html/test/escape.test.js | 72 ++++++ .../fixtures/escape/src/components/Test.html | 4 + .../fixtures/escape/src/pages/index.astro | 5 + .../html-component/src/components/Test.html | 3 + .../html-component/src/pages/index.astro | 5 + .../fixtures/html-page/src/pages/index.html | 1 + .../slots/src/components/Default.html | 1 + .../fixtures/slots/src/components/Inline.html | 1 + .../fixtures/slots/src/components/Named.html | 3 + .../test/fixtures/slots/src/pages/index.astro | 13 + .../html/test/html-component.test.js | 60 +++++ .../integrations/html/test/html-page.test.js | 56 +++++ packages/integrations/html/test/slots.test.js | 78 ++++++ packages/integrations/html/tsconfig.json | 4 +- pnpm-lock.yaml | 51 +++- 25 files changed, 434 insertions(+), 291 deletions(-) delete mode 100644 packages/astro/src/html/renderer.ts delete mode 100644 packages/astro/src/html/server.ts create mode 100644 packages/integrations/html/README.md delete mode 100644 packages/integrations/html/src/assets.ts rename packages/integrations/html/src/{ => transform}/escape.ts (89%) rename packages/integrations/html/src/{transform.ts => transform/index.ts} (93%) rename packages/integrations/html/src/{ => transform}/slots.ts (70%) rename packages/integrations/html/src/{ => transform}/utils.ts (84%) create mode 100644 packages/integrations/html/test/escape.test.js create mode 100644 packages/integrations/html/test/fixtures/escape/src/components/Test.html create mode 100644 packages/integrations/html/test/fixtures/escape/src/pages/index.astro create mode 100644 packages/integrations/html/test/fixtures/html-component/src/components/Test.html create mode 100644 packages/integrations/html/test/fixtures/html-component/src/pages/index.astro create mode 100644 packages/integrations/html/test/fixtures/html-page/src/pages/index.html create mode 100644 packages/integrations/html/test/fixtures/slots/src/components/Default.html create mode 100644 packages/integrations/html/test/fixtures/slots/src/components/Inline.html create mode 100644 packages/integrations/html/test/fixtures/slots/src/components/Named.html create mode 100644 packages/integrations/html/test/fixtures/slots/src/pages/index.astro create mode 100644 packages/integrations/html/test/html-component.test.js create mode 100644 packages/integrations/html/test/html-page.test.js create mode 100644 packages/integrations/html/test/slots.test.js diff --git a/packages/astro/src/html/renderer.ts b/packages/astro/src/html/renderer.ts deleted file mode 100644 index 786bf5a79bfa..000000000000 --- a/packages/astro/src/html/renderer.ts +++ /dev/null @@ -1,6 +0,0 @@ -const renderer = { - name: 'astro:html', - serverEntrypoint: 'astro/html/server.js' -}; - -export default renderer; diff --git a/packages/astro/src/html/server.ts b/packages/astro/src/html/server.ts deleted file mode 100644 index c55b93fdfbba..000000000000 --- a/packages/astro/src/html/server.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function check(Component: any) { - return !!Component['astro:html']; -} - -export async function renderToStaticMarkup(Component) -) { - const slots: Record = {}; - for (const [key, value] of Object.entries(slotted)) { - const name = slotName(key); - slots[name] = value; - } - - const { result } = this; - try { - const html = await renderJSX(result, jsx(Component, { ...props, ...slots, children })); - return { html }; - } catch (e) {} -} - -export default { - check, - renderToStaticMarkup, -}; diff --git a/packages/integrations/html/README.md b/packages/integrations/html/README.md new file mode 100644 index 000000000000..4702117cb6ff --- /dev/null +++ b/packages/integrations/html/README.md @@ -0,0 +1,49 @@ +# @astrojs/html 📕 + +This **[Astro integration][astro-integration]** enables the usage of [HTML](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics) components and allows you to create pages as `.html` files. + +- [Why HTML?](#why-html) +- [Installation](#installation) +- [Usage](#usage) +- [Configuration](#configuration) +- [Examples](#examples) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [Changelog](#changelog) + +## Why HTML? + +HTML is the standardized format for structuring a web page and its content. With built-in Astro support for `.html` pages and components, migrating from a legacy framework to Astro has never been easier. + +## Installation + +The `@astrojs/html` integration is enabled by default. Currently, it cannot be disabled. + +## Usage + +TODO + +## Configuration + +There are currently no configuration options for the `@astrojs/html` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share. + +## Examples + +- The [Astro HTML example](https://github.com/withastro/astro/tree/latest/examples/with-html) shows how to use HTML files in your Astro project. + +## Troubleshooting + +For help, check out the `#support-threads` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! + +You can also check our [Astro Integration Documentation][astro-integration] for more on integrations. + +## Contributing + +This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this integration. + +[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ +[astro-ui-frameworks]: https://docs.astro.build/en/core-concepts/framework-components/#using-framework-components diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json index 7d94d40dd994..4a295bed6f3a 100644 --- a/packages/integrations/html/package.json +++ b/packages/integrations/html/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/html", - "description": "Add HTML pages to your Astro site", + "description": "Add HTML pages and components to your Astro site", "version": "0.0.1", "type": "module", "types": "./dist/index.d.ts", @@ -15,19 +15,34 @@ "homepage": "https://astro.build", "exports": { ".": "./dist/index.js", - "./server.js": "./dist/server.js", + "./dist/server.js": "./dist/server.js", "./package.json": "./package.json" }, + "keywords": [ + "astro-component", + "renderer", + "mdx" + ], "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"" + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "mocha --exit --timeout 20000" }, "devDependencies": { + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.1", + "astro-scripts": "workspace:*", "astro": "workspace:*", - "astro-scripts": "workspace:*" + "chai": "^4.3.6", + "linkedom": "^0.14.12", + "mocha": "^9.2.2", + "unified": "^10.1.2" }, "dependencies": { - "linkedom": "^0.14.12" + "magic-string": "^0.25.9", + "rehype": "^12.0.1", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2" } } diff --git a/packages/integrations/html/src/assets.ts b/packages/integrations/html/src/assets.ts deleted file mode 100644 index aa48d3836ab9..000000000000 --- a/packages/integrations/html/src/assets.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { pathToFileURL } from 'node:url' -import type { Plugin } from 'unified'; -import type { Root, RootContent, Element } from 'hast'; -import { visit } from 'unist-util-visit'; -import MagicString from 'magic-string'; - -import { replaceAttribute } from './utils.js'; - -const rehypeAssets: Plugin<[{ s: MagicString, imports: Map }], Root> = ({ s, imports }) => { - return (tree, file) => { - const fileURL = new URL(file.path, 'file://'); - function addImport(el: Element, attr: string, value: string) { - let url = value.trim(); - - // Skip if url points to id, e.g. sprite sheets - if (url.startsWith('#')) return - - if (/^https?:\/\//.test(url)) return - - if (el.tagName === 'script') { - url = `${url}`; - } - - if (url.startsWith('.')) { - url = '/@fs/' + new URL(url, fileURL).pathname.slice(1); - } - url += `?url` - let importName = '' - - if (imports.has(url)) { - importName = imports.get(url)! - } else { - importName = `${ASSET_PREFIX}${imports.size}`; - imports.set(url, importName) - } - - replaceAttribute(s, el, attr, `${attr}="\${${importName}}"`); - } - - - visit(tree, (node: Root | RootContent) => { - if (node.type !== 'element') return; - check(node); - function check(el: Element) { - const name = el.tagName; - for (const source of DEFAULT_SOURCES) { - if (source.tag !== name) continue; - - const attributes = el.properties ?? {}; - - if (source.filter && !source.filter({ attributes })) { - continue; - } - - // Check src - source.srcAttributes?.forEach((attr) => { - const value = attributes[attr]; - if (!value) return; - addImport(el, attr, value.toString()); - }); - - // Check srcset - source.srcsetAttributes?.forEach((attr) => { - const value = attributes[attr]; - if (!value) return; - const srcsetRegex = /\s*([^,\s]+).*?(?:,|$)\s*/gm; - let match: RegExpExecArray | null; - while ((match = srcsetRegex.exec(value.toString()))) { - addImport(el, attr, match[1]); - } - }); - } - } - }); - } -} - -export default rehypeAssets; - -export interface AssetSource { - tag: string; - srcAttributes?: string[]; - srcsetAttributes?: string[]; - filter?: (metadata: FilterMetadata) => boolean; -} - -export interface FilterMetadata { - attributes: Record; -} - -export const ASSET_PREFIX = '___ASSET___'; - -const ALLOWED_REL = [ - 'stylesheet', - 'icon', - 'shortcut icon', - 'mask-icon', - 'apple-touch-icon', - 'apple-touch-icon-precomposed', - 'apple-touch-startup-image', - 'manifest', - 'prefetch', - 'preload', -]; - -const ALLOWED_ITEMPROP = [ - 'image', - 'logo', - 'screenshot', - 'thumbnailurl', - 'contenturl', - 'downloadurl', - 'duringmedia', - 'embedurl', - 'installurl', - 'layoutimage', -]; - -const ALLOWED_META_NAME = [ - 'msapplication-tileimage', - 'msapplication-square70x70logo', - 'msapplication-square150x150logo', - 'msapplication-wide310x150logo', - 'msapplication-square310x310logo', - 'msapplication-config', - 'twitter:image', -]; - -const ALLOWED_META_PROPERTY = [ - 'og:image', - 'og:image:url', - 'og:image:secure_url', - 'og:audio', - 'og:audio:secure_url', - 'og:video', - 'og:video:secure_url', - 'vk:image', -]; - -const DEFAULT_SOURCES: AssetSource[] = [ - { - tag: 'script', - srcAttributes: ['src'], - }, - { - tag: 'audio', - srcAttributes: ['src'], - }, - { - tag: 'embed', - srcAttributes: ['src'], - }, - { - tag: 'img', - srcAttributes: ['src'], - srcsetAttributes: ['srcset'], - }, - { - tag: 'input', - srcAttributes: ['src'], - }, - { - tag: 'object', - srcAttributes: ['src'], - }, - { - tag: 'source', - srcAttributes: ['src'], - srcsetAttributes: ['srcset'], - }, - { - tag: 'track', - srcAttributes: ['src'], - }, - { - tag: 'video', - srcAttributes: ['poster', 'src'], - }, - { - tag: 'image', - srcAttributes: ['href', 'xlink:href'], - }, - { - tag: 'use', - srcAttributes: ['href', 'xlink:href'], - }, - { - tag: 'link', - srcAttributes: ['href'], - srcsetAttributes: ['imagesrcset'], - filter({ attributes }) { - if (attributes.rel && ALLOWED_REL.includes(attributes.rel.trim().toLowerCase())) { - return true; - } - - if ( - attributes.itemprop && - ALLOWED_ITEMPROP.includes(attributes.itemprop.trim().toLowerCase()) - ) { - return true; - } - - return false; - }, - }, - { - tag: 'meta', - srcAttributes: ['content'], - filter({ attributes }) { - if (attributes.name && ALLOWED_META_NAME.includes(attributes.name.trim().toLowerCase())) { - return true; - } - - if ( - attributes.property && - ALLOWED_META_PROPERTY.includes(attributes.property.trim().toLowerCase()) - ) { - return true; - } - - if ( - attributes.itemprop && - ALLOWED_ITEMPROP.includes(attributes.itemprop.trim().toLowerCase()) - ) { - return true; - } - - return false; - }, - }, -]; diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index 50350afd0c59..976dba0d667f 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -1,18 +1,20 @@ import type { AstroIntegration, AstroRenderer } from 'astro'; -import { readFileSync } from 'fs'; -import { transform } from './transform.js'; +import { transform } from './transform/index.js'; function getRenderer(): AstroRenderer { return { name: '@astrojs/html', - serverEntrypoint: '@astrojs/html/server.js', + serverEntrypoint: './dist/server.js', }; } function getViteConfiguration() { return { optimizeDeps: { - exclude: ['@astrojs/html/server.js'], + exclude: ['@astrojs/html/dist/server.js'], + }, + ssr: { + external: ['@astrojs/html/dist/server.js'] }, plugins: [ { diff --git a/packages/integrations/html/src/escape.ts b/packages/integrations/html/src/transform/escape.ts similarity index 89% rename from packages/integrations/html/src/escape.ts rename to packages/integrations/html/src/transform/escape.ts index 4ac836d2b62d..8b58056141ee 100644 --- a/packages/integrations/html/src/escape.ts +++ b/packages/integrations/html/src/transform/escape.ts @@ -1,5 +1,5 @@ import type { Plugin } from 'unified'; -import type { Root, RootContent, Element } from 'hast'; +import type { Root, RootContent } from 'hast'; import type MagicString from 'magic-string'; import { visit } from 'unist-util-visit'; @@ -14,7 +14,7 @@ const rehypeEscape: Plugin<[{ s: MagicString }], Root> = ({ s }) => { } } else if (node.type === 'element') { for (const [key, value] of Object.entries(node.properties ?? {})) { - const newKey = escape(key); + const newKey = needsEscape(key) ? escape(key) : key; const newValue = needsEscape(value) ? escape(value) : value; if (newKey === key && newValue === value) continue; replaceAttribute(s, node, key, (value === '') ? newKey : `${newKey}="${newValue}"`); diff --git a/packages/integrations/html/src/transform.ts b/packages/integrations/html/src/transform/index.ts similarity index 93% rename from packages/integrations/html/src/transform.ts rename to packages/integrations/html/src/transform/index.ts index 0a42c920db71..de6431ea7649 100644 --- a/packages/integrations/html/src/transform.ts +++ b/packages/integrations/html/src/transform/index.ts @@ -3,7 +3,6 @@ import { rehype } from 'rehype'; import { VFile } from 'vfile'; import escape from './escape.js'; import slots, { SLOT_PREFIX } from './slots.js'; -import assets from './assets.js'; export async function transform(code: string, id: string) { const s = new MagicString(code, { filename: id }); @@ -11,7 +10,6 @@ export async function transform(code: string, id: string) { const parser = rehype() .data('settings', { fragment: true }) .use(escape, { s }) - .use(assets, { s, imports }) .use(slots, { s }); const vfile = new VFile({ value: code, path: id }) diff --git a/packages/integrations/html/src/slots.ts b/packages/integrations/html/src/transform/slots.ts similarity index 70% rename from packages/integrations/html/src/slots.ts rename to packages/integrations/html/src/transform/slots.ts index 407253ded5d8..c8cb32f13a97 100644 --- a/packages/integrations/html/src/slots.ts +++ b/packages/integrations/html/src/transform/slots.ts @@ -1,10 +1,10 @@ import type { Plugin } from 'unified'; import type { Root, RootContent } from 'hast'; + import { visit } from 'unist-util-visit'; import MagicString from 'magic-string'; import { escape } from './utils.js'; - const rehypeSlots: Plugin<[{ s: MagicString }], Root> = ({ s }) => { return (tree, file) => { visit(tree, (node: Root | RootContent, index, parent) => { @@ -25,14 +25,3 @@ const rehypeSlots: Plugin<[{ s: MagicString }], Root> = ({ s }) => { export default rehypeSlots; export const SLOT_PREFIX = `___SLOTS___`; - -// export function transformSlots(document: Document) { -// for (const slot of document.querySelectorAll('slot')) { -// if (slot.closest('template')) continue; -// if (slot.hasAttribute('is:inline')) { -// slot.removeAttribute('is:inline'); -// continue; -// } -// slot.replaceWith(document.createTextNode(`\${${SLOT_PREFIX}['${slot.getAttribute('name') || 'default'}'] ?? ${JSON.stringify(slot.innerHTML)}}`)); -// } -// } diff --git a/packages/integrations/html/src/utils.ts b/packages/integrations/html/src/transform/utils.ts similarity index 84% rename from packages/integrations/html/src/utils.ts rename to packages/integrations/html/src/transform/utils.ts index 51b0e0030f08..313bfe662c62 100644 --- a/packages/integrations/html/src/utils.ts +++ b/packages/integrations/html/src/transform/utils.ts @@ -1,7 +1,7 @@ import type { Element } from 'hast'; import MagicString from 'magic-string'; -const splitAttrsTokenizer = /([a-z0-9_\:\-]*)\s*?=\s*?(['"]?)(.*?)\2\s+/gim; +const splitAttrsTokenizer = /([\$\{\}\@a-z0-9_\:\-]*)\s*?=\s*?(['"]?)(.*?)\2\s+/gim; export function replaceAttribute(s: MagicString, node: Element, key: string, newValue: string) { splitAttrsTokenizer.lastIndex = 0; @@ -10,7 +10,8 @@ export function replaceAttribute(s: MagicString, node: Element, key: string, new if (offset === -1) return; const start = node.position!.start.offset! + offset; const tokens = text.slice(offset).split(splitAttrsTokenizer); - if (tokens[0].trim() === key) { + const token = tokens[0].replace(/([^>])(\>[\s\S]*$)/gmi, '$1'); + if (token.trim() === key) { const end = start + key.length; s.overwrite(start, end, newValue) } else { diff --git a/packages/integrations/html/test/escape.test.js b/packages/integrations/html/test/escape.test.js new file mode 100644 index 000000000000..a7261355363e --- /dev/null +++ b/packages/integrations/html/test/escape.test.js @@ -0,0 +1,72 @@ +import integration from '@astrojs/html'; + +import { expect } from 'chai'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +describe('HTML Escape', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/escape/', import.meta.url), + integrations: [integration()], + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const div = document.querySelector('div'); + expect(div.textContent).to.equal('${foo}'); + + const span = document.querySelector('span'); + expect(span.getAttribute('${attr}')).to.equal(""); + + const ce = document.querySelector('custom-element'); + expect(ce.getAttribute('x-data')).to.equal("`${test}`"); + + const script = document.querySelector('script'); + expect(script.textContent).to.equal('console.log(`hello ${"world"}!`)'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const div = document.querySelector('div'); + expect(div.textContent).to.equal('${foo}'); + + const span = document.querySelector('span'); + expect(span.getAttribute('${attr}')).to.equal(""); + + const ce = document.querySelector('custom-element'); + expect(ce.getAttribute('x-data')).to.equal("`${test}`"); + + const script = document.querySelector('script'); + expect(script.textContent).to.equal('console.log(`hello ${"world"}!`)'); + }); + }); +}); diff --git a/packages/integrations/html/test/fixtures/escape/src/components/Test.html b/packages/integrations/html/test/fixtures/escape/src/components/Test.html new file mode 100644 index 000000000000..6ee4b52000dd --- /dev/null +++ b/packages/integrations/html/test/fixtures/escape/src/components/Test.html @@ -0,0 +1,4 @@ +
${foo}
+ + + diff --git a/packages/integrations/html/test/fixtures/escape/src/pages/index.astro b/packages/integrations/html/test/fixtures/escape/src/pages/index.astro new file mode 100644 index 000000000000..821d12538b9a --- /dev/null +++ b/packages/integrations/html/test/fixtures/escape/src/pages/index.astro @@ -0,0 +1,5 @@ +--- +import Test from '../components/Test.html'; +--- + + diff --git a/packages/integrations/html/test/fixtures/html-component/src/components/Test.html b/packages/integrations/html/test/fixtures/html-component/src/components/Test.html new file mode 100644 index 000000000000..ec16bf314cf1 --- /dev/null +++ b/packages/integrations/html/test/fixtures/html-component/src/components/Test.html @@ -0,0 +1,3 @@ +

Hello component!

+ +
bar
diff --git a/packages/integrations/html/test/fixtures/html-component/src/pages/index.astro b/packages/integrations/html/test/fixtures/html-component/src/pages/index.astro new file mode 100644 index 000000000000..821d12538b9a --- /dev/null +++ b/packages/integrations/html/test/fixtures/html-component/src/pages/index.astro @@ -0,0 +1,5 @@ +--- +import Test from '../components/Test.html'; +--- + + diff --git a/packages/integrations/html/test/fixtures/html-page/src/pages/index.html b/packages/integrations/html/test/fixtures/html-page/src/pages/index.html new file mode 100644 index 000000000000..5d4d2282712e --- /dev/null +++ b/packages/integrations/html/test/fixtures/html-page/src/pages/index.html @@ -0,0 +1 @@ +

Hello page!

diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Default.html b/packages/integrations/html/test/fixtures/slots/src/components/Default.html new file mode 100644 index 000000000000..9f012095fbf0 --- /dev/null +++ b/packages/integrations/html/test/fixtures/slots/src/components/Default.html @@ -0,0 +1 @@ +
diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Inline.html b/packages/integrations/html/test/fixtures/slots/src/components/Inline.html new file mode 100644 index 000000000000..121164e380e4 --- /dev/null +++ b/packages/integrations/html/test/fixtures/slots/src/components/Inline.html @@ -0,0 +1 @@ +
diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Named.html b/packages/integrations/html/test/fixtures/slots/src/components/Named.html new file mode 100644 index 000000000000..0993cfb27263 --- /dev/null +++ b/packages/integrations/html/test/fixtures/slots/src/components/Named.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/packages/integrations/html/test/fixtures/slots/src/pages/index.astro b/packages/integrations/html/test/fixtures/slots/src/pages/index.astro new file mode 100644 index 000000000000..aa4a3bd2587a --- /dev/null +++ b/packages/integrations/html/test/fixtures/slots/src/pages/index.astro @@ -0,0 +1,13 @@ +--- +import Default from '../components/Default.html'; +import Named from '../components/Named.html'; +import Inline from '../components/Inline.html'; +--- + +Default + + A + B + C + + diff --git a/packages/integrations/html/test/html-component.test.js b/packages/integrations/html/test/html-component.test.js new file mode 100644 index 000000000000..b647c9d3073a --- /dev/null +++ b/packages/integrations/html/test/html-component.test.js @@ -0,0 +1,60 @@ +import integration from '@astrojs/html'; + +import { expect } from 'chai'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +describe('HTML Component', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/html-component/', import.meta.url), + integrations: [integration()], + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + const foo = document.querySelector('#foo'); + + expect(h1.textContent).to.equal('Hello component!'); + expect(foo.textContent).to.equal('bar'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + const foo = document.querySelector('#foo'); + + expect(h1.textContent).to.equal('Hello component!'); + expect(foo.textContent).to.equal('bar'); + }); + }); +}); diff --git a/packages/integrations/html/test/html-page.test.js b/packages/integrations/html/test/html-page.test.js new file mode 100644 index 000000000000..726f4c824fbf --- /dev/null +++ b/packages/integrations/html/test/html-page.test.js @@ -0,0 +1,56 @@ +import integration from '@astrojs/html'; + +import { expect } from 'chai'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +describe('HTML Page', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/html-page/', import.meta.url), + integrations: [integration()], + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + + expect(h1.textContent).to.equal('Hello page!'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const h1 = document.querySelector('h1'); + + expect(h1.textContent).to.equal('Hello page!'); + }); + }); +}); diff --git a/packages/integrations/html/test/slots.test.js b/packages/integrations/html/test/slots.test.js new file mode 100644 index 000000000000..aeb66b3bedc1 --- /dev/null +++ b/packages/integrations/html/test/slots.test.js @@ -0,0 +1,78 @@ +import integration from '@astrojs/html'; + +import { expect } from 'chai'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +describe('HTML Slots', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/slots/', import.meta.url), + integrations: [integration()], + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const slotDefault = document.querySelector('#default'); + expect(slotDefault.textContent).to.equal('Default'); + + const a = document.querySelector('#a'); + expect(a.textContent.trim()).to.equal('A'); + + const b = document.querySelector('#b'); + expect(b.textContent.trim()).to.equal('B'); + + const c = document.querySelector('#c'); + expect(c.textContent.trim()).to.equal('C'); + + const inline = document.querySelector('#inline'); + expect(inline.innerHTML).to.equal(''); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const slotDefault = document.querySelector('#default'); + expect(slotDefault.textContent).to.equal('Default'); + + const a = document.querySelector('#a'); + expect(a.textContent.trim()).to.equal('A'); + + const b = document.querySelector('#b'); + expect(b.textContent.trim()).to.equal('B'); + + const c = document.querySelector('#c'); + expect(c.textContent.trim()).to.equal('C'); + + const inline = document.querySelector('#inline'); + expect(inline.innerHTML).to.equal(''); + }); + }); +}); diff --git a/packages/integrations/html/tsconfig.json b/packages/integrations/html/tsconfig.json index 44baf375c882..af1b43564edc 100644 --- a/packages/integrations/html/tsconfig.json +++ b/packages/integrations/html/tsconfig.json @@ -3,8 +3,8 @@ "include": ["src"], "compilerOptions": { "allowJs": true, - "module": "ES2020", + "module": "ES2022", "outDir": "./dist", - "target": "ES2020" + "target": "ES2022" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bc9076bbb7f..3758a34b6cd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2018,14 +2018,32 @@ importers: packages/integrations/html: specifiers: + '@types/chai': ^4.3.1 + '@types/mocha': ^9.1.1 astro: workspace:* astro-scripts: workspace:* + chai: ^4.3.6 linkedom: ^0.14.12 + magic-string: ^0.25.9 + mocha: ^9.2.2 + rehype: ^12.0.1 + unified: ^10.1.2 + unist-util-visit: ^4.1.0 + vfile: ^5.3.2 dependencies: - linkedom: 0.14.12 + magic-string: 0.25.9 + rehype: 12.0.1 + unist-util-visit: 4.1.0 + vfile: 5.3.4 devDependencies: + '@types/chai': 4.3.1 + '@types/mocha': 9.1.1 astro: link:../../astro astro-scripts: link:../../../scripts + chai: 4.3.6 + linkedom: 0.14.12 + mocha: 9.2.2 + unified: 10.1.2 packages/integrations/image: specifiers: @@ -9510,7 +9528,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /concurrently/7.2.2: resolution: {integrity: sha512-DcQkI0ruil5BA/g7Xy3EWySGrFJovF5RYAYxwGvv9Jf9q9B1v3jPFP2tl6axExNf1qgF30kjoNYrangZ0ey4Aw==} @@ -9612,6 +9630,7 @@ packages: domhandler: 5.0.3 domutils: 3.0.1 nth-check: 2.1.1 + dev: true /css-selector-parser/1.4.1: resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==} @@ -9619,6 +9638,7 @@ packages: /css-what/6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + dev: true /cssdb/6.6.3: resolution: {integrity: sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA==} @@ -9631,6 +9651,7 @@ packages: /cssom/0.5.0: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true /csstype/2.6.20: resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==} @@ -9907,15 +9928,18 @@ packages: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.3.1 + dev: true /domelementtype/2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true /domhandler/5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} dependencies: domelementtype: 2.3.0 + dev: true /domutils/3.0.1: resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} @@ -9923,6 +9947,7 @@ packages: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 + dev: true /dotenv/8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} @@ -9992,6 +10017,7 @@ packages: /entities/4.3.1: resolution: {integrity: sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==} engines: {node: '>=0.12'} + dev: true /eol/0.9.1: resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} @@ -11229,6 +11255,7 @@ packages: domhandler: 5.0.3 domutils: 3.0.1 entities: 4.3.1 + dev: true /http-errors/2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -11870,6 +11897,7 @@ packages: html-escaper: 3.0.3 htmlparser2: 8.0.1 uhyphen: 0.1.0 + dev: true /lit-element/3.2.1: resolution: {integrity: sha512-2PxyE9Yq9Jyo/YBK2anycaHcqo93YvB5D+24JxloPVqryW/BOXekne+jGsm0Ke3E5E2v7CDgkmpEmCAzYfrHCQ==} @@ -14160,6 +14188,15 @@ packages: unist-util-visit: 4.1.0 dev: true + /rehype-parse/8.0.4: + resolution: {integrity: sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==} + dependencies: + '@types/hast': 2.3.4 + hast-util-from-parse5: 7.1.0 + parse5: 6.0.1 + unified: 10.1.2 + dev: false + /rehype-raw/6.1.1: resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==} dependencies: @@ -14194,6 +14231,15 @@ packages: '@jsdevtools/rehype-toc': 3.0.2 dev: true + /rehype/12.0.1: + resolution: {integrity: sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==} + dependencies: + '@types/hast': 2.3.4 + rehype-parse: 8.0.4 + rehype-stringify: 9.0.3 + unified: 10.1.2 + dev: false + /remark-code-titles/0.1.2: resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==} dependencies: @@ -15461,6 +15507,7 @@ packages: /uhyphen/0.1.0: resolution: {integrity: sha512-o0QVGuFg24FK765Qdd5kk0zU/U4dEsCtN/GSiwNI9i8xsSVtjIAOdTaVhLwZ1nrbWxFVMxNDDl+9fednsOMsBw==} + dev: true /unbox-primitive/1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} From 3226f8eb62ae356c33da4e08ac41afd80a6ec98e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 14:20:32 -0500 Subject: [PATCH 05/12] feat(html): add html support to astro core --- packages/astro/package.json | 1 + packages/astro/src/core/config.ts | 5 ++ .../fixtures/html-page/src/pages/index.html | 1 + packages/astro/test/html-page.test.js | 49 +++++++++++++++++++ packages/astro/test/test-utils.js | 5 ++ pnpm-lock.yaml | 34 +++++++++++++ 6 files changed, 95 insertions(+) create mode 100644 packages/astro/test/fixtures/html-page/src/pages/index.html create mode 100644 packages/astro/test/html-page.test.js diff --git a/packages/astro/package.json b/packages/astro/package.json index ee82786b3478..bb7f64854708 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -83,6 +83,7 @@ }, "dependencies": { "@astrojs/compiler": "^0.20.0", + "@astrojs/html": "^0.0.1", "@astrojs/language-server": "^0.20.0", "@astrojs/markdown-remark": "^0.12.0", "@astrojs/prism": "0.6.1", diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 75fabdc89857..766b4620097b 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -332,6 +332,11 @@ export async function validateConfig( adapter: undefined, }, }; + if (!result.integrations.find((integration) => integration.name === '@astrojs/html')) { + // @ts-ignore types can't be resolved, but it's here! + const { default: html } = await import('@astrojs/html'); + result.integrations.push(html()); + } if (result.integrations.find((integration) => integration.name === '@astrojs/mdx')) { // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('../jsx/renderer.js'); diff --git a/packages/astro/test/fixtures/html-page/src/pages/index.html b/packages/astro/test/fixtures/html-page/src/pages/index.html new file mode 100644 index 000000000000..5d4d2282712e --- /dev/null +++ b/packages/astro/test/fixtures/html-page/src/pages/index.html @@ -0,0 +1 @@ +

Hello page!

diff --git a/packages/astro/test/html-page.test.js b/packages/astro/test/html-page.test.js new file mode 100644 index 000000000000..cbda4a30ae46 --- /dev/null +++ b/packages/astro/test/html-page.test.js @@ -0,0 +1,49 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('HTML Page', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/html-page/', + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + let $ = cheerio.load(html); + expect($('h1')).to.have.a.lengthOf(1, 'renders h1'); + expect($('h1').text().trim()).to.have.equal('Hello page!'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + let $ = cheerio.load(html); + expect($('h1')).to.have.a.lengthOf(1, 'renders h1'); + expect($('h1').text().trim()).to.have.equal('Hello page!'); + }); + }); +}); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index ab43a94cc8ce..fdea44d3a321 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -83,6 +83,11 @@ export async function loadFixture(inlineConfig) { if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { config.base = inlineConfig.base + '/'; } + if (!config.integrations.find((integration) => integration.name === '@astrojs/html')) { + // @ts-ignore types can't be resolved, but it's here! + const { default: html } = await import('@astrojs/html'); + config.integrations.push(html()); + } if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) { // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('astro/jsx/renderer.js'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3758a34b6cd7..eb5d2427b9f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -439,6 +439,7 @@ importers: packages/astro: specifiers: '@astrojs/compiler': ^0.20.0 + '@astrojs/html': ^0.0.1 '@astrojs/language-server': ^0.20.0 '@astrojs/markdown-remark': ^0.12.0 '@astrojs/prism': 0.6.1 @@ -524,6 +525,7 @@ importers: zod: ^3.17.3 dependencies: '@astrojs/compiler': 0.20.0 + '@astrojs/html': link:../integrations/html '@astrojs/language-server': 0.20.0 '@astrojs/markdown-remark': link:../markdown/remark '@astrojs/prism': link:../astro-prism @@ -2045,6 +2047,38 @@ importers: mocha: 9.2.2 unified: 10.1.2 + packages/integrations/html/test/fixtures/escape: + specifiers: + '@astrojs/html': workspace:* + astro: workspace:* + dependencies: + '@astrojs/html': link:../../.. + astro: link:../../../../../astro + + packages/integrations/html/test/fixtures/html-component: + specifiers: + '@astrojs/html': workspace:* + astro: workspace:* + dependencies: + '@astrojs/html': link:../../.. + astro: link:../../../../../astro + + packages/integrations/html/test/fixtures/html-page: + specifiers: + '@astrojs/html': workspace:* + astro: workspace:* + dependencies: + '@astrojs/html': link:../../.. + astro: link:../../../../../astro + + packages/integrations/html/test/fixtures/slots: + specifiers: + '@astrojs/html': workspace:* + astro: workspace:* + dependencies: + '@astrojs/html': link:../../.. + astro: link:../../../../../astro + packages/integrations/image: specifiers: '@types/etag': ^1.8.1 From c46ac5be9254fac07f5c0c75cd04f627f510a3a5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 14:20:48 -0500 Subject: [PATCH 06/12] test(html): update html tests with package.json files --- packages/integrations/html/package.json | 6 +++--- packages/integrations/html/server.js | 13 +++++++++++++ packages/integrations/html/src/index.ts | 2 +- .../html/test/fixtures/escape/package.json | 9 +++++++++ .../html/test/fixtures/html-component/package.json | 9 +++++++++ .../html/test/fixtures/html-page/package.json | 9 +++++++++ .../html/test/fixtures/slots/package.json | 9 +++++++++ packages/integrations/html/tsconfig.json | 2 +- 8 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 packages/integrations/html/server.js create mode 100644 packages/integrations/html/test/fixtures/escape/package.json create mode 100644 packages/integrations/html/test/fixtures/html-component/package.json create mode 100644 packages/integrations/html/test/fixtures/html-page/package.json create mode 100644 packages/integrations/html/test/fixtures/slots/package.json diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json index 4a295bed6f3a..5114d7aaab72 100644 --- a/packages/integrations/html/package.json +++ b/packages/integrations/html/package.json @@ -9,19 +9,19 @@ "repository": { "type": "git", "url": "https://github.com/withastro/astro.git", - "directory": "packages/integrations/node" + "directory": "packages/integrations/html" }, "bugs": "https://github.com/withastro/astro/issues", "homepage": "https://astro.build", "exports": { ".": "./dist/index.js", - "./dist/server.js": "./dist/server.js", + "./server.js": "./dist/server.js", "./package.json": "./package.json" }, "keywords": [ "astro-component", "renderer", - "mdx" + "html" ], "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", diff --git a/packages/integrations/html/server.js b/packages/integrations/html/server.js new file mode 100644 index 000000000000..f35e9b3e0fb3 --- /dev/null +++ b/packages/integrations/html/server.js @@ -0,0 +1,13 @@ +function check(Component) { + return Component && typeof Component === 'object' && Component['astro:html']; +} + +async function renderToStaticMarkup(Component, _, slots) { + const html = Component.render({ slots }); + return { html }; +} + +export default { + check, + renderToStaticMarkup, +}; diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index 976dba0d667f..5283103d18a3 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -4,7 +4,7 @@ import { transform } from './transform/index.js'; function getRenderer(): AstroRenderer { return { name: '@astrojs/html', - serverEntrypoint: './dist/server.js', + serverEntrypoint: '@astrojs/html/server.js', }; } diff --git a/packages/integrations/html/test/fixtures/escape/package.json b/packages/integrations/html/test/fixtures/escape/package.json new file mode 100644 index 000000000000..a38b4db1c898 --- /dev/null +++ b/packages/integrations/html/test/fixtures/escape/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/html-escape", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/html": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/html/test/fixtures/html-component/package.json b/packages/integrations/html/test/fixtures/html-component/package.json new file mode 100644 index 000000000000..3d29a96932f0 --- /dev/null +++ b/packages/integrations/html/test/fixtures/html-component/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/html-component", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/html": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/html/test/fixtures/html-page/package.json b/packages/integrations/html/test/fixtures/html-page/package.json new file mode 100644 index 000000000000..70eba8ed493e --- /dev/null +++ b/packages/integrations/html/test/fixtures/html-page/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/html-page", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/html": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/html/test/fixtures/slots/package.json b/packages/integrations/html/test/fixtures/slots/package.json new file mode 100644 index 000000000000..700e7d7991db --- /dev/null +++ b/packages/integrations/html/test/fixtures/slots/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/html-slots", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/html": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/html/tsconfig.json b/packages/integrations/html/tsconfig.json index af1b43564edc..d31d6a5e5ce6 100644 --- a/packages/integrations/html/tsconfig.json +++ b/packages/integrations/html/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../../tsconfig.base.json", - "include": ["src"], + "include": ["src", "server.ts"], "compilerOptions": { "allowJs": true, "module": "ES2022", From fd12cb646b30b416392d7b8a446bfaea0c767b93 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 14:21:23 -0500 Subject: [PATCH 07/12] chore: add changeset --- .changeset/strong-stingrays-compete.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-stingrays-compete.md diff --git a/.changeset/strong-stingrays-compete.md b/.changeset/strong-stingrays-compete.md new file mode 100644 index 000000000000..f01d4cc8c4a2 --- /dev/null +++ b/.changeset/strong-stingrays-compete.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Add support for `.html` components and pages From d5a527f0dd46ccc579dffa660407b43630cebf7e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 14:38:35 -0500 Subject: [PATCH 08/12] fix: remove import cycle --- packages/integrations/html/package.json | 1 - packages/integrations/html/src/index.ts | 5 ++--- pnpm-lock.yaml | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json index 5114d7aaab72..05a73ab1e3ad 100644 --- a/packages/integrations/html/package.json +++ b/packages/integrations/html/package.json @@ -33,7 +33,6 @@ "@types/chai": "^4.3.1", "@types/mocha": "^9.1.1", "astro-scripts": "workspace:*", - "astro": "workspace:*", "chai": "^4.3.6", "linkedom": "^0.14.12", "mocha": "^9.2.2", diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index 5283103d18a3..5604cdfadb63 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -1,7 +1,6 @@ -import type { AstroIntegration, AstroRenderer } from 'astro'; import { transform } from './transform/index.js'; -function getRenderer(): AstroRenderer { +function getRenderer() { return { name: '@astrojs/html', serverEntrypoint: '@astrojs/html/server.js', @@ -31,7 +30,7 @@ function getViteConfiguration() { }; } -export default function createIntegration(): AstroIntegration { +export default function createIntegration() { return { name: '@astrojs/html', hooks: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb5d2427b9f3..612a46239eb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2022,7 +2022,6 @@ importers: specifiers: '@types/chai': ^4.3.1 '@types/mocha': ^9.1.1 - astro: workspace:* astro-scripts: workspace:* chai: ^4.3.6 linkedom: ^0.14.12 @@ -2040,7 +2039,6 @@ importers: devDependencies: '@types/chai': 4.3.1 '@types/mocha': 9.1.1 - astro: link:../../astro astro-scripts: link:../../../scripts chai: 4.3.6 linkedom: 0.14.12 From f1d308332d552750a60fb6830ca1a0405bddc06b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Jul 2022 14:45:13 -0500 Subject: [PATCH 09/12] chore: fix types --- packages/integrations/html/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts index 5604cdfadb63..598d073a1591 100644 --- a/packages/integrations/html/src/index.ts +++ b/packages/integrations/html/src/index.ts @@ -34,7 +34,7 @@ export default function createIntegration() { return { name: '@astrojs/html', hooks: { - 'astro:config:setup': ({ addRenderer, updateConfig, addPageExtension }) => { + 'astro:config:setup': ({ addRenderer, updateConfig, addPageExtension }: any) => { addRenderer(getRenderer()) updateConfig({ vite: getViteConfiguration() }); addPageExtension('.html'); From 3a79d342875c6a86a214f7915f49a017d11311ec Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 21 Jul 2022 16:36:39 -0500 Subject: [PATCH 10/12] refactor: remove @astrojs/html, add to core --- packages/astro/package.json | 4 +- packages/astro/src/core/config.ts | 7 +- packages/astro/src/core/create-vite.ts | 2 + packages/astro/src/core/dev/index.ts | 9 +- packages/astro/src/core/errors.ts | 3 +- packages/astro/src/runtime/server/index.ts | 16 ++++ packages/astro/src/vite-plugin-html/index.ts | 14 +++ .../src/vite-plugin-html}/transform/escape.ts | 0 .../src/vite-plugin-html}/transform/index.ts | 0 .../src/vite-plugin-html}/transform/slots.ts | 0 .../src/vite-plugin-html}/transform/utils.ts | 0 .../test/fixtures/html-component/package.json | 1 - .../html-component/src/components/Test.html | 0 .../html-component}/src/pages/index.astro | 0 .../test/fixtures/html-escape}/package.json | 1 - .../html-escape}/src/components/Test.html | 0 .../html-escape}/src/pages/index.astro | 0 .../test/fixtures/html-page/package.json | 1 - .../test/fixtures/html-slots}/package.json | 1 - .../html-slots}/src/components/Default.html | 0 .../html-slots}/src/components/Inline.html | 0 .../html-slots}/src/components/Named.html | 0 .../html-slots}/src/pages/index.astro | 0 .../test/html-component.test.js} | 27 +++--- packages/astro/test/html-escape.test.js | 69 ++++++++++++++ packages/astro/test/html-page.test.js | 16 ++-- packages/astro/test/html-slots.test.js | 75 +++++++++++++++ packages/astro/test/test-utils.js | 5 - packages/integrations/html/README.md | 49 ---------- packages/integrations/html/package.json | 47 ---------- packages/integrations/html/server.js | 13 --- packages/integrations/html/src/index.ts | 44 --------- packages/integrations/html/src/server.ts | 13 --- .../integrations/html/test/escape.test.js | 72 --------------- .../fixtures/html-page/src/pages/index.html | 1 - .../html/test/html-component.test.js | 60 ------------ packages/integrations/html/test/slots.test.js | 78 ---------------- packages/integrations/html/tsconfig.json | 10 -- pnpm-lock.yaml | 91 ++++++------------- 39 files changed, 237 insertions(+), 492 deletions(-) create mode 100644 packages/astro/src/vite-plugin-html/index.ts rename packages/{integrations/html/src => astro/src/vite-plugin-html}/transform/escape.ts (100%) rename packages/{integrations/html/src => astro/src/vite-plugin-html}/transform/index.ts (100%) rename packages/{integrations/html/src => astro/src/vite-plugin-html}/transform/slots.ts (100%) rename packages/{integrations/html/src => astro/src/vite-plugin-html}/transform/utils.ts (100%) rename packages/{integrations/html => astro}/test/fixtures/html-component/package.json (78%) rename packages/{integrations/html => astro}/test/fixtures/html-component/src/components/Test.html (100%) rename packages/{integrations/html/test/fixtures/escape => astro/test/fixtures/html-component}/src/pages/index.astro (100%) rename packages/{integrations/html/test/fixtures/escape => astro/test/fixtures/html-escape}/package.json (77%) rename packages/{integrations/html/test/fixtures/escape => astro/test/fixtures/html-escape}/src/components/Test.html (100%) rename packages/{integrations/html/test/fixtures/html-component => astro/test/fixtures/html-escape}/src/pages/index.astro (100%) rename packages/{integrations/html => astro}/test/fixtures/html-page/package.json (77%) rename packages/{integrations/html/test/fixtures/slots => astro/test/fixtures/html-slots}/package.json (77%) rename packages/{integrations/html/test/fixtures/slots => astro/test/fixtures/html-slots}/src/components/Default.html (100%) rename packages/{integrations/html/test/fixtures/slots => astro/test/fixtures/html-slots}/src/components/Inline.html (100%) rename packages/{integrations/html/test/fixtures/slots => astro/test/fixtures/html-slots}/src/components/Named.html (100%) rename packages/{integrations/html/test/fixtures/slots => astro/test/fixtures/html-slots}/src/pages/index.astro (100%) rename packages/{integrations/html/test/html-page.test.js => astro/test/html-component.test.js} (53%) create mode 100644 packages/astro/test/html-escape.test.js create mode 100644 packages/astro/test/html-slots.test.js delete mode 100644 packages/integrations/html/README.md delete mode 100644 packages/integrations/html/package.json delete mode 100644 packages/integrations/html/server.js delete mode 100644 packages/integrations/html/src/index.ts delete mode 100644 packages/integrations/html/src/server.ts delete mode 100644 packages/integrations/html/test/escape.test.js delete mode 100644 packages/integrations/html/test/fixtures/html-page/src/pages/index.html delete mode 100644 packages/integrations/html/test/html-component.test.js delete mode 100644 packages/integrations/html/test/slots.test.js delete mode 100644 packages/integrations/html/tsconfig.json diff --git a/packages/astro/package.json b/packages/astro/package.json index bb7f64854708..b02f29011930 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -83,7 +83,6 @@ }, "dependencies": { "@astrojs/compiler": "^0.20.0", - "@astrojs/html": "^0.0.1", "@astrojs/language-server": "^0.20.0", "@astrojs/markdown-remark": "^0.12.0", "@astrojs/prism": "0.6.1", @@ -126,6 +125,7 @@ "prismjs": "^1.28.0", "prompts": "^2.4.2", "recast": "^0.20.5", + "rehype": "^12.0.1", "resolve": "^1.22.0", "rollup": "^2.75.6", "semver": "^7.3.7", @@ -137,6 +137,8 @@ "strip-ansi": "^7.0.1", "supports-esm": "^1.0.0", "tsconfig-resolver": "^3.0.1", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2", "vite": "3.0.2", "yargs-parser": "^21.0.1", "zod": "^3.17.3" diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 766b4620097b..bbfad04396a9 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -325,18 +325,13 @@ export async function validateConfig( const result = { ...(await AstroConfigRelativeSchema.parseAsync(userConfig)), _ctx: { - pageExtensions: ['.astro', '.md'], + pageExtensions: ['.astro', '.md', '.html'], scripts: [], renderers: [], injectedRoutes: [], adapter: undefined, }, }; - if (!result.integrations.find((integration) => integration.name === '@astrojs/html')) { - // @ts-ignore types can't be resolved, but it's here! - const { default: html } = await import('@astrojs/html'); - result.integrations.push(html()); - } if (result.integrations.find((integration) => integration.name === '@astrojs/mdx')) { // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('../jsx/renderer.js'); diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index c853b29c78a6..72e357470633 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -11,6 +11,7 @@ import configAliasVitePlugin from '../vite-plugin-config-alias/index.js'; import envVitePlugin from '../vite-plugin-env/index.js'; import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-container/index.js'; import jsxVitePlugin from '../vite-plugin-jsx/index.js'; +import htmlVitePlugin from '../vite-plugin-html/index.js'; import markdownVitePlugin from '../vite-plugin-markdown/index.js'; import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import { createCustomViteLogger } from './errors.js'; @@ -73,6 +74,7 @@ export async function createVite( mode === 'dev' && astroViteServerPlugin({ config: astroConfig, logging }), envVitePlugin({ config: astroConfig }), markdownVitePlugin({ config: astroConfig }), + htmlVitePlugin(), jsxVitePlugin({ config: astroConfig, logging }), astroPostprocessVitePlugin({ config: astroConfig }), astroIntegrationsContainerPlugin({ config: astroConfig }), diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index df26858e5e26..eff09c5a4e58 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -45,14 +45,7 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro mode: 'development', server: { host }, optimizeDeps: { - include: [ - 'astro/client/idle.js', - 'astro/client/load.js', - 'astro/client/visible.js', - 'astro/client/media.js', - 'astro/client/only.js', - ...rendererClientEntries, - ], + include: rendererClientEntries, }, }, { astroConfig: config, logging: options.logging, mode: 'dev' } diff --git a/packages/astro/src/core/errors.ts b/packages/astro/src/core/errors.ts index 49bf4b6f2fab..13eec5cb8739 100644 --- a/packages/astro/src/core/errors.ts +++ b/packages/astro/src/core/errors.ts @@ -58,7 +58,8 @@ export function fixViteErrorMessage(_err: unknown, server: ViteDevServer, filePa const content = fs.readFileSync(fileURLToPath(filePath)).toString(); const lns = content.split('\n'); const line = lns.findIndex((ln) => ln.includes(importName)); - const column = lns[line].indexOf(importName); + if (line == -1) return err; + const column = lns[line]?.indexOf(importName); if (!(err as any).id) { (err as any).id = `${fileURLToPath(filePath)}:${line + 1}:${column + 1}`; } diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 59473798d05e..77673e953c21 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -210,6 +210,21 @@ export async function renderComponent( return markHTMLString(children); } + if (Component && typeof Component === 'object' && (Component as any)['astro:html']) { + const children: Record = {}; + if (slots) { + await Promise.all( + Object.entries(slots).map(([key, value]) => + renderSlot(result, value as string).then((output) => { + children[key] = output; + }) + ) + ); + } + const html = (Component as any).render({ slots: children }); + return markHTMLString(html); + } + if (Component && (Component as any).isAstroComponentFactory) { async function* renderAstroComponentInline(): AsyncGenerator { let iterable = await renderToIterable(result, Component as any, _props, slots); @@ -279,6 +294,7 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`') ) ); } + // Call the renderers `check` hook to see if any claim this component. let renderer: SSRLoadedRenderer | undefined; if (metadata.hydrate !== 'only') { diff --git a/packages/astro/src/vite-plugin-html/index.ts b/packages/astro/src/vite-plugin-html/index.ts new file mode 100644 index 000000000000..c593bc2015c9 --- /dev/null +++ b/packages/astro/src/vite-plugin-html/index.ts @@ -0,0 +1,14 @@ +import { transform } from './transform/index.js'; + +export default function html() { + return { + name: 'astro:html', + options(options: any) { + options.plugins = options.plugins?.filter((p: any) => p.name !== 'vite:build-html'); + }, + async transform(source: string, id: string) { + if (!id.endsWith('.html')) return; + return await transform(source, id); + } + } +} diff --git a/packages/integrations/html/src/transform/escape.ts b/packages/astro/src/vite-plugin-html/transform/escape.ts similarity index 100% rename from packages/integrations/html/src/transform/escape.ts rename to packages/astro/src/vite-plugin-html/transform/escape.ts diff --git a/packages/integrations/html/src/transform/index.ts b/packages/astro/src/vite-plugin-html/transform/index.ts similarity index 100% rename from packages/integrations/html/src/transform/index.ts rename to packages/astro/src/vite-plugin-html/transform/index.ts diff --git a/packages/integrations/html/src/transform/slots.ts b/packages/astro/src/vite-plugin-html/transform/slots.ts similarity index 100% rename from packages/integrations/html/src/transform/slots.ts rename to packages/astro/src/vite-plugin-html/transform/slots.ts diff --git a/packages/integrations/html/src/transform/utils.ts b/packages/astro/src/vite-plugin-html/transform/utils.ts similarity index 100% rename from packages/integrations/html/src/transform/utils.ts rename to packages/astro/src/vite-plugin-html/transform/utils.ts diff --git a/packages/integrations/html/test/fixtures/html-component/package.json b/packages/astro/test/fixtures/html-component/package.json similarity index 78% rename from packages/integrations/html/test/fixtures/html-component/package.json rename to packages/astro/test/fixtures/html-component/package.json index 3d29a96932f0..548f0bc7b591 100644 --- a/packages/integrations/html/test/fixtures/html-component/package.json +++ b/packages/astro/test/fixtures/html-component/package.json @@ -3,7 +3,6 @@ "version": "0.0.0", "private": true, "dependencies": { - "@astrojs/html": "workspace:*", "astro": "workspace:*" } } diff --git a/packages/integrations/html/test/fixtures/html-component/src/components/Test.html b/packages/astro/test/fixtures/html-component/src/components/Test.html similarity index 100% rename from packages/integrations/html/test/fixtures/html-component/src/components/Test.html rename to packages/astro/test/fixtures/html-component/src/components/Test.html diff --git a/packages/integrations/html/test/fixtures/escape/src/pages/index.astro b/packages/astro/test/fixtures/html-component/src/pages/index.astro similarity index 100% rename from packages/integrations/html/test/fixtures/escape/src/pages/index.astro rename to packages/astro/test/fixtures/html-component/src/pages/index.astro diff --git a/packages/integrations/html/test/fixtures/escape/package.json b/packages/astro/test/fixtures/html-escape/package.json similarity index 77% rename from packages/integrations/html/test/fixtures/escape/package.json rename to packages/astro/test/fixtures/html-escape/package.json index a38b4db1c898..b6eb747659fb 100644 --- a/packages/integrations/html/test/fixtures/escape/package.json +++ b/packages/astro/test/fixtures/html-escape/package.json @@ -3,7 +3,6 @@ "version": "0.0.0", "private": true, "dependencies": { - "@astrojs/html": "workspace:*", "astro": "workspace:*" } } diff --git a/packages/integrations/html/test/fixtures/escape/src/components/Test.html b/packages/astro/test/fixtures/html-escape/src/components/Test.html similarity index 100% rename from packages/integrations/html/test/fixtures/escape/src/components/Test.html rename to packages/astro/test/fixtures/html-escape/src/components/Test.html diff --git a/packages/integrations/html/test/fixtures/html-component/src/pages/index.astro b/packages/astro/test/fixtures/html-escape/src/pages/index.astro similarity index 100% rename from packages/integrations/html/test/fixtures/html-component/src/pages/index.astro rename to packages/astro/test/fixtures/html-escape/src/pages/index.astro diff --git a/packages/integrations/html/test/fixtures/html-page/package.json b/packages/astro/test/fixtures/html-page/package.json similarity index 77% rename from packages/integrations/html/test/fixtures/html-page/package.json rename to packages/astro/test/fixtures/html-page/package.json index 70eba8ed493e..070024b1e14d 100644 --- a/packages/integrations/html/test/fixtures/html-page/package.json +++ b/packages/astro/test/fixtures/html-page/package.json @@ -3,7 +3,6 @@ "version": "0.0.0", "private": true, "dependencies": { - "@astrojs/html": "workspace:*", "astro": "workspace:*" } } diff --git a/packages/integrations/html/test/fixtures/slots/package.json b/packages/astro/test/fixtures/html-slots/package.json similarity index 77% rename from packages/integrations/html/test/fixtures/slots/package.json rename to packages/astro/test/fixtures/html-slots/package.json index 700e7d7991db..10b04cfd4f22 100644 --- a/packages/integrations/html/test/fixtures/slots/package.json +++ b/packages/astro/test/fixtures/html-slots/package.json @@ -3,7 +3,6 @@ "version": "0.0.0", "private": true, "dependencies": { - "@astrojs/html": "workspace:*", "astro": "workspace:*" } } diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Default.html b/packages/astro/test/fixtures/html-slots/src/components/Default.html similarity index 100% rename from packages/integrations/html/test/fixtures/slots/src/components/Default.html rename to packages/astro/test/fixtures/html-slots/src/components/Default.html diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Inline.html b/packages/astro/test/fixtures/html-slots/src/components/Inline.html similarity index 100% rename from packages/integrations/html/test/fixtures/slots/src/components/Inline.html rename to packages/astro/test/fixtures/html-slots/src/components/Inline.html diff --git a/packages/integrations/html/test/fixtures/slots/src/components/Named.html b/packages/astro/test/fixtures/html-slots/src/components/Named.html similarity index 100% rename from packages/integrations/html/test/fixtures/slots/src/components/Named.html rename to packages/astro/test/fixtures/html-slots/src/components/Named.html diff --git a/packages/integrations/html/test/fixtures/slots/src/pages/index.astro b/packages/astro/test/fixtures/html-slots/src/pages/index.astro similarity index 100% rename from packages/integrations/html/test/fixtures/slots/src/pages/index.astro rename to packages/astro/test/fixtures/html-slots/src/pages/index.astro diff --git a/packages/integrations/html/test/html-page.test.js b/packages/astro/test/html-component.test.js similarity index 53% rename from packages/integrations/html/test/html-page.test.js rename to packages/astro/test/html-component.test.js index 726f4c824fbf..0145a22aa395 100644 --- a/packages/integrations/html/test/html-page.test.js +++ b/packages/astro/test/html-component.test.js @@ -1,16 +1,13 @@ -import integration from '@astrojs/html'; - import { expect } from 'chai'; -import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; -describe('HTML Page', () => { +describe('HTML Component', () => { let fixture; before(async () => { fixture = await loadFixture({ - root: new URL('./fixtures/html-page/', import.meta.url), - integrations: [integration()], + root: './fixtures/html-component/', }); }); @@ -21,11 +18,13 @@ describe('HTML Page', () => { it('works', async () => { const html = await fixture.readFile('/index.html'); - const { document } = parseHTML(html); + const $ = cheerio.load(html); - const h1 = document.querySelector('h1'); + const h1 = $('h1'); + const foo = $('#foo'); - expect(h1.textContent).to.equal('Hello page!'); + expect(h1.text()).to.equal('Hello component!'); + expect(foo.text()).to.equal('bar'); }); }); @@ -46,11 +45,13 @@ describe('HTML Page', () => { expect(res.status).to.equal(200); const html = await res.text(); - const { document } = parseHTML(html); + const $ = cheerio.load(html); - const h1 = document.querySelector('h1'); + const h1 = $('h1'); + const foo = $('#foo'); - expect(h1.textContent).to.equal('Hello page!'); + expect(h1.text()).to.equal('Hello component!'); + expect(foo.text()).to.equal('bar'); }); }); }); diff --git a/packages/astro/test/html-escape.test.js b/packages/astro/test/html-escape.test.js new file mode 100644 index 000000000000..ed19105c6567 --- /dev/null +++ b/packages/astro/test/html-escape.test.js @@ -0,0 +1,69 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('HTML Escape', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/html-escape/', + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + const div = $('div'); + expect(div.text()).to.equal('${foo}'); + + const span = $('span'); + expect(span.attr('${attr}')).to.equal(""); + + const ce = $('custom-element'); + expect(ce.attr('x-data')).to.equal("`${test}`"); + + const script = $('script'); + expect(script.text()).to.equal('console.log(`hello ${"world"}!`)'); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const $ = cheerio.load(html); + + const div = $('div'); + expect(div.text()).to.equal('${foo}'); + + const span = $('span'); + expect(span.attr('${attr}')).to.equal(""); + + const ce = $('custom-element'); + expect(ce.attr('x-data')).to.equal("`${test}`"); + + const script = $('script'); + expect(script.text()).to.equal('console.log(`hello ${"world"}!`)'); + }); + }); +}); diff --git a/packages/astro/test/html-page.test.js b/packages/astro/test/html-page.test.js index cbda4a30ae46..fe4c01f68050 100644 --- a/packages/astro/test/html-page.test.js +++ b/packages/astro/test/html-page.test.js @@ -18,9 +18,11 @@ describe('HTML Page', () => { it('works', async () => { const html = await fixture.readFile('/index.html'); - let $ = cheerio.load(html); - expect($('h1')).to.have.a.lengthOf(1, 'renders h1'); - expect($('h1').text().trim()).to.have.equal('Hello page!'); + const $ = cheerio.load(html) + + const h1 = $('h1'); + + expect(h1.text()).to.equal('Hello page!'); }); }); @@ -41,9 +43,11 @@ describe('HTML Page', () => { expect(res.status).to.equal(200); const html = await res.text(); - let $ = cheerio.load(html); - expect($('h1')).to.have.a.lengthOf(1, 'renders h1'); - expect($('h1').text().trim()).to.have.equal('Hello page!'); + const $ = cheerio.load(html) + + const h1 = $('h1'); + + expect(h1.text()).to.equal('Hello page!'); }); }); }); diff --git a/packages/astro/test/html-slots.test.js b/packages/astro/test/html-slots.test.js new file mode 100644 index 000000000000..af1a05a83327 --- /dev/null +++ b/packages/astro/test/html-slots.test.js @@ -0,0 +1,75 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('HTML Slots', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/html-slots/', + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + const slotDefault = $('#default'); + expect(slotDefault.text()).to.equal('Default'); + + const a = $('#a'); + expect(a.text().trim()).to.equal('A'); + + const b = $('#b'); + expect(b.text().trim()).to.equal('B'); + + const c = $('#c'); + expect(c.text().trim()).to.equal('C'); + + const inline = $('#inline'); + expect(inline.html()).to.equal(''); + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + + expect(res.status).to.equal(200); + + const html = await res.text(); + const $ = cheerio.load(html); + + const slotDefault = $('#default'); + expect(slotDefault.text()).to.equal('Default'); + + const a = $('#a'); + expect(a.text().trim()).to.equal('A'); + + const b = $('#b'); + expect(b.text().trim()).to.equal('B'); + + const c = $('#c'); + expect(c.text().trim()).to.equal('C'); + + const inline = $('#inline'); + expect(inline.html()).to.equal(''); + }); + }); +}); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index fdea44d3a321..ab43a94cc8ce 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -83,11 +83,6 @@ export async function loadFixture(inlineConfig) { if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { config.base = inlineConfig.base + '/'; } - if (!config.integrations.find((integration) => integration.name === '@astrojs/html')) { - // @ts-ignore types can't be resolved, but it's here! - const { default: html } = await import('@astrojs/html'); - config.integrations.push(html()); - } if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) { // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('astro/jsx/renderer.js'); diff --git a/packages/integrations/html/README.md b/packages/integrations/html/README.md deleted file mode 100644 index 4702117cb6ff..000000000000 --- a/packages/integrations/html/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# @astrojs/html 📕 - -This **[Astro integration][astro-integration]** enables the usage of [HTML](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics) components and allows you to create pages as `.html` files. - -- [Why HTML?](#why-html) -- [Installation](#installation) -- [Usage](#usage) -- [Configuration](#configuration) -- [Examples](#examples) -- [Troubleshooting](#troubleshooting) -- [Contributing](#contributing) -- [Changelog](#changelog) - -## Why HTML? - -HTML is the standardized format for structuring a web page and its content. With built-in Astro support for `.html` pages and components, migrating from a legacy framework to Astro has never been easier. - -## Installation - -The `@astrojs/html` integration is enabled by default. Currently, it cannot be disabled. - -## Usage - -TODO - -## Configuration - -There are currently no configuration options for the `@astrojs/html` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share. - -## Examples - -- The [Astro HTML example](https://github.com/withastro/astro/tree/latest/examples/with-html) shows how to use HTML files in your Astro project. - -## Troubleshooting - -For help, check out the `#support-threads` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help! - -You can also check our [Astro Integration Documentation][astro-integration] for more on integrations. - -## Contributing - -This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! - -## Changelog - -See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this integration. - -[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ -[astro-ui-frameworks]: https://docs.astro.build/en/core-concepts/framework-components/#using-framework-components diff --git a/packages/integrations/html/package.json b/packages/integrations/html/package.json deleted file mode 100644 index 05a73ab1e3ad..000000000000 --- a/packages/integrations/html/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@astrojs/html", - "description": "Add HTML pages and components to your Astro site", - "version": "0.0.1", - "type": "module", - "types": "./dist/index.d.ts", - "author": "withastro", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/withastro/astro.git", - "directory": "packages/integrations/html" - }, - "bugs": "https://github.com/withastro/astro/issues", - "homepage": "https://astro.build", - "exports": { - ".": "./dist/index.js", - "./server.js": "./dist/server.js", - "./package.json": "./package.json" - }, - "keywords": [ - "astro-component", - "renderer", - "html" - ], - "scripts": { - "build": "astro-scripts build \"src/**/*.ts\" && tsc", - "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"", - "test": "mocha --exit --timeout 20000" - }, - "devDependencies": { - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.1", - "astro-scripts": "workspace:*", - "chai": "^4.3.6", - "linkedom": "^0.14.12", - "mocha": "^9.2.2", - "unified": "^10.1.2" - }, - "dependencies": { - "magic-string": "^0.25.9", - "rehype": "^12.0.1", - "unist-util-visit": "^4.1.0", - "vfile": "^5.3.2" - } -} diff --git a/packages/integrations/html/server.js b/packages/integrations/html/server.js deleted file mode 100644 index f35e9b3e0fb3..000000000000 --- a/packages/integrations/html/server.js +++ /dev/null @@ -1,13 +0,0 @@ -function check(Component) { - return Component && typeof Component === 'object' && Component['astro:html']; -} - -async function renderToStaticMarkup(Component, _, slots) { - const html = Component.render({ slots }); - return { html }; -} - -export default { - check, - renderToStaticMarkup, -}; diff --git a/packages/integrations/html/src/index.ts b/packages/integrations/html/src/index.ts deleted file mode 100644 index 598d073a1591..000000000000 --- a/packages/integrations/html/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { transform } from './transform/index.js'; - -function getRenderer() { - return { - name: '@astrojs/html', - serverEntrypoint: '@astrojs/html/server.js', - }; -} - -function getViteConfiguration() { - return { - optimizeDeps: { - exclude: ['@astrojs/html/dist/server.js'], - }, - ssr: { - external: ['@astrojs/html/dist/server.js'] - }, - plugins: [ - { - name: '@astrojs/html', - options(options: any) { - options.plugins = options.plugins?.filter((p: any) => p.name !== 'vite:build-html'); - }, - async transform(source: string, id: string) { - if (!id.endsWith('.html')) return; - return await transform(source, id); - } - } - ], - }; -} - -export default function createIntegration() { - return { - name: '@astrojs/html', - hooks: { - 'astro:config:setup': ({ addRenderer, updateConfig, addPageExtension }: any) => { - addRenderer(getRenderer()) - updateConfig({ vite: getViteConfiguration() }); - addPageExtension('.html'); - } - }, - }; -} diff --git a/packages/integrations/html/src/server.ts b/packages/integrations/html/src/server.ts deleted file mode 100644 index 48059a7435f3..000000000000 --- a/packages/integrations/html/src/server.ts +++ /dev/null @@ -1,13 +0,0 @@ -function check(Component: any) { - return Component && typeof Component === 'object' && Component['astro:html']; -} - -async function renderToStaticMarkup(Component: any, _: any, slots: Record) { - const html = Component.render({ slots }); - return { html }; -} - -export default { - check, - renderToStaticMarkup, -}; diff --git a/packages/integrations/html/test/escape.test.js b/packages/integrations/html/test/escape.test.js deleted file mode 100644 index a7261355363e..000000000000 --- a/packages/integrations/html/test/escape.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import integration from '@astrojs/html'; - -import { expect } from 'chai'; -import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; - -describe('HTML Escape', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/escape/', import.meta.url), - integrations: [integration()], - }); - }); - - describe('build', () => { - before(async () => { - await fixture.build(); - }); - - it('works', async () => { - const html = await fixture.readFile('/index.html'); - const { document } = parseHTML(html); - - const div = document.querySelector('div'); - expect(div.textContent).to.equal('${foo}'); - - const span = document.querySelector('span'); - expect(span.getAttribute('${attr}')).to.equal(""); - - const ce = document.querySelector('custom-element'); - expect(ce.getAttribute('x-data')).to.equal("`${test}`"); - - const script = document.querySelector('script'); - expect(script.textContent).to.equal('console.log(`hello ${"world"}!`)'); - }); - }); - - describe('dev', () => { - let devServer; - - before(async () => { - devServer = await fixture.startDevServer(); - }); - - after(async () => { - await devServer.stop(); - }); - - it('works', async () => { - const res = await fixture.fetch('/'); - - expect(res.status).to.equal(200); - - const html = await res.text(); - const { document } = parseHTML(html); - - const div = document.querySelector('div'); - expect(div.textContent).to.equal('${foo}'); - - const span = document.querySelector('span'); - expect(span.getAttribute('${attr}')).to.equal(""); - - const ce = document.querySelector('custom-element'); - expect(ce.getAttribute('x-data')).to.equal("`${test}`"); - - const script = document.querySelector('script'); - expect(script.textContent).to.equal('console.log(`hello ${"world"}!`)'); - }); - }); -}); diff --git a/packages/integrations/html/test/fixtures/html-page/src/pages/index.html b/packages/integrations/html/test/fixtures/html-page/src/pages/index.html deleted file mode 100644 index 5d4d2282712e..000000000000 --- a/packages/integrations/html/test/fixtures/html-page/src/pages/index.html +++ /dev/null @@ -1 +0,0 @@ -

Hello page!

diff --git a/packages/integrations/html/test/html-component.test.js b/packages/integrations/html/test/html-component.test.js deleted file mode 100644 index b647c9d3073a..000000000000 --- a/packages/integrations/html/test/html-component.test.js +++ /dev/null @@ -1,60 +0,0 @@ -import integration from '@astrojs/html'; - -import { expect } from 'chai'; -import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; - -describe('HTML Component', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/html-component/', import.meta.url), - integrations: [integration()], - }); - }); - - describe('build', () => { - before(async () => { - await fixture.build(); - }); - - it('works', async () => { - const html = await fixture.readFile('/index.html'); - const { document } = parseHTML(html); - - const h1 = document.querySelector('h1'); - const foo = document.querySelector('#foo'); - - expect(h1.textContent).to.equal('Hello component!'); - expect(foo.textContent).to.equal('bar'); - }); - }); - - describe('dev', () => { - let devServer; - - before(async () => { - devServer = await fixture.startDevServer(); - }); - - after(async () => { - await devServer.stop(); - }); - - it('works', async () => { - const res = await fixture.fetch('/'); - - expect(res.status).to.equal(200); - - const html = await res.text(); - const { document } = parseHTML(html); - - const h1 = document.querySelector('h1'); - const foo = document.querySelector('#foo'); - - expect(h1.textContent).to.equal('Hello component!'); - expect(foo.textContent).to.equal('bar'); - }); - }); -}); diff --git a/packages/integrations/html/test/slots.test.js b/packages/integrations/html/test/slots.test.js deleted file mode 100644 index aeb66b3bedc1..000000000000 --- a/packages/integrations/html/test/slots.test.js +++ /dev/null @@ -1,78 +0,0 @@ -import integration from '@astrojs/html'; - -import { expect } from 'chai'; -import { parseHTML } from 'linkedom'; -import { loadFixture } from '../../../astro/test/test-utils.js'; - -describe('HTML Slots', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/slots/', import.meta.url), - integrations: [integration()], - }); - }); - - describe('build', () => { - before(async () => { - await fixture.build(); - }); - - it('works', async () => { - const html = await fixture.readFile('/index.html'); - const { document } = parseHTML(html); - - const slotDefault = document.querySelector('#default'); - expect(slotDefault.textContent).to.equal('Default'); - - const a = document.querySelector('#a'); - expect(a.textContent.trim()).to.equal('A'); - - const b = document.querySelector('#b'); - expect(b.textContent.trim()).to.equal('B'); - - const c = document.querySelector('#c'); - expect(c.textContent.trim()).to.equal('C'); - - const inline = document.querySelector('#inline'); - expect(inline.innerHTML).to.equal(''); - }); - }); - - describe('dev', () => { - let devServer; - - before(async () => { - devServer = await fixture.startDevServer(); - }); - - after(async () => { - await devServer.stop(); - }); - - it('works', async () => { - const res = await fixture.fetch('/'); - - expect(res.status).to.equal(200); - - const html = await res.text(); - const { document } = parseHTML(html); - - const slotDefault = document.querySelector('#default'); - expect(slotDefault.textContent).to.equal('Default'); - - const a = document.querySelector('#a'); - expect(a.textContent.trim()).to.equal('A'); - - const b = document.querySelector('#b'); - expect(b.textContent.trim()).to.equal('B'); - - const c = document.querySelector('#c'); - expect(c.textContent.trim()).to.equal('C'); - - const inline = document.querySelector('#inline'); - expect(inline.innerHTML).to.equal(''); - }); - }); -}); diff --git a/packages/integrations/html/tsconfig.json b/packages/integrations/html/tsconfig.json deleted file mode 100644 index d31d6a5e5ce6..000000000000 --- a/packages/integrations/html/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "include": ["src", "server.ts"], - "compilerOptions": { - "allowJs": true, - "module": "ES2022", - "outDir": "./dist", - "target": "ES2022" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 612a46239eb7..738882324952 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -439,7 +439,6 @@ importers: packages/astro: specifiers: '@astrojs/compiler': ^0.20.0 - '@astrojs/html': ^0.0.1 '@astrojs/language-server': ^0.20.0 '@astrojs/markdown-remark': ^0.12.0 '@astrojs/prism': 0.6.1 @@ -507,6 +506,7 @@ importers: prismjs: ^1.28.0 prompts: ^2.4.2 recast: ^0.20.5 + rehype: ^12.0.1 resolve: ^1.22.0 rollup: ^2.75.6 sass: ^1.52.2 @@ -520,12 +520,13 @@ importers: strip-ansi: ^7.0.1 supports-esm: ^1.0.0 tsconfig-resolver: ^3.0.1 + unist-util-visit: ^4.1.0 + vfile: ^5.3.2 vite: 3.0.2 yargs-parser: ^21.0.1 zod: ^3.17.3 dependencies: '@astrojs/compiler': 0.20.0 - '@astrojs/html': link:../integrations/html '@astrojs/language-server': 0.20.0 '@astrojs/markdown-remark': link:../markdown/remark '@astrojs/prism': link:../astro-prism @@ -568,6 +569,7 @@ importers: prismjs: 1.28.0 prompts: 2.4.2 recast: 0.20.5 + rehype: 12.0.1 resolve: 1.22.1 rollup: 2.76.0 semver: 7.3.7 @@ -579,6 +581,8 @@ importers: strip-ansi: 7.0.1 supports-esm: 1.0.0 tsconfig-resolver: 3.0.1 + unist-util-visit: 4.1.0 + vfile: 5.3.4 vite: 3.0.2_sass@1.53.0 yargs-parser: 21.0.1 zod: 3.17.3 @@ -1520,6 +1524,30 @@ importers: dependencies: astro: link:../../.. + packages/astro/test/fixtures/html-component: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + + packages/astro/test/fixtures/html-escape: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + + packages/astro/test/fixtures/html-page: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + + packages/astro/test/fixtures/html-slots: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/import-ts-with-js: specifiers: astro: workspace:* @@ -2018,65 +2046,6 @@ importers: '@astrojs/deno': link:../../.. astro: link:../../../../../astro - packages/integrations/html: - specifiers: - '@types/chai': ^4.3.1 - '@types/mocha': ^9.1.1 - astro-scripts: workspace:* - chai: ^4.3.6 - linkedom: ^0.14.12 - magic-string: ^0.25.9 - mocha: ^9.2.2 - rehype: ^12.0.1 - unified: ^10.1.2 - unist-util-visit: ^4.1.0 - vfile: ^5.3.2 - dependencies: - magic-string: 0.25.9 - rehype: 12.0.1 - unist-util-visit: 4.1.0 - vfile: 5.3.4 - devDependencies: - '@types/chai': 4.3.1 - '@types/mocha': 9.1.1 - astro-scripts: link:../../../scripts - chai: 4.3.6 - linkedom: 0.14.12 - mocha: 9.2.2 - unified: 10.1.2 - - packages/integrations/html/test/fixtures/escape: - specifiers: - '@astrojs/html': workspace:* - astro: workspace:* - dependencies: - '@astrojs/html': link:../../.. - astro: link:../../../../../astro - - packages/integrations/html/test/fixtures/html-component: - specifiers: - '@astrojs/html': workspace:* - astro: workspace:* - dependencies: - '@astrojs/html': link:../../.. - astro: link:../../../../../astro - - packages/integrations/html/test/fixtures/html-page: - specifiers: - '@astrojs/html': workspace:* - astro: workspace:* - dependencies: - '@astrojs/html': link:../../.. - astro: link:../../../../../astro - - packages/integrations/html/test/fixtures/slots: - specifiers: - '@astrojs/html': workspace:* - astro: workspace:* - dependencies: - '@astrojs/html': link:../../.. - astro: link:../../../../../astro - packages/integrations/image: specifiers: '@types/etag': ^1.8.1 From 073913ba0ba50705aa892f3f0dc4d2c6c48de118 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 21 Jul 2022 16:43:52 -0500 Subject: [PATCH 11/12] chore: update types for `*.html` --- packages/astro/client.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index cfbb65ef0e48..9ac2e176823d 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -1,5 +1,11 @@ /// +// HTML +declare module "*.html" { + const Component: { render(opts: { slots: Record }): string }; + export default content; +} + // CSS modules type CSSModuleClasses = { readonly [key: string]: string }; From 41820f90959f5b10c101e3ad30f2d2ea655b8480 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 21 Jul 2022 16:48:53 -0500 Subject: [PATCH 12/12] fix: move *.html to astro/env --- packages/astro/client.d.ts | 6 ------ packages/astro/env.d.ts | 5 +++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 9ac2e176823d..cfbb65ef0e48 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -1,11 +1,5 @@ /// -// HTML -declare module "*.html" { - const Component: { render(opts: { slots: Record }): string }; - export default content; -} - // CSS modules type CSSModuleClasses = { readonly [key: string]: string }; diff --git a/packages/astro/env.d.ts b/packages/astro/env.d.ts index aa649c896fdd..619a4422bc54 100644 --- a/packages/astro/env.d.ts +++ b/packages/astro/env.d.ts @@ -26,3 +26,8 @@ declare module '*.md' { const load: MD['default']; export default load; } + +declare module "*.html" { + const Component: { render(opts: { slots: Record }): string }; + export default Component; +}