Respect comments when scanning imports

Use es-module-lexer for import scanning in HMX scripts
This commit is contained in:
Drew Powers 2021-03-18 18:10:08 -06:00
parent d27bd74b05
commit d75107a20e
8 changed files with 66 additions and 56 deletions

View file

@ -14,3 +14,12 @@ export interface JsxItem {
name: string; name: string;
jsx: string; jsx: string;
} }
export interface TransformResult {
script: string;
items: JsxItem[];
}
export interface CompileResult {
contents: string;
}

View file

@ -1,6 +1,6 @@
import type { CompileOptions } from '../@types/compiler'; import type { CompileOptions } from '../@types/compiler';
import type { Ast, TemplateNode } from '../compiler/interfaces'; 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 eslexer from 'es-module-lexer';
import esbuild from 'esbuild'; import esbuild from 'esbuild';
@ -14,13 +14,13 @@ interface Attribute {
end: number; end: number;
type: 'Attribute'; type: 'Attribute';
name: string; name: string;
value: any value: any;
} }
interface CodeGenOptions { interface CodeGenOptions {
compileOptions: CompileOptions; compileOptions: CompileOptions;
filename: string; filename: string;
fileID: string fileID: string;
} }
function internalImport(internalPath: 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); 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 { function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string {
// esbuild treeshakes unused imports. In our case these are components, so let's keep them. // esbuild treeshakes unused imports. In our case these are components, so let's keep them.
const imports: Array<string> = []; const imports = eslexer
raw.replace(patternImport, (value: string) => { .parse(raw)[0]
imports.push(value); .filter(({ d }) => d === -1)
return value; .map((i: any) => raw.substring(i.ss, i.se));
});
let { code } = transformSync(raw, { let { code } = transformSync(raw, {
loader, loader,
@ -169,7 +167,8 @@ function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string {
return code; return code;
} }
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions) { export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> {
await eslexer.init;
const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx'); const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx');
// Compile scripts as TypeScript, always // Compile scripts as TypeScript, always

View file

@ -16,7 +16,7 @@ snowpackLogger.level = 'silent';
const logging: LogOptions = { const logging: LogOptions = {
level: 'debug', level: 'debug',
dest: defaultLogDestination dest: defaultLogDestination,
}; };
export default async function (astroConfig: AstroConfig) { export default async function (astroConfig: AstroConfig) {

View file

@ -61,13 +61,13 @@ function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection)
fn(node); fn(node);
} }
} }
} },
}); });
} }
interface OptimizeOptions { interface OptimizeOptions {
filename: string, filename: string;
fileID: string fileID: string;
} }
export async function optimize(ast: Ast, opts: OptimizeOptions) { export async function optimize(ast: Ast, opts: OptimizeOptions) {
@ -81,5 +81,5 @@ export async function optimize(ast: Ast, opts: OptimizeOptions) {
walkAstWithVisitors(ast.css, cssVisitors); walkAstWithVisitors(ast.css, cssVisitors);
// Run all of the finalizer functions in parallel because why not. // Run all of the finalizer functions in parallel because why not.
await Promise.all(finalizers.map(fn => fn())); await Promise.all(finalizers.map((fn) => fn()));
} }

View file

@ -1,8 +1,8 @@
import type { Ast, TemplateNode } from '../compiler/interfaces'; import type { Ast, TemplateNode } from '../compiler/interfaces';
import type { Optimizer } from './types' import type { Optimizer } from './types';
import { transformStyle } from '../style.js'; 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<string> = new Set(); const classNames: Set<string> = new Set();
let stylesPromises: any[] = []; let stylesPromises: any[] = [];
@ -23,8 +23,8 @@ export default function({ filename, fileID }: { filename: string, fileID: string
} }
} }
} }
} },
} },
}, },
css: { css: {
Style: { Style: {
@ -39,13 +39,13 @@ export default function({ filename, fileID }: { filename: string, fileID: string
fileID, fileID,
}) })
); // TODO: styles needs to go in <head> ); // TODO: styles needs to go in <head>
} },
} },
} },
}, },
async finalize() { async finalize() {
const styles = await Promise.all(stylesPromises); // TODO: clean this up const styles = await Promise.all(stylesPromises); // TODO: clean this up
console.log({ styles }); // console.log({ styles });
} },
}; };
} }

