Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

On prerendered pages, Webpack injects async <script> that requires webpackJsonp to be defined #9

Closed
drewlustro opened this issue Oct 24, 2016 · 10 comments
Labels

Comments

@drewlustro
Copy link
Collaborator

I have a vue 1.x application that is using prerender-spa-plugin. When generating the index.html files for each route on build, webpack will sometimes inject <script src="/static/js/0.[hash].js" async=""></script> into the <head></head>.

This is normal and expected, as webpack optimizes for loading additional chunks on-demand.


A problem surfaces when visiting the endpoint, because the browser tries tries to evaluate the script 0.[hash].js before manifest.js or vendor.js chunks, and reliably causes an exception:

Uncaught ReferenceError: webpackJsonp is not defined.

Is there a flag or option to temporarily prevent webpack from injecting on-demand chunks? Or must we get hacky and scrub the <script /> tags from prerendered indexes? I cannot naively set the captureAfterTime: 0, as I rely on other aspects of the page asynchronously rendering.

@chrisvfritz
Copy link
Owner

It sounds like you just need to change where (or in what order) scripts are getting injected. If you're using html-webpack-plugin, you may find the inject or chunksSortMode options useful.

@drewlustro
Copy link
Collaborator Author

@chrisvfritz , your suggestion would address my problem if html-webpack-plugin was to blame. It is behaving correctly and as expected. Perhaps there's a misunderstanding of my problem.

To clarify:

After html-webpack-plugin:

index.html emitted with approximate structure:

<!DOCTYPE html>
<html>
<head>
    <title>Website</title>
    <link href="/static/css/app.65a8b165ef01db392fbe347dbb15474e.css" rel="stylesheet">
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="/static/js/manifest.678ccd30da482f0c1dfc.js"></script>
    <script type="text/javascript" src="/static/js/vendor.eed2bc873a3067e62761.js"></script>
    <script type="text/javascript" src="/static/js/app.7c3e217f3fa2b2b68deb.js"></script>
</body>
</html>

Everything works and is 💯 .

After prerender-spa-plugin:

some-endpoint/index.html emitted with option captureAfterTime: 3000:

<!DOCTYPE html>
<html>
<head>
    <title>Website</title>
    <link href="/static/css/app.65a8b165ef01db392fbe347dbb15474e.css" rel="stylesheet">
    <script type="text/javascript" charset="utf-8" async="" src="/static/js/0.e62b0759da64f0dbfc4c.js"></script>
    <style type="text/css">
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="/static/js/manifest.678ccd30da482f0c1dfc.js"></script>
    <script type="text/javascript" src="/static/js/vendor.eed2bc873a3067e62761.js"></script>
    <script type="text/javascript" src="/static/js/app.7c3e217f3fa2b2b68deb.js"></script>
</body>
</html>

👎 Broken due to the only pivotal difference: a new <script type="text/javascript" charset="utf-8" async="" src="/static/js/0.e62b0759da64f0dbfc4c.js"></script> injected into <head>.

My understanding of what's happening

  • PhantomJS client views a well-formed, vue+webpack powered endpoint and asks for more chunks, namely 0.e62b0759da64f0dbfc4c.js
  • Chunks get injected into <head> (as expected)
  • Resulting final HTML is captured by prerender-spa-plugin and saved to a destination some-endpoint/index.html
  • Final HTML for some-endpoint/index.html is unusable because it possess a rouge <script> tag in <head> that assumes it was injected after evaluating the manifest, app, and vendor JS.

Sidenote: I tried your suggestion for html-webpack-plugin and set inject: 'head'. It breaks the page before prerender-spa-plugin gets to it, most likely because the DOM is not guaranteed to be ready.

@chrisvfritz
Copy link
Owner

Thanks for clarifying. Unfortunately, Webpack hard-codes it in that split chunks get appended to the head rather than the body. I think your options are to either:

  • Fork Webpack and modify JsonpMainTemplate.prototype.renderRequireEnsure to append to the body instead.
  • Submit a PR to Webpack to add an option changing where the script gets injected.
  • Submit a PR to this repo to allow an arbitrary function to be run within the document directly before page capture. In that function, you could manually remove the script tag from the head - or move it to the body instead.

@drewlustro
Copy link
Collaborator Author

Damn. Thanks for listing those fine options, my friend.

Imma pray on this for a bit and decide on monday what I'll do.

drewlustro added a commit to drewlustro/prerender-spa-plugin that referenced this issue Nov 4, 2016
@nemtsov
Copy link

nemtsov commented Nov 8, 2016

@drewlustro Assuming you are using the HtmlWebpackPlugin and as an alternative, you can set inject: 'head' for the plugin to add all of your scripts (manifest, vendor, and app) into the <head>. Webpack will place it's async script right after yours, which will make the app work. If you end up using this, as I have, you'll also probably want to put your initial new Vue( inside a document.addEventListener('DOMContentLoaded', () => {, since the script in the head will run before the DOM is loaded.

@drewlustro
Copy link
Collaborator Author

drewlustro commented Nov 15, 2016

bahahaha @nemtsov 💯 that works and is much more elegant.

I had checked your (pre-edit) approach, and was stressed because my pages were still broken. The DOMContentLoaded event listener patched the issue easy. Are there any other drawbacks to this approach? The only minor thing I can think of is that <script> tags within <head> are blocking on super-legacy browsers (not important for me).

If no other drawbacks are known, it looks like my #10 PR is unnecessary.

@chrisvfritz
Copy link
Owner

@drewlustro If you don't find any other drawbacks, I'd still be happy to accept a PR documenting this approach. 😃

@lili21
Copy link

lili21 commented Apr 12, 2017

@nemtsov I don't think that will fix the problem. async script will be executed immediately after the file be loaded. the run order is still unpredictable.

@iamike
Copy link

iamike commented Jul 10, 2017

is this issue has been fixed?

@Yami-Bitshark
Copy link

Actually, the solution is easy , just go where u added the spa prerender plugin, and add ignoreJSErrors: true:
new PrerenderSpaPlugin(
// Path to compiled app
path.join(__dirname, '../dist'),
// List of endpoints you wish to prerender
[ '/'],{
ignoreJSErrors: true
}
)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

6 participants