Open MarianDabrowski opened 2 years ago
Context from my discovery & our discord conversation:
The web components are being defined/registered/detected correctly when served through Next.js. The style
tag for each web component is not being rendered though. There is an inline comment where the style blocks should be. This results in all the components being in a broken/styleless state.
The fact that it worked < v6, suggests it is an issue with the custom elements build.
I've confirmed that nothing in the Next.js project configuration points at an issue with the reproduction app, so my thoughts are that:
Hi there! Any updates on this topic?
This is also happening with Ionic Core loaded from a CDN, so possibly related: https://github.com/ionic-team/ionic-framework/issues/25398
Upon further discovery, this only effects components that have multiple stylesheets.
For instance:
<IonText color="danger">Hello world</IonText>
Renders correctly, since it only has one stylesheet, regardless of the mode.
Components that have stylesheets per-mode, seem to not create the constructed stylesheet correctly. I believe the source of this issue likely resides from Stencil's internals.
Edit:
Also confirmed that with a brand new Stencil component library making use of multiple stylesheets, it is not a problem within Next.js. This appears to be isolated to Ionic Framework.
Does the Stencil component library use the custom elements bundle or lazy loaded bundle?
Custom elements bundle, doing something similar to:
const App: React.FC = () => {
useEffect(() => {
import("stencil-nextjs/dist/components/my-component.js").then((x) => {
x.defineCustomElement();
});
}, []);
return (
<my-component first="Sean" last="Perkins" mode="md"></my-component>
);
};
Compared with setting the mode explicitly for our components, but still does not render with the stylesheet. I am curious if our logic to return early if window is not defined in initialize is related: https://github.com/ionic-team/ionic-framework/blob/main/core/src/global/ionic-global.ts#L17-L19
I love to see some updates on this topic! Please tell me, if there are any tasks that I can help you with. @sean-perkins awesome analysis on your side!
@sean-perkins getIonMode
should be falling back to defaultMode
: https://github.com/ionic-team/ionic-framework/blob/main/core/src/global/ionic-global.ts#L13, but I see that it is not set until here: https://github.com/ionic-team/ionic-framework/blob/main/core/src/global/ionic-global.ts#L60-L64
I wonder if we could make it so that the in-memory config is setup even if window
is not defined? That would at least let us have the mode get configured properly even if configFromSession
and configFromURL
won't work in SSR mode.
Something like this:
const configObj = {
persistConfig: false,
...userConfig,
};
config.reset(configObj);
if (config.getBoolean('persistConfig')) {
saveConfig(win, configObj);
}
// we can't call || isPlatform(win, 'ios') ? 'ios' : 'md' here because `window`
// is not necessarily available. Maybe we can re-update `defaultMode` in the
// if block below?
defaultMode = config.get(
'mode',
doc.documentElement.getAttribute('mode')
);
if (typeof window !== 'undefined') {
// everything else
}
@liamdebeasi as I understand, the consequences of this issue is that no meter the framework we cannot use SSR/SSG with ionic 6. Is it so?
Have a similar issue. Any progress? @liamdebeasi @sean-perkins
Something like this:
const configObj = { persistConfig: false, ...userConfig, }; config.reset(configObj); if (config.getBoolean('persistConfig')) { saveConfig(win, configObj); } // we can't call || isPlatform(win, 'ios') ? 'ios' : 'md' here because `window` // is not necessarily available. Maybe we can re-update `defaultMode` in the // if block below? defaultMode = config.get( 'mode', doc.documentElement.getAttribute('mode') ); if (typeof window !== 'undefined') { // everything else }
@liamdebeasi I wonder if you can elaborate on this, I tried to build it locally, but I failed to understand what changes are necessary.
The main issue is that when setting up the config we return early if no window
is available: https://github.com/ionic-team/ionic-framework/blob/main/core/src/global/ionic-global.ts#L18
This means that the in-memory config object is never created. As a result, certain things like the default mode are not defined. The in-memory config should be usable even if window
is not defined, so we likely need to re-arrange the initialize
function to not return early. Instead, we likely need to wrap the window-specific parts in if/else blocks so that the in-memory config is still created even in SSR/SSG environments.
@liamdebeasi thank you. for the response it has helped me much. I have played with the order and guess the one below may solve the issue.
export const initialize = (userConfig: IonicConfig = {}) => {
if (typeof (window as any) === 'undefined') {
Context.config = config;
const Ionic = {} as any;
const platformHelpers: any = {};
if (userConfig._ael) {
platformHelpers.ael = userConfig._ael;
}
if (userConfig._rel) {
platformHelpers.rel = userConfig._rel;
}
if (userConfig._ce) {
platformHelpers.ce = userConfig._ce;
}
setPlatformHelpers(platformHelpers);
const configObj = {
persistConfig: false,
...userConfig,
};
config.reset(configObj);
Ionic.config = config;
Ionic.mode = defaultMode = config.get(
'mode',
'md' // I assume 'md' is default
);
config.set('mode', defaultMode);
if (config.getBoolean('_testing')) {
config.set('animated', false);
}
setMode((_: any) => defaultMode);
}
else {
const doc = window.document;
const win = window;
Context.config = config;
const Ionic = ((win as any).Ionic = (win as any).Ionic || {});
const platformHelpers: any = {};
if (userConfig._ael) {
platformHelpers.ael = userConfig._ael;
}
if (userConfig._rel) {
platformHelpers.rel = userConfig._rel;
}
if (userConfig._ce) {
platformHelpers.ce = userConfig._ce;
}
setPlatformHelpers(platformHelpers);
// create the Ionic.config from raw config object (if it exists)
// and convert Ionic.config into a ConfigApi that has a get() fn
const configObj = {
...configFromSession(win),
persistConfig: false,
...Ionic.config,
...configFromURL(win),
...userConfig,
};
config.reset(configObj);
if (config.getBoolean('persistConfig')) {
saveConfig(win, configObj);
}
// Setup platforms
setupPlatforms(win);
// first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by pre-rendering
// otherwise get the mode via config settings, and fallback to md
Ionic.config = config;
Ionic.mode = defaultMode = config.get(
'mode',
doc.documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md')
);
config.set('mode', defaultMode);
doc.documentElement.setAttribute('mode', defaultMode);
doc.documentElement.classList.add(defaultMode);
if (config.getBoolean('_testing')) {
config.set('animated', false);
}
const isIonicElement = (elm: any) => elm.tagName?.startsWith('ION-');
const isAllowedIonicModeValue = (elmMode: string) => ['ios', 'md'].includes(elmMode);
setMode((elm: any) => {
while (elm) {
const elmMode = (elm as any).mode || elm.getAttribute('mode');
if (elmMode) {
if (isAllowedIonicModeValue(elmMode)) {
return elmMode;
} else if (isIonicElement(elm)) {
console.warn('Invalid ionic mode: "' + elmMode + '", expected: "ios" or "md"');
}
}
elm = elm.parentElement;
}
return defaultMode;
});
}
};
Are there any docs that describe how to build ionic so that I can check if it works with nextjs SSR/SSG?
@MarianDabrowski our contributing guide includes steps for building both core
and the individual framework packages: https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#building-changes
You will want to:
npm run build
from core/
npm run build
from /packages/react-router
(you may need to install dependencies in this directory if it is your first time)npm run build
from /packages/react
(you may need to install dependencies in this directory if it is your first time)npm pack
from /packages/react
npm install
the path of the .tgz@sean-perkins thank you! I will do it!
I have just tested the proposed idea and unfortunately the app does not work as expected. I have followed the steps to get the package (during npm i have used --legacy-peer-deps
, as there was some conflict with tslint), the experiment result is here: https://github.com/MarianDabrowski/next-app/tree/experiment/changes-inonic-core-initialize
The problem appears to be that the style
variable in Stencil's initializeComponent
function is undefined: https://github.com/ionic-team/stencil/blob/63dbb47a14cc840c8d37f1bf7ce315d306194788/src/runtime/initialize-component.ts#L95
Taking ion-button
as an example, the stylesheet strings do appear to be loaded. The computeMode
function in that initializeComponent
function returns undefined
, so Stencil does not grab the correct stylesheet string. Understanding why computeMode
cannot determine the mode is likely the key to fixing this issue.
Editing the file and then saving it "fixes" the issue as the styles get loaded.
There are a couple problems here:
initialize
function returns early, causing the config + mode to never be set up.
We return early if the window
is not defined: https://github.com/ionic-team/ionic-framework/blob/1b1b1a3800c4d044b4a3e7418f534e9271770ec6/core/src/global/ionic-global.ts#L17While this prevents the config from being setup, it also prevents a critical piece of Stencil's "mode" infrastructure from being initialized: https://github.com/ionic-team/ionic-framework/blob/1b1b1a3800c4d044b4a3e7418f534e9271770ec6/core/src/global/ionic-global.ts#L76
This function is what automatically determines which mode to apply to the components. Since no mode could be determined, no stylesheets are loaded for components with mode-specific stylesheets. (See https://github.com/ionic-team/ionic-framework/issues/25100#issuecomment-1172553894)
setMode
is called too late in a Next.js app.
Even if we reconfigured the initialize
function to move setMode
all the way to the top, that would not completely fix the issue. The initialize
function appears to be getting called after Stencil tries to apply the stylesheets.This code reproduces the issue in Next.js:
import { setupIonicReact, IonApp, IonButton } from "@ionic/react";
setupIonicReact();
const App: React.FC = () => (
<IonApp>
<IonButton color="danger">Cick me</IonButton>
</IonApp>
);
export default App;
If you were to place the same code in a Create React App/Webpack React app, the styles would get loaded because initialize
is called before Stencil tries to apply the stylesheets. This difference needs to be investigated more.
Are there any updates?
Our team is having the same exact issue, but we're trying to integrate our Stencil component library's React wrapper (reactOutputTarget
). So that we can use React-wrapped versions of our components in NextJS.
We've tried several iterations of styled-components
setups.
Has anyone found any possible solutions or ideas that we can try?
@romfilippini-gp I believe the issue is related to this https://github.com/ionic-team/stencil-ds-output-targets/issues/323
Wow, calling setupIonicReact()
worked for me.
Wow, calling
setupIonicReact()
worked for me.
@qbx2 Could you please post a sample of how you did it in your project?
@romfilippini-gp I was trying to setup nextjs+tailwind+ionic+capacitor from scratch. Without setupIonicReact()
, ionic css didn't apply. The example is here: https://github.com/mlynch/nextjs-tailwind-ionic-capacitor-starter/blob/main/components/AppShell.jsx#L14 . Now I have nextjs+tailwind+ionic+capacitor with latest version
Any update with Ionic 7? Thanks!
Bump
Prerequisites
Ionic Framework Version
Current Behavior
SSR/SSG of Nextjs do not work with ionic/react: "^6.0.0". The version worked well with ionic/react 5. I have reached out to ionic community in discord and was suggested creating the issue. The styles do not apply the behaviour is odd when we use SSR or SSG. It works well on client side though.
Expected Behavior
I expect the code to work similar as it worked on version 5.9.3. All the styles do apply there.
Steps to Reproduce
Code Reproduction URL
https://github.com/MarianDabrowski/next-app
Ionic Info
[WARN] You are not in an Ionic project directory. Project context may be missing.
Ionic:
Ionic CLI : 6.19.0
Utility:
cordova-res : 0.15.4 native-run : not installed globally
System:
NodeJS : v17.3.1 npm : 8.5.3 OS : macOS Monterey
Additional Information
Based on the discussion with @sean-perkins I believe Sean is right saying: "...custom elements should workin SSR/SSG, so the fact that it isn't, is a little concerning. The root issue is likely Stencil, if the styles are not being applied, but I would recommend creating a ticket in ionic-framework, since the UI kit should support Next.js."
_app.tsx is rendered before each page render As i understand setupIonicReact shall be called at the initial stage.
Altering pages/index.tsx to
makes the code work properly