feat: more efficient code generation when referencing globals (#12712)

* feat: more efficient code generation when referencing globals

* update test
pull/12735/head
Rich Harris 3 months ago committed by GitHub
parent 93cfa6cd69
commit e66416bec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: more efficient code generation when referencing globals

@ -4,7 +4,7 @@
import { get_rune } from '../../scope.js'; import { get_rune } from '../../scope.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { get_parent, unwrap_optional } from '../../../utils/ast.js'; import { get_parent, unwrap_optional } from '../../../utils/ast.js';
import { is_known_safe_call, is_safe_identifier } from './shared/utils.js'; import { is_pure, is_safe_identifier } from './shared/utils.js';
/** /**
* @param {CallExpression} node * @param {CallExpression} node
@ -150,7 +150,7 @@ export function CallExpression(node, context) {
break; break;
} }
if (context.state.expression && !is_known_safe_call(node.callee, context)) { if (context.state.expression && !is_pure(node.callee, context)) {
context.state.expression.has_call = true; context.state.expression.has_call = true;
context.state.expression.has_state = true; context.state.expression.has_state = true;
} }

@ -1,7 +1,7 @@
/** @import { MemberExpression } from 'estree' */ /** @import { MemberExpression } from 'estree' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { is_safe_identifier } from './shared/utils.js'; import { is_pure, is_safe_identifier } from './shared/utils.js';
/** /**
* @param {MemberExpression} node * @param {MemberExpression} node
@ -15,7 +15,7 @@ export function MemberExpression(node, context) {
} }
} }
if (context.state.expression) { if (context.state.expression && !is_pure(node, context)) {
context.state.expression.has_state = true; context.state.expression.has_state = true;
} }

@ -1,13 +1,13 @@
/** @import { TaggedTemplateExpression, VariableDeclarator } from 'estree' */ /** @import { TaggedTemplateExpression, VariableDeclarator } from 'estree' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import { is_known_safe_call } from './shared/utils.js'; import { is_pure } from './shared/utils.js';
/** /**
* @param {TaggedTemplateExpression} node * @param {TaggedTemplateExpression} node
* @param {Context} context * @param {Context} context
*/ */
export function TaggedTemplateExpression(node, context) { export function TaggedTemplateExpression(node, context) {
if (context.state.expression && !is_known_safe_call(node.tag, context)) { if (context.state.expression && !is_pure(node.tag, context)) {
context.state.expression.has_call = true; context.state.expression.has_call = true;
context.state.expression.has_state = true; context.state.expression.has_state = true;
} }

@ -4,7 +4,7 @@
/** @import { Scope } from '../../../scope' */ /** @import { Scope } from '../../../scope' */
/** @import { NodeLike } from '../../../../errors.js' */ /** @import { NodeLike } from '../../../../errors.js' */
import * as e from '../../../../errors.js'; import * as e from '../../../../errors.js';
import { extract_identifiers } from '../../../../utils/ast.js'; import { extract_identifiers, object } from '../../../../utils/ast.js';
import * as w from '../../../../warnings.js'; import * as w from '../../../../warnings.js';
/** /**
@ -167,24 +167,23 @@ export function is_safe_identifier(expression, scope) {
} }
/** /**
* @param {Expression | Super} callee * @param {Expression | Super} node
* @param {Context} context * @param {Context} context
* @returns {boolean} * @returns {boolean}
*/ */
export function is_known_safe_call(callee, context) { export function is_pure(node, context) {
// String / Number / BigInt / Boolean casting calls if (node.type !== 'Identifier' && node.type !== 'MemberExpression') {
if (callee.type === 'Identifier') { return false;
const name = callee.name;
const binding = context.state.scope.get(name);
if (
binding === null &&
(name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean')
) {
return true;
}
} }
// TODO add more cases const left = object(node);
if (!left) return false;
if (left.type === 'Identifier') {
const binding = context.state.scope.get(left.name);
if (binding === null) return true; // globals are assumed to be safe
}
// TODO add more cases (safe Svelte imports, etc)
return false; return false;
} }

@ -1,5 +1,5 @@
<script> <script>
import {untrack} from 'svelte'; import { untrack } from 'svelte';
const foo = $effect.tracking(); const foo = $effect.tracking();
let bar = $state(false); let bar = $state(false);
@ -10,5 +10,5 @@
<p>{foo}</p> <p>{foo}</p>
<p>{bar}</p> <p>{bar}</p>
<p>{$effect.tracking()}</p> <p>{(bar, $effect.tracking())}</p>
<p>{untrack(() => $effect.tracking())}</p> <p>{untrack(() => (bar, $effect.tracking()))}</p>

@ -0,0 +1,32 @@
import "svelte/internal/disclose-version";
import * as $ from "svelte/internal/client";
var root = $.template(`<p> </p> <p> </p> <!>`, 1);
export default function Purity($$anchor) {
let min = 0;
let max = 100;
let number = 50;
let value = 'hello';
var fragment = root();
var p = $.first_child(fragment);
var text = $.child(p);
text.nodeValue = Math.max(min, Math.min(max, number));
$.reset(p);
var p_1 = $.sibling($.sibling(p, true));
var text_1 = $.child(p_1);
text_1.nodeValue = location.href;
$.reset(p_1);
var node = $.sibling($.sibling(p_1, true));
Child(node, {
prop: encodeURIComponent(value),
$$legacy: true
});
$.append($$anchor, fragment);
}

@ -0,0 +1,12 @@
import * as $ from "svelte/internal/server";
export default function Purity($$payload) {
let min = 0;
let max = 100;
let number = 50;
let value = 'hello';
$$payload.out += `<p>${$.escape(Math.max(min, Math.min(max, number)))}</p> <p>${$.escape(location.href)}</p> `;
Child($$payload, { prop: encodeURIComponent(value) });
$$payload.out += `<!---->`;
}

@ -0,0 +1,12 @@
<script>
let min = 0;
let max = 100;
let number = 50;
let value = 'hello';
</script>
<p>{Math.max(min, Math.min(max, number))}</p>
<p>{location.href}</p>
<Child prop={encodeURIComponent(value)} />
Loading…
Cancel
Save