Jan Widmer
frontend engineer. fotographer. web creator.
A journey in Frontend Developlment
https://makeameme.org/meme/disclaimer-there-will-d173dcf663
https://www.reddit.com/r/ProgrammerHumor/comments/gt0tkj/frontend_vs_backend_developer/
... well, that is not done yet
Backend be like...
... well, that is not merged yet
... well, that is not deployed yet
... well, the dev server does not run yet
https://arstechnica.com/information-technology/2012/04/internet-explorer-market-share-surges-as-version-9-wins-hearts-and-minds/
Accelerator Demo Store
Two Ways of working
FE built as static
templates
https://makeameme.org/meme/i-dont-always-olwyx5
FE built working
directly with Hybris
https://www.reddit.com/r/ProgrammerHumor/comments/tgogft/sometimes_progress_looks_like_failure/
POV: FE when just wanting to
do Frontend things..
https://www.reddit.com/r/webdev/comments/c9zqir/frontend_evolution_19952019/#lightbox
https://gs.statcounter.com/browser-market-share/desktop/worldwide/2019
https://www.facebook.com/whatwaitwhat/
https://www.imdb.com/title/tt1442449/
https://www.linkedin.com/posts/mostafil_sap-commerce-spartacus-activity-6982974989041844224-oF1X/
AKA Composable Storefront
✓ no more JSP
✓ no more Hybris
✓ no more waiting
https://composable-storefront-demo.eastus.cloudapp.azure.com:543/electronics-spa/en/USD/
API Endpoint can be whereever
"Mock Server"
Text
Text
Mock Server
// languages handler
server.get('/occ/v2/my-store/cms/languages', (_req, res) => {
res.status(200).json({
languages: [
createLanguage(),
createLanguage({
isocode: 'de',
name: 'German',
nativeName: 'Deutsch',
}),
createLanguage({
isocode: 'it',
name: 'Italian',
nativeName: 'Italiano',
})
],
});
});const app: Express = express();
const serverHttp = http.createServer(app);
serverHttp.listen(portHttp,
(): void => console.log(`listening on http://localhost:${portHttp}`)
);// pages handler
server.get('/occ/v2/my-store/cms/pages', (req, res) => {
const url = getUrl(req);
const pageLabelOrId = url.searchParams?.get('pageLabelOrId');
const pageType = url.searchParams?.get('pageType');
if (pageType === 'ContentPage' && pageLabelOrId) {
// logic to return a content page
res.status(200).json(contentPages()[pageLabelOrId]);
} else if (pageType === 'ProductPage') {
// logic to return product detail page
const productCode = url.searchParams?.get('code');
res.status(200).json(productDetailPage(productCode || ''));
} else if (!pageType && !pageLabelOrId) {
// logic to return the homepage
res.status(200).json(homePage());
} else {
res.status(200).json(tempPage(pageType || 'ContentPage', pageLabelOrId || ''));
}
});https://www.globalnerdy.com/2022/08/10/the-most-important-tech-skill-isnt-googling-for-answers-but-this/
https://mswjs.io/
// mock.server/browser.ts
import { setupWorker } from 'msw';
import { handlers } from './handlers';
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers);
// main.ts
function prepare() {
if (environment.mockServer) {
const { worker } = require('./mock-server/browser');
return worker.start();
}
return Promise.resolve();
}
// call angular bootstrap function after mock server is ready
prepare().then(() => bootstrap());export const handlers = [
rest.get('/occ/v2/my-store/cms/languages',
(_req: RestRequest, res: ResponseComposition, ctx: RestContext) => {
return res(ctx.status(200), ctx.json(languages()));
}),
// cms pages call
rest.get('/occ/v2/my-store/cms/pages', (req: RestRequest, res: ResponseComposition, ctx: RestContext) => {
const pageLabelOrId = req.url.searchParams?.get('pageLabelOrId');
const pageType = req.url.searchParams?.get('pageType');
if (pageType === 'ContentPage' && pageLabelOrId) {
return res(ctx.status(200), ctx.json(contentPages()[pageLabelOrId]));
} else if (pageType === 'ProductPage') {
// its a product detail page
//const productCode = url.searchParams?.get('code');
const productCode = '12345';
return res(ctx.status(200), ctx.json(productDetailPage(productCode || '')));
} else if (!pageType && !pageLabelOrId) {
// its the homepage
return res(ctx.status(200), ctx.json(homePage()));
} else {
return res(ctx.status(200), ctx.json(tempPage(pageType || 'ContentPage', pageLabelOrId || '')));
}
}),
]// passthrough.ts
export const passThroughUrls: PassThroughUrl[] = [
{ url: '/assets/*', requestFunction: 'get' },
{ url: '*.woff2', requestFunction: 'get' },
{ url: '*.woff', requestFunction: 'get' },
{ url: '*.js', requestFunction: 'get' },
{ url: '*.css', requestFunction: 'get' },
{ url: '*.webp', requestFunction: 'get' },
{ url: '/site.webmanifest', requestFunction: 'get' },
{ url: 'https://www.googletagmanager.com/*', requestFunction: 'get' },
{ url: 'https://maps.gstatic.com/*', requestFunction: 'get' },
{ url: 'https://maps.googleapis.com/*', requestFunction: 'get' },
{ url: 'https://fonts.googleapis.com/*', requestFunction: 'get' },
];
// handlers.ts
export const handlers = [
...passThroughUrls.map((passThroughUrl) => {
return rest[passThroughUrl.requestFunction](passThroughUrl.url, (req) => {
return req.passthrough();
});
}),
]export const readUrlParams = (params: PathParams<string>, paramName: string): string => {
return (params[paramName] as string) || '';
};
export const readSearchParams = (request: Request, param: string): string | undefined => {
// Construct a URL instance out of the intercepted request.
const url = new URL(request.url);
// Read the "param" URL query parameter using the "URLSearchParams" API.
return url.searchParams.get(param) || undefined;
};
export function redirect(destination: string, statusCode: number) {
return new HttpResponse(null, {
status: statusCode,
headers: {
Location: destination,
},
});
}
// usage
http.get('/occ/v2/media/:mediaId/16x9_1680/:mediaName', ({ request }) => {
const urlArray = request.url.split('/');
// equals to 16x9_1680, rendition is contained in second last path element
const renditionName = urlArray[urlArray.length - 2];
const ratio = renditionName.split('_')[0]; // equals to 16x9
const width = parseInt(renditionName.split('_')[1], 10); // equals to 1200
const ratioArray = ratio.split('x');
const ratioNumber = parseInt(ratioArray[1], 10) / parseInt(ratioArray[0], 10);
// Dynamic mock-images (Picsum)
return redirect(`https://picsum.photos/${width}/${Math.ceil(width * ratioNumber)}.webp`, 301);
}),no downsides from previous solution
https://github.com/valantic/spartacus-mock
import { MockConfig } from '@valantic/spartacus-mock';
import { handlers } from './handlers';
import { translationResources } from './mock-data/translations/translations';
import { passThroughUrls } from './passThrough';
import { environment } from '@environments/environment';
const mockConfig: MockConfig = {
enableDefaultData: false,
inclusionMode: false,
enableWorker: environment.mockServer || false,
environment,
passThroughRequests: passThroughUrls,
handlers: handlers,
translations: translationResources,
};
export async function prepareMockServer(): Promise<ServiceWorkerRegistration | undefined> {
const { prepareMock } = await import('@valantic/spartacus-mock');
return prepareMock(mockConfig);
}export const environment: Environment = {
env: 'dev-fe',
production: false,
cookieSecret: 'some-supersecret',
smartEdit: '*.my-store.ch',
mockServer: true,
backend: {
occ: {
// baseUrl: 'https://api.my-store.ch',
baseUrl: 'https://api-staging.my-store.ch',
// baseUrl: 'https://api-dev.my-store.ch',
prefix: '/occ/v2/',
},
},
};
(still) need to talk with BE ;-)
const headerSlots = getHeaderSlots();
const footerSlots = getFooterSlots();
const pages = contentPages(headerSlots, footerSlots);By Jan Widmer