@lwc/ssr-compiler 8.5.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,9 +14,15 @@ type RenderCall = CallExpression & {
14
14
  callee: RenderMemberExpression;
15
15
  };
16
16
  /** Returns `true` if the node is an identifier or `<something>.render()`. */
17
- export declare const isIdentOrRenderCall: (node: Node | null | undefined) => node is Identifier | RenderCall;
17
+ export declare const isIdentOrRenderCall: {
18
+ (node: Node | null | undefined): node is Identifier | RenderCall;
19
+ __debugName: string;
20
+ };
18
21
  /** A validator that returns `true` if the node is `null`. */
19
22
  type NullableChecker<T extends Node> = (node: Node | null | undefined) => node is T | null;
20
23
  /** Extends a validator to return `true` if the node is `null`. */
21
24
  export declare function isNullableOf<T extends Node>(validator: Checker<T>): NullableChecker<T>;
25
+ export declare namespace isNullableOf {
26
+ var __debugName: string;
27
+ }
22
28
  export {};
package/dist/index.cjs.js CHANGED
@@ -11,9 +11,9 @@ var meriyah = require('meriyah');
11
11
  var immer = require('immer');
12
12
  var node_path = require('node:path');
13
13
  var acorn = require('acorn');
14
+ var shared = require('@lwc/shared');
14
15
  var templateCompiler = require('@lwc/template-compiler');
15
16
  var builders = require('estree-toolkit/dist/builders');
16
- var shared = require('@lwc/shared');
17
17
  var util = require('util');
18
18
 
19
19
  /*
@@ -297,10 +297,13 @@ const getReplacementNode = (state, placeholderId) => {
297
297
  !(Array.isArray(replacementNode)
298
298
  ? replacementNode.every(validateReplacement)
299
299
  : validateReplacement(replacementNode))) {
300
- const nodeType = Array.isArray(replacementNode)
300
+ const expectedType = validateReplacement.__debugName ||
301
+ validateReplacement.name ||
302
+ '(could not determine)';
303
+ const actualType = Array.isArray(replacementNode)
301
304
  ? `[${replacementNode.map((n) => n.type)}.join(', ')]`
302
305
  : replacementNode?.type;
303
- throw new Error(`Validation failed for templated node of type ${nodeType}`);
306
+ throw new Error(`Validation failed for templated node. Expected type ${expectedType}, but received ${actualType}.`);
304
307
  }
305
308
  return replacementNode;
306
309
  };
@@ -431,6 +434,24 @@ const isIdentOrRenderCall = (node) => {
431
434
  estreeToolkit.is.identifier(node.callee.property) &&
432
435
  node.callee.property.name === 'render'));
433
436
  };
437
+ isIdentOrRenderCall.__debugName = 'identifier or .render() call';
438
+ /** Extends a validator to return `true` if the node is `null`. */
439
+ function isNullableOf(validator) {
440
+ const nullableValidator = (node) => {
441
+ return node === null || validator(node);
442
+ };
443
+ if (process.env.NODE_ENV !== 'production') {
444
+ nullableValidator.__debugName = `nullable(${validator.__debugName || validator.name || 'unknown validator'})`;
445
+ }
446
+ return nullableValidator;
447
+ }
448
+ isNullableOf.__debugName = 'isNullableOf';
449
+ if (process.env.NODE_ENV !== 'production') {
450
+ // Modifying another package's exports is a code smell!
451
+ for (const [key, val] of shared.entries(estreeToolkit.is)) {
452
+ val.__debugName = key;
453
+ }
454
+ }
434
455
 
