krux / postscribe

Asynchronously write javascript, even with document.write.
MIT License
986 stars 156 forks source link

Writing async scripts using document.write with releaseAsync on fails #480

Open Goues opened 5 years ago

Goues commented 5 years ago

Hi,

we're tryng to switch advert rendering over to postscribe but we're struggling with an issue. Having multiple ads on the website, where most of them are async, means it takes a long time to render them one by one by one. We tries using the releaseAsync option because it can speed it up a LOT.

However, there is an ad out there that is incompatible with the current implementation of postscribe, it looks like this:

<script> document.write("<scr"+"ipt async src='remote/describe-write.js'></scr"+"ipt>"); </script>

Notice that there is a document write for async script. I know, this is stupid, but this is real world example.

Using releaseAsync to speed up rendering, postscribe fails in this case, because it releases on the inner async script when it is not the one on top of the stack. Adding it to test suit in karma and logging tok and stack inside _onScriptDone:

it('does something wierd.', done => {
    postscribe(document.body, '<script>document.write("<scr"+"ipt async src=\'remote/describe-write.js\'></scr"+"ipt>");</script>', {
      releaseAsync: true,
      afterAsync: () => {
        expect(1);
        done();
      },
      error: (error) => {
        console.log(error);
      }
    });
  });

current TOK:

AtomicTagToken {
    type: 'atomicTag',
    length: 54,
    text: '<script async src='remote/describe-write.js'></script>',
    tagName: 'script',
    attrs: Object {
        async: '', src: 'remote/describe-write.js'
    },
    booleanAttrs: Object {
        async: true
    },
    unary: false,
    html5Unary: false,
    content: '',
    src: 'remote/describe-write.js'
}

stack:

[
    AtomicTagToken {
        type: 'atomicTag',
        length: 96,
        text: '<script>document.write("<scr"+"ipt async src='remote/describe-write.js'></scr"+"ipt>");</script>',
        tagName: 'script',
        attrs: Object {},
        booleanAttrs: Object {},
        unary: false,
        html5Unary: false,
        content: 'document.write("<scr"+"ipt async src='remote/describe-write.js'></scr"+"ipt>");',
        src: undefined,
        outerWrites: [...]
    }
]

This demonstrates that the inner script is considered DONE when it is not on top of the stack, only the outer script is there.

The stack call is 1) start outer 2) done inner 3) throw error 4) start inner.

The problem for me is also that the test case itself does not fail, because afterAsync IS called, so expect(1) gets called. It only breaks postscribe for the next ad.