diff options
Diffstat (limited to 'src/optimize')
-rw-r--r-- | src/optimize/index.ts | 85 | ||||
-rw-r--r-- | src/optimize/styles.ts | 51 | ||||
-rw-r--r-- | src/optimize/types.ts | 17 |
3 files changed, 153 insertions, 0 deletions
diff --git a/src/optimize/index.ts b/src/optimize/index.ts new file mode 100644 index 000000000..d22854a32 --- /dev/null +++ b/src/optimize/index.ts @@ -0,0 +1,85 @@ +import type { Ast, TemplateNode } from '../compiler/interfaces'; +import { NodeVisitor, Optimizer, VisitorFn } from './types'; +import { walk } from 'estree-walker'; + +import optimizeStyles from './styles.js'; + +interface VisitorCollection { + enter: Map<string, VisitorFn[]>; + leave: Map<string, VisitorFn[]>; +} + +function addVisitor(visitor: NodeVisitor, collection: VisitorCollection, nodeName: string, event: 'enter' | 'leave') { + if(event in visitor) { + if(collection[event].has(nodeName)) { + collection[event].get(nodeName)!.push(visitor[event]!); + } + + collection.enter.set(nodeName, [visitor[event]!]); + } +} + +function collectVisitors(optimizer: Optimizer, htmlVisitors: VisitorCollection, cssVisitors: VisitorCollection, finalizers: Array<() => Promise<void>>) { + if(optimizer.visitors) { + if(optimizer.visitors.html) { + for(const [nodeName, visitor] of Object.entries(optimizer.visitors.html)) { + addVisitor(visitor, htmlVisitors, nodeName, 'enter'); + addVisitor(visitor, htmlVisitors, nodeName, 'leave'); + } + } + if(optimizer.visitors.css) { + for(const [nodeName, visitor] of Object.entries(optimizer.visitors.css)) { + addVisitor(visitor, cssVisitors, nodeName, 'enter'); + addVisitor(visitor, cssVisitors, nodeName, 'leave'); + } + } + } + finalizers.push(optimizer.finalize); +} + +function createVisitorCollection() { + return { + enter: new Map<string, VisitorFn[]>(), + leave: new Map<string, VisitorFn[]>(), + }; +} + +function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection) { + walk(tmpl, { + enter(node) { + if(collection.enter.has(node.type)) { + const fns = collection.enter.get(node.type)!; + for(let fn of fns) { + fn(node); + } + } + }, + leave(node) { + if(collection.leave.has(node.type)) { + const fns = collection.leave.get(node.type)!; + for(let fn of fns) { + fn(node); + } + } + } + }); +} + +interface OptimizeOptions { + filename: string, + fileID: string +} + +export async function optimize(ast: Ast, opts: OptimizeOptions) { + const htmlVisitors = createVisitorCollection(); + const cssVisitors = createVisitorCollection(); + const finalizers: Array<() => Promise<void>> = []; + + collectVisitors(optimizeStyles(opts), htmlVisitors, cssVisitors, finalizers); + + walkAstWithVisitors(ast.html, htmlVisitors); + walkAstWithVisitors(ast.css, cssVisitors); + + // Run all of the finalizer functions in parallel because why not. + await Promise.all(finalizers.map(fn => fn())); +}
\ No newline at end of file diff --git a/src/optimize/styles.ts b/src/optimize/styles.ts new file mode 100644 index 000000000..b654ca7d1 --- /dev/null +++ b/src/optimize/styles.ts @@ -0,0 +1,51 @@ +import type { Ast, TemplateNode } from '../compiler/interfaces'; +import type { Optimizer } from './types' +import { transformStyle } from '../style.js'; + +export default function({ filename, fileID }: { filename: string, fileID: string }): Optimizer { + const classNames: Set<string> = new Set(); + let stylesPromises: any[] = []; + + return { + visitors: { + html: { + Element: { + enter(node) { + for(let attr of node.attributes) { + if(attr.name === 'class') { + for(let value of attr.value) { + if(value.type === 'Text') { + const classes = value.data.split(' '); + for(const className in classes) { + classNames.add(className); + } + } + } + } + } + } + } + }, + css: { + Style: { + enter(node: TemplateNode) { + const code = node.content.styles; + const typeAttr = node.attributes && node.attributes.find(({ name }: { name: string }) => name === 'type'); + stylesPromises.push( + transformStyle(code, { + type: (typeAttr.value[0] && typeAttr.value[0].raw) || undefined, + classNames, + filename, + fileID, + }) + ); // TODO: styles needs to go in <head> + } + } + } + }, + async finalize() { + const styles = await Promise.all(stylesPromises); // TODO: clean this up + console.log({ styles }); + } + }; +}
\ No newline at end of file diff --git a/src/optimize/types.ts b/src/optimize/types.ts new file mode 100644 index 000000000..e22700cba --- /dev/null +++ b/src/optimize/types.ts @@ -0,0 +1,17 @@ +import type { TemplateNode } from '../compiler/interfaces'; + + +export type VisitorFn = (node: TemplateNode) => void; + +export interface NodeVisitor { + enter?: VisitorFn; + leave?: VisitorFn; +} + +export interface Optimizer { + visitors?: { + html?: Record<string, NodeVisitor>, + css?: Record<string, NodeVisitor> + }, + finalize: () => Promise<void> +}
\ No newline at end of file |