question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

"preact build" try to load an (3rd lib) un-exist ts file

See original GitHub issue

Do you want to request a feature or report a bug? bug (maybe)

What is the current behaviour? I using a 3rd parity library, and preact build fail due to trying to load an un-exist ts file

  • @justinribeiro/lite-youtube
  • @justinribeiro/lite-youtube’s file structure, package.json and lite-youtube.js are listed in below
    • package.json’s main and module are indicate to lite-youtube.js
    • but preact-cli try to load ts file (lite-youtube.ts)

problem can be solved by --no-prerender flag, but is it normal ?

If the current behaviour is a bug, please provide the steps to reproduce.

steps to reproduce

  1. preact create Default preact-cli-build-issue
  2. cd preact-cli-build-issue
  3. npm i @justinribeiro/lite-youtube
  4. import @justinribeiro/lite-youtube and write some code (https://github.com/flameddd/preact-cli-build-issue/commit/a1bb98f837dd91ef54ce440a25856b408ce289b4)
  5. npm run build

I had create repo to reproduce this

What is the expected behaviour? npm run build success
(according @rschristian explanation) correct error message

Please mention other relevant information.

@justinribeiro/lite-youtube file structure

.
./lite-youtube.d.ts
./LICENSE
./lite-youtube.js.map
./README.md
./package.json
./lite-youtube.js

@justinribeiro/lite-youtube package.json

Click to expand package.json
{
  "_from": "@justinribeiro/lite-youtube",
  "_id": "@justinribeiro/lite-youtube@0.9.1",
  "_inBundle": false,
  "_integrity": "sha512-IgcpHnovzZGxU4Ec+0c7sSLhrJWflvYliQUmdcwBgyVkGw0ZL9Y8IU/m09NPk9EzIk2HAOWUGLywTVpB785egA==",
  "_location": "/@justinribeiro/lite-youtube",
  "_phantomChildren": {},
  "_requested": {
    "type": "tag",
    "registry": true,
    "raw": "@justinribeiro/lite-youtube",
    "name": "@justinribeiro/lite-youtube",
    "escapedName": "@justinribeiro%2flite-youtube",
    "scope": "@justinribeiro",
    "rawSpec": "",
    "saveSpec": null,
    "fetchSpec": "latest"
  },
  "_requiredBy": [
    "#USER",
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/@justinribeiro/lite-youtube/-/lite-youtube-0.9.1.tgz",
  "_shasum": "c9f83861daad361d58de76b2a5e078de6fe6b751",
  "_spec": "@justinribeiro/lite-youtube",
  "_where": "/Users/flameddd/program/preact-cli-build-issue",
  "author": {
    "name": "Justin Ribeiro",
    "email": "justin@justinribeiro.com"
  },
  "bugs": {
    "url": "https://github.com/justinribeiro/lite-youtube/issues"
  },
  "bundleDependencies": false,
  "deprecated": false,
  "description": "A web component that loads YouTube embed iframes faster. ShadowDom based version of Paul Irish' concept.",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^2.29.0",
    "@typescript-eslint/parser": "^2.29.0",
    "eslint": "^6.8.0",
    "eslint-config-google": "^0.14.0",
    "eslint-config-prettier": "^6.10.0",
    "eslint-plugin-html": "^6.0.0",
    "eslint-plugin-lit": "^1.2.0",
    "prettier": "^2.0.0",
    "typescript": "^3.8.0"
  },
  "files": [
    "lite-youtube.d.ts",
    "lite-youtube.js",
    "lite-youtube.js.map"
  ],
  "homepage": "https://github.com/justinribeiro/lite-youtube#readme",
  "keywords": [
    "web components",
    "youtube"
  ],
  "license": "MIT",
  "main": "lite-youtube.js",
  "module": "lite-youtube.js",
  "name": "@justinribeiro/lite-youtube",
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/justinribeiro/lite-youtube.git"
  },
  "scripts": {
    "build": "tsc --project tsconfig.json",
    "lint": "npm run lint:eslint && npm run lint:prettier",
    "lint:eslint": "eslint *.ts --ignore-path .gitignore",
    "lint:prettier": "prettier --check *.ts --ignore-path .gitignore",
    "prepublishOnly": "npm run build"
  },
  "types": "lite-youtube.d.ts",
  "version": "0.9.1"
}

@justinribeiro/lite-youtube lite-youtube.js

Click to expand lite-youtube.js
/**
 *
 * The shadowDom / Intersection Observer version of Paul's concept:
 * https://github.com/paulirish/lite-youtube-embed
 *
 * A lightweight YouTube embed. Still should feel the same to the user, just
 * MUCH faster to initialize and paint.
 *
 * Thx to these as the inspiration
 *   https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
 *   https://autoplay-youtube-player.glitch.me/
 *
 * Once built it, I also found these (👍👍):
 *   https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube
 *   https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe
 */
