Skip to content

Commit

Permalink
Register newly inserted import statement as a declaration in Babel sc…
Browse files Browse the repository at this point in the history
…ope (#406)

* Remove support for non-ESM files

* Register newly inserted import statement as a declaration in Babel scope

* Add changesets
  • Loading branch information
andrewiggins authored Sep 5, 2023
1 parent 216a592 commit 71caaad
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 22 deletions.
7 changes: 7 additions & 0 deletions .changeset/cyan-swans-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@preact/signals-react-transform": minor
---

Remove support for transforming CJS files

Removing support for transforming CommonJS files since we have no tests for it currently
5 changes: 5 additions & 0 deletions .changeset/lucky-radios-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@preact/signals-react-transform": patch
---

Register newly inserted import statement as a scope declaration in Babel's scope tracking
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@babel/standalone": "^7.22.6",
"@changesets/changelog-github": "^0.4.6",
"@changesets/cli": "^2.24.2",
"@types/babel__traverse": "^7.18.5",
"@types/chai": "^4.3.3",
"@types/mocha": "^9.1.1",
"@types/node": "^18.6.5",
Expand Down
59 changes: 38 additions & 21 deletions packages/react-transform/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
NodePath,
template,
} from "@babel/core";
import { isModule, addNamed, addNamespace } from "@babel/helper-module-imports";
import { isModule, addNamed } from "@babel/helper-module-imports";

// TODO:
// - how to trigger rerenders on attributes change if transform never sees
Expand Down Expand Up @@ -263,29 +263,46 @@ function createImportLazily(
path: NodePath<BabelTypes.Program>,
importName: string,
source: string
) {
): () => BabelTypes.Identifier {
return () => {
if (isModule(path)) {
let reference = get(pass, `imports/${importName}`);
if (reference) return types.cloneNode(reference);
reference = addNamed(path, importName, source, {
importedInterop: "uncompiled",
importPosition: "after",
});
set(pass, `imports/${importName}`, reference);
return reference;
} else {
let reference = get(pass, `requires/${source}`);
if (reference) {
reference = types.cloneNode(reference);
} else {
reference = addNamespace(path, source, {
importedInterop: "uncompiled",
});
set(pass, `requires/${source}`, reference);
if (!isModule(path)) {
throw new Error(
`Cannot import ${importName} outside of an ESM module file`
);
}

let reference: BabelTypes.Identifier = get(pass, `imports/${importName}`);
if (reference) return types.cloneNode(reference);
reference = addNamed(path, importName, source, {
importedInterop: "uncompiled",
importPosition: "after",
});
set(pass, `imports/${importName}`, reference);

/** Helper function to determine if an import declaration's specifier matches the given importName */
const matchesImportName = (
s: BabelTypes.ImportDeclaration["specifiers"][0]
) => {
if (s.type !== "ImportSpecifier") return false;
return (
(s.imported.type === "Identifier" && s.imported.name === importName) ||
(s.imported.type === "StringLiteral" &&
s.imported.value === importName)
);
};

for (let statement of path.get("body")) {
if (
statement.isImportDeclaration() &&
statement.node.source.value === source &&
statement.node.specifiers.some(matchesImportName)
) {
path.scope.registerDeclaration(statement);
break;
}
return types.memberExpression(reference, types.identifier(importName));
}

return reference;
};
}

Expand Down
54 changes: 53 additions & 1 deletion packages/react-transform/test/node/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { transform } from "@babel/core";
import { transform, traverse } from "@babel/core";
import type { Visitor } from "@babel/core";
import type { Scope } from "@babel/traverse";
import signalsTransform, { PluginOptions } from "../../src/index";

function dedent(str: string) {
Expand Down Expand Up @@ -965,4 +967,54 @@ describe("React Signals Babel Transform", () => {
runTest(inputCode, expectedOutput, { importSource: "custom-source" });
});
});

describe("scope tracking", () => {
interface VisitorState {
programScope?: Scope;
}

const programScopeVisitor: Visitor<VisitorState> = {
Program: {
exit(path, state) {
state.programScope = path.scope;
},
},
};

function getRootScope(code: string) {
const signalsPluginConfig: any[] = [signalsTransform];
const result = transform(code, {
ast: true,
plugins: [signalsPluginConfig, "@babel/plugin-syntax-jsx"],
});
if (!result) {
throw new Error("Could not transform code");
}

const state: VisitorState = {};
traverse(result.ast, programScopeVisitor, undefined, state);

const scope = state.programScope;
if (!scope) {
throw new Error("Could not find program scope");
}

return scope;
}

it("adds newly inserted import declarations and usages to program scope", () => {
const scope = getRootScope(`
const MyComponent = () => {
signal.value;
return <div>Hello World</div>;
};
`);

const signalsBinding = scope.bindings["_useSignals"];
console.log(signalsBinding);

Check warning on line 1014 in packages/react-transform/test/node/index.test.tsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement
expect(signalsBinding).to.exist;
expect(signalsBinding.kind).to.equal("module");
expect(signalsBinding.referenced).to.be.true;
});
});
});
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 71caaad

Please sign in to comment.