@lwc/ssr-compiler 8.5.0 → 8.7.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,4 +14,6 @@ export interface ComponentMetaState {
14
14
  tmplExplicitImports: Map<string, string> | null;
15
15
  cssExplicitImports: Map<string, string> | null;
16
16
  staticStylesheetIds: Set<string> | null;
17
+ publicFields: Array<string>;
18
+ privateFields: Array<string>;
17
19
  }
@@ -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.
@@ -440,7 +461,13 @@ const isIdentOrRenderCall = (node) => {
440
461
  */
441
462
  const bGenerateMarkup = (esTemplate `
442
463
  export async function* generateMarkup(tagName, props, attrs, slotted) {
443
- attrs = attrs ?? {};
464
+ attrs = attrs ?? Object.create(null);
465
+ props = props ?? Object.create(null);
466
+ props = __filterProperties(
467
+ props,
468
+ ${ /*public fields*/estreeToolkit.is.arrayExpression},
469
+ ${ /*private fields*/estreeToolkit.is.arrayExpression},
470
+ );
444
471
  const instance = new ${ /* Component class */estreeToolkit.is.identifier}({
445
472
  tagName: tagName.toUpperCase(),
446
473
  });
@@ -453,13 +480,15 @@ const bGenerateMarkup = (esTemplate `
453
480
  }
454
481
  const tmplFn = ${isIdentOrRenderCall} ?? __fallbackTmpl;
455
482
  yield \`<\${tagName}\`;
456
- const shouldRenderScopeToken = tmplFn.hasScopedStylesheets || hasScopedStaticStylesheets(${ /*Component class */0});
483
+ const shouldRenderScopeToken =
484
+ tmplFn.hasScopedStylesheets ||
485
+ hasScopedStaticStylesheets(${ /*component class*/2});
457
486
  if (shouldRenderScopeToken) {
458
487
  yield \` class="\${tmplFn.stylesheetScopeToken}-host"\`;
459
488
  }
460
489
  yield* __renderAttrs(instance, attrs);
461
490
  yield '>';
462
- yield* tmplFn(props, attrs, slotted, ${0}, instance);
491
+ yield* tmplFn(props, attrs, slotted, ${ /*component class*/2}, instance);
463
492
  yield \`</\${tagName}>\`;
464
493
  }
465
494
  `);