export class LiteYTEmbed extends HTMLElement {
    constructor() {
        super();
        this.iframeLoaded = false;
        this.setupDom();
    }
    static get observedAttributes() {
        return ['videoid'];
    }
    connectedCallback() {
        this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {
            once: true,
        });
        this.addEventListener('click', () => this.addIframe());
    }
    get videoId() {
        return encodeURIComponent(this.getAttribute('videoid') || '');
    }
    set videoId(id) {
        this.setAttribute('videoid', id);
    }
    get videoTitle() {
        return this.getAttribute('videotitle') || 'Video';
    }
    set videoTitle(title) {
        this.setAttribute('videotitle', title);
    }
    get videoPlay() {
        return this.getAttribute('videoPlay') || 'Play';
    }
    set videoPlay(name) {
        this.setAttribute('videoPlay', name);
    }
    get videoStartAt() {
        return Number(this.getAttribute('videoStartAt') || '0');
    }
    set videoStartAt(time) {
        this.setAttribute('videoStartAt', String(time));
    }
    get autoLoad() {
        return this.hasAttribute('autoload');
    }
    set autoLoad(value) {
        if (value) {
            this.setAttribute('autoload', '');
        }
        else {
            this.removeAttribute('autoload');
        }
    }
    get params() {
        return `start=${this.videoStartAt}&${this.getAttribute('params')}`;
    }
    /**
     * Define our shadowDOM for the component
     */
    setupDom() {
        const shadowDom = this.attachShadow({ mode: 'open' });
        shadowDom.innerHTML = `
      <style>
        :host {
          contain: content;
          display: block;
          position: relative;
          width: 100%;
          padding-bottom: calc(100% / (16 / 9));
        }

        #frame, #fallbackPlaceholder, iframe {
          position: absolute;
          width: 100%;
          height: 100%;
        }

        #frame {
          cursor: pointer;
        }

        #fallbackPlaceholder {
          object-fit: cover;
        }

        #frame::before {
          content: '';
          display: block;
          position: absolute;
          top: 0;
          background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
          background-position: top;
          background-repeat: repeat-x;
          height: 60px;
          padding-bottom: 50px;
          width: 100%;
          transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
          z-index: 1;
        }
        /* play button */
        .lty-playbtn {
          width: 70px;
          height: 46px;
          background-color: #212121;
          z-index: 1;
          opacity: 0.8;
          border-radius: 14%; /* TODO: Consider replacing this with YT's actual svg. Eh. */
          transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
          border: 0;
        }
        #frame:hover .lty-playbtn {
          background-color: #f00;
          opacity: 1;
        }
        /* play button triangle */
        .lty-playbtn:before {
          content: '';
          border-style: solid;
          border-width: 11px 0 11px 19px;
          border-color: transparent transparent transparent #fff;
        }
        .lty-playbtn,
        .lty-playbtn:before {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate3d(-50%, -50%, 0);
        }

        /* Post-click styles */
        .lyt-activated {
          cursor: unset;
        }

        #frame.lyt-activated::before,
        .lyt-activated .lty-playbtn {
          display: none;
        }
      </style>
      <div id="frame">
        <picture>
          <source id="webpPlaceholder" type="image/webp">
          <source id="jpegPlaceholder" type="image/jpeg">
          <img id="fallbackPlaceholder" referrerpolicy="origin">
        </picture>
        <button class="lty-playbtn"></button>
      </div>
    `;
        this.domRefFrame = this.shadowRoot.querySelector('#frame');
        this.domRefImg = {
            fallback: this.shadowRoot.querySelector('#fallbackPlaceholder'),
            webp: this.shadowRoot.querySelector('#webpPlaceholder'),
            jpeg: this.shadowRoot.querySelector('#jpegPlaceholder'),
        };
        this.domRefPlayButton = this.shadowRoot.querySelector('.lty-playbtn');
    }
    /**
     * Parse our attributes and fire up some placeholders
     */
    setupComponent() {
        this.initImagePlaceholder();
        this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
        this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`);
        if (this.autoLoad) {
            this.initIntersectionObserver();
        }
    }
    /**
     * Lifecycle method that we use to listen for attribute changes to period
     * @param {*} name
     * @param {*} oldVal
     * @param {*} newVal
     */
    attributeChangedCallback(name, oldVal, newVal) {
        switch (name) {
            case 'videoid': {
                if (oldVal !== newVal) {
                    this.setupComponent();
                    // if we have a previous iframe, remove it and the activated class
                    if (this.domRefFrame.classList.contains('lyt-activated')) {
                        this.domRefFrame.classList.remove('lyt-activated');
                        this.shadowRoot.querySelector('iframe').remove();
                        this.iframeLoaded = false;
                    }
                }
                break;
            }
            default:
                break;
        }
    }
    /**
     * Inject the iframe into the component body
     */
    addIframe() {
        if (!this.iframeLoaded) {
            const iframeHTML = `
<iframe frameborder="0"
  allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen
  src="https://www.youtube.com/embed/${this.videoId}?autoplay=1&${this.params}"
></iframe>`;
            this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML);
            this.domRefFrame.classList.add('lyt-activated');
            this.iframeLoaded = true;
        }
    }
    /**
     * Setup the placeholder image for the component
     */
    initImagePlaceholder() {
        // we don't know which image type to preload, so warm the connection
        LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/');
        const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`;
        const posterUrlJpeg = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`;
        this.domRefImg.webp.srcset = posterUrlWebp;
        this.domRefImg.jpeg.srcset = posterUrlJpeg;
        this.domRefImg.fallback.src = posterUrlJpeg;
        this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
        this.domRefImg.fallback.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`);
    }
    /**
     * Setup the Intersection Observer to load the iframe when scrolled into view
     */
    initIntersectionObserver() {
        if ('IntersectionObserver' in window &&
            'IntersectionObserverEntry' in window) {
            const options = {
                root: null,
                rootMargin: '0px',
                threshold: 0,
            };
            const observer = new IntersectionObserver((entries, observer) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting && !this.iframeLoaded) {
                        LiteYTEmbed.warmConnections();
                        this.addIframe();
                        observer.unobserve(this);
                    }
                });
            }, options);
            observer.observe(this);
        }
    }
    /**
     * Add a <link rel={preload | preconnect} ...> to the head
     * @param {*} kind
     * @param {*} url
     * @param {*} as
     */
    static addPrefetch(kind, url, as) {
        const linkElem = document.createElement('link');
        linkElem.rel = kind;
        linkElem.href = url;
        if (as) {
            linkElem.as = as;
        }
        linkElem.crossOrigin = 'true';
        document.head.append(linkElem);
    }
    /**
     * Begin preconnecting to warm up the iframe load Since the embed's netwok
     * requests load within its iframe, preload/prefetch'ing them outside the
     * iframe will only cause double-downloads. So, the best we can do is warm up
     * a few connections to origins that are in the critical path.
     *
     * Maybe `<link rel=preload as=document>` would work, but it's unsupported:
     * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site
     * Isolation and split caches adding serious complexity.
     */
    static warmConnections() {
        if (LiteYTEmbed.preconnected)
            return;
        // Host that YT uses to serve JS needed by player, per amp-youtube
        LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com');
        // The iframe document and most of its subresources come right off
        // youtube.com
        LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com');
        // The botguard script is fetched off from google.com
        LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');
        // TODO: Not certain if these ad related domains are in the critical path.
        // Could verify with domain-specific throttling.
        LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net');
        LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');
        LiteYTEmbed.preconnected = true;
    }
}
LiteYTEmbed.preconnected = false;
// Register custom element
customElements.define('lite-youtube', LiteYTEmbed);
//# sourceMappingURL=lite-youtube.js.map

