-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support relative imports #40
Changes from 21 commits
d13dbc8
640db4f
db1a051
6e976e7
e8e5e3c
0d6033c
1222cba
68456bb
909f798
294b484
a58687c
ab5439d
e276389
1a33113
ae9486d
ea807d0
0fc3aa3
e733072
f996813
4d16092
b1cdf2f
fd9b860
be546da
00e064c
55d068d
22ce01e
2ced0f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
import {Parser} from "acorn"; | ||
import type {Node} from "acorn"; | ||
import {simple} from "acorn-walk"; | ||
import {readFileSync} from "node:fs"; | ||
import {dirname, join, relative, resolve} from "node:path"; | ||
import {parseOptions} from "../javascript.js"; | ||
import {dirname, join} from "node:path"; | ||
import {type JavaScriptNode, parseOptions} from "../javascript.js"; | ||
import {getStringLiteralValue, isStringLiteral} from "./features.js"; | ||
|
||
export function findImports(body, root) { | ||
const imports: {name: string}[] = []; | ||
export function findImports(body: Node, root: string, sourcePath: string) { | ||
const imports: {name: string; type: "global" | "local"}[] = []; | ||
const features: {name: string; type: string}[] = []; | ||
const paths = new Set<string>(); | ||
|
||
simple(body, { | ||
|
@@ -19,8 +21,11 @@ export function findImports(body, root) { | |
function findImport(node) { | ||
if (isStringLiteral(node.source)) { | ||
const value = getStringLiteralValue(node.source); | ||
if (value.startsWith("./")) findLocalImports(join(root, value)); | ||
imports.push({name: value}); | ||
if (isLocalImport(value, root, sourcePath)) { | ||
findLocalImports(join(dirname(sourcePath), value)); | ||
} else { | ||
imports.push({name: value, type: "global"}); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -29,8 +34,10 @@ export function findImports(body, root) { | |
function findLocalImports(path) { | ||
if (paths.has(path)) return; | ||
paths.add(path); | ||
imports.push({type: "local", name: path}); | ||
features.push({type: "FileAttachment", name: path}); | ||
try { | ||
const input = readFileSync(path, "utf-8"); | ||
const input = readFileSync(join(root, path), "utf-8"); | ||
const program = Parser.parse(input, parseOptions); | ||
simple(program, { | ||
ImportDeclaration: findLocalImport, | ||
|
@@ -44,38 +51,41 @@ export function findImports(body, root) { | |
function findLocalImport(node) { | ||
if (isStringLiteral(node.source)) { | ||
const value = getStringLiteralValue(node.source); | ||
if (value.startsWith("./")) { | ||
const subpath = resolve(dirname(path), value); | ||
if (isLocalImport(value, root, path)) { | ||
const subpath = join(dirname(path), value); | ||
findLocalImports(subpath); | ||
imports.push({name: `./${relative(root, subpath)}`}); | ||
} else { | ||
imports.push({name: value}); | ||
imports.push({name: value, type: "global"}); | ||
// non-local imports don't need to be promoted to file attachments | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the functions findImport() and findLocalImport() are the same (?). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
} | ||
} | ||
|
||
return imports; | ||
return {imports, features}; | ||
} | ||
|
||
// TODO parallelize multiple static imports | ||
// TODO need to know the local path of the importing notebook; this assumes it’s in the root | ||
export function rewriteImports(output, root) { | ||
simple(root.body, { | ||
export function rewriteImports(output: any, rootNode: JavaScriptNode, root: string, sourcePath: string) { | ||
simple(rootNode.body, { | ||
ImportExpression(node: any) { | ||
if (isStringLiteral(node.source)) { | ||
const value = getStringLiteralValue(node.source); | ||
output.replaceLeft( | ||
node.source.start, | ||
node.source.end, | ||
JSON.stringify(value.startsWith("./") ? `/_file/${value.slice(2)}` : resolveImport(value)) | ||
JSON.stringify( | ||
isLocalImport(value, root, sourcePath) | ||
? join("/_file/", join(dirname(sourcePath), value)) | ||
: resolveImport(value) | ||
) | ||
); | ||
} | ||
}, | ||
ImportDeclaration(node: any) { | ||
if (isStringLiteral(node.source)) { | ||
const value = getStringLiteralValue(node.source); | ||
root.async = true; | ||
rootNode.async = true; | ||
output.replaceLeft( | ||
node.start, | ||
node.end, | ||
|
@@ -86,7 +96,9 @@ export function rewriteImports(output, root) { | |
? node.specifiers.find(isNamespaceSpecifier).local.name | ||
: "{}" | ||
} = await import(${JSON.stringify( | ||
value.startsWith("./") ? `/_file/${value.slice(2)}` : resolveImport(value) | ||
isLocalImport(value, root, sourcePath) | ||
? join("/_file/", join(dirname(sourcePath), value)) | ||
: resolveImport(value) | ||
)});` | ||
); | ||
} | ||
|
@@ -102,6 +114,13 @@ function rewriteImportSpecifier(node) { | |
: `${node.imported.name}: ${node.local.name}`; | ||
} | ||
|
||
export function isLocalImport(value: string, root: string, sourcePath: string): boolean { | ||
return ( | ||
["./", "../", "/"].some((prefix) => value.startsWith(prefix)) && | ||
join(root + "/", dirname(sourcePath), value).startsWith(root) | ||
); | ||
} | ||
|
||
function isNamespaceSpecifier(node) { | ||
return node.type === "ImportNamespaceSpecifier"; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't really like how it returns features and imports in a function called "findImports" 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will also have additional features like Secrets soon, so we should anticipate that if we can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed a ticket to address the confusion around adding local fetches and local imports as file attachments: #87