435
456
  /*
436
457
  * Copyright (c) 2024, salesforce.com, inc.
@@ -1787,6 +1808,12 @@ var DiagnosticLevel;
1787
1808
  level: DiagnosticLevel.Error,
1788
1809
  url: '',
1789
1810
  },
1811
+ IGNORED_SLOT_ATTRIBUTE_IN_CHILD: {
1812
+ code: 1201,
1813
+ message: 'The slot attribute in {0} will be ignored due to its ancestor {1}. It must be a direct child of the containing component.',
1814
+ level: DiagnosticLevel.Warning,
1815
+ url: '',
1816
+ },
1790
1817
  });
1791
1818
 
1792
1819
  /**
@@ -1799,7 +1826,7 @@ var CompilerMetrics;
1799
1826
  CompilerMetrics["LWCSpreadDirective"] = "lwc-spread-directive";
1800
1827
  CompilerMetrics["DynamicImportTransform"] = "dynamic-import-transform";
1801
1828
  })(CompilerMetrics || (CompilerMetrics = {}));
1802
- /** version: 8.5.0 */
1829
+ /** version: 8.6.0 */
1803
1830
 
1804
1831
  /*
1805
1832
  * Copyright (c) 2024, Salesforce, Inc.
@@ -1976,13 +2003,16 @@ const bYieldFromChildGenerator = (esTemplateWithYield `
1976
2003
  slottedContent.light[name] = [fn]
1977
2004
  }
1978
2005
  }
1979
- ${ /* addContent statements */estreeToolkit.is.callExpression}
2006
+ ${ /* light DOM addContent statements */estreeToolkit.is.callExpression}
2007
+ ${ /* scoped slot addContent statements */estreeToolkit.is.callExpression}
1980
2008
  yield* ${estreeToolkit.is.identifier}(${estreeToolkit.is.literal}, childProps, childAttrs, slottedContent);
1981
2009
  }
1982
2010
  `);
1983
2011
  const bAddContent = (esTemplate `
