Dynamic Server-Side Rendering with Svelte. How to hydrate it on a client? - server-side-rendering

Assume we have a web page with the HTML lang attribute set dynamically based on the Accept-language header of the request. The body of the page is also language-specific. What I came up with:
require('svelte/register')
// Render the body
const Body = require('./Body.svelte').default
const body = Body.render({ language })
// Render the entire page
const Page = require('./Page.svelte').default
const page = Page.render(Object.assign(body, { language })
// Send page.html to the client
Where Page.svelte is:
<script>
export let html
export let language
</script>
<!DOCTYPE html>
<html lang="{ language }">
<body>
{ #html html }
</body>
</html>
Everything works fine. But now I'd like to have the page interact. I use the Body.svelte in the App and set hydrate option:
const app = new App({
target: document.body,
hydrate: true
})
Without this option set the content is rendered twice. With the option set we have an error in the browser:
Uncaught Error: options.hydrate only works if the component was compiled with the `hydratable: true` option
We can pass options to the compiler, according to the documentation:
require('svelte/register')({ hydratable: true })
It says:
hydratable: true adds a marker to each element in the <head> so that the component knows which elements it's responsible for removing during hydration
But I observe no markers. It has no effect on client-side rendering, and the error remains.
Am I missing something?

Related

Pupeteer with very large PDF not waiting until loaded

Problem: Pupeteer generates a PDF when only about 5% of my data is there.
I'm using puppeteer to pass about 3000 lines of text to a handlebars HTML template I'm then trying to use puppeteer to print a PDF from. Had this working earlier today but a Git fiasco made me roll back and now I cant seem to generate a pdf longer than 3.5 pages (earlier this week it was up to about 90).
I'm thinking this has to do with the following:
const browser = await puppeteer.launch({
args: ['--no-sandbox'],
headless: true
});
var page = await browser.newPage();
await page.goto(`data:text/html;charset=UTF-8,${html}`, {
waitUntil:'load'. <------ (i've also tried networkidle0 and networkidle2)
});
await page.pdf(options);
await browser.close()
Heres the template.html
<!DOCTYPE html>
<html>
<head>
<title>PDF</title>
<head>
<style type="text/css">
</style>
<meta charset="utf-8">
</head>
<body>
<ul id="script">
{{#each this}}
<li class={{category}}>{{text}}</li>
{{/each}}
</ul>
</body>
</html>
My data is an array of 3300 objects and I know it's getting where it needs to. Is there anyway to set a static timeout for Puppeteer? I realize this is a lot of data but am I doing something wrong here?
The waitUntil:'load' goto parameter is the default, you don't need to set it, while the networkidle0 and networkidle2 options are waiting for network connections to be finished: as you don't have any of these as it is a plain HTML markup it neither helps to wait until it is populated with your desired data. I would rather suggest you to use domcontentloaded if you want to use waitUntil. You can check what are the exact differences between them in the docs.
I.) Your problem can be solved with a static timeout, it is called page.waitFor. If you are sure all data will be in the pdf in a certain time then you can set a static timeout, e.g. 3000 milliseconds (3 seconds) before the pdf generation.
await page.waitFor(3000);
await page.pdf(options);
II.) If you can access the very last text value of each object, you could also wait for the content to be appeared. But it will only work if you have unique content for each <li> element.
const veryLastItemText = options[options.length - 1].text // if "options" is an array with "category" and "text" property names inside
await page.waitForXPath(`//li[contains(text(), "${veryLastItemText}")]`);
await page.pdf(options);

Error when loading an external URL in an iframe with Electron

I am trying to create a desktop application using Electron but I am unable to load an external URL like google.com in an iframe.
The code below, inside index.html, triggers an error.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- All of the Node.js APIs are available in this renderer process. -->
<iframe src="http://www.w3schools.com"></iframe>
<script>
// You can also require other files to run in this process
require('./renderer.js')
</script>
</body>
</html>
The error :
index.html:1 Refused to display 'https://www.w3schools.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
www.w3schools.com/ Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE
What is causing this issue and how can I resolve it?
Adding to what has already been answered by Sjoerd Dal.
Adding External URL using IFRAME : Sites block adding their web pages to any other web page, for avoiding click-jacking. This is usually done by :
a. Adding a response in the header. This stops pages which are not whitelisted/not from same-origin to be included in iframes
b. Checking if top window is same as current window.
Now to answer your question, there is actually a very easy way to do that:
const urls = [
"https://www.google.com"
]
const createWindow = () =>{
win = new BrowserWindow({
center: true,
resizable: true,
webPreferences:{
nodeIntegration: false,
show: false
}
});
win.maximize();
win.webContents.openDevTools();
//win.webContents.
console.log(urls[0]);
win.loadURL(urls[0]);
// win.loadURL(url.format({
// pathname: path.join(__dirname,"index.html"),
// protocol: 'file',
// slashes: true
// }));
win.once('ready-to-show',()=>{
win.show()
});
win.on('closed',()=>{
win = null;
});
}
app.on('ready', createWindow);
Most sites these days block other people from iframing them. As you can see with this error, the site only allows iframes coming from the same domain. As an alternative you can use Electron's webview tag which starts the website on a separate thread, sandboxed in its own BrowserWindow. https://electronjs.org/docs/api/webview-tag

Angular2 create a component that displays the content of an external webpage

I need to create a component that displays the content of another webpage.
So for instance if I have the site of stackoverflow, I would like to create a component that does a http request and displays the content through my app. By the way, the external website is just django-rest-swagger and to access it, I need to include a header everytime I access it. So, when I make the request for the external site content I need to inlclude the header x-forwarded-host.
<div>
<html> CONTENT OF EXTERNAL WEBSITE </html>
</div>
thank you
#Component({
selector: ...
template: `<div [innerHTML]="fetchedHtml"></div>
})
export class ExternalHtml {
constructor(http:Http) {
var headers = new Headers();
headers.append('x-forwarded-host', 'foo');
http.get('someUrl', {headers: headers}).subscribe(response => {
this.fetchedHtml = response.json();
}
}
See also
In RC.1 some styles can't be added using binding syntax
Alternatively you can also use an iframe to display the fetched HTML.
You can display it as follows:
<div [innerHTML]="contentOfTheExternalWebsite">
</div>

Solution for SAPUI5 error message because of templateShareable:true?

Since an upgrade of SAPUI5 1.28.20 I receive the following error message:
A shared template must be marked with templateShareable:true in the
binding info
Code is in MangedObject.js and looks like this:
} else if ( oBindingInfo.templateShareable === MAYBE_SHAREABLE_OR_NOT ) {
// a 'clone' operation implies sharing the template (if templateShareable is not set to false)
oBindingInfo.templateShareable = oCloneBindingInfo.templateShareable = true;
jQuery.sap.log.error("A shared template must be marked with templateShareable:true in the binding info");
}
Value of oBindingInfo.templateShareable is true, value of MAYBE_SHAREABLE_OR_NOT is 1.
According to documentation the default of oBindingInfo.templateShareable is true.
So what is wrong here? A bug in the library? Or something with my code?
See also: https://sapui5.netweaver.ondemand.com/sdk/#docs/api/symbols/sap.ui.base.ManagedObject.html
Update for SAPUI5 version 1.32.x
With version 1.32.x the message has changed it is now:
A template was reused in a binding, but was already marked as
candidate for destroy. You better should declare such a usage with
templateShareable:true in the binding configuration. -
but according to the documentation the default should still be true:
{boolean} oBindingInfo.templateShareable?, Default: true option to
enable that the template will be shared which means that it won't be
destroyed or cloned automatically
Now it looks like, that this produces some endless loading, I got this message again and again till the browser crashes.
Anyone an idea what could be wrong?
Looks like the message occurs if the template was instantiated outside the binding. Example:
This code will work:
new sap.m.Select({
items : {
path : "/Items",
template : new sap.ui.core.Item({
text : "{Name}"
})
}
})
This code seems to produce the message:
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
})
new sap.m.Select({
items : {
path : "/Items",
template :oTemplate
}
})
This seems to fix the problem:
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
})
new sap.m.Select({
items : {
path : "/Items",
template :oTemplate,
templateShareable : true
}
})
The answer above marked as being correct is actually not correct at all because this here is wrong:
Looks like the message occurs if the template was instantiated outside
the binding. [...] This code seems to produce the message: [...]
To prove the answer above os wrong on your own just run this example (SAPUI5 1.28.20 has the same result):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SAPUI5 single file template | nabisoft</title>
<script
src="https://openui5.hana.ondemand.com/1.36.12/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"></script>
<!-- use "sync" or change the code below if you have issues -->
<script>
sap.ui.getCore().attachInit(function () {
"use strict";
var oModel = new sap.ui.model.json.JSONModel({
Items: [
{Name: "Michael"},
{Name: "John"},
{Name: "Frank"},
{Name: "Jane"}
]
});
sap.ui.getCore().setModel(oModel);
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
});
new sap.m.Select({
items : {
path : "/Items",
template : oTemplate
}
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody">
<div id="content"></div>
</body>
</html>
Basically, a clear definition of the lifecycle for templates is (or was) missing in UI5. When this issue was detected there were already many older apps around... So now this new "feature" was introduced somewhen last year (which is kind of backwards compatible). UI5 tries to detect automatically the developer's intention about the lifecycle of a given template used in the binding (using some heuristic). If UI5 cannot clearly tell what the developer actually wanted then you will see this error log - which actually does not affect the functionality at all. It just tells the developer that there is a template somewhere which will not be destroyed by the UI5 runtime. In other words, if you set templateShareable=true then you should make sure to destroy the template in order to avoid memory leaks. So just setting templateShareable=true is not the whole story...
I have published a detailed blog about this: Understanding templateShareable in SAPUI5

jsdom does not fetch scripts on local file system

This is how i construct it:
var fs = require("fs");
var jsdom = require("jsdom");
var htmlSource = fs.readFileSync("./test.html", "utf8");
var doc = jsdom.jsdom(htmlSource, {
features: {
FetchExternalResources : ['script'],
ProcessExternalResources : ['script'],
MutationEvents : '2.0'
},
parsingMode: "auto",
created: function (error, window) {
console.log(window.b); // always undefined
}
});
jsdom.jQueryify(doc.defaultView, 'https://code.jquery.com/jquery-2.1.3.min.js', function() {
console.log( doc.defaultView.b ); // undefined with local jquery in html
});
the html:
<!DOCTYPE HTML>
<html>
<head></head>
<body>
<script src="./js/lib/vendor/jquery.js"></script>
<!-- <script src="http://code.jquery.com/jquery.js"></script> -->
<script type="text/javascript">
var a = $("body"); // script crashes here
var b = "b";
</script>
</body>
</html>
As soon as i replace the jquery path in the html with a http source it works. The local path is perfectly relative to the working dir of the shell / actual node script. To be honest i don't even know why i need jQueryify, but without it the window never has jQuery and even with it, it still needs the http source inside the html document.
You're not telling jsdom where the base of your website lies. It has no idea how to resolve the (relative) path you give it (and tries to resolve from the default about:blank, which just doesn't work). This also the reason why it works with an absolute (http) URL, it doesn't need to know where to resolve from since it's absolute.
You'll need to provide the url option in your initialization to give it the base url (which should look like file:///path/to/your/file).
jQuerify just inserts a script tag with the path you give it - when you get the reference in the html working, you don't need it.
I found out. I'll mark Sebmasters answer as accepted because it solved one of two problems. The other cause was that I didn't properly wait for the load event, thus the code beyond the external scripts wasn't parsed yet.
What i needed to do was after the jsdom() call add a load listener to doc.defaultView.
The reason it worked when using jQuerify was simply because it created enough of a timeout for the embedded script to load.
I had the same issue when full relative path of the jquery library to the jQueryify function. and I solved this problem by providing the full path instead.
const jsdom = require('node-jsdom')
const jqueryPath = __dirname + '/node_modules/jquery/dist/jquery.js'
window = jsdom.jsdom().parentWindow
jsdom.jQueryify(window, jqueryPath, function() {
window.$('body').append('<div class="testing">Hello World, It works')
console.log(window.$('.testing').text())
})

Resources