Skip to content

Commit

Permalink
chore: setup lit cohabitation (#4834)
Browse files Browse the repository at this point in the history
First PR to scaffold the Lit migration, essentially #4787 without the
atomic-text migration.

Mainly:

## Web Dev Server
Replace the Stencil web dev server with a framework-agnostic one.
For now, it comes with degraded performance (it will need to be
replaced/adapted when the transition is over)

This comes with some hack removal in the Cypress tests.

## Autoloader
Setup an auto-loader for future Lit Elements, heavily inspired by
Shoelace

## Stencil Proxy
Proxy some files generated by Stencil to 'plug-in' what's relative to
Lit.

# Bikeshedding

- CSS: ought to setup CSS building for Lit
- Decorators & utils setup still
  • Loading branch information
louis-bompart authored Jan 13, 2025
1 parent db77dd0 commit ed66ba0
Show file tree
Hide file tree
Showing 89 changed files with 3,166 additions and 5,570 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ module.exports = {
'scripts/deploy/execute-deployment-pipeline.mjs',
'build',
'.deployment.config.json',
'packages/atomic-angular/scripts/build-lit.mjs',
'packages/atomic-react/scripts/build-lit.mjs',
],
env: {
jest: true,
Expand Down
4 changes: 2 additions & 2 deletions .github/actions/e2e-atomic-insight-panel/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ runs:
browser: chrome
command-prefix: 'npx -w @coveo/atomic cypress-repeat run -n 3 --until-passes --rerun-failed-only --'
config-file: cypress-insight-panel.config.mjs
start: npm start -w @coveo/atomic
wait-on: 'http://localhost:3333/ping'
start: npx nx web:dev atomic
wait-on: 'http://localhost:3333'
wait-on-timeout: 600000
install: false
record: false
4 changes: 2 additions & 2 deletions .github/actions/e2e-atomic-screenshots/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ runs:
browser: chrome
command-prefix: 'npx -w @coveo/atomic cypress-repeat run -n 3 --until-passes --rerun-failed-only --'
config-file: cypress-screenshots.config.mjs
start: npm start -w @coveo/atomic
wait-on: 'http://localhost:3333/ping'
start: npx nx web:dev atomic
wait-on: 'http://localhost:3333'
wait-on-timeout: 600000
install: false
record: false
Expand Down
4 changes: 2 additions & 2 deletions .github/actions/e2e-atomic/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ runs:
browser: chrome
command-prefix: 'npx -w @coveo/atomic cypress-repeat run -n 3 --until-passes --rerun-failed-only --'
spec: ${{ inputs.spec }}
start: npm start -w @coveo/atomic
wait-on: 'http://localhost:3333/ping'
start: npx nx web:dev atomic
wait-on: 'http://localhost:3333'
wait-on-timeout: 600000
install: false
record: false
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ packages/samples/headless-commerce-ssr-remix/build/**/*
packages/samples/angular/src/lang/*.json
packages/samples/vuejs/public/lang/*.json
.deployment.config.json
packages/atomic-angular/scripts/build-lit.mjs
packages/atomic-react/scripts/build-lit.mjs
7,451 changes: 2,239 additions & 5,212 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion packages/atomic-angular/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
"dependsOn": ["^build"],
"executor": "nx:run-commands",
"options": {
"commands": ["npm run build:bundles", "npm run build:assets"],
"commands": [
"node ./scripts/build-lit.mjs",
"npm run build:bundles",
"npm run build:assets"
],
"parallel": false,
"cwd": "packages/atomic-angular"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3106,3 +3106,5 @@ export class AtomicTimeframeFacet {
export declare interface AtomicTimeframeFacet extends Components.AtomicTimeframeFacet {}



import type {} from '@coveo/atomic/components';
59 changes: 59 additions & 0 deletions packages/atomic-angular/scripts/build-lit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import cem from '@coveo/atomic/custom-elements-manifest' with {type: 'json'};
import { createWriteStream, readFileSync, writeFileSync } from 'fs';

const isLitDeclaration = (declaration) => declaration?.superclass?.name === 'LitElement';

const atomicAngularModuleFilePath ='projects/atomic-angular/src/lib/stencil-generated/atomic-angular.module.ts';
const atomicAngularModuleFileContent = readFileSync(atomicAngularModuleFilePath, 'utf-8');
const atomicAngularComponentFileStream = createWriteStream('projects/atomic-angular/src/lib/stencil-generated/components.ts', {flags: 'a'});
const litDeclarations = [];


const declarationToProxyCmp = (declaration) =>
`
@ProxyCmp({
inputs: [${declaration.attributes.map(attr => `'${attr.name}'`).join(', ')}]
})
@Component({
selector: '${declaration.tagName}',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [${declaration.attributes.map(attr => `'${attr.name}'`).join(', ')}]
})
export class ${declaration.name} {
protected readonly el: HTMLElement;
constructor(c: ChangeDetectorRef, el: ElementRef, protected z: NgZone) {
c.detach();
this.el = el.nativeElement;
}
}
export declare interface ${declaration.name} extends Lit${declaration.name} {}
`

const declarationToLitImport = (declaration) => `${declaration.name} as Lit${declaration.name}`;

const litImports = []

for (const module of cem.modules) {
for (const declaration of module.declarations) {
if (isLitDeclaration(declaration)) {
atomicAngularComponentFileStream.write(declarationToProxyCmp(declaration));
litImports.push(declarationToLitImport(declaration));
litDeclarations.push(`${declaration.name}`);
}
}
}
atomicAngularComponentFileStream.write(`\nimport type {${litImports.join(',')}} from '@coveo/atomic/components';`);
atomicAngularComponentFileStream.end();


if(litDeclarations.length > 0) {
writeFileSync(
atomicAngularModuleFilePath,
atomicAngularModuleFileContent
.replace(/const DECLARATIONS = \[\n/m, `const DECLARATIONS = [\n${litDeclarations.join(',\n')},\n`)
.replace(/^import \{$/m, `import {\n${litDeclarations.join(',\n')},`)
);
}
4 changes: 3 additions & 1 deletion packages/atomic-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"commerce/"
],
"dependencies": {
"@coveo/atomic": "3.13.0"
"@coveo/atomic": "3.13.0",
"@lit/react": "1.0.6",
"lit": "3.2.1"
},
"devDependencies": {
"@coveo/release": "1.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/atomic-react/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"node ./scripts/build-lit.mjs",
"npm run build:fixLoaderImportPaths",
"npm run build:fixGeneratedImportPaths",
"npm run build:bundles",
Expand Down
5 changes: 5 additions & 0 deletions packages/atomic-react/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ const commonExternal = [
'react-dom',
'react-dom/client',
'react-dom/server',
'lit',
'lit/decorators.js',
'@lit/react',
'@coveo/atomic',
'@coveo/atomic/loader',
'@coveo/atomic/components',
'@coveo/headless',
'@coveo/headless/recommendation',
'@coveo/headless/commerce',
Expand Down
90 changes: 90 additions & 0 deletions packages/atomic-react/scripts/build-lit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import cem from '@coveo/atomic/custom-elements-manifest' with {type: 'json'};
import {writeFileSync} from 'node:fs';
import * as prettier from 'prettier';

const isLitDeclaration = (declaration) =>
declaration?.superclass?.name === 'LitElement';

const entries = [
{
path: 'src/components/search/components.ts',
content: '',
excludedComponents: [
'atomic-result-template',
'atomic-recs-result-template',
'atomic-field-condition',
],
excludedComponentDirectories: ['src/components/commerce'],
computedComponentImports: [],
},
{
path: 'src/components/commerce/components.ts',
content: '',
excludedComponents: [
'atomic-product-template',
'atomic-recs-result-template',
'atomic-field-condition',
],
excludedComponentDirectories: [
'src/components/search',
'src/components/recommendations',
],
computedComponentImports: [],
},
];

const declarationToLitImport = (declaration) =>
`${declaration.name} as Lit${declaration.name}`;

const declarationToComponent = (declaration) =>
`
export const ${declaration.name} = createComponent({
tagName: '${declaration.tagName}',
react: React,
elementClass: Lit${declaration.name},
});
`;

for (const module of cem.modules) {
for (const declaration of module.declarations) {
if (isLitDeclaration(declaration)) {
for (const entry of entries) {
if (
entry.excludedComponentDirectories.some((directory) =>
module.path.startsWith(directory)
) ||
entry.excludedComponents.includes(declaration.tagName)
) {
continue;
}
entry.computedComponentImports.push(
declarationToLitImport(declaration)
);
entry.content+=(declarationToComponent(declaration));
}
}
}
}

for (const entry of entries) {
const prettierConfig = {
...(await prettier.resolveConfig(entry.path)),
parser: 'typescript'
};
if(entry.computedComponentImports.length===0) {
writeFileSync(entry.path, await prettier.format('export {}', prettierConfig));
continue;
}
writeFileSync(
entry.path,
await prettier.format(
[
`import {createComponent} from '@lit/react';`,
`import React from 'react';`,
`import {${entry.computedComponentImports.join(',')}} from '@coveo/atomic/components';`,
entry.content
].join('\n'),
prettierConfig
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
1 change: 1 addition & 0 deletions packages/atomic-react/src/components/commerce/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components.js';
export * from '../stencil-generated/commerce/index.js';
export {CommerceBindings, i18n} from '@coveo/atomic';

Expand Down
2 changes: 2 additions & 0 deletions packages/atomic-react/src/components/recommendation/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from '../stencil-generated/search/index.js';
export * from '../search/components.js';

export {RecsBindings, i18n} from '@coveo/atomic';

// Important: Re-exporting under the same name (eg: "AtomicRecsInterface") shadows the original component
Expand Down
1 change: 1 addition & 0 deletions packages/atomic-react/src/components/search/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
1 change: 1 addition & 0 deletions packages/atomic-react/src/components/search/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from '../stencil-generated/search/index.js';
export * from './components.js';
export {Bindings, i18n} from '@coveo/atomic';

// Important: Re-exporting under the same name (eg: "AtomicSearchInterface") shadows the original component
Expand Down
3 changes: 2 additions & 1 deletion packages/atomic/custom-elements-manifest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {cemPlugin} from './scripts/cem-plugin.mjs';

export default {
/** Globs to analyze */
globs: ['src/**/*.tsx'],
globs: ['src/**/*.tsx', 'src/**/*.ts'],
/** Globs to exclude */
exclude: ['**/*.stories.tsx', '**/*.stories.ts', '**/*.stories.js'],
stencil: true,
litelement: true,
plugins: [cemPlugin()],
};
21 changes: 9 additions & 12 deletions packages/atomic/cypress/e2e/breadbox-assertions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {TestFixture} from '../fixtures/test-fixture';
import {deselectBreadcrumbAtIndex} from './breadbox-actions';
import {BreadboxSelectors} from './breadbox-selectors';
import {should} from './common-assertions';
Expand Down Expand Up @@ -70,17 +69,15 @@ export function assertRemoveBreadcrumbShowMoreInDOM() {

export function assertDisplayBreadcrumbClearIcon() {
it('should display a "Clear" icon next to each facetValue', () => {
cy.wait(TestFixture.interceptAliases.Build).then(() =>
BreadboxSelectors.breadcrumbClearFacetValueButton()
.its('length')
.then((count) => {
for (let i = 0; i < count; i++) {
BreadboxSelectors.breadcrumbClearFacetValueButtonAtIndex(i).should(
'be.visible'
);
}
})
);
BreadboxSelectors.breadcrumbClearFacetValueButton()
.its('length')
.then((count) => {
for (let i = 0; i < count; i++) {
BreadboxSelectors.breadcrumbClearFacetValueButtonAtIndex(i).should(
'be.visible'
);
}
});
});
}

Expand Down
6 changes: 1 addition & 5 deletions packages/atomic/cypress/e2e/breadbox-selectors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {TestFixture} from '../fixtures/test-fixture';

export const breadboxComponent = 'atomic-breadbox';
export const BreadboxSelectors = {
shadow: () => cy.get(breadboxComponent).shadow(),
Expand All @@ -14,9 +12,7 @@ export const BreadboxSelectors = {
breadcrumbValueAtIndex: (index: number) =>
BreadboxSelectors.breadcrumbButtonValue().eq(index),
breadcrumbClearFacetValueButton: () =>
cy
.wait(TestFixture.interceptAliases.Build)
.then(() => BreadboxSelectors.breadcrumbButton().find('atomic-icon')),
BreadboxSelectors.breadcrumbButton().find('atomic-icon'),
breadcrumbClearFacetValueButtonAtIndex: (index: number) =>
BreadboxSelectors.breadcrumbClearFacetValueButton().eq(index),
clearAllButton: () => BreadboxSelectors.shadow().find('[part="clear"]'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {TestFixture} from '../../../fixtures/test-fixture';

export const ratingFacetComponent = 'atomic-rating-facet';
export const RatingFacetSelectors = {
withId(id: string) {
Expand Down Expand Up @@ -78,8 +76,6 @@ export const RatingFacetSelectors = {
return this.valueRating().eq(index);
},
starsIconAtIndex(index: number) {
return cy
.wait(TestFixture.interceptAliases.Build)
.then(() => this.facetValueAtIndex(index).find('atomic-icon'));
return this.facetValueAtIndex(index).find('atomic-icon');
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {TestFixture} from '../../../fixtures/test-fixture';

export const ratingRangeFacetComponent = 'atomic-rating-range-facet';
export const RatingRangeFacetSelectors = {
withId(id: string) {
Expand Down Expand Up @@ -63,8 +61,6 @@ export const RatingRangeFacetSelectors = {
return this.valueLabel().eq(index);
},
starsIconAtIndex(index: number) {
return cy
.wait(TestFixture.interceptAliases.Build)
.then(() => this.facetValueAtIndex(index).find('atomic-icon'));
return this.facetValueAtIndex(index).find('atomic-icon');
},
};
2 changes: 1 addition & 1 deletion packages/atomic/cypress/e2e/icon-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function getSvg(fileName: string) {
const file = cy.readFile(`./www/build/assets/${fileName}.svg`);
const file = cy.readFile(`./dist/atomic/assets/${fileName}.svg`);
return file;
}
2 changes: 1 addition & 1 deletion packages/atomic/cypress/e2e/icon.cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('Icon Test Suites', () => {

describe('with an asset path', () => {
const asset = 'assets://some-icon-that-does-not-exist';
const url = asset.replace('assets://', '/build/assets/') + '.svg';
const url = asset.replace('assets://', '/assets/') + '.svg';

describe('with an asset path that returns a 404', () => {
beforeEach(() => {
Expand Down
6 changes: 1 addition & 5 deletions packages/atomic/cypress/e2e/query-error-selectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {TestFixture} from '../fixtures/test-fixture';
import {AriaLiveSelectors} from './aria-live-selectors';

export const queryErrorComponent = 'atomic-query-error';
Expand All @@ -8,10 +7,7 @@ export const QueryErrorSelectors = {
QueryErrorSelectors.shadow().find('[part="more-info-btn"]'),
moreInfoMessage: () =>
QueryErrorSelectors.shadow().find('[part="error-info"]'),
icon: () =>
cy
.wait(TestFixture.interceptAliases.Build)
.then(() => QueryErrorSelectors.shadow().find('atomic-icon')),
icon: () => QueryErrorSelectors.shadow().find('atomic-icon'),
errorTitle: () => QueryErrorSelectors.shadow().find('[part="title"]'),
errorDescription: () =>
QueryErrorSelectors.shadow().find('[part="description"]'),
Expand Down
Loading

0 comments on commit ed66ba0

Please sign in to comment.