@lwc/ssr-compiler 9.0.2 → 9.0.3

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2720 @@
1
+ /**
2
+ * Copyright (c) 2026 Salesforce, Inc.
3
+ */
4
+ 'use strict';
5
+
6
+ Object.defineProperty(exports, '__esModule', { value: true });
7
+
8
+ var shared = require('@lwc/shared');
9
+ var astring = require('astring');
10
+ var estreeToolkit = require('estree-toolkit');
11
+ var meriyah = require('meriyah');
12
+ var errors = require('@lwc/errors');
13
+ var immer = require('immer');
14
+ var acorn = require('acorn');
15
+ var node_path = require('node:path');
16
+ var templateCompiler = require('@lwc/template-compiler');
17
+ var builders = require('estree-toolkit/dist/builders');
18
+ var types = require('@babel/types');
19
+ var node_util = require('node:util');
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
+
169
+ /*
170
+ * Copyright (c) 2024, Salesforce, Inc.
171
+ * All rights reserved.
172
+ * SPDX-License-Identifier: MIT
173
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
174
+ */
175
+ /** Function names that may be transmogrified. All should start with `__lwc`. */
176
+ // Rollup may rename variables to prevent shadowing. When it does, it uses the format `foo$0`, `foo$1`, etc.
177
+ const TRANSMOGRIFY_TARGET = /^__lwc(Generate|Tmpl).*$/;
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);
194
+ }
195
+ return false;
196
+ };
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 = {
211
+ // @ts-expect-error types for `traverse` do not support sharing a visitor between node types:
212
+ // https://github.com/sarsamurmu/estree-toolkit/issues/20
213
+ 'FunctionDeclaration|FunctionExpression'(path, state) {
214
+ const { node } = path;
215
+ if (!node?.async || !node?.generator) {
216
+ return;
217
+ }
218
+ // Component authors might conceivably use async generator functions in their own code. Therefore,
219
+ // when traversing & transforming written+generated code, we need to disambiguate generated async
220
+ // generator functions from those that were written by the component author.
221
+ if (!isWithinTargetFunc(path)) {
222
+ return;
223
+ }
224
+ node.generator = false;
225
+ node.async = state.mode === 'async';
226
+ node.body.body = [bDeclareYieldVar(), ...node.body.body, bReturnYieldVar()];
227
+ },
228
+ YieldExpression(path, state) {
229
+ const { node } = path;
230
+ if (!node) {
231
+ return;
232
+ }
233
+ // Component authors might conceivably use generator functions within their own code. Therefore,
234
+ // when traversing & transforming written+generated code, we need to disambiguate generated yield
235
+ // expressions from those that were written by the component author.
236
+ if (!isWithinTargetFunc(path)) {
237
+ return;
238
+ }
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.`);
243
+ }
244
+ path.replaceWith(bAppendToYieldVar(state.mode === 'sync' ? arg : estreeToolkit.builders.awaitExpression(arg)));
245
+ },
246
+ ImportSpecifier(path, _state) {
247
+ // @lwc/ssr-runtime has a couple of helper functions that need to conform to either the generator or
248
+ // no-generator compilation mode/paradigm. Since these are simple helper functions, we can maintain
249
+ // two implementations of each helper method:
250
+ //
251
+ // - renderAttrs vs renderAttrsNoYield
252
+ // - fallbackTmpl vs fallbackTmplNoYield
253
+ //
254
+ // If this becomes too burdensome to maintain, we can officially deprecate the generator-based approach
255
+ // and switch the @lwc/ssr-runtime implementation wholesale over to the no-generator paradigm.
256
+ const { node } = path;
257
+ if (!node || node.imported.type !== 'Identifier') {
258
+ throw new Error('Implementation error: unexpected missing identifier in import specifier');
259
+ }
260
+ if (path.parent?.type !== 'ImportDeclaration' ||
261
+ path.parent.source.value !== '@lwc/ssr-runtime') {
262
+ return;
263
+ }
264
+ if (node.imported.name === 'fallbackTmpl') {
265
+ node.imported.name = 'fallbackTmplNoYield';
266
+ }
267
+ else if (node.imported.name === 'renderAttrs') {
268
+ node.imported.name = 'renderAttrsNoYield';
269
+ }
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
+ },
282
+ };
283
+ /**
284
+ * Transforms async-generator code into either the async or synchronous alternatives that are
285
+ * ~semantically equivalent. For example, this template:
286
+ *
287
+ * <template>
288
+ * <div>foobar</div>
289
+ * <x-child></x-child>
290
+ * </template>
291
+ *
292
+ * Is compiled into the following JavaScript, intended for execution during SSR & stripped down
293
+ * for the purposes of this example:
294
+ *
295
+ * async function* __lwcTmpl(props, attrs, slottedContent, Cmp, instance) {
296
+ * yield '<div>foobar</div>';
297
+ * const childProps = {};
298
+ * const childAttrs = {};
299
+ * yield* generateChildMarkup("x-child", childProps, childAttrs, childSlottedContentGenerator);
300
+ * }
301
+ *
302
+ * When transmogrified in async-mode, the above generated template function becomes the following:
303
+ *
304
+ * async function __lwcTmpl($$emit, props, attrs, slottedContent, Cmp, instance) {
305
+ * $$emit('<div>foobar</div>');
306
+ * const childProps = {};
307
+ * const childAttrs = {};
308
+ * await generateChildMarkup($$emit, "x-child", childProps, childAttrs, childSlottedContentGenerator);
309
+ * }
310
+ *
311
+ * When transmogrified in sync-mode, the template function becomes the following:
312
+ *
313
+ * function __lwcTmpl($$emit, props, attrs, slottedContent, Cmp, instance) {
314
+ * $$emit('<div>foobar</div>');
315
+ * const childProps = {};
316
+ * const childAttrs = {};
317
+ * generateChildMarkup($$emit, "x-child", childProps, childAttrs, childSlottedContentGenerator);
318
+ * }
319
+ *
320
+ * There are tradeoffs for each of these modes. Notably, the async-yield variety is the easiest to transform
321
+ * into either of the other varieties and, for that reason, is the variety that is "authored" by the SSR compiler.
322
+ */
323
+ function transmogrify(compiledComponentAst, mode = 'sync') {
324
+ const state = {
325
+ mode,
326
+ };
327
+ return immer.produce(compiledComponentAst, (astDraft) => estreeToolkit.traverse(astDraft, visitors$1, state));
328
+ }
329
+
330
+ /******************************************************************************
331
+ Copyright (c) Microsoft Corporation.
332
+
333
+ Permission to use, copy, modify, and/or distribute this software for any
334
+ purpose with or without fee is hereby granted.
335
+
336
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
337
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
338
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
339
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
340
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
341
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
342
+ PERFORMANCE OF THIS SOFTWARE.
343
+ ***************************************************************************** */
344
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
345
+
346
+
347
+ function __classPrivateFieldGet(receiver, state, kind, f) {
348
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
349
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
350
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
351
+ }
352
+
353
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
354
+ var e = new Error(message);
355
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
356
+ };
357
+
358
+ /*
359
+ * Copyright (c) 2024, salesforce.com, inc.
360
+ * All rights reserved.
361
+ * SPDX-License-Identifier: MIT
362
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
363
+ */
364
+ /**
365
+ * Creates an import statement, e.g. `import { foo, bar as $bar$ } from "pkg"`
366
+ * @param imports names to be imported; values can be a string (plain import) or object (aliased)
367
+ * @param source source location to import from; defaults to @lwc/ssr-runtime
368
+ */
369
+ const bImportDeclaration = (imports, source = '@lwc/ssr-runtime') => {
370
+ let parsed;
371
+ if (typeof imports === 'string') {
372
+ parsed = [[imports, undefined]];
373
+ }
374
+ else if (Array.isArray(imports)) {
375
+ parsed = imports.map((imp) => [imp, undefined]);
376
+ }
377
+ else {
378
+ parsed = Object.entries(imports);
379
+ }
380
+ const specifiers = parsed.map(([imported, local]) => {
381
+ if (imported === 'default') {
382
+ return estreeToolkit.builders.importDefaultSpecifier(estreeToolkit.builders.identifier(local));
383
+ }
384
+ else if (imported === '*') {
385
+ return estreeToolkit.builders.importNamespaceSpecifier(estreeToolkit.builders.identifier(local));
386
+ }
387
+ else if (local) {
388
+ return estreeToolkit.builders.importSpecifier(estreeToolkit.builders.identifier(imported), estreeToolkit.builders.identifier(local));
389
+ }
390
+ else {
391
+ return estreeToolkit.builders.importSpecifier(estreeToolkit.builders.identifier(imported));
392
+ }
393
+ });
394
+ return estreeToolkit.builders.importDeclaration(specifiers, estreeToolkit.builders.literal(source));
395
+ };
396
+
397
+ /*
398
+ * Copyright (c) 2024, Salesforce, Inc.
399
+ * All rights reserved.
400
+ * SPDX-License-Identifier: MIT
401
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
402
+ */
403
+ var _ImportManager_map;
404
+ class ImportManager {
405
+ constructor() {
406
+ _ImportManager_map.set(this, new Map());
407
+ }
408
+ /** Add an import to a collection of imports, probably for adding to the AST later. */
409
+ add(imports, source = '@lwc/ssr-runtime') {
410
+ let specifiers;
411
+ if (typeof imports === 'string') {
412
+ specifiers = [[imports, undefined]];
413
+ }
414
+ else if (Array.isArray(imports)) {
415
+ specifiers = imports.map((name) => [name, undefined]);
416
+ }
417
+ else {
418
+ specifiers = Object.entries(imports);
419
+ }
420
+ let specifierMap = __classPrivateFieldGet(this, _ImportManager_map, "f").get(source);
421
+ if (specifierMap) {
422
+ for (const [imported, local] of specifiers) {
423
+ specifierMap.set(imported, local);
424
+ }
425
+ }
426
+ else {
427
+ specifierMap = new Map(specifiers);
428
+ __classPrivateFieldGet(this, _ImportManager_map, "f").set(source, specifierMap);
429
+ }
430
+ }
431
+ /** Get the collection of imports for adding to the AST, probably soon! */
432
+ getImportDeclarations() {
433
+ return Array.from(__classPrivateFieldGet(this, _ImportManager_map, "f"), ([source, specifierMap]) => {
434
+ return bImportDeclaration(Object.fromEntries(specifierMap), source);
435
+ });
436
+ }
437
+ }
438
+ _ImportManager_map = new WeakMap();
439
+
440
+ /*
441
+ * Copyright (c) 2024, salesforce.com, inc.
442
+ * All rights reserved.
443
+ * SPDX-License-Identifier: MIT
444
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
445
+ */
446
+ /**
447
+ * This accomplishes two things:
448
+ *
449
+ * 1. it replaces "lwc" with "@lwc/ssr-runtime" in an import specifier
450
+ * 2. it makes note of the local var name associated with the `LightningElement` import
451
+ */
452
+ function replaceLwcImport(path, state) {
453
+ if (!path.node || !isLwcSource(path)) {
454
+ return;
455
+ }
456
+ for (const specifier of path.node.specifiers) {
457
+ if (specifier.type === 'ImportSpecifier' &&
458
+ specifier.imported.type === 'Identifier' &&
459
+ specifier.imported.name === 'LightningElement') {
460
+ state.lightningElementIdentifier = specifier.local.name;
461
+ break;
462
+ }
463
+ }
464
+ path.replaceWith(estreeToolkit.builders.importDeclaration(structuredClone(path.node.specifiers), estreeToolkit.builders.literal('@lwc/ssr-runtime')));
465
+ }
466
+ /**
467
+ * This handles lwc barrel exports by replacing "lwc" with "@lwc/ssr-runtime"
468
+ */
469
+ function replaceNamedLwcExport(path) {
470
+ if (!path.node || !isLwcSource(path)) {
471
+ return;
472
+ }
473
+ path.replaceWith(estreeToolkit.builders.exportNamedDeclaration(structuredClone(path.node.declaration), structuredClone(path.node.specifiers), estreeToolkit.builders.literal('@lwc/ssr-runtime')));
474
+ }
475
+ /**
476
+ * This handles all lwc barrel exports by replacing "lwc" with "@lwc/ssr-runtime"
477
+ */
478
+ function replaceAllLwcExport(path) {
479
+ if (!path.node || !isLwcSource(path)) {
480
+ return;
481
+ }
482
+ path.replaceWith(estreeToolkit.builders.exportAllDeclaration(estreeToolkit.builders.literal('@lwc/ssr-runtime'), structuredClone(path.node.exported)));
483
+ }
484
+ /**
485
+ * Utility to determine if a node source is 'lwc'
486
+ */
487
+ function isLwcSource(path) {
488
+ return path.node?.source?.value === 'lwc';
489
+ }
490
+
491
+ /*
492
+ * Copyright (c) 2024, salesforce.com, inc.
493
+ * All rights reserved.
494
+ * SPDX-License-Identifier: MIT
495
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
496
+ */
497
+ function catalogAndReplaceStyleImports(path, state) {
498
+ const specifier = path.node.specifiers[0];
499
+ if (typeof path.node.source.value !== 'string' ||
500
+ !path.node.source.value.endsWith('.css') ||
501
+ path.node.specifiers.length !== 1 ||
502
+ specifier.type !== 'ImportDefaultSpecifier') {
503
+ return;
504
+ }
505
+ // Any file ending in `*.scoped.css` which is directly imported into a Component `*.js` file (and assumed
506
+ // to be used for `static stylesheets`) is assumed to be scoped, so needs to be marked as such with a query param.
507
+ // Outside of SSR, this is done by `@lwc/babel-plugin-component`, so we need to emulate its behavior. The goal here
508
+ // is for `@lwc/template-compiler` to know to add `stylesheet.$scoped$ = true` to its compiled output, which it
509
+ // detects using the query param.
510
+ if (path.node?.source.value.endsWith('.scoped.css')) {
511
+ path.replaceWith(estreeToolkit.builders.importDeclaration(path.node.specifiers, estreeToolkit.builders.literal(path.node.source.value + '?scoped=true')));
512
+ }
513
+ state.cssExplicitImports = state.cssExplicitImports ?? new Map();
514
+ state.cssExplicitImports.set(specifier.local.name, path.node.source.value);
515
+ }
516
+ /**
517
+ * This adds implicit style imports to the compiled component artifact.
518
+ */
519
+ function getStylesheetImports(filepath) {
520
+ const moduleName = /(?<moduleName>[^/]+)\.html$/.exec(filepath)?.groups?.moduleName;
521
+ if (!moduleName) {
522
+ throw new Error(`Could not determine module name from file path: ${filepath}`);
523
+ }
524
+ return [
525
+ [{ default: 'defaultStylesheets' }, `./${moduleName}.css`],
526
+ [{ default: 'defaultScopedStylesheets' }, `./${moduleName}.scoped.css?scoped=true`],
527
+ ];
528
+ }
529
+ function catalogStaticStylesheets(ids, state) {
530
+ state.staticStylesheetIds = state.staticStylesheetIds ?? new Set();
531
+ for (const id of ids) {
532
+ state.staticStylesheetIds.add(id);
533
+ }
534
+ }
535
+
536
+ /*
537
+ * Copyright (c) 2024, Salesforce, Inc.
538
+ * All rights reserved.
539
+ * SPDX-License-Identifier: MIT
540
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
541
+ */
542
+ function generateError(node, error, ...messageArgs) {
543
+ return errors.generateCompilerError(error, {
544
+ messageArgs,
545
+ origin: node.loc
546
+ ? {
547
+ filename: node.loc.source || undefined,
548
+ location: {
549
+ line: node.loc.start.line,
550
+ column: node.loc.start.column,
551
+ ...(node.range
552
+ ? { start: node.range[0], length: node.range[1] - node.range[0] }
553
+ : {}),
554
+ },
555
+ }
556
+ : undefined,
557
+ });
558
+ }
559
+
560
+ /*
561
+ * Copyright (c) 2024, salesforce.com, inc.
562
+ * All rights reserved.
563
+ * SPDX-License-Identifier: MIT
564
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
565
+ */
566
+ function bMemberExpressionChain(props) {
567
+ // Technically an incorrect assertion, but it works fine...
568
+ let expr = estreeToolkit.builders.identifier('instance');
569
+ for (const prop of props) {
570
+ expr = estreeToolkit.builders.memberExpression(expr, estreeToolkit.builders.literal(prop), true);
571
+ }
572
+ return expr;
573
+ }
574
+ function getWireParams(node) {
575
+ const { decorators } = node;
576
+ if (decorators.length > 1) {
577
+ throw generateError(node, errors.DecoratorErrors.ONE_WIRE_DECORATOR_ALLOWED);
578
+ }
579
+ // Before calling this function, we validate that it has exactly one decorator, @wire
580
+ const wireDecorator = decorators[0].expression;
581
+ if (!estreeToolkit.is.callExpression(wireDecorator)) {
582
+ throw generateError(node, errors.DecoratorErrors.FUNCTION_IDENTIFIER_SHOULD_BE_FIRST_PARAMETER);
583
+ }
584
+ const args = wireDecorator.arguments;
585
+ if (args.length === 0) {
586
+ throw generateError(node, errors.DecoratorErrors.ADAPTER_SHOULD_BE_FIRST_PARAMETER);
587
+ }
588
+ return args;
589
+ }
590
+ function validateWireId(id, path) {
591
+ // name of identifier or object used in member expression (e.g. "foo" for `foo.bar`)
592
+ let wireAdapterVar;
593
+ if (estreeToolkit.is.memberExpression(id)) {
594
+ if (id.computed) {
595
+ throw generateError(path.node, errors.DecoratorErrors.FUNCTION_IDENTIFIER_CANNOT_HAVE_COMPUTED_PROPS);
596
+ }
597
+ if (!estreeToolkit.is.identifier(id.object)) {
598
+ throw generateError(path.node, errors.DecoratorErrors.FUNCTION_IDENTIFIER_CANNOT_HAVE_NESTED_MEMBER_EXRESSIONS);
599
+ }
600
+ wireAdapterVar = id.object.name;
601
+ }
602
+ else if (!estreeToolkit.is.identifier(id)) {
603
+ throw generateError(path.node, errors.DecoratorErrors.FUNCTION_IDENTIFIER_SHOULD_BE_FIRST_PARAMETER);
604
+ }
605
+ else {
606
+ wireAdapterVar = id.name;
607
+ }
608
+ // This is not the exact same validation done in @lwc/babel-plugin-component but it accomplishes the same thing
609
+ if (path.scope?.getBinding(wireAdapterVar)?.kind !== 'module') {
610
+ throw generateError(path.node, errors.DecoratorErrors.COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL);
611
+ }
612
+ }
613
+ function validateWireConfig(config, path) {
614
+ if (!estreeToolkit.is.objectExpression(config)) {
615
+ throw generateError(path.node, errors.DecoratorErrors.CONFIG_OBJECT_SHOULD_BE_SECOND_PARAMETER);
616
+ }
617
+ for (const property of config.properties) {
618
+ // Only validate computed object properties because static props are all valid
619
+ // and we ignore {...spreads} and {methods(){}}
620
+ if (!estreeToolkit.is.property(property) || !property.computed)
621
+ continue;
622
+ const key = property.key;
623
+ if (estreeToolkit.is.identifier(key)) {
624
+ const binding = path.scope?.getBinding(key.name);
625
+ // TODO [#3956]: Investigate allowing imported constants
626
+ if (binding?.kind === 'const')
627
+ continue;
628
+ // By default, the identifier `undefined` has no binding (when it's actually undefined),
629
+ // but has a binding if it's used as a variable (e.g. `let undefined = "don't do this"`)
630
+ if (key.name === 'undefined' && !binding)
631
+ continue;
632
+ }
633
+ else if (estreeToolkit.is.literal(key)) {
634
+ if (estreeToolkit.is.templateLiteral(key)) {
635
+ // A template literal is not guaranteed to always result in the same value
636
+ // (e.g. `${Math.random()}`), so we disallow them entirely.
637
+ throw generateError(path.node, errors.DecoratorErrors.COMPUTED_PROPERTY_CANNOT_BE_TEMPLATE_LITERAL);
638
+ }
639
+ else if (!('regex' in key)) {
640
+ // A literal can be a regexp, template literal, or primitive; only allow primitives
641
+ continue;
642
+ }
643
+ }
644
+ else if (estreeToolkit.is.templateLiteral(key)) {
645
+ throw generateError(path.node, errors.DecoratorErrors.COMPUTED_PROPERTY_CANNOT_BE_TEMPLATE_LITERAL);
646
+ }
647
+ throw generateError(path.node, errors.DecoratorErrors.COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL);
648
+ }
649
+ }
650
+ function catalogWireAdapters(path, state) {
651
+ const node = path.node;
652
+ const [id, config] = getWireParams(node);
653
+ validateWireId(id, path);
654
+ let reactiveConfig;
655
+ if (config) {
656
+ validateWireConfig(config, path);
657
+ reactiveConfig = immer.produce(config, (draft) => {
658
+ // replace '$foo' values with `instance.foo`; preserve everything else
659
+ for (const prop of draft.properties) {
660
+ const { value } = prop;
661
+ if (estreeToolkit.is.literal(value) &&
662
+ typeof value.value === 'string' &&
663
+ value.value.startsWith('$')) {
664
+ prop.value = bMemberExpressionChain(value.value.slice(1).split('.'));
665
+ }
666
+ }
667
+ });
668
+ }
669
+ else {
670
+ reactiveConfig = estreeToolkit.builders.objectExpression([]); // empty object
671
+ }
672
+ state.wireAdapters = [
673
+ ...state.wireAdapters,
674
+ { adapterId: id, config: reactiveConfig, field: node },
675
+ ];
676
+ }
677
+ const bSetWiredProp = (esTemplate `
678
+ instance.${ /*wire-decorated property*/estreeToolkit.is.identifier} = newValue
679
+ `);
680
+ const bCallWiredMethod = (esTemplate `
681
+ instance.${ /*wire-decorated method*/estreeToolkit.is.identifier}(newValue)
682
+ `);
683
+ const bWireAdapterPlumbing = (esTemplate `{
684
+ // Callable adapters are expressed as a function having an 'adapter' property, which
685
+ // is the actual wire constructor.
686
+ const AdapterCtor = ${ /*wire adapter constructor*/estreeToolkit.is.expression}?.adapter ?? ${ /*wire adapter constructor*/0};
687
+ const wireInstance = new AdapterCtor((newValue) => {
688
+ ${ /*update the decorated property or call the decorated method*/estreeToolkit.is.expressionStatement};
689
+ });
690
+ wireInstance.connect?.();
691
+ if (wireInstance.update) {
692
+ const getLiveConfig = () => {
693
+ return ${ /* reactive wire config */estreeToolkit.is.objectExpression};
694
+ };
695
+ // This may look a bit weird, in that the 'update' function is called twice: once with
696
+ // an 'undefined' value and possibly again with a context-provided value. While weird,
697
+ // this preserves the behavior of the browser-side wire implementation as well as the
698
+ // original SSR implementation.
699
+ wireInstance.update(getLiveConfig(), undefined);
700
+ __connectContext(AdapterCtor, instance, (newContextValue) => {
701
+ wireInstance.update(getLiveConfig(), newContextValue);
702
+ });
703
+ }
704
+ }`);
705
+ function bWireAdaptersPlumbing(adapters) {
706
+ return adapters.map(({ adapterId, config, field }) => {
707
+ const actionUponNewValue = estreeToolkit.is.methodDefinition(field) && field.kind === 'method'
708
+ ? // Validation in compile-js/index.ts `visitors` ensures `key` is an identifier
709
+ bCallWiredMethod(field.key)
710
+ : bSetWiredProp(field.key);
711
+ return bWireAdapterPlumbing(adapterId, actionUponNewValue, config);
712
+ });
713
+ }
714
+ function isWireDecorator(decorator) {
715
+ return (estreeToolkit.is.callExpression(decorator?.expression) &&
716
+ estreeToolkit.is.identifier(decorator.expression.callee) &&
717
+ decorator.expression.callee.name === 'wire');
718
+ }
719
+
720
+ /*
721
+ * Copyright (c) 2024, salesforce.com, inc.
722
+ * All rights reserved.
723
+ * SPDX-License-Identifier: MIT
724
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
725
+ */
726
+ const bGenerateMarkup = (esTemplate `
727
+ // These variables may mix with component-authored variables, so should be reasonably unique
728
+ const __lwcSuperPublicProperties__ = Array.from(Object.getPrototypeOf(${ /* Component class */estreeToolkit.is.identifier})?.__lwcPublicProperties__?.values?.() ?? []);
729
+ const __lwcPublicProperties__ = new Set(${ /*public properties*/estreeToolkit.is.arrayExpression}.concat(__lwcSuperPublicProperties__));
730
+
731
+ Object.defineProperty(
732
+ ${ /* component class */0},
733
+ __SYMBOL__GENERATE_MARKUP,
734
+ {
735
+ configurable: false,
736
+ enumerable: false,
737
+ writable: false,
738
+ value: async function* __lwcGenerateMarkup(
739
+ tagName,
740
+ props,
741
+ attrs,
742
+ parent,
743
+ scopeToken,
744
+ contextfulParent,
745
+ renderContext,
746
+ shadowSlottedContent,
747
+ lightSlottedContent,
748
+ scopedSlottedContent,
749
+ ) {
750
+ tagName = tagName ?? ${ /*component tag name*/estreeToolkit.is.literal};
751
+ attrs = attrs ?? Object.create(null);
752
+ props = props ?? Object.create(null);
753
+ const instance = new ${ /* Component class */0}({
754
+ tagName: tagName.toUpperCase(),
755
+ });
756
+
757
+ __establishContextfulRelationship(contextfulParent, instance);
758
+
759
+ instance[__SYMBOL__SET_INTERNALS](
760
+ props,
761
+ attrs,
762
+ __lwcPublicProperties__
763
+ );
764
+ ${ /*connect wire*/estreeToolkit.is.statement}
765
+ instance.isConnected = true;
766
+ if (instance.connectedCallback) {
767
+ __mutationTracker.enable(instance);
768
+ instance.connectedCallback();
769
+ __mutationTracker.disable(instance);
770
+ }
771
+ // If a render() function is defined on the class or any of its superclasses, then that takes priority.
772
+ // Next, if the class or any of its superclasses has an implicitly-associated template, then that takes
773
+ // second priority (e.g. a foo.html file alongside a foo.js file). Finally, there is a fallback empty template.
774
+ const tmplFn = instance.render?.() ?? ${ /*component class*/0}[__SYMBOL__DEFAULT_TEMPLATE] ?? __fallbackTmpl;
775
+ yield \`<\${tagName}\`;
776
+
777
+ const hostHasScopedStylesheets =
778
+ tmplFn.hasScopedStylesheets ||
779
+ hasScopedStaticStylesheets(${ /*component class*/0});
780
+ const hostScopeToken = hostHasScopedStylesheets ? tmplFn.stylesheetScopeToken + "-host" : undefined;
781
+
782
+ yield* __renderAttrs(instance, attrs, hostScopeToken, scopeToken);
783
+ yield '>';
784
+ yield* tmplFn(
785
+ shadowSlottedContent,
786
+ lightSlottedContent,
787
+ scopedSlottedContent,
788
+ ${ /*component class*/0},
789
+ instance,
790
+ renderContext
791
+ );
792
+ yield \`</\${tagName}>\`;
793
+ }
794
+ });
795
+ Object.defineProperty(
796
+ ${ /* component class */0},
797
+ '__lwcPublicProperties__',
798
+ {
799
+ configurable: false,
800
+ enumerable: false,
801
+ writable: false,
802
+ value: __lwcPublicProperties__
803
+ }
804
+ );
805
+ `);
806
+ const bExposeTemplate = (esTemplate `
807
+ if (${ /*template*/estreeToolkit.is.identifier}) {
808
+ Object.defineProperty(
809
+ ${ /* component class */estreeToolkit.is.identifier},
810
+ __SYMBOL__DEFAULT_TEMPLATE,
811
+ {
812
+ configurable: false,
813
+ enumerable: false,
814
+ writable: false,
815
+ value: ${ /*template*/0}
816
+ }
817
+ );
818
+ }
819
+ `);
820
+ /**
821
+ * This builds a generator function `generateMarkup` and adds it to the component JS's
822
+ * compilation output. `generateMarkup` acts as the glue between component JS and its
823
+ * template(s), including:
824
+ *
825
+ * - managing reflection of attrs & props
826
+ * - instantiating the component instance
827
+ * - setting the internal state of that component instance
828
+ * - invoking component lifecycle methods
829
+ * - yielding the tag name & attributes
830
+ * - deferring to the template function for yielding child content
831
+ */
832
+ function addGenerateMarkupFunction(program, state, tagName, filename) {
833
+ const { publicProperties } = state;
834
+ // The default tag name represents the component name that's passed to the transformer.
835
+ // This is needed to generate markup for dynamic components which are invoked through
836
+ // the generateMarkup function on the constructor.
837
+ // At the time of generation, the invoker does not have reference to its tag name to pass as an argument.
838
+ const defaultTagName = estreeToolkit.builders.literal(tagName);
839
+ const classIdentifier = estreeToolkit.builders.identifier(state.lwcClassName);
840
+ let exposeTemplateBlock = null;
841
+ const defaultTmplPath = `./${node_path.parse(filename).name}.html`;
842
+ const tmplVar = estreeToolkit.builders.identifier('__lwcTmpl');
843
+ program.body.unshift(bImportDeclaration({ default: tmplVar.name }, defaultTmplPath));
844
+ program.body.unshift(bImportDeclaration({ SYMBOL__DEFAULT_TEMPLATE: '__SYMBOL__DEFAULT_TEMPLATE' }));
845
+ exposeTemplateBlock = bExposeTemplate(tmplVar, classIdentifier);
846
+ // If no wire adapters are detected on the component, we don't bother injecting the wire-related code.
847
+ let connectWireAdapterCode = [];
848
+ if (state.wireAdapters.length) {
849
+ connectWireAdapterCode = bWireAdaptersPlumbing(state.wireAdapters);
850
+ program.body.unshift(bImportDeclaration({ connectContext: '__connectContext' }));
851
+ }
852
+ program.body.unshift(bImportDeclaration({
853
+ fallbackTmpl: '__fallbackTmpl',
854
+ hasScopedStaticStylesheets: undefined,
855
+ mutationTracker: '__mutationTracker',
856
+ renderAttrs: '__renderAttrs',
857
+ SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
858
+ SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
859
+ establishContextfulRelationship: '__establishContextfulRelationship',
860
+ }));
861
+ program.body.push(...bGenerateMarkup(classIdentifier, estreeToolkit.builders.arrayExpression([...publicProperties.keys()].map(estreeToolkit.builders.literal)), defaultTagName, connectWireAdapterCode));
862
+ if (exposeTemplateBlock) {
863
+ program.body.push(exposeTemplateBlock);
864
+ }
865
+ }
866
+
867
+ /*
868
+ * Copyright (c) 2024, Salesforce, Inc.
869
+ * All rights reserved.
870
+ * SPDX-License-Identifier: MIT
871
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
872
+ */
873
+ function validateName(definition) {
874
+ if (definition.computed) {
875
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_CANNOT_BE_COMPUTED);
876
+ }
877
+ const propertyName = definition.key.name;
878
+ switch (true) {
879
+ case propertyName === 'part':
880
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_NAME_PART_IS_RESERVED, propertyName);
881
+ case propertyName.startsWith('on'):
882
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_NAME_CANNOT_START_WITH_ON, propertyName);
883
+ case propertyName.startsWith('data') && propertyName.length > 4:
884
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_NAME_CANNOT_START_WITH_DATA, propertyName);
885
+ case shared.DISALLOWED_PROP_SET.has(propertyName):
886
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_NAME_IS_RESERVED, propertyName);
887
+ case shared.AMBIGUOUS_PROP_SET.has(propertyName):
888
+ throw generateError(definition, errors.DecoratorErrors.PROPERTY_NAME_IS_AMBIGUOUS, propertyName, shared.AMBIGUOUS_PROP_SET.get(propertyName));
889
+ }
890
+ }
891
+ function validatePropertyValue(property) {
892
+ if (estreeToolkit.is.literal(property.value) && property.value.value === true) {
893
+ throw generateError(property, errors.DecoratorErrors.INVALID_BOOLEAN_PUBLIC_PROPERTY);
894
+ }
895
+ }
896
+ function validatePropertyUnique(node, state) {
897
+ if (state.publicProperties.has(node.key.name)) {
898
+ throw generateError(node, errors.DecoratorErrors.DUPLICATE_API_PROPERTY, node.key.name);
899
+ }
900
+ }
901
+ function validateApiProperty(node, state) {
902
+ validatePropertyUnique(node, state);
903
+ validateName(node);
904
+ validatePropertyValue(node);
905
+ }
906
+ function validateUniqueMethod(node, state) {
907
+ const field = state.publicProperties.get(node.key.name);
908
+ if (!field) {
909
+ return;
910
+ }
911
+ if (field.type === 'MethodDefinition' &&
912
+ (field.kind === 'get' || field.kind === 'set') &&
913
+ (node.kind === 'get' || node.kind === 'set')) {
914
+ throw generateError(node, errors.DecoratorErrors.SINGLE_DECORATOR_ON_SETTER_GETTER_PAIR, node.key.name);
915
+ }
916
+ throw generateError(node, errors.DecoratorErrors.DUPLICATE_API_PROPERTY, node.key.name);
917
+ }
918
+ function validateApiMethod(node, state) {
919
+ validateUniqueMethod(node, state);
920
+ validateName(node);
921
+ }
922
+
923
+ function isApiDecorator(decorator) {
924
+ return decorator?.expression.type === 'Identifier' && decorator.expression.name === 'api';
925
+ }
926
+
927
+ /*
928
+ * Copyright (c) 2024, salesforce.com, inc.
929
+ * All rights reserved.
930
+ * SPDX-License-Identifier: MIT
931
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
932
+ */
933
+ const decorators = new Set(['api', 'wire', 'track']);
934
+ function removeDecoratorImport(path) {
935
+ if (!path.node || path.node.source.value !== '@lwc/ssr-runtime') {
936
+ return;
937
+ }
938
+ const filteredSpecifiers = path.node.specifiers.filter((specifier) => !(specifier.type === 'ImportSpecifier' &&
939
+ specifier.imported.type === 'Identifier' &&
940
+ decorators.has(specifier.imported.name)));
941
+ if (filteredSpecifiers.length !== path.node.specifiers.length) {
942
+ path.replaceWith(estreeToolkit.builders.importDeclaration(filteredSpecifiers, estreeToolkit.builders.literal('@lwc/ssr-runtime')));
943
+ }
944
+ }
945
+
946
+ function isTrackDecorator(decorator) {
947
+ return decorator?.expression.type === 'Identifier' && decorator.expression.name === 'track';
948
+ }
949
+
950
+ /*
951
+ * Copyright (c) 2024, Salesforce, Inc.
952
+ * All rights reserved.
953
+ * SPDX-License-Identifier: MIT
954
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
955
+ */
956
+ function validateUniqueDecorator(decorators) {
957
+ if (decorators.length < 2) {
958
+ return;
959
+ }
960
+ const wire = decorators.find(isWireDecorator);
961
+ const api = decorators.find(isApiDecorator);
962
+ const track = decorators.find(isTrackDecorator);
963
+ if (wire) {
964
+ if (api) {
965
+ throw generateError(wire, errors.DecoratorErrors.CONFLICT_WITH_ANOTHER_DECORATOR, 'api');
966
+ }
967
+ if (track) {
968
+ throw generateError(wire, errors.DecoratorErrors.CONFLICT_WITH_ANOTHER_DECORATOR, 'track');
969
+ }
970
+ }
971
+ if (api && track) {
972
+ throw generateError(api, errors.DecoratorErrors.API_AND_TRACK_DECORATOR_CONFLICT);
973
+ }
974
+ }
975
+
976
+ /*
977
+ * Copyright (c) 2024, salesforce.com, inc.
978
+ * All rights reserved.
979
+ * SPDX-License-Identifier: MIT
980
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
981
+ */
982
+ const visitors = {
983
+ $: { scope: true },
984
+ ExportNamedDeclaration(path) {
985
+ replaceNamedLwcExport(path);
986
+ },
987
+ ExportAllDeclaration(path) {
988
+ replaceAllLwcExport(path);
989
+ },
990
+ ImportDeclaration(path, state) {
991
+ if (!path.node || !path.node.source.value || typeof path.node.source.value !== 'string') {
992
+ return;
993
+ }
994
+ replaceLwcImport(path, state);
995
+ catalogAndReplaceStyleImports(path, state);
996
+ removeDecoratorImport(path);
997
+ },
998
+ ImportExpression(path, state) {
999
+ const { dynamicImports, importManager } = state;
1000
+ if (!dynamicImports) {
1001
+ // if no `dynamicImports` config, then leave dynamic `import()`s as-is
1002
+ return;
1003
+ }
1004
+ if (dynamicImports.strictSpecifier) {
1005
+ if (!estreeToolkit.is.literal(path.node?.source) || typeof path.node.source.value !== 'string') {
1006
+ throw generateError(path.node, errors.LWCClassErrors.INVALID_DYNAMIC_IMPORT_SOURCE_STRICT, estreeToolkit.is.literal(path.node?.source) ? String(path.node.source.value) : 'undefined');
1007
+ }
1008
+ }
1009
+ const loader = dynamicImports.loader;
1010
+ if (!loader) {
1011
+ // if no `loader` defined, then leave dynamic `import()`s as-is
1012
+ return;
1013
+ }
1014
+ const source = path.node.source;
1015
+ // 1. insert `import { load as __lwcLoad } from '${loader}'` at top of program
1016
+ importManager.add({ load: '__lwcLoad' }, loader);
1017
+ // 2. replace this `import(source)` with `__lwcLoad(source)`
1018
+ const load = estreeToolkit.builders.identifier('__lwcLoad');
1019
+ state.trustedLwcIdentifiers.add(load);
1020
+ path.replaceWith(estreeToolkit.builders.callExpression(load, [structuredClone(source)]));
1021
+ },
1022
+ ClassDeclaration: {
1023
+ enter(path, state) {
1024
+ const { node } = path;
1025
+ if (node?.superClass &&
1026
+ // export default class extends LightningElement {}
1027
+ (estreeToolkit.is.exportDefaultDeclaration(path.parentPath) ||
1028
+ // class Cmp extends LightningElement {}; export default Cmp
1029
+ path.scope
1030
+ ?.getBinding(node.id.name)
1031
+ ?.references.some((ref) => estreeToolkit.is.exportDefaultDeclaration(ref.parent)))) {
1032
+ // If it's a default-exported class with a superclass, then it's an LWC component!
1033
+ state.isLWC = true;
1034
+ state.currentComponent = node;
1035
+ if (node.id) {
1036
+ state.lwcClassName = node.id.name;
1037
+ }
1038
+ else {
1039
+ node.id = estreeToolkit.builders.identifier('DefaultComponentName');
1040
+ state.lwcClassName = 'DefaultComponentName';
1041
+ }
1042
+ // There's no builder for comment nodes :\
1043
+ const lwcVersionComment = {
1044
+ type: 'Block',
1045
+ value: shared.LWC_VERSION_COMMENT,
1046
+ };
1047
+ // Add LWC version comment to end of class body
1048
+ const { body } = node;
1049
+ if (body.trailingComments) {
1050
+ body.trailingComments.push(lwcVersionComment);
1051
+ }
1052
+ else {
1053
+ body.trailingComments = [lwcVersionComment];
1054
+ }
1055
+ }
1056
+ },
1057
+ leave(path, state) {
1058
+ // Indicate that we're no longer traversing an LWC component
1059
+ if (state.currentComponent && path.node === state.currentComponent) {
1060
+ state.currentComponent = null;
1061
+ }
1062
+ },
1063
+ },
1064
+ PropertyDefinition(path, state) {
1065
+ // Don't do anything unless we're in a component
1066
+ if (!state.currentComponent) {
1067
+ return;
1068
+ }
1069
+ const node = path.node;
1070
+ if (!node?.key) {
1071
+ // Seems to occur for `@wire() [symbol];` -- not sure why
1072
+ throw new Error('Unknown state: property definition has no key');
1073
+ }
1074
+ if (!isKeyIdentifier(node)) {
1075
+ return;
1076
+ }
1077
+ const { decorators } = node;
1078
+ validateUniqueDecorator(decorators);
1079
+ if (isApiDecorator(decorators[0])) {
1080
+ validateApiProperty(node, state);
1081
+ state.publicProperties.set(node.key.name, node);
1082
+ }
1083
+ else if (isWireDecorator(decorators[0])) {
1084
+ catalogWireAdapters(path, state);
1085
+ state.privateProperties.add(node.key.name);
1086
+ }
1087
+ else {
1088
+ state.privateProperties.add(node.key.name);
1089
+ }
1090
+ if (node.static &&
1091
+ node.key.name === 'stylesheets' &&
1092
+ estreeToolkit.is.arrayExpression(node.value) &&
1093
+ node.value.elements.every((el) => estreeToolkit.is.identifier(el))) {
1094
+ catalogStaticStylesheets(node.value.elements.map((el) => el.name), state);
1095
+ }
1096
+ },
1097
+ MethodDefinition(path, state) {
1098
+ const node = path.node;
1099
+ if (!isKeyIdentifier(node)) {
1100
+ return;
1101
+ }
1102
+ // If we mutate any class-methods that are piped through this compiler, then we'll be
1103
+ // inadvertently mutating things like Wire adapters.
1104
+ if (!state.currentComponent) {
1105
+ return;
1106
+ }
1107
+ const { decorators } = node;
1108
+ validateUniqueDecorator(decorators);
1109
+ if (isApiDecorator(decorators[0])) {
1110
+ validateApiMethod(node, state);
1111
+ state.publicProperties.set(node.key.name, node);
1112
+ }
1113
+ else if (isWireDecorator(decorators[0])) {
1114
+ if (node.computed) {
1115
+ // TODO [W-17758410]: implement
1116
+ throw new Error('@wire cannot be used on computed properties in SSR context.');
1117
+ }
1118
+ const isRealMethod = node.kind === 'method';
1119
+ // Getters and setters are methods in the AST, but treated as properties by @wire
1120
+ // Note that this means that their implementations are ignored!
1121
+ if (!isRealMethod) {
1122
+ const methodAsProp = estreeToolkit.builders.propertyDefinition(structuredClone(node.key), null, node.computed, node.static);
1123
+ methodAsProp.decorators = structuredClone(decorators);
1124
+ path.replaceWith(methodAsProp);
1125
+ // We do not need to call `catalogWireAdapters()` because, by replacing the current
1126
+ // node, `traverse()` will visit it again automatically, so we will just call
1127
+ // `catalogWireAdapters()` later anyway.
1128
+ return;
1129
+ }
1130
+ else {
1131
+ catalogWireAdapters(path, state);
1132
+ }
1133
+ }
1134
+ switch (node.key.name) {
1135
+ case 'constructor':
1136
+ // add our own custom arg after any pre-existing constructor args
1137
+ node.value.params = [
1138
+ ...structuredClone(node.value.params),
1139
+ estreeToolkit.builders.identifier('propsAvailableAtConstruction'),
1140
+ ];
1141
+ break;
1142
+ case 'connectedCallback':
1143
+ state.hasConnectedCallback = true;
1144
+ break;
1145
+ case 'renderedCallback':
1146
+ state.hadRenderedCallback = true;
1147
+ path.remove();
1148
+ break;
1149
+ case 'disconnectedCallback':
1150
+ state.hadDisconnectedCallback = true;
1151
+ path.remove();
1152
+ break;
1153
+ case 'errorCallback':
1154
+ state.hadErrorCallback = true;
1155
+ path.remove();
1156
+ break;
1157
+ }
1158
+ },
1159
+ Super(path, state) {
1160
+ // If we mutate any super calls that are piped through this compiler, then we'll be
1161
+ // inadvertently mutating things like Wire adapters.
1162
+ if (!state.currentComponent) {
1163
+ return;
1164
+ }
1165
+ const parentFn = path.getFunctionParent();
1166
+ if (parentFn &&
1167
+ parentFn.parentPath?.node?.type === 'MethodDefinition' &&
1168
+ parentFn.parentPath?.node?.kind === 'constructor' &&
1169
+ path.parentPath &&
1170
+ path.parentPath.node?.type === 'CallExpression') {
1171
+ // add our own custom arg after any pre-existing super() args
1172
+ path.parentPath.node.arguments = [
1173
+ ...structuredClone(path.parentPath.node.arguments),
1174
+ estreeToolkit.builders.identifier('propsAvailableAtConstruction'),
1175
+ ];
1176
+ }
1177
+ },
1178
+ Program: {
1179
+ leave(path, state) {
1180
+ // After parsing the whole tree, insert needed imports
1181
+ const importDeclarations = state.importManager.getImportDeclarations();
1182
+ if (importDeclarations.length > 0) {
1183
+ path.node?.body.unshift(...importDeclarations);
1184
+ }
1185
+ },
1186
+ },
1187
+ Identifier(path, state) {
1188
+ const { node } = path;
1189
+ if (node?.name.startsWith('__lwc') && !state.trustedLwcIdentifiers.has(node)) {
1190
+ throw generateError(node, errors.SsrCompilerErrors.RESERVED_IDENTIFIER_PREFIX);
1191
+ }
1192
+ },
1193
+ };
1194
+ function compileJS(src, filename, tagName, options, compilationMode) {
1195
+ let ast = meriyah.parseModule(src, {
1196
+ module: true,
1197
+ next: true,
1198
+ loc: true,
1199
+ source: filename,
1200
+ ranges: true,
1201
+ });
1202
+ const state = {
1203
+ isLWC: false,
1204
+ currentComponent: null,
1205
+ hasConstructor: false,
1206
+ hasConnectedCallback: false,
1207
+ hadRenderedCallback: false,
1208
+ hadDisconnectedCallback: false,
1209
+ hadErrorCallback: false,
1210
+ lightningElementIdentifier: null,
1211
+ lwcClassName: null,
1212
+ cssExplicitImports: null,
1213
+ staticStylesheetIds: null,
1214
+ publicProperties: new Map(),
1215
+ privateProperties: new Set(),
1216
+ wireAdapters: [],
1217
+ dynamicImports: options.dynamicImports,
1218
+ importManager: new ImportManager(),
1219
+ trustedLwcIdentifiers: new WeakSet(),
1220
+ };
1221
+ estreeToolkit.traverse(ast, visitors, state);
1222
+ if (!state.isLWC) {
1223
+ // If an `extends LightningElement` is not detected in the JS, the
1224
+ // file in question is likely not an LWC. With this v1 implementation,
1225
+ // we'll just return the original source.
1226
+ return {
1227
+ code: astring.generate(ast, {}),
1228
+ };
1229
+ }
1230
+ addGenerateMarkupFunction(ast, state, tagName, filename);
1231
+ if (compilationMode === 'async' || compilationMode === 'sync') {
1232
+ ast = transmogrify(ast, compilationMode);
1233
+ }
1234
+ return {
1235
+ code: astring.generate(ast, {
1236
+ // The AST generated by meriyah doesn't seem to include comments,
1237
+ // so this just preserves the LWC version comment we added
1238
+ comments: true,
1239
+ }),
1240
+ };
1241
+ }
1242
+ function isKeyIdentifier(node) {
1243
+ return estreeToolkit.is.identifier(node?.key);
1244
+ }
1245
+
1246
+ /*
1247
+ * Copyright (c) 2024, Salesforce, Inc.
1248
+ * All rights reserved.
1249
+ * SPDX-License-Identifier: MIT
1250
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1251
+ */
1252
+ const bStylesheetTokenDeclaration = (esTemplate `
1253
+ const stylesheetScopeToken = '${estreeToolkit.is.literal}';
1254
+ `);
1255
+ const bHasScopedStylesheetsDeclaration = (esTemplate `
1256
+ const hasScopedStylesheets = defaultScopedStylesheets !== undefined && defaultScopedStylesheets.length > 0;
1257
+ `);
1258
+ // Scope tokens are associated with a given template. This is assigned here so that it can be used in `generateMarkup`.
1259
+ // We also need to keep track of whether the template has any scoped styles or not so that we can render (or not) the
1260
+ // scope token.
1261
+ const tmplAssignmentBlock = (esTemplate `
1262
+ ${ /* template */estreeToolkit.is.identifier}.hasScopedStylesheets = hasScopedStylesheets;
1263
+ ${ /* template */0}.stylesheetScopeToken = stylesheetScopeToken;
1264
+ `);
1265
+ function addScopeTokenDeclarations(program, filename, namespace, componentName) {
1266
+ const { scopeToken } = templateCompiler.generateScopeTokens(filename, namespace, componentName);
1267
+ program.body.unshift(bStylesheetTokenDeclaration(builders.builders.literal(scopeToken)), bHasScopedStylesheetsDeclaration());
1268
+ program.body.push(...tmplAssignmentBlock(builders.builders.identifier('__lwcTmpl')));
1269
+ }
1270
+
1271
+ /*
1272
+ * Copyright (c) 2024, salesforce.com, inc.
1273
+ * All rights reserved.
1274
+ * SPDX-License-Identifier: MIT
1275
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1276
+ */
1277
+ function expressionIrToEs(node, cxt) {
1278
+ const isComplexTemplateExpressionEnabled = cxt.templateOptions.experimentalComplexExpressions &&
1279
+ shared.isAPIFeatureEnabled(11 /* APIFeature.ENABLE_COMPLEX_TEMPLATE_EXPRESSIONS */, cxt.templateOptions.apiVersion);
1280
+ return templateCompiler.bindExpression(node, (n) => cxt.isLocalVar(n.name), 'instance', isComplexTemplateExpressionEnabled);
1281
+ }
1282
+ /**
1283
+ * Given an expression in a context, return an expression that may be scoped to that context.
1284
+ * For example, for the expression `foo`, it will typically be `instance.foo`, but if we're
1285
+ * inside a `for:each` block then the `foo` variable may refer to the scoped `foo`,
1286
+ * e.g. `<template for:each={foos} for:item="foo">`
1287
+ * @param expression
1288
+ * @param cxt
1289
+ */
1290
+ function getScopedExpression(expression, cxt) {
1291
+ let scopeReferencedId = null;
1292
+ if (expression.type === 'MemberExpression') {
1293
+ // e.g. `foo.bar` -> scopeReferencedId is `foo`
1294
+ scopeReferencedId = getRootIdentifier$1(expression);
1295
+ }
1296
+ else if (expression.type === 'Identifier') {
1297
+ // e.g. `foo` -> scopeReferencedId is `foo`
1298
+ scopeReferencedId = expression;
1299
+ }
1300
+ if (scopeReferencedId === null && !cxt.templateOptions.experimentalComplexExpressions) {
1301
+ throw new Error(`Invalid expression, must be a MemberExpression or Identifier, found type="${expression.type}": \`${JSON.stringify(expression)}\``);
1302
+ }
1303
+ return cxt.isLocalVar(scopeReferencedId?.name)
1304
+ ? expression
1305
+ : expressionIrToEs(expression, cxt);
1306
+ }
1307
+ function getRootMemberExpression$1(node) {
1308
+ return node.object.type === 'MemberExpression' ? getRootMemberExpression$1(node.object) : node;
1309
+ }
1310
+ function getRootIdentifier$1(node) {
1311
+ const rootMemberExpression = getRootMemberExpression$1(node);
1312
+ if (rootMemberExpression.object.type === 'Identifier') {
1313
+ return rootMemberExpression.object;
1314
+ }
1315
+ throw new Error(`Invalid expression, must be an Identifier, found type="${rootMemberExpression.type}": \`${JSON.stringify(rootMemberExpression)}\``);
1316
+ }
1317
+
1318
+ /*
1319
+ * Copyright (c) 2024, salesforce.com, inc.
1320
+ * All rights reserved.
1321
+ * SPDX-License-Identifier: MIT
1322
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1323
+ */
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)
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
+ }
1375
+ }
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
+ }, []);
1404
+ }
1405
+ function bAttributeValue(node, attrName) {
1406
+ if (!('attributes' in node)) {
1407
+ throw new TypeError(`Cannot get attribute value from ${node.type}`);
1408
+ }
1409
+ const nameAttrValue = node.attributes.find((attr) => attr.name === attrName)?.value;
1410
+ if (!nameAttrValue) {
1411
+ return estreeToolkit.builders.literal(null);
1412
+ }
1413
+ else if (nameAttrValue.type === 'Literal') {
1414
+ const name = typeof nameAttrValue.value === 'string' ? nameAttrValue.value : '';
1415
+ return estreeToolkit.builders.literal(name);
1416
+ }
1417
+ else {
1418
+ return estreeToolkit.builders.memberExpression(estreeToolkit.builders.literal('instance'), nameAttrValue);
1419
+ }
1420
+ }
1421
+ function normalizeClassAttributeValue(value) {
1422
+ // @ts-expect-error weird indirection results in wrong overload being picked up
1423
+ return shared.StringReplace.call(shared.StringTrim.call(value), /\s+/g, ' ');
1424
+ }
1425
+ function getChildAttrsOrProps(attrs, cxt) {
1426
+ const objectAttrsOrProps = attrs
1427
+ .map(({ name, value, type }) => {
1428
+ // Babel function required to align identifier validation with babel-plugin-component: https://github.com/salesforce/lwc/issues/4826
1429
+ const key = types.isValidES3Identifier(name) ? estreeToolkit.builders.identifier(name) : estreeToolkit.builders.literal(name);
1430
+ const nameLower = name.toLowerCase();
1431
+ if (value.type === 'Literal' && typeof value.value === 'string') {
1432
+ let literalValue = value.value;
1433
+ if (name === 'style') {
1434
+ literalValue = shared.normalizeStyleAttributeValue(literalValue);
1435
+ }
1436
+ else if (name === 'class') {
1437
+ literalValue = normalizeClassAttributeValue(literalValue);
1438
+ if (literalValue === '') {
1439
+ return; // do not render empty `class=""`
1440
+ }
1441
+ }
1442
+ else if (name === 'spellcheck') {
1443
+ // `spellcheck` string values are specially handled to massage them into booleans:
1444
+ // https://github.com/salesforce/lwc/blob/574ffbd/packages/%40lwc/template-compiler/src/codegen/index.ts#L445-L448
1445
+ literalValue = literalValue.toLowerCase() !== 'false';
1446
+ }
1447
+ else if (nameLower === 'tabindex') {
1448
+ // Global HTML "tabindex" attribute is specially massaged into a stringified number
1449
+ // This follows the historical behavior in api.ts:
1450
+ // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211
1451
+ literalValue = shared.normalizeTabIndex(literalValue);
1452
+ }
1453
+ return estreeToolkit.builders.property('init', key, estreeToolkit.builders.literal(literalValue));
1454
+ }
1455
+ else if (value.type === 'Literal' && typeof value.value === 'boolean') {
1456
+ if (name === 'class') {
1457
+ return; // do not render empty `class=""`
1458
+ }
1459
+ return estreeToolkit.builders.property('init', key, estreeToolkit.builders.literal(type === 'Attribute' ? '' : value.value));
1460
+ }
1461
+ else if (value.type === 'Identifier' || value.type === 'MemberExpression') {
1462
+ let propValue = expressionIrToEs(value, cxt);
1463
+ if (name === 'class') {
1464
+ cxt.import('normalizeClass');
1465
+ propValue = estreeToolkit.builders.callExpression(estreeToolkit.builders.identifier('normalizeClass'), [propValue]);
1466
+ }
1467
+ else if (nameLower === 'tabindex') {
1468
+ cxt.import('normalizeTabIndex');
1469
+ propValue = estreeToolkit.builders.callExpression(estreeToolkit.builders.identifier('normalizeTabIndex'), [propValue]);
1470
+ }
1471
+ return estreeToolkit.builders.property('init', key, propValue);
1472
+ }
1473
+ throw new Error(`Unimplemented child attr IR node type: ${value.type}`);
1474
+ })
1475
+ .filter(Boolean);
1476
+ return estreeToolkit.builders.objectExpression(objectAttrsOrProps);
1477
+ }
1478
+ /**
1479
+ * Determine if the provided node is of type Literal
1480
+ * @param node
1481
+ */
1482
+ function isLiteral(node) {
1483
+ return node.type === 'Literal';
1484
+ }
1485
+
1486
+ /*
1487
+ * Copyright (c) 2024, Salesforce, Inc.
1488
+ * All rights reserved.
1489
+ * SPDX-License-Identifier: MIT
1490
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1491
+ */
1492
+ const bNormalizeTextContent = (esTemplate `
1493
+ normalizeTextContent(${ /* string value */estreeToolkit.is.expression});
1494
+ `);
1495
+ const bYieldTextContent = (esTemplateWithYield `
1496
+ yield renderTextContent(${ /* text concatenation, possibly as binary expression */estreeToolkit.is.expression});
1497
+ `);
1498
+ /**
1499
+ * True if this is one of a series of text content nodes and/or comment node that are adjacent to one another as
1500
+ * siblings. (Comment nodes are ignored when preserve-comments is turned off.) This allows for adjacent text
1501
+ * node concatenation.
1502
+ */
1503
+ const isConcatenatedNode = (node, cxt) => {
1504
+ switch (node.type) {
1505
+ case 'Text':
1506
+ return true;
1507
+ case 'Comment':
1508
+ return !cxt.templateOptions.preserveComments;
1509
+ default:
1510
+ return false;
1511
+ }
1512
+ };
1513
+ const isLastConcatenatedNode = (cxt) => {
1514
+ const siblings = cxt.siblings;
1515
+ const currentNodeIndex = cxt.currentNodeIndex;
1516
+ const nextSibling = siblings[currentNodeIndex + 1];
1517
+ if (!nextSibling) {
1518
+ // we are the last sibling
1519
+ return true;
1520
+ }
1521
+ return !isConcatenatedNode(nextSibling, cxt);
1522
+ };
1523
+ function generateExpressionFromTextNode(node, cxt) {
1524
+ return isLiteral(node.value) ? builders.builders.literal(node.value.value) : expressionIrToEs(node.value, cxt);
1525
+ }
1526
+ function generateConcatenatedTextNodesExpressions(cxt) {
1527
+ const siblings = cxt.siblings;
1528
+ const currentNodeIndex = cxt.currentNodeIndex;
1529
+ const textNodes = [];
1530
+ for (let i = currentNodeIndex; i >= 0; i--) {
1531
+ const sibling = siblings[i];
1532
+ if (isConcatenatedNode(sibling, cxt)) {
1533
+ if (sibling.type === 'Text') {
1534
+ textNodes.unshift(sibling);
1535
+ }
1536
+ }
1537
+ else {
1538
+ // If we reach a non-Text/Comment node, we are done. These should not be concatenated
1539
+ // with sibling Text nodes separated by e.g. an Element:
1540
+ // {a}{b}<div></div>{c}{d}
1541
+ // In the above, {a} and {b} are concatenated, and {c} and {d} are concatenated,
1542
+ // but the `<div>` separates the two groups.
1543
+ break;
1544
+ }
1545
+ }
1546
+ if (!textNodes.length) {
1547
+ // Render nothing. This can occur if we hit a comment in non-preserveComments mode with no adjacent text nodes
1548
+ return [];
1549
+ }
1550
+ cxt.import(['normalizeTextContent', 'renderTextContent']);
1551
+ // Generate a binary expression to concatenate the text together. E.g.:
1552
+ // renderTextContent(
1553
+ // normalizeTextContent(a) +
1554
+ // normalizeTextContent(b) +
1555
+ // normalizeTextContent(c)
1556
+ // )
1557
+ const concatenatedExpression = textNodes
1558
+ .map((node) => bNormalizeTextContent(generateExpressionFromTextNode(node, cxt)))
1559
+ .reduce((accumulator, expression) => builders.builders.binaryExpression('+', accumulator, expression));
1560
+ return [bYieldTextContent(concatenatedExpression)];
1561
+ }
1562
+
1563
+ /*
1564
+ * Copyright (c) 2024, salesforce.com, inc.
1565
+ * All rights reserved.
1566
+ * SPDX-License-Identifier: MIT
1567
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1568
+ */
1569
+ const Comment = function Comment(node, cxt) {
1570
+ if (cxt.templateOptions.preserveComments) {
1571
+ return [estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(estreeToolkit.builders.literal(`<!--${node.value}-->`)))];
1572
+ }
1573
+ else {
1574
+ const isLastInSeries = isLastConcatenatedNode(cxt);
1575
+ // If preserve comments is off, we check if we should flush text content
1576
+ // for adjacent text nodes. (If preserve comments is on, then the previous
1577
+ // text node already flushed.)
1578
+ if (isLastInSeries) {
1579
+ return generateConcatenatedTextNodesExpressions(cxt);
1580
+ }
1581
+ return [];
1582
+ }
1583
+ };
1584
+
1585
+ /*
1586
+ * Copyright (c) 2024, salesforce.com, inc.
1587
+ * All rights reserved.
1588
+ * SPDX-License-Identifier: MIT
1589
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1590
+ */
1591
+ /** Extends a validator to return `true` if the node is `null`. */
1592
+ function isNullableOf(validator) {
1593
+ const nullableValidator = (node) => {
1594
+ return node === null || validator(node);
1595
+ };
1596
+ if (process.env.NODE_ENV !== 'production') {
1597
+ nullableValidator.__debugName = `nullable(${validator.__debugName || validator.name || 'unknown validator'})`;
1598
+ }
1599
+ return nullableValidator;
1600
+ }
1601
+ isNullableOf.__debugName = 'isNullableOf';
1602
+ if (process.env.NODE_ENV !== 'production') {
1603
+ // Modifying another package's exports is a code smell!
1604
+ for (const [key, val] of shared.entries(estreeToolkit.is)) {
1605
+ val.__debugName = key;
1606
+ }
1607
+ }
1608
+
1609
+ /*
1610
+ * Copyright (c) 2024, salesforce.com, inc.
1611
+ * All rights reserved.
1612
+ * SPDX-License-Identifier: MIT
1613
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1614
+ */
1615
+ // This function will be defined once and hoisted to the top of the template function. It'll be
1616
+ // referenced deeper in the call stack where the function is called or passed as a parameter.
1617
+ // It is a higher-order function that curries local variables that may be referenced by the
1618
+ // shadow slot content.
1619
+ const bGenerateShadowSlottedContent = (esTemplateWithYield `
1620
+ const ${ /* function name */estreeToolkit.is.identifier} = (${ /* local vars */estreeToolkit.is.identifier}) => async function* ${ /* function name */0}(contextfulParent) {
1621
+ // The 'contextfulParent' variable is shadowed here so that a contextful relationship
1622
+ // is established between components rendered in slotted content & the "parent"
1623
+ // component that contains the <slot>.
1624
+ ${ /* shadow slot content */estreeToolkit.is.statement}
1625
+ };
1626
+ `);
1627
+ // By passing in the set of local variables (which correspond 1:1 to the variables expected by
1628
+ // the referenced function), `shadowSlottedContent` will be curried function that can generate
1629
+ // shadow-slotted content.
1630
+ const bGenerateShadowSlottedContentRef = (esTemplateWithYield `
1631
+ const shadowSlottedContent = ${ /* reference to hoisted fn */estreeToolkit.is.identifier}(${ /* local vars */estreeToolkit.is.identifier});
1632
+ `);
1633
+ const bNullishGenerateShadowSlottedContent = (esTemplateWithYield `
1634
+ const shadowSlottedContent = null;
1635
+ `);
1636
+ const blightSlottedContentMap = (esTemplateWithYield `
1637
+ const ${ /* name of the content map */estreeToolkit.is.identifier} = Object.create(null);
1638
+ `);
1639
+ const bNullishLightSlottedContentMap = (esTemplateWithYield `
1640
+ const ${ /* name of the content map */estreeToolkit.is.identifier} = null;
1641
+ `);
1642
+ const bGenerateSlottedContent = (esTemplateWithYield `
1643
+ ${ /* const shadowSlottedContent = ... */estreeToolkit.is.variableDeclaration}
1644
+ ${ /* const lightSlottedContentMap */estreeToolkit.is.variableDeclaration}
1645
+ ${ /* const scopedSlottedContentMap */estreeToolkit.is.variableDeclaration}
1646
+ ${ /* light DOM addLightContent statements */estreeToolkit.is.expressionStatement}
1647
+ ${ /* scoped slot addLightContent statements */estreeToolkit.is.expressionStatement}
1648
+ `);
1649
+ // Note that this function name (`__lwcGenerateSlottedContent`) does not need to be scoped even though
1650
+ // it may be repeated multiple times in the same scope, because it's a function _expression_ rather
1651
+ // than a function _declaration_, so it isn't available to be referenced anywhere.
1652
+ const bAddSlottedContent = (esTemplate `
1653
+ addSlottedContent(
1654
+ ${ /* slot name */estreeToolkit.is.expression} ?? "",
1655
+ async function* __lwcGenerateSlottedContent(
1656
+ contextfulParent,
1657
+ ${ /* scoped slot data variable */isNullableOf(estreeToolkit.is.identifier)},
1658
+ slotAttributeValue)
1659
+ {
1660
+ ${ /* slot content */estreeToolkit.is.statement}
1661
+ },
1662
+ ${ /* content map */estreeToolkit.is.identifier}
1663
+ );
1664
+ `);
1665
+ function getShadowSlottedContent(slottableChildren, cxt) {
1666
+ return optimizeAdjacentYieldStmts(irChildrenToEs(slottableChildren, cxt, (child) => {
1667
+ const { isSlotted } = cxt;
1668
+ if (child.type === 'ExternalComponent' || child.type === 'Element') {
1669
+ cxt.isSlotted = false;
1670
+ }
1671
+ // cleanup function
1672
+ return () => {
1673
+ cxt.isSlotted = isSlotted;
1674
+ };
1675
+ }));
1676
+ }
1677
+ // Light DOM slots are a bit complex because of needing to handle slots _not_ at the top level
1678
+ // At the non-top level, it matters what the ancestors are. These are relevant to slots:
1679
+ // - If (`if:true`, `if:false`)
1680
+ // - IfBlock/ElseBlock/ElseifBlock (`lwc:if`, `lwc:elseif`, `lwc:else`)
1681
+ // Whereas anything else breaks the relationship between the slotted content and the containing
1682
+ // Component (e.g. another Component/ExternalComponent) or is disallowed (e.g. ForEach/ForOf).
1683
+ // Then there are the leaf nodes, which _may_ have a `slot` attribute on them:
1684
+ // - Element/Text/Component/ExternalComponent (e.g. `<div>`, `<x-foo>`)
1685
+ // Once you reach a leaf, you know what content should be rendered for a given slot name. But you
1686
+ // also need to consider all of its ancestors, which may cause the slot content to be conditionally
1687
+ // rendered (e.g. IfBlock/ElseBlock).
1688
+ // For example:
1689
+ // <x-foo>
1690
+ // <template lwc:if={darkTheme}>
1691
+ // <div slot="footer"></div>
1692
+ // </template>
1693
+ // <template lwc:else>
1694
+ // yolo
1695
+ // </template>
1696
+ // </x-foo>
1697
+ // In this example, we render the `<div>` into the `footer` slot, if `darkTheme` is true.
1698
+ // Otherwise, we will render the text node `yolo` into the default slot.
1699
+ // The goal here is to traverse through the tree and identify all unique `slot` attribute names
1700
+ // and group those into AST trees on a per-`slot` name basis, only for leafs/ancestors that are
1701
+ // relevant to slots (as mentioned above).
1702
+ function getLightSlottedContent(rootNodes, cxt) {
1703
+ const results = [];
1704
+ // For the given slot name, get the EsExpressions we should use to render it
1705
+ // The ancestorIndices is an array of integers referring to the chain of ancestors
1706
+ // and their positions in the child arrays of their own parents
1707
+ const addLightDomSlotContent = (slotName, ancestorIndices) => {
1708
+ const clone = immer.produce(rootNodes[ancestorIndices[0]], (draft) => {
1709
+ // Create a clone of the AST with only the ancestors and no other siblings
1710
+ let current = draft;
1711
+ for (let i = 1; i < ancestorIndices.length; i++) {
1712
+ const nextIndex = ancestorIndices[i];
1713
+ // If i >= 1 then the current must necessarily be a SlottableAncestorIrType
1714
+ const next = current.children[nextIndex];
1715
+ current.children = [next];
1716
+ current = next;
1717
+ }
1718
+ // The leaf must necessarily be a SlottableLeafIrType
1719
+ const leaf = current;
1720
+ // Light DOM slots do not actually render the `slot` attribute.
1721
+ if (leaf.type !== 'Text') {
1722
+ leaf.attributes = leaf.attributes.filter((attr) => attr.name !== 'slot');
1723
+ }
1724
+ });
1725
+ const { isSlotted: originalIsSlotted } = cxt;
1726
+ cxt.isSlotted = ancestorIndices.length > 1 || clone.type === 'Slot';
1727
+ const slotContent = optimizeAdjacentYieldStmts(irToEs(clone, cxt));
1728
+ cxt.isSlotted = originalIsSlotted;
1729
+ results.push(estreeToolkit.builders.expressionStatement(bAddSlottedContent(slotName, null, slotContent, estreeToolkit.builders.identifier('lightSlottedContentMap'))));
1730
+ };
1731
+ const traverse = (nodes, ancestorIndices) => {
1732
+ for (let i = 0; i < nodes.length; i++) {
1733
+ // must set the siblings inside the for loop due to nested children
1734
+ cxt.siblings = nodes;
1735
+ cxt.currentNodeIndex = i;
1736
+ const node = nodes[i];
1737
+ switch (node.type) {
1738
+ // SlottableAncestorIrType
1739
+ case 'If':
1740
+ case 'IfBlock':
1741
+ case 'ElseifBlock':
1742
+ case 'ElseBlock': {
1743
+ traverse(node.children, [...ancestorIndices, i]);
1744
+ break;
1745
+ }
1746
+ // SlottableLeafIrType
1747
+ case 'Slot':
1748
+ case 'Element':
1749
+ case 'Text':
1750
+ case 'Component':
1751
+ case 'ExternalComponent': {
1752
+ // '' is the default slot name. Text nodes are always slotted into the default slot
1753
+ const slotName = node.type === 'Text' ? estreeToolkit.builders.literal('') : bAttributeValue(node, 'slot');
1754
+ // For concatenated adjacent text nodes, for any but the final text node, we
1755
+ // should skip them and let the final text node take care of rendering its siblings
1756
+ if (node.type === 'Text' && !isLastConcatenatedNode(cxt)) {
1757
+ continue;
1758
+ }
1759
+ addLightDomSlotContent(slotName, [...ancestorIndices, i]);
1760
+ break;
1761
+ }
1762
+ }
1763
+ }
1764
+ // reset the context
1765
+ cxt.siblings = undefined;
1766
+ cxt.currentNodeIndex = undefined;
1767
+ };
1768
+ traverse(rootNodes, []);
1769
+ return results;
1770
+ }
1771
+ function getSlottedContent(node, cxt) {
1772
+ const { isSlotted } = cxt;
1773
+ cxt.isSlotted = true;
1774
+ // Anything inside the slotted content is a normal slotted content except for `<template lwc:slot-data>` which is a scoped slot.
1775
+ const slottableChildren = node.children.filter((child) => child.type !== 'ScopedSlotFragment');
1776
+ const scopedSlottableChildren = node.children.filter((child) => child.type === 'ScopedSlotFragment');
1777
+ const shadowSlotContent = getShadowSlottedContent(slottableChildren, cxt);
1778
+ const lightSlotContent = getLightSlottedContent(slottableChildren, cxt);
1779
+ const scopedSlotContent = scopedSlottableChildren.map((child) => {
1780
+ const boundVariableName = child.slotData.value.name;
1781
+ const boundVariable = estreeToolkit.builders.identifier(boundVariableName);
1782
+ cxt.pushLocalVars([boundVariableName]);
1783
+ const slotName = isLiteral(child.slotName)
1784
+ ? estreeToolkit.builders.literal(child.slotName.value)
1785
+ : expressionIrToEs(child.slotName, cxt);
1786
+ // TODO [#4768]: what if the bound variable is `generateMarkup` or some framework-specific identifier?
1787
+ const addLightContentExpr = estreeToolkit.builders.expressionStatement(bAddSlottedContent(slotName, boundVariable, optimizeAdjacentYieldStmts(irChildrenToEs(child.children, cxt)), estreeToolkit.builders.identifier('scopedSlottedContentMap')));
1788
+ cxt.popLocalVars();
1789
+ return addLightContentExpr;
1790
+ });
1791
+ const hasShadowSlottedContent = shadowSlotContent.length > 0;
1792
+ const hasLightSlottedContent = lightSlotContent.length > 0;
1793
+ const hasScopedSlottedContent = scopedSlotContent.length > 0;
1794
+ cxt.isSlotted = isSlotted;
1795
+ if (hasShadowSlottedContent || hasLightSlottedContent || hasScopedSlottedContent) {
1796
+ cxt.import('addSlottedContent');
1797
+ }
1798
+ // Elsewhere, nodes and their subtrees are cloned. This design decision means that
1799
+ // the node objects themselves cannot be used as unique identifiers (e.g. as keys
1800
+ // in a map). However, for a given template, a node's location information does
1801
+ // uniquely identify that node.
1802
+ const uniqueNodeId = `${node.name}:${node.location.start}:${node.location.end}`;
1803
+ const localVars = cxt.getLocalVars();
1804
+ const localVarIds = localVars.map(estreeToolkit.builders.identifier);
1805
+ if (hasShadowSlottedContent && !cxt.slots.shadow.isDuplicate(uniqueNodeId)) {
1806
+ // Colon characters in <lwc:component> element name will result in an invalid
1807
+ // JavaScript identifier if not otherwise accounted for.
1808
+ const kebabCmpName = shared.kebabCaseToCamelCase(node.name).replace(':', '_');
1809
+ const shadowSlotContentFnName = cxt.slots.shadow.register(uniqueNodeId, kebabCmpName);
1810
+ const shadowSlottedContentFn = bGenerateShadowSlottedContent(estreeToolkit.builders.identifier(shadowSlotContentFnName),
1811
+ // If the slot-fn were defined here instead of hoisted to the top of the module,
1812
+ // the local variables (e.g. from for:each) would be closed-over. When hoisted,
1813
+ // however, we need to curry these variables.
1814
+ localVarIds, shadowSlotContent);
1815
+ cxt.hoist.templateFn(shadowSlottedContentFn, node);
1816
+ }
1817
+ const shadowSlottedContentFn = hasShadowSlottedContent
1818
+ ? bGenerateShadowSlottedContentRef(estreeToolkit.builders.identifier(cxt.slots.shadow.getFnName(uniqueNodeId)), localVarIds)
1819
+ : bNullishGenerateShadowSlottedContent();
1820
+ const lightSlottedContentMap = hasLightSlottedContent
1821
+ ? blightSlottedContentMap(estreeToolkit.builders.identifier('lightSlottedContentMap'))
1822
+ : bNullishLightSlottedContentMap(estreeToolkit.builders.identifier('lightSlottedContentMap'));
1823
+ const scopedSlottedContentMap = hasScopedSlottedContent
1824
+ ? blightSlottedContentMap(estreeToolkit.builders.identifier('scopedSlottedContentMap'))
1825
+ : bNullishLightSlottedContentMap(estreeToolkit.builders.identifier('scopedSlottedContentMap'));
1826
+ return bGenerateSlottedContent(shadowSlottedContentFn, lightSlottedContentMap, scopedSlottedContentMap, lightSlotContent, scopedSlotContent);
1827
+ }
1828
+
1829
+ /*
1830
+ * Copyright (c) 2024, salesforce.com, inc.
1831
+ * All rights reserved.
1832
+ * SPDX-License-Identifier: MIT
1833
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1834
+ */
1835
+ const bYieldFromChildGenerator = (esTemplateWithYield `
1836
+ {
1837
+ const childProps = ${ /* child props */estreeToolkit.is.objectExpression};
1838
+ const childAttrs = ${ /* child attrs */estreeToolkit.is.objectExpression};
1839
+ /*
1840
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1841
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1842
+ */
1843
+ if (slotAttributeValue) {
1844
+ childAttrs.slot = slotAttributeValue;
1845
+ }
1846
+ ${
1847
+ /*
1848
+ Slotted content is inserted here.
1849
+ Note that the slotted content will be stored in variables named
1850
+ `shadowSlottedContent`/`lightSlottedContentMap / scopedSlottedContentMap` which are used below
1851
+ when the child's generateMarkup function is invoked.
1852
+ */
1853
+ estreeToolkit.is.statement}
1854
+
1855
+ const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined;
1856
+ const generateMarkup = ${ /* Component */estreeToolkit.is.identifier}[__SYMBOL__GENERATE_MARKUP];
1857
+ const tagName = ${ /* tag name */estreeToolkit.is.literal};
1858
+
1859
+ if (generateMarkup) {
1860
+ yield* generateMarkup(
1861
+ tagName,
1862
+ childProps,
1863
+ childAttrs,
1864
+ instance,
1865
+ scopeToken,
1866
+ contextfulParent,
1867
+ renderContext,
1868
+ shadowSlottedContent,
1869
+ lightSlottedContentMap,
1870
+ scopedSlottedContentMap
1871
+ );
1872
+ } else {
1873
+ yield \`<\${tagName}>\`;
1874
+ yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${ /* Component */3}, instance, renderContext)
1875
+ yield \`</\${tagName}>\`;
1876
+ }
1877
+ }
1878
+ `);
1879
+ const Component = function Component(node, cxt) {
1880
+ // Import the custom component's generateMarkup export.
1881
+ const childComponentLocalName = `ChildComponentCtor_${templateCompiler.toPropertyName(node.name)}`;
1882
+ const importPath = templateCompiler.kebabcaseToCamelcase(node.name);
1883
+ cxt.import({ default: childComponentLocalName }, importPath);
1884
+ cxt.import({
1885
+ SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
1886
+ fallbackTmpl: '__fallbackTmpl',
1887
+ });
1888
+ const childTagName = node.name;
1889
+ return [
1890
+ bYieldFromChildGenerator(getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(node.attributes, cxt), getSlottedContent(node, cxt), estreeToolkit.builders.identifier(childComponentLocalName), estreeToolkit.builders.literal(childTagName)),
1891
+ ];
1892
+ };
1893
+
1894
+ /*
1895
+ * Copyright (c) 2024, salesforce.com, inc.
1896
+ * All rights reserved.
1897
+ * SPDX-License-Identifier: MIT
1898
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1899
+ */
1900
+ const bYieldFromDynamicComponentConstructorGenerator = (esTemplateWithYield `
1901
+ const Ctor = '${ /* lwcIs attribute value */estreeToolkit.is.expression}';
1902
+ if (Ctor) {
1903
+ if (typeof Ctor !== 'function' || !(Ctor.prototype instanceof LightningElement)) {
1904
+ throw new Error(\`Invalid constructor: "\${String(Ctor)}" is not a LightningElement constructor.\`)
1905
+ }
1906
+ const childProps = ${ /* child props */estreeToolkit.is.objectExpression};
1907
+ const childAttrs = ${ /* child attrs */estreeToolkit.is.objectExpression};
1908
+ /*
1909
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
1910
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
1911
+ */
1912
+ if (slotAttributeValue) {
1913
+ childAttrs.slot = slotAttributeValue;
1914
+ }
1915
+ ${
1916
+ /*
1917
+ Slotted content is inserted here.
1918
+ Note that the slotted content will be stored in variables named
1919
+ `shadowSlottedContent`/`lightSlottedContentMap / scopedSlottedContentMap` which are used below
1920
+ when the child's generateMarkup function is invoked.
1921
+ */
1922
+ estreeToolkit.is.statement}
1923
+
1924
+ const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined;
1925
+
1926
+ yield* Ctor[__SYMBOL__GENERATE_MARKUP](
1927
+ null,
1928
+ childProps,
1929
+ childAttrs,
1930
+ instance,
1931
+ scopeToken,
1932
+ contextfulParent,
1933
+ renderContext,
1934
+ shadowSlottedContent,
1935
+ lightSlottedContentMap,
1936
+ scopedSlottedContentMap
1937
+ );
1938
+ }
1939
+ `);
1940
+ const LwcComponent = function LwcComponent(node, cxt) {
1941
+ const { directives } = node;
1942
+ const lwcIs = directives.find((directive) => directive.name === 'Is');
1943
+ if (!shared.isUndefined(lwcIs)) {
1944
+ cxt.import({
1945
+ LightningElement: undefined,
1946
+ SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
1947
+ });
1948
+ return bYieldFromDynamicComponentConstructorGenerator(
1949
+ // The template compiler has validation to prevent lwcIs.value from being a literal
1950
+ expressionIrToEs(lwcIs.value, cxt), getChildAttrsOrProps(node.properties, cxt), getChildAttrsOrProps(node.attributes, cxt), getSlottedContent(node, cxt));
1951
+ }
1952
+ else {
1953
+ return [];
1954
+ }
1955
+ };
1956
+
1957
+ /*
1958
+ * Copyright (c) 2024, salesforce.com, inc.
1959
+ * All rights reserved.
1960
+ * SPDX-License-Identifier: MIT
1961
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1962
+ */
1963
+ const bYield = (expr) => estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(expr));
1964
+ // TODO [#4714]: scope token renders as a suffix for literals, but prefix for expressions
1965
+ const bYieldDynamicValue = (esTemplateWithYield `
1966
+ {
1967
+ const attrName = ${ /* attribute name */estreeToolkit.is.literal};
1968
+ let attrValue = ${ /* attribute value expression */estreeToolkit.is.expression};
1969
+ const isHtmlBooleanAttr = ${ /* isHtmlBooleanAttr */estreeToolkit.is.literal};
1970
+
1971
+ // Global HTML boolean attributes are specially coerced into booleans
1972
+ // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/template-compiler/src/codegen/index.ts#L450-L454
1973
+ if (isHtmlBooleanAttr) {
1974
+ attrValue = attrValue ? '' : undefined;
1975
+ }
1976
+
1977
+ // Global HTML "tabindex" attribute is specially massaged into a stringified number
1978
+ // This follows the historical behavior in api.ts:
1979
+ // https://github.com/salesforce/lwc/blob/f34a347/packages/%40lwc/engine-core/src/framework/api.ts#L193-L211
1980
+ if (attrName === 'tabindex') {
1981
+ const shouldNormalize = attrValue > 0 && typeof attrValue !== 'boolean';
1982
+ attrValue = shouldNormalize ? 0 : attrValue;
1983
+ }
1984
+
1985
+ // Backwards compatibility with historical patchStyleAttribute() behavior:
1986
+ // https://github.com/salesforce/lwc/blob/59e2c6c/packages/%40lwc/engine-core/src/framework/modules/computed-style-attr.ts#L40
1987
+ if (attrName === 'style' && (typeof attrValue !== 'string' || attrValue === '')) {
1988
+ attrValue = undefined;
1989
+ }
1990
+
1991
+ if (attrValue !== undefined && attrValue !== null) {
1992
+ yield ' ' + attrName;
1993
+
1994
+ if (attrValue !== '') {
1995
+ yield \`="\${htmlEscape(String(attrValue), true)}"\`;
1996
+ }
1997
+ }
1998
+ }
1999
+ `);
2000
+ const bYieldClassDynamicValue = (esTemplateWithYield `
2001
+ {
2002
+ const attrValue = normalizeClass(${ /* attribute value expression */estreeToolkit.is.expression});
2003
+ const shouldRenderScopeToken = hasScopedStylesheets || hasScopedStaticStylesheets(Cmp);
2004
+
2005
+ // Concatenate the scope token with the class attribute value as necessary.
2006
+ // If either is missing, render the other alone.
2007
+ let combinedValue = shouldRenderScopeToken ? stylesheetScopeToken : '';
2008
+ if (attrValue) {
2009
+ if (combinedValue) {
2010
+ combinedValue += ' ';
2011
+ }
2012
+ combinedValue += htmlEscape(String(attrValue), true);
2013
+ }
2014
+ if (combinedValue) {
2015
+ yield \` class="\${combinedValue}"\`;
2016
+ }
2017
+ }
2018
+ `);
2019
+ // TODO [#4714]: scope token renders as a suffix for literals, but prefix for expressions
2020
+ const bStringLiteralYield = (esTemplateWithYield `
2021
+ {
2022
+ const attrName = ${ /* attribute name */estreeToolkit.is.literal}
2023
+ const attrValue = ${ /* attribute value */estreeToolkit.is.literal};
2024
+
2025
+ const shouldRenderScopeToken = attrName === 'class' &&
2026
+ (hasScopedStylesheets || hasScopedStaticStylesheets(Cmp));
2027
+ const suffix = shouldRenderScopeToken ? ' ' + stylesheetScopeToken : '';
2028
+
2029
+ yield ' ' + attrName;
2030
+ if (attrValue !== '' || shouldRenderScopeToken) {
2031
+ yield '="' + attrValue + suffix + '"';
2032
+ }
2033
+
2034
+ }
2035
+ `);
2036
+ const bConditionallyYieldScopeTokenClass = (esTemplateWithYield `
2037
+ if (hasScopedStylesheets || hasScopedStaticStylesheets(Cmp)) {
2038
+ yield \` class="\${stylesheetScopeToken}"\`;
2039
+ }
2040
+ `);
2041
+ /*
2042
+ If `slotAttributeValue` is set, it references a slot that does not exist, and the `slot` attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
2043
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
2044
+ */
2045
+ const bConditionallyYieldDanglingSlotName = (esTemplateWithYield `
2046
+ if (slotAttributeValue) {
2047
+ yield \` slot="\${slotAttributeValue}"\`;
2048
+ }
2049
+ `);
2050
+ const bYieldSanitizedHtml = esTemplateWithYield `
2051
+ yield sanitizeHtmlContent(${ /* lwc:inner-html content */estreeToolkit.is.expression})
2052
+ `;
2053
+ function yieldAttrOrPropLiteralValue(name, valueNode) {
2054
+ const { value, type } = valueNode;
2055
+ if (typeof value === 'string') {
2056
+ let yieldedValue;
2057
+ if (name === 'style') {
2058
+ yieldedValue = shared.normalizeStyleAttributeValue(value);
2059
+ }
2060
+ else if (name === 'class') {
2061
+ yieldedValue = normalizeClassAttributeValue(value);
2062
+ if (yieldedValue === '') {
2063
+ return [];
2064
+ }
2065
+ }
2066
+ else if (name === 'spellcheck') {
2067
+ // `spellcheck` string values are specially handled to massage them into booleans.
2068
+ // https://github.com/salesforce/lwc/blob/fe4e95f/packages/%40lwc/template-compiler/src/codegen/index.ts#L445-L448
2069
+ yieldedValue = String(value.toLowerCase() !== 'false');
2070
+ }
2071
+ else {
2072
+ yieldedValue = value;
2073
+ }
2074
+ return [bStringLiteralYield(estreeToolkit.builders.literal(name), estreeToolkit.builders.literal(yieldedValue))];
2075
+ }
2076
+ else if (typeof value === 'boolean') {
2077
+ if (name === 'class') {
2078
+ return [];
2079
+ }
2080
+ return [bYield(estreeToolkit.builders.literal(` ${name}`))];
2081
+ }
2082
+ throw new Error(`Unknown attr/prop literal: ${type}`);
2083
+ }
2084
+ function yieldAttrOrPropDynamicValue(elementName, name, value, cxt) {
2085
+ cxt.import('htmlEscape');
2086
+ const scopedExpression = getScopedExpression(value, cxt);
2087
+ switch (name) {
2088
+ case 'class':
2089
+ cxt.import('normalizeClass');
2090
+ return [bYieldClassDynamicValue(scopedExpression)];
2091
+ default:
2092
+ return [
2093
+ bYieldDynamicValue(estreeToolkit.builders.literal(name), scopedExpression, estreeToolkit.builders.literal(shared.isBooleanAttribute(name, elementName))),
2094
+ ];
2095
+ }
2096
+ }
2097
+ function reorderAttributes(attrs, props) {
2098
+ let classAttr = null;
2099
+ let styleAttr = null;
2100
+ let slotAttr = null;
2101
+ const boringAttrs = attrs.filter((attr) => {
2102
+ if (attr.name === 'class') {
2103
+ classAttr = attr;
2104
+ return false;
2105
+ }
2106
+ else if (attr.name === 'style') {
2107
+ styleAttr = attr;
2108
+ return false;
2109
+ }
2110
+ else if (attr.name === 'slot') {
2111
+ slotAttr = attr;
2112
+ return false;
2113
+ }
2114
+ return true;
2115
+ });
2116
+ return [classAttr, styleAttr, ...boringAttrs, ...props, slotAttr].filter((el) => el !== null);
2117
+ }
2118
+ const Element = function Element(node, cxt) {
2119
+ const innerHtmlDirective = node.type === 'Element' && node.directives.find((dir) => dir.name === 'InnerHTML');
2120
+ const attrsAndProps = reorderAttributes(node.attributes, node.properties);
2121
+ let hasClassAttribute = false;
2122
+ const yieldAttrsAndProps = attrsAndProps
2123
+ .filter(({ name }) => {
2124
+ // `<input checked>`/`<input value>` is treated as a property, not an attribute,
2125
+ // so should never be SSR'd. See https://github.com/salesforce/lwc/issues/4763
2126
+ return !(node.name === 'input' && (name === 'value' || name === 'checked'));
2127
+ })
2128
+ .flatMap(({ name, value, type }) => {
2129
+ if (type === 'Attribute' && (name === 'inner-h-t-m-l' || name === 'outer-h-t-m-l')) {
2130
+ throw new Error(`Cannot set attribute "${name}" on <${node.name}>.`);
2131
+ }
2132
+ let result;
2133
+ if (value.type === 'Literal') {
2134
+ result = yieldAttrOrPropLiteralValue(name, value);
2135
+ }
2136
+ else {
2137
+ result = yieldAttrOrPropDynamicValue(node.name, name, value, cxt);
2138
+ }
2139
+ if (result.length > 0 && name === 'class') {
2140
+ // actually yielded a class attribute value
2141
+ hasClassAttribute = true;
2142
+ }
2143
+ return result;
2144
+ });
2145
+ let childContent;
2146
+ // An element can have children or lwc:inner-html, but not both
2147
+ // If it has both, the template compiler will throw an error before reaching here
2148
+ if (node.children.length) {
2149
+ childContent = irChildrenToEs(node.children, cxt);
2150
+ }
2151
+ else if (innerHtmlDirective) {
2152
+ const value = innerHtmlDirective.value;
2153
+ const unsanitizedHtmlExpression = value.type === 'Literal' ? estreeToolkit.builders.literal(value.value) : expressionIrToEs(value, cxt);
2154
+ childContent = [bYieldSanitizedHtml(unsanitizedHtmlExpression)];
2155
+ cxt.import('sanitizeHtmlContent');
2156
+ }
2157
+ else {
2158
+ childContent = [];
2159
+ }
2160
+ const isForeignSelfClosingElement = node.namespace !== shared.HTML_NAMESPACE && childContent.length === 0;
2161
+ const isSelfClosingElement = shared.isVoidElement(node.name, shared.HTML_NAMESPACE) || isForeignSelfClosingElement;
2162
+ return [
2163
+ bYield(estreeToolkit.builders.literal(`<${node.name}`)),
2164
+ bConditionallyYieldDanglingSlotName(),
2165
+ // If we haven't already prefixed the scope token to an existing class, add an explicit class here
2166
+ ...(hasClassAttribute ? [] : [bConditionallyYieldScopeTokenClass()]),
2167
+ ...yieldAttrsAndProps,
2168
+ bYield(estreeToolkit.builders.literal(isForeignSelfClosingElement ? `/>` : `>`)),
2169
+ ...(isSelfClosingElement ? [] : [...childContent, bYield(estreeToolkit.builders.literal(`</${node.name}>`))]),
2170
+ ].filter(Boolean);
2171
+ };
2172
+
2173
+ /*
2174
+ * Copyright (c) 2024, salesforce.com, inc.
2175
+ * All rights reserved.
2176
+ * SPDX-License-Identifier: MIT
2177
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2178
+ */
2179
+ const bForOfYieldFrom$1 = (esTemplate `
2180
+ for (let [${estreeToolkit.is.identifier}, ${estreeToolkit.is.identifier}] of Object.entries(${estreeToolkit.is.expression} ?? {})) {
2181
+ ${estreeToolkit.is.statement};
2182
+ }
2183
+ `);
2184
+ const ForEach = function ForEach(node, cxt) {
2185
+ const forItemId = node.item.name;
2186
+ const forIndexId = node.index?.name ?? '__unused__';
2187
+ cxt.pushLocalVars([forItemId, forIndexId]);
2188
+ const forEachStatements = irChildrenToEs(node.children, cxt);
2189
+ cxt.popLocalVars();
2190
+ const expression = node.expression;
2191
+ const iterable = getScopedExpression(expression, cxt);
2192
+ return [
2193
+ bForOfYieldFrom$1(estreeToolkit.builders.identifier(forIndexId), estreeToolkit.builders.identifier(forItemId), iterable, optimizeAdjacentYieldStmts(forEachStatements)),
2194
+ ];
2195
+ };
2196
+
2197
+ /*
2198
+ * Copyright (c) 2024, salesforce.com, inc.
2199
+ * All rights reserved.
2200
+ * SPDX-License-Identifier: MIT
2201
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2202
+ */
2203
+ function getRootMemberExpression(node) {
2204
+ return node.object.type === 'MemberExpression' ? getRootMemberExpression(node.object) : node;
2205
+ }
2206
+ function getRootIdentifier(node) {
2207
+ const rootMemberExpression = getRootMemberExpression(node);
2208
+ return estreeToolkit.is.identifier(rootMemberExpression?.object) ? rootMemberExpression.object : null;
2209
+ }
2210
+ const bForOfYieldFrom = (esTemplate `
2211
+ for (let ${estreeToolkit.is.identifier} of toIteratorDirective(${estreeToolkit.is.expression} ?? [])) {
2212
+ ${estreeToolkit.is.statement};
2213
+ }
2214
+ `);
2215
+ const ForOf = function ForEach(node, cxt) {
2216
+ const id = node.iterator.name;
2217
+ cxt.pushLocalVars([id]);
2218
+ const forEachStatements = irChildrenToEs(node.children, cxt);
2219
+ cxt.popLocalVars();
2220
+ const expression = node.expression;
2221
+ const scopeReferencedId = estreeToolkit.is.memberExpression(expression)
2222
+ ? getRootIdentifier(expression)
2223
+ : null;
2224
+ const iterable = cxt.isLocalVar(scopeReferencedId?.name)
2225
+ ? node.expression
2226
+ : estreeToolkit.builders.memberExpression(estreeToolkit.builders.identifier('instance'), node.expression);
2227
+ cxt.import('toIteratorDirective');
2228
+ return [
2229
+ bForOfYieldFrom(estreeToolkit.builders.identifier(id), iterable, optimizeAdjacentYieldStmts(forEachStatements)),
2230
+ ];
2231
+ };
2232
+
2233
+ /*
2234
+ * Copyright (c) 2024, salesforce.com, inc.
2235
+ * All rights reserved.
2236
+ * SPDX-License-Identifier: MIT
2237
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2238
+ */
2239
+ const LegacyIf = function If(node, cxt) {
2240
+ const { modifier: trueOrFalseAsStr, condition, children } = node;
2241
+ const trueOrFalse = trueOrFalseAsStr === 'true';
2242
+ const test = trueOrFalse
2243
+ ? expressionIrToEs(condition, cxt)
2244
+ : estreeToolkit.builders.unaryExpression('!', expressionIrToEs(condition, cxt));
2245
+ const childStatements = irChildrenToEs(children, cxt);
2246
+ const block = estreeToolkit.builders.blockStatement(optimizeAdjacentYieldStmts(childStatements));
2247
+ return [estreeToolkit.builders.ifStatement(test, block)];
2248
+ };
2249
+
2250
+ /*
2251
+ * Copyright (c) 2024, salesforce.com, inc.
2252
+ * All rights reserved.
2253
+ * SPDX-License-Identifier: MIT
2254
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2255
+ */
2256
+ const bConditionalSlot = (esTemplateWithYield `
2257
+ if (isLightDom) {
2258
+ const isScopedSlot = ${ /* isScopedSlot */estreeToolkit.is.literal};
2259
+ const isSlotted = ${ /* isSlotted */estreeToolkit.is.literal};
2260
+ const slotName = ${ /* slotName */estreeToolkit.is.expression} ?? "";
2261
+ const lightGenerators = lightSlottedContent?.[slotName];
2262
+ const scopedGenerators = scopedSlottedContent?.[slotName];
2263
+ const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
2264
+ const generators = isScopedSlot ? scopedGenerators : lightGenerators;
2265
+ /*
2266
+ If a slotAttributeValue is present, it should be provided for assignment to any slotted content. This behavior aligns with v1 and engine-dom.
2267
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example.
2268
+ Note the slot mapping does not work for scoped slots, so the slot name is not rendered in this case.
2269
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/scoped-slots for example.
2270
+ */
2271
+ const danglingSlotName = !isScopedSlot ? ${ /* slotAttributeValue */estreeToolkit.is.expression} || slotAttributeValue : null;
2272
+ // start bookend HTML comment for light DOM slot vfragment
2273
+ if (!isSlotted) {
2274
+ yield '<!---->';
2275
+
2276
+ // If there is slot data, scoped slot factory has its own vfragment hence its own bookend
2277
+ if (isScopedSlot && generators) {
2278
+ yield '<!---->';
2279
+ }
2280
+ }
2281
+
2282
+ if (generators) {
2283
+ for (let i = 0; i < generators.length; i++) {
2284
+ yield* generators[i](contextfulParent, ${ /* scoped slot data */isNullableOf(estreeToolkit.is.expression)}, danglingSlotName);
2285
+ // Scoped slotted data is separated by bookends. Final bookends are added outside of the loop below.
2286
+ if (isScopedSlot && i < generators.length - 1) {
2287
+ yield '<!---->';
2288
+ yield '<!---->';
2289
+ }
2290
+ }
2291
+ /*
2292
+ If there were mismatched slots, do not fallback to the default. This is required for parity with
2293
+ engine-core which resets children to an empty array when there are children (mismatched or not).
2294
+ Because the child nodes are reset, the default slotted content is not rendered in the mismatched slot case.
2295
+ See https://github.com/salesforce/lwc/blob/master/packages/%40lwc/engine-core/src/framework/api.ts#L238
2296
+ */
2297
+ } else if (!mismatchedSlots) {
2298
+ // If we're in this else block, then the generator _must_ have yielded
2299
+ // something. It's impossible for a slottedContent["foo"] to exist
2300
+ // without the generator yielding at least a text node / element.
2301
+ ${ /* slot fallback content */estreeToolkit.is.statement}
2302
+ }
2303
+
2304
+ // end bookend HTML comment for light DOM slot vfragment
2305
+ if (!isSlotted) {
2306
+ yield '<!---->';
2307
+
2308
+ // If there is slot data, scoped slot factory has its own vfragment hence its own bookend
2309
+ if (isScopedSlot && generators) {
2310
+ yield '<!---->';
2311
+ }
2312
+ }
2313
+ } else {
2314
+ ${ /* slot element AST */estreeToolkit.is.statement}
2315
+ }
2316
+ `);
2317
+ const Slot = function Slot(node, ctx) {
2318
+ const slotBindDirective = node.directives.find((dir) => dir.name === 'SlotBind');
2319
+ const slotBound = slotBindDirective?.value
2320
+ ? getScopedExpression(slotBindDirective.value, ctx)
2321
+ : null;
2322
+ const slotName = bAttributeValue(node, 'name');
2323
+ // FIXME: avoid serializing the slot's children twice
2324
+ const slotAst = Element(node, ctx);
2325
+ const slotChildren = irChildrenToEs(node.children, ctx);
2326
+ const isScopedSlot = estreeToolkit.builders.literal(Boolean(slotBound));
2327
+ const isSlotted = estreeToolkit.builders.literal(Boolean(ctx.isSlotted));
2328
+ const slotAttributeValue = bAttributeValue(node, 'slot');
2329
+ return [
2330
+ bConditionalSlot(isScopedSlot, isSlotted, slotName, slotAttributeValue, slotBound, slotChildren, slotAst),
2331
+ ];
2332
+ };
2333
+
2334
+ /*
2335
+ * Copyright (c) 2024, salesforce.com, inc.
2336
+ * All rights reserved.
2337
+ * SPDX-License-Identifier: MIT
2338
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2339
+ */
2340
+ const Text = function Text(node, cxt) {
2341
+ if (isLastConcatenatedNode(cxt)) {
2342
+ // render all concatenated content up to us
2343
+ return generateConcatenatedTextNodesExpressions(cxt);
2344
+ }
2345
+ // our last sibling is responsible for rendering our content, not us
2346
+ return [];
2347
+ };
2348
+
2349
+ /*
2350
+ * Copyright (c) 2024, salesforce.com, inc.
2351
+ * All rights reserved.
2352
+ * SPDX-License-Identifier: MIT
2353
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2354
+ */
2355
+ function createNewContext(templateOptions) {
2356
+ const importManager = new ImportManager();
2357
+ const localVarStack = [];
2358
+ const pushLocalVars = (vars) => {
2359
+ localVarStack.push(new Set(vars));
2360
+ };
2361
+ const popLocalVars = () => {
2362
+ localVarStack.pop();
2363
+ };
2364
+ const isLocalVar = (varName) => {
2365
+ if (!varName) {
2366
+ return false;
2367
+ }
2368
+ for (const stackFrame of localVarStack) {
2369
+ if (stackFrame.has(varName)) {
2370
+ return true;
2371
+ }
2372
+ }
2373
+ return false;
2374
+ };
2375
+ // Unique local variable names across block scopes, we don't care about block scope order
2376
+ const getLocalVars = () => [
2377
+ ...new Set(localVarStack.flatMap((varsSet) => Array.from(varsSet))),
2378
+ ];
2379
+ const hoistedStatements = {
2380
+ module: [],
2381
+ templateFn: [],
2382
+ };
2383
+ const hoistedModuleDedupe = new Set();
2384
+ const hoistedTemplateDedupe = new Set();
2385
+ const hoist = {
2386
+ // Anything added here will be inserted at the top of the compiled template's
2387
+ // JS module.
2388
+ module(stmt, optionalDedupeKey) {
2389
+ if (optionalDedupeKey) {
2390
+ if (hoistedModuleDedupe.has(optionalDedupeKey)) {
2391
+ return;
2392
+ }
2393
+ hoistedModuleDedupe.add(optionalDedupeKey);
2394
+ }
2395
+ hoistedStatements.module.push(stmt);
2396
+ },
2397
+ // Anything added here will be inserted at the top of the JavaScript function
2398
+ // corresponding to the template (typically named `__lwcTmpl`).
2399
+ templateFn(stmt, optionalDedupeKey) {
2400
+ if (optionalDedupeKey) {
2401
+ if (hoistedTemplateDedupe.has(optionalDedupeKey)) {
2402
+ return;
2403
+ }
2404
+ hoistedTemplateDedupe.add(optionalDedupeKey);
2405
+ }
2406
+ hoistedStatements.templateFn.push(stmt);
2407
+ },
2408
+ };
2409
+ const shadowSlotToFnName = new Map();
2410
+ let fnNameUniqueId = 0;
2411
+ // At present, we only track shadow-slotted content. This is because the functions
2412
+ // corresponding to shadow-slotted content are deduped and hoisted to the top of
2413
+ // the template function, whereas light-dom-slotted content is inlined. It may be
2414
+ // desirable to also track light-dom-slotted content at some future point in time.
2415
+ const slots = {
2416
+ shadow: {
2417
+ isDuplicate(uniqueNodeId) {
2418
+ return shadowSlotToFnName.has(uniqueNodeId);
2419
+ },
2420
+ register(uniqueNodeId, kebabCmpName) {
2421
+ if (slots.shadow.isDuplicate(uniqueNodeId)) {
2422
+ return shadowSlotToFnName.get(uniqueNodeId);
2423
+ }
2424
+ const shadowSlotContentFnName = `__lwcGenerateShadowSlottedContent_${kebabCmpName}_${fnNameUniqueId++}`;
2425
+ shadowSlotToFnName.set(uniqueNodeId, shadowSlotContentFnName);
2426
+ return shadowSlotContentFnName;
2427
+ },
2428
+ getFnName(uniqueNodeId) {
2429
+ return shadowSlotToFnName.get(uniqueNodeId) ?? null;
2430
+ },
2431
+ },
2432
+ };
2433
+ return {
2434
+ getImports: () => importManager.getImportDeclarations(),
2435
+ cxt: {
2436
+ pushLocalVars,
2437
+ popLocalVars,
2438
+ isLocalVar,
2439
+ getLocalVars,
2440
+ templateOptions,
2441
+ hoist,
2442
+ hoistedStatements,
2443
+ slots,
2444
+ import: importManager.add.bind(importManager),
2445
+ siblings: undefined,
2446
+ currentNodeIndex: undefined,
2447
+ },
2448
+ };
2449
+ }
2450
+
2451
+ /*
2452
+ * Copyright (c) 2024, salesforce.com, inc.
2453
+ * All rights reserved.
2454
+ * SPDX-License-Identifier: MIT
2455
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2456
+ */
2457
+ // lwc:if/lwc:elseif/lwc:else use bookend comments due to VFragment vdom node using them
2458
+ // The bookends should surround the entire if/elseif/else series
2459
+ // Note: these should only be rendered if _something_ is rendered by a series of if/elseif/else's
2460
+ function bYieldBookendComment() {
2461
+ return estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(estreeToolkit.builders.literal(`<!---->`)));
2462
+ }
2463
+ function bBlockStatement(childNodes, cxt) {
2464
+ const childStatements = irChildrenToEs(childNodes, cxt);
2465
+ // Due to `flattenFragmentsInChildren`, we have to remove bookends for all _top-level_ slotted
2466
+ // content. This applies to both light DOM and shadow DOM slots, although light DOM slots have
2467
+ // the additional wrinkle that they themselves are VFragments with their own bookends.
2468
+ // https://github.com/salesforce/lwc/blob/a33b390/packages/%40lwc/engine-core/src/framework/rendering.ts#L718-L753
2469
+ const statements = cxt.isSlotted
2470
+ ? childStatements
2471
+ : [bYieldBookendComment(), ...childStatements, bYieldBookendComment()];
2472
+ return estreeToolkit.builders.blockStatement(optimizeAdjacentYieldStmts(statements));
2473
+ }
2474
+ function bIfStatement(ifElseIfNode, cxt) {
2475
+ const { children, condition, else: elseNode } = ifElseIfNode;
2476
+ let elseBlock = null;
2477
+ if (elseNode) {
2478
+ if (elseNode.type === 'ElseBlock') {
2479
+ elseBlock = bBlockStatement(elseNode.children, cxt);
2480
+ }
2481
+ else {
2482
+ elseBlock = bIfStatement(elseNode, cxt);
2483
+ }
2484
+ }
2485
+ return estreeToolkit.builders.ifStatement(expressionIrToEs(condition, cxt), bBlockStatement(children, cxt), elseBlock);
2486
+ }
2487
+ const IfBlock = function IfBlock(node, cxt) {
2488
+ return [bIfStatement(node, cxt)];
2489
+ };
2490
+
2491
+ /*
2492
+ * Copyright (c) 2024, salesforce.com, inc.
2493
+ * All rights reserved.
2494
+ * SPDX-License-Identifier: MIT
2495
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2496
+ */
2497
+ const bThrowError = (esTemplate `
2498
+ throw new Error(${estreeToolkit.is.literal});
2499
+ `);
2500
+ const Root = function Root(node, cxt) {
2501
+ return irChildrenToEs(node.children, cxt);
2502
+ };
2503
+ const defaultTransformer = (node) => {
2504
+ throw new Error(`Unimplemented IR node: ${node_util.inspect(node)}`);
2505
+ };
2506
+ const transformers = {
2507
+ Comment,
2508
+ Component,
2509
+ Element,
2510
+ ExternalComponent: Element,
2511
+ ForEach,
2512
+ ForOf,
2513
+ If: LegacyIf,
2514
+ IfBlock,
2515
+ Root,
2516
+ Text,
2517
+ // lwc:elseif cannot exist without an lwc:if (IfBlock); this gets handled by that transformer
2518
+ ElseifBlock: defaultTransformer,
2519
+ // lwc:elseif cannot exist without an lwc:elseif (IfBlock); this gets handled by that transformer
2520
+ ElseBlock: defaultTransformer,
2521
+ ScopedSlotFragment: defaultTransformer,
2522
+ Slot,
2523
+ Lwc: LwcComponent,
2524
+ };
2525
+ function irChildrenToEs(children, cxt, cb) {
2526
+ const result = [];
2527
+ for (let i = 0; i < children.length; i++) {
2528
+ // must set the siblings inside the for loop due to nested children
2529
+ cxt.siblings = children;
2530
+ cxt.currentNodeIndex = i;
2531
+ const cleanUp = cb?.(children[i]);
2532
+ result.push(...irToEs(children[i], cxt));
2533
+ cleanUp?.();
2534
+ }
2535
+ // reset the context
2536
+ cxt.siblings = undefined;
2537
+ cxt.currentNodeIndex = undefined;
2538
+ return result;
2539
+ }
2540
+ function irToEs(node, cxt) {
2541
+ if ('directives' in node && node.directives.some((d) => d.name === 'Dynamic')) {
2542
+ return [
2543
+ bThrowError(estreeToolkit.builders.literal('The lwc:dynamic directive is not supported for SSR. Use <lwc:component> instead.')),
2544
+ ];
2545
+ }
2546
+ const transformer = transformers[node.type];
2547
+ return transformer(node, cxt);
2548
+ }
2549
+ function templateIrToEsTree(node, contextOpts) {
2550
+ const { getImports, cxt } = createNewContext(contextOpts);
2551
+ const statements = irToEs(node, cxt);
2552
+ return {
2553
+ addImport: cxt.import,
2554
+ getImports,
2555
+ statements,
2556
+ cxt,
2557
+ };
2558
+ }
2559
+
2560
+ /*
2561
+ * Copyright (c) 2024, salesforce.com, inc.
2562
+ * All rights reserved.
2563
+ * SPDX-License-Identifier: MIT
2564
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2565
+ */
2566
+ // TODO [#4663]: Render mode mismatch between template and compiler should throw.
2567
+ const bExportTemplate = (esTemplate `
2568
+ export default async function* __lwcTmpl(
2569
+ shadowSlottedContent,
2570
+ lightSlottedContent,
2571
+ scopedSlottedContent,
2572
+ Cmp,
2573
+ instance,
2574
+ renderContext
2575
+ ) {
2576
+ // Deliberately using let so we can mutate as many times as we want in the same scope.
2577
+ // These should be scoped to the "tmpl" function however, to avoid conflicts with other templates.
2578
+ let textContentBuffer = '';
2579
+ let didBufferTextContent = false;
2580
+
2581
+ // This will get overridden but requires initialization.
2582
+ const slotAttributeValue = null;
2583
+
2584
+ // Establishes a contextual relationship between two components for ContextProviders.
2585
+ // This variable will typically get overridden (shadowed) within slotted content.
2586
+ const contextfulParent = instance;
2587
+
2588
+ const isLightDom = Cmp.renderMode === 'light';
2589
+ if (!isLightDom) {
2590
+ yield \`<template shadowrootmode="open"\${Cmp.delegatesFocus ? ' shadowrootdelegatesfocus' : ''}>\`
2591
+ }
2592
+
2593
+ const { stylesheets: staticStylesheets } = Cmp;
2594
+ if (defaultStylesheets || defaultScopedStylesheets || staticStylesheets) {
2595
+ yield renderStylesheets(
2596
+ renderContext,
2597
+ defaultStylesheets,
2598
+ defaultScopedStylesheets,
2599
+ staticStylesheets,
2600
+ stylesheetScopeToken,
2601
+ Cmp,
2602
+ hasScopedStylesheets,
2603
+ );
2604
+ }
2605
+
2606
+ ${estreeToolkit.is.statement};
2607
+
2608
+ if (!isLightDom) {
2609
+ yield '</template>';
2610
+ if (shadowSlottedContent) {
2611
+ // instance must be passed in; this is used to establish the contextful relationship
2612
+ // between context provider (aka parent component) and context consumer (aka slotted content)
2613
+ yield* shadowSlottedContent(contextfulParent);
2614
+ }
2615
+ }
2616
+ }
2617
+ `);
2618
+ function compileTemplate(src, filename, options, compilationMode) {
2619
+ const { root, warnings } = templateCompiler.parse(src, {
2620
+ // `options` is from @lwc/compiler, and may have flags that @lwc/template-compiler doesn't
2621
+ // know about, so we must explicitly extract the relevant props.
2622
+ name: options.name,
2623
+ namespace: options.namespace,
2624
+ customRendererConfig: options.customRendererConfig,
2625
+ experimentalComputedMemberExpression: options.experimentalComputedMemberExpression,
2626
+ experimentalComplexExpressions: options.experimentalComplexExpressions,
2627
+ enableDynamicComponents: options.enableDynamicComponents,
2628
+ enableLwcOn: options.enableLwcOn,
2629
+ preserveHtmlComments: options.preserveHtmlComments,
2630
+ enableStaticContentOptimization: options.enableStaticContentOptimization,
2631
+ instrumentation: options.instrumentation,
2632
+ apiVersion: options.apiVersion,
2633
+ disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
2634
+ // TODO [#3331]: remove usage of lwc:dynamic in 246
2635
+ experimentalDynamicDirective: options.experimentalDynamicDirective,
2636
+ });
2637
+ if (!root || warnings.length) {
2638
+ for (const warning of warnings) {
2639
+ // eslint-disable-next-line no-console
2640
+ console.error('Cannot compile:', warning.message);
2641
+ }
2642
+ // The legacy SSR implementation would not bail from compilation even if a
2643
+ // DiagnosticLevel.Fatal error was encountered. It would only fail if the
2644
+ // template parser failed to return a root node. That behavior is duplicated
2645
+ // here.
2646
+ if (!root) {
2647
+ throw new Error('Template compilation failure; see warnings in the console.');
2648
+ }
2649
+ }
2650
+ const preserveComments = !!root.directives.find((directive) => directive.name === 'PreserveComments')?.value?.value;
2651
+ const experimentalComplexExpressions = Boolean(options.experimentalComplexExpressions);
2652
+ const apiVersion = Number(options.apiVersion);
2653
+ const { addImport, getImports, statements, cxt } = templateIrToEsTree(root, {
2654
+ preserveComments,
2655
+ experimentalComplexExpressions,
2656
+ apiVersion,
2657
+ });
2658
+ addImport(['renderStylesheets', 'hasScopedStaticStylesheets']);
2659
+ for (const [imports, source] of getStylesheetImports(filename)) {
2660
+ addImport(imports, source);
2661
+ }
2662
+ let tmplDecl = bExportTemplate(optimizeAdjacentYieldStmts([
2663
+ // Deep in the compiler, we may choose to hoist statements and declarations
2664
+ // to the top of the template function. After `templateIrToEsTree`, these
2665
+ // hoisted statements/declarations are prepended to the template function's
2666
+ // body.
2667
+ ...cxt.hoistedStatements.templateFn,
2668
+ ...statements,
2669
+ ]));
2670
+ // Ideally, we'd just do ${LWC_VERSION_COMMENT} in the code template,
2671
+ // but placeholders have a special meaning for `esTemplate`.
2672
+ tmplDecl = immer.produce(tmplDecl, (draft) => {
2673
+ draft.declaration.body.trailingComments = [
2674
+ {
2675
+ type: 'Block',
2676
+ value: shared.LWC_VERSION_COMMENT,
2677
+ },
2678
+ ];
2679
+ });
2680
+ let program = estreeToolkit.builders.program([
2681
+ // All import declarations come first...
2682
+ ...getImports(),
2683
+ // ... followed by any statements or declarations that need to be hoisted
2684
+ // to the top of the module scope...
2685
+ ...cxt.hoistedStatements.module,
2686
+ // ... followed by the template function declaration itself.
2687
+ tmplDecl,
2688
+ ], 'module');
2689
+ addScopeTokenDeclarations(program, filename, options.namespace, options.name);
2690
+ if (compilationMode === 'async' || compilationMode === 'sync') {
2691
+ program = transmogrify(program, compilationMode);
2692
+ }
2693
+ return {
2694
+ code: astring.generate(program, {
2695
+ // The generated AST doesn't have comments; this just preserves the LWC version comment
2696
+ comments: true,
2697
+ }),
2698
+ };
2699
+ }
2700
+
2701
+ /*
2702
+ * Copyright (c) 2024, salesforce.com, inc.
2703
+ * All rights reserved.
2704
+ * SPDX-License-Identifier: MIT
2705
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
2706
+ */
2707
+ function compileComponentForSSR(src, filename, options, mode = shared.DEFAULT_SSR_MODE) {
2708
+ const tagName = shared.generateCustomElementTagName(options.namespace, options.name);
2709
+ const { code } = compileJS(src, filename, tagName, options, mode);
2710
+ return { code, map: undefined };
2711
+ }
2712
+ function compileTemplateForSSR(src, filename, options, mode = shared.DEFAULT_SSR_MODE) {
2713
+ const { code } = compileTemplate(src, filename, options, mode);
2714
+ return { code, map: undefined };
2715
+ }
2716
+
2717
+ exports.compileComponentForSSR = compileComponentForSSR;
2718
+ exports.compileTemplateForSSR = compileTemplateForSSR;
2719
+ /** version: 9.0.3 */
2720
+ //# sourceMappingURL=index.cjs.map