chrisvfritz / prerender-spa-plugin

Prerenders static HTML in a single-page application.
MIT License
7.32k stars 634 forks source link

JS Isn’t Firing #31

Closed nathanaelphilip closed 6 years ago

nathanaelphilip commented 7 years ago

Hi Chris!

Thanks for the plugin and the demos!

I’ve got the build running properly, and the static pages are rendered, but no JS seems to be loading. I have a few @clicks and @submits that aren’t firing when clicked. Is there another step when you have some JS that allows the user to interact with the page?

in main.js

/* eslint-disable no-new */
let root = new Vue({
    router,
    store,
    render: h => h(App)
})

document.addEventListener('DOMContentLoaded', function () {
    root.$mount('#app')
})

the other configs are set up like the example in the Vue Spa repo.

chrisvfritz commented 7 years ago

I think this is a duplicate of #25. Let me know if the comment there resolves your problem.

nathanaelphilip commented 7 years ago

I think that may be it, but new Event() keeps failing during the build: 'Event' is not defined. I thought that it was supported, but the vue-cli builds don’t seem to like it.

nathanaelphilip commented 7 years ago

i tried this:

let event = document.createEvent('Event')
event.initEvent('vue-post-render', true, true)

but the production build seems to hang up

Edit:

document.dispatchEvent(new window.Event('vue-post-render'))

I was able to do that in main.js, but the production build still doesn’t run fully.

nathanaelphilip commented 7 years ago

ok, update – the build process seems to work, but it doesn’t allow the npm run build to finish properly, which will probably cause issues when we deploy. Locally, the static files work fine though.

very strange

chrisvfritz commented 7 years ago

@nathanaelphilip What version of the plugin are you using? The prerendering not correctly reporting when it was finished was a bug in 1.x, but should be fixed in v2.

nathanaelphilip commented 7 years ago

i’m using 2.0.1

nathanaelphilip commented 7 years ago

not sure if this helps:

"autoprefixer": "^6.4.0",
    "babel-core": "^6.0.0",
    "babel-eslint": "^7.0.0",
    "babel-loader": "^6.0.0",
    "babel-plugin-transform-runtime": "^6.0.0",
    "babel-preset-es2015": "^6.0.0",
    "babel-preset-stage-2": "^6.0.0",
    "babel-register": "^6.0.0",
    "chai": "^3.5.0",
    "chalk": "^1.1.3",
    "chromedriver": "^2.21.2",
    "connect-history-api-fallback": "^1.1.0",
    "cross-spawn": "^4.0.2",
    "css-loader": "^0.25.0",
    "email-validator": "^1.0.7",
    "eslint": "^3.7.1",
    "eslint-config-standard": "^6.1.0",
    "eslint-friendly-formatter": "^2.0.5",
    "eslint-loader": "^1.5.0",
    "eslint-plugin-html": "^1.3.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^2.0.1",
    "eventsource-polyfill": "^0.9.6",
    "express": "^4.13.3",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "function-bind": "^1.0.2",
    "html-webpack-plugin": "^2.8.1",
    "http-proxy-middleware": "^0.17.2",
    "inject-loader": "^2.0.1",
    "isparta-loader": "^2.0.0",
    "json-loader": "^0.5.4",
    "karma": "^1.3.0",
    "karma-coverage": "^1.1.1",
    "karma-mocha": "^1.2.0",
    "karma-phantomjs-launcher": "^1.0.0",
    "karma-sinon-chai": "^1.2.0",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-spec-reporter": "0.0.26",
    "karma-webpack": "^1.7.0",
    "lolex": "^1.4.0",
    "mocha": "^3.1.0",
    "nightwatch": "^0.9.8",
    "node-emoji": "^1.4.3",
    "node-sass": "^4.0.0",
    "opn": "^4.0.2",
    "ora": "^0.3.0",
    "paper": "^0.10.2",
    "phantomjs-prebuilt": "^2.1.3",
    "prerender-spa-plugin": "^2.0.1",
    "rs-breakpoints": "^0.0.5",
    "sass-loader": "^4.1.0",
    "selenium-server": "2.53.1",
    "semver": "^5.3.0",
    "shelljs": "^0.7.4",
    "sinon": "^1.17.3",
    "sinon-chai": "^2.8.0",
    "url-loader": "^0.5.7",
    "velocity-animate": "^1.4.0",
    "vue-head": "^2.0.10",
    "vue-loader": "^10.0.0",
    "vue-resource": "^1.0.3",
    "vue-router": "^2.1.3",
    "vue-style-loader": "^1.0.0",
    "vue-svg-loader": "^0.1.2",
    "vue-template-compiler": "^2.1.0",
    "vuex": "^2.1.1",
    "webpack": "^1.13.2",
    "webpack-dev-middleware": "^1.8.3",
    "webpack-hot-middleware": "^2.12.2",
    "webpack-merge": "^0.14.1"
