Skip to content

Commit

Permalink
fix(ssr): make sync mode default, fix it (#4869)
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanlawson authored Nov 15, 2024
1 parent d30539f commit cdbf06c
Show file tree
Hide file tree
Showing 15 changed files with 69 additions and 62 deletions.
4 changes: 2 additions & 2 deletions packages/@lwc/compiler/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { InstrumentationObject, CompilerValidationErrors, invariant } from '@lwc/errors';
import { isUndefined, isBoolean, getAPIVersionFromNumber } from '@lwc/shared';
import { isUndefined, isBoolean, getAPIVersionFromNumber, DEFAULT_SSR_MODE } from '@lwc/shared';
import { CompilationMode } from '@lwc/ssr-compiler';
import type { CustomRendererConfig } from '@lwc/template-compiler';

Expand Down Expand Up @@ -33,7 +33,7 @@ const DEFAULT_OPTIONS = {
disableSyntheticShadowSupport: false,
enableLightningWebSecurityTransforms: false,
targetSSR: false,
ssrMode: 'asyncYield',
ssrMode: DEFAULT_SSR_MODE,
} as const;

const DEFAULT_DYNAMIC_IMPORT_CONFIG: Required<DynamicImportConfig> = {
Expand Down
3 changes: 0 additions & 3 deletions packages/@lwc/perf-benchmarks-components/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));

const { tmpDir, styledComponents } = generateStyledComponents();

const SSR_MODE = 'asyncYield';

