diff options
22 files changed, 162 insertions, 28 deletions
diff --git a/examples/snowpack/astro/layouts/content-with-cover.astro b/examples/snowpack/astro/layouts/content-with-cover.astro index ac84f6354..46006bdfb 100644 --- a/examples/snowpack/astro/layouts/content-with-cover.astro +++ b/examples/snowpack/astro/layouts/content-with-cover.astro @@ -7,6 +7,7 @@ import BaseLayout from '../components/BaseLayout.astro'; export let content: any; --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/layouts/content.astro b/examples/snowpack/astro/layouts/content.astro index 8a7504264..c8f9ca907 100644 --- a/examples/snowpack/astro/layouts/content.astro +++ b/examples/snowpack/astro/layouts/content.astro @@ -7,6 +7,7 @@ import BaseLayout from '../components/BaseLayout.astro'; export let content: any; --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/layouts/post.astro b/examples/snowpack/astro/layouts/post.astro index 8f0a3c52b..05f28bf24 100644 --- a/examples/snowpack/astro/layouts/post.astro +++ b/examples/snowpack/astro/layouts/post.astro @@ -6,6 +6,7 @@ import { format as formatDate, parseISO } from 'date-fns'; export let content: any; --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/pages/404.astro b/examples/snowpack/astro/pages/404.astro index 4677ed50d..053c931f4 100644 --- a/examples/snowpack/astro/pages/404.astro +++ b/examples/snowpack/astro/pages/404.astro @@ -6,6 +6,7 @@ let title = 'Not Found'; let description = 'Snowpack is a lightning-fast frontend build tool, designed for the modern web.'; --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/pages/guides.astro b/examples/snowpack/astro/pages/guides.astro index 608283243..e02856ede 100644 --- a/examples/snowpack/astro/pages/guides.astro +++ b/examples/snowpack/astro/pages/guides.astro @@ -40,6 +40,7 @@ let communityGuides; }); --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/pages/index.astro b/examples/snowpack/astro/pages/index.astro index 31dba0f51..aa5ee59b6 100644 --- a/examples/snowpack/astro/pages/index.astro +++ b/examples/snowpack/astro/pages/index.astro @@ -8,8 +8,8 @@ let title = 'Snowpack'; let description = 'Snowpack is a lightning-fast frontend build tool, designed for the modern web.'; --- +<!doctype html> <html> - <head> <style lang="scss"> @use '../../public/css/var' as *; @@ -149,5 +149,4 @@ let description = 'Snowpack is a lightning-fast frontend build tool, designed fo <!-- Place this tag in your head or just before your close body tag. --> <script async="async" defer="defer" src="https://buttons.github.io/buttons.js"></script> </body> - </html> diff --git a/examples/snowpack/astro/pages/news.astro b/examples/snowpack/astro/pages/news.astro index bd1c7b309..bae3e5ad7 100644 --- a/examples/snowpack/astro/pages/news.astro +++ b/examples/snowpack/astro/pages/news.astro @@ -16,6 +16,7 @@ const title = 'Community & News'; const description = 'Snowpack community news and companies that use Snowpack.'; --- +<!doctype html> <html> <head> diff --git a/examples/snowpack/astro/pages/plugins.astro b/examples/snowpack/astro/pages/plugins.astro index faae4a26d..bffe9b4b9 100644 --- a/examples/snowpack/astro/pages/plugins.astro +++ b/examples/snowpack/astro/pages/plugins.astro @@ -7,6 +7,7 @@ let title = 'The Snowpack Plugin Catalog'; let description = 'Snowpack plugins allow for configuration-minimal tooling integration.'; --- +<!doctype html> <html> <head> diff --git a/src/@types/optimizer.ts b/src/@types/optimizer.ts index b9e228f3e..22027d2e2 100644 --- a/src/@types/optimizer.ts +++ b/src/@types/optimizer.ts @@ -1,6 +1,6 @@ import type { TemplateNode } from '../parser/interfaces'; -export type VisitorFn = (node: TemplateNode) => void; +export type VisitorFn = (node: TemplateNode, parent: TemplateNode, type: string, index: number) => void; export interface NodeVisitor { enter?: VisitorFn; diff --git a/src/compiler/optimize/doctype.ts b/src/compiler/optimize/doctype.ts new file mode 100644 index 000000000..a666876bf --- /dev/null +++ b/src/compiler/optimize/doctype.ts @@ -0,0 +1,33 @@ +import { Optimizer } from '../../@types/optimizer'; + +export default function (_opts: { filename: string; fileID: string }): Optimizer { + let hasDoctype = false; + + return { + visitors: { + html: { + Element: { + enter(node, parent, _key, index) { + if(node.name === '!doctype') { + hasDoctype = true; + } + if(node.name === 'html' && !hasDoctype) { + const dtNode = { + start: 0, end: 0, + attributes: [{ type: 'Attribute', name: 'html', value: true, start: 0, end: 0 }], + children: [], + name: '!doctype', + type: 'Element' + }; + parent.children!.splice(index, 0, dtNode); + hasDoctype = true; + } + } + } + } + }, + async finalize() { + // Nothing happening here. + } + } +}
\ No newline at end of file diff --git a/src/compiler/optimize/index.ts b/src/compiler/optimize/index.ts index aa5ca58f3..d86ce3c24 100644 --- a/src/compiler/optimize/index.ts +++ b/src/compiler/optimize/index.ts @@ -1,7 +1,10 @@ import { walk } from 'estree-walker'; import type { Ast, TemplateNode } from '../../parser/interfaces'; import { NodeVisitor, Optimizer, VisitorFn } from '../../@types/optimizer'; + +// Optimizers import optimizeStyles from './styles.js'; +import optimizeDoctype from './doctype.js'; interface VisitorCollection { enter: Map<string, VisitorFn[]>; @@ -44,19 +47,19 @@ function createVisitorCollection() { function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection) { walk(tmpl, { - enter(node) { + enter(node, parent, key, index) { if (collection.enter.has(node.type)) { const fns = collection.enter.get(node.type)!; for (let fn of fns) { - fn(node); + fn(node, parent, key, index); } } }, - leave(node) { + leave(node, parent, key, index) { if (collection.leave.has(node.type)) { const fns = collection.leave.get(node.type)!; for (let fn of fns) { - fn(node); + fn(node, parent, key, index); } } }, @@ -73,7 +76,7 @@ export async function optimize(ast: Ast, opts: OptimizeOptions) { const cssVisitors = createVisitorCollection(); const finalizers: Array<() => Promise<void>> = []; - const optimizers = [optimizeStyles(opts)]; + const optimizers = [optimizeStyles(opts), optimizeDoctype(opts)]; for (const optimizer of optimizers) { collectVisitors(optimizer, htmlVisitors, cssVisitors, finalizers); diff --git a/src/frontend/h.ts b/src/frontend/h.ts index cd94583f8..70965e135 100644 --- a/src/frontend/h.ts +++ b/src/frontend/h.ts @@ -6,6 +6,15 @@ export type HTag = string | AstroComponent; const voidTags = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']); function* _h(tag: string, attrs: HProps, children: Array<HChild>) { + if(tag === '!doctype') { + yield '<!doctype '; + if(attrs) { + yield Object.keys(attrs).join(' '); + } + yield '>'; + return; + } + yield `<${tag}`; if (attrs) { yield ' '; diff --git a/src/runtime.ts b/src/runtime.ts index ac6d4664a..1b7261dfc 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -2,12 +2,14 @@ import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, LoadRes import type { AstroConfig } from './@types/astro'; import type { LogOptions } from './logger'; import type { CompileError } from './parser/utils/error.js'; -import { info, error, parseError } from './logger.js'; +import { info } from './logger.js'; -import { existsSync, promises as fsPromises } from 'fs'; -import { loadConfiguration, startServer as startSnowpackServer } from 'snowpack'; - -const { readFile } = fsPromises; +import { existsSync } from 'fs'; +import { + loadConfiguration, + logger as snowpackLogger, + startServer as startSnowpackServer +} from 'snowpack'; interface RuntimeConfig { astroConfig: AstroConfig; @@ -27,6 +29,9 @@ type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: Comp export type LoadResult = LoadResultSuccess | LoadResultNotFound | LoadResultError; +// Disable snowpack from writing to stdout/err. +snowpackLogger.level = 'silent'; + async function load(config: RuntimeConfig, rawPathname: string | undefined): Promise<LoadResult> { const { logging, snowpack, snowpackRuntime } = config; const { astroRoot } = config.astroConfig; @@ -130,13 +135,18 @@ export async function createRuntime(astroConfig: AstroConfig, { logging }: Runti resolve: async (pkgName: string) => snowpack.getUrlForPackage(pkgName), }; + const mountOptions = { + [astroRoot.pathname]: '/_astro', + [internalPath.pathname]: '/_astro_internal' + } + + if(existsSync(astroConfig.public)) { + mountOptions[astroConfig.public.pathname] = '/'; + } + const snowpackConfig = await loadConfiguration({ root: projectRoot.pathname, - mount: { - [astroRoot.pathname]: '/_astro', - [internalPath.pathname]: '/_astro_internal', - public: '/', - }, + mount: mountOptions, plugins: [[new URL('../snowpack-plugin.cjs', import.meta.url).pathname, astroPlugOptions], '@snowpack/plugin-sass', '@snowpack/plugin-svelte', '@snowpack/plugin-vue'], devOptions: { open: 'none', diff --git a/test/astro-basic.test.js b/test/astro-basic.test.js index 79b71cfa7..606a94679 100644 --- a/test/astro-basic.test.js +++ b/test/astro-basic.test.js @@ -1,6 +1,7 @@ import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { createRuntime } from '../lib/runtime.js'; +import { loadConfig } from '../lib/config.js'; import { doc } from './test-utils.js'; const Basics = suite('HMX Basics'); @@ -8,18 +9,14 @@ const Basics = suite('HMX Basics'); let runtime; Basics.before(async () => { - const astroConfig = { - projectRoot: new URL('./fixtures/astro-basic/', import.meta.url), - hmxRoot: new URL('./fixtures/astro-basic/astro/', import.meta.url), - dist: './_site', - }; + const astroConfig = await loadConfig(new URL('./fixtures/astro-basics', import.meta.url).pathname); const logging = { level: 'error', dest: process.stderr, }; - runtime = await createRuntime(astroConfig, logging); + runtime = await createRuntime(astroConfig, {logging}); }); Basics.after(async () => { diff --git a/test/astro-doctype.test.js b/test/astro-doctype.test.js new file mode 100644 index 000000000..23188fd97 --- /dev/null +++ b/test/astro-doctype.test.js @@ -0,0 +1,54 @@ +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { loadConfig } from '../lib/config.js'; +import { createRuntime } from '../lib/runtime.js'; + +const DType = suite('doctype'); + +let runtime, setupError; + +DType.before(async () => { + try { + const astroConfig = await loadConfig(new URL('./fixtures/astro-doctype', import.meta.url).pathname); + + const logging = { + level: 'error', + dest: process.stderr, + }; + + + runtime = await createRuntime(astroConfig, {logging}); + } catch (err) { + console.error(err); + setupError = err; + } +}); + +DType.after(async () => { + (await runtime) && runtime.shutdown(); +}); + +DType('No errors creating a runtime', () => { + assert.equal(setupError, undefined); +}); + +DType('Automatically prepends the standards mode doctype', async () => { + const result = await runtime.load('/prepend'); + + assert.equal(result.statusCode, 200); + + const html = result.contents.toString('utf-8'); + assert.ok(html.startsWith('<!doctype html>'), 'Doctype always included'); +}); + +DType.skip('Preserves user provided doctype', async () => { + const result = await runtime.load('/preserve'); + + assert.equal(result.statusCode, 200); + + const html = result.contents.toString('utf-8'); + assert.ok(html.startsWith('<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'), + 'Doctype included was preserved'); +}); + +DType.run(); diff --git a/test/astro-markdown.test.js b/test/astro-markdown.test.js index cbab25e36..7f5b310e3 100644 --- a/test/astro-markdown.test.js +++ b/test/astro-markdown.test.js @@ -17,7 +17,7 @@ Markdown.before(async () => { }; try { - runtime = await createRuntime(astroConfig, logging); + runtime = await createRuntime(astroConfig, {logging}); } catch (err) { console.error(err); setupError = err; diff --git a/test/astro-styles-ssr.test.js b/test/astro-styles-ssr.test.js index 69fa68353..27da41049 100644 --- a/test/astro-styles-ssr.test.js +++ b/test/astro-styles-ssr.test.js @@ -16,7 +16,7 @@ StylesSSR.before(async () => { dest: process.stderr, }; - runtime = await createRuntime(astroConfig, logging); + runtime = await createRuntime(astroConfig, {logging}); }); StylesSSR.after(async () => { diff --git a/test/fixtures/astro-doctype/astro.config.mjs b/test/fixtures/astro-doctype/astro.config.mjs new file mode 100644 index 000000000..c7cbdb435 --- /dev/null +++ b/test/fixtures/astro-doctype/astro.config.mjs @@ -0,0 +1,5 @@ +export default { + projectRoot: '.', + astroRoot: './astro', + dist: './_site', +}; diff --git a/test/fixtures/astro-doctype/astro/pages/prepend.astro b/test/fixtures/astro-doctype/astro/pages/prepend.astro new file mode 100644 index 000000000..f8fb1bacd --- /dev/null +++ b/test/fixtures/astro-doctype/astro/pages/prepend.astro @@ -0,0 +1,8 @@ +--- +let title = 'My Site'; +--- + +<html lang="en"> + <head><title>{title}</title></head> + <body><h1>Hello world</h1></body> +</html>
\ No newline at end of file diff --git a/test/fixtures/astro-doctype/astro/pages/preserve.astro b/test/fixtures/astro-doctype/astro/pages/preserve.astro new file mode 100644 index 000000000..3e1ca934f --- /dev/null +++ b/test/fixtures/astro-doctype/astro/pages/preserve.astro @@ -0,0 +1,9 @@ +--- +let title = 'My Site'; +--- + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> + <head><title>{title}</title></head> + <body><h1>Hello world</h1></body> +</html>
\ No newline at end of file diff --git a/test/react-component.test.js b/test/react-component.test.js index 85892a623..48608f902 100644 --- a/test/react-component.test.js +++ b/test/react-component.test.js @@ -17,7 +17,7 @@ React.before(async () => { }; try { - runtime = await createRuntime(astroConfig, logging); + runtime = await createRuntime(astroConfig, {logging}); } catch (err) { console.error(err); setupError = err; diff --git a/test/snowpack-integration.test.js b/test/snowpack-integration.test.js index 097550d39..ba2c509c0 100644 --- a/test/snowpack-integration.test.js +++ b/test/snowpack-integration.test.js @@ -25,7 +25,7 @@ SnowpackDev.before(async () => { }; try { - runtime = await createRuntime(astroConfig, logging); + runtime = await createRuntime(astroConfig, {logging}); } catch (err) { console.error(err); setupError = err; |