-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Brainstorming: what would a modernized jsdom API look like? #1139
Comments
What about streams: fs.openAsync('foo.html', 'r')
.then(function(fds)
{
return jsdom.fromStream(fd);
})
.then(function(document)
{
}); express.get('/stuff', function(request, response, next)
{
stuffDocument().then(function(document)
{
response.set('Content-Type', 'text/html; charset=utf-8');
return jsdom.serializeAsStream(document).pipe(response);
}).catch(next);
}); |
Would also have to think about what constitutes as resolving the promise when parsing html:
I see a couple of choices:
|
I was thinking of that. But, that's really an entirely new feature request: adding streaming parsing support (parse5 already supports that, but hooking it up seems difficult) and adding streaming serialization support (need parse5 to add support).
As soon as possible, I'd think, to match the raw |
The jQuery support should perhaps be moved to a separate package? Something like: builder = builder.plugin(require('jquery-jsdom')); This is also motivated by the decreasing relevance of jQuery, DOM is getting better. (And most jquery plugins are horrible ;) ) |
No more default fetch & parse options would be great. I keep having to remind myself to disable this when using jsdom.jsdom() |
My thought was to remove the jQuery support since var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
}); is not much nicer than var jsdom = require("jsdom");
jsdom.env(undefined, ["http://code.jquery.com/jquery-2.1.1.js"], function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
}); |
Maybe it would be good to add a method like Jsdom.prototype.insertScript = function (src) {
const window = this.window;
return new Promise(function (resolve, reject) {
const script = window.document.createElement("script");
script.src = src;
script.addEventListener("load", function () { resolve(); });
script.addEventListener("error", function () { reject(new Error("Failed to load script " + src)); });
window.document.body.appendChild(script);
});
}; to make the jQueryify use case into const jsdom = require("jsdom");
const dom = jsdom();
dom.insertScript("http://code.jquery.com/jquery-2.1.1.js").then(function () {
dom.window.$("body").append('<div class="testing">Hello World, It works</div>');
}); |
All one-off "events" (document object is available, all scripts loaded, all html parsed, etc) could be promises on the var jsdom = require('jsdom).parseFile('foo.html', {});
jsdom.documentAvailable.then(..);
jsdom.scriptsLoaded.then(...); Some events that occur multiple times might be interesting: var jsdom = require('jsdom).parseFile('foo.html', {});
jsdom.on('console', function(level, message){ ... });
jsdom.on('raise', function(level, message){ ... });
jsdom.on('scriptError', function(err){ ... });
jsdom.on('fetch', function(type, url){ ... }); |
I'd really like to preserve a synchronous cheerio-esque way of just getting a window/document ASAP. |
I can also imagine a use case where you would want to inject a script before any other script executes. The downside about using promises for the loading phases is that they are fired async. E.g. if I want to inject a script at the correct time, a promise will be too late. |
Me too. That is actually my primary use case in jsdom. The point at which a document is available does not have to be a promise. So iit would just be empty initially if you are loading stuff from an URL. |
I really dislike that kind of bimodal API. I'd rather |
That is what i meant |
Right, so in that case having |
@domenic parse5 don't support streaming at the moment (related discussion: inikulin/parse5#26). But it supports parser suspension on |
Not just parsing a stream is useful. Writing to a stream is also useful, this should be a lot easier to implement. parse5 simply has a bunch of |
@Joris-van-der-Wel You are right, streaming serializer implementation is not a big deal. |
Here are some examples of my current thinking. More are needed, especially showcasing the options. These are kind of insertScript-focused. jsdom.fromUrl("https://iojs.org/dist/")
.then(function (dom) {
return dom.insertScript("http://code.jquery.com/jquery.js");
})
.then(function (dom) {
const window = dom.window;
console.log("there have been", window.$("a").length - 4, "io.js releases!");
}); const dom = jsdom.fromHtml(`<p><a class="the-link" href="https://github.com/tmpvar/jsdom">jsdom!</a></p>`);
dom.insertScript("http://code.jquery.com/jquery.js").then(function () {
const window = dom.window;
console.log("contents of a.the-link:", window.$("a.the-link").text());
}); |
|
Yeah wasn't sure the best name for it. It's a container that has stuff like |
It will definetly make sense if we'll have ctor: const dom = new JsDom(`<p><a class="the-link" href="https://github.com/tmpvar/jsdom">jsdom!</a></p>`);
dom.insertScript("http://code.jquery.com/jquery.js").then(function () {
const window = dom.window;
console.log("contents of a.the-link:", window.$("a.the-link").text());
}); And the static method for the URL: JsDom.fromUrl("https://iojs.org/dist/")
.then(function (dom) {
return dom.insertScript("http://code.jquery.com/jquery.js");
})
.then(function (dom) {
const window = dom.window;
console.log("there have been", window.$("a").length - 4, "io.js releases!");
}); However, it introduces inconsistency in the way we build DOM. But guys with the C#/Java background will be definetly happy 😄 |
I think the constructor is an improvement over my original proposal, which was just a set of factory functions that would return |
I might be thinking of something different, but one pattern I've seen is for knex.table('users').pluck('id').then(ids => console.log(ids));
function withUserName(queryBuilder, foreignKey) {
queryBuilder
.leftJoin('users', foreignKey, 'users.id')
.select('users.name');
}
knex
.table('articles')
.select('title', 'body')
.modify(withUserName, 'articles_user.id')
.then(({name}) => console.log(name)); Another pattern I've seen, which would be more relevant to JSDom as a browser technology, is the internal promise queue à la driver.get('http://www.google.com/ncr');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
driver.wait(until.titleIs('webdriver - Google Search'), 1000); Each of those methods returns a it('should have the right title', function () {
driver.get('http://www.google.com/ncr');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
return driver.wait(until.titleIs('webdriver - Google Search'), 1000);
}); |
"It returns an object that has a then method (a thenable, in promise terms) that builds the query, executes it, attaches its callback to a promise, and then returns that promise in order to enable chaining. The cool thing about this is that you can chain all day and then nothing happens until the first call to then. " The not so cool part of it is now the promise(able) also has unexpected side effects. An interface should work as the developer expects. I'd expect nothing to be executed when calling a then. Only when I call an end or finally would I expect an operation to run. I'd stay away from overriding the |
First draft up at https://github.com/tmpvar/jsdom/blob/newapi/lib/newapi1.md. Upon re-reading this thread I seem to have forgotten the constructor idea, so I should probably switch to that before doing a release. But I might do a release with newapi available soon (while leaving oldapi as the default). Comments welcome! |
@domenic Maybe it's unneeded.. but how about, something related to shadow dom, elements api js methods / and etc related to that.. i'm not an expert so.. sorry for dumb questions/ideas, sometimes i'm thinking about a problems like:
|
That doesn't really seem to have anything to do with the new jsdom API, and is instead some kind of jsdom feature request that it support shadow DOM and custom elements and CSP? |
I think it's a good thing, and maybe w3c forgot to add something for that in usual dom api (yes you are completely right, and maybe after a year of working with Web Components i'm feeling like that.. and I ask myself why this transfer to the Web Components takes so much time and some features that we are still waiting to be implemented in shadow dom v1 and future releases). Of course i think someone should start it with propose / demo / polyfills.. . And thanks for this work! You're our superhero! An excellent example and a big motivation.. |
What will be the new equivalent of the |
The tentative idea is to provide a dom.loaded promise, as described at the bottom of the document. jsdom() is not synchronous for any external resources. |
Two issues I am currently struggling with:
|
@domenic you want api for thought ? https://webkit.org/blog/3476/content-blockers-first-look/ |
I think this should be closed after #1741? |
Never did figure out what to do for resource loader, but yeah, let's close this :) |
I feel like the current API is not the most user-friendly. Here are a few of my complaints:
require("jsdom")
doing something useful)I think as a prerequisite to any redesign, we'd need to fix:
If we got those down, here are some broad ideas on what I could imagine:
Alternately I was considering a "builder" class instead of a Jsdom class, which would allow things like .insertScript() to get back that jsdom.env/jsdom.jQueryify functionality, and other nicer methods... might be worth sketching that out too. We could put a promise-returning insertScript on Jsdom.prototype though perhaps.
The text was updated successfully, but these errors were encountered: