@firsthandjs/compiler 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -128,7 +128,7 @@ function firsthandPlugin(_api, options = {}) {
|
|
|
128
128
|
path.replaceWith(compileNode(path, state));
|
|
129
129
|
},
|
|
130
130
|
CallExpression(path, state) {
|
|
131
|
-
annotateComponent(path, state);
|
|
131
|
+
annotateComponent(path, state, options);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
};
|
|
@@ -157,7 +157,7 @@ function runtime(state, name, source = RUNTIME) {
|
|
|
157
157
|
}
|
|
158
158
|
return t.cloneNode(local);
|
|
159
159
|
}
|
|
160
|
-
function annotateComponent(path, state) {
|
|
160
|
+
function annotateComponent(path, state, options) {
|
|
161
161
|
const callee = path.node.callee;
|
|
162
162
|
if (!t.isIdentifier(callee, { name: "component" }) || path.node.arguments.length > 2) {
|
|
163
163
|
return;
|
|
@@ -172,6 +172,9 @@ function annotateComponent(path, state) {
|
|
|
172
172
|
}
|
|
173
173
|
const name = declaredName(path);
|
|
174
174
|
rewritePropsDestructuring(path, name, state);
|
|
175
|
+
if (options.strictReactivity === true) {
|
|
176
|
+
checkKeptReads(path.get("arguments.0"), name);
|
|
177
|
+
}
|
|
175
178
|
path.node.arguments = [
|
|
176
179
|
setup,
|
|
177
180
|
path.node.arguments[1] ?? t.identifier("undefined"),
|
|
@@ -179,6 +182,64 @@ function annotateComponent(path, state) {
|
|
|
179
182
|
t.stringLiteral(name)
|
|
180
183
|
];
|
|
181
184
|
}
|
|
185
|
+
function checkKeptReads(setup, name) {
|
|
186
|
+
const parameter = setup.node.params[0];
|
|
187
|
+
const propsName = t.isIdentifier(parameter) ? parameter.name : null;
|
|
188
|
+
const body = setup.get("body");
|
|
189
|
+
if (!body.isBlockStatement()) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
body.traverse({
|
|
193
|
+
Function(nested) {
|
|
194
|
+
nested.skip();
|
|
195
|
+
},
|
|
196
|
+
VariableDeclarator(declarator) {
|
|
197
|
+
const init = declarator.node.init;
|
|
198
|
+
if (init === null || init === void 0 || !readsOnly(init, propsName)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const target = declarator.node.id;
|
|
202
|
+
const label = t.isIdentifier(target) ? `\`${target.name}\`` : "This value";
|
|
203
|
+
throw declarator.buildCodeFrameError(
|
|
204
|
+
`${name}: ${label} is read once, here, and then kept. A setup runs one time per instance, so this value will not change again.
|
|
205
|
+
|
|
206
|
+
Move the read into the part, handler, effect or computed that should re-read it \u2014 or wrap it in snapshot(() => \u2026) if reading once is what you meant.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function readsOnly(node, propsName) {
|
|
212
|
+
let read = false;
|
|
213
|
+
const walk = (current) => {
|
|
214
|
+
if (t.isIdentifier(current) || t.isLiteral(current)) {
|
|
215
|
+
if (t.isTemplateLiteral(current)) {
|
|
216
|
+
return current.expressions.every((part) => walk(part));
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (t.isMemberExpression(current)) {
|
|
221
|
+
const property = current.property;
|
|
222
|
+
if (!current.computed && t.isIdentifier(property, { name: "value" })) {
|
|
223
|
+
read = true;
|
|
224
|
+
}
|
|
225
|
+
if (propsName !== null && t.isIdentifier(current.object, { name: propsName })) {
|
|
226
|
+
read = true;
|
|
227
|
+
}
|
|
228
|
+
return walk(current.object) && (!current.computed || walk(property));
|
|
229
|
+
}
|
|
230
|
+
if (t.isUnaryExpression(current)) {
|
|
231
|
+
return walk(current.argument);
|
|
232
|
+
}
|
|
233
|
+
if (t.isBinaryExpression(current) || t.isLogicalExpression(current)) {
|
|
234
|
+
return walk(current.left) && walk(current.right);
|
|
235
|
+
}
|
|
236
|
+
if (t.isConditionalExpression(current)) {
|
|
237
|
+
return walk(current.test) && walk(current.consequent) && walk(current.alternate);
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
};
|
|
241
|
+
return walk(node) && read;
|
|
242
|
+
}
|
|
182
243
|
function isFirsthandImport(binding) {
|
|
183
244
|
const parent = binding.path.parentPath;
|
|
184
245
|
if (!parent.isImportDeclaration()) {
|
|
@@ -830,7 +891,10 @@ function buildPlugins(options) {
|
|
|
830
891
|
plugins.push([typescriptSyntax, { isTSX: true }]);
|
|
831
892
|
}
|
|
832
893
|
plugins.push(jsxSyntax);
|
|
833
|
-
plugins.push([
|
|
894
|
+
plugins.push([
|
|
895
|
+
firsthandPlugin,
|
|
896
|
+
{ packageName: options.packageName, strictReactivity: options.strictReactivity }
|
|
897
|
+
]);
|
|
834
898
|
return plugins;
|
|
835
899
|
}
|
|
836
900
|
|
package/dist/index.js
CHANGED
package/dist/transform.d.ts
CHANGED
|
@@ -27,6 +27,14 @@ declare module '@babel/core' {
|
|
|
27
27
|
export interface FirsthandPluginOptions {
|
|
28
28
|
/** Package name used when hashing stable component ids (ADR-0004). */
|
|
29
29
|
packageName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Refuse to compile a value that is read once in a setup and then kept.
|
|
32
|
+
*
|
|
33
|
+
* Off by default: reading once is legal and often deliberate. On, it is a
|
|
34
|
+
* build error rather than a warning, because the cases it can see are the
|
|
35
|
+
* unambiguous ones — see `checkKeptReads` (ADR-0019).
|
|
36
|
+
*/
|
|
37
|
+
strictReactivity?: boolean;
|
|
30
38
|
}
|
|
31
39
|
export default function firsthandPlugin(_api: unknown, options?: FirsthandPluginOptions): PluginObject;
|
|
32
40
|
export {};
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAelC,UAAU,cAAc;IACtB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,QAAQ,aAAa,CAAC;IAC3B,UAAU,UAAU;QAClB,SAAS,EAAE,cAAc,CAAC;KAC3B;CACF;AAUD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAelC,UAAU,cAAc;IACtB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,QAAQ,aAAa,CAAC;IAC3B,UAAU,UAAU;QAClB,SAAS,EAAE,cAAc,CAAC;KAC3B;CACF;AAUD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,sBAA2B,GACnC,YAAY,CAgDd"}
|
package/dist/vite.js
CHANGED
package/package.json
CHANGED