Skip to content

Commit

Permalink
Fix/ssr unsafe replace (#5094)
Browse files Browse the repository at this point in the history
  • Loading branch information
GiveMe-A-Name committed Dec 14, 2023
1 parent 03a3196 commit 563c286
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 45 deletions.
6 changes: 6 additions & 0 deletions .changeset/lazy-impalas-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modern-js/runtime': patch
---

fix: the Helment replace may meets special char
fix: the Helment 替换可能会遇到特殊字符,导致替换出问题
5 changes: 4 additions & 1 deletion packages/runtime/plugin-runtime/src/ssr/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
} from './serverRender/types';
import { WithCallback } from './react/withCallback';
import { formatClient, mockResponse, isReact18 } from './utils';
import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID } from './serverRender/utils';
import {
ROUTER_DATA_JSON_ID,
SSR_DATA_JSON_ID,
} from './serverRender/constants';

declare module '../core' {
interface SSRContainer {
Expand Down
11 changes: 11 additions & 0 deletions packages/runtime/plugin-runtime/src/ssr/serverRender/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const HTML_PLACEHOLDER = '<!--<?- html ?>-->';

export const SSR_DATA_PLACEHOLDER = '<!--<?- SSRDataScript ?>-->';

export const CHUNK_JS_PLACEHOLDER = '<!--<?- chunksMap.js ?>-->';

export const CHUNK_CSS_PLACEHOLDER = '<!--<?- chunksMap.css ?>-->';

export const SSR_DATA_JSON_ID = '__MODERN_SSR_DATA__';

export const ROUTER_DATA_JSON_ID = '__MODERN_ROUTER_DATA__';
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 用于 react-helmet 正则替换
import { EOL } from 'os';
import { HelmetData } from 'react-helmet';
import { unsafeReplace } from './utils';

const RE_HTML_ATTR = /<html[^>]*>/;
const RE_BODY_ATTR = /<body[^>]*>/;
Expand All @@ -14,12 +15,12 @@ export default function helmet(content: string, helmetData: HelmetData) {
let result = content;
const bodyAttributes = helmetData.bodyAttributes.toString();
if (bodyAttributes) {
result = result.replace(RE_BODY_ATTR, `<body ${bodyAttributes}>`);
result = unsafeReplace(result, RE_BODY_ATTR, `<body ${bodyAttributes}>`);
}

const htmlAttributes = helmetData.htmlAttributes.toString();
if (htmlAttributes) {
result = result.replace(RE_HTML_ATTR, `<html ${htmlAttributes}>`);
result = unsafeReplace(result, RE_HTML_ATTR, `<html ${htmlAttributes}>`);
}

const base = helmetData.base.toString();
Expand Down Expand Up @@ -50,5 +51,5 @@ export default function helmet(content: string, helmetData: HelmetData) {
return pre + (cur.length > 0 ? ` ${cur}${EOL}` : '');
}, '');

return result.replace(RE_LAST_IN_HEAD, `${helmetStr}</head>`);
return unsafeReplace(result, RE_LAST_IN_HEAD, `${helmetStr}</head>`);
}
4 changes: 2 additions & 2 deletions packages/runtime/plugin-runtime/src/ssr/serverRender/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

import { isReact18 } from '../utils';
import { ServerRenderOptions } from './types';
import { CSS_CHUNKS_PLACEHOLDER } from './utils';
import { CHUNK_JS_PLACEHOLDER } from './constants';

export default async function serverRender(options: ServerRenderOptions) {
if (options.context.ssrContext?.template) {
options.context.ssrContext.template =
options.context.ssrContext?.template.replace(
'</head>',
`${CSS_CHUNKS_PLACEHOLDER}</head>`,
`${CHUNK_JS_PLACEHOLDER}</head>`,
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { serializeJson } from '@modern-js/runtime-utils/node';
import { RenderLevel, RuntimeContext } from '../types';
import { attributesToString } from '../utils';
import { attributesToString, unsafeReplace } from '../utils';
import { SSR_DATA_PLACEHOLDER } from '../constants';
import { BuildTemplateCb, buildTemplate } from './buildTemplate.share';

type BuildShellAfterTemplateOptions = {
Expand All @@ -16,7 +17,8 @@ export function buildShellAfterTemplate(

function injectSSRDataScript(template: string) {
const ssrDataScript = buildSSRDataScript();
return template.replace('<!--<?- SSRDataScript ?>-->', ssrDataScript);

return unsafeReplace(template, SSR_DATA_PLACEHOLDER, ssrDataScript);

function buildSSRDataScript() {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import ReactHelmet, { HelmetData } from 'react-helmet';
import { matchRoutes } from '@modern-js/runtime-utils/router';
import helmetReplace from '../helmet';
import { RuntimeContext } from '../types';
import { CSS_CHUNKS_PLACEHOLDER } from '../utils';
import { CHUNK_CSS_PLACEHOLDER } from '../constants';
import { unsafeReplace } from '../utils';
import {
HEAD_REG_EXP,
BuildTemplateCb,
Expand All @@ -30,7 +31,7 @@ function getHeadTemplate(beforeEntryTemplate: string, context: RuntimeContext) {
return buildTemplate(headTemplate, callbacks);

function injectCss(headTemplate: string) {
return headTemplate.replace(CSS_CHUNKS_PLACEHOLDER, getCssChunks());
return unsafeReplace(headTemplate, CHUNK_CSS_PLACEHOLDER, getCssChunks());

function getCssChunks() {
const { routeManifest, routerContext, routes } = context;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
export type BuildHtmlCb = (tempalte: string) => string;
import { unsafeReplace } from '../utils';
import {
HTML_PLACEHOLDER,
SSR_DATA_PLACEHOLDER,
CHUNK_JS_PLACEHOLDER,
CHUNK_CSS_PLACEHOLDER,
} from '../constants';

/**
* It is unsafe unsafeReplace, only support serachValue exsit one time.
* @param source
* @param searchValue
* @param replaceValue
* @returns
*/
function unsafeReplace(
source: string,
searchValue: RegExp | string,
replaceValue: string,
) {
const [s1, s2] = source.split(searchValue);
return s1 + replaceValue + s2;
}
export type BuildHtmlCb = (tempalte: string) => string;

export function buildHtml(template: string, callbacks: BuildHtmlCb[]) {
return callbacks.reduce((tmp, cb) => cb(tmp), template);
}

export function createReplaceHtml(html: string): BuildHtmlCb {
const HTML_REMARK = '<!--<?- html ?>-->';
return (template: string) => unsafeReplace(template, HTML_REMARK, html);
return (template: string) => unsafeReplace(template, HTML_PLACEHOLDER, html);
}

export function createReplaceSSRDataScript(data: string): BuildHtmlCb {
const SSR_DATA_REMARK = '<!--<?- SSRDataScript ?>-->';
return (template: string) => unsafeReplace(template, SSR_DATA_REMARK, data);
return (template: string) =>
unsafeReplace(template, SSR_DATA_PLACEHOLDER, data);
}

export function createReplaceChunkJs(js: string): BuildHtmlCb {
const CHUNK_JS_REMARK = '<!--<?- chunksMap.js ?>-->';
return (template: string) => unsafeReplace(template, CHUNK_JS_REMARK, js);
return (template: string) =>
unsafeReplace(template, CHUNK_JS_PLACEHOLDER, js);
}

export function createReplaceChunkCss(css: string): BuildHtmlCb {
const CHUNK_CSS_REG = '<!--<?- chunksMap.css ?>-->';
return (template: string) => unsafeReplace(template, CHUNK_CSS_REG, css);
return (template: string) =>
unsafeReplace(template, CHUNK_CSS_PLACEHOLDER, css);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ import {
SSRServerContext,
} from '../types';
import prefetch from '../../prefetch';
import {
ROUTER_DATA_JSON_ID,
SSR_DATA_JSON_ID,
attributesToString,
} from '../utils';
import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID } from '../constants';
import { attributesToString } from '../utils';
import { SSRErrors, SSRTimings, SSRTracker } from '../tracker';
import { createLoadableCollector } from './loadable';
import { createRender } from './render';
Expand Down
22 changes: 16 additions & 6 deletions packages/runtime/plugin-runtime/src/ssr/serverRender/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
export const CSS_CHUNKS_PLACEHOLDER = '<!--<?- chunksMap.css ?>-->';

export const SSR_DATA_JSON_ID = '__MODERN_SSR_DATA__';

export const ROUTER_DATA_JSON_ID = '__MODERN_ROUTER_DATA__';

export function attributesToString(attributes: Record<string, any>) {
// Iterate through the properties and convert them into a string, only including properties that are not undefined.
return Object.entries(attributes).reduce((str, [key, value]) => {
return value === undefined ? str : `${str} ${key}="${value}"`;
}, '');
}

/**
* It is unsafe unsafeReplace, only support serachValue exsit one time.
* @param source
* @param searchValue
* @param replaceValue
* @returns
*/
export function unsafeReplace(
source: string,
searchValue: RegExp | string,
replaceValue: string,
) {
const [s1, s2] = source.split(searchValue);
return s1 + replaceValue + s2;
}

0 comments on commit 563c286

Please sign in to comment.