Comments (38)
@husayt just clone this repo and change the lines 91 and 120: on former remove the inicial state and later add the initial state, on build you will have on each html page its own state, just modify the router example with your logic
from vite-ssg.
@userquin if this issue is fixed I expect the code above to just work. That would make vite-SSG so much more powerful and flexible
from vite-ssg.
I now experience the same issue.
from vite-ssg.
You are right, I would say we need both for vite-ssg
to be most flexible. The callback you propose can initialize the state and we also need to provide a configurable way to allow for custom serialization/deserialization of all states (global and page isolated states), and this is already provided by the transformState
callback. So, maybe a callback like initializeGlobalState
in the ViteSSG
function might be helpful.
Concerning the page
state, it believe it's best to provide a composable like useAsyncData
or usePageState
maybe.
from vite-ssg.
Something similar to this:
export const createApp = ViteSSG(
App,
{ routes },
// NEW GLOBAL STATE CALLBACK
(/* ARGS NOT AVAILABLE YET*/) => {
// here global state will be loaded
},
({ app, router, routes, isClient, initialState, routePath, globalState }) => {
...
if (isClient) {
// restore page state
// restore global state
} else {
if (import.meta.env.VITE_SSG) {
// here the `initialState` will be always the page state, the global state will be handled from NEW GLOBAL STATE CALLBACK
initialState.data = { name: routePath ==="/a" ? ''ALPHA" : "BETA" }
console.log('saving ' ,initialState.data)
} else {
// here store the global state: `globalState` will be populated by NEW GLOBAL STATE CALLBACK
}
}
...
}
)
from vite-ssg.
@userquin do we need import.meta.env.VITE_SSG
since we already have isClient
?
i thought that import.meta.env.VITE_SSG === !isClient
from vite-ssg.
If you use mock: true
option on ssgOptions
: see https://github.com/antfu/vite-ssg/blob/main/src/node/build.ts#L106, the window
is defined...
I've never get it working (JSDOM
), it always throws an error, maybe we need to add some thing... see
from vite-ssg.
@userquin not sure about page or global scoped state, but here is the bug originating from how the static pages generated during SSG. On one side to generate the body of html plugin uses page specific state, but it adds the same
<script>window.__INITIAL_STATE__={data:{...}</script>
to the footer of all of these pages
it seems it generates initial html first
[vite-ssg] Build for server...
vite v2.5.6 building SSR bundle for production...
saving { name:"ALPHA" }
and then when rendering pages it uses bits from the html generated during initial render
[vite-ssg] Rendering Pages... (378)
saving { name:"ALPHA" }
saving { name:"BETA" }
from vite-ssg.
@husayt The console log is just a trace, the state saved is not that state, if you see this line https://github.com/antfu/vite-ssg/blob/main/src/node/build.ts#L133, the state is saved here but using the initial state outside the promise loop defined here https://github.com/antfu/vite-ssg/blob/main/src/node/build.ts#L91 instead using the state of each page, that should be declared here https://github.com/antfu/vite-ssg/blob/main/src/node/build.ts#L120.
With my changes, your initial state will have the scoped
page state, that's, each page with its own state. Just see the /a.html
state on my screenshot.
The state you've on the console is a not the state that will be saved, since the L133 is using the reference on L91 and not using the state on L120, that it is the state your are showing on console.
The initialState
on L91 is the global
state while the state that should be retrieved from L120 is the page
scoped.
from vite-ssg.
@userquin one more thing, as I expected , router hook is not required. It just works
from vite-ssg.
It should indeed be fixed by #93, sorry for the duplicate issue @antfu
from vite-ssg.
@yassilah No, unfortunately, this is still not resolved.
from vite-ssg.
@husayt seems there is a problem, here some inputs, I'll try to fix it tmr or later, I'm very busy at work.
/cc @antfu : maybe can help to digging into the problem.
I'm testing with your changes on multiple-pages-with-store
example:
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, initialState, routePath }) => {
const pinia = createPinia()
app.use(pinia)
if (import.meta.env.SSR) {
console.log(`RoutePath: ${routePath}`)
console.log(`SSR Pinia state: ${JSON.stringify(pinia.state.value)}`)
// this will be stringified and set to window.__INITIAL_STATE__
initialState.pinia = { routePath: routePath || '/' } // pinia.state.value
}
else {
console.log(`Pinia state: ${JSON.stringify(initialState?.pinia || {})}`)
// on the client side, we restore the state
pinia.state.value = initialState?.pinia || {}
}
router.beforeEach((to, from, next) => {
const store = useRootStore(pinia)
store.initialize()
next()
})
},
{
transformState(state) {
return import.meta.env.SSR ? devalue(state) : state
},
},
)
Seems there is something wrong with the app state, you can see on following screenshot at bottom rendering the /nested/deep/b
, I think some missing await on the logic:
The html part on SSG rendering is this for /nested/deep/b
page:
<html>
....
<body>
<div id="app" data-server-rendered="true"><main data-v-9aed9e50=""><div class="markdown-body" data-v-9aed9e50=""><h1>Page A</h1></div></main></div>
<script>window.__INITIAL_STATE__={pinia:{routePath:"\u002F"}}</script>
</body></html>
from vite-ssg.
@antfu please fix the problem, I cannot make a PR rigth now.
I've found the problem, we are using the SSR initialState instead each page ones':
const { routes, initialState } = await createApp(false)
then inside the Promise.all
block we're using this initial state instead the createApp
one (lines 88 and 114):
from vite-ssg.
@userquin that might explain what is going on there. One other benefit of this fix, would be allowing for much easier store initialisation. One would not need router.beforeEach now. It becomes so much simpler
export const createApp = ViteSSG(
App,
{ routes }, ({
router,
app,
head,
isClient,
routes,
initialState,
routePath,
}) => {
const pinia = createPinia()
pinia.use(({ store }) => {
store.router = markRaw(router)
})
app.use(pinia)
let trans = []
if (!isClient) {
// this will be stringified and set to window.__INITIAL_STATE__
initialState.trans = getRouteSpecificState(routePath)
} else {
// on the client side, we restore the state
trans = initialState.trans
}
const strg = useStorageStore(pinia)
strg.initialise(trans)
}))
@antfu this is why it is so important to get this right. I tried to fix it few times myself, but was really confused.
from vite-ssg.
@userquin that might explain what is going on there. One other benefit of this fix, would be allowing for much easier store initialisation. One would not need router.beforeEach now. It becomes so much simpler
export const createApp = ViteSSG( App, { routes }, ({ router, app, head, isClient, routes, initialState, routePath, }) => { const pinia = createPinia() pinia.use(({ store }) => { store.router = markRaw(router) }) app.use(pinia) let trans = [] if (!isClient) { // this will be stringified and set to window.__INITIAL_STATE__ initialState.trans = getRouteSpecificState(routePath) } else { // on the client side, we restore the state trans = initialState.trans } const strg = useStorageStore(pinia) strg.initialise(trans) }))
The problem is that we are using an SPA
and so the initial context state will be only applied if using the router, you are not rerendring the html page, only when refrshing page. The initialState is global, cannot have one store for each page...
from vite-ssg.
@husayt maybe I'm wrong ;)
from vite-ssg.
@husayt So what's the desired outcome of the code you provided?
I'm not quite sure if I understood it correctly.
So, do you wish for the initial state to be different depending on which route is accessed first when accessing the page?
Example:
- you access the page
/index
, your state is{ routePath: '/index' }
. Then as you use vue router to travel to different pages, the initial state remains{ routePath: '/index' }
. - as soon as you access another page for the first time or when reloading a page, e.g.,
/another-page
, then your state is at first{ routePath: '/another-page' }
and it remains as you travel through the page with vue router (SPA like navigation)
Or do you wish for the initial state to be different depending on which page is currently accessed (including navigation via vue router)?
- you access the page
/index
, then your initial state is{ routePath: '/index' }
. As you travel through the page with vue-router, your initial state changes - it would also change to the path of any page you are currently visiting. Both by using vue-router or reloading the page.
from vite-ssg.
@pantajoe The problem here is that we have an SPA
, not an MPA
, I'm really confusing about the behavior @husayt wants: remove the initial state from the router
on page transition will prevent the behavior, unless the state is shared between pages and just added
on each route, but then, my suggestion will break that, since the initialState
is created on each route.
If we have an isolated initialSate
for each page, then it will need to be loaded by the router (like vuex
scoped/namespaced modules where the module is shared between groups of pages and required to be loaded with the router hook without this
: beforeRouteEnter
)
from vite-ssg.
Exactly, that's why I'm confused, too. I implemented this initialState
feature because I needed something to be run once during SSG that's available in all pages, similar to the hook nuxtServerInit
when you are using Nuxt, i.e., one API to provide available languages, configuration, whatsoever, to the app and that's properly hydrated during static site generation.
Hence, I'm not sure if it makes sense to modify the initial state in such a way that @husayt can executed the provided code as intended.
The question here is what does getRouteSpecificState
do? Because I feel like this is another type of state
, not the initialState
, but the state of the page
, a bit similar to asyncData
in Nuxt.
from vite-ssg.
Yeah, on nuxt the vuex namespaced modules
equivalent is asyncData
(I don't know if it is using the beforeRouteEnter
hook but seems that should use it on each route or some internal hook on router).
The problems here are:
- we need a
global
initial state? - we need an
isolated
page state?
If we need a global
state, we'll need to do 2 loops instead one (on src/node/build.ts
, former to collect this global
state, and later to generate the pages with this global
state and page one, and should change the api to include both states.
If the global
state is not required, then my suggestion will work, but the router will need to restore the state between page transition, with or without global
state since this is not a MPA
.
from vite-ssg.
Maybe the first loop is not required since it is global, but ppl need to know if calling for global state to just initialize the global only on /
route (process.env.SSG
will be true and process.env.VITE_SSG
will be false).
I think we can add a callback to initialize this global
state instead doing it on the app
logic, it can be confusing.
@husayt what's exactly what you want? I think adding this 2 states will cover all cases.
from vite-ssg.
/cc @antfu sorry to bother you man, what do you think of this new proposal (read last 5 comments)?
from vite-ssg.
Let me address the issue from different end, to make it maybe more clear.
Let's say i have two pages a.vue
and b.vue
// src/main.ts
// ...
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, routes, isClient, initialState, routePath }) => {
// ...
if (import.meta.env.SSR) {
initialState.data = { name: routePath ==="/a" ? ''ALPHA" : "BETA" }
console.log('saving ' ,initialState.data)
} else {
console.log(initialState.data) // => { name:"ALPHA" } or { name:"BETA" }
}
// ...
}
)
what i expect is two files:
a.html
to contain this
window.__INITIAL_STATE__={name: "ALPHA"}
- and
b.html
to contain this
window.__INITIAL_STATE__={name: "BETA"}
The funny thing when i run build I see in console:
saving { name:"ALPHA" }
saving { name:"BETA" }
but when both htmls generated, both contain the same:
window.__INITIAL_STATE__={name: "ALPHA"}
this is clearly wrong and here is the issue originates from.
from vite-ssg.
@husayt so you need a page
scoped state, this will be addressed with my proposed changes, but you will need to load the page
state between page transitions, since the state is not global and so each page state will need to be restored.
from vite-ssg.
To handle also the global state, it requires to be included inside the import.meta.env.VITE_SSG
if statement, since the SSR
will be only used to load routes to traverse them on SSG
.
Maybe we can also include isSsg
and/or isSsr
on the context instead force ppl use process.env
or import.meta.env
stuff.
from vite-ssg.
I'm writing some ideas here not in project source code, we need to review it.
isClient
is initialized always with const isClient = typeof window !== 'undefined'
, see:
- https://github.com/antfu/vite-ssg/blob/main/src/client/index.ts#L22
- https://github.com/antfu/vite-ssg/blob/main/src/client/single-page.ts#L20
While process.env.SSR
and process.env.VITE_SSG
are initialized before index.html
build and entry.ts
build: on client side these 2 guys will be false and only true
when building
.
from vite-ssg.
@userquin Would there be a case when they are not the same ? i,e the following not true
import.meta.env.VITE_SSG === !isClient
from vite-ssg.
@userquin thank you for detailed explanation.
Seems really interesting. Where can I test your proposal? is there a pull request?
from vite-ssg.
@userquin this is exactly what was missing. Shall we do a pull request for that. This is the most useful one liner change ever.
from vite-ssg.
@husayt We are waiting for antfu input, you can do a PR but I think he is working on this. Maybe the global state can be also added merging the global with the page on a single store...
The important thing is that the router hook will be required to switch this inital state, I hope you finally undertood why I was confused with your comments about removing the router hook ;)
from vite-ssg.
This is such a complex topic, as we have to understand behavior of the same code and its derivates on different sides, I think very few have a clear idea about what is going on.
Lets wait for what @antfu comes with. But your changes at least fix the current issue, so I use them on my website now
from vite-ssg.
@husayt I'm trying to figure out how is that posible, I'm 99% sure that shouldn't work, on page transition without the hook the New page should have the previous page state: and if you do a hard refresh should work.
I mean without this hook https://github.com/antfu/vite-ssg/blob/main/examples/multiple-pages-with-store/src/main.ts#L24
from vite-ssg.
@pantajoe this comment is the problem, so the code seems to be ok, the state is global not per page: https://github.com/antfu/vite-ssg/blob/main/src/client/index.ts#L70
@husayt this issue is a New feat, if previous comment is correct.
from vite-ssg.
@userquin @husayt I just think that this has to be revised in its core as we discussed earlier in this thread.
We need a global initial state like the state initiated in nuxtServerInit
in Nuxt.js
We also need a state per page similar to asyncData
in Nuxt.js
Maybe it might be a good idea to use the same approach for both states.
@antfu Sorry to bother you, but I think we need some input on this 😅
from vite-ssg.
Otherwise I will try to modify my open PR #67 as I proposed. In the meantime, I don't see no harm if @husayt's PR is merged for now
from vite-ssg.
@pantajoe you are raising very important topic here, but the pull request I have is fixing what is clearly a bug and is better to be merged separately to allow more constructive discussion here.
from vite-ssg.
@userquin @husayt I just think that this has to be revised in its core as we discussed earlier in this thread.
We need a global initial state like the state initiated in
nuxtServerInit
in Nuxt.jsWe also need a state per page similar to
asyncData
in Nuxt.jsMaybe it might be a good idea to use the same approach for both states.
@antfu Sorry to bother you, but I think we need some input on this 😅
You can include a global state on your main.ts
and then do a merge with the page state
(the state passed on the cb is now the page state
and not the global) or just copying it via Object.assign({}, globalState)}
. If you have also page state
, just add it to previous assignment.
You can also do the equivalent nuxt asyncData
for page state
since the callback can be also async
:
fn?: (context: ViteSSGContext<true>) => Promise<void> | void,
from vite-ssg.
Related Issues (20)
- lottie-web build ssg 报错Ω HOT 1
- Support stripe-away slot of `ClientOnly` on SSG
- add quasar 2.14.0 with vite 5.0.0 pnpm build failed
- Inferred type of createApp HOT 2
- let headers = new Headers() returning undefined in @servicestack node modules
- Cannot import CommonJS module
- Implement optional route parameters
- issue with build
- Cannot access the right router HOT 1
- Can'not build with vue-select HOT 1
- SSG for just home page? HOT 1
- build exception when add jsx support and change all vue to jsx.
- re-rendering issues
- fetch gives 'ERR_INVALID_URL' HOT 1
- using build.rollupOptions.output.manualChunks
- Adding ClientOnly to any part of the App.Vue gives an empty error HOT 1
- html-minifier high security vulnerability HOT 5
- SyntaxError: Cannot use import statement outside a module HOT 3
- How to remove crossorigin from html tags? HOT 1
- Vite-ssg build error in SSR step HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from vite-ssg.