From d75107a20e971ad26a0398229b2b3fd13c45c6ee Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Thu, 18 Mar 2021 18:10:08 -0600 Subject: [PATCH] Respect comments when scanning imports Use es-module-lexer for import scanning in HMX scripts --- src/@types/astro.ts | 9 +++++++++ src/@types/compiler.ts | 2 +- src/codegen/index.ts | 21 ++++++++++----------- src/dev.ts | 4 ++-- src/optimize/index.ts | 34 +++++++++++++++++----------------- src/optimize/styles.ts | 30 +++++++++++++++--------------- src/style.ts | 2 +- src/transform2.ts | 20 +++++++++++--------- 8 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/@types/astro.ts b/src/@types/astro.ts index 02dcc8cf3..f7170cb61 100644 --- a/src/@types/astro.ts +++ b/src/@types/astro.ts @@ -14,3 +14,12 @@ export interface JsxItem { name: string; jsx: string; } + +export interface TransformResult { + script: string; + items: JsxItem[]; +} + +export interface CompileResult { + contents: string; +} diff --git a/src/@types/compiler.ts b/src/@types/compiler.ts index 343aa548b..456924267 100644 --- a/src/@types/compiler.ts +++ b/src/@types/compiler.ts @@ -3,4 +3,4 @@ import type { LogOptions } from '../logger'; export interface CompileOptions { logging: LogOptions; resolve: (p: string) => string; -} \ No newline at end of file +} diff --git a/src/codegen/index.ts b/src/codegen/index.ts index c0f4199c6..3257d9936 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -1,6 +1,6 @@ import type { CompileOptions } from '../@types/compiler'; import type { Ast, TemplateNode } from '../compiler/interfaces'; -import type { JsxItem } from '../@types/astro.js'; +import type { JsxItem, TransformResult } from '../@types/astro.js'; import eslexer from 'es-module-lexer'; import esbuild from 'esbuild'; @@ -14,13 +14,13 @@ interface Attribute { end: number; type: 'Attribute'; name: string; - value: any + value: any; } interface CodeGenOptions { compileOptions: CompileOptions; filename: string; - fileID: string + fileID: string; } function internalImport(internalPath: string) { @@ -144,14 +144,12 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url: throw new Error('Unknown Component Type: ' + name); } -const patternImport = new RegExp(/import(?:["'\s]*([\w*${}\n\r\t, ]+)from\s*)?["'\s]["'\s](.*[@\w_-]+)["'\s].*;$/, 'mg'); function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string { // esbuild treeshakes unused imports. In our case these are components, so let's keep them. - const imports: Array = []; - raw.replace(patternImport, (value: string) => { - imports.push(value); - return value; - }); + const imports = eslexer + .parse(raw)[0] + .filter(({ d }) => d === -1) + .map((i: any) => raw.substring(i.ss, i.se)); let { code } = transformSync(raw, { loader, @@ -169,7 +167,8 @@ function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string { return code; } -export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions) { +export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise { + await eslexer.init; const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx'); // Compile scripts as TypeScript, always @@ -339,4 +338,4 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions) { script: script + '\n' + Array.from(additionalImports).join('\n'), items, }; -} \ No newline at end of file +} diff --git a/src/dev.ts b/src/dev.ts index d2a268ef6..524379dd1 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -16,7 +16,7 @@ snowpackLogger.level = 'silent'; const logging: LogOptions = { level: 'debug', - dest: defaultLogDestination + dest: defaultLogDestination, }; export default async function (astroConfig: AstroConfig) { @@ -97,7 +97,7 @@ export default async function (astroConfig: AstroConfig) { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(html); } catch (err) { - switch(err.code) { + switch (err.code) { case 'parse-error': { err.filename = pathRelative(projectRoot.pathname, err.filename); debugger; diff --git a/src/optimize/index.ts b/src/optimize/index.ts index d22854a32..a0604b1c8 100644 --- a/src/optimize/index.ts +++ b/src/optimize/index.ts @@ -10,8 +10,8 @@ interface VisitorCollection { } function addVisitor(visitor: NodeVisitor, collection: VisitorCollection, nodeName: string, event: 'enter' | 'leave') { - if(event in visitor) { - if(collection[event].has(nodeName)) { + if (event in visitor) { + if (collection[event].has(nodeName)) { collection[event].get(nodeName)!.push(visitor[event]!); } @@ -20,15 +20,15 @@ function addVisitor(visitor: NodeVisitor, collection: VisitorCollection, nodeNam } function collectVisitors(optimizer: Optimizer, htmlVisitors: VisitorCollection, cssVisitors: VisitorCollection, finalizers: Array<() => Promise>) { - if(optimizer.visitors) { - if(optimizer.visitors.html) { - for(const [nodeName, visitor] of Object.entries(optimizer.visitors.html)) { + 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)) { + if (optimizer.visitors.css) { + for (const [nodeName, visitor] of Object.entries(optimizer.visitors.css)) { addVisitor(visitor, cssVisitors, nodeName, 'enter'); addVisitor(visitor, cssVisitors, nodeName, 'leave'); } @@ -47,27 +47,27 @@ function createVisitorCollection() { function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection) { walk(tmpl, { enter(node) { - if(collection.enter.has(node.type)) { + if (collection.enter.has(node.type)) { const fns = collection.enter.get(node.type)!; - for(let fn of fns) { + for (let fn of fns) { fn(node); } } }, leave(node) { - if(collection.leave.has(node.type)) { + if (collection.leave.has(node.type)) { const fns = collection.leave.get(node.type)!; - for(let fn of fns) { + for (let fn of fns) { fn(node); } } - } + }, }); } -interface OptimizeOptions { - filename: string, - fileID: string +interface OptimizeOptions { + filename: string; + fileID: string; } export async function optimize(ast: Ast, opts: OptimizeOptions) { @@ -81,5 +81,5 @@ export async function optimize(ast: Ast, opts: OptimizeOptions) { 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 + await Promise.all(finalizers.map((fn) => fn())); +} diff --git a/src/optimize/styles.ts b/src/optimize/styles.ts index b654ca7d1..6d15cb602 100644 --- a/src/optimize/styles.ts +++ b/src/optimize/styles.ts @@ -1,8 +1,8 @@ import type { Ast, TemplateNode } from '../compiler/interfaces'; -import type { Optimizer } from './types' +import type { Optimizer } from './types'; import { transformStyle } from '../style.js'; -export default function({ filename, fileID }: { filename: string, fileID: string }): Optimizer { +export default function ({ filename, fileID }: { filename: string; fileID: string }): Optimizer { const classNames: Set = new Set(); let stylesPromises: any[] = []; @@ -11,20 +11,20 @@ export default function({ filename, fileID }: { filename: string, fileID: string html: { Element: { enter(node) { - for(let attr of node.attributes) { - if(attr.name === 'class') { - for(let value of attr.value) { - if(value.type === 'Text') { + 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) { + for (const className in classes) { classNames.add(className); } } } } } - } - } + }, + }, }, css: { Style: { @@ -39,13 +39,13 @@ export default function({ filename, fileID }: { filename: string, fileID: string fileID, }) ); // TODO: styles needs to go in - } - } - } + }, + }, + }, }, async finalize() { const styles = await Promise.all(stylesPromises); // TODO: clean this up - console.log({ styles }); - } + // console.log({ styles }); + }, }; -} \ No newline at end of file +} diff --git a/src/style.ts b/src/style.ts index 527c13f99..489f22ce8 100644 --- a/src/style.ts +++ b/src/style.ts @@ -85,7 +85,7 @@ export async function transformStyle( }), autoprefixer(), ]) - .process(css, { from: filename }) + .process(css, { from: filename, to: undefined }) .then((result) => result.css); return { css, cssModules }; diff --git a/src/transform2.ts b/src/transform2.ts index 84277efdf..047ccc3d0 100644 --- a/src/transform2.ts +++ b/src/transform2.ts @@ -1,12 +1,11 @@ import type { LogOptions } from './logger.js'; import path from 'path'; -import esbuild from 'esbuild'; -import eslexer from 'es-module-lexer'; import micromark from 'micromark'; import gfmSyntax from 'micromark-extension-gfm'; import matter from 'gray-matter'; import gfmHtml from 'micromark-extension-gfm/html.js'; +import { CompileResult, TransformResult } from './@types/astro'; import { parse } from './compiler/index.js'; import markdownEncode from './markdown-encode.js'; import { defaultLogOptions } from './logger.js'; @@ -30,12 +29,11 @@ function internalImport(internalPath: string) { interface ConvertHmxOptions { compileOptions: CompileOptions; filename: string; - fileID: string + fileID: string; } -async function convertHmxToJsx(template: string, opts: ConvertHmxOptions) { +async function convertHmxToJsx(template: string, opts: ConvertHmxOptions): Promise { const { filename } = opts; - await eslexer.init; // 1. Parse const ast = parse(template, { @@ -49,7 +47,10 @@ async function convertHmxToJsx(template: string, opts: ConvertHmxOptions) { return await codegen(ast, opts); } -async function convertMdToJsx(contents: string, { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }) { +async function convertMdToJsx( + contents: string, + { compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string } +): Promise { // This doesn't work. const { data: _frontmatterData, content } = matter(contents); const mdHtml = micromark(content, { @@ -84,7 +85,7 @@ async function convertMdToJsx(contents: string, { compileOptions, filename, file async function transformFromSource( contents: string, { compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } -): Promise> { +): Promise { const fileID = path.relative(projectRoot, filename); switch (path.extname(filename)) { case '.hmx': @@ -99,8 +100,9 @@ async function transformFromSource( export async function compilePage( source: string, { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } -) { +): Promise { const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); + const headItem = sourceJsx.items.find((item) => item.name === 'head'); const bodyItem = sourceJsx.items.find((item) => item.name === 'body'); const headItemJsx = !headItem ? 'null' : headItem.jsx.replace('"head"', 'isRoot ? "head" : Fragment'); @@ -122,7 +124,7 @@ export function body({title, description, props}, child, isRoot) { return (${bod export async function compileComponent( source: string, { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } -) { +): Promise { const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); const componentJsx = sourceJsx.items.find((item) => item.name === 'Component'); if (!componentJsx) {