Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support auto encode data #6540

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/basic-project/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default defineAppConfig(() => ({
app: {
rootId: 'app',
},
encodeData: true,
}));

export const dataLoader = defineDataLoader(() => {
Expand Down
7 changes: 5 additions & 2 deletions packages/runtime/src/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { WindowContext, RouteMatch, AssetsManifest } from './types.js';
import { useAppContext, useAppData } from './AppContext.js';
import { getMeta, getTitle, getLinks, getScripts } from './routesConfig.js';
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
import { encodeWindowContext } from './utils/formatWindowContext.js';

interface DocumentContext {
main: React.ReactNode | null;
Expand Down Expand Up @@ -162,7 +163,8 @@ export type DataType = (props: DataProps) => JSX.Element;

// use app context separately
export const Data: DataType = (props: DataProps) => {
const { documentOnly, matches, downgrade, renderMode, serverData, loaderData, revalidate } = useAppContext();
const { documentOnly, matches, downgrade, renderMode, serverData, loaderData, revalidate, appConfig } = useAppContext();
const { encodeData } = appConfig;
const appData = useAppData();
const {
ScriptElement = 'script',
Expand All @@ -171,6 +173,7 @@ export const Data: DataType = (props: DataProps) => {
const matchedIds = matches.map(match => match.route.id);
const routePath = matches.length > 0 ? encodeURI(getCurrentRoutePath(matches)) : '';
const windowContext: WindowContext = {
encodeData,
appData,
loaderData,
routePath,
Expand All @@ -187,7 +190,7 @@ export const Data: DataType = (props: DataProps) => {
// Should merge global context when there are multiple <Data />.
<ScriptElement
suppressHydrationWarning={documentOnly}
dangerouslySetInnerHTML={{ __html: `!(function () {var a = window.__ICE_APP_CONTEXT__ || {};var b = ${JSON.stringify(windowContext)};for (var k in a) {b[k] = a[k]}window.__ICE_APP_CONTEXT__=b;})();` }}
dangerouslySetInnerHTML={{ __html: `!(function () {var a = window.__ICE_APP_CONTEXT__ || {};var b = ${JSON.stringify(encodeData ? encodeWindowContext(windowContext) : windowContext)};for (var k in a) {b[k] = a[k]}window.__ICE_APP_CONTEXT__=b;})();` }}
/>
);
};
Expand Down
11 changes: 6 additions & 5 deletions packages/runtime/src/Suspense.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Request = (ctx: RequestContext) => Promise<any>;

const SuspenseContext = React.createContext<SuspenseState | undefined>(undefined);

const getHydrateData = (id: string) => {
const getHydrateData = (id: string, encodeData: boolean) => {
let data = null;
const loaderData = isClient && window[LOADER];
let hasHydrateData: boolean;
Expand All @@ -43,17 +43,17 @@ const getHydrateData = (id: string) => {
}
return {
hasHydrateData,
data,
data: encodeData ? JSON.parse(decodeURI(data)) : data,
};
};

export function useSuspenseData(request?: Request) {
const appContext = useAppContext();
const { requestContext } = appContext;
const { requestContext, appConfig } = appContext;
const suspenseState = React.useContext(SuspenseContext);

const { data, done, promise, update, error, id } = suspenseState;
const { hasHydrateData, data: hydrateData } = getHydrateData(id);
const { hasHydrateData, data: hydrateData } = getHydrateData(id, appConfig.encodeData);

let thenable: Promise<any> = null;
if (!hasHydrateData && !error && !done && !promise && request) {
Expand Down Expand Up @@ -163,9 +163,10 @@ export function withSuspense(Component) {
}

function Data(props) {
const { appConfig } = useAppContext();
const data = useSuspenseData();

return (
<script dangerouslySetInnerHTML={{ __html: `!function(){window['${LOADER}'] = window['${LOADER}'] || {};window['${LOADER}']['${props.id}'] = ${JSON.stringify(data)}}();` }} />
<script dangerouslySetInnerHTML={{ __html: `!function(){window['${LOADER}'] = window['${LOADER}'] || {};window['${LOADER}']['${props.id}'] = ${appConfig.encodeData ? encodeURI(JSON.stringify(data)) : JSON.stringify(data)}}();` }} />
);
}
4 changes: 3 additions & 1 deletion packages/runtime/src/dataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
RuntimeModules, StaticRuntimePlugin, CommonJsRuntime,
Loader, DataLoaderResult, StaticDataLoader, DataLoaderConfig, DataLoaderOptions,
} from './types.js';
import { decodeWindowContext } from './utils/formatWindowContext.js';
interface Loaders {
[routeId: string]: DataLoaderConfig;
}
Expand Down Expand Up @@ -167,7 +168,8 @@ const cache = new Map<string, CachedResult>();
* Start getData once data-loader.js is ready in client, and set to cache.
*/
function loadInitialDataInClient(loaders: Loaders) {
const context = (window as any).__ICE_APP_CONTEXT__ || {};
const windowContext = (window as any).__ICE_APP_CONTEXT__ || {};
const context = windowContext.encodeData ? decodeWindowContext((window as any).__ICE_APP_CONTEXT__ || {}) : windowContext;
const matchedIds = context.matchedIds || [];
const loaderData = context.loaderData || {};
const { renderMode } = context;
Expand Down
7 changes: 4 additions & 3 deletions packages/runtime/src/runClientApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import addLeadingSlash from './utils/addLeadingSlash.js';
import { AppContextProvider } from './AppContext.js';
import { deprecatedHistory } from './utils/deprecatedHistory.js';
import reportRecoverableError from './reportRecoverableError.js';
import { decodeWindowContext } from './utils/formatWindowContext.js';

export type CreateRoutes = (options: Pick<RouteLoaderOptions, 'renderMode' | 'requestContext'>) => RouteItem[];

Expand Down Expand Up @@ -49,7 +50,7 @@ export default async function runClientApp(options: RunClientAppOptions) {
dataLoaderFetcher,
dataLoaderDecorator,
} = options;

const appConfig = getAppConfig(app);
const windowContext: WindowContext = (window as any).__ICE_APP_CONTEXT__ || {};
const assetsManifest: AssetsManifest = (window as any).__ICE_ASSETS_MANIFEST__ || {};
let {
Expand All @@ -61,10 +62,10 @@ export default async function runClientApp(options: RunClientAppOptions) {
renderMode,
serverData,
revalidate,
} = windowContext;
} = appConfig.encodeData ? decodeWindowContext(windowContext) : windowContext;
const formattedBasename = addLeadingSlash(basename);
const requestContext = getRequestContext(window.location);
const appConfig = getAppConfig(app);

const routes = createRoutes ? createRoutes({
requestContext,
renderMode: 'CSR',
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface AppConfig {
basename?: string;
initialEntries?: InitialEntry[];
};
encodeData?: boolean;
}

export interface RoutesConfig {
Expand Down Expand Up @@ -126,7 +127,7 @@ AppContext,
export type WindowContext = Pick<
AppContext,
'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' | 'revalidate'
>;
> & Pick<AppContext['appConfig'], 'encodeData'>;

export type Renderer = (
container: Element | Document,
Expand Down
27 changes: 27 additions & 0 deletions packages/runtime/src/utils/formatWindowContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { WindowContext } from '../types.js';

const DATA_KEYS = ['appData', 'loaderData', 'serverData'];

export const encodeWindowContext = (context: WindowContext) => {
const encodedContext = {};
Object.keys(context).forEach((key) => {
if (DATA_KEYS.includes(key) && context[key]) {
encodedContext[key] = encodeURI(JSON.stringify(context[key]));
} else {
encodedContext[key] = context[key];
}
});
return encodedContext;
};

export const decodeWindowContext = (context: WindowContext) => {
const decodedContext = {};
Object.keys(context).forEach((key) => {
if (DATA_KEYS.includes(key) && typeof context[key] === 'string') {
decodedContext[key] = JSON.parse(decodeURI(context[key]));
} else {
decodedContext[key] = context[key];
}
});
return decodedContext as WindowContext;
};