diff --git a/.changeset/wet-lizards-repair.md b/.changeset/wet-lizards-repair.md
new file mode 100644
index 000000000..a7d3a5f1d
--- /dev/null
+++ b/.changeset/wet-lizards-repair.md
@@ -0,0 +1,5 @@
+---
+"marko": patch
+---
+
+Fix issue where scoped directive was being applied twice when tag had no-update directive.
diff --git a/packages/runtime-class/src/translator/tag/attribute/modifiers/scoped.js b/packages/runtime-class/src/translator/tag/attribute/modifiers/scoped.js
index 09b08354d..d55c725e5 100644
--- a/packages/runtime-class/src/translator/tag/attribute/modifiers/scoped.js
+++ b/packages/runtime-class/src/translator/tag/attribute/modifiers/scoped.js
@@ -2,22 +2,27 @@ import { types as t } from "@marko/compiler";
import withPreviousLocation from "../../../util/with-previous-location";
+const seen = new WeakSet();
+
export default {
exit(tag, _, value) {
const {
hub: { file },
} = tag;
- value.replaceWith(
- withPreviousLocation(
- t.callExpression(
- t.memberExpression(
- file._componentDefIdentifier,
- t.identifier("elId"),
- ),
- [value.node],
- ),
- value.node,
+
+ if (seen.has(value.node)) {
+ return;
+ }
+
+ const replacement = withPreviousLocation(
+ t.callExpression(
+ t.memberExpression(file._componentDefIdentifier, t.identifier("elId")),
+ [value.node],
),
+ value.node,
);
+
+ seen.add(replacement);
+ value.replaceWith(replacement);
},
};
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/cjs-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/cjs-expected.js
new file mode 100644
index 000000000..6bf7fa5f1
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/cjs-expected.js
@@ -0,0 +1,27 @@
+"use strict";
+
+exports.__esModule = true;
+exports.default = void 0;
+var _index = require("marko/src/runtime/html/index.js");
+var _dataMarko = _interopRequireDefault(require("marko/src/runtime/html/helpers/data-marko.js"));
+var _attr = _interopRequireDefault(require("marko/src/runtime/html/helpers/attr.js"));
+var _preserveTag = _interopRequireDefault(require("marko/src/core-tags/components/preserve-tag.js"));
+var _renderTag = _interopRequireDefault(require("marko/src/runtime/helpers/render-tag.js"));
+var _renderer = _interopRequireDefault(require("marko/src/runtime/components/renderer.js"));
+function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
+const _marko_componentType = "__tests__/template.marko",
+ _marko_template = (0, _index.t)(_marko_componentType);
+var _default = exports.default = _marko_template;
+const _marko_component = {};
+_marko_template._ = (0, _renderer.default)(function (input, out, _componentDef, _component, state, $global) {
+ (0, _renderTag.default)(_preserveTag.default, {
+ "n": true,
+ "renderBody": out => {
+ out.w(``);
+ }
+ }, out, _componentDef, "0");
+}, {
+ t: _marko_componentType,
+ i: true,
+ d: true
+}, _marko_component);
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/generated-expected.marko b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/generated-expected.marko
new file mode 100644
index 000000000..b6e405569
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/generated-expected.marko
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/html-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/html-expected.js
new file mode 100644
index 000000000..4b2ff77c1
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/html-expected.js
@@ -0,0 +1,22 @@
+import { t as _t } from "marko/src/runtime/html/index.js";
+const _marko_componentType = "__tests__/template.marko",
+ _marko_template = _t(_marko_componentType);
+export default _marko_template;
+import _marko_props from "marko/src/runtime/html/helpers/data-marko.js";
+import _marko_attr from "marko/src/runtime/html/helpers/attr.js";
+import _preserve from "marko/src/core-tags/components/preserve-tag.js";
+import _marko_tag from "marko/src/runtime/helpers/render-tag.js";
+import _marko_renderer from "marko/src/runtime/components/renderer.js";
+const _marko_component = {};
+_marko_template._ = _marko_renderer(function (input, out, _componentDef, _component, state, $global) {
+ _marko_tag(_preserve, {
+ "n": true,
+ "renderBody": out => {
+ out.w(``);
+ }
+ }, out, _componentDef, "0");
+}, {
+ t: _marko_componentType,
+ i: true,
+ d: true
+}, _marko_component);
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/htmlProduction-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/htmlProduction-expected.js
new file mode 100644
index 000000000..8959b0a0c
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/htmlProduction-expected.js
@@ -0,0 +1,21 @@
+import { t as _t } from "marko/dist/runtime/html/index.js";
+const _marko_componentType = "FiW8Sbt",
+ _marko_template = _t(_marko_componentType);
+export default _marko_template;
+import _marko_props from "marko/dist/runtime/html/helpers/data-marko.js";
+import _marko_attr from "marko/dist/runtime/html/helpers/attr.js";
+import _preserve from "marko/dist/core-tags/components/preserve-tag.js";
+import _marko_tag from "marko/dist/runtime/helpers/render-tag.js";
+import _marko_renderer from "marko/dist/runtime/components/renderer.js";
+const _marko_component = {};
+_marko_template._ = _marko_renderer(function (input, out, _componentDef, _component, state, $global) {
+ _marko_tag(_preserve, {
+ "n": true,
+ "renderBody": out => {
+ out.w(``);
+ }
+ }, out, _componentDef, "0");
+}, {
+ t: _marko_componentType,
+ i: true
+}, _marko_component);
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/hydrate-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/hydrate-expected.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdom-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdom-expected.js
new file mode 100644
index 000000000..d1d8232e3
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdom-expected.js
@@ -0,0 +1,26 @@
+import { t as _t } from "marko/src/runtime/vdom/index.js";
+const _marko_componentType = "__tests__/template.marko",
+ _marko_template = _t(_marko_componentType);
+export default _marko_template;
+import _preserve from "marko/src/core-tags/components/preserve-tag.js";
+import _marko_tag from "marko/src/runtime/helpers/render-tag.js";
+import _marko_renderer from "marko/src/runtime/components/renderer.js";
+import { r as _marko_registerComponent } from "marko/src/runtime/components/registry.js";
+_marko_registerComponent(_marko_componentType, () => _marko_template);
+const _marko_component = {};
+_marko_template._ = _marko_renderer(function (input, out, _componentDef, _component, state, $global) {
+ _marko_tag(_preserve, {
+ "n": true,
+ "renderBody": out => {
+ out.e("input", {
+ "id": _componentDef.elId("input1")
+ }, "0", _component, 0, 1);
+ }
+ }, out, _componentDef, "0");
+}, {
+ t: _marko_componentType,
+ i: true,
+ d: true
+}, _marko_component);
+import _marko_defineComponent from "marko/src/runtime/components/defineComponent.js";
+_marko_template.Component = _marko_defineComponent(_marko_component, _marko_template._);
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdomProduction-expected.js b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdomProduction-expected.js
new file mode 100644
index 000000000..bd5e9b516
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/snapshots/vdomProduction-expected.js
@@ -0,0 +1,25 @@
+import { t as _t } from "marko/dist/runtime/vdom/index.js";
+const _marko_componentType = "FiW8Sbt",
+ _marko_template = _t(_marko_componentType);
+export default _marko_template;
+import _preserve from "marko/dist/core-tags/components/preserve-tag.js";
+import _marko_tag from "marko/dist/runtime/helpers/render-tag.js";
+import _marko_renderer from "marko/dist/runtime/components/renderer.js";
+import { r as _marko_registerComponent } from "marko/dist/runtime/components/registry.js";
+_marko_registerComponent(_marko_componentType, () => _marko_template);
+const _marko_component = {};
+_marko_template._ = _marko_renderer(function (input, out, _componentDef, _component, state, $global) {
+ _marko_tag(_preserve, {
+ "n": true,
+ "renderBody": out => {
+ out.e("input", {
+ "id": _componentDef.elId("input1")
+ }, "0", _component, 0, 1);
+ }
+ }, out, _componentDef, "0");
+}, {
+ t: _marko_componentType,
+ i: true
+}, _marko_component);
+import _marko_defineComponent from "marko/dist/runtime/components/defineComponent.js";
+_marko_template.Component = _marko_defineComponent(_marko_component, _marko_template._);
\ No newline at end of file
diff --git a/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/template.marko b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/template.marko
new file mode 100644
index 000000000..415bba2bc
--- /dev/null
+++ b/packages/runtime-class/test/translator/fixtures/attr-no-update-scoped/template.marko
@@ -0,0 +1 @@
+