流行りのAIアートをブラインドテストできるサイトをriot.jsで作った


## 制作したサイト

最近AIで描かれた絵のクオリティがスゴイと話題になっています。
AIの絵には意図がないため魅力がないという意見がある一方で、[画像生成AI「Midjourney」の描いた絵が美術品評会で1位](https://gigazine.net/news/20220901-midjourney-win-fine-arts-competition/)を取ったりしています。
実際のところ見分けはつかないのではないだろうかという気持ちがあり、ブラインドテストができるサイトを作ってみました。
### [Blind AI（ブラインドAI）](https://service.webgoto.net/blind-ai/)
![Blind AI](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140146/e4730867-6085-6d04-279e-5d86ba176600.png)
[サイトはこちら](https://service.webgoto.net/blind-ai/)


## riot.jsについて
一応SPA的な作りになっているのでフロントエンドには[riot.js](https://riot.js.org/)というUIライブラリ(react,vueの類)を使用しています。
webpack等の事前コンパイル無しでコンポーネントが使用できるという手軽さが魅力です。
仕組みとしてはインブラウザコンパイルと呼ばれる処理にて、ブラウザ上でriotタグをコンポーネントに変換しているのですが、この処理が高速で数ミリ秒しか掛かりません。
またTypeScriptやAutoPrefixer等を使いたい場合は事前にコンパイルすることも可能です。
コンポーネントファイルはvueと似ていると思います。
```html:riot-tag.html
<tag-sample>
	<div>
		<p>{count}</p>
		<button onclick="{add}">Add</button>
	</div>
	<style>
	p{
		color: red;
		font-weight: bold;
	}
	</style>
	<script>
		let tag = this;
		tag.count = 1;
		tag.add = (e)=>{
			tag.count++;
		}
	</script>
</tag-sample>
```

### ネストしたCSSを書く
バンドラを使用しないデメリットとして素のcssではネストを使用できないという点があったのですが、解決する方法が見つかりました。
riotではhtml, css, jsのインブラウザコンパイル時のパーサーが自作できるようになっています。
これを利用しコンポーネント内のcssでネストができるようにしてみました。

### 方法
[El](https://github.com/frameable/el)というWeb Componentsを簡単に生成できるマイクロライブラリに、ネスト + &(アンパサンド)を使用したstyleを通常のcssに変換するロジックがあったので流用させてもらいました。
Elはpreactやriot.jsに似た感じで、6KB(gZip:2KB)と軽量なのでコチラはコチラで面白そうなライブラリです。
同じ作者さんが[zcss](https://github.com/dchester/zcss.js)という変換そのモノのライブラリを作られているのですが、ライセンスが書かれていなかったのでMITライセンスと明記されているElの方から抜き出しました。
ブラウザで読み込むjsで次のように設定します。

```js:riot.js v7系の場合
//registerPreprocessorを使う
riot.compiler.registerPreprocessor('css', 'scss', function(css, status){
	let lines = [], stack = [], open, opened, close
	const src = css.replace(/,\n/gs, ',')
	for (let line of src.split(/\n/)) {
		line = line.replace(/(.+,.+){/, ":is($1){")
		if (line.match(/^\s*@[msdk].*\{/))
			opened = open = close = (opened && !lines.push('}')) || lines.push(line) & 0
		else if (line.match(/\{\s*$/)) open = stack.push(line.replace('{','').trim()) | 1
		else if (line.match(/\s*\}\s*$/)) close = (!stack.pop() && lines.push('}')) | 1
		else {
			if (!line.trim()) continue
			if (opened && (open || close)) opened = close = lines.push('}') & 0
			if (open || close) opened = !(open = lines.push(stack.join` `.replace(/ &/g, '') + '{') & 0)
			lines.push(line)
		}
	}
	css = close && lines.push('}') && lines.join('\n')
	return {
		code: css,
		map: null,
	}
});

riot.compileFromString(tag);
riot.mount('[is="riot-nest"]');

```

```js:riot.js v3系の場合
//riot.parsers.cssを使う
riot.parsers.css.scss = function(tagName, css){
	let lines = [], stack = [], open, opened, close
	const src = css.replace(/,\n/gs, ',')
	for (let line of src.split(/\n/)) {
		line = line.replace(/(.+,.+){/, ":is($1){")
		if (line.match(/^\s*@[msdk].*\{/))
			opened = open = close = (opened && !lines.push('}')) || lines.push(line) & 0
		else if (line.match(/\{\s*$/)) open = stack.push(line.replace('{','').trim()) | 1
		else if (line.match(/\s*\}\s*$/)) close = (!stack.pop() && lines.push('}')) | 1
		else {
			if (!line.trim()) continue
			if (opened && (open || close)) opened = close = lines.push('}') & 0
			if (open || close) opened = !(open = lines.push(stack.join` `.replace(/ &/g, '') + '{') & 0)
			lines.push(line)
		}
	}
	css = close && lines.push('}') && lines.join('\n')
	console.log(css);
	return css
};

riot.compile(tag);
riot.mount('[data-is="riot-nest"]');

```
上記のように設定することで次のようなネストされたcssを使用する事ができます。設定名をscssにしているのエディター(WebStorm)のシンタックスが効くからそうしているだけで、自由な名前に設定できます。
```html:riot-tag.html
<tag-sample>
	<div>
		<p>text</p>
	</div>
	<style type="text/scss"><!--typeに設定した名前を追加-->
	div{
		p{
            color: red;
            &:hover{
                color: blue;
            }
        }
	}
	</style>
	<script>
		let tag = this;
	</script>
</tag-sample>
```

## 最後に
riot.jsは仮想DOMもシャドウDOMも使わないプレーンなDOMを使用するので、jQueryやvanillaなライブラリと苦労せず共存できます。
気軽に使えるライブラリなのでユーザーが増えてriot.jsの記事が増える事を願っています！