diff --git a/docs/lib/react.md b/docs/lib/react.md new file mode 100644 index 000000000..64099df0d --- /dev/null +++ b/docs/lib/react.md @@ -0,0 +1,52 @@ +# React + +```js echo +import {createRoot} from "react-dom/client"; + +const root = createRoot(display(document.createElement("DIV"))); +``` + +The code above creates a root; a place for React content to live. We render some content into the root below. + +```js echo +root.render(jsxs(createContent, {})); +``` + +The content is defined as a component, but hand-authored using the JSX runtime. You wouldn’t normally write this code by hand, but Framework doesn’t support JSX yet. We’re working on it. + +```js echo +import {useState} from "react"; +import {Fragment, jsx, jsxs} from "react/jsx-runtime"; + +function createContent() { + const [counter, setCounter] = useState(0); + return jsxs(Fragment, { + children: [ + jsx("p", { + children: ["Hello, world! ", counter] + }), + "\n", + jsx("p", { + children: "This content is rendered by React." + }), + "\n", + jsx("div", { + style: { + backgroundColor: "indigo", + padding: "1rem" + }, + onClick: () => setCounter(counter + 1), + children: jsxs("p", { + children: [ + "Try changing the background color to ", + jsx("code", { + children: "tomato" + }), + "." + ] + }) + }) + ] + }); +} +``` diff --git a/package.json b/package.json index 3f6444fa6..91fe23a00 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,9 @@ "@clack/prompts": "^0.7.0", "@observablehq/inputs": "^0.10.6", "@observablehq/runtime": "^5.9.4", + "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-virtual": "^3.0.2", "acorn": "^8.11.2", "acorn-walk": "^8.3.0", "ci-info": "^4.0.0", @@ -118,6 +120,8 @@ "glob": "^10.3.10", "mocha": "^10.2.0", "prettier": "^3.0.3 <3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "rimraf": "^5.0.5", "tempy": "^3.1.0", "typescript": "^5.2.2", diff --git a/src/files.ts b/src/files.ts index 5a24fea91..b082989e6 100644 --- a/src/files.ts +++ b/src/files.ts @@ -48,21 +48,17 @@ export function* visitMarkdownFiles(root: string): Generator { } } -/** Yields every file within the given root, recursively. */ +/** Yields every file within the given root, recursively, ignoring .observablehq. */ export function* visitFiles(root: string): Generator { const visited = new Set(); const queue: string[] = [(root = normalize(root))]; - try { - visited.add(statSync(join(root, ".observablehq")).ino); - } catch { - // ignore the .observablehq directory, if it exists - } for (const path of queue) { const status = statSync(path); if (status.isDirectory()) { if (visited.has(status.ino)) continue; // circular symlink visited.add(status.ino); for (const entry of readdirSync(path)) { + if (entry === ".observablehq") continue; // ignore the .observablehq directory queue.push(join(path, entry)); } } else { diff --git a/src/node.ts b/src/node.ts index b155d98c2..8f4fa5b5f 100644 --- a/src/node.ts +++ b/src/node.ts @@ -4,7 +4,9 @@ import {createRequire} from "node:module"; import op from "node:path"; import {extname, join} from "node:path/posix"; import {pathToFileURL} from "node:url"; +import commonjs from "@rollup/plugin-commonjs"; import {nodeResolve} from "@rollup/plugin-node-resolve"; +import virtual from "@rollup/plugin-virtual"; import {packageDirectory} from "pkg-dir"; import type {AstNode, OutputChunk, Plugin, ResolveIdResult} from "rollup"; import {rollup} from "rollup"; @@ -12,15 +14,15 @@ import esbuild from "rollup-plugin-esbuild"; import {prepareOutput, toOsPath} from "./files.js"; import type {ImportReference} from "./javascript/imports.js"; import {isJavaScript, parseImports} from "./javascript/imports.js"; -import {parseNpmSpecifier} from "./npm.js"; -import {isPathImport} from "./path.js"; +import {parseNpmSpecifier, rewriteNpmImports} from "./npm.js"; +import {isPathImport, relativePath} from "./path.js"; import {faint} from "./tty.js"; export async function resolveNodeImport(root: string, spec: string): Promise { return resolveNodeImportInternal(op.join(root, ".observablehq", "cache", "_node"), root, spec); } -const bundlePromises = new Map>(); +const bundlePromises = new Map>(); async function resolveNodeImportInternal(cacheRoot: string, packageRoot: string, spec: string): Promise { const {name, path = "."} = parseNpmSpecifier(spec); @@ -31,24 +33,23 @@ async function resolveNodeImportInternal(cacheRoot: string, packageRoot: string, const {version} = JSON.parse(await readFile(op.join(packageResolution, "package.json"), "utf-8")); const resolution = `${name}@${version}/${extname(path) ? path : path === "." ? "index.js" : `${path}.js`}`; const outputPath = op.join(cacheRoot, toOsPath(resolution)); - if (!existsSync(outputPath)) { - let promise = bundlePromises.get(outputPath); - if (!promise) { - promise = (async () => { - process.stdout.write(`${spec} ${faint("→")} ${resolution}\n`); - await prepareOutput(outputPath); - if (isJavaScript(pathResolution)) { - await writeFile(outputPath, await bundle(spec, cacheRoot, packageResolution)); - } else { - await copyFile(pathResolution, outputPath); - } - })(); - bundlePromises.set(outputPath, promise); - promise.catch(console.error).then(() => bundlePromises.delete(outputPath)); + const resolutionPath = `/_node/${resolution}`; + if (existsSync(outputPath)) return resolutionPath; + let promise = bundlePromises.get(outputPath); + if (promise) return promise; // coalesce concurrent requests + promise = (async () => { + console.log(`${spec} ${faint("→")} ${outputPath}`); + await prepareOutput(outputPath); + if (isJavaScript(pathResolution)) { + await writeFile(outputPath, await bundle(resolutionPath, spec, require, cacheRoot, packageResolution), "utf-8"); + } else { + await copyFile(pathResolution, outputPath); } - await promise; - } - return `/_node/${resolution}`; + return resolutionPath; + })(); + promise.catch(console.error).then(() => bundlePromises.delete(outputPath)); + bundlePromises.set(outputPath, promise); + return promise; } /** @@ -69,29 +70,59 @@ export function extractNodeSpecifier(path: string): string { return path.replace(/^\/_node\//, ""); } -async function bundle(input: string, cacheRoot: string, packageRoot: string): Promise { +/** + * React (and its dependencies) are distributed as CommonJS modules, and worse, + * they’re incompatible with cjs-module-lexer; so when we try to import them as + * ES modules we only see a default export. We fix this by creating a shim + * module that exports everything that is visible to require. I hope the React + * team distributes ES modules soon… + * + * https://github.com/facebook/react/issues/11503 + */ +function isBadCommonJs(specifier: string): boolean { + const {name} = parseNpmSpecifier(specifier); + return name === "react" || name === "react-dom" || name === "react-is" || name === "scheduler"; +} + +function shimCommonJs(specifier: string, require: NodeRequire): string { + return `export {${Object.keys(require(specifier))}} from ${JSON.stringify(specifier)};\n`; +} + +async function bundle( + path: string, + input: string, + require: NodeRequire, + cacheRoot: string, + packageRoot: string +): Promise { const bundle = await rollup({ - input, + input: isBadCommonJs(input) ? "-" : input, plugins: [ - nodeResolve({browser: true, rootDir: packageRoot}), + ...(isBadCommonJs(input) ? [(virtual as any)({"-": shimCommonJs(input, require)})] : []), importResolve(input, cacheRoot, packageRoot), + nodeResolve({browser: true, rootDir: packageRoot}), + (commonjs as any)({esmExternals: true}), esbuild({ format: "esm", platform: "browser", target: ["es2022", "chrome96", "firefox96", "safari16", "node18"], exclude: [], // don’t exclude node_modules + define: {"process.env.NODE_ENV": JSON.stringify("production")}, minify: true }) ], + external(source) { + return source.startsWith("/_node/"); + }, onwarn(message, warn) { if (message.code === "CIRCULAR_DEPENDENCY") return; warn(message); } }); try { - const output = await bundle.generate({format: "es"}); - const code = output.output.find((o): o is OutputChunk => o.type === "chunk")!.code; // TODO don’t assume one chunk? - return code; + const output = await bundle.generate({format: "es", exports: "named"}); + const code = output.output.find((o): o is OutputChunk => o.type === "chunk")!.code; + return rewriteNpmImports(code, (i) => (i.startsWith("/_node/") ? relativePath(path, i) : i)); } finally { await bundle.close(); } diff --git a/src/npm.ts b/src/npm.ts index ba309411a..c0e2b8a4d 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -75,29 +75,28 @@ const npmRequests = new Map>(); /** Note: path must start with "/_npm/". */ export async function populateNpmCache(root: string, path: string): Promise { if (!path.startsWith("/_npm/")) throw new Error(`invalid npm path: ${path}`); - const filePath = join(root, ".observablehq", "cache", path); - if (existsSync(filePath)) return filePath; - let promise = npmRequests.get(filePath); + const outputPath = join(root, ".observablehq", "cache", path); + if (existsSync(outputPath)) return outputPath; + let promise = npmRequests.get(outputPath); if (promise) return promise; // coalesce concurrent requests - promise = (async function () { + promise = (async () => { const specifier = extractNpmSpecifier(path); const href = `https://cdn.jsdelivr.net/npm/${specifier}`; - process.stdout.write(`npm:${specifier} ${faint("→")} `); + console.log(`npm:${specifier} ${faint("→")} ${outputPath}`); const response = await fetch(href); if (!response.ok) throw new Error(`unable to fetch: ${href}`); - process.stdout.write(`${filePath}\n`); - await mkdir(dirname(filePath), {recursive: true}); + await mkdir(dirname(outputPath), {recursive: true}); if (/^application\/javascript(;|$)/i.test(response.headers.get("content-type")!)) { const source = await response.text(); const resolver = await getDependencyResolver(root, path, source); - await writeFile(filePath, rewriteNpmImports(source, resolver), "utf-8"); + await writeFile(outputPath, rewriteNpmImports(source, resolver), "utf-8"); } else { - await writeFile(filePath, Buffer.from(await response.arrayBuffer())); + await writeFile(outputPath, Buffer.from(await response.arrayBuffer())); } - return filePath; + return outputPath; })(); - promise.catch(() => {}).then(() => npmRequests.delete(filePath)); - npmRequests.set(filePath, promise); + promise.catch(console.error).then(() => npmRequests.delete(outputPath)); + npmRequests.set(outputPath, promise); return promise; } diff --git a/test/files-test.ts b/test/files-test.ts index 02a2dfa0c..b7eadf9e0 100644 --- a/test/files-test.ts +++ b/test/files-test.ts @@ -62,6 +62,9 @@ describe("visitFiles(root)", () => { if (os.platform() === "win32") this.skip(); // symlinks are not the same on Windows assert.deepStrictEqual(collect(visitFiles("test/input/circular-files")), ["a/a.txt", "b/b.txt"]); }); + it("ignores .observablehq at any level", function () { + assert.deepStrictEqual(collect(visitFiles("test/files")), ["visible.txt", "sub/visible.txt"]); + }); }); describe("visitMarkdownFiles(root)", () => { diff --git a/test/files/.observablehq/hidden.txt b/test/files/.observablehq/hidden.txt new file mode 100644 index 000000000..353c37f4a --- /dev/null +++ b/test/files/.observablehq/hidden.txt @@ -0,0 +1 @@ +This file shouldn’t be found by visitFiles. diff --git a/test/files/sub/.observablehq/hidden.txt b/test/files/sub/.observablehq/hidden.txt new file mode 100644 index 000000000..353c37f4a --- /dev/null +++ b/test/files/sub/.observablehq/hidden.txt @@ -0,0 +1 @@ +This file shouldn’t be found by visitFiles. diff --git a/test/files/sub/visible.txt b/test/files/sub/visible.txt new file mode 100644 index 000000000..0b52c4f6a --- /dev/null +++ b/test/files/sub/visible.txt @@ -0,0 +1 @@ +This file should be found by visitFiles. diff --git a/test/files/visible.txt b/test/files/visible.txt new file mode 100644 index 000000000..0b52c4f6a --- /dev/null +++ b/test/files/visible.txt @@ -0,0 +1 @@ +This file should be found by visitFiles. diff --git a/test/node-test.ts b/test/node-test.ts index 5e86eaec4..3f2923207 100644 --- a/test/node-test.ts +++ b/test/node-test.ts @@ -1,6 +1,6 @@ import assert from "node:assert"; -import {existsSync} from "node:fs"; import {rm} from "node:fs/promises"; +import {join} from "node:path/posix"; import {extractNodeSpecifier, isNodeBuiltin, resolveNodeImport, resolveNodeImports} from "../src/node.js"; describe("isNodeBuiltin(specifier)", () => { @@ -29,64 +29,63 @@ describe("isNodeBuiltin(specifier)", () => { }); }); -describe("resolveNodeImport(root, spec)", () => { - const importRoot = "../../input/packages/.observablehq/cache"; - before(async () => { - await rm("docs/.observablehq/cache/_node", {recursive: true, force: true}); - await rm("test/input/packages/.observablehq/cache", {recursive: true, force: true}); - }); +describe("resolveNodeImport(root, spec) with top-level node_modules", () => { + const testRoot = "test/output/node-import"; // unique to this test + before(() => rm(join(testRoot, ".observablehq/cache/_node"), {recursive: true, force: true})); it("resolves the version of a direct dependency", async () => { - assert.deepStrictEqual(await resolveNodeImport("docs", "d3-array"), "/_node/d3-array@3.2.4/index.js"); - assert.deepStrictEqual(await resolveNodeImport("docs", "mime"), "/_node/mime@4.0.1/index.js"); + assert.deepStrictEqual(await resolveNodeImport(testRoot, "d3-array"), "/_node/d3-array@3.2.4/index.js"); + assert.deepStrictEqual(await resolveNodeImport(testRoot, "mime"), "/_node/mime@4.0.1/index.js"); }); it("allows entry points", async () => { - assert.deepStrictEqual(await resolveNodeImport("docs", "mime/lite"), "/_node/mime@4.0.1/lite.js"); + assert.deepStrictEqual(await resolveNodeImport(testRoot, "mime/lite"), "/_node/mime@4.0.1/lite.js"); }); it("allows non-javascript entry points", async () => { - assert.deepStrictEqual(await resolveNodeImport("docs", "glob/package.json"), "/_node/glob@10.3.10/package.json"); + assert.deepStrictEqual(await resolveNodeImport(testRoot, "glob/package.json"), "/_node/glob@10.3.10/package.json"); }); it("does not allow version ranges", async () => { - await assert.rejects(() => resolveNodeImport("docs", "mime@4"), /Cannot find module/); + await assert.rejects(() => resolveNodeImport(testRoot, "mime@4"), /Cannot find module/); }); +}); + +describe("resolveNodeImport(root, spec) with test node_modules", () => { + const testRoot = "test/input/packages"; // unique to this test + const importRoot = "../../input/packages/.observablehq/cache"; + before(() => rm(join(testRoot, ".observablehq/cache/_node"), {recursive: true, force: true})); it("bundles a package with a shorthand export", async () => { - assert.strictEqual(await resolveNodeImport("test/input/packages", "test-shorthand-export"), "/_node/test-shorthand-export@1.0.0/index.js"); // prettier-ignore + assert.strictEqual(await resolveNodeImport(testRoot, "test-shorthand-export"), "/_node/test-shorthand-export@1.0.0/index.js"); // prettier-ignore assert.strictEqual((await import(`${importRoot}/_node/test-shorthand-export@1.0.0/index.js`)).name, "test-shorthand-export"); // prettier-ignore }); it("bundles a package with an import conditional export", async () => { - assert.strictEqual(await resolveNodeImport("test/input/packages", "test-import-condition"), "/_node/test-import-condition@1.0.0/index.js"); // prettier-ignore + assert.strictEqual(await resolveNodeImport(testRoot, "test-import-condition"), "/_node/test-import-condition@1.0.0/index.js"); // prettier-ignore assert.strictEqual((await import(`${importRoot}/_node/test-import-condition@1.0.0/index.js`)).name, "test-import-condition:import"); // prettier-ignore }); it("bundles a package with a browser field", async () => { - assert.strictEqual(await resolveNodeImport("test/input/packages", "test-browser-field"), "/_node/test-browser-field@1.0.0/index.js"); // prettier-ignore + assert.strictEqual(await resolveNodeImport(testRoot, "test-browser-field"), "/_node/test-browser-field@1.0.0/index.js"); // prettier-ignore assert.strictEqual((await import(`${importRoot}/_node/test-browser-field@1.0.0/index.js`)).name, "test-browser-field:browser"); // prettier-ignore }); it("bundles a package with a browser map", async () => { - assert.strictEqual(await resolveNodeImport("test/input/packages", "test-browser-map"), "/_node/test-browser-map@1.0.0/index.js"); // prettier-ignore + assert.strictEqual(await resolveNodeImport(testRoot, "test-browser-map"), "/_node/test-browser-map@1.0.0/index.js"); // prettier-ignore assert.strictEqual((await import(`${importRoot}/_node/test-browser-map@1.0.0/index.js`)).name, "test-browser-map:browser"); // prettier-ignore }); it("bundles a package with a browser conditional export", async () => { - assert.strictEqual(await resolveNodeImport("test/input/packages", "test-browser-condition"), "/_node/test-browser-condition@1.0.0/index.js"); // prettier-ignore + assert.strictEqual(await resolveNodeImport(testRoot, "test-browser-condition"), "/_node/test-browser-condition@1.0.0/index.js"); // prettier-ignore assert.strictEqual((await import(`${importRoot}/_node/test-browser-condition@1.0.0/index.js`)).name, "test-browser-condition:browser"); // prettier-ignore }); }); describe("resolveNodeImports(root, path)", () => { - before(async () => { - if (existsSync("docs/.observablehq/cache/_node")) { - await rm("docs/.observablehq/cache/_node", {recursive: true}); - } - }); + const testRoot = "test/output/node-imports"; // unique to this test + before(() => rm(join(testRoot, ".observablehq/cache/_node"), {recursive: true, force: true})); it("resolves the imports of a dependency", async () => { - assert.deepStrictEqual(await resolveNodeImports("docs", await resolveNodeImport("docs", "d3-array")), [ - { - method: "static", - name: "../internmap@2.0.3/index.js", - type: "local" - } + assert.deepStrictEqual(await resolveNodeImports(testRoot, await resolveNodeImport(testRoot, "d3-array")), [ + {method: "static", name: "../internmap@2.0.3/index.js", type: "local"} ]); }); it("ignores non-JavaScript paths", async () => { - assert.deepStrictEqual(await resolveNodeImports("docs", await resolveNodeImport("docs", "glob/package.json")), []); + assert.deepStrictEqual( + await resolveNodeImports(testRoot, await resolveNodeImport(testRoot, "glob/package.json")), + [] + ); }); }); diff --git a/test/resolvers-test.ts b/test/resolvers-test.ts index b92aa1fca..92aeb4b4b 100644 --- a/test/resolvers-test.ts +++ b/test/resolvers-test.ts @@ -5,82 +5,82 @@ import {parseMarkdown} from "../src/markdown.js"; import {getResolvers} from "../src/resolvers.js"; import {mockJsDelivr} from "./mocks/jsdelivr.js"; -async function getOptions({root, path}: {root: string; path: string}): Promise { - return {...(await normalizeConfig({root})), path}; +function getOptions({root, path}: {root: string; path: string}): Config & {path: string} { + return {...normalizeConfig({root}), path}; } describe("getResolvers(page, {root, path})", () => { mockJsDelivr(); const builtins = ["npm:@observablehq/runtime", "npm:@observablehq/stdlib", "observablehq:client"]; it("resolves directly-attached files", async () => { - const options = await getOptions({root: "test/input", path: "attached.md"}); + const options = getOptions({root: "test/input", path: "attached.md"}); const page = parseMarkdown("${FileAttachment('foo.csv')}", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.files, new Set(["./foo.csv"])); }); it("ignores files that are outside of the source root", async () => { - const options = await getOptions({root: "test/input", path: "attached.md"}); + const options = getOptions({root: "test/input", path: "attached.md"}); const page = parseMarkdown("${FileAttachment('../foo.csv')}", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.files, new Set([])); }); it("detects file methods", async () => { - const options = await getOptions({root: "test/input", path: "attached.md"}); + const options = getOptions({root: "test/input", path: "attached.md"}); const page = parseMarkdown("${FileAttachment('foo.csv').csv}", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(["npm:d3-dsv", ...builtins])); }); it("detects local static imports", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport './bar.js';\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(["./bar.js", ...builtins])); assert.deepStrictEqual(resolvers.localImports, new Set(["./bar.js"])); }); it("detects local transitive static imports", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport './other/foo.js';\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(["./other/foo.js", "./bar.js", ...builtins])); assert.deepStrictEqual(resolvers.localImports, new Set(["./other/foo.js", "./bar.js"])); }); it("detects local transitive static imports (2)", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport './transitive-static-import.js';\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(["./transitive-static-import.js", "./other/foo.js", "./bar.js", ...builtins])); // prettier-ignore assert.deepStrictEqual(resolvers.localImports, new Set(["./transitive-static-import.js", "./other/foo.js", "./bar.js"])); // prettier-ignore }); it("detects local transitive dynamic imports", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport './dynamic-import.js';\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(["./dynamic-import.js", ...builtins])); assert.deepStrictEqual(resolvers.localImports, new Set(["./dynamic-import.js", "./bar.js"])); }); it("detects local transitive dynamic imports (2)", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport('./dynamic-import.js');\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(builtins)); assert.deepStrictEqual(resolvers.localImports, new Set(["./dynamic-import.js", "./bar.js"])); }); it("detects local transitive dynamic imports (3)", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport('./transitive-dynamic-import.js');\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(builtins)); assert.deepStrictEqual(resolvers.localImports, new Set(["./transitive-dynamic-import.js", "./other/foo.js", "./bar.js"])); // prettier-ignore }); it("detects local transitive dynamic imports (4)", async () => { - const options = await getOptions({root: "test/input/imports", path: "attached.md"}); + const options = getOptions({root: "test/input/imports", path: "attached.md"}); const page = parseMarkdown("```js\nimport('./transitive-static-import.js');\n```", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(builtins)); assert.deepStrictEqual(resolvers.localImports, new Set(["./transitive-static-import.js", "./other/foo.js", "./bar.js"])); // prettier-ignore }); it("detects local dynamic imports", async () => { - const options = await getOptions({root: "test/input", path: "attached.md"}); + const options = getOptions({root: "test/input", path: "attached.md"}); const page = parseMarkdown("${import('./foo.js')}", options); const resolvers = await getResolvers(page, options); assert.deepStrictEqual(resolvers.staticImports, new Set(builtins)); diff --git a/yarn.lock b/yarn.lock index f8da13c48..9c88d2930 100644 --- a/yarn.lock +++ b/yarn.lock @@ -339,7 +339,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -410,6 +410,18 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@rollup/plugin-commonjs@^25.0.7": + version "25.0.7" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz#145cec7589ad952171aeb6a585bbeabd0fd3b4cf" + integrity sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.30.3" + "@rollup/plugin-node-resolve@^15.2.3": version "15.2.3" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9" @@ -422,6 +434,11 @@ is-module "^1.0.0" resolve "^1.22.1" +"@rollup/plugin-virtual@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz#17e17eeecb4c9fa1c0a6e72c9e5f66382fddbb82" + integrity sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A== + "@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.5": version "5.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" @@ -523,7 +540,7 @@ resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== -"@types/estree@1.0.5", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -590,13 +607,20 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b" integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg== -"@types/node@*", "@types/node@^20.7.1": +"@types/node@*": version "20.11.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.26.tgz#3fbda536e51d5c79281e1d9657dcb0131baabd2d" integrity sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ== dependencies: undici-types "~5.26.4" +"@types/node@^20.7.1": + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + dependencies: + undici-types "~5.26.4" + "@types/prompts@^2.4.9": version "2.4.9" resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.4.9.tgz#8775a31e40ad227af511aa0d7f19a044ccbd371e" @@ -1128,6 +1152,11 @@ commander@7: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + component-emitter@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" @@ -1970,7 +1999,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@8.1.0: +glob@8.1.0, glob@^8.0.3: version "8.1.0" resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -2353,6 +2382,13 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-reference@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -2464,6 +2500,11 @@ jackspeak@^2.3.5: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -2602,6 +2643,13 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + loupe@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" @@ -2621,6 +2669,13 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== +magic-string@^0.30.3: + version "0.30.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.9.tgz#8927ae21bfdd856310e07a1bc8dd5e73cb6c251d" + integrity sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -3036,6 +3091,21 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -3221,6 +3291,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"