diff --git a/packages/@lwc/aria-reflection/package.json b/packages/@lwc/aria-reflection/package.json index f9e0439f25..cf523560e9 100644 --- a/packages/@lwc/aria-reflection/package.json +++ b/packages/@lwc/aria-reflection/package.json @@ -34,8 +34,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/babel-plugin-component/package.json b/packages/@lwc/babel-plugin-component/package.json index 5b75e64d65..2f3e2e0678 100644 --- a/packages/@lwc/babel-plugin-component/package.json +++ b/packages/@lwc/babel-plugin-component/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/compiler/package.json b/packages/@lwc/compiler/package.json index b38cd27656..a292a3dbcd 100755 --- a/packages/@lwc/compiler/package.json +++ b/packages/@lwc/compiler/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/engine-core/package.json b/packages/@lwc/engine-core/package.json index 2b410e8199..f65e1fe935 100644 --- a/packages/@lwc/engine-core/package.json +++ b/packages/@lwc/engine-core/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/engine-dom/package.json b/packages/@lwc/engine-dom/package.json index 2fc25226f7..d33cc5b75e 100644 --- a/packages/@lwc/engine-dom/package.json +++ b/packages/@lwc/engine-dom/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/engine-server/package.json b/packages/@lwc/engine-server/package.json index 5828dacf45..f0c0b2d913 100644 --- a/packages/@lwc/engine-server/package.json +++ b/packages/@lwc/engine-server/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/errors/package.json b/packages/@lwc/errors/package.json index 0a1cb520be..9b4ab36e3e 100644 --- a/packages/@lwc/errors/package.json +++ b/packages/@lwc/errors/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/features/package.json b/packages/@lwc/features/package.json index c0321aa808..ce4eea5824 100644 --- a/packages/@lwc/features/package.json +++ b/packages/@lwc/features/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/module-resolver/package.json b/packages/@lwc/module-resolver/package.json index 00f5e13fd4..80d9bd5839 100644 --- a/packages/@lwc/module-resolver/package.json +++ b/packages/@lwc/module-resolver/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/rollup-plugin/README.md b/packages/@lwc/rollup-plugin/README.md index 9c4fa03901..2780773ff0 100644 --- a/packages/@lwc/rollup-plugin/README.md +++ b/packages/@lwc/rollup-plugin/README.md @@ -37,3 +37,4 @@ export default { - `disableSyntheticShadowSupport` (type: `boolean`, default: `false`) - Set to true if synthetic shadow DOM support is not needed, which can result in smaller output. - `apiVersion` (type: `number`, default: `undefined`) - Set to a valid API version such as 58, 59, etc. This will be overriden if the component itself overrides the version with a `*.js-meta.xml` file. - `enableStaticContentOptimization` (type: `boolean`, optional) - True if the static content optimization should be enabled. Passed to `@lwc/template-compiler`. True by default. +- `targetSSR` (type: `boolean`) - Utilize the experimental SSR compilation mode. False by default. Do not use unless you know what you're doing. diff --git a/packages/@lwc/rollup-plugin/package.json b/packages/@lwc/rollup-plugin/package.json index c0b29d9626..90a1aa7fb8 100644 --- a/packages/@lwc/rollup-plugin/package.json +++ b/packages/@lwc/rollup-plugin/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/shared/package.json b/packages/@lwc/shared/package.json index 8e88271f71..6247689d93 100644 --- a/packages/@lwc/shared/package.json +++ b/packages/@lwc/shared/package.json @@ -30,8 +30,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { diff --git a/packages/@lwc/shared/src/ecmascript.ts b/packages/@lwc/shared/src/ecmascript.ts new file mode 100644 index 0000000000..1735e79767 --- /dev/null +++ b/packages/@lwc/shared/src/ecmascript.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ +export const reservedKeywords = new Set([ + 'NaN', + 'arguments', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'else', + 'enum', + 'eval', + 'export', + 'extends', + 'false', + 'finally', + 'for', + 'function', + 'if', + 'implements', + 'import', + 'in', + 'instanceof', + 'interface', + 'let', + 'new', + 'null', + 'package', + 'private', + 'protected', + 'public', + 'return', + 'static', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'undefined', + 'var', + 'void', + 'while', + 'with', + 'yield', +]); diff --git a/packages/@lwc/shared/src/index.ts b/packages/@lwc/shared/src/index.ts index 091638e8c2..f86a74fedc 100644 --- a/packages/@lwc/shared/src/index.ts +++ b/packages/@lwc/shared/src/index.ts @@ -8,6 +8,7 @@ import * as assert from './assert'; export * from './api-version'; export * from './aria'; +export * from './ecmascript'; export * from './language'; export * from './keys'; export * from './void-elements'; diff --git a/packages/@lwc/ssr-compiler/package.json b/packages/@lwc/ssr-compiler/package.json index 2fbbb084d0..e901d05559 100644 --- a/packages/@lwc/ssr-compiler/package.json +++ b/packages/@lwc/ssr-compiler/package.json @@ -7,7 +7,9 @@ "version": "6.5.3", "description": "Compile component for use during server-side rendering", "keywords": [ - "lwc" + "compiler", + "lwc", + "ssr" ], "homepage": "https://lwc.dev", "repository": { @@ -30,8 +32,7 @@ ], "scripts": { "build": "rollup --config ../../../scripts/rollup/rollup.config.js", - "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen", - "test-manual": "jest ./src/**/*.spec.ts" + "dev": "rollup --config ../../../scripts/rollup/rollup.config.js --watch --no-watch.clearScreen" }, "nx": { "targets": { @@ -47,7 +48,7 @@ "@lwc/sfdc-compiler-utils": "6.5.3-0", "@lwc/style-compiler": "6.5.3", "@lwc/template-compiler": "6.5.3", - "acorn": "~8.10.0", + "acorn": "8.10.0", "astring": "^1.8.6", "estree-toolkit": "^1.7.3", "immer": "^10.0.3", diff --git a/packages/@lwc/ssr-compiler/src/__tests__/estemplate.spec.ts b/packages/@lwc/ssr-compiler/src/__tests__/estemplate.spec.ts new file mode 100644 index 0000000000..968b75bdba --- /dev/null +++ b/packages/@lwc/ssr-compiler/src/__tests__/estemplate.spec.ts @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ + +import { is, builders as b } from 'estree-toolkit'; +import { esTemplate, esTemplateWithYield } from '../estemplate'; + +Object.entries({ + esTemplate, + esTemplateWithYield, +}).forEach(([topLevelFnName, topLevelFn]) => { + const yieldStmtsAllowed = topLevelFnName === 'esTemplateWithYield'; + + describe(topLevelFnName, () => { + describe('failure upon parse', () => { + yieldStmtsAllowed || + test('with yield statements', () => { + const createTemplate = () => topLevelFn` + const foo = "bar"; + yield foo; + `; + expect(createTemplate).toThrow('Unexpected token'); + }); + + test('when attempting to replace unreplaceable code constructs', () => { + // Someone might try to create a template where 'class' or 'function' + // is provided as an argument to the ES template. + const isFunctionOrClass = (node: any) => + is.functionDeclaration(node) || is.classDeclaration(node); + const createTemplate = () => topLevelFn` + ${isFunctionOrClass} classOrFunctionDecl {} + `; + expect(createTemplate).toThrow('Unexpected token'); + }); + + test('syntax errors due to typo', () => { + const createTemplate = () => topLevelFn` + calss MyCmp extends LightningElement { + connectedCallbac() { + console.log('if you see this, calss has been added to the JS language'); + } + } + `; + expect(createTemplate).toThrow('Unexpected token'); + }); + }); + + describe('failure upon invocation', () => { + test('when replacing incorrect node type', () => { + const tmpl = topLevelFn` + const ${is.identifier} = 'foo'; + `; + const doReplacement = () => tmpl(b.literal('I am not an identifier')); + expect(doReplacement).toThrow( + 'Validation failed for templated node of type Identifier' + ); + }); + }); + + describe('successful replacement', () => { + yieldStmtsAllowed && + test('with yield statements', () => { + const tmpl = topLevelFn` + yield ${is.literal}; + `; + const replacedAst = tmpl(b.literal('foo')); + + expect(replacedAst).toMatchObject({ + expression: { + argument: { + type: 'Literal', + value: 'foo', + }, + delegate: false, + type: 'YieldExpression', + }, + type: 'ExpressionStatement', + }); + }); + + test('with LH identifier nodes', () => { + const tmpl = topLevelFn` + const ${is.identifier} = 'foobar' + `; + const replacedAst = tmpl(b.identifier('heyImNewHere')); + expect(replacedAst).toMatchObject({ + declarations: [ + { + id: { + name: 'heyImNewHere', + type: 'Identifier', + }, + init: { + type: 'Literal', + value: 'foobar', + }, + type: 'VariableDeclarator', + }, + ], + kind: 'const', + type: 'VariableDeclaration', + }); + }); + test('with multiple nodes', () => { + const tmpl = topLevelFn` + const foo = ${is.literal} + ${is.identifier}; + fnCall(foo, ${is.objectExpression}); + `; + const replacedAst = tmpl( + b.literal(5), + b.identifier('bar'), + b.objectExpression([ + b.property('init', b.literal('config'), b.literal('someConfig')), + ]) + ); + + expect(replacedAst).toMatchObject([ + { + declarations: [ + { + id: { + name: 'foo', + type: 'Identifier', + }, + init: { + left: { + type: 'Literal', + value: 5, + }, + operator: '+', + right: { + name: 'bar', + type: 'Identifier', + }, + type: 'BinaryExpression', + }, + type: 'VariableDeclarator', + }, + ], + kind: 'const', + type: 'VariableDeclaration', + }, + { + expression: { + arguments: [ + { + name: 'foo', + type: 'Identifier', + }, + { + properties: [ + { + computed: false, + key: { + type: 'Literal', + value: 'config', + }, + kind: 'init', + shorthand: false, + type: 'Property', + value: { + type: 'Literal', + value: 'someConfig', + }, + }, + ], + type: 'ObjectExpression', + }, + ], + callee: { + name: 'fnCall', + type: 'Identifier', + }, + optional: false, + type: 'CallExpression', + }, + type: 'ExpressionStatement', + }, + ]); + }); + }); + }); +}); diff --git a/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts b/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts index 432e716b43..4e18d3e6fa 100644 --- a/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts +++ b/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts @@ -9,18 +9,18 @@ import fs from 'fs'; import path from 'path'; import { rollup, RollupLog } from 'rollup'; -// @ts-ignore import lwcRollupPlugin from '@lwc/rollup-plugin'; import { isVoidElement, HTML_NAMESPACE } from '@lwc/shared'; +import { FeatureFlagName } from '@lwc/features/dist/types'; import { testFixtureDir } from '@lwc/jest-utils-lwc-internals'; -import { serverSideRenderComponent } from '../index'; +import { serverSideRenderComponent } from '@lwc/ssr-runtime'; interface FixtureModule { tagName: string; default: any; generateMarkup: any; props?: { [key: string]: any }; - features?: any[]; + features?: FeatureFlagName[]; } jest.setTimeout(10_000 /* 10 seconds */); @@ -82,13 +82,19 @@ function formatHTML(src: string): string { if (src.charAt(pos) === '<') { const tagNameMatch = src.slice(pos).match(/(\w+)/); + if (!tagNameMatch) { + throw new Error( + `Expected to find tagname at pos ${pos} but found "${src.slice(pos, 20)}"` + ); + } + // Special handling for `