Eduardo San Martin Morote Frontend Nerd @posva 🔗 esm.dev
A presentation at DotJS in December 2019 in Paris, France by Eduardo San Martin Morote
Eduardo San Martin Morote Frontend Nerd @posva 🔗 esm.dev
Modern Routing
Modern Routing
Modern Routing
Modern Frontend Routing
https://movies.com/search?genre=horror&page=1#filters
https://movies.com/search?genre=horror&page=1#filters History
https://movies.com/search?genre=horror&page=1#filters History Router
https://movies.com/search?genre=horror&page=1#filters History Matcher Router
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components
History API history.pushState(data, title, url) history.replaceState(data, title, url) window.addEventListener(‘popstate’, handler) history.state history.length posva
https://movies.com history.state history.length null 1 posva
https://movies.com history.state history.length null 1 posva
https://movies.com history.state history.length null 1 history.pushState({ from: null }, ”, ‘/releases’) posva
https://movies.com /releases history.state history.length { from: null } 2 history.pushState({ from: null }, ”, ‘/releases’) posva
https://movies.com /releases history.state history.length { from: null } 2 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /releases history.state history.length { from: null } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /releases history.state history.length { from: null } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com /search?genre=horror&page=1#filters history.state history.length { from: ‘/releases’ } 3 history.pushState({ from: null }, ”, ‘/releases’) history.pushState({ from: ‘/releases’ }, ”, ‘/search?genre=horror&page=1#filters’) posva
https://movies.com/search?genre=horror&page=1#filters posva
https://movies.com/search?genre=horror&page=1#filters Path Query (or search) Hash (or fragment) posva
Path /search Query ?genre=horror&page=1 Hash #filters posva
Path /search Query ?genre=horror&page=1 Hash #filters location posva
Path /search Query ?genre=horror&page=1 Hash #filters location.pathname posva
Path /search Query ?genre=horror&page=1 Hash #filters location.search posva
Path /search Query ?genre=horror&page=1 Hash #filters location.hash posva
Path /search Query ?genre=horror&page=1 Hash #filters location.href posva
/search?genre=horror&page=1#filters posva
/search?genre=horror&page=1#filters { } path: ‘/search’, query: { genre: ‘horror’, page: ‘1’ }, hash: ‘#filters’ posva
?genre=horror&page=1 posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
URLSearchParams API const q = new URLSearchParams(‘genre=horror&page=1’) for (const key of q.keys()) { q.getAll(key) } posva
posva
Encoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1 posva
Encoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1
<p class=”a”> posvaEncoding Character Hex Encoded ” U+0022 %22 < U+003C %3C ñ U+00F1 %C3%B1
<p class=”a”> %3Cp%20class=%22a%22%3E posvaEncoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) Path Query Hash posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%) Path Query Hash ␣ ” < > ` posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > Query Hash ␣ " < >
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > # ? { } Query Hash ␣ " < >
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path ␣ ” < > # ? { } Query ␣ " < >
Hash ␣ ” < > `
posva
Encoding Non-printable characters: U+0000 (NULL) to U+001F (US) and U+007F (DEL) Any non-ascii character: > U+007E (~) The percentage character: U+0025 (%)
Path
Query
Hash
␣ ” < > # ? { } ␣ " < > # & = ␣ " < >
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>`’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>` location.hash: ‘#%20%22%3C%3E%60’
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>` location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash = encodeURI('# "<>
’)
location.hash: ‘# “<>' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash = encodeURI('# "<>
’)
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' location.hash = encodeURI('# "<>
’)
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..># “<>location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20"<>%60 location.hash: '# "<>
’
https://<…..># “<>`
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60
location.hash: ‘# “<>' https://<…..># "<>
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
✅
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>https://<…..># "<>
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' 🤔 location.hash: ‘# "<>
’
💯
location.hash: ‘#%20%22%3C%3E%60’
✅
location.hash: ‘#%20%22%3C%3E%60’
🤪
location.hash: ‘# “<>' https://<…..>#%20"<>
https://<…..># “<>https://<…..>#%20"<>%60 https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
location.hash = ‘# “<>' location.hash: ‘# "<>
’
https://<…..>#%20”<>location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘# “<>' https://<…..># "<>
location.hash = encodeURI(‘# “<>') location.hash: ‘#%20%22%3C%3E%60' https://<…..>#%20%22%3C%3E%60 location.hash: ‘#%20%22%3C%3E%60' https://<…..># "<>
location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20”<>%60 location.hash: ‘#%20%22%3C%3E%60’
https://<…..>#%20%22%3C%3E%60
posva
Encode you URL even if it looks bad encodeURI / decodeURI encodeURIComponent / decodeURIComponent Path and hash Query keys and values posva
History { } path: ‘/movies/493922’, query: {}, hash: ” Communicate with the address bar Handle encoding Handle browser specific bugs/inconsistencies posva
{ { } path: ‘/movies/493922’, query: {}, hash: ” } path: ‘/movies/493922’, params: { id: ‘493922’}, name: ‘MovieDetail’, component: …, query: {}, hash: ” posva
Matcher { { } path: ‘/movies/493922’, query: {}, hash: ” } path: ‘/movies/493922’, params: { id: ‘493922’}, name: ‘MovieDetail’, component: …, query: {}, hash: ” posva
Matcher ‘/home’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/:id’ /^/movies/(\n+)$/i ‘/movies/:id(\n+)’ /^/movies/new$/i ‘/movies/new’ path-to-regexp posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i ‘/movies/new’ /^/movies/([^/]+)$/i /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i ‘/movies/new’ /^/movies/([^/]+)$/i /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher ‘/home’ ‘/movies/:id’ ‘/movies/:id(\n+)’ ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher 70 ‘/home’ 130 ‘/movies/:id’ 135 ‘/movies/:id(\n+)’ 140 ‘/movies/new’ /^/home$/i /^/movies/([^/]+)$/i ‘/movies/new’ /^/movies/(\n+)$/i /^/movies/new$/i posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ ‘/movies/new’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ ‘/movies/new’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ 70 posva
Matcher 140 ‘/movies/new’ 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 ‘/home’ 70 + 70 70 posva
Matcher 140 ‘/movies/new’ 70 + 70 135 ‘/movies/:id(\n+)’ 130 ‘/movies/:id’ 70 + 60 70 ‘/home’ 70 posva
Matcher 140 ‘/movies/new’ 70 + 70 135 ‘/movies/:id(\n+)’ 70 + 60 130 ‘/movies/:id’ 70 + 60 70 ‘/home’ 70 + 5 posva
140 70 + 70 135 70 + 60 130 70 + 60 70 70 ‘/movies/new’ + 5 posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” View Component posva
Router { } path: ‘/movies/new’, params: {}, name: ‘MovieNew’, component: …, query: {}, hash: ” View Component posva
https://movies.com/search?genre=horror&page=1#filters History Matcher Router Components posva
Thank you! posva