@lwc/ssr-compiler 8.24.0 → 8.25.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.
Files changed (3) hide show
  1. package/dist/index.cjs.js +290 -218
  2. package/dist/index.js +291 -219
  3. package/package.json +5 -5
package/dist/index.cjs.js CHANGED
@@ -11,39 +11,203 @@ var estreeToolkit = require('estree-toolkit');
11
11
  var meriyah = require('meriyah');
12
12
  var errors = require('@lwc/errors');
13
13
  var immer = require('immer');
14
- var node_path = require('node:path');
15
14
  var acorn = require('acorn');
15
+ var node_path = require('node:path');
16
16
  var templateCompiler = require('@lwc/template-compiler');
17
17
  var builders = require('estree-toolkit/dist/builders');
18
18
  var types = require('@babel/types');
19
19
  var util = require('util');
20
20
 
21
+ /*
22
+ * Copyright (c) 2024, salesforce.com, inc.
23
+ * All rights reserved.
24
+ * SPDX-License-Identifier: MIT
25
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
26
+ */
27
+ /** Placeholder value to use to opt out of validation. */
28
+ const NO_VALIDATION = false;
29
+ /**
30
+ * `esTemplate` generates JS code with "holes" to be filled later. In order to have a valid AST,
31
+ * it uses identifiers with this prefix at the location of the holes.
32
+ */
33
+ const PLACEHOLDER_PREFIX = '__lwc_ESTEMPLATE_PLACEHOLDER__';
34
+ const getReplacementNode = (state, placeholderId) => {
35
+ const key = Number(placeholderId.slice(PLACEHOLDER_PREFIX.length));
36
+ const nodeCount = state.replacementNodes.length;
37
+ if (key >= nodeCount) {
38
+ throw new Error(`Cannot use index ${key} when only ${nodeCount} values have been provided.`);
39
+ }
40
+ const validateReplacement = state.placeholderToValidator.get(key);
41
+ const replacementNode = state.replacementNodes[key];
42
+ if (validateReplacement &&
43
+ !(Array.isArray(replacementNode)
44
+ ? replacementNode.every(validateReplacement)
45
+ : validateReplacement(replacementNode))) {
46
+ const expectedType = validateReplacement.__debugName ||
47
+ validateReplacement.name ||
48
+ '(could not determine)';
49
+ const actualType = Array.isArray(replacementNode)
50
+ ? `[${replacementNode.map((n) => n && n.type).join(', ')}]`
51
+ : replacementNode?.type;
52
+ throw new Error(`Validation failed for templated node. Expected type ${expectedType}, but received ${actualType}.`);
53
+ }
54
+ return replacementNode;
55
+ };
56
+ const visitors$2 = {
57
+ Identifier(path, state) {
58
+ if (path.node?.name.startsWith(PLACEHOLDER_PREFIX)) {
59
+ const replacementNode = getReplacementNode(state, path.node.name);
60
+ if (replacementNode === null) {
61
+ path.remove();
62
+ }
63
+ else if (Array.isArray(replacementNode)) {
64
+ if (replacementNode.length === 0) {
65
+ path.remove();
66
+ }
67
+ else {
68
+ if (path.parentPath?.node?.type === 'ExpressionStatement') {
69
+ path.parentPath.replaceWithMultiple(replacementNode);
70
+ }
71
+ else {
72
+ path.replaceWithMultiple(replacementNode);
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ path.replaceWith(replacementNode);
78
+ }
79
+ }
80
+ },
81
+ Literal(path, state) {
82
+ if (typeof path.node?.value === 'string' &&
83
+ path.node.value.startsWith(PLACEHOLDER_PREFIX)) {
84
+ // A literal can only be replaced with a single node
85
+ const replacementNode = getReplacementNode(state, path.node.value);
86
+ path.replaceWith(replacementNode);
87
+ }
88
+ },
89
+ };
90
+ function esTemplateImpl(javascriptSegments, validators, wrap, unwrap) {
91
+ let placeholderCount = 0;
92
+ let parsableCode = javascriptSegments[0];
93
+ const placeholderToValidator = new Map();
94
+ for (let i = 1; i < javascriptSegments.length; i += 1) {
95
+ const segment = javascriptSegments[i];
96
+ const validator = validators[i - 1]; // always one less value than strings in template literals
97
+ if (typeof validator === 'function' || validator === NO_VALIDATION) {
98
+ // Template slot will be filled by a *new* argument passed to the generated function
99
+ if (validator !== NO_VALIDATION) {
100
+ placeholderToValidator.set(placeholderCount, validator);
101
+ }
102
+ parsableCode += `${PLACEHOLDER_PREFIX}${placeholderCount}`;
103
+ placeholderCount += 1;
104
+ }
105
+ else {
106
+ // Template slot uses a *previously defined* argument passed to the generated function
107
+ if (validator >= placeholderCount) {
108
+ throw new Error(`Reference to argument ${validator} at index ${i} cannot be used. Only ${placeholderCount - 1} arguments have been defined.`);
109
+ }
110
+ parsableCode += `${PLACEHOLDER_PREFIX}${validator}`;
111
+ }
112
+ parsableCode += segment;
113
+ }
114
+ if (wrap) {
115
+ parsableCode = wrap(parsableCode);
116
+ }
117
+ const originalAstProgram = acorn.parse(parsableCode, {
118
+ ecmaVersion: 2022,
119
+ allowAwaitOutsideFunction: true,
120
+ allowReturnOutsideFunction: true,
121
+ allowSuperOutsideMethod: true,
122
+ allowImportExportEverywhere: true,
123
+ locations: false,
124
+ });
125
+ let originalAst;
126
+ const finalCharacter = javascriptSegments.at(-1)?.trimEnd()?.at(-1);
127
+ if (originalAstProgram.body.length === 1) {
128
+ originalAst =
129
+ finalCharacter === ';' && originalAstProgram.body[0].type === 'ExpressionStatement'
130
+ ? (originalAst = originalAstProgram.body[0].expression)
131
+ : (originalAst = originalAstProgram.body[0]);
132
+ }
133
+ else {
134
+ originalAst = originalAstProgram.body;
135
+ }
136
+ // Turns Acorn AST objects into POJOs, for use with Immer.
137
+ originalAst = JSON.parse(JSON.stringify(originalAst));
138
+ return function templatedAst(...replacementNodes) {
139
+ const result = immer.produce(originalAst, (astDraft) => estreeToolkit.traverse(astDraft, visitors$2, {
140
+ placeholderToValidator,
141
+ replacementNodes,
142
+ }));
143
+ return (unwrap ? unwrap(result) : result);
144
+ };
145
+ }
146
+ /**
147
+ * Template literal tag that generates a builder function. Like estree's `builders`, but for more
148
+ * complex structures. The template values should be estree `is` validators or a back reference to
149
+ * a previous slot (to re-use the referenced value).
150
+ *
151
+ * To have the generated function return a particular node type, the generic comes _after_ the
152
+ * template literal. Kinda weird, but it's necessary to infer the types of the template values.
153
+ * (If it were at the start, we'd need to explicitly provide _all_ type params. Tedious!)
154
+ * @example
155
+ * const bSum = esTemplate`(${is.identifier}, ${is.identifier}) => ${0} + ${1}`<EsArrowFunctionExpression>
156
+ * const sumFuncNode = bSum(b.identifier('a'), b.identifier('b'))
157
+ * // `sumFuncNode` is an AST node representing `(a, b) => a + b`
158
+ */
159
+ function esTemplate(javascriptSegments, ...Validators) {
160
+ return esTemplateImpl(javascriptSegments, Validators);
161
+ }
162
+ /** Similar to {@linkcode esTemplate}, but supports `yield` expressions. */
163
+ function esTemplateWithYield(javascriptSegments, ...validators) {
164
+ const wrap = (code) => `function* placeholder() {${code}}`;
165
+ const unwrap = (node) => node.body.body.length === 1 ? node.body.body[0] : node.body.body;
166
+ return esTemplateImpl(javascriptSegments, validators, wrap, unwrap);
167
+ }
168
+
21
169
  /*
22
170
  * Copyright (c) 2024, Salesforce, Inc.
23
171
  * All rights reserved.
24
172
  * SPDX-License-Identifier: MIT
25
173
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
26
174
  */
27
- const EMIT_IDENT = estreeToolkit.builders.identifier('$$emit');
28
175
  /** Function names that may be transmogrified. All should start with `__lwc`. */
29
176
  // Rollup may rename variables to prevent shadowing. When it does, it uses the format `foo$0`, `foo$1`, etc.
30
177
  const TRANSMOGRIFY_TARGET = /^__lwc(Generate|Tmpl).*$/;
31
- const isWithinFn = (nodePath) => {
32
- const { node } = nodePath;
33
- if (!node) {
34
- return false;
35
- }
36
- if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
37
- node.id &&
38
- TRANSMOGRIFY_TARGET.test(node.id.name)) {
39
- return true;
40
- }
41
- if (nodePath.parentPath) {
42
- return isWithinFn(nodePath.parentPath);
178
+ const isNonArrowFunction = (node) => {
179
+ return estreeToolkit.is.functionDeclaration(node) || estreeToolkit.is.functionExpression(node);
180
+ };
181
+ /**
182
+ * Determines whether a node is a function we want to transmogrify or within one, at any level.
183
+ */
184
+ const isWithinTargetFunc = (nodePath) => {
185
+ let path = isNonArrowFunction(nodePath)
186
+ ? nodePath
187
+ : nodePath.findParent(isNonArrowFunction);
188
+ while (path?.node) {
189
+ const { id } = path.node;
190
+ if (id && TRANSMOGRIFY_TARGET.test(id.name)) {
191
+ return true;
192
+ }
193
+ path = path.findParent(isNonArrowFunction);
43
194
  }
44
195
  return false;
45
196
  };
46
- const visitors$2 = {
197
+ /**
198
+ * Determines whether the nearest function encapsulating this node is a function we transmogrify.
199
+ */
200
+ const isImmediateWithinTargetFunc = (nodePath) => {
201
+ const parentFunc = nodePath.findParent(estreeToolkit.is.function);
202
+ return Boolean(parentFunc &&
203
+ isNonArrowFunction(parentFunc) &&
204
+ parentFunc.node?.id &&
205
+ TRANSMOGRIFY_TARGET.test(parentFunc.node.id.name));
206
+ };
207
+ const bDeclareYieldVar = (esTemplate `let __lwcYield = '';`);
208
+ const bAppendToYieldVar = (esTemplate `__lwcYield += ${estreeToolkit.is.expression};`);
209
+ const bReturnYieldVar = (esTemplate `return __lwcYield;`);
210
+ const visitors$1 = {
47
211
  // @ts-expect-error types for `traverse` do not support sharing a visitor between node types:
48
212
  // https://github.com/sarsamurmu/estree-toolkit/issues/20
49
213
  'FunctionDeclaration|FunctionExpression'(path, state) {
@@ -54,12 +218,12 @@ const visitors$2 = {
54
218
  // Component authors might conceivably use async generator functions in their own code. Therefore,
55
219
  // when traversing & transforming written+generated code, we need to disambiguate generated async
56
220
  // generator functions from those that were written by the component author.
57
- if (!isWithinFn(path)) {
221
+ if (!isWithinTargetFunc(path)) {
58
222
  return;
59
223
  }
60
224
  node.generator = false;
61
225
  node.async = state.mode === 'async';
62
- node.params.unshift(EMIT_IDENT);
226
+ node.body.body = [bDeclareYieldVar(), ...node.body.body, bReturnYieldVar()];
63
227
  },
64
228
  YieldExpression(path, state) {
65
229
  const { node } = path;
@@ -69,26 +233,15 @@ const visitors$2 = {
69
233
  // Component authors might conceivably use generator functions within their own code. Therefore,
70
234
  // when traversing & transforming written+generated code, we need to disambiguate generated yield
71
235
  // expressions from those that were written by the component author.
72
- if (!isWithinFn(path)) {
236
+ if (!isWithinTargetFunc(path)) {
73
237
  return;
74
238
  }
75
- if (node.delegate) {
76
- // transform `yield* foo(arg)` into `foo($$emit, arg)` or `await foo($$emit, arg)`
77
- if (node.argument?.type !== 'CallExpression') {
78
- throw new Error('Implementation error: cannot transmogrify complex yield-from expressions');
79
- }
80
- const callExpr = node.argument;
81
- callExpr.arguments.unshift(EMIT_IDENT);
82
- path.replaceWith(state.mode === 'sync' ? callExpr : estreeToolkit.builders.awaitExpression(callExpr));
83
- }
84
- else {
85
- // transform `yield foo` into `$$emit(foo)`
86
- const emittedExpression = node.argument;
87
- if (!emittedExpression) {
88
- throw new Error('Implementation error: cannot transform a yield expression that yields nothing');
89
- }
90
- path.replaceWith(estreeToolkit.builders.callExpression(EMIT_IDENT, [emittedExpression]));
239
+ const arg = node.argument;
240
+ if (!arg) {
241
+ const type = node.delegate ? 'yield*' : 'yield';
242
+ throw new Error(`Cannot transmogrify ${type} statement without an argument.`);
91
243
  }
244
+ path.replaceWith(bAppendToYieldVar(state.mode === 'sync' ? arg : estreeToolkit.builders.awaitExpression(arg)));
92
245
  },
93
246
  ImportSpecifier(path, _state) {
94
247
  // @lwc/ssr-runtime has a couple of helper functions that need to conform to either the generator or
@@ -115,6 +268,17 @@ const visitors$2 = {
115
268
  node.imported.name = 'renderAttrsNoYield';
116
269
  }
117
270
  },
271
+ ReturnStatement(path) {
272
+ if (!isImmediateWithinTargetFunc(path)) {
273
+ return;
274
+ }
275
+ // The transmogrify result returns __lwcYield, so we skip it
276
+ const arg = path.node?.argument;
277
+ if (estreeToolkit.is.identifier(arg) && arg.name === '__lwcYield') {
278
+ return;
279
+ }
280
+ throw new Error('Cannot transmogrify function with return statement.');
281
+ },
118
282
  };
119
283
  /**
120
284
  * Transforms async-generator code into either the async or synchronous alternatives that are
@@ -160,7 +324,7 @@ function transmogrify(compiledComponentAst, mode = 'sync') {
160
324
  const state = {
161
325
  mode,
162
326
  };
163
- return immer.produce(compiledComponentAst, (astDraft) => estreeToolkit.traverse(astDraft, visitors$2, state));
327
+ return immer.produce(compiledComponentAst, (astDraft) => estreeToolkit.traverse(astDraft, visitors$1, state));
164
328
  }
165
329
 
166
330
  /******************************************************************************
@@ -369,154 +533,6 @@ function catalogStaticStylesheets(ids, state) {
369
533
  }
370
534
  }
371
535
 
372
- /*
373
- * Copyright (c) 2024, salesforce.com, inc.
374
- * All rights reserved.
375
- * SPDX-License-Identifier: MIT
376
- * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
377
- */
378
- /** Placeholder value to use to opt out of validation. */
379
- const NO_VALIDATION = false;
380
- /**
381
- * `esTemplate` generates JS code with "holes" to be filled later. In order to have a valid AST,
382
- * it uses identifiers with this prefix at the location of the holes.
383
- */
384
- const PLACEHOLDER_PREFIX = '__lwc_ESTEMPLATE_PLACEHOLDER__';
385
- const getReplacementNode = (state, placeholderId) => {
386
- const key = Number(placeholderId.slice(PLACEHOLDER_PREFIX.length));
387
- const nodeCount = state.replacementNodes.length;
388
- if (key >= nodeCount) {
389
- throw new Error(`Cannot use index ${key} when only ${nodeCount} values have been provided.`);
390
- }
391
- const validateReplacement = state.placeholderToValidator.get(key);
392
- const replacementNode = state.replacementNodes[key];
393
- if (validateReplacement &&
394
- !(Array.isArray(replacementNode)
395
- ? replacementNode.every(validateReplacement)
396
- : validateReplacement(replacementNode))) {
397
- const expectedType = validateReplacement.__debugName ||
398
- validateReplacement.name ||
399
- '(could not determine)';
400
- const actualType = Array.isArray(replacementNode)
401
- ? `[${replacementNode.map((n) => n && n.type).join(', ')}]`
402
- : replacementNode?.type;
403
- throw new Error(`Validation failed for templated node. Expected type ${expectedType}, but received ${actualType}.`);
404
- }
405
- return replacementNode;
406
- };
407
- const visitors$1 = {
408
- Identifier(path, state) {
409
- if (path.node?.name.startsWith(PLACEHOLDER_PREFIX)) {
410
- const replacementNode = getReplacementNode(state, path.node.name);
411
- if (replacementNode === null) {
412
- path.remove();
413
- }
414
- else if (Array.isArray(replacementNode)) {
415
- if (replacementNode.length === 0) {
416
- path.remove();
417
- }
418
- else {
419
- if (path.parentPath?.node?.type === 'ExpressionStatement') {
420
- path.parentPath.replaceWithMultiple(replacementNode);
421
- }
422
- else {
423
- path.replaceWithMultiple(replacementNode);
424
- }
425
- }
426
- }
427
- else {
428
- path.replaceWith(replacementNode);
429
- }
430
- }
431
- },
432
- Literal(path, state) {
433
- if (typeof path.node?.value === 'string' &&
434
- path.node.value.startsWith(PLACEHOLDER_PREFIX)) {
435
- // A literal can only be replaced with a single node
436
- const replacementNode = getReplacementNode(state, path.node.value);
437
- path.replaceWith(replacementNode);
438
- }
439
- },
440
- };
441
- function esTemplateImpl(javascriptSegments, validators, wrap, unwrap) {
442
- let placeholderCount = 0;
443
- let parsableCode = javascriptSegments[0];
444
- const placeholderToValidator = new Map();
445
- for (let i = 1; i < javascriptSegments.length; i += 1) {
446
- const segment = javascriptSegments[i];
447
- const validator = validators[i - 1]; // always one less value than strings in template literals
448
- if (typeof validator === 'function' || validator === NO_VALIDATION) {
449
- // Template slot will be filled by a *new* argument passed to the generated function
450
- if (validator !== NO_VALIDATION) {
451
- placeholderToValidator.set(placeholderCount, validator);
452
- }
453
- parsableCode += `${PLACEHOLDER_PREFIX}${placeholderCount}`;
454
- placeholderCount += 1;
455
- }
456
- else {
457
- // Template slot uses a *previously defined* argument passed to the generated function
458
- if (validator >= placeholderCount) {
459
- throw new Error(`Reference to argument ${validator} at index ${i} cannot be used. Only ${placeholderCount - 1} arguments have been defined.`);
460
- }
461
- parsableCode += `${PLACEHOLDER_PREFIX}${validator}`;
462
- }
463
- parsableCode += segment;
464
- }
465
- if (wrap) {
466
- parsableCode = wrap(parsableCode);
467
- }
468
- const originalAstProgram = acorn.parse(parsableCode, {
469
- ecmaVersion: 2022,
470
- allowAwaitOutsideFunction: true,
471
- allowReturnOutsideFunction: true,
472
- allowSuperOutsideMethod: true,
473
- allowImportExportEverywhere: true,
474
- locations: false,
475
- });
476
- let originalAst;
477
- const finalCharacter = javascriptSegments.at(-1)?.trimEnd()?.at(-1);
478
- if (originalAstProgram.body.length === 1) {
479
- originalAst =
480
- finalCharacter === ';' && originalAstProgram.body[0].type === 'ExpressionStatement'
481
- ? (originalAst = originalAstProgram.body[0].expression)
482
- : (originalAst = originalAstProgram.body[0]);
483
- }
484
- else {
485
- originalAst = originalAstProgram.body;
486
- }
487
- // Turns Acorn AST objects into POJOs, for use with Immer.
488
- originalAst = JSON.parse(JSON.stringify(originalAst));
489
- return function templatedAst(...replacementNodes) {
490
- const result = immer.produce(originalAst, (astDraft) => estreeToolkit.traverse(astDraft, visitors$1, {
491
- placeholderToValidator,
492
- replacementNodes,
493
- }));
494
- return (unwrap ? unwrap(result) : result);
495
- };
496
- }
497
- /**
498
- * Template literal tag that generates a builder function. Like estree's `builders`, but for more
499
- * complex structures. The template values should be estree `is` validators or a back reference to
500
- * a previous slot (to re-use the referenced value).
501
- *
502
- * To have the generated function return a particular node type, the generic comes _after_ the
503
- * template literal. Kinda weird, but it's necessary to infer the types of the template values.
504
- * (If it were at the start, we'd need to explicitly provide _all_ type params. Tedious!)
505
- * @example
506
- * const bSum = esTemplate`(${is.identifier}, ${is.identifier}) => ${0} + ${1}`<EsArrowFunctionExpression>
507
- * const sumFuncNode = bSum(b.identifier('a'), b.identifier('b'))
508
- * // `sumFuncNode` is an AST node representing `(a, b) => a + b`
509
- */
510
- function esTemplate(javascriptSegments, ...Validators) {
511
- return esTemplateImpl(javascriptSegments, Validators);
512
- }
513
- /** Similar to {@linkcode esTemplate}, but supports `yield` expressions. */
514
- function esTemplateWithYield(javascriptSegments, ...validators) {
515
- const wrap = (code) => `function* placeholder() {${code}}`;
516
- const unwrap = (node) => node.body.body.length === 1 ? node.body.body[0] : node.body.body;
517
- return esTemplateImpl(javascriptSegments, validators, wrap, unwrap);
518
- }
519
-
520
536
  /*
521
537
  * Copyright (c) 2024, Salesforce, Inc.
522
538
  * All rights reserved.
@@ -720,13 +736,13 @@ const bGenerateMarkup = (esTemplate `
720
736
  enumerable: false,
721
737
  writable: false,
722
738
  value: async function* __lwcGenerateMarkup(
723
- // The $$emit function is magically inserted here
724
739
  tagName,
725
740
  props,
726
741
  attrs,
727
742
  parent,
728
743
  scopeToken,
729
744
  contextfulParent,
745
+ renderContext,
730
746
  shadowSlottedContent,
731
747
  lightSlottedContent,
732
748
  scopedSlottedContent,
@@ -770,7 +786,8 @@ const bGenerateMarkup = (esTemplate `
770
786
  lightSlottedContent,
771
787
  scopedSlottedContent,
772
788
  ${ /*component class*/0},
773
- instance
789
+ instance,
790
+ renderContext
774
791
  );
775
792
  yield \`</\${tagName}>\`;
776
793
  }
@@ -1304,33 +1321,86 @@ function getRootIdentifier$1(node) {
1304
1321
  * SPDX-License-Identifier: MIT
1305
1322
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1306
1323
  */
1307
- function optimizeAdjacentYieldStmts(statements) {
1308
- let prevStmt = null;
1309
- return statements
1310
- .map((stmt) => {
1311
- if (
1312
- // Check if the current statement and previous statement are
1313
- // both yield expression statements that yield a string literal.
1314
- prevStmt &&
1315
- estreeToolkit.is.expressionStatement(prevStmt) &&
1316
- estreeToolkit.is.yieldExpression(prevStmt.expression) &&
1317
- !prevStmt.expression.delegate &&
1318
- prevStmt.expression.argument &&
1319
- estreeToolkit.is.literal(prevStmt.expression.argument) &&
1320
- typeof prevStmt.expression.argument.value === 'string' &&
1321
- estreeToolkit.is.expressionStatement(stmt) &&
1322
- estreeToolkit.is.yieldExpression(stmt.expression) &&
1323
- !stmt.expression.delegate &&
1324
- stmt.expression.argument &&
1325
- estreeToolkit.is.literal(stmt.expression.argument) &&
1326
- typeof stmt.expression.argument.value === 'string') {
1327
- prevStmt.expression.argument.value += stmt.expression.argument.value;
1324
+ const bYieldTernary = (esTemplateWithYield `yield ${estreeToolkit.is.expression} ? ${estreeToolkit.is.expression} : ${estreeToolkit.is.expression}`);
1325
+ const bOptimizedYield = (esTemplateWithYield `yield ${estreeToolkit.is.expression};`);
1326
+ function isOptimizableYield(stmt) {
1327
+ return (estreeToolkit.is.expressionStatement(stmt) &&
1328
+ estreeToolkit.is.yieldExpression(stmt.expression) &&
1329
+ stmt.expression.delegate === false);
1330
+ }
1331
+ /** Returns null if the statement cannot be optimized. */
1332
+ function optimizeSingleStatement(stmt) {
1333
+ if (estreeToolkit.is.blockStatement(stmt)) {
1334
+ // `if (cond) { ... }` => optimize inner yields and see if we can condense
1335
+ const optimizedBlock = optimizeAdjacentYieldStmts(stmt.body);
1336
+ // More than one statement cannot be optimized into a single yield
1337
+ if (optimizedBlock.length !== 1)
1328
1338
  return null;
1339
+ const [optimized] = optimizedBlock;
1340
+ return isOptimizableYield(optimized) ? optimized : null;
1341
+ }
1342
+ else if (estreeToolkit.is.expressionStatement(stmt)) {
1343
+ // `if (cond) expression` => just check if expression is a yield
1344
+ return estreeToolkit.is.yieldExpression(stmt) ? stmt : null;
1345
+ }
1346
+ else {
1347
+ // Can only optimize expression/block statements
1348
+ return null;
1349
+ }
1350
+ }
1351
+ /**
1352
+ * Tries to reduce if statements that only contain yields into a single yielded ternary
1353
+ * Returns null if the statement cannot be optimized.
1354
+ */
1355
+ function optimizeIfStatement(stmt) {
1356
+ const consequent = optimizeSingleStatement(stmt.consequent)?.expression.argument;
1357
+ if (!consequent) {
1358
+ return null;
1359
+ }
1360
+ const alternate = stmt.alternate
1361
+ ? optimizeSingleStatement(stmt.alternate)?.expression.argument
1362
+ : estreeToolkit.builders.literal('');
1363
+ if (!alternate) {
1364
+ return null;
1365
+ }
1366
+ return bYieldTernary(stmt.test, consequent, alternate);
1367
+ }
1368
+ function optimizeAdjacentYieldStmts(statements) {
1369
+ return statements.reduce((result, stmt) => {
1370
+ if (estreeToolkit.is.ifStatement(stmt)) {
1371
+ const optimized = optimizeIfStatement(stmt);
1372
+ if (optimized) {
1373
+ stmt = optimized;
1374
+ }
1329
1375
  }
1330
- prevStmt = stmt;
1331
- return stmt;
1332
- })
1333
- .filter((el) => el !== null);
1376
+ const prev = result.at(-1);
1377
+ if (!isOptimizableYield(stmt) || !isOptimizableYield(prev)) {
1378
+ // nothing to do
1379
+ return [...result, stmt];
1380
+ }
1381
+ const arg = stmt.expression.argument;
1382
+ if (!arg || (estreeToolkit.is.literal(arg) && arg.value === '')) {
1383
+ // bare `yield` and `yield ""` amount to nothing, so we can drop them
1384
+ return result;
1385
+ }
1386
+ const newArg = immer.produce(prev.expression.argument, (draft) => {
1387
+ if (estreeToolkit.is.literal(arg) && typeof arg.value === 'string') {
1388
+ let concatTail = draft;
1389
+ while (estreeToolkit.is.binaryExpression(concatTail) && concatTail.operator === '+') {
1390
+ concatTail = concatTail.right;
1391
+ }
1392
+ if (estreeToolkit.is.literal(concatTail) && typeof concatTail.value === 'string') {
1393
+ // conat adjacent strings now, rather than at runtime
1394
+ concatTail.value += arg.value;
1395
+ return draft;
1396
+ }
1397
+ }
1398
+ // concat arbitrary values at runtime
1399
+ return estreeToolkit.builders.binaryExpression('+', draft, arg);
1400
+ });
1401
+ // replace the last `+` chain with a new one with the new arg
1402
+ return [...result.slice(0, -1), bOptimizedYield(newArg)];
1403
+ }, []);
1334
1404
  }
1335
1405
  function bAttributeValue(node, attrName) {
1336
1406
  if (!('attributes' in node)) {
@@ -1794,13 +1864,14 @@ estreeToolkit.is.statement}
1794
1864
  instance,
1795
1865
  scopeToken,
1796
1866
  contextfulParent,
1867
+ renderContext,
1797
1868
  shadowSlottedContent,
1798
1869
  lightSlottedContentMap,
1799
1870
  scopedSlottedContentMap
1800
1871
  );
1801
1872
  } else {
1802
1873
  yield \`<\${tagName}>\`;
1803
- yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${ /* Component */3}, instance)
1874
+ yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${ /* Component */3}, instance, renderContext)
1804
1875
  yield \`</\${tagName}>\`;
1805
1876
  }
1806
1877
  }
@@ -1859,6 +1930,7 @@ estreeToolkit.is.statement}
1859
1930
  instance,
1860
1931
  scopeToken,
1861
1932
  contextfulParent,
1933
+ renderContext,
1862
1934
  shadowSlottedContent,
1863
1935
  lightSlottedContentMap,
1864
1936
  scopedSlottedContentMap
@@ -2185,9 +2257,9 @@ const bConditionalSlot = (esTemplateWithYield `
2185
2257
  if (isLightDom) {
2186
2258
  const isScopedSlot = ${ /* isScopedSlot */estreeToolkit.is.literal};
2187
2259
  const isSlotted = ${ /* isSlotted */estreeToolkit.is.literal};
2188
- const slotName = ${ /* slotName */estreeToolkit.is.expression};
2189
- const lightGenerators = lightSlottedContent?.[slotName ?? ""];
2190
- const scopedGenerators = scopedSlottedContent?.[slotName ?? ""];
2260
+ const slotName = ${ /* slotName */estreeToolkit.is.expression} ?? "";
2261
+ const lightGenerators = lightSlottedContent?.[slotName];
2262
+ const scopedGenerators = scopedSlottedContent?.[slotName];
2191
2263
  const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
2192
2264
  const generators = isScopedSlot ? scopedGenerators : lightGenerators;
2193
2265
  /*
@@ -2494,12 +2566,12 @@ function templateIrToEsTree(node, contextOpts) {
2494
2566
  // TODO [#4663]: Render mode mismatch between template and compiler should throw.
2495
2567
  const bExportTemplate = (esTemplate `
2496
2568
  export default async function* __lwcTmpl(
2497
- // This is where $$emit comes from
2498
2569
  shadowSlottedContent,
2499
2570
  lightSlottedContent,
2500
2571
  scopedSlottedContent,
2501
2572
  Cmp,
2502
- instance
2573
+ instance,
2574
+ renderContext
2503
2575
  ) {
2504
2576
  // Deliberately using let so we can mutate as many times as we want in the same scope.
2505
2577
  // These should be scoped to the "tmpl" function however, to avoid conflicts with other templates.
@@ -2521,7 +2593,7 @@ const bExportTemplate = (esTemplate `
2521
2593
  const { stylesheets: staticStylesheets } = Cmp;
2522
2594
  if (defaultStylesheets || defaultScopedStylesheets || staticStylesheets) {
2523
2595
  yield renderStylesheets(
2524
- $$emit,
2596
+ renderContext,
2525
2597
  defaultStylesheets,
2526
2598
  defaultScopedStylesheets,
2527
2599
  staticStylesheets,
@@ -2644,5 +2716,5 @@ function compileTemplateForSSR(src, filename, options, mode = shared.DEFAULT_SSR
2644
2716
 
2645
2717
  exports.compileComponentForSSR = compileComponentForSSR;
2646
2718
  exports.compileTemplateForSSR = compileTemplateForSSR;
2647
- /** version: 8.24.0 */
2719
+ /** version: 8.25.0 */
2648
2720
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -3,43 +3,207 @@
3
3
  */
4
4
  import { AMBIGUOUS_PROP_SET, DISALLOWED_PROP_SET, LWC_VERSION_COMMENT, isAPIFeatureEnabled, normalizeStyleAttributeValue, normalizeTabIndex, StringReplace, StringTrim, entries, kebabCaseToCamelCase, isUndefined, HTML_NAMESPACE, isVoidElement, isBooleanAttribute, DEFAULT_SSR_MODE, generateCustomElementTagName } from '@lwc/shared';
5
5
  import { generate } from 'astring';
6
- import { builders, traverse, is } from 'estree-toolkit';
6
+ import { traverse, is, builders } from 'estree-toolkit';
7
7
  import { parseModule } from 'meriyah';
8
8
  import { generateCompilerError, DecoratorErrors, SsrCompilerErrors, LWCClassErrors } from '@lwc/errors';
9
9
  import { produce } from 'immer';
10
- import { parse as parse$1 } from 'node:path';
11
10
  import { parse } from 'acorn';
11
+ import { parse as parse$1 } from 'node:path';
12
12
  import { generateScopeTokens, bindExpression, toPropertyName, kebabcaseToCamelcase, parse as parse$2 } from '@lwc/template-compiler';
13
13
  import { builders as builders$1 } from 'estree-toolkit/dist/builders';
14
14
  import { isValidES3Identifier } from '@babel/types';
15
15
  import { inspect } from 'util';
16
16
 
17
+ /*
18
+ * Copyright (c) 2024, salesforce.com, inc.
19
+ * All rights reserved.
20
+ * SPDX-License-Identifier: MIT
21
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
22
+ */
23
+ /** Placeholder value to use to opt out of validation. */
24
+ const NO_VALIDATION = false;
25
+ /**
26
+ * `esTemplate` generates JS code with "holes" to be filled later. In order to have a valid AST,
27
+ * it uses identifiers with this prefix at the location of the holes.
28
+ */
29
+ const PLACEHOLDER_PREFIX = '__lwc_ESTEMPLATE_PLACEHOLDER__';
30
+ const getReplacementNode = (state, placeholderId) => {
31
+ const key = Number(placeholderId.slice(PLACEHOLDER_PREFIX.length));
32
+ const nodeCount = state.replacementNodes.length;
33
+ if (key >= nodeCount) {
34
+ throw new Error(`Cannot use index ${key} when only ${nodeCount} values have been provided.`);
35
+ }
36
+ const validateReplacement = state.placeholderToValidator.get(key);
37
+ const replacementNode = state.replacementNodes[key];
38
+ if (validateReplacement &&
39
+ !(Array.isArray(replacementNode)
40
+ ? replacementNode.every(validateReplacement)
41
+ : validateReplacement(replacementNode))) {
42
+ const expectedType = validateReplacement.__debugName ||
43
+ validateReplacement.name ||
44
+ '(could not determine)';
45
+ const actualType = Array.isArray(replacementNode)
46
+ ? `[${replacementNode.map((n) => n && n.type).join(', ')}]`
47
+ : replacementNode?.type;
48
+ throw new Error(`Validation failed for templated node. Expected type ${expectedType}, but received ${actualType}.`);
49
+ }
50
+ return replacementNode;
51
+ };
52
+ const visitors$2 = {
53
+ Identifier(path, state) {
54
+ if (path.node?.name.startsWith(PLACEHOLDER_PREFIX)) {
55
+ const replacementNode = getReplacementNode(state, path.node.name);
56
+ if (replacementNode === null) {
57
+ path.remove();
58
+ }
59
+ else if (Array.isArray(replacementNode)) {
60
+ if (replacementNode.length === 0) {
61
+ path.remove();
62
+ }
63
+ else {
64
+ if (path.parentPath?.node?.type === 'ExpressionStatement') {
65
+ path.parentPath.replaceWithMultiple(replacementNode);
66
+ }
67
+ else {
68
+ path.replaceWithMultiple(replacementNode);
69
+ }
70
+ }
71
+ }
72
+ else {
73
+ path.replaceWith(replacementNode);
74
+ }
75
+ }
76
+ },
77
+ Literal(path, state) {
78
+ if (typeof path.node?.value === 'string' &&
79
+ path.node.value.startsWith(PLACEHOLDER_PREFIX)) {
80
+ // A literal can only be replaced with a single node
81
+ const replacementNode = getReplacementNode(state, path.node.value);
82
+ path.replaceWith(replacementNode);
83
+ }
84
+ },
85
+ };
86
+ function esTemplateImpl(javascriptSegments, validators, wrap, unwrap) {
87
+ let placeholderCount = 0;
88
+ let parsableCode = javascriptSegments[0];
89
+ const placeholderToValidator = new Map();
90
+ for (let i = 1; i < javascriptSegments.length; i += 1) {
91
+ const segment = javascriptSegments[i];
92
+ const validator = validators[i - 1]; // always one less value than strings in template literals
93
+ if (typeof validator === 'function' || validator === NO_VALIDATION) {
94
+ // Template slot will be filled by a *new* argument passed to the generated function
95
+ if (validator !== NO_VALIDATION) {
96
+ placeholderToValidator.set(placeholderCount, validator);
97
+ }
98
+ parsableCode += `${PLACEHOLDER_PREFIX}${placeholderCount}`;
99
+ placeholderCount += 1;
100
+ }
101
+ else {
102
+ // Template slot uses a *previously defined* argument passed to the generated function
103
+ if (validator >= placeholderCount) {
104
+ throw new Error(`Reference to argument ${validator} at index ${i} cannot be used. Only ${placeholderCount - 1} arguments have been defined.`);
105
+ }
106
+ parsableCode += `${PLACEHOLDER_PREFIX}${validator}`;
107
+ }
108
+ parsableCode += segment;
109
+ }
110
+ if (wrap) {
111
+ parsableCode = wrap(parsableCode);
112
+ }
113
+ const originalAstProgram = parse(parsableCode, {
114
+ ecmaVersion: 2022,
115
+ allowAwaitOutsideFunction: true,
116
+ allowReturnOutsideFunction: true,
117
+ allowSuperOutsideMethod: true,
118
+ allowImportExportEverywhere: true,
119
+ locations: false,
120
+ });
121
+ let originalAst;
122
+ const finalCharacter = javascriptSegments.at(-1)?.trimEnd()?.at(-1);
123
+ if (originalAstProgram.body.length === 1) {
124
+ originalAst =
125
+ finalCharacter === ';' && originalAstProgram.body[0].type === 'ExpressionStatement'
126
+ ? (originalAst = originalAstProgram.body[0].expression)
127
+ : (originalAst = originalAstProgram.body[0]);
128
+ }
129
+ else {
130
+ originalAst = originalAstProgram.body;
131
+ }
132
+ // Turns Acorn AST objects into POJOs, for use with Immer.
133
+ originalAst = JSON.parse(JSON.stringify(originalAst));
134
+ return function templatedAst(...replacementNodes) {
135
+ const result = produce(originalAst, (astDraft) => traverse(astDraft, visitors$2, {
136
+ placeholderToValidator,
137
+ replacementNodes,
138
+ }));
139
+ return (unwrap ? unwrap(result) : result);
140
+ };
141
+ }
142
+ /**
143
+ * Template literal tag that generates a builder function. Like estree's `builders`, but for more
144
+ * complex structures. The template values should be estree `is` validators or a back reference to
145
+ * a previous slot (to re-use the referenced value).
146
+ *
147
+ * To have the generated function return a particular node type, the generic comes _after_ the
148
+ * template literal. Kinda weird, but it's necessary to infer the types of the template values.
149
+ * (If it were at the start, we'd need to explicitly provide _all_ type params. Tedious!)
150
+ * @example
151
+ * const bSum = esTemplate`(${is.identifier}, ${is.identifier}) => ${0} + ${1}`<EsArrowFunctionExpression>
152
+ * const sumFuncNode = bSum(b.identifier('a'), b.identifier('b'))
153
+ * // `sumFuncNode` is an AST node representing `(a, b) => a + b`
154
+ */
155
+ function esTemplate(javascriptSegments, ...Validators) {
156
+ return esTemplateImpl(javascriptSegments, Validators);
157
+ }
158
+ /** Similar to {@linkcode esTemplate}, but supports `yield` expressions. */
159
+ function esTemplateWithYield(javascriptSegments, ...validators) {
160
+ const wrap = (code) => `function* placeholder() {${code}}`;
161
+ const unwrap = (node) => node.body.body.length === 1 ? node.body.body[0] : node.body.body;
162
+ return esTemplateImpl(javascriptSegments, validators, wrap, unwrap);
163
+ }
164
+
17
165
  /*
18
166
  * Copyright (c) 2024, Salesforce, Inc.
19
167
  * All rights reserved.
20
168
  * SPDX-License-Identifier: MIT
21
169
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
22
170
  */
23
- const EMIT_IDENT = builders.identifier('$$emit');
24
171
  /** Function names that may be transmogrified. All should start with `__lwc`. */
25
172
  // Rollup may rename variables to prevent shadowing. When it does, it uses the format `foo$0`, `foo$1`, etc.
26
173
  const TRANSMOGRIFY_TARGET = /^__lwc(Generate|Tmpl).*$/;
27
- const isWithinFn = (nodePath) => {
28
- const { node } = nodePath;
29
- if (!node) {
30
- return false;
31
- }
32
- if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
33
- node.id &&
34
- TRANSMOGRIFY_TARGET.test(node.id.name)) {
35
- return true;
36
- }
37
- if (nodePath.parentPath) {
38
- return isWithinFn(nodePath.parentPath);
174
+ const isNonArrowFunction = (node) => {
175
+ return is.functionDeclaration(node) || is.functionExpression(node);
176
+ };
177
+ /**
178
+ * Determines whether a node is a function we want to transmogrify or within one, at any level.
179
+ */
180
+ const isWithinTargetFunc = (nodePath) => {
181
+ let path = isNonArrowFunction(nodePath)
182
+ ? nodePath
183
+ : nodePath.findParent(isNonArrowFunction);
184
+ while (path?.node) {
185
+ const { id } = path.node;
186
+ if (id && TRANSMOGRIFY_TARGET.test(id.name)) {
187
+ return true;
188
+ }
189
+ path = path.findParent(isNonArrowFunction);
39
190
  }
40
191
  return false;
41
192
  };
42
- const visitors$2 = {
193
+ /**
194
+ * Determines whether the nearest function encapsulating this node is a function we transmogrify.
195
+ */
196
+ const isImmediateWithinTargetFunc = (nodePath) => {
197
+ const parentFunc = nodePath.findParent(is.function);
198
+ return Boolean(parentFunc &&
199
+ isNonArrowFunction(parentFunc) &&
200
+ parentFunc.node?.id &&
201
+ TRANSMOGRIFY_TARGET.test(parentFunc.node.id.name));
202
+ };
203
+ const bDeclareYieldVar = (esTemplate `let __lwcYield = '';`);
204
+ const bAppendToYieldVar = (esTemplate `__lwcYield += ${is.expression};`);
205
+ const bReturnYieldVar = (esTemplate `return __lwcYield;`);
206
+ const visitors$1 = {
43
207
  // @ts-expect-error types for `traverse` do not support sharing a visitor between node types:
44
208
  // https://github.com/sarsamurmu/estree-toolkit/issues/20
45
209
  'FunctionDeclaration|FunctionExpression'(path, state) {
@@ -50,12 +214,12 @@ const visitors$2 = {
50
214
  // Component authors might conceivably use async generator functions in their own code. Therefore,
51
215
  // when traversing & transforming written+generated code, we need to disambiguate generated async
52
216
  // generator functions from those that were written by the component author.
53
- if (!isWithinFn(path)) {
217
+ if (!isWithinTargetFunc(path)) {
54
218
  return;
55
219
  }
56
220
  node.generator = false;
57
221
  node.async = state.mode === 'async';
58
- node.params.unshift(EMIT_IDENT);
222
+ node.body.body = [bDeclareYieldVar(), ...node.body.body, bReturnYieldVar()];
59
223
  },
60
224
  YieldExpression(path, state) {
61
225
  const { node } = path;
@@ -65,26 +229,15 @@ const visitors$2 = {
65
229
  // Component authors might conceivably use generator functions within their own code. Therefore,
66
230
  // when traversing & transforming written+generated code, we need to disambiguate generated yield
67
231
  // expressions from those that were written by the component author.
68
- if (!isWithinFn(path)) {
232
+ if (!isWithinTargetFunc(path)) {
69
233
  return;
70
234
  }
71
- if (node.delegate) {
72
- // transform `yield* foo(arg)` into `foo($$emit, arg)` or `await foo($$emit, arg)`
73
- if (node.argument?.type !== 'CallExpression') {
74
- throw new Error('Implementation error: cannot transmogrify complex yield-from expressions');
75
- }
76
- const callExpr = node.argument;
77
- callExpr.arguments.unshift(EMIT_IDENT);
78
- path.replaceWith(state.mode === 'sync' ? callExpr : builders.awaitExpression(callExpr));
79
- }
80
- else {
81
- // transform `yield foo` into `$$emit(foo)`
82
- const emittedExpression = node.argument;
83
- if (!emittedExpression) {
84
- throw new Error('Implementation error: cannot transform a yield expression that yields nothing');
85
- }
86
- path.replaceWith(builders.callExpression(EMIT_IDENT, [emittedExpression]));
235
+ const arg = node.argument;
236
+ if (!arg) {
237
+ const type = node.delegate ? 'yield*' : 'yield';
238
+ throw new Error(`Cannot transmogrify ${type} statement without an argument.`);
87
239
  }
240
+ path.replaceWith(bAppendToYieldVar(state.mode === 'sync' ? arg : builders.awaitExpression(arg)));
88
241
  },
89
242
  ImportSpecifier(path, _state) {
90
243
  // @lwc/ssr-runtime has a couple of helper functions that need to conform to either the generator or
@@ -111,6 +264,17 @@ const visitors$2 = {
111
264
  node.imported.name = 'renderAttrsNoYield';
112
265
  }
113
266
  },
267
+ ReturnStatement(path) {
268
+ if (!isImmediateWithinTargetFunc(path)) {
269
+ return;
270
+ }
271
+ // The transmogrify result returns __lwcYield, so we skip it
272
+ const arg = path.node?.argument;
273
+ if (is.identifier(arg) && arg.name === '__lwcYield') {
274
+ return;
275
+ }
276
+ throw new Error('Cannot transmogrify function with return statement.');
277
+ },
114
278
  };
115
279
  /**
116
280
  * Transforms async-generator code into either the async or synchronous alternatives that are
@@ -156,7 +320,7 @@ function transmogrify(compiledComponentAst, mode = 'sync') {
156
320
  const state = {
157
321
  mode,
158
322
  };
159
- return produce(compiledComponentAst, (astDraft) => traverse(astDraft, visitors$2, state));
323
+ return produce(compiledComponentAst, (astDraft) => traverse(astDraft, visitors$1, state));
160
324
  }
161
325
 
162
326
  /******************************************************************************
@@ -365,154 +529,6 @@ function catalogStaticStylesheets(ids, state) {
365
529
  }
366
530
  }
367
531
 
368
- /*
369
- * Copyright (c) 2024, salesforce.com, inc.
370
- * All rights reserved.
371
- * SPDX-License-Identifier: MIT
372
- * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
373
- */
374
- /** Placeholder value to use to opt out of validation. */
375
- const NO_VALIDATION = false;
376
- /**
377
- * `esTemplate` generates JS code with "holes" to be filled later. In order to have a valid AST,
378
- * it uses identifiers with this prefix at the location of the holes.
379
- */
380
- const PLACEHOLDER_PREFIX = '__lwc_ESTEMPLATE_PLACEHOLDER__';
381
- const getReplacementNode = (state, placeholderId) => {
382
- const key = Number(placeholderId.slice(PLACEHOLDER_PREFIX.length));
383
- const nodeCount = state.replacementNodes.length;
384
- if (key >= nodeCount) {
385
- throw new Error(`Cannot use index ${key} when only ${nodeCount} values have been provided.`);
386
- }
387
- const validateReplacement = state.placeholderToValidator.get(key);
388
- const replacementNode = state.replacementNodes[key];
389
- if (validateReplacement &&
390
- !(Array.isArray(replacementNode)
391
- ? replacementNode.every(validateReplacement)
392
- : validateReplacement(replacementNode))) {
393
- const expectedType = validateReplacement.__debugName ||
394
- validateReplacement.name ||
395
- '(could not determine)';
396
- const actualType = Array.isArray(replacementNode)
397
- ? `[${replacementNode.map((n) => n && n.type).join(', ')}]`
398
- : replacementNode?.type;
399
- throw new Error(`Validation failed for templated node. Expected type ${expectedType}, but received ${actualType}.`);
400
- }
401
- return replacementNode;
402
- };
403
- const visitors$1 = {
404
- Identifier(path, state) {
405
- if (path.node?.name.startsWith(PLACEHOLDER_PREFIX)) {
406
- const replacementNode = getReplacementNode(state, path.node.name);
407
- if (replacementNode === null) {
408
- path.remove();
409
- }
410
- else if (Array.isArray(replacementNode)) {
411
- if (replacementNode.length === 0) {
412
- path.remove();
413
- }
414
- else {
415
- if (path.parentPath?.node?.type === 'ExpressionStatement') {
416
- path.parentPath.replaceWithMultiple(replacementNode);
417
- }
418
- else {
419
- path.replaceWithMultiple(replacementNode);
420
- }
421
- }
422
- }
423
- else {
424
- path.replaceWith(replacementNode);
425
- }
426
- }
427
- },
428
- Literal(path, state) {
429
- if (typeof path.node?.value === 'string' &&
430
- path.node.value.startsWith(PLACEHOLDER_PREFIX)) {
431
- // A literal can only be replaced with a single node
432
- const replacementNode = getReplacementNode(state, path.node.value);
433
- path.replaceWith(replacementNode);
434
- }
435
- },
436
- };
437
- function esTemplateImpl(javascriptSegments, validators, wrap, unwrap) {
438
- let placeholderCount = 0;
439
- let parsableCode = javascriptSegments[0];
440
- const placeholderToValidator = new Map();
441
- for (let i = 1; i < javascriptSegments.length; i += 1) {
442
- const segment = javascriptSegments[i];
443
- const validator = validators[i - 1]; // always one less value than strings in template literals
444
- if (typeof validator === 'function' || validator === NO_VALIDATION) {
445
- // Template slot will be filled by a *new* argument passed to the generated function
446
- if (validator !== NO_VALIDATION) {
447
- placeholderToValidator.set(placeholderCount, validator);
448
- }
449
- parsableCode += `${PLACEHOLDER_PREFIX}${placeholderCount}`;
450
- placeholderCount += 1;
451
- }
452
- else {
453
- // Template slot uses a *previously defined* argument passed to the generated function
454
- if (validator >= placeholderCount) {
455
- throw new Error(`Reference to argument ${validator} at index ${i} cannot be used. Only ${placeholderCount - 1} arguments have been defined.`);
456
- }
457
- parsableCode += `${PLACEHOLDER_PREFIX}${validator}`;
458
- }
459
- parsableCode += segment;
460
- }
461
- if (wrap) {
462
- parsableCode = wrap(parsableCode);
463
- }
464
- const originalAstProgram = parse(parsableCode, {
465
- ecmaVersion: 2022,
466
- allowAwaitOutsideFunction: true,
467
- allowReturnOutsideFunction: true,
468
- allowSuperOutsideMethod: true,
469
- allowImportExportEverywhere: true,
470
- locations: false,
471
- });
472
- let originalAst;
473
- const finalCharacter = javascriptSegments.at(-1)?.trimEnd()?.at(-1);
474
- if (originalAstProgram.body.length === 1) {
475
- originalAst =
476
- finalCharacter === ';' && originalAstProgram.body[0].type === 'ExpressionStatement'
477
- ? (originalAst = originalAstProgram.body[0].expression)
478
- : (originalAst = originalAstProgram.body[0]);
479
- }
480
- else {
481
- originalAst = originalAstProgram.body;
482
- }
483
- // Turns Acorn AST objects into POJOs, for use with Immer.
484
- originalAst = JSON.parse(JSON.stringify(originalAst));
485
- return function templatedAst(...replacementNodes) {
486
- const result = produce(originalAst, (astDraft) => traverse(astDraft, visitors$1, {
487
- placeholderToValidator,
488
- replacementNodes,
489
- }));
490
- return (unwrap ? unwrap(result) : result);
491
- };
492
- }
493
- /**
494
- * Template literal tag that generates a builder function. Like estree's `builders`, but for more
495
- * complex structures. The template values should be estree `is` validators or a back reference to
496
- * a previous slot (to re-use the referenced value).
497
- *
498
- * To have the generated function return a particular node type, the generic comes _after_ the
499
- * template literal. Kinda weird, but it's necessary to infer the types of the template values.
500
- * (If it were at the start, we'd need to explicitly provide _all_ type params. Tedious!)
501
- * @example
502
- * const bSum = esTemplate`(${is.identifier}, ${is.identifier}) => ${0} + ${1}`<EsArrowFunctionExpression>
503
- * const sumFuncNode = bSum(b.identifier('a'), b.identifier('b'))
504
- * // `sumFuncNode` is an AST node representing `(a, b) => a + b`
505
- */
506
- function esTemplate(javascriptSegments, ...Validators) {
507
- return esTemplateImpl(javascriptSegments, Validators);
508
- }
509
- /** Similar to {@linkcode esTemplate}, but supports `yield` expressions. */
510
- function esTemplateWithYield(javascriptSegments, ...validators) {
511
- const wrap = (code) => `function* placeholder() {${code}}`;
512
- const unwrap = (node) => node.body.body.length === 1 ? node.body.body[0] : node.body.body;
513
- return esTemplateImpl(javascriptSegments, validators, wrap, unwrap);
514
- }
515
-
516
532
  /*
517
533
  * Copyright (c) 2024, Salesforce, Inc.
518
534
  * All rights reserved.
@@ -716,13 +732,13 @@ const bGenerateMarkup = (esTemplate `
716
732
  enumerable: false,
717
733
  writable: false,
718
734
  value: async function* __lwcGenerateMarkup(
719
- // The $$emit function is magically inserted here
720
735
  tagName,
721
736
  props,
722
737
  attrs,
723
738
  parent,
724
739
  scopeToken,
725
740
  contextfulParent,
741
+ renderContext,
726
742
  shadowSlottedContent,
727
743
  lightSlottedContent,
728
744
  scopedSlottedContent,
@@ -766,7 +782,8 @@ const bGenerateMarkup = (esTemplate `
766
782
  lightSlottedContent,
767
783
  scopedSlottedContent,
768
784
  ${ /*component class*/0},
769
- instance
785
+ instance,
786
+ renderContext
770
787
  );
771
788
  yield \`</\${tagName}>\`;
772
789
  }
@@ -1300,33 +1317,86 @@ function getRootIdentifier$1(node) {
1300
1317
  * SPDX-License-Identifier: MIT
1301
1318
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1302
1319
  */
1303
- function optimizeAdjacentYieldStmts(statements) {
1304
- let prevStmt = null;
1305
- return statements
1306
- .map((stmt) => {
1307
- if (
1308
- // Check if the current statement and previous statement are
1309
- // both yield expression statements that yield a string literal.
1310
- prevStmt &&
1311
- is.expressionStatement(prevStmt) &&
1312
- is.yieldExpression(prevStmt.expression) &&
1313
- !prevStmt.expression.delegate &&
1314
- prevStmt.expression.argument &&
1315
- is.literal(prevStmt.expression.argument) &&
1316
- typeof prevStmt.expression.argument.value === 'string' &&
1317
- is.expressionStatement(stmt) &&
1318
- is.yieldExpression(stmt.expression) &&
1319
- !stmt.expression.delegate &&
1320
- stmt.expression.argument &&
1321
- is.literal(stmt.expression.argument) &&
1322
- typeof stmt.expression.argument.value === 'string') {
1323
- prevStmt.expression.argument.value += stmt.expression.argument.value;
1320
+ const bYieldTernary = (esTemplateWithYield `yield ${is.expression} ? ${is.expression} : ${is.expression}`);
1321
+ const bOptimizedYield = (esTemplateWithYield `yield ${is.expression};`);
1322
+ function isOptimizableYield(stmt) {
1323
+ return (is.expressionStatement(stmt) &&
1324
+ is.yieldExpression(stmt.expression) &&
1325
+ stmt.expression.delegate === false);
1326
+ }
1327
+ /** Returns null if the statement cannot be optimized. */
1328
+ function optimizeSingleStatement(stmt) {
1329
+ if (is.blockStatement(stmt)) {
1330
+ // `if (cond) { ... }` => optimize inner yields and see if we can condense
1331
+ const optimizedBlock = optimizeAdjacentYieldStmts(stmt.body);
1332
+ // More than one statement cannot be optimized into a single yield
1333
+ if (optimizedBlock.length !== 1)
1324
1334
  return null;
1335
+ const [optimized] = optimizedBlock;
1336
+ return isOptimizableYield(optimized) ? optimized : null;
1337
+ }
1338
+ else if (is.expressionStatement(stmt)) {
1339
+ // `if (cond) expression` => just check if expression is a yield
1340
+ return is.yieldExpression(stmt) ? stmt : null;
1341
+ }
1342
+ else {
1343
+ // Can only optimize expression/block statements
1344
+ return null;
1345
+ }
1346
+ }
1347
+ /**
1348
+ * Tries to reduce if statements that only contain yields into a single yielded ternary
1349
+ * Returns null if the statement cannot be optimized.
1350
+ */
1351
+ function optimizeIfStatement(stmt) {
1352
+ const consequent = optimizeSingleStatement(stmt.consequent)?.expression.argument;
1353
+ if (!consequent) {
1354
+ return null;
1355
+ }
1356
+ const alternate = stmt.alternate
1357
+ ? optimizeSingleStatement(stmt.alternate)?.expression.argument
1358
+ : builders.literal('');
1359
+ if (!alternate) {
1360
+ return null;
1361
+ }
1362
+ return bYieldTernary(stmt.test, consequent, alternate);
1363
+ }
1364
+ function optimizeAdjacentYieldStmts(statements) {
1365
+ return statements.reduce((result, stmt) => {
1366
+ if (is.ifStatement(stmt)) {
1367
+ const optimized = optimizeIfStatement(stmt);
1368
+ if (optimized) {
1369
+ stmt = optimized;
1370
+ }
1325
1371
  }
1326
- prevStmt = stmt;
1327
- return stmt;
1328
- })
1329
- .filter((el) => el !== null);
1372
+ const prev = result.at(-1);
1373
+ if (!isOptimizableYield(stmt) || !isOptimizableYield(prev)) {
1374
+ // nothing to do
1375
+ return [...result, stmt];
1376
+ }
1377
+ const arg = stmt.expression.argument;
1378
+ if (!arg || (is.literal(arg) && arg.value === '')) {
1379
+ // bare `yield` and `yield ""` amount to nothing, so we can drop them
1380
+ return result;
1381
+ }
1382
+ const newArg = produce(prev.expression.argument, (draft) => {
1383
+ if (is.literal(arg) && typeof arg.value === 'string') {
1384
+ let concatTail = draft;
1385
+ while (is.binaryExpression(concatTail) && concatTail.operator === '+') {
1386
+ concatTail = concatTail.right;
1387
+ }
1388
+ if (is.literal(concatTail) && typeof concatTail.value === 'string') {
1389
+ // conat adjacent strings now, rather than at runtime
1390
+ concatTail.value += arg.value;
1391
+ return draft;
1392
+ }
1393
+ }
1394
+ // concat arbitrary values at runtime
1395
+ return builders.binaryExpression('+', draft, arg);
1396
+ });
1397
+ // replace the last `+` chain with a new one with the new arg
1398
+ return [...result.slice(0, -1), bOptimizedYield(newArg)];
1399
+ }, []);
1330
1400
  }
1331
1401
  function bAttributeValue(node, attrName) {
1332
1402
  if (!('attributes' in node)) {
@@ -1790,13 +1860,14 @@ is.statement}
1790
1860
  instance,
1791
1861
  scopeToken,
1792
1862
  contextfulParent,
1863
+ renderContext,
1793
1864
  shadowSlottedContent,
1794
1865
  lightSlottedContentMap,
1795
1866
  scopedSlottedContentMap
1796
1867
  );
1797
1868
  } else {
1798
1869
  yield \`<\${tagName}>\`;
1799
- yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${ /* Component */3}, instance)
1870
+ yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${ /* Component */3}, instance, renderContext)
1800
1871
  yield \`</\${tagName}>\`;
1801
1872
  }
1802
1873
  }
@@ -1855,6 +1926,7 @@ is.statement}
1855
1926
  instance,
1856
1927
  scopeToken,
1857
1928
  contextfulParent,
1929
+ renderContext,
1858
1930
  shadowSlottedContent,
1859
1931
  lightSlottedContentMap,
1860
1932
  scopedSlottedContentMap
@@ -2181,9 +2253,9 @@ const bConditionalSlot = (esTemplateWithYield `
2181
2253
  if (isLightDom) {
2182
2254
  const isScopedSlot = ${ /* isScopedSlot */is.literal};
2183
2255
  const isSlotted = ${ /* isSlotted */is.literal};
2184
- const slotName = ${ /* slotName */is.expression};
2185
- const lightGenerators = lightSlottedContent?.[slotName ?? ""];
2186
- const scopedGenerators = scopedSlottedContent?.[slotName ?? ""];
2256
+ const slotName = ${ /* slotName */is.expression} ?? "";
2257
+ const lightGenerators = lightSlottedContent?.[slotName];
2258
+ const scopedGenerators = scopedSlottedContent?.[slotName];
2187
2259
  const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
2188
2260
  const generators = isScopedSlot ? scopedGenerators : lightGenerators;
2189
2261
  /*
@@ -2490,12 +2562,12 @@ function templateIrToEsTree(node, contextOpts) {
2490
2562
  // TODO [#4663]: Render mode mismatch between template and compiler should throw.
2491
2563
  const bExportTemplate = (esTemplate `
2492
2564
  export default async function* __lwcTmpl(
2493
- // This is where $$emit comes from
2494
2565
  shadowSlottedContent,
2495
2566
  lightSlottedContent,
2496
2567
  scopedSlottedContent,
2497
2568
  Cmp,
2498
- instance
2569
+ instance,
2570
+ renderContext
2499
2571
  ) {
2500
2572
  // Deliberately using let so we can mutate as many times as we want in the same scope.
2501
2573
  // These should be scoped to the "tmpl" function however, to avoid conflicts with other templates.
@@ -2517,7 +2589,7 @@ const bExportTemplate = (esTemplate `
2517
2589
  const { stylesheets: staticStylesheets } = Cmp;
2518
2590
  if (defaultStylesheets || defaultScopedStylesheets || staticStylesheets) {
2519
2591
  yield renderStylesheets(
2520
- $$emit,
2592
+ renderContext,
2521
2593
  defaultStylesheets,
2522
2594
  defaultScopedStylesheets,
2523
2595
  staticStylesheets,
@@ -2639,5 +2711,5 @@ function compileTemplateForSSR(src, filename, options, mode = DEFAULT_SSR_MODE)
2639
2711
  }
2640
2712
 
2641
2713
  export { compileComponentForSSR, compileTemplateForSSR };
2642
- /** version: 8.24.0 */
2714
+ /** version: 8.25.0 */
2643
2715
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/ssr-compiler",
7
- "version": "8.24.0",
7
+ "version": "8.25.0",
8
8
  "description": "Compile component for use during server-side rendering",
9
9
  "keywords": [
10
10
  "compiler",
@@ -49,9 +49,9 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@babel/types": "7.28.5",
52
- "@lwc/errors": "8.24.0",
53
- "@lwc/shared": "8.24.0",
54
- "@lwc/template-compiler": "8.24.0",
52
+ "@lwc/errors": "8.25.0",
53
+ "@lwc/shared": "8.25.0",
54
+ "@lwc/template-compiler": "8.25.0",
55
55
  "acorn": "8.15.0",
56
56
  "astring": "^1.9.0",
57
57
  "estree-toolkit": "^1.7.13",
@@ -59,7 +59,7 @@
59
59
  "meriyah": "^5.0.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@lwc/babel-plugin-component": "8.24.0",
62
+ "@lwc/babel-plugin-component": "8.25.0",
63
63
  "@types/estree": "^1.0.8"
64
64
  }
65
65
  }