chrisvfritz commented 7 years ago

Hmm, I might be misunderstanding then. Can you describe in more detail what you mean by this?

it doesn’t allow the npm run build to finish properly

nathanaelphilip commented 7 years ago

You’ll see here that if I remove the {captureAfterDocumentEvent: 'vue-post-render'} line from the webpack config, it finishes the build process. If I add it back in, it never stops processing. But if i look at the static files, all of my JS fires properly on the build with {captureAfterDocumentEvent: 'vue-post-render'} enabled (even though the process doesn’t stop). So static site is getting built – but we can’t deploy properly if the script never finishes.

http://quick.as/QZjRSAAoG

chrisvfritz commented 7 years ago

Interesting. It looks like despite being asynchronous, the DOMContentLoaded callback is being run just before PhantomJS reports the page is loaded. You can force the event to fire in the following tick (when we're actually listening for it) with this hack:

document.addEventListener('DOMContentLoaded', function () {
  setTimeout(function () {
    document.dispatchEvent(new Event('vue-post-render'))
  }, 1)
})

I think I'll add a note about this in the README.

nathanaelphilip commented 7 years ago

I’ve added that code and it’s still not finishing – is there a way to see which errors are happening? I tried --verbose but that doesn’t really help.

chrisvfritz commented 7 years ago

If there are runtime errors while rendering, they should actually be printed to the console. If they aren't in your case, there must be a bug. 😕 I'm pretty slammed right now, but I'll take a look at it when I can. If you get to it sooner and discover something, pull requests are welcome!

Hyra commented 7 years ago

Same here, building hangs forever when using the captureAfterDocumentEvent

I tried moving the event from beforeMount to created, but neither seems to work.

sebpettersson commented 7 years ago

Same thing for me. The build actually succeeded once, but every other time I've tried it just hangs (and never times out either).

Edit: wrapping the dispatchEvent in a setTimeout seems to have fixed it.

lili21 commented 7 years ago

@Hyra try moving that to mounted.

drewlustro commented 7 years ago

Hi @nathanaelphilip ! Are you still having this issue?

nathanaelphilip commented 7 years ago

hey @drewlustro i think my coworker figured it out. feel free to close!

stursby commented 7 years ago

@nathanaelphilip Would you mind sharing your solution? I'm running into the same issue.

nathanaelphilip commented 7 years ago

hi @stursby I didn’t come up with the solution, but here’s the cod:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueHead from 'vue-head'
import VueResource from 'vue-resource'
import App from './App'
import store from './store'
import routes from './routes'
import VueAnalytics from 'vue-analytics'

import comm from './comm'

Vue.use(VueRouter)
Vue.use(VueHead)
Vue.use(VueResource)

const router = new VueRouter({
    mode: 'history',
    root: '/',
    routes // short for routes: routes
})

Vue.use(VueAnalytics, { id, router })

let root = new Vue({
    router,
    store,
    render: h => h(App)
})

document.addEventListener('DOMContentLoaded', function () {
    root.$mount('#app')
})
stursby commented 7 years ago

@nathanaelphilip Ok, I have essentially the exact same thing... but am trying to use captureAfterDocumentEvent method. What does your PreloadWebpackPlugin config look like if you don't mind me asking?

nathanaelphilip commented 7 years ago

i don’t think i have that...

nathanaelphilip commented 7 years ago

this is the production conf:

new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.html',
      inject: 'head',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),

    // prerendering
    new PrerenderSpaPlugin(
      path.join(__dirname, '../dist'),
      [ '/', '/features', '/terms', '/privacy' ]
    ),

    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module, count) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    }),
hurradieweltgehtunter commented 7 years ago

