Repository: pyscript/pyscript
File: pyscriptjs/src/components/pyrepl.ts (TypeScript)

Code before:
            ensureUniqueId(this);
            if (!this.hasAttribute('exec-id')) {
                this.setAttribute('exec-id', '0');
            }
            if (!this.hasAttribute('root')) {
                this.setAttribute('root', this.id);
            }

            const pySrc = htmlDecode(this.innerHTML).trim();
            this.innerHTML = '';
            this.editor = this.makeEditor(pySrc);
            const boxDiv = this.makeBoxDiv();
            this.appendChild(boxDiv);
            this.editor.focus();
            logger.debug(`element ${this.id} successfully connected`);
        }

        get src() {
            return this.getAttribute('src');
        }
        set src(value) {
            this.setAttribute('src', value);
        }
        attributeChangedCallback(name: string, oldVal: string, newVal: string) {
            if (name === 'src' && newVal !== oldVal) {
                this.src = newVal;
                void this.loadReplSrc();
            }
        }
        /**
         * Fetch url from src attribute of py-repl tags and
         * preload the code from fetch response into the Corresponding py-repl tag,
         * but please note that they will not be pre-run unless you click the runbotton.
         */
        async loadReplSrc() {
            logger.info('loading code from', this.src, 'to repl');
            await fetch(this.src)
                .then(response => (response.ok ? response.text() : undefined))
                .then(content => {
                    //The innerHTML function converts the HTML entity or tag in the content string into HTML.
                    //This code will escape "<", "&" to make the content display normally
                    const code = (content + '').replace(/[<&]/g, match => (match === '<' ? '&lt;' : '&amp;'));
                    const parent_Element = this.querySelector('div>div>div>div>div.cm-content'); //NOTE: If the structure of the py-repl changes, it should also be changed here
                    if (content) {
                        //Clear all content(old code) from the py-repl tag
                        let childCounts = parent_Element.children.length;
                        while (childCounts > 1) {
                            parent_Element.removeChild(parent_Element.firstChild);
                            childCounts--;
                        }
                        parent_Element.firstElementChild.innerHTML = code;

Review comment (suggestion):
you are triggering the setter here which in turns triggers the `attributeChangedCallback` ... you don't need to do so because when this callback happens the `src` is already `newVal` ... moreover, if you don't add the static `observedAttributes` in the class this won't work if something else changes the attribute in the wild.

Code after:
        connectedCallback() {
            ensureUniqueId(this);

            if (!this.hasAttribute('exec-id')) {
                this.setAttribute('exec-id', '0');
            }
            if (!this.hasAttribute('root')) {
                this.setAttribute('root', this.id);
            }

            const pySrc = htmlDecode(this.innerHTML).trim();
            this.innerHTML = '';
            this.editor = this.makeEditor(pySrc);
            const boxDiv = this.makeBoxDiv();
            this.appendChild(boxDiv);
            this.editor.focus();
            logger.debug(`element ${this.id} successfully connected`);
        }

        get src() {
            return this.getAttribute('src');
        }

        set src(value) {
            this.setAttribute('src', value);
        }

        attributeChangedCallback(name: string, oldVal: string, newVal: string) {
            if (name === 'src' && newVal !== oldVal) {
                void this.loadReplSrc();
            }
        }

        /**
         * Fetch url from src attribute of py-repl tags and
         * preload the code from fetch response into the Corresponding py-repl tag,
         * but please note that they will not be pre-run unless you click the runbotton.
         */
        async loadReplSrc() {
            try {
                const response = await robustFetch(this.src);
                if (!response.ok) {
                    return;
                }
                const cmcontentElement = this.querySelector("div[class='cm-content']");
                const { lastElementChild } = cmcontentElement;
                cmcontentElement.replaceChildren(lastElementChild);
                lastElementChild.textContent = await response.text();
                logger.info(`loading code from ${this.src} to repl...success`);
            } catch (err) {
                const e = err as Error;