SASS/SCSS ファイルを読み込んでブラウザ上でコンパイル・適用する「in-browser-sass」を作った
HTML に link
要素で SASS 記法のファイルを読み込ませたり、style
要素で SCSS 記法のコードを埋め込んだりして、それをブラウザ上でコンパイルして通常の CSS として適用させるライブラリ in-browser-sass を作った。
目次
デモ
ライブラリの紹介ページで実際に使ってみているので、以下のページを開発者ツール等で見てみて欲しい。
初期表示でウニウニとスタイルが動くはず。裏で外部 SASS / SCSS ファイルを読み込んで処理したりしているので、反映までに若干時間がかかる。
ライブラリの使い方
このライブラリは sass.js というライブラリを使用して、ブラウザ上で JavaScript だけで SASS/SCSS をコンパイルしている。そのため、本ライブラリと同時に sass.js を読み込んでおく必要がある。
本ライブラリについては npm より npm install @neos21/in-browser-sass
で取得してもいいし、unpkg.com を CDN として利用してもらっても良い。
<!-- sass.js を読み込む -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.7/sass.sync.min.js"></script>
<!-- 本ライブラリを読み込む -->
<script src="https://unpkg.com/@neos21/in-browser-sass@1.0.2/in-browser-sass.min.js"></script>
次に SASS/SCSS コードを用意するワケだが、link
要素で .sass
ファイルや .scss
ファイルを外部読み込みしても良いし、style
要素で埋め込んでも良い。その際、SASS 記法なら type="text/sass"
、SCSS 記法なら type="text/scss"
と属性を振っておくこと。
<!-- 以下のいずれの方法でも読み込み可能 -->
<link rel="stylesheet" type="text/sass" href="example.sass">
<link rel="stylesheet" type="text/scss" href="example.scss">
<style type="text/sass"> /* SASS 記法 */ </style>
<style type="text/scss"> /* SCSS 記法 */ </style>
style
要素は body
要素に書いても問題ない。
ページが開かれると、本ライブラリがコンパイルを行い、元の link
・style
要素の直後にコンパイルした CSS コードを配置する。つまり以下のようになる。
<link rel="stylesheet" type="text/sass" href="example.sass">
<style type="text/css"> /* example.sass の内容 */ </style>
<link rel="stylesheet" type="text/scss" href="example.scss">
<style type="text/css"> /* example.scss の内容 */ </style>
<style type="text/sass"> /* SASS 記法 */ </style>
<style type="text/css"> /* 「SASS 記法」の内容 */ </style>
<style type="text/scss"> /* SCSS 記法 */ </style>
<style type="text/css"> /* 「SCSS 記法」の内容 */ </style>
コレにより、複数の SASS/SCSS ファイルを書いた時のカスケード順が保持される。
実用性は?
本ライブラリ、実用性はほとんどない。お遊びとして使うに留めてほしい。
まず、利用している sass.js のコンパイラが 3MB 以上あるので、それだけでも読み込みに時間がかかる。
それに、link
要素を使用している場合は本ライブラリが Ajax で再度そのファイルを取得しに行くので、外部 SASS / SCSS ファイルへのリクエスト数が2倍になる。それからコンパイルを行い、DOM 要素として挿入するので、ページに実際に CSS が適用されるまでラグがある。
そんなワケで、実用は難しい。プロダクトではちゃんとプリコンパイルしたコードを使おう。
このライブラリを作るまで
あとはこのライブラリを作るまでの軌跡。
初めは、ふと「SASS ってブラウザ上でコンパイルできないのかね?」と思い立ったところ。LESS は JavaScript ベースで、SASS は Ruby ベースだったと思うので、移植されていないかなーと思ったら、既に libsass というプロジェクトがあるようだった。libsass は C/C++ で実装していて、ほぼほぼ互換性が保たれているようだ。
そしてその中で sass.js という JavaScript への移植版が見つかったので、コレを使うかーと思い立った。
で、よくよく sass.js を管理している GitHub のオーガナイザを見ていたら、「browser-sass」なるリポジトリを発見。なんだ既にあるんか、と思いきや Work in progress だった。
ただ、イシューによさげなコードは載っていて、link
要素は Ajax で取得すりゃいいのかなーとか思ってた。ファイル内での @import
を解決していないんだけども (コレは本ライブラリも同じ)。
もう少し調べていると、今度は SCSS 限定で同じことをやろうとしている browser-scss というツールを公開している人がいて、大部分はこのライブラリから着想を得た。
ただ、このライブラリは style
要素で埋め込んだ SCSS 記法のみ対応していたのと、不要な処理が混じっていたりしたので、ゼロから作り直すことにした。
実装について
本ライブラリのコード全量は以下で確認できる。コメントを日本語で書く乱暴さ。
全体は inBrowserSass()
という一つの関数に処理を入れていて、最後に「ブラウザ上で実行されているか」などを確認している。
まずはその最後の部分を紹介しようかしら。
関数起動処理
ブラウザで動作しているかは window
や document
が存在していれば OK とする。typeof 【対象】 !== 'undefined'
と文字列にして比較するのが確実。
ページ全体にある link
・style
要素を拾うために DOMContentLoaded
イベントまで待ってから処理を始める。グローバルに Sass
オブジェクトがあれば、メイン処理 inBrowserSass()
を呼ぶだけで終わり。
Sass
がない場合は、sass.js が読み込めていないので、CDN から読み込みを試みる。script
要素を生成し、CDN の URL をブチ込み、script
要素に onload
イベント属性を付与して、sass.js が読み込めたら inBrowserSass()
関数を呼ぶようにしてある。この script
要素を head
要素に埋め込めば、自動的に sass.js の読み込みが始まり、読み込みが完了次第 onload
イベントに指定した inBrowserSass()
関数が実行される、というワケ。
link
・style
要素の収集
続いて inBrowserSass()
関数の中の説明。先に compileAndInsert()
という関数を用意しているがコレは後述。先に link
要素と style
要素を収集するところから。
DOM 要素の収集には document.querySelectorAll()
を使った。この API、document.querySelectorAll('style, link')
のようにして要素を複数列挙して一気に取得できるのだ。
こうして取得した要素のコレクションは Array Like なオブジェクトで、forEach()
するために Array.prototype.forEach.call()
を使っている。
各要素は type
属性値を拾っていて、text/sass
か text/scss
でなければ処理しないようにしている。この属性値は後で「SASS コンパイル」「SCSS コンパイル」を振り分けるためにも使っている。
style
要素の処理
ココで、style
要素と link
要素とで処理が別れる。
style
要素の場合は簡単で、innerHTML
で内容が拾えるので、コレをコンパイル用の関数に渡すだけ。
link
要素の処理
一方 link
要素は、外部ファイルの内容を取得しないといけないので、XMLHttpRequest
を生成して Ajax GET 通信をしている。responseText
が拾えたらコンパイル用の関数に渡している。
この辺の例外ハンドリングとか、クロスブラウザ対応とかした方が良いのかしら?
コンパイル用の関数
コンパイル用の関数 compileAndInsert()
の中では、sass.js を使ってコンパイルを行っている。
Sass.compile()
でコンパイルをするのだが、sass.js のコンパイルオプションに indentedSyntax
というモノがあり、SASS の場合はコレを true
にしないといけない。
コールバック関数でコンパイル結果テキストを受け取り、新規生成した style
要素に詰め込む。style
要素には念のため type="text/css"
を付与したが、HTML5 の仕様だとデフォルトで不要みたいなので省いても問題ないか。
「元の要素の直後に挿入する」は、jQuery の after()
と同様に、【元要素】.parentElement.insertBefore(【入れたい要素】, 【元要素】.nextSibling)
というイディオムを使っている。
コレで完了。
実用性は乏しいが、JavaScript だけで色んなことできるなぁとか、例外ハンドリングちゃんとやろうとしたら大変だな、とか、色んな勉強になった。
Issues や Pull Requests とかがもらえたら対応するので、ぜひチェックしていただたきたく!