1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebComponents でルーターを書いてみる。

Posted at

History API があるんだから、WebComponents ベースで SPA 作ってるときはルーターは自前で書いちゃえばいいんじゃない?

ということで、約100行のシンプルなルーターを書いてみました。

  • Lazy Loading
  • Inner HTML

にも対応しています

index.html
<li route=/>Home</li>
<li route=/img/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2Fa%2Fac%2FJordens_inre.jpg%2F230px-Jordens_inre.jpg>Sample image</li>

<jp-route
    path=/
    title=Home
    data="<b>HOME</b>"
    spec=html
></jp-route>

<jp-route
    path=/img/:src
    title=Image
    data="img"
    spec=tag
></jp-route>

<script type=module src=./jp-router.js></script>
<jp-router></jp-router>

こんな html を書いておいて

  • Home をクリックしたら

スクリーンショット 2020-05-09 7.28.10.png

  • Sample image をクリックしたら

スクリーンショット 2020-05-09 7.28.16.png

となるようにしたい。またブラウザのバック/フォワードボタンが効くようにしたい。

実装

jp-router.js
const
PathSegs = uri => uri.split( '/' ).filter( $ => $.length )

const
Key = $ => {
	const _ = /^:(.+)/.exec( $ )
	return _ ? _[ 1 ] : _
}

const
Match = ( route, data ) => {
	const rSegs = PathSegs( route )
	const dSegs = PathSegs( data )

	if ( rSegs.length != dSegs.length ) return null

	const _ = rSegs.flatMap( ( $, _ ) => $ == dSegs[ _ ] ? [] : [ [ Key( $ ), decodeURIComponent( dSegs[ _ ] ) ] ] )
	return _.every( $ => $[ 0 ] ) ? Object.fromEntries( _ ) : null
}

class
Router extends HTMLElement {

	connectedCallback() {
		this.UpdateLinks()
		window.onpopstate = ev => this.Navigate( location.pathname )
		window.onpopstate()
	}

	disconnectedCallback() {
		window.onpopstate = null
	}

	UpdateLinks() {
		document.querySelectorAll( '[route]' ).forEach(
			$ => $.onclick = ev => {
				const route = $.getAttribute( 'route' )
				this.Navigate( route )
				history.pushState( null, null, route )
			}
		)
	}

	Navigate( url ) {

		const routes = Array.from( document.querySelectorAll( 'jp-route' ) ).map(
			$ => (
				{	path	: $.getAttribute( 'path'	)
				,	title	: $.getAttribute( 'title'	)
				,	data	: $.getAttribute( 'data'	)
				,	spec	: $.getAttribute( 'spec'	)
				}
			)
		)
		routes.forEach( $ => $.params = Match( $.path, url.split( '?' )[ 0 ] ) )
		const _ = routes.filter( $ => $.params )

		switch ( _.length ) {
		case 1:
			{	const { path, title, data, spec, params } = _[ 0 ]

				while ( this.firstChild ) this.removeChild( this.firstChild )

				document.title = title || url

				const
				AttachView = view => {
					for ( let key in params ) view.setAttribute( key, params[ key ] )
					this.appendChild( view )
					this.UpdateLinks()
				}

				switch ( spec ) {
				case 'html':
					this.innerHTML = data
					AttachView( this.firstChild )
					break
				case 'tag':
					AttachView( document.createElement( data ) )
					break
				case 'source':
					import( data ).then( $ => AttachView( new $.default() ) )
					break
				}
			}
			break
		case 0:
			document.title = '404'
			this.innerHTML = '404 Page not found. URL: ' + url
			break
		default:
			document.title = 'MULTIPLE ROUTE'
			this.innerHTML = 'Internal logic error: MULTIPLE ROUTE, see console'
			console.error( 'MULTIPLE ROUTE', JSON.stringify( _ ) )
			break
		}
	}
}

customElements.define( 'jp-router', Router )

解説

windowonpopstateをトラップします。

Document中のrouteという属性を持ってるHTML要素のonclickを以下のように設定します。

  • route属性とマッチするpath属性を持つjp-routeを探し、そのjp-routeの内容を作成して、自分の子エレメントにします。
  • History APIpushStateを使って、ブラウザの履歴に登録します。(pushStateの2番目のパラメータは履歴に現れるtitleですが、nullにしておくと、その時のtitleを設定してくれます。)

マッチとパラメータ

route属性 path属性 マッチ パラメータ
/a/b /a/b/ true {}
/a/b /a/ false -
/img/:src /img/sample true {src:'sample'}

パラメータは作成されたHTML要素の属性としてセットします。

NPM

これで十分って方のために、NPM に登録しておきました。
https://www.npmjs.com/package/@satachito/jp-router

$ npm i @satachito/jp-router --save

node_modules/@satachito/jp-router/demo/index.htmlLazy Loadingなど他のパターンも入れてありますので、よかったら参考にしてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?