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's file structure, package.json and lite-youtube.js are listed in belowpackage.json's main and module are indicate to lite-youtube.jspreact-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.
preact create Default preact-cli-build-issuecd preact-cli-build-issuenpm i @justinribeiro/lite-youtube@justinribeiro/lite-youtube and write some code (https://github.com/flameddd/preact-cli-build-issue/commit/a1bb98f837dd91ef54ce440a25856b408ce289b4)npm run buildI 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/[email protected]",
"_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": "[email protected]"
},
"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://[email protected]/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.
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
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> }).
Yep @rschristian has the right fix.
The lite-youtube.ts error is actually correct - that is the sourcemapped error location. If you find the GitHub repo, it likely includes a .ts file by that name and you'll see line 367 is the one Ryan mentioned that relies on HTMLElement.
Ah, whoops, source map didn't occur to me. Thanks for the save! @developit
So, the process is
preact build load lite-youtube.jsHTMLElementpreact build is trying to dig more information, so load lite-youtube.js.maplite-youtube.js.map's sources field is ["lite-youtube.ts"]lite-youtube.ts is unable to read (bcuz it's not exist)am i right?
Does step 3 (load source map) is common behavior (for dig more information) ?
Does other framework or bundler will do this TOO ? (any reference ?)
I did some google
lite-youtube.js.map's sources field indicate to TS file is RIGHT. I thinking should I open an @justinribeiro/lite-youtube Issue to suggest
馃檹 thanks @rschristian and @developit answering 馃檹
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.
Thanks for your reply !!
Most helpful comment
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:
Preact-CLI prerenders in Node, and
HTMLElementis 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> }).