Skip to content

Commit 83d21f2

Browse files
authored
import.meta.resolve for files (#1696)
1 parent e4668f1 commit 83d21f2

30 files changed

+259
-28
lines changed

src/build.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export async function build(
250250
return applyHash(join("/_import", path), hash);
251251
};
252252
for (const path of localImports) {
253+
if (!path.endsWith(".js")) continue;
253254
const module = findModule(root, path);
254255
if (!module) throw new Error(`import not found: ${path}`);
255256
const sourcePath = join(root, module.path);

src/javascript/imports.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export interface ImportReference {
1414
name: string;
1515
/** Is this a reference to a local module, or a non-local (e.g., npm) one? */
1616
type: "local" | "global";
17-
/** Is this a static import declaration, or a dynamic import expression? */
18-
method: "static" | "dynamic";
17+
/** Is this a static import, a dynamic import, or import.meta.resolve? */
18+
method: "static" | "dynamic" | "resolve";
1919
}
2020

2121
export type ImportNode = ImportDeclaration | ImportExpression;
@@ -98,9 +98,9 @@ export function findImports(body: Node, path: string, input: string): ImportRefe
9898
if (isPathImport(name)) {
9999
const localPath = resolveLocalPath(path, name);
100100
if (!localPath) throw syntaxError(`non-local import: ${name}`, node, input); // prettier-ignore
101-
addImport({name: relativePath(path, localPath), type: "local", method: "dynamic"});
101+
addImport({name: relativePath(path, localPath), type: "local", method: "resolve"});
102102
} else {
103-
addImport({name, type: "global", method: "dynamic"});
103+
addImport({name, type: "global", method: "resolve"});
104104
}
105105
}
106106

src/javascript/transpile.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {Sourcemap} from "../sourcemap.js";
1010
import type {FileExpression} from "./files.js";
1111
import {findFiles} from "./files.js";
1212
import type {ExportNode, ImportNode} from "./imports.js";
13-
import {hasImportDeclaration, isImportMetaResolve} from "./imports.js";
13+
import {hasImportDeclaration, isImportMetaResolve, isJavaScript} from "./imports.js";
1414
import type {FileInfo} from "./module.js";
1515
import {findParams} from "./params.js";
1616
import type {JavaScriptNode} from "./parse.js";
@@ -24,11 +24,12 @@ export interface TranspileOptions {
2424
params?: Params;
2525
mode?: string;
2626
resolveImport?: (specifier: string) => string;
27+
resolveFile?: (specifier: string) => string;
2728
}
2829

2930
export function transpileJavaScript(
3031
node: JavaScriptNode,
31-
{id, path, params, mode, resolveImport}: TranspileOptions
32+
{id, path, params, mode, resolveImport, resolveFile}: TranspileOptions
3233
): string {
3334
let async = node.async;
3435
const inputs = Array.from(new Set<string>(node.references.map((r) => r.name)));
@@ -39,7 +40,7 @@ export function transpileJavaScript(
3940
const output = new Sourcemap(node.input).trim();
4041
if (params) rewriteParams(output, node.body, params, node.input);
4142
rewriteImportDeclarations(output, node.body, resolveImport);
42-
rewriteImportExpressions(output, node.body, resolveImport);
43+
rewriteImportExpressions(output, node.body, resolveImport, resolveFile);
4344
rewriteFileExpressions(output, node.files, path);
4445
if (display) output.insertLeft(0, "display(await(\n").insertRight(node.input.length, "\n))");
4546
output.insertLeft(0, `, body: ${async ? "async " : ""}(${inputs}) => {\n`);
@@ -134,7 +135,9 @@ export async function transpileModule(
134135
for (const node of calls) {
135136
const source = node.arguments[0];
136137
if (isImportMetaResolve(node) && isStringLiteral(source)) {
137-
await rewriteImportSource(source);
138+
const value = getStringLiteralValue(source);
139+
const resolution = isPathImport(value) && !isJavaScript(value) ? resolveFile(value) : await resolveImport(value);
140+
output.replaceLeft(source.start, source.end, JSON.stringify(resolution));
138141
}
139142
}
140143

@@ -152,10 +155,11 @@ function rewriteFileExpressions(output: Sourcemap, files: FileExpression[], path
152155
function rewriteImportExpressions(
153156
output: Sourcemap,
154157
body: Node,
155-
resolve: (specifier: string) => string = String
158+
resolveImport: (specifier: string) => string = String,
159+
resolveFile: (specifier: string) => string = String
156160
): void {
157161
function rewriteImportSource(source: StringLiteral) {
158-
output.replaceLeft(source.start, source.end, JSON.stringify(resolve(getStringLiteralValue(source))));
162+
output.replaceLeft(source.start, source.end, JSON.stringify(resolveImport(getStringLiteralValue(source))));
159163
}
160164
simple(body, {
161165
ImportExpression(node) {
@@ -167,7 +171,8 @@ function rewriteImportExpressions(
167171
CallExpression(node) {
168172
const source = node.arguments[0];
169173
if (isImportMetaResolve(node) && isStringLiteral(source)) {
170-
const resolution = resolve(getStringLiteralValue(source));
174+
const value = getStringLiteralValue(source);
175+
const resolution = isPathImport(value) && !isJavaScript(value) ? resolveFile(value) : resolveImport(value);
171176
output.replaceLeft(
172177
node.start,
173178
node.end,

src/render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ import ${preview || page.code.length ? `{${preview ? "open, " : ""}define} from
7979
: ""
8080
}${data?.sql ? `\n${registerTables(data.sql, options)}` : ""}
8181
${preview ? `\nopen({hash: ${JSON.stringify(resolvers.hash)}, eval: (body) => eval(body)});\n` : ""}${page.code
82-
.map(({node, id, mode}) => `\n${transpileJavaScript(node, {id, path, params, mode, resolveImport})}`)
82+
.map(({node, id, mode}) => `\n${transpileJavaScript(node, {id, path, params, mode, resolveImport, resolveFile})}`)
8383
.join("")}`)}
8484
</script>${sidebar ? html`\n${await renderSidebar(options, resolvers)}` : ""}
8585
<div id="observablehq-center">${renderHeader(page.header, resolvers)}${

src/resolvers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {createHash} from "node:crypto";
22
import {extname, join} from "node:path/posix";
33
import {findAssets} from "./html.js";
44
import {defaultGlobals} from "./javascript/globals.js";
5+
import {isJavaScript} from "./javascript/imports.js";
56
import {getFileHash, getModuleHash, getModuleInfo} from "./javascript/module.js";
67
import {extractJsrSpecifier, resolveJsrImport, resolveJsrImports} from "./jsr.js";
78
import {getImplicitDependencies, getImplicitDownloads} from "./libraries.js";
@@ -114,6 +115,7 @@ export async function getResolvers(page: MarkdownPage, config: ResolversConfig):
114115
for (const i of node.imports) {
115116
(i.type === "local" ? localImports : globalImports).add(i.name);
116117
if (i.method === "static") staticImports.add(i.name);
118+
if (i.type === "local" && i.method === "resolve" && !isJavaScript(i.name)) files.add(i.name);
117119
}
118120
}
119121

test/input/build/imports/foo/foo.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ FileAttachment("/top.js");
1414
import {foobar} from "./foo%20bar.js";
1515
display(foobar);
1616
```
17+
18+
```js
19+
import.meta.resolve("./hello.txt")
20+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello

test/input/imports/local-fetch-data.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const foo = import.meta.resolve("./fetch-local-data.json");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const foo = import.meta.resolve("./foo.js");

0 commit comments

Comments
 (0)