View file

@ -85,7 +85,7 @@ export async function transformStyle(
}), }),
autoprefixer(), autoprefixer(),
]) ])
.process(css, { from: filename }) .process(css, { from: filename, to: undefined })
.then((result) => result.css); .then((result) => result.css);
return { css, cssModules }; return { css, cssModules };

View file

@ -1,12 +1,11 @@
import type { LogOptions } from './logger.js'; import type { LogOptions } from './logger.js';
import path from 'path'; import path from 'path';
import esbuild from 'esbuild';
import eslexer from 'es-module-lexer';
import micromark from 'micromark'; import micromark from 'micromark';
import gfmSyntax from 'micromark-extension-gfm'; import gfmSyntax from 'micromark-extension-gfm';
import matter from 'gray-matter'; import matter from 'gray-matter';
import gfmHtml from 'micromark-extension-gfm/html.js'; import gfmHtml from 'micromark-extension-gfm/html.js';
import { CompileResult, TransformResult } from './@types/astro';
import { parse } from './compiler/index.js'; import { parse } from './compiler/index.js';
import markdownEncode from './markdown-encode.js'; import markdownEncode from './markdown-encode.js';
import { defaultLogOptions } from './logger.js'; import { defaultLogOptions } from './logger.js';
@ -30,12 +29,11 @@ function internalImport(internalPath: string) {
interface ConvertHmxOptions { interface ConvertHmxOptions {
compileOptions: CompileOptions; compileOptions: CompileOptions;
filename: string; filename: string;
fileID: string fileID: string;
} }
async function convertHmxToJsx(template: string, opts: ConvertHmxOptions) { async function convertHmxToJsx(template: string, opts: ConvertHmxOptions): Promise<TransformResult> {
const { filename } = opts; const { filename } = opts;
await eslexer.init;
// 1. Parse // 1. Parse
const ast = parse(template, { const ast = parse(template, {
@ -49,7 +47,10 @@ async function convertHmxToJsx(template: string, opts: ConvertHmxOptions) {
return await codegen(ast, opts); 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<TransformResult> {
// This doesn't work. // This doesn't work.
const { data: _frontmatterData, content } = matter(contents); const { data: _frontmatterData, content } = matter(contents);
const mdHtml = micromark(content, { const mdHtml = micromark(content, {
@ -84,7 +85,7 @@ async function convertMdToJsx(contents: string, { compileOptions, filename, file
async function transformFromSource( async function transformFromSource(
contents: string, contents: string,
{ compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } { compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
): Promise<ReturnType<typeof convertHmxToJsx>> { ): Promise<TransformResult> {
const fileID = path.relative(projectRoot, filename); const fileID = path.relative(projectRoot, filename);
switch (path.extname(filename)) { switch (path.extname(filename)) {
case '.hmx': case '.hmx':
@ -99,8 +100,9 @@ async function transformFromSource(
export async function compilePage( export async function compilePage(
source: string, source: string,
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
) { ): Promise<CompileResult> {
const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
const headItem = sourceJsx.items.find((item) => item.name === 'head'); const headItem = sourceJsx.items.find((item) => item.name === 'head');
const bodyItem = sourceJsx.items.find((item) => item.name === 'body'); const bodyItem = sourceJsx.items.find((item) => item.name === 'body');
const headItemJsx = !headItem ? 'null' : headItem.jsx.replace('"head"', 'isRoot ? "head" : Fragment'); 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( export async function compileComponent(
source: string, source: string,
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string } { compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
) { ): Promise<CompileResult> {
const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot }); const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
const componentJsx = sourceJsx.items.find((item) => item.name === 'Component'); const componentJsx = sourceJsx.items.find((item) => item.name === 'Component');
if (!componentJsx) { if (!componentJsx) {