Skip to content

Commit

Permalink
refactor: use estemplate instead of builder and address pr feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jmsjtu committed Nov 14, 2024
1 parent 2746e71 commit 9ba0901
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 75 deletions.
5 changes: 2 additions & 3 deletions packages/@lwc/babel-plugin-component/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as types from '@babel/types';
import { addDefault, addNamed } from '@babel/helper-module-imports';
import { NodePath } from '@babel/traverse';
import { Visitor } from '@babel/core';
import { getAPIVersionFromNumber } from '@lwc/shared';
import { generateCustomElementTagName, getAPIVersionFromNumber } from '@lwc/shared';
import {
COMPONENT_NAME_KEY,
LWC_PACKAGE_ALIAS,
Expand Down Expand Up @@ -48,8 +48,7 @@ function needsComponentRegistration(path: DeclarationPath) {

function getComponentRegisteredName(t: BabelTypes, state: LwcBabelPluginPass) {
const { namespace, name } = state.opts;
const kebabCasedName = name?.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
const componentName = namespace && kebabCasedName ? `${namespace}-${kebabCasedName}` : '';
const componentName = generateCustomElementTagName(namespace, name);
return t.stringLiteral(componentName);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@lwc/engine-core/src/framework/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ function dc(

if (!isComponentConstructor(Ctor)) {
throw new Error(
`Invalid constructor ${toString(Ctor)} is not a LightningElement constructor.`
`Invalid constructor "${toString(Ctor)}" is not a LightningElement constructor.`
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Invalid constructor frankenstein is not a LightningElement constructor.
Invalid constructor "frankenstein" is not a LightningElement constructor.
20 changes: 20 additions & 0 deletions packages/@lwc/shared/src/custom-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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
*/

/**
* Generates a custom element tag name given a namespace and component name.
* Based on the LWC file system requirements, component names come from the file system name which is
* camel cased. The component's name will be converted to kebab case when the tag name is produced.
*
* @param namespace component namespace
* @param name component name
* @returns component tag name
*/
export function generateCustomElementTagName(namespace: string = '', name: string = '') {
const kebabCasedName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
return `${namespace}-${kebabCasedName}`;
}
1 change: 1 addition & 0 deletions packages/@lwc/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ export * from './overridable-hooks';
export * from './static-part-tokens';
export * from './style';
export * from './signals';
export * from './custom-element';

export { assert };
9 changes: 3 additions & 6 deletions packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { is, builders as b } from 'estree-toolkit';
import { esTemplate } from '../estemplate';
import { isIdentOrRenderCall } from '../estree/validators';
import { bImportDeclaration, bImportDefaultDeclaration } from '../estree/builders';
import { TransformOptions } from '../shared';
import { bWireAdaptersPlumbing } from './wire';

import type {
Expand All @@ -30,8 +29,7 @@ type RenderCallExpression = SimpleCallExpression & {
};

const bGenerateMarkup = esTemplate`
export async function* generateMarkup(tagName, props, attrs, slotted, parent, scopeToken) {
tagName = tagName ?? ${/*component tag name*/ is.literal}
export async function* generateMarkup(tagName = ${/*component tag name*/ is.literal}, props, attrs, slotted, parent, scopeToken) {
attrs = attrs ?? Object.create(null);
props = props ?? Object.create(null);
props = __filterProperties(
Expand Down Expand Up @@ -89,17 +87,16 @@ const bAssignGenerateMarkupToComponentClass = esTemplate`
export function addGenerateMarkupExport(
program: Program,
state: ComponentMetaState,
options: TransformOptions,
tagName: string,
filename: string
) {
const { hasRenderMethod, privateFields, publicFields, tmplExplicitImports } = state;
const { namespace, name } = options;

// The default tag name represents the component name that's passed to the transformer.
// This is needed to generate markup for dynamic components which are invoked through
// the generateMarkup function on the constructor.
// At the time of generation, the invoker does not have reference to its tag name to pass as an argument.
const defaultTagName = b.literal(`${namespace}-${name}`);
const defaultTagName = b.literal(tagName);
const classIdentifier = b.identifier(state.lwcClassName!);
const renderCall = hasRenderMethod
? (b.callExpression(
Expand Down
7 changes: 3 additions & 4 deletions packages/@lwc/ssr-compiler/src/compile-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { catalogWireAdapters } from './wire';
import { removeDecoratorImport } from './remove-decorator-import';
import type { Identifier as EsIdentifier, Program as EsProgram, Expression } from 'estree';
import type { Visitors, ComponentMetaState } from './types';
import type { CompilationMode, TransformOptions } from '../shared';
import type { CompilationMode } from '../shared';
import type {
PropertyDefinition as DecoratatedPropertyDefinition,
MethodDefinition as DecoratatedMethodDefinition,
Expand Down Expand Up @@ -153,7 +153,7 @@ const visitors: Visitors = {
export default function compileJS(
src: string,
filename: string,
options: TransformOptions,
tagName: string,
compilationMode: CompilationMode
) {
let ast = parseModule(src, {
Expand Down Expand Up @@ -190,8 +190,7 @@ export default function compileJS(
};
}

// Add the tag name here, either export tag name as additional export or just attach it to the class
addGenerateMarkupExport(ast, state, options, filename);
addGenerateMarkupExport(ast, state, tagName, filename);
assignGenerateMarkupToComponent(ast, state);

if (compilationMode === 'async' || compilationMode === 'sync') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { builders as b, is } from 'estree-toolkit';
import { is } from 'estree-toolkit';
import { isUndefined } from '@lwc/shared';
import { Transformer } from '../types';
import { expressionIrToEs } from '../expression';
Expand All @@ -15,56 +15,25 @@ import type {
LwcComponent as IrLwcComponent,
Expression as IrExpression,
} from '@lwc/template-compiler';
import type { BlockStatement as EsBlockStatement, Expression, Statement } from 'estree';
import type {
IfStatement as EsIfStatement,
VariableDeclaration as EsVariableDeclaration,
} from 'estree';

const bDynamicComponentConstructorDeclaration = esTemplate`
const Ctor = '${/*lwcIs attribute value*/ is.expression}';
`<EsVariableDeclaration>;

const bYieldFromDynamicComponentConstructorGenerator = esTemplateWithYield`
{
if (Ctor) {
if (typeof Ctor !== 'function' || !(Ctor.prototype instanceof LightningElement)) {
throw new Error(\`Invalid constructor "\${String(Ctor)}" is not a LightningElement constructor.\`)
}
const childProps = __cloneAndDeepFreeze(${/* child props */ is.objectExpression});
const childAttrs = ${/* child attrs */ is.objectExpression};
yield* ${/*component ctor*/ is.expression}[SYMBOL__GENERATE_MARKUP](null, childProps, childAttrs);
}
`<EsBlockStatement>;

const bThrowErrorForInvalidConstructor = esTemplate`
{
throw new Error(\`Invalid constructor \${String(${/*component ctor*/ is.expression})} is not a LightningElement constructor.\`)
yield* Ctor[SYMBOL__GENERATE_MARKUP](null, childProps, childAttrs);
}
`<EsBlockStatement>;

function bIfLwcIsExpressionDefined(lwcIsExpression: Expression, consequent: Statement) {
// instance.lwcIsValue !== undefined && instance.lwcIsValue !== null
const lwcIsExpressionDefined = b.logicalExpression(
'&&',
b.binaryExpression('!==', lwcIsExpression, b.identifier('undefined')),
b.binaryExpression('!==', lwcIsExpression, b.identifier('null'))
);

return b.ifStatement(lwcIsExpressionDefined, b.blockStatement([consequent]));
}

function bifLwcIsExpressionTypeCorrect(
lwcIsExpression: Expression,
consequent: Statement,
alternate: Statement
) {
// typeof instance.lwcIsValue === 'function'
const typeComparison = b.binaryExpression(
'===',
b.unaryExpression('typeof', lwcIsExpression),
b.literal('function')
);

// instance.lwcIsValue.prototype instanceof LightningElement
const protoComparison = b.binaryExpression(
'instanceof',
b.memberExpression(lwcIsExpression, b.identifier('prototype')),
b.identifier('LightningElement')
);

const comparison = b.logicalExpression('&&', typeComparison, protoComparison);

return b.ifStatement(comparison, consequent, alternate);
}
`<EsIfStatement>;

export const LwcComponent: Transformer<IrLwcComponent> = function LwcComponent(node, cxt) {
const { directives } = node;
Expand All @@ -81,20 +50,14 @@ export const LwcComponent: Transformer<IrLwcComponent> = function LwcComponent(n
'import:cloneAndDeepFreeze'
);

// The template compiler has validation to prevent lwcIs.value from being a literal
const lwcIsExpression = expressionIrToEs(lwcIs.value as IrExpression, cxt);
return [
bIfLwcIsExpressionDefined(
lwcIsExpression,
bifLwcIsExpressionTypeCorrect(
lwcIsExpression,
bYieldFromDynamicComponentConstructorGenerator(
getChildAttrsOrProps(node.properties, cxt),
getChildAttrsOrProps(node.attributes, cxt),
lwcIsExpression
),
bThrowErrorForInvalidConstructor(lwcIsExpression)
)
bDynamicComponentConstructorDeclaration(
// The template compiler has validation to prevent lwcIs.value from being a literal
expressionIrToEs(lwcIs.value as IrExpression, cxt)
),
bYieldFromDynamicComponentConstructorGenerator(
getChildAttrsOrProps(node.properties, cxt),
getChildAttrsOrProps(node.attributes, cxt)
),
];
} else {
Expand Down
4 changes: 3 additions & 1 deletion packages/@lwc/ssr-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { generateCustomElementTagName } from '@lwc/shared';
import compileJS from './compile-js';
import compileTemplate from './compile-template';
import type { CompilationMode, TransformOptions } from './shared';
Expand All @@ -22,7 +23,8 @@ export function compileComponentForSSR(
options: TransformOptions,
mode: CompilationMode = 'asyncYield'
): CompilationResult {
const { code } = compileJS(src, filename, options, mode);
const tagName = generateCustomElementTagName(options.namespace, options.name);
const { code } = compileJS(src, filename, tagName, mode);
return { code, map: undefined };
}

Expand Down

0 comments on commit 9ba0901

Please sign in to comment.