Add support for doctype (#37)

* Add support for doctype

* Automatically prepend doctype
This commit is contained in:
Matthew Phillips 2021-03-30 10:51:31 -04:00 committed by GitHub
parent dbd764bdeb
commit 3b27eaac43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 162 additions and 28 deletions

View file

@ -7,6 +7,7 @@ import BaseLayout from '../components/BaseLayout.astro';
export let content: any;
---
<!doctype html>
<html>
<head>

View file

@ -7,6 +7,7 @@ import BaseLayout from '../components/BaseLayout.astro';
export let content: any;
---
<!doctype html>
<html>
<head>

View file

@ -6,6 +6,7 @@ import { format as formatDate, parseISO } from 'date-fns';
export let content: any;
---
<!doctype html>
<html>
<head>

View file

@ -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>

View file

@ -40,6 +40,7 @@ let communityGuides;
});
---
<!doctype html>
<html>
<head>

View file

@ -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>

View file

@ -16,6 +16,7 @@ const title = 'Community & News';
const description = 'Snowpack community news and companies that use Snowpack.';
---
<!doctype html>
<html>
<head>

View file

@ -7,6 +7,7 @@ let title = 'The Snowpack Plugin Catalog';
let description = 'Snowpack plugins allow for configuration-minimal tooling integration.';
---
<!doctype html>
<html>
<head>

View file

@ -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;

View file

@ -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.
}
}
}

View file

@ -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);

View file

@ -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 ' ';

View file

@ -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',

View file

@ -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 () => {

View file

@ -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();

View file

@ -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;

View file

@ -16,7 +16,7 @@ StylesSSR.before(async () => {
dest: process.stderr,
};
runtime = await createRuntime(astroConfig, logging);
runtime = await createRuntime(astroConfig, {logging});
});
StylesSSR.after(async () => {

View file

@ -0,0 +1,5 @@
export default {
projectRoot: '.',
astroRoot: './astro',
dist: './_site',
};

View file

@ -0,0 +1,8 @@
---
let title = 'My Site';
---
<html lang="en">
<head><title>{title}</title></head>
<body><h1>Hello world</h1></body>
</html>

View file

@ -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>

View file

@ -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;

View file

@ -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;