I'm having the same issue. But with one difference.

case 1: Dynamic pages

I have a page with dynamic content loaded via AJAX. after a successful request, i'm setting data with this.product = response.body and call document.dispatchEvent(new Event('custom-post-render-event')) Executing npm run build runs fine, generating static html files, boom, thank you.

case 2: Static pages

Trying to render a static page (like /contact, no initial ajax calls) results in the behavior above. npm run build never finishes. I'm calling document.dispatchEvent(new Event('custom-post-render-event')) directly in the created() hook inside my contact component. I tried it with

document.dispatchEvent(new Event('custom-post-render-event'))

-> no success

also

this.$nextTick(() => {
  document.dispatchEvent(new Event('custom-post-render-event'))
})

-> no success

i tried it with

setTimeout(() => {
  document.dispatchEvent(new Event('custom-post-render-event'))
}, 3000)

--> success

So i think it might be a timing problem that the event gets fired before the eventListener is set up (but it's just a thought)

Investigation

I searched a little, added some console outputs etc. In phantom-page-render.js is the distinction between the render trigger options. Using a custom event it depends on phantomjs's onCallback event which is marked as experimental (http://phantomjs.org/api/webpage/handler/on-callback.html). ATM i'm trying to get a workaround if this is the line where the capture process hangs (I never used phantomJS before, so i have no clue what I'm doing :D)

If anyone has a tip, feel free :)

drewlustro commented 7 years ago

The more this issue resurfaces, the more I am convinced that it is a phantomjs issue, as noted by @hurradieweltgehtunter . Thanks for all your investigation into this – if anyone finds a concrete solution, please let me know!

hurradieweltgehtunter commented 7 years ago

Why aren't you using the window.callPhantom() method as event triggered rendering method? in mypage.html:

if (typeof window.callPhantom === 'function') {
  window.callPhantom({ hello: 'world' });
}

in phantomJS (serverside)

var webPage = require('webpage');
var page = webPage.create();

page.onInitialized = function() {
  page.onCallback = function(data) {
    console.log('CALLBACK: ' + JSON.stringify(data));
    // Prints 'CALLBACK: { "hello": "world" }'
  };
}

(taken from http://phantomjs.org/api/webpage/handler/on-callback.html)

I made it work in a test, only problem was that it told me, that it needed a polyfill for this browser. I stopped at this point for now but maybe this could work?

drewlustro commented 7 years ago

What kind of polyfill did it need? Unfortunately PhantomJS has been sunset, so we won't see any next-generation browser features implemented until the capture engine is replaced with Chrome Headless.

mubaidr commented 7 years ago

Having the same issues using latest vuejs and prerender-spa-plugin:

If anybody has solution or idea please do share.

mubaidr commented 7 years ago

A little update about the issue, after lot of tinkering around with the code, I decided to update some packages. Surprisingly it worked! No more stuck on 'building for production' BUT still js will not fire for vuejs 2 app. :(

Here is repo with changes that worked: https://github.com/mubaidr/prerender-spa-plugin

bobmoff commented 6 years ago

Same here. Cant get it work when using either captureAfterElementExists or captureAfterDocumentEvent. And when skipping them I only get the CSS pre-rendered, but not the HTML.

vivacityappdev commented 6 years ago

Hi,

We are experiencing the same issue. We're using the advanced configuration captureAfterDocumentEvent :

new Promise(resolve => {
        axios.get(url).then((res) => {
            console.log(res.data);
            this.message = res.data.short_name;
            resolve();
          });
      }).then(() => {
        document.dispatchEvent(new Event('fetch-done'));
      });

We also tried it this way :

setTimeout(function () {
    document.dispatchEvent(new Event('custom-post-render-event'));
}, 3000);

But none of them worked. Running the 'npm run build' command takes a really long while, waited for it for an hour and a half to pre-render a single route '/' and got an out of memory error.

nikolaynesov commented 6 years ago

Same problem here. JS never gets executed.

PS: In my case it was the ES6 problem. PhantomJS does not support ES6 yet. They were going to introduce it in 2.5 version but looks like it will never be released.

Coletrane commented 6 years ago

Same problem using code splitting. My <router-view> doesn't render, only my <footer> component in App.vue.

asaldivar commented 6 years ago

Hi Everyone,

Having the same issue with my npm run build seemingly hanging up. Been scratching my head on this for hours so I figured I'd ask for help.

For context, I'm using the vue webpack boilerplate.

Here's my configuration in webpack.prod.conf.js (option to use captureAfterDocumentEvent):

    new PrerenderSpaPlugin(
      // Absolute path to compiled SPA
      path.join(__dirname, '../dist'),
      // List of routes to prerender
      [ '/', '/about', '/donate', '/subscribe', '/contact' ],
      {captureAfterDocumentEvent: 'custom-post-render-event'}
    ),

Main.js:

import Vue from 'vue'
import VeeValidate from 'vee-validate'
import VuePaginate from 'vue-paginate'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

Vue.use(VeeValidate)
Vue.use(VuePaginate)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

Action (I dispatch my event, custom-post-render-event, here after my API call):

export const trimmerActions = {
    async getTrimmers({ commit }) {
        commit('setTrimmers', await TrimmerService.fetchTrimmers())
        document.dispatchEvent(new Event('custom-post-render-event'))
    }
}

My dispatch to the action from my component (I even tried putting the dispatchEvent in created():

...
        async created() {
            try {
                await this.$store.dispatch('getTrimmers')
                                // (tried this as well) document.dispatchEvent(new Event('custom-post-render-event'))
            } catch(e) {
                console.warn(e)
            }
        },
        computed: {
            trimmers() {
                return this.$store.getters.allTrimmers
            },
...

Here's even my mutation:

export const trimmerMutations = {
    setTrimmers(state, payload) {
        state.trimmers = payload.data
    }
}

Locally, my asynchronous call works fine and the DOM eventually loads with the dynamic content. However, I can't figure out why the prerender-spa-plugin script hangs up. Could it be something with my configuration? I'm using "prerender-spa-plugin": "^2.1.0", and "vue": "^2.5.2", if that helps.

Another thing I noticed was that when I installed prerender-spa-plugin there was a message, phantomjs not found on path. Seemed just like a warning but I want to mention it in-case it's critical.

@hurradieweltgehtunter , I believe I'm trying to accomplish what you successfully did in your case 1: Dynamic pages. Do you happen to have any insight?

Are we coming to the conclusion that we can't use captureAfterDocumentEvent? When I remove this option from the plugin the build is able to successfully complete and all my static pages are created as expected (knowing that I won't have my asynchronous data prerendered).

Thanks in advance everyone for any help.

asaldivar commented 6 years ago

Just to add, I also tried captureAfterElementExists on its own which succesfully generated my /dist/index.html but npm run build still hung. I think @bobmoff was having a similar issue with the build hanging. So close!

Maplesog commented 6 years ago

Anyone solve this problem?

asaldivar commented 6 years ago

bump

Maplesog commented 6 years ago

Anyone solve this problem?

mubaidr commented 6 years ago

Even if I am able to generate static pages with captureAfterDocumentEvent (prerender-spa-plugin finishes good), vuejs JavaScript/events does not work.

robsterlini commented 6 years ago

@mubaidr I had a similar issue where my Vue component wasn't being mounted and it came down to something as simple as id="app" was being removed in the prerender stage so there was no identifier to mount Vue to 🙈. Hope that helps!

mubaidr commented 6 years ago

Hmm... interesting. I will see if that is the case. Definitely worth a try. 👍

iMomen commented 6 years ago

Any update on this issue?

asaldivar commented 6 years ago

+1

mubaidr commented 6 years ago

I am doing a major re write of this module, lets hope I can fix it. 🔢

asaldivar commented 6 years ago

Thanks @mubaidr and thanks for keeping us updated!

mubaidr commented 6 years ago

I have created a new plugin inspired by this one, you can follow progress here: https://github.com/mubaidr/prerender-plugin

asaldivar commented 6 years ago

@mubaidr Awesome! I looked through the issues but wasn't quite able to tell, have you gotten the custom-document-event option to work?

mubaidr commented 6 years ago

@asaldivar Yes. The new plugin is not based on this one, it is re-written entirely. So, go ahead and try it.

asaldivar commented 6 years ago

Thanks so much! I'll try today!

speir-wang commented 6 years ago

Anyone got the solution?

codeofsumit commented 6 years ago

same problems here :(