Please paste the results of preact info here.

preact info

Environment Info:
  System:
    OS: macOS 10.15.2
    CPU: (4) x64 Intel(R) Core(TM) i5-4260U CPU @ 1.40GHz
  Binaries:
    Node: 10.15.1 - ~/.nvm/versions/node/v10.15.1/bin/node
    npm: 6.14.4 - ~/.nvm/versions/node/v10.15.1/bin/npm
  Browsers:
    Chrome: 86.0.4240.183
    Edge: 81.0.416.58
    Safari: 13.0.4
  npmGlobalPackages:
    preact-cli: 3.0.3

thanks for ALL preact contributors’ hard work

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
rschristiancommented, Nov 8, 2020

Weird error, definitely need to take a look at that. No idea where that’s coming from as the lib does look to be set up right.

However, the fact that something errored out is correct behaviour. The library you’re trying to use has the following line:

export class LiteYTEmbed extends HTMLElement

Preact-CLI prerenders in Node, and HTMLElement is browser-only. This gives you a few options: opt out of prerendering as you have, or wrap the library in a window check (if (window !== undefined) { <use library here> }).

1reaction
developitcommented, Nov 9, 2020

Generally a sourcemap should either include the sources content (the .ts file is inlined into the .map file as a string), or it links to the file (which definitely means that file should be included when publishing).

In general it’s better to inline sources into the sourcemap files, since it’s easier for bundlers to create derivative sourcemaps that way.

Btw - one other option you could consider would be to dynamically import this module. Dynamic imports are not executed during Preact CLI’s prerendering.

Read more comments on GitHub >

github_iconTop Results From Across the Web

TSConfig Reference - Docs on every TSConfig option
Intro to the TSConfig Reference. A TSConfig file in a directory indicates that the directory is the root of a TypeScript or JavaScript...
Read more >
TypeScript | Preact: Fast 3kb React alternative with the same ...
Preact ships TypeScript type definitions, which are used by the library ... Rename your .jsx files to .tsx for TypeScript to correctly parse...
Read more >
preact-cli | Yarn - Package Manager
Fast, reliable, and secure dependency management.
Read more >
Typescript complains Property does not exist on type 'JSX ...
We're all here having the same lapse in judgement here trying to camelCase a react element. Chances are if you're reading this you're...
Read more >
Dependency resolution - Parcel
As Parcel builds your source code, it discovers dependencies, ... Note that these may only be omitted when importing from a JavaScript or...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found