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 要素に書いても問題ない。

ページが開かれると、本ライブラリがコンパイルを行い、元の linkstyle 要素の直後にコンパイルした 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() という一つの関数に処理を入れていて、最後に「ブラウザ上で実行されているか」などを確認している。

まずはその最後の部分を紹介しようかしら。

関数起動処理

ブラウザで動作しているかは windowdocument が存在していれば OK とする。typeof 【対象】 !== 'undefined' と文字列にして比較するのが確実。

ページ全体にある linkstyle 要素を拾うために DOMContentLoaded イベントまで待ってから処理を始める。グローバルに Sass オブジェクトがあれば、メイン処理 inBrowserSass() を呼ぶだけで終わり。

Sass がない場合は、sass.js が読み込めていないので、CDN から読み込みを試みる。script 要素を生成し、CDN の URL をブチ込み、script 要素に onload イベント属性を付与して、sass.js が読み込めたら inBrowserSass() 関数を呼ぶようにしてある。この script 要素を head 要素に埋め込めば、自動的に sass.js の読み込みが始まり、読み込みが完了次第 onload イベントに指定した inBrowserSass() 関数が実行される、というワケ。

linkstyle 要素の収集

続いて inBrowserSass() 関数の中の説明。先に compileAndInsert() という関数を用意しているがコレは後述。先に link 要素と style 要素を収集するところから。

DOM 要素の収集には document.querySelectorAll() を使った。この API、document.querySelectorAll('style, link') のようにして要素を複数列挙して一気に取得できるのだ。

こうして取得した要素のコレクションは Array Like なオブジェクトで、forEach() するために Array.prototype.forEach.call() を使っている。

各要素は type 属性値を拾っていて、text/sasstext/scss でなければ処理しないようにしている。この属性値は後で「SASS コンパイル」「SCSS コンパイル」を振り分けるためにも使っている。

style 要素の処理

ココで、style 要素と link 要素とで処理が別れる。

style 要素の場合は簡単で、innerHTML で内容が拾えるので、コレをコンパイル用の関数に渡すだけ。

一方 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 とかがもらえたら対応するので、ぜひチェックしていただたきたく!