summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/dependency-tree-esm/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@11ty/dependency-tree-esm/main.js')
-rw-r--r--node_modules/@11ty/dependency-tree-esm/main.js98
1 files changed, 98 insertions, 0 deletions
diff --git a/node_modules/@11ty/dependency-tree-esm/main.js b/node_modules/@11ty/dependency-tree-esm/main.js
new file mode 100644
index 0000000..ad2e0d0
--- /dev/null
+++ b/node_modules/@11ty/dependency-tree-esm/main.js
@@ -0,0 +1,98 @@
+const path = require("path");
+const { existsSync } = require("fs");
+const { readFile } = require("fs/promises");
+
+const acorn = require("acorn");
+const normalizePath = require("normalize-path");
+const { TemplatePath } = require("@11ty/eleventy-utils");
+
+// Is *not* a bare specifier (e.g. 'some-package')
+// https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#terminology
+function isNonBareSpecifier(importSource) {
+ // Change \\ to / on Windows
+ let normalized = normalizePath(importSource);
+ // Relative specifier (e.g. './startup.js')
+ if(normalized.startsWith("./") || normalized.startsWith("../")) {
+ return true;
+ }
+ // Absolute specifier (e.g. 'file:///opt/nodejs/config.js')
+ if(normalized.startsWith("file:")) {
+ return true;
+ }
+
+ return false;
+}
+
+function normalizeFilePath(filePath) {
+ return TemplatePath.standardizeFilePath(path.relative(".", filePath));
+}
+
+function normalizeImportSourceToFilePath(filePath, source) {
+ let { dir } = path.parse(filePath);
+ let normalized = path.join(dir, source);
+ return normalizeFilePath(normalized);
+}
+
+function getImportAttributeType(attributes = []) {
+ for(let node of attributes) {
+ if(node.type === "ImportAttribute" && node.key.type === "Identifier" && node.key.name === "type") {
+ return node.value.value;
+ }
+ }
+}
+
+async function findByContents(contents, filePath, alreadyParsedSet) {
+ // Should we use dependency-graph for these relationships?
+ let sources = new Set();
+ let nestedSources = new Set();
+
+ let ast = acorn.parse(contents, {sourceType: "module", ecmaVersion: "latest"});
+
+ for(let node of ast.body) {
+ if(node.type === "ImportDeclaration" && isNonBareSpecifier(node.source.value)) {
+ let importAttributeType = getImportAttributeType(node?.attributes);
+ let normalized = normalizeImportSourceToFilePath(filePath, node.source.value);
+ if(normalized !== filePath) {
+ sources.add(normalized);
+
+ // Right now only `css` and `json` are valid but others might come later
+ if(!importAttributeType) {
+ nestedSources.add(normalized);
+ }
+ }
+ }
+ }
+
+ // Recurse for nested deps
+ for(let source of nestedSources) {
+ let s = await find(source, alreadyParsedSet);
+ for(let p of s) {
+ if(sources.has(p) || p === filePath) {
+ continue;
+ }
+
+ sources.add(p);
+ }
+ }
+
+ return Array.from(sources);
+}
+
+async function find(filePath, alreadyParsedSet = new Set()) {
+ // TODO add a cache here
+ // Unfortunately we need to read the entire file, imports need to be at the top level but they can be anywhere 🫠
+ let normalized = normalizeFilePath(filePath);
+ if(alreadyParsedSet.has(normalized) || !existsSync(filePath)) {
+ return [];
+ }
+ alreadyParsedSet.add(normalized);
+
+ let contents = await readFile(normalized, { encoding: 'utf8' });
+ let sources = await findByContents(contents, normalized, alreadyParsedSet);
+
+ return sources;
+}
+
+module.exports = {
+ find
+}; \ No newline at end of file