A presentation at EmberFest 2025 in in Brussels, Belgium by Thomas Gossmann

The Frontend City Thomas Gossmann gos.si gos.si gossi
Brüssel
Brüssel 540 Unispin in front of the Basilique Nationale du Sacré-Cœur
Problem Statement We explain a technical initiative to non-tech folks We use technical language in our explanation We are not understood They approve the budget for our technical initiative They won’t - we weren’t understood Examples (Local) Refactoring Writing RFCs Change build system to Embroider/vite …
Example: Change Build System to Embroider/vite Arguments Questions It’s faster What will it cost us? It’s fancy How long will it take? Tree shaking Half a year to have the same situation as we have Smaller bundle size now? It’s modern How much features can be shipped instead? What’s the return of this investment?
Solution: Translate “ When we can explain it to my Mum, we are good. - My Manager
Task Responsibility Work on the Build System Keep the Frontend Operable
Question What would you need to do to keep the Frontend Operable?
Question 2 What would you need to do to keep a City Operable?
The Frontend City “ We built this city We built this city of rock and roll Built this city We built this city of rock and roll
Infrastructure City Layout Transportation Health & Security Sport & Entertainment Education City Construction
Infrastructure
Infrastructure City Software Electricity Dependency Management Water Build System Cable Release Management Garbage Collection Automated Maintenance Systems (dep upgrades) Traffic (lights) Framework infrastructure
City Layout Architecture
City Layout: Districts Hierarchical structures for managing complexity: Districts Districts to own all coherent parts of it
Architecture: System Landscape Subdomains System Components C4 Model
City Layout: Zoning American Suburbia Strict zoning (housing, commercial, industrial) Less/No mixed zoning Car dependent city Few transportation options By Avi Waxman on Unsplash By Michael Moloney on Unsplash
City Layout: Zoning Medieval Europe Zoning (housing, commercial, industrial) Mixed zoning Many transportation options By Svetlana Gumerova on Unsplash Shorter travels The “walkable city” By Matt Mutlu on Unsplash By Patrick Robert Doyle on Unsplash
Architecture: Code Organization Car dependent city = framework dependent product Your domain defines the organization of your code (never a framework) Use architectural patterns: Clean Architecture Vertical Slice Architecture Hexagonal Architecture … Prevent Tech Debt
Transportation APIs
Transportation Modalities Public Transport Private Transport Infrastructure Transport Hubs Railway network Streets Scope of traffic Local Traffic Global Traffic Public Transport Vehicles Private Transport Vehicles Bike Unicycle Tram Feet U-Bahn Car S-Bahn Bus ICE Stations Train Stations Bus Stops Stations Parking Space
Transportation: APIs Public API amongst subdomains Private API within a subdomain Use architecture lint to prevent illegal connections
Construction
Construction Houses in a City are the equivalent to Features of a Product City Software Construction workers evolve their tooling Linting - as railguards for programming languages Build houses… Formatting - to have the same syntactical look … faster (faster recognition) … safer Editor integration - For instant feedback on their … accessible fingertips … more sustainable Fixtures + Mocks Frontend Workshop
Health & Security
Health & Security: City Institutions Processes Firefighters Alarms for immediate actions Hospitals Pre-warn systems Emergency Medical Services Security checks for buildings Police Alpine Rescue Service Disaster Control …
Health & Security: Software Institutions Error Monitoring (Sentry) Processes Vulnerability management Entire app Monitoring or Subdomains Emergency changes Resource monitoring in grafana All sorts of tests from the testing pyramid Code coverage metrics Pre warn system through @embroider/try Rollback of changes Testing procedures Bug reporting Risk management
Sport & Entertainment DX
Sport & Entertainment Panem et circenses City Sport and entertainment are the most unnecessary Software DX minor matters in a city A city would function without Citizens greatly benefit from its presence Scaling Frontend: Superchar Ember Fest
Education
Education City Software Schools Onboarding Libraries Documentation Universities Diataxis: … Tutorials How-To Guides Explanation Reference Training Learning Time
The Frontend City “ Take me down to the Paradise City Where the grass is green and the girls are pretty Take me home (I want you, please, take me home) - Guns N’ Roses /gossi/the-frontend-city
Usage We do live longer in cities, than we live in code! Documentation Hub Decision Framework Structure your documentation based on the
Example: Change Build System to Embroider/vite Task vs. Responsibility Task Responsibility Work on the Build System Keep the Frontend Operable See the task through the lens of responsibility Connect to context Reshape weight of task vs. responsibility -> Priority?
Solution Mission (Translation) Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber
Reason: Time and Money Savings Optical fiber has lower latency, more resilient conncetions, higher bandwidth Economic Proof on Savings N umberOf Engineers ∗ ØRebuilds/Day ∗ RebuildT ime N umberOf Engineers ∗ ØBuilds/Day ∗ BuildT ime N umberOf Engineers ∗ Ext.SocialM ediaT ime/(Re)BuildDelay CIruns/Day ∗ BuildT ime Use numbers (Re)Build time numbers with classic and embroider/vite to run a comparsion Threshhold for N umberOf Engineers to savings to become significant If saving numbers are too low/insignificant, ROI/amortisation is not of interest
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Time and Money Savings
Assessment: Technical Feasibility classic Embroider (loose mode) Can we roll out fiber in the city? Can we roll out fiber in to each district? Can we roll out fiber to each street? Can we roll out fiber to each house? Embroider (strict mode) vite
Assessment: Technical Implementation classic Embroider (loose mode) Embroider (strict mode) Preparation: Strict Imports Prepare requirements: Strict Components/Helpers/Modifiers, use @embroider/template-tag-codemod Tasks can be parallelized Can be done by product teams Great to onboard them with the new technology Iterative learning opportunity vite
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Time and Money Savings Method Parallelize Involve product teams Incremental Delivery & Improvements
Challenge: Mindset Shift ember-cli shields engineers from build system internals vite provides much more exposure bundlers/transpilers are much more closer to engineers NPM is much closer ( imports and exports fields) CJS vs. ESM CJS ESM Align with JS Ecosystem Storybook 10 is ESM-only
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Method Time and Money Savings Parallelize JS Ecosystem Compatibility Involve product teams More OTS Solutions (JS Ecosystem compat) Incremental Delivery & Improvements
Ecosystem Integration Internationlization ember-intl Authentication ember-simple-auth Icons ember-svg-jar ember-phosphor-icons intl-schematic openauth-js @formatjs/intl auth.js i18next better-auth linguist passport.js globalize Polyglot.js paraglide-js The solution for all of us
Integration: paraglide-js services/intl.ts export class IntlService extends Service { @tracked locale = getLocale(); setLocale = (locale: Locale) => { setLocale(locale, { reload: false }); this.locale = locale; }; t<MessageFn extends (…args: any) => string>( messageFn: MessageFn, params: Parameters<MessageFn>[0], options: Options = {} ) { return messageFn(params, { locale: this.locale, …options }); } formatNumber(value: number, options?: Intl.NumberFormatOptions) { return new Intl.NumberFormat(this.locale, options).format(value); }
Integration: paraglide-js helpers.ts function T<MessageFn extends (…args: any) => string>( messageFn: MessageFn, options?: Options | Parameters<MessageFn>[0], params?: Parameters<MessageFn>[0] ) { return resource(({ owner }) => { const intl = owner.lookup(‘service:intl’); if (params) { return intl.t(messageFn, params, options); } return intl.t(messageFn, options); }); } export const t = resourceFactory(T);
Integration: paraglide-js Usage import { m } from ‘#paraglide/messages.js’; import { formatNumber, t } from ‘ember-paraglide’; <template> {{t m.greeting name=”World”}} {{t m.greeting}} </template> Paraglide compiles messages to functions (vite/unplugin plugin) The (t) accepts a function Tree-shaking unused messages away Tooling Ecosystem (UI for messages in vscode)
Integration: better-auth They have signals Beatiful implementation for Vue and Svelte Let’s start using signals for a similar Ember implementation … That is not a smooth experience yet Community Feedback needed: RFC #1071, RFC #1079, RFC #1122
Integration: better-auth class AuthService<User extends BetterAuthUser = BetterAuthUser> extends Service { client!: AuthClient; @tracked internalData?: Data<User> = undefined; get user(): User | undefined { return this.internalData?.user; } setup = (auth: AuthClient) => { this.client = auth; if (auth.useSession.value?.data) { this.internalData = auth.useSession.value.data as unknown as { user: User; session: Session }; } this.loading = !this.internalData; auth.useSession.subscribe((payload) => { if (!payload.isPending) { if (payload.data?.user && !this.authenticated) { this.#handleSessionAuthenticated(payload.data as unknown as Data<User>); } else if (!payload.data?.user && this.authenticated) { this.#handleSessionInvalidated(); } } this.loading = payload.isPending; this.internalData = (payload.data as unknown as { user: User; session: Session } | undefined) ?? undefined; });
Integration: better-auth class AuthService<User extends BetterAuthUser = BetterAuthUser> extends Service { requireAuthentication(transition: Transition) { return new Promise((resolve) => { if (this.authenticated) { resolve(this.authenticated); } const handleAuthentication = () => { if (!this.authenticated) { this.#attemptedTransition = transition; } resolve(this.authenticated); }; const waitUntilLoadingIsComplete = () => { if (this.loading) { return next(waitUntilLoadingIsComplete); } handleAuthentication(); }; if (this.loading) { next(waitUntilLoadingIsComplete); } else { handleAuthentication(); } });
Integration: better-auth very similar to ember-simple-auth user menu protected route export default class UserMenu extends Component { @service declare auth: AuthService; export default class ProtectedRoute extends Route { @service declare auth: AuthService; <template> async beforeModel(transition: Transition) { {{#if this.auth.authenticated}} {{#let (popover position=”bottom-start”) as |p|}} const authenticated = await this.auth .requireAuthentication(transition); <Button {{p.trigger}}> {{this.auth.user.name}} if (!authenticated) { </Button> this._router.transitionTo(‘login’); }
<Menu {{p.target}} as |m|> <m.Item @href=”/user/profile”>Profile</m.Item> <m.Item @href=”/user/sessions”>Sessions</m.Item> <hr /> <m.Item @href=”/user/logout”>Logout</m.Item> </Menu> {{/let}} {{else}} <Link @href=”/login”>Login</Link> {{/if}} } }Icons Use Iconify Configure vite.config.js import { FileSystemIconLoader } from ‘unplugin-icons/loaders’; import icons from ‘unplugin-icons/vite’; export default defineConfig({ plugins: [ // … icons({ autoInstall: true, compiler: ‘raw’, customCollections: { custom: FileSystemIconLoader(‘./assets/icons’) } }) ] }); Usage sports.gts import import import import { Icon } from ‘@hokulea/ember’; Unicycle from ‘~icons/custom/unicycle’; Activity from ‘~icons/ph/activity’; svgJar from ‘ember-svg-jar/helpers/svg-jar’; <template> Do some <Icon @icon={{Unicycle}} /> <Icon @icon={{Activity}} /> Fallback <Icon @icon={{svgJar “unicycle”}} /> <Icon @icon={{svgJar “activity”}} /> </template>
Integrations with Ember Github Organization: ember-integrations Candidates: vitest-browser-ember , ember-vitepress , … Integration code is tiny glue code
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Method Time and Money Savings Parallelize More OTS Solutions (JS Ecosystem compat) Involve product teams Integrate with JS ecosystem Incremental Delivery & Improvements Faster and cheaper development with more functionality
Breaking Changes? Ember Ember provides a migration path Can you migrate in time to receive future updates? Relevant: Security fixes Addons Supported Ember versions are at the will of its authors When will addon authors stop supporting Ember version x? EMBER INTEGRATION ADDONS Use tsdown over rollup ESM only type=”module” No @embroider/addon-dev
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Method Time and Money Savings Parallelize More OTS Solutions (JS Ecosystem compat) Involve product teams Faster and cheaper development with more Incremental Delivery & Improvements functionality Risk Framework Update Continuity (security fixes) OTS Adoption (limited to legacy adoptions = higher cost)
Architecture: Exploit the Degree of Freedom Strict imports (template imports): Organize code by your domain Strict resolver: RFC #1132 Don’t build a car-dependent city Ember has the best template system Router: Subject of the next Edition to also build the best Router
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Method Time and Money Savings Parallelize More OTS Solutions (JS Ecosystem compat) Involve product teams Faster and cheaper development with more functionality Increase Architectural Freedom Architectural Freedom: Build our City Risk Framework Update Continuity (security fixes) OTS Adoption (limited to adoptions = higher cost)
Solution Mission Technical: Change Build System to Embroider/vite City: Change connectivity from broadband (DSL) to optical fiber Reasons Method Time and Money Savings Parallelize More OTS Solutions (JS Ecosystem compat) Involve product teams Faster and cheaper development with more functionality Architectural Freedom: Build our City Risk Framework Update Continuity (security fixes) OTS Adoption (limited to adoptions = higher cost)
Thank You There was something about a Translation and a City There was something with vite There was something with modern Ember on vite
Leading a team of engineers and maintaining a live-product means staying highly alert, high-pressure situations, making plenty decisions and while also guiding and supporting peers. There is rewarding tasks but also annoying ones. Let’s turn some of the annoying into rewarding ones.
The journey no developer likes or wants: Pitching a technical initiative to upper management. It’s a journey of being misunderstood, seeing technical improvements and benefits for your team being denied, while drowning in politics.
Maintaining your application, ensuring all aspects of operating a production app are addressed and handled can be overwhelming.
This talk aims to bring piece to these aspects. Following the idea to treat your app as a city and address the needs to keep it operable, maintainable and evolving. Being able to quickly communicate your ideas to upper management as well as fellow engineers speeds up your own workflow providing a framework for making good, better and quicker decisions.
The following resources were mentioned during the presentation or are useful additional information.