JavaScriptにも@importを

JavaScriptにも、CSSのような@importが欲しい。

ってとき、多くの場合はscriptタグ を document.write するんだけど、相対パスの基準ディレクトリが問題になる。CSSから他のファイルを参照するときは、そのCSSファイルのあるディレクトリが相対パスの基準になるけど、document.write じゃ HTMLのURLが基準になってしまう。そのせいで、scriptタグを document.write するスクリプト(import.jsとか)を、階層の異なるHTMLから共通して使うのがちょっと難しい。

普通の解法は、import.jsを使う前に、相対パスプレフィクスを変数に入れておくか、インポートを関数として、HTMLから基準パスを渡すか…。

<script type="text/javascript">
var GLOBAL_JAVASCRIPT_PREFIX = "../../js/";
</script>
<!-- import.js が一括で scriptタグ を document.write する -->
<script type="text/javascript" src="../../js/import.js"></script>
<script type="text/javascript" src="../../js/import.js"></script>
<script type="text/javascript">
import_all("../../js/"); //ベースパスを渡す
</script>

…どっちもDRYじゃないからダサいなぁ。

で、突如思いついたのがこの方法。

HTMLで、外部ファイルのスクリプトが実行される瞬間、「DOMの最後の要素」は scriptタグであり、src属性には「HTMLからみたスクリプト自身のパス」が書かれている。そいつを暗黙的にデフォルトとすれば…

(function() {
    var jsfiles = ["a.js", "b.js"];  // ロードされるスクリプト(このファイルからの相対パス指定)
    
    /****************************** DO NOT EDIT BELOW *****************************/
    function lastof(es)    { return es[es.length - 1]; }
    function dirname(path) { return path.substring(0, path.lastIndexOf('/')); }
    var prefix = dirname(lastof(document.getElementsByTagName('script')).src);
    for(var i = 0; i < jsfiles.length; i++) {
        document.write('<script type="text/javascript" src="' + prefix + '/' + jsfiles[i] + '"></script>');
    }
}).call(this);

これで、

<script type="text/javascript" src="../js/import.js"></script>

は

<script type="text/javascript" src="../js/a.js"></script>
<script type="text/javascript" src="../js/b.js"></script>

だし、

<script type="text/javascript" src="../../js/import.js"></script>

は

<script type="text/javascript" src="../../js/a.js"></script>
<script type="text/javascript" src="../../js/b.js"></script>

になる。

まあ、import.js の scriptタグをDOMの途中に挿入したらアウトだけど、そもそも、それだと document.write がアウトだから、気にしない気にしない。