summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@skypack.dev> 2021-03-16 10:23:29 -0400
committerGravatar Matthew Phillips <matthew@skypack.dev> 2021-03-16 10:23:29 -0400
commite5ebebbcd74df8027651f3cf717fb16ffe712367 (patch)
treebeed51f761bf12acf039dd3ee6eb395cb198d5ee /src
parent01c34ac5d4a9da9e1a15222653b53620f0e48df6 (diff)
downloadastro-e5ebebbcd74df8027651f3cf717fb16ffe712367.tar.gz
astro-e5ebebbcd74df8027651f3cf717fb16ffe712367.tar.zst
astro-e5ebebbcd74df8027651f3cf717fb16ffe712367.zip
Progress on preparsing
Diffstat (limited to 'src')
-rw-r--r--src/dev.ts2
-rw-r--r--src/parser.ts304
-rw-r--r--src/transform2.ts45
3 files changed, 350 insertions, 1 deletions
diff --git a/src/dev.ts b/src/dev.ts
index a9b785103..d7a4ec47f 100644
--- a/src/dev.ts
+++ b/src/dev.ts
@@ -35,7 +35,7 @@ export default async function(astroConfig: AstroConfig) {
[internalPath.pathname]: '/__hmx_internal__'
},
plugins: [
- ['hmx-v2/snowpack-plugin', hmxPlugOptions]
+ ['astro/snowpack-plugin', hmxPlugOptions]
],
devOptions: {
open: 'none',
diff --git a/src/parser.ts b/src/parser.ts
new file mode 100644
index 000000000..f47486f65
--- /dev/null
+++ b/src/parser.ts
@@ -0,0 +1,304 @@
+const [
+ CHARS,
+ TAG_START,
+ TAG_END,
+ END_TAG_START,
+ EQ,
+ EOF,
+ UNKNOWN
+] = Array.from(new Array(20), (x, i) => i + 1);
+
+const voidTags = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr',
+ 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
+
+type Visitor = (tag: Tag) => Tag;
+
+interface State {
+ code: string;
+ index: number;
+ visitor: Visitor;
+ tagName?: string;
+}
+
+interface Attribute {
+ name: string;
+ value?: string;
+ boolean: boolean;
+ start: number;
+ end: number;
+}
+
+interface Text {
+ type: 0;
+ data: string;
+ start: number;
+ end: number;
+}
+
+export interface Tag {
+ type: 1;
+ tagName: string;
+ attributes: Array<Attribute>;
+ children: Array<Tag | Text>;
+ void: boolean;
+ start: number;
+ end: number;
+}
+
+interface Document {
+ children: Array<Tag | Text>;
+}
+
+function stateChar(state: State) {
+ return state.code[state.index];
+}
+
+function stateNext(state: State) {
+ state.index++;
+ return stateChar(state);
+}
+
+function stateRewind(state: State) {
+ state.index--;
+ return stateChar(state);
+}
+
+function stateInBounds(state: State) {
+ return state.index < state.code.length;
+}
+
+function createState(code: string, visitor: Visitor): State {
+ return {
+ code,
+ index: 0,
+ visitor
+ };
+}
+
+function* _stringify(tag: Tag): Generator<string, void, unknown> {
+ yield '<';
+ yield tag.tagName;
+ for(let attr of tag.attributes) {
+ yield ' ';
+ yield `"${attr.name}"`;
+ if(!attr.boolean) {
+ yield '=';
+ yield `"${attr.value}"`;
+ }
+ }
+ if(!tag.void) {
+ for(let child of tag.children) {
+ if(child.type === 0) {
+ yield child.data;
+ } else {
+ yield * _stringify(child);
+ }
+ }
+ }
+}
+
+function stringify(tag: Tag) {
+ let out = '';
+ for(let chunk of _stringify(tag)) {
+ out += chunk;
+ }
+ return out;
+}
+
+function spliceSlice(str: string, index: number, count: number, add: string) {
+ // We cannot pass negative indexes directly to the 2nd slicing operation.
+ if (index < 0) {
+ index = str.length + index;
+ if (index < 0) {
+ index = 0;
+ }
+ }
+
+ return str.slice(0, index) + (add || "") + str.slice(index + count);
+}
+
+function replaceTag(state: State, tag: Tag) {
+ const origLen = tag.end - tag.start;
+ const html = stringify(tag);
+ const newLen = html.length;
+ const newCurIndex = tag.start + newLen;
+
+ state.code = spliceSlice(state.code, tag.start, origLen, html);
+ state.index = newCurIndex;
+}
+
+function consumeToken(state: State) {
+ do {
+ const c = stateNext(state);
+
+ if(/\s/.test(c)) {
+ continue;
+ }
+
+ if(c === '<') {
+ return TAG_START;
+ }
+
+ if(c === '>') {
+ return TAG_END;
+ }
+
+ if(c === '/') {
+ return END_TAG_START;
+ }
+
+ if(/[a-zA-Z]/.test(c)) {
+ return CHARS;
+ }
+
+ return UNKNOWN;
+ } while(stateInBounds(state));
+
+ return EOF;
+}
+
+function consumeText(state: State): Text {
+ let start = state.index;
+ let data = '';
+ let c = stateNext(state);
+ while(stateInBounds(state) && c !== '<') {
+ data += c;
+ c = stateNext(state);
+ }
+
+ return {
+ type: 0,
+ data,
+ start,
+ end: state.index - 1
+ };
+}
+
+function consumeTagName(state: State): string {
+ let name = '';
+ let token = consumeToken(state);
+ while(token === CHARS) {
+ name += stateChar(state);
+ token = consumeToken(state);
+ }
+ return name.toLowerCase();
+}
+
+function consumeAttribute(state: State): Attribute {
+ let start = state.index;
+ let name = '', token;
+ do {
+ name += stateChar(state).toLowerCase();
+ token = consumeToken(state);
+ } while(token === CHARS);
+
+ if(token !== EQ) {
+ stateRewind(state);
+ return {
+ name,
+ boolean: true,
+ start,
+ end: state.index - 1
+ };
+ }
+
+ let value = '';
+ do {
+ value += stateChar(state).toLowerCase();
+ token = consumeToken(state);
+ } while(token === CHARS);
+
+ return {
+ name,
+ value,
+ boolean: false,
+ start,
+ end: state.index - 1
+ };
+}
+
+function consumeChildren(state: State): Array<Tag | Text> {
+ const children: Array<Tag | Text> = [];
+
+ childLoop: while(stateInBounds(state)) {
+ const token = consumeToken(state);
+ switch(token) {
+ case TAG_START: {
+ const next = consumeToken(state);
+ if(next === END_TAG_START) {
+ consumeTagName(state);
+ consumeToken(state); // >
+ break childLoop;
+ } else {
+ stateRewind(state);
+ consumeTag(state);
+ }
+ break;
+ }
+ case CHARS: {
+ children.push(consumeText(state));
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ return children;
+}
+
+function consumeTag(state: State): Tag {
+ const start = state.index - 1;
+ const tagName = consumeTagName(state);
+ const attributes: Array<Attribute> = [];
+
+ let token = consumeToken(state);
+
+ // Collect attributes
+ attrLoop: while(token !== TAG_END) {
+ switch(token) {
+ case CHARS: {
+ attributes.push(consumeAttribute(state));
+ break;
+ }
+ default: {
+ break attrLoop;
+ }
+ }
+
+ token = consumeToken(state);
+ }
+
+ const children: Array<Tag | Text> = consumeChildren(state);
+
+ const node: Tag = {
+ type: 1,
+ tagName,
+ attributes,
+ children,
+ void: voidTags.has(tagName),
+ start,
+ end: state.index - 1
+ };
+
+ const replacement = state.visitor(node);
+ if(replacement !== node) {
+ replaceTag(state, node);
+ }
+
+ return node;
+}
+
+function consumeDocument(state: State): Document {
+ const children: Array<Tag | Text> = consumeChildren(state);
+
+ return {
+ children
+ };
+}
+
+export function preparse(code: string, visitor: Visitor) {
+ const state = createState(code, visitor);
+ consumeDocument(state);
+} \ No newline at end of file
diff --git a/src/transform2.ts b/src/transform2.ts
index 51399a48a..485c575c6 100644
--- a/src/transform2.ts
+++ b/src/transform2.ts
@@ -11,6 +11,7 @@ import matter from "gray-matter";
import gfmHtml from "micromark-extension-gfm/html.js";
import { walk, parse } from "./compiler.js";
import markdownEncode from './markdown-encode.js';
+import { preparse } from './parser.js';
const { transformSync } = esbuild;
@@ -161,11 +162,55 @@ function getComponentWrapper(
throw new Error("Unknown Component Type: " + name);
}
+function runPreparser(template: string): string {
+ const doc = preparse(template, tag => {
+ if(tag.tagName === 'script') {
+ let isSetup = false;
+ for(let attr of tag.attributes) {
+ if(attr.name === 'hmx' && attr.value === 'setup') {
+ isSetup = true;
+ break;
+ }
+ }
+ if(isSetup && typeof tag.children[0] === 'string') {
+ debugger;
+
+ const content = tag.children[0];
+ let { code } = transformSync(content, {
+ loader: "tsx",
+ jsxFactory: "h",
+ jsxFragment: "Fragment",
+ charset: "utf8",
+ });
+ return {
+ ...tag,
+ children: [{
+ type: 0,
+ data: code,
+ start: 0,
+ end: 0
+ }]
+ };
+ }
+ }
+
+ return tag;
+ });
+
+ // TODO codegen
+
+ return template;
+}
+
async function convertHmxToJsx(template: string, compileOptions: CompileOptions) {
await eslexer.init;
+
+ //template = runPreparser(template);
+
const ast = parse(template, {});
// Todo: Validate that `h` and `Fragment` aren't defined in the script
const script = ast.instance ? astring.generate(ast.instance.content) : "";
+
const [scriptImports] = eslexer.parse(script, "optional-sourcename");
const components = Object.fromEntries(
scriptImports.map((imp) => {