const ENGINE_TYPE_TO_LWC_IMPORT = {
dom: '@lwc/engine-dom',
server: '@lwc/engine-server',
Expand All @@ -36,7 +34,6 @@ function createConfig(componentFile, engineType) {
rootDir,
experimentalComplexExpressions: true,
targetSSR: engineType === 'ssr',
ssrMode: SSR_MODE,
}),
replace({
preventAssignment: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { renderComponent } from '@lwc/ssr-runtime';
import SlotUsage from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/slotUsageComponentLight/slotUsageComponentLight.js';
import Store from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/store/store.js';

const SSR_MODE = 'asyncYield';
const NUMBER_OF_ROWS = 5000;

benchmark(`ssr/slot/light/create/5k`, () => {
Expand All @@ -24,6 +23,6 @@ benchmark(`ssr/slot/light/create/5k`, () => {
titleOfComponentWithSlot: 'Component that receives a slot',
rowsOfComponentWithSlot: rowsOfComponentWithSlot,
};
return renderComponent('benchmark-slot-usage-component-light', SlotUsage, props, SSR_MODE);
return renderComponent('benchmark-slot-usage-component-light', SlotUsage, props);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { renderComponent } from '@lwc/ssr-runtime';
import SlotUsage from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/slotUsageComponent/slotUsageComponent.js';
import Store from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/store/store.js';

const SSR_MODE = 'asyncYield';
const NUMBER_OF_ROWS = 5000;

benchmark(`ssr/slot/shadow/create/5k`, () => {
Expand All @@ -24,6 +23,6 @@ benchmark(`ssr/slot/shadow/create/5k`, () => {
titleOfComponentWithSlot: 'Component that receives a slot',
rowsOfComponentWithSlot: rowsOfComponentWithSlot,
};
return renderComponent('benchmark-slot-usage-component', SlotUsage, props, SSR_MODE);
return renderComponent('benchmark-slot-usage-component', SlotUsage, props);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@ import { renderComponent } from '@lwc/ssr-runtime';
import Table from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/table/table.js';
import Store from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/store/store.js';

const SSR_MODE = 'asyncYield';

benchmark(`ssr/table-v2/render/10k`, () => {
run(() => {
const store = new Store();
store.runLots();

return renderComponent(
'benchmark-table',
Table,
{
rows: store.data,
},
SSR_MODE
);
return renderComponent('benchmark-table', Table, {
rows: store.data,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@ import { renderComponent } from '@lwc/ssr-runtime';
import Table from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/tableComponent/tableComponent.js';
import Store from '@lwc/perf-benchmarks-components/dist/ssr/benchmark/store/store.js';

const SSR_MODE = 'asyncYield';

benchmark(`ssr/table-component/render/10k`, () => {
run(() => {
const store = new Store();
store.runLots();

return renderComponent(
'benchmark-table',
Table,
{
rows: store.data,
},
SSR_MODE
);
return renderComponent('benchmark-table', Table, {
rows: store.data,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { renderComponent } from '@lwc/ssr-runtime';

const SSR_MODE = 'asyncYield';

// Generic benchmark for styled components, SSR-flavored!
export function styledComponentSsrBenchmark(
name,
Expand All @@ -17,8 +15,7 @@ export function styledComponentSsrBenchmark(
await renderComponent(
isArray ? `styled-component${i}` : 'styled-component',
isArray ? componentOrComponents[i] : componentOrComponents,
{},
SSR_MODE
{}
);
}
});
Expand Down
1 change: 1 addition & 0 deletions packages/@lwc/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export * from './static-part-tokens';
export * from './style';
export * from './signals';
export * from './custom-element';
export * from './ssr';

export { assert };
7 changes: 7 additions & 0 deletions packages/@lwc/shared/src/ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright (c) 2024, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
export const DEFAULT_SSR_MODE = 'sync';
3 changes: 2 additions & 1 deletion packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import lwcRollupPlugin from '@lwc/rollup-plugin';
import { FeatureFlagName } from '@lwc/features/dist/types';
import { testFixtureDir, formatHTML } from '@lwc/test-utils-lwc-internals';
import { serverSideRenderComponent } from '@lwc/ssr-runtime';
import { DEFAULT_SSR_MODE } from '@lwc/shared';
import { expectedFailures } from './utils/expected-failures';
import type { CompilationMode } from '../index';

Expand All @@ -38,7 +39,7 @@ vi.mock('@lwc/ssr-runtime', async () => {
return runtime;
});

const SSR_MODE: CompilationMode = 'asyncYield';
const SSR_MODE: CompilationMode = DEFAULT_SSR_MODE;

async function compileFixture({ input, dirname }: { input: string; dirname: string }) {
const modulesDir = path.resolve(dirname, './modules');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const expectedFailures = new Set([
'empty-text-with-comments-non-static-optimized/index.js',
'if-conditional-slot-content/index.js',
'known-boolean-attributes/default-def-html-attributes/static-on-component/index.js',
'rehydration/index.js',
'render-dynamic-value/index.js',
'scoped-slots/advanced/index.js',
'scoped-slots/expression/index.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const bYieldFromChildGenerator = esTemplateWithYield`
// The 'instance' variable is shadowed here so that a contextful relationship
// is established between components rendered in slotted content & the "parent"
// component that contains the <slot>.
shadow: async function* (instance) {
shadow: async function* generateSlottedContent(instance) {
${/* shadow slot content */ is.statement}
}
};
Expand Down Expand Up @@ -59,8 +59,11 @@ const bYieldFromChildGenerator = esTemplateWithYield`
}
`<EsBlockStatement>;

// Note that this function name (`generateSlottedContent`) does not need to be scoped even though
// it may be repeated multiple times in the same scope, because it's a function _expression_ rather
// than a function _declaration_, so it isn't available to be referenced anywhere.
const bAddContent = esTemplate`
addContent(${/* slot name */ is.expression} ?? "", async function* (${
addContent(${/* slot name */ is.expression} ?? "", async function* generateSlottedContent(${
/* scoped slot data variable */ isNullableOf(is.identifier)
}) {
// FIXME: make validation work again
Expand Down
6 changes: 3 additions & 3 deletions packages/@lwc/ssr-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { generateCustomElementTagName } from '@lwc/shared';
import { DEFAULT_SSR_MODE, generateCustomElementTagName } from '@lwc/shared';
import compileJS from './compile-js';
import compileTemplate from './compile-template';
import type { CompilationMode, TransformOptions } from './shared';
Expand All @@ -21,7 +21,7 @@ export function compileComponentForSSR(
src: string,
filename: string,
options: TransformOptions,
mode: CompilationMode = 'asyncYield'
mode: CompilationMode = DEFAULT_SSR_MODE
): CompilationResult {
const tagName = generateCustomElementTagName(options.namespace, options.name);
const { code } = compileJS(src, filename, tagName, mode);
Expand All @@ -32,7 +32,7 @@ export function compileTemplateForSSR(
src: string,
filename: string,
options: TransformOptions,
mode: CompilationMode = 'asyncYield'
mode: CompilationMode = DEFAULT_SSR_MODE
): CompilationResult {
const { code } = compileTemplate(src, filename, options, mode);
return { code, map: undefined };
Expand Down
58 changes: 38 additions & 20 deletions packages/@lwc/ssr-compiler/src/transmogrify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/
import { traverse, builders as b, type NodePath } from 'estree-toolkit';
import { produce } from 'immer';
import type { Node } from 'estree';
import type { FunctionDeclaration, FunctionExpression, Node } from 'estree';
import type { Program as EsProgram } from 'estree';
import type { Node as EstreeToolkitNode } from 'estree-toolkit/dist/helpers';

export type TransmogrificationMode = 'sync' | 'async';

Expand All @@ -20,14 +21,19 @@ export type Visitors = Parameters<typeof traverse<Node, TransmogrificationState>
const EMIT_IDENT = b.identifier('$$emit');
// Rollup may rename variables to prevent shadowing. When it does, it uses the format `foo$0`, `foo$1`, etc.
const TMPL_FN_PATTERN = /tmpl($\d+)?/;
const GEN_MARKUP_PATTERN = /generateMarkup($\d+)?/;
const GEN_MARKUP_OR_GEN_SLOTTED_CONTENT_PATTERN =
/(?:generateMarkup|generateSlottedContent)($\d+)?/;

const isWithinFn = (pattern: RegExp, nodePath: NodePath): boolean => {
const { node } = nodePath;
if (!node) {
return false;
}
if (node.type === 'FunctionDeclaration' && pattern.test(node.id.name)) {
if (
(node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
node.id &&
pattern.test(node.id.name)
) {
return true;
}
if (nodePath.parentPath) {
Expand All @@ -36,23 +42,32 @@ const isWithinFn = (pattern: RegExp, nodePath: NodePath): boolean => {
return false;
};

const visitors: Visitors = {
FunctionDeclaration(path, state) {
const { node } = path;
if (!node?.async || !node?.generator) {
return;
}
function transformFunction(
path: NodePath<FunctionDeclaration | FunctionExpression, EstreeToolkitNode>,
state: TransmogrificationState
): undefined {
const { node } = path;
if (!node?.async || !node?.generator) {
return;
}

// Component authors might conceivably use async generator functions in their own code. Therefore,
// when traversing & transforming written+generated code, we need to disambiguate generated async
// generator functions from those that were written by the component author.
if (!isWithinFn(GEN_MARKUP_PATTERN, path) && !isWithinFn(TMPL_FN_PATTERN, path)) {
return;
}
node.generator = false;
node.async = state.mode === 'async';
node.params.unshift(EMIT_IDENT);
},
// Component authors might conceivably use async generator functions in their own code. Therefore,
// when traversing & transforming written+generated code, we need to disambiguate generated async
// generator functions from those that were written by the component author.
if (
!isWithinFn(GEN_MARKUP_OR_GEN_SLOTTED_CONTENT_PATTERN, path) &&
!isWithinFn(TMPL_FN_PATTERN, path)
) {
return;
}
node.generator = false;
node.async = state.mode === 'async';
node.params.unshift(EMIT_IDENT);
}

const visitors: Visitors = {
FunctionDeclaration: transformFunction,
FunctionExpression: transformFunction,
YieldExpression(path, state) {
const { node } = path;
if (!node) {
Expand All @@ -62,7 +77,10 @@ const visitors: Visitors = {
// Component authors might conceivably use generator functions within their own code. Therefore,
// when traversing & transforming written+generated code, we need to disambiguate generated yield
// expressions from those that were written by the component author.
if (!isWithinFn(TMPL_FN_PATTERN, path) && !isWithinFn(GEN_MARKUP_PATTERN, path)) {
if (
!isWithinFn(TMPL_FN_PATTERN, path) &&
!isWithinFn(GEN_MARKUP_OR_GEN_SLOTTED_CONTENT_PATTERN, path)
) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/@lwc/ssr-runtime/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { getOwnPropertyNames, isNull, isString, isUndefined } from '@lwc/shared';
import { getOwnPropertyNames, isNull, isString, isUndefined, DEFAULT_SSR_MODE } from '@lwc/shared';
import { mutationTracker } from './mutation-tracker';
import {
LightningElement,
Expand Down Expand Up @@ -143,7 +143,7 @@ export async function serverSideRenderComponent(
tagName: string,
Component: GenerateMarkupFnVariants | ComponentWithGenerateMarkup,
props: Properties = {},
mode: 'asyncYield' | 'async' | 'sync' = 'asyncYield'
mode: 'asyncYield' | 'async' | 'sync' = DEFAULT_SSR_MODE
): Promise<string> {
if (typeof tagName !== 'string') {
throw new Error(`tagName must be a string, found: ${tagName}`);
Expand Down

0 comments on commit cdbf06c

Please sign in to comment.