1984
- addContent(${ /* slot name */estreeToolkit.is.expression} ?? "", async function* () {
1985
- ${ /* slot content */estreeToolkit.is.statement}
2012
+ addContent(${ /* slot name */estreeToolkit.is.expression} ?? "", async function* (${
2013
+ /* scoped slot data variable */ isNullableOf(estreeToolkit.is.identifier)}) {
2014
+ // FIXME: make validation work again
2015
+ ${ /* slot content */false}
1986
2016
  });
1987
2017
  `);
1988
2018
  const bImportGenerateMarkup = (localName, importPath) => estreeToolkit.builders.importDeclaration([estreeToolkit.builders.importSpecifier(estreeToolkit.builders.identifier('generateMarkup'), estreeToolkit.builders.identifier(localName))], estreeToolkit.builders.literal(importPath));
@@ -2006,20 +2036,6 @@ function getChildAttrsOrProps(attrs, cxt) {
2006
2036
  });
2007
2037
  return estreeToolkit.builders.objectExpression(objectAttrsOrProps);
2008
2038
  }
2009
- function reflectAriaPropsAsAttrs(props) {
2010
- return props
2011
- .map((prop) => {
2012
- if (prop.attributeName.startsWith('aria-') || prop.attributeName === 'role') {
2013
- return {
2014
- type: 'Attribute',
2015
- name: prop.attributeName,
2016
- value: prop.value,
2017
- };
2018
- }
2019
- return null;
2020
- })
2021
- .filter((el) => el !== null);
2022
- }
2023
2039
  const Component = function Component(node, cxt) {
2024
2040
  // Import the custom component's generateMarkup export.
2025
2041
  const childGeneratorLocalName = `generateMarkup_${templateCompiler.toPropertyName(node.name)}`;
@@ -2027,25 +2043,35 @@ const Component = function Component(node, cxt) {
2027
2043
  const componentImport = bImportGenerateMarkup(childGeneratorLocalName, importPath);
2028
2044
  cxt.hoist(componentImport, childGeneratorLocalName);
2029
2045
  const childTagName = node.name;
2030
- const attributes = [...node.attributes, ...reflectAriaPropsAsAttrs(node.properties)];
2031
- const shadowSlotContent = optimizeAdjacentYieldStmts(irChildrenToEs(node.children, cxt));
2032
- const lightSlotContent = node.children.map((child) => {
2046
+ // Anything inside the slotted content is a normal slotted content except for `<template lwc:slot-data>` which is a scoped slot.
2047
+ const slottableChildren = node.children.filter((child) => child.type !== 'ScopedSlotFragment');
2048
+ const scopedSlottableChildren = node.children.filter((child) => child.type === 'ScopedSlotFragment');
2049
+ const shadowSlotContent = optimizeAdjacentYieldStmts(irChildrenToEs(slottableChildren, cxt));
2050
+ const lightSlotContent = slottableChildren.map((child) => {
2033
2051
  if ('attributes' in child) {
2034
2052
  const slotName = bAttributeValue(child, 'slot');
2035
- // FIXME: We don't know what happens for slot attributes inside an lwc:if block
2036
2053
  // Light DOM slots do not actually render the `slot` attribute.
2037
2054
  const clone = immer.produce(child, (draft) => {
2038
2055
  draft.attributes = draft.attributes.filter((attr) => attr.name !== 'slot');
2039
2056
  });
2040
2057
  const slotContent = irToEs(clone, cxt);
2041
- return bAddContent(slotName, slotContent);
2058
+ return bAddContent(slotName, null, slotContent);
2042
2059
  }
2043
2060
  else {
2044
- return bAddContent(estreeToolkit.builders.literal(''), irToEs(child, cxt));
2061
+ return bAddContent(estreeToolkit.builders.literal(''), null, irToEs(child, cxt));
2045
2062
  }
2046
2063
  });
2064
+ const scopedSlotContent = scopedSlottableChildren.map((child) => {
2065
+ const boundVariableName = child.slotData.value.name;
2066
+ const boundVariable = estreeToolkit.builders.identifier(boundVariableName);
2067
+ cxt.pushLocalVars([boundVariableName]);
2068
+ // TODO [#4768]: what if the bound variable is `generateMarkup` or some framework-specific identifier?
2069
+ const addContentExpr = bAddContent(child.slotName, boundVariable, irChildrenToEs(child.children, cxt));
2070
+ cxt.popLocalVars();
2071
+ return addContentExpr;
2072
+ });
2047
2073
  return [
2048
- bYieldFromChildGenerator(getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(attributes, cxt), shadowSlotContent, lightSlotContent, estreeToolkit.builders.identifier(childGeneratorLocalName), estreeToolkit.builders.literal(childTagName)),
2074
+ bYieldFromChildGenerator(getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(node.attributes, cxt), shadowSlotContent, lightSlotContent, scopedSlotContent, estreeToolkit.builders.identifier(childGeneratorLocalName), estreeToolkit.builders.literal(childTagName)),
2049
2075
  ];
2050
2076
  };
2051
2077
 
@@ -2091,6 +2117,9 @@ const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
2091
2117
  }
2092
2118
  }
2093
2119
  `);
2120
+ const bYieldSanitizedHtml = esTemplateWithYield `
2121
+ yield sanitizeHtmlContent(${ /* lwc:inner-html content */estreeToolkit.is.expression})
2122
+ `;
2094
2123
  function yieldAttrOrPropLiteralValue(name, valueNode, isClass) {
2095
2124
  const { value, type } = valueNode;
2096
2125
  if (typeof value === 'string') {
@@ -2143,10 +2172,15 @@ const Element = function Element(node, cxt) {
2143
2172
  let hasClassAttribute = false;
2144
2173
  const yieldAttrsAndProps = attrsAndProps.flatMap((attr) => {
2145
2174
  const { name, value, type } = attr;
2146
- // For classes, these may need to be prefixed with the scope token
2147
- const isClass = type === 'Attribute' && name === 'class';
2148
- if (isClass) {
2149
- hasClassAttribute = true;
2175
+ let isClass = false;
2176
+ if (type === 'Attribute') {
2177
+ if (name === 'inner-h-t-m-l' || name === 'outer-h-t-m-l') {
2178
+ throw new Error(`Cannot set attribute "${name}" on <${node.name}>.`);
2179
+ }
2180
+ else if (name === 'class') {
2181
+ isClass = true;
2182
+ hasClassAttribute = true;
2183
+ }
2150
2184
  }
2151
2185
  cxt.hoist(bImportHtmlEscape(), importHtmlEscapeKey);
2152
2186
  if (value.type === 'Literal') {
@@ -2168,7 +2202,8 @@ const Element = function Element(node, cxt) {
2168
2202
  else if (innerHtmlDirective) {
2169
2203
  const value = innerHtmlDirective.value;
2170
2204
  const unsanitizedHtmlExpression = value.type === 'Literal' ? estreeToolkit.builders.literal(value.value) : expressionIrToEs(value, cxt);
2171
- childContent = [bYield$1(unsanitizedHtmlExpression)];
2205
+ childContent = [bYieldSanitizedHtml(unsanitizedHtmlExpression)];
2206
+ cxt.hoist(bImportDeclaration(['sanitizeHtmlContent']), 'import:sanitizeHtmlContent');
2172
2207
  }
2173
2208
  else {
2174
2209
  childContent = [];
@@ -2294,13 +2329,19 @@ const IfBlock = function IfBlock(node, cxt) {
2294
2329
  */
2295
2330
  const bConditionalSlot = (esTemplateWithYield `
2296
2331
  if (isLightDom) {
2297
- // start bookend HTML comment
2332
+ const isScopedSlot = ${ /* isScopedSlot */estreeToolkit.is.literal};
2333
+ // start bookend HTML comment for light DOM slot vfragment
2298
2334
  yield '<!---->';
2299
2335
 
2336
+ // scoped slot factory has its own vfragment hence its own bookend
2337
+ if (isScopedSlot) {
2338
+ yield '<!---->';
2339
+ }
2340
+
2300
2341
  const generators = slottedContent?.light[${ /* slotName */estreeToolkit.is.expression} ?? ""];
2301
2342
  if (generators) {
2302
2343
  for (const generator of generators) {
2303
- yield* generator();
2344
+ yield* generator(${ /* scoped slot data */isNullableOf(estreeToolkit.is.expression)});
2304
2345
  }
2305
2346
  } else {
2306
2347
  // If we're in this else block, then the generator _must_ have yielded
@@ -2310,19 +2351,29 @@ const bConditionalSlot = (esTemplateWithYield `
2310
2351
  // TODO: default/fallback slot content
2311
2352
  ${ /* slot fallback content */estreeToolkit.is.statement}
2312
2353
  }
2354
+
2355
+ // scoped slot factory has its own vfragment hence its own bookend
2356
+ if (isScopedSlot) {
2357
+ yield '<!---->';
2358
+ }
2313
2359
 
2314
- // end bookend HTML comment
2360
+ // end bookend HTML comment for light DOM slot vfragment
2315
2361
  yield '<!---->';
2316
2362
  } else {
2317
2363
  ${ /* slot element AST */estreeToolkit.is.statement}
2318
2364
  }
2319
2365
  `);
2320
2366
  const Slot = function Slot(node, ctx) {
2367
+ const slotBindDirective = node.directives.find((dir) => dir.name === 'SlotBind');
2368
+ const slotBound = slotBindDirective?.value
2369
+ ? getScopedExpression(slotBindDirective.value, ctx)
2370
+ : null;
2321
2371
  const slotName = bAttributeValue(node, 'name');
2322
2372
  // FIXME: avoid serializing the slot's children twice
2323
2373
  const slotAst = Element(node, ctx);
2324
2374
  const slotChildren = irChildrenToEs(node.children, ctx);
2325
- return [bConditionalSlot(slotName, slotChildren, slotAst)];
2375
+ const isScopedSlot = estreeToolkit.builders.literal(Boolean(slotBound));
2376
+ return [bConditionalSlot(isScopedSlot, slotName, slotBound, slotChildren, slotAst)];
2326
2377
  };
2327
2378
 
2328
2379
  /*
@@ -2572,5 +2623,5 @@ function compileTemplateForSSR(src, filename, options, mode = 'asyncYield') {
2572
2623
 
2573
2624
  exports.compileComponentForSSR = compileComponentForSSR;
2574
2625
  exports.compileTemplateForSSR = compileTemplateForSSR;
2575
- /** version: 8.5.0 */
2626
+ /** version: 8.6.0 */
2576
2627
  //# sourceMappingURL=index.cjs.js.map