@@ -481,7 +510,7 @@ const bAssignGenerateMarkupToComponentClass = (esTemplate `
481
510
  * - deferring to the template function for yielding child content
482
511
  */
483
512
  function addGenerateMarkupExport(program, state, filename) {
484
- const { hasRenderMethod, tmplExplicitImports } = state;
513
+ const { hasRenderMethod, privateFields, publicFields, tmplExplicitImports } = state;
485
514
  const classIdentifier = estreeToolkit.builders.identifier(state.lwcClassName);
486
515
  const renderCall = hasRenderMethod
487
516
  ? estreeToolkit.builders.callExpression(estreeToolkit.builders.memberExpression(estreeToolkit.builders.identifier('instance'), estreeToolkit.builders.identifier('render')), [])
@@ -493,13 +522,14 @@ function addGenerateMarkupExport(program, state, filename) {
493
522
  program.body.unshift(bImportDeclaration([
494
523
  {
495
524
  fallbackTmpl: '__fallbackTmpl',
525
+ filterProperties: '__filterProperties',
496
526
  mutationTracker: '__mutationTracker',
497
527
  renderAttrs: '__renderAttrs',
498
528
  SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
499
529
  },
500
530
  ]));
501
531
  program.body.unshift(bImportDeclaration(['hasScopedStaticStylesheets']));
502
- program.body.push(bGenerateMarkup(classIdentifier, renderCall));
532
+ program.body.push(bGenerateMarkup(estreeToolkit.builders.arrayExpression(publicFields.map(estreeToolkit.builders.literal)), estreeToolkit.builders.arrayExpression(privateFields.map(estreeToolkit.builders.literal)), classIdentifier, renderCall));
503
533
  }
504
534
  /**
505
535
  * Attach the `generateMarkup` function to the Component class so that it can be found later
@@ -549,13 +579,23 @@ const visitors = {
549
579
  }
550
580
  },
551
581
  PropertyDefinition(path, state) {
552
- if (path.node &&
553
- path.node.static &&
554
- estreeToolkit.is.identifier(path.node.key) &&
555
- path.node.key.name === 'stylesheets' &&
556
- estreeToolkit.is.arrayExpression(path.node.value) &&
557
- path.node.value.elements.every((el) => estreeToolkit.is.identifier(el))) {
558
- catalogStaticStylesheets(path.node.value.elements.map((el) => el.name), state);
582
+ const node = path.node;
583
+ if (!estreeToolkit.is.identifier(node?.key)) {
584
+ return;
585
+ }
586
+ // TODO [#4773]: Why do we get conflicting PropertyDefinition types when removing "vitest/globals"?
587
+ const decorators = node.decorators;
588
+ if (estreeToolkit.is.identifier(decorators[0]?.expression) && decorators[0].expression.name === 'api') {
589
+ state.publicFields.push(node.key.name);
590
+ }
591
+ else {
592
+ state.privateFields.push(node.key.name);
593
+ }
594
+ if (node.static &&
595
+ node.key.name === 'stylesheets' &&
596
+ estreeToolkit.is.arrayExpression(node.value) &&
597
+ node.value.elements.every((el) => estreeToolkit.is.identifier(el))) {
598
+ catalogStaticStylesheets(node.value.elements.map((el) => el.name), state);
559
599
  }
560
600
  },
561
601
  MethodDefinition(path, state) {
@@ -615,6 +655,8 @@ function compileJS(src, filename, compilationMode) {
615
655
  tmplExplicitImports: null,
616
656
  cssExplicitImports: null,
617
657
  staticStylesheetIds: null,
658
+ publicFields: [],
659
+ privateFields: [],
618
660
  };
619
661
  estreeToolkit.traverse(ast, visitors, state);
620
662
  if (!state.isLWC) {
@@ -1787,6 +1829,12 @@ var DiagnosticLevel;
1787
1829
  level: DiagnosticLevel.Error,
1788
1830
  url: '',
1789
1831
  },
1832
+ IGNORED_SLOT_ATTRIBUTE_IN_CHILD: {
1833
+ code: 1201,
1834
+ message: 'The slot attribute in {0} will be ignored due to its ancestor {1}. It must be a direct child of the containing component.',
1835
+ level: DiagnosticLevel.Warning,
1836
+ url: '',
1837
+ },
1790
1838
  });
1791
1839
 
1792
1840
  /**
@@ -1799,7 +1847,7 @@ var CompilerMetrics;
1799
1847
  CompilerMetrics["LWCSpreadDirective"] = "lwc-spread-directive";
1800
1848
  CompilerMetrics["DynamicImportTransform"] = "dynamic-import-transform";
1801
1849
  })(CompilerMetrics || (CompilerMetrics = {}));
1802
- /** version: 8.5.0 */
1850
+ /** version: 8.7.0 */
1803
1851
 
1804
1852
  /*
1805
1853
  * Copyright (c) 2024, Salesforce, Inc.
@@ -1960,8 +2008,8 @@ function expressionIrToEs(node, cxt) {
1960
2008
  */
1961
2009
  const bYieldFromChildGenerator = (esTemplateWithYield `
1962
2010
  {
1963
- const childProps = ${estreeToolkit.is.objectExpression};
1964
- const childAttrs = ${estreeToolkit.is.objectExpression};
2011
+ const childProps = __cloneAndDeepFreeze(${ /* child props */estreeToolkit.is.objectExpression});
2012
+ const childAttrs = ${ /* child attrs */estreeToolkit.is.objectExpression};
1965
2013
  const slottedContent = {
1966
2014
  light: Object.create(null),
1967
2015
  shadow: async function* () {
@@ -1976,13 +2024,16 @@ const bYieldFromChildGenerator = (esTemplateWithYield `
1976
2024
  slottedContent.light[name] = [fn]
1977
2025
  }
1978
2026
  }
1979
- ${ /* addContent statements */estreeToolkit.is.callExpression}
2027
+ ${ /* light DOM addContent statements */estreeToolkit.is.callExpression}
2028
+ ${ /* scoped slot addContent statements */estreeToolkit.is.callExpression}
1980
2029
  yield* ${estreeToolkit.is.identifier}(${estreeToolkit.is.literal}, childProps, childAttrs, slottedContent);
1981
2030
  }
1982
2031
  `);
1983
2032
  const bAddContent = (esTemplate `
1984
- addContent(${ /* slot name */estreeToolkit.is.expression} ?? "", async function* () {
1985
- ${ /* slot content */estreeToolkit.is.statement}
2033
+ addContent(${ /* slot name */estreeToolkit.is.expression} ?? "", async function* (${
2034
+ /* scoped slot data variable */ isNullableOf(estreeToolkit.is.identifier)}) {
2035
+ // FIXME: make validation work again
2036
+ ${ /* slot content */false}
1986
2037
  });
1987
2038
  `);
1988
2039
  const bImportGenerateMarkup = (localName, importPath) => estreeToolkit.builders.importDeclaration([estreeToolkit.builders.importSpecifier(estreeToolkit.builders.identifier('generateMarkup'), estreeToolkit.builders.identifier(localName))], estreeToolkit.builders.literal(importPath));
@@ -2006,46 +2057,43 @@ function getChildAttrsOrProps(attrs, cxt) {
2006
2057
  });
2007
2058
  return estreeToolkit.builders.objectExpression(objectAttrsOrProps);
2008
2059
  }
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
2060
  const Component = function Component(node, cxt) {
2024
2061
  // Import the custom component's generateMarkup export.
2025
2062
  const childGeneratorLocalName = `generateMarkup_${templateCompiler.toPropertyName(node.name)}`;
2026
2063
  const importPath = templateCompiler.kebabcaseToCamelcase(node.name);
2027
2064
  const componentImport = bImportGenerateMarkup(childGeneratorLocalName, importPath);
2028
2065
  cxt.hoist(componentImport, childGeneratorLocalName);
2066
+ cxt.hoist(bImportDeclaration([{ cloneAndDeepFreeze: '__cloneAndDeepFreeze' }]), 'import:cloneAndDeepFreeze');
2029
2067
  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) => {
2068
+ // Anything inside the slotted content is a normal slotted content except for `<template lwc:slot-data>` which is a scoped slot.
2069
+ const slottableChildren = node.children.filter((child) => child.type !== 'ScopedSlotFragment');
2070
+ const scopedSlottableChildren = node.children.filter((child) => child.type === 'ScopedSlotFragment');
2071
+ const shadowSlotContent = optimizeAdjacentYieldStmts(irChildrenToEs(slottableChildren, cxt));
2072
+ const lightSlotContent = slottableChildren.map((child) => {
2033
2073
  if ('attributes' in child) {
2034
2074
  const slotName = bAttributeValue(child, 'slot');
2035
- // FIXME: We don't know what happens for slot attributes inside an lwc:if block
2036
2075
  // Light DOM slots do not actually render the `slot` attribute.
2037
2076
  const clone = immer.produce(child, (draft) => {
2038
2077
  draft.attributes = draft.attributes.filter((attr) => attr.name !== 'slot');
2039
2078
  });
2040
2079
  const slotContent = irToEs(clone, cxt);
2041
- return bAddContent(slotName, slotContent);
2080
+ return bAddContent(slotName, null, slotContent);
2042
2081
  }
2043
2082
  else {
2044
- return bAddContent(estreeToolkit.builders.literal(''), irToEs(child, cxt));
2083
+ return bAddContent(estreeToolkit.builders.literal(''), null, irToEs(child, cxt));
2045
2084
  }
2046
2085
  });
2086
+ const scopedSlotContent = scopedSlottableChildren.map((child) => {
2087
+ const boundVariableName = child.slotData.value.name;
2088
+ const boundVariable = estreeToolkit.builders.identifier(boundVariableName);
2089
+ cxt.pushLocalVars([boundVariableName]);
2090
+ // TODO [#4768]: what if the bound variable is `generateMarkup` or some framework-specific identifier?
2091
+ const addContentExpr = bAddContent(child.slotName, boundVariable, irChildrenToEs(child.children, cxt));
2092
+ cxt.popLocalVars();
2093
+ return addContentExpr;
2094
+ });
2047
2095
  return [
2048
- bYieldFromChildGenerator(getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(attributes, cxt), shadowSlotContent, lightSlotContent, estreeToolkit.builders.identifier(childGeneratorLocalName), estreeToolkit.builders.literal(childTagName)),
2096
+ bYieldFromChildGenerator(getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(node.attributes, cxt), shadowSlotContent, lightSlotContent, scopedSlotContent, estreeToolkit.builders.identifier(childGeneratorLocalName), estreeToolkit.builders.literal(childTagName)),
2049
2097
  ];
2050
2098
  };
2051
2099
 
@@ -2059,17 +2107,32 @@ const bYield$1 = (expr) => estreeToolkit.builders.expressionStatement(estreeTool
2059
2107
  // TODO [#4714]: scope token renders as a suffix for literals, but prefix for expressions
2060
2108
  const bConditionalLiveYield = (esTemplateWithYield `
2061
2109
  {
2062
- const shouldRenderScopeToken = ${ /* isClass */estreeToolkit.is.literal} &&
2110
+ const attrName = ${ /* attribute name */estreeToolkit.is.literal};
2111
+ let attrValue = ${ /* attribute value expression */estreeToolkit.is.expression};
2112
+ const isHtmlBooleanAttr = ${ /* isHtmlBooleanAttr */estreeToolkit.is.literal};
2113
+
2114
+ const shouldRenderScopeToken = attrName === 'class' &&
2063
2115
  (hasScopedStylesheets || hasScopedStaticStylesheets(Cmp));
2064
2116
  const prefix = shouldRenderScopeToken ? stylesheetScopeToken + ' ' : '';
2065
2117
 
2066
- const attrValue = ${ /* attribute value expression */estreeToolkit.is.expression};
2067
- const valueType = typeof attrValue;
2118
+ // Global HTML boolean attributes are specially coerced into booleans
2119
+ // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/template-compiler/src/codegen/index.ts#L450-L454
2120
+ if (isHtmlBooleanAttr) {
2121
+ attrValue = attrValue ? '' : undefined;
2122
+ }
2068
2123
 
2069
- if (attrValue && (valueType === 'string' || valueType === 'boolean')) {
2070
- yield ' ' + ${ /* attribute name */estreeToolkit.is.literal};
2071
- if (valueType === 'string') {
2072
- yield \`="\${prefix}\${htmlEscape(attrValue, true)}"\`;
2124
+ // Global HTML "tabindex" attribute is specially massaged into a stringified number
2125
+ // This follows the historical behavior in api.ts:
2126
+ // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211
2127
+ if (attrName === 'tabindex') {
2128
+ const shouldNormalize = attrValue > 0 && typeof attrValue !== 'boolean';
2129
+ attrValue = shouldNormalize ? 0 : attrValue;
2130
+ }
2131
+
2132
+ if (attrValue !== undefined && attrValue !== null) {
2133
+ yield ' ' + attrName;
2134
+ if (attrValue !== '') {
2135
+ yield \`="\${prefix}\${htmlEscape(String(attrValue), true)}"\`;
2073
2136
  }
2074
2137
  }
2075
2138
  }
@@ -2077,10 +2140,18 @@ const bConditionalLiveYield = (esTemplateWithYield `
2077
2140
  // TODO [#4714]: scope token renders as a suffix for literals, but prefix for expressions
2078
2141
  const bStringLiteralYield = (esTemplateWithYield `
2079
2142
  {
2080
- const shouldRenderScopeToken = ${ /* isClass */estreeToolkit.is.literal} &&
2143
+ const attrName = ${ /* attribute name */estreeToolkit.is.literal}
2144
+ const attrValue = ${ /* attribute value */estreeToolkit.is.literal};
2145
+
2146
+ const shouldRenderScopeToken = attrName === 'class' &&
2081
2147
  (hasScopedStylesheets || hasScopedStaticStylesheets(Cmp));
2082
2148
  const suffix = shouldRenderScopeToken ? ' ' + stylesheetScopeToken : '';
2083
- yield ' ' + ${ /* attribute name */estreeToolkit.is.literal} + '="' + "${ /* attribute value */estreeToolkit.is.literal}" + suffix + '"'
2149
+
2150
+ yield ' ' + attrName;
2151
+ if (attrValue !== '' || shouldRenderScopeToken) {
2152
+ yield '="' + attrValue + suffix + '"';
2153
+ }
2154
+
2084
2155
  }
2085
2156
  `);
2086
2157
  const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
@@ -2091,7 +2162,10 @@ const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
2091
2162
  }
2092
2163
  }
2093
2164
  `);
2094
- function yieldAttrOrPropLiteralValue(name, valueNode, isClass) {
2165
+ const bYieldSanitizedHtml = esTemplateWithYield `
2166
+ yield sanitizeHtmlContent(${ /* lwc:inner-html content */estreeToolkit.is.expression})
2167
+ `;
2168
+ function yieldAttrOrPropLiteralValue(name, valueNode) {
2095
2169
  const { value, type } = valueNode;
2096
2170
  if (typeof value === 'string') {
2097
2171
  let yieldedValue;
@@ -2102,19 +2176,25 @@ function yieldAttrOrPropLiteralValue(name, valueNode, isClass) {
2102
2176
  // @ts-expect-error weird indirection results in wrong overload being picked up
2103
2177
  yieldedValue = shared.StringReplace.call(shared.StringTrim.call(value), /\s+/g, ' ');
2104
2178
  }
2179
+ else if (name === 'spellcheck') {
2180
+ // `spellcheck` string values are specially handled to massage them into booleans.
2181
+ // https://github.com/salesforce/lwc/blob/fe4e95f/packages/%40lwc/template-compiler/src/codegen/index.ts#L445-L448
2182
+ yieldedValue = String(value.toLowerCase() !== 'false');
2183
+ }
2105
2184
  else {
2106
2185
  yieldedValue = value;
2107
2186
  }
2108
- return [bStringLiteralYield(estreeToolkit.builders.literal(isClass), estreeToolkit.builders.literal(name), estreeToolkit.builders.literal(yieldedValue))];
2187
+ return [bStringLiteralYield(estreeToolkit.builders.literal(name), estreeToolkit.builders.literal(yieldedValue))];
2109
2188
  }
2110
2189
  else if (typeof value === 'boolean') {
2111
2190
  return [bYield$1(estreeToolkit.builders.literal(` ${name}`))];
2112
2191
  }
2113
2192
  throw new Error(`Unknown attr/prop literal: ${type}`);
2114
2193
  }
2115
- function yieldAttrOrPropLiveValue(name, value, isClass, cxt) {
2194
+ function yieldAttrOrPropLiveValue(elementName, name, value, cxt) {
2195
+ const isHtmlBooleanAttr = shared.isBooleanAttribute(name, elementName);
2116
2196
  const scopedExpression = getScopedExpression(value, cxt);
2117
- return [bConditionalLiveYield(estreeToolkit.builders.literal(isClass), scopedExpression, estreeToolkit.builders.literal(name))];
2197
+ return [bConditionalLiveYield(estreeToolkit.builders.literal(name), scopedExpression, estreeToolkit.builders.literal(isHtmlBooleanAttr))];
2118
2198
  }
2119
2199
  function reorderAttributes(attrs, props) {
2120
2200
  let classAttr = null;
@@ -2141,19 +2221,28 @@ const Element = function Element(node, cxt) {
2141
2221
  const innerHtmlDirective = node.type === 'Element' && node.directives.find((dir) => dir.name === 'InnerHTML');
2142
2222
  const attrsAndProps = reorderAttributes(node.attributes, node.properties);
2143
2223
  let hasClassAttribute = false;
2144
- const yieldAttrsAndProps = attrsAndProps.flatMap((attr) => {
2224
+ const yieldAttrsAndProps = attrsAndProps
2225
+ .filter(({ name }) => {
2226
+ // `<input checked>`/`<input value>` is treated as a property, not an attribute,
2227
+ // so should never be SSR'd. See https://github.com/salesforce/lwc/issues/4763
2228
+ return !(node.name === 'input' && (name === 'value' || name === 'checked'));
2229
+ })
2230
+ .flatMap((attr) => {
2145
2231
  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;
2232
+ if (type === 'Attribute') {
2233
+ if (name === 'inner-h-t-m-l' || name === 'outer-h-t-m-l') {
2234
+ throw new Error(`Cannot set attribute "${name}" on <${node.name}>.`);
2235
+ }
2236
+ else if (name === 'class') {
2237
+ hasClassAttribute = true;
2238
+ }
2150
2239
  }
2151
2240
  cxt.hoist(bImportHtmlEscape(), importHtmlEscapeKey);
2152
2241
  if (value.type === 'Literal') {
2153
- return yieldAttrOrPropLiteralValue(name, value, isClass);
2242
+ return yieldAttrOrPropLiteralValue(name, value);
2154
2243
  }
2155
2244
  else {
2156
- return yieldAttrOrPropLiveValue(name, value, isClass, cxt);
2245
+ return yieldAttrOrPropLiveValue(node.name, name, value, cxt);
2157
2246
  }
2158
2247
  });
2159
2248
  if (shared.isVoidElement(node.name, shared.HTML_NAMESPACE)) {
@@ -2168,7 +2257,8 @@ const Element = function Element(node, cxt) {
2168
2257
  else if (innerHtmlDirective) {
2169
2258
  const value = innerHtmlDirective.value;
2170
2259
  const unsanitizedHtmlExpression = value.type === 'Literal' ? estreeToolkit.builders.literal(value.value) : expressionIrToEs(value, cxt);
2171
- childContent = [bYield$1(unsanitizedHtmlExpression)];
2260
+ childContent = [bYieldSanitizedHtml(unsanitizedHtmlExpression)];
2261
+ cxt.hoist(bImportDeclaration(['sanitizeHtmlContent']), 'import:sanitizeHtmlContent');
2172
2262
  }
2173
2263
  else {
2174
2264
  childContent = [];
@@ -2294,13 +2384,19 @@ const IfBlock = function IfBlock(node, cxt) {
2294
2384
  */
2295
2385
  const bConditionalSlot = (esTemplateWithYield `
2296
2386
  if (isLightDom) {
2297
- // start bookend HTML comment
2387
+ const isScopedSlot = ${ /* isScopedSlot */estreeToolkit.is.literal};
2388
+ // start bookend HTML comment for light DOM slot vfragment
2298
2389
  yield '<!---->';
2299
2390
 
2391
+ // scoped slot factory has its own vfragment hence its own bookend
2392
+ if (isScopedSlot) {
2393
+ yield '<!---->';
2394
+ }
2395
+
2300
2396
  const generators = slottedContent?.light[${ /* slotName */estreeToolkit.is.expression} ?? ""];
2301
2397
  if (generators) {
2302
2398
  for (const generator of generators) {
2303
- yield* generator();
2399
+ yield* generator(${ /* scoped slot data */isNullableOf(estreeToolkit.is.expression)});
2304
2400
  }
2305
2401
  } else {
2306
2402
  // If we're in this else block, then the generator _must_ have yielded
@@ -2310,19 +2406,29 @@ const bConditionalSlot = (esTemplateWithYield `
2310
2406
  // TODO: default/fallback slot content
2311
2407
  ${ /* slot fallback content */estreeToolkit.is.statement}
2312
2408
  }
2409
+
2410
+ // scoped slot factory has its own vfragment hence its own bookend
2411
+ if (isScopedSlot) {
2412
+ yield '<!---->';
2413
+ }
2313
2414
 
2314
- // end bookend HTML comment
2415
+ // end bookend HTML comment for light DOM slot vfragment
2315
2416
  yield '<!---->';
2316
2417
  } else {
2317
2418
  ${ /* slot element AST */estreeToolkit.is.statement}
2318
2419
  }
2319
2420
  `);
2320
2421
  const Slot = function Slot(node, ctx) {
2422
+ const slotBindDirective = node.directives.find((dir) => dir.name === 'SlotBind');
2423
+ const slotBound = slotBindDirective?.value
2424
+ ? getScopedExpression(slotBindDirective.value, ctx)
2425
+ : null;
2321
2426
  const slotName = bAttributeValue(node, 'name');
2322
2427
  // FIXME: avoid serializing the slot's children twice
2323
2428
  const slotAst = Element(node, ctx);
2324
2429
  const slotChildren = irChildrenToEs(node.children, ctx);
2325
- return [bConditionalSlot(slotName, slotChildren, slotAst)];
2430
+ const isScopedSlot = estreeToolkit.builders.literal(Boolean(slotBound));
2431
+ return [bConditionalSlot(isScopedSlot, slotName, slotBound, slotChildren, slotAst)];
2326
2432
  };
2327
2433
 
2328
2434
  /*
@@ -2334,12 +2440,16 @@ const Slot = function Slot(node, ctx) {
2334
2440
  const bYield = (expr) => estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(expr));
2335
2441
  const bYieldEscapedString = (esTemplateWithYield `
2336
2442
  const ${estreeToolkit.is.identifier} = ${estreeToolkit.is.expression};
2337
- if (typeof ${0} === 'string') {
2338
- yield (${estreeToolkit.is.literal} && ${0} === '') ? '\\u200D' : htmlEscape(${0});
2339
- } else if (typeof ${0} === 'number') {
2340
- yield ${0}.toString();
2341
- } else {
2342
- yield ${0} ? htmlEscape(${0}.toString()) : '\\u200D';
2443
+ switch (typeof ${0}) {
2444
+ case 'string':
2445
+ yield (${estreeToolkit.is.literal} && ${0} === '') ? '\\u200D' : htmlEscape(${0});
2446
+ break;
2447
+ case 'number':
2448
+ case 'boolean':
2449
+ yield String(${0});
2450
+ break;
2451
+ default:
2452
+ yield ${0} ? htmlEscape(${0}.toString()) : '\\u200D';
2343
2453
  }
2344
2454
  `);
2345
2455
  function isLiteral(node) {
@@ -2572,5 +2682,5 @@ function compileTemplateForSSR(src, filename, options, mode = 'asyncYield') {
2572
2682
 
2573
2683
  exports.compileComponentForSSR = compileComponentForSSR;
2574
2684
  exports.compileTemplateForSSR = compileTemplateForSSR;
2575
- /** version: 8.5.0 */
2685
+ /** version: 8.7.0 */
2576
2686
  //# sourceMappingURL=index.cjs.js.map