クライアントサイドJavaScriptのライセンス管理
最近シリコンウエハーもらって嬉しかったago(@kyo_ago)です。
このエントリはGREE Advent Calendar 2013 11日目の記事です。
今回はクライアントサイドJavaScriptにおけるライセンス管理の問題を取り上げたいと思います。
ライセンス管理の問題点
「使用しているライブラリのライセンス管理をどうするか」はクライアントサイドJavaScriptにかぎらず発生する問題ですが、クライアントサイドJavaScriptには以下の様な特徴があるため問題が複雑になります。
- コードが結合、圧縮される場合がある
クライアントサイドJavaScriptでは読み込みの速度を上げるため、使用しているライブラリの結合、圧縮を行うことがあります。しかし、この時誤ってライセンス文が捨てられてしまうことがあります。 - ソースが外部に公開される
クライアントサイドJavaScriptではソースコードは基本的に公開された状態で配布されるため、比較的自由度の高いライセンスであっても権利上問題になることがあります。
ライセンス管理の問題点に対する対応
これらの問題に対しては一般的に以下の様な対応が取られます。
- 圧縮前にライセンス文を退避し、圧縮後に退避しておいたライセンス文を追加する
- 使用しているライブラリのライセンス文だけを別ファイルに書き出し、htmlやJavaScriptのソース内にコメントでライセンスが書かれたファイルへのURLを記載する
- 未圧縮版のソースへのURLをコメントで埋め込む
- SourceMapで未圧縮版を配布する
ただ、1, 2の方法は手動での対応が必要になるため自動化が難しいという問題があり、3, 4の方法は本来不要な未圧縮コードを公開する必要があるという問題があります。
圧縮ライブラリ側の対応
この点に関しては圧縮ライブラリ側での対応も行われています。
ここからは各圧縮ライブラリがライセンス文を残すために行っている対応を紹介します。
- Closure Compiler
ブロックコメントの先頭を/**で開始して、コメント内に@licenseを記述する
1 2 3 |
/** @license some license information here */ |
- YUI Compressor
ブロックコメントの先頭を/*!開始する
1 |
/*! some copyright information here */ |
- mishoo/UglifyJS2
- commentsオプションを渡して、独立したブロックコメント内に@licenseを記述する(--commentsで残したいコメントの正規表現を指定することも可能)
1 |
/* @license some license information here */ |
この中でClosure Compilerは「一度圧縮するとライセンス表記が変わる」という問題があり、そのため2回圧縮するとライセンス表記が消えてしまう点に注意してください。
上記のライセンス表記をClosure Compilerで圧縮すると以下の形式で出力されます(このまま再度圧縮するとライセンス表記が消えてしまいます)
1 2 3 |
/* some license information here <h3 id="hs_b7a5e508822d051164ca69465860e8f6_header_0">/</h3> |
各ライブラリの記述方式
次は比較的メジャーなライブラリを元に、ライセンス文の記述方式を紹介します。
jQuery
まずは未圧縮版jQueryのライセンス表記です。
jQueryのライセンス表記は割と一般的なライセンス表記です。
ただ、この形式でもYUI Compressorでは正しくライセンス表記が残りますが、Closure Compiler、UglifyJS2で圧縮した場合にはライセンス表記が消えてしまいます(UglifyJS2は正規表現で対応可能)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*! * jQuery JavaScript Library v2.0.3 * http://jquery.com/ * * Includes Sizzle.js * http://sizzlejs.com/ * * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2013-07-03T13:30Z */ |
ちなみに、圧縮版は以下の形式になっています。
1 2 3 |
/*! jQuery v2.0.3 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license //@ sourceMappingURL=jquery-2.0.3.min.map <h3 id="hs_b7a5e508822d051164ca69465860e8f6_header_1">/ |
AngularJS
次は未圧縮版AngularJSのライセンス表記です。
こちらはClosure Compiler、UglifyJS2では正しくライセンス表記が残りますが、YUI Compressorで圧縮した場合にはライセンス表記が消えてしまいます。
1 2 3 4 5 |
/** * @license AngularJS v1.2.4 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ |
ちなみに、圧縮版のライセンス表記は以下のようになっているため、どの圧縮ライブラリで圧縮してもライセンス表記は消えてしまいます。
1 2 3 4 5 |
/* AngularJS v1.2.4 (c) 2010-2014 Google, Inc. http://angularjs.org License: MIT <h3 id="hs_b7a5e508822d051164ca69465860e8f6_header_2">/ |
Underscore.js
Underscore.jsのライセンス表記はラインコメントの組み合わせで出来ています。
この形式はどの圧縮ライブラリでもサポートされておらず、基本的に手動で対応を行う必要があります。
1 2 3 4 |
// Underscore.js 1.5.2 // http://underscorejs.org // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. |
これに関しては、本家Issueにも「/*!で始まる形式に変更してほしい」という要望が挙げられていますが、作者から却下されています。
Use bang comment to preserve license · Issue #1280 · jashkenas/underscore
Underscore.jsは圧縮版、未圧縮版でライセンス表記に違いはありませんでした。
Backbone.js
Backbone.jsのライセンス表記もUnderscore.jsと同じようにラインコメントの組み合わせで出来ています。
1 2 3 4 5 6 7 |
// Backbone.js 1.1.0 // (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org |
しかも最初のコメントの後に改行が入っているため、さらに圧縮ライブラリでの処理が難しくなっています。
Backbone.jsの圧縮版にはライセンス表記はなくなっていますが、SourceMapへの参照が入っています(ファイル末尾)
1 |
//# sourceMappingURL=backbone-min.map |
Zepto.js
Zepto.jsの場合、ライセンス表記は以下のように「ライセンス表記へのURLを記述する」形式になっています。
1 |
/* Zepto v1.1 - zepto event ajax form ie - zeptojs.com/license */ |
Underscore.jsは圧縮版、未圧縮版でライセンス表記に違いはありませんでした。
Esprima
Esprimaの場合、ライセンス文は記述されていますが、「License」と言った文字は記載されていないため、圧縮ライブラリでの処理は困難になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be> Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <h3 id="hs_b7a5e508822d051164ca69465860e8f6_header_3">/</h3> |
解決案
ここまで紹介してきたようにクライアントサイドJavaScriptではライセンス管理が非常に煩雑になる可能性があります。
この問題に関して、本来であればライブラリの作者、圧縮ライブラリ双方の対応で解決することがベストだとは思いますが、とりあえず何とかしたいのでいい感じに取得できるgrunt taskを作成しました。
grunt-license-saverの紹介
このgrunt taskはここまで紹介したすべての形式のライセンス文を解析でき、text, Markdown, JavaScript, JSONのいずれかの形式で書き出すことができます。
使い方
以下のコマンドでinstallします。
1 |
$ npm install grunt-license-saver --save-dev |
Gruntfile.jsに以下の内容を記述します。
1 2 3 4 5 6 7 8 9 10 |
grunt.initConfig({ 'save_license' : { 'libs' : { 'src' : ['js/lib.js'], // ディレクトリ以下すべてのJSファイルが対象の場合、['js/**/*.js']とする // 'format' : '', // formatは'text', 'JavaScript', 'Markdown', 'JSON'のいずれかの指定が可能。指定がない場合、destに指定されたファイル名の拡張子から推測 'dest' : 'license.text' // ['license.text', 'license.js']形式で複数指定も可能 } } }); grunt.loadNpmTasks('grunt-license-saver'); |
以下のコマンドを実行します。
1 |
grunt save_license |
これで以下の様なJSを元にして
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/*! * jQuery JavaScript Library v2.0.3 * http://jquery.com/ * * Includes Sizzle.js * http://sizzlejs.com/ * * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2013-07-03T13:30Z */ alert(1); // Backbone.js 1.1.0 // (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org alert(2); |
以下の様な内容のlicense.textが生成されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
! * jQuery JavaScript Library v2.0.3 * http://jquery.com/ * * Includes Sizzle.js * http://sizzlejs.com/ * * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2013-07-03T13:30Z <br /> Backbone.js 1.1.0 (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Backbone may be freely distributed under the MIT license. For all details and documentation: http://backbonejs.org |
基本的にはMarkdownやtext形式で書き出して、圧縮後のJS内にコメントとして埋め込むことを推奨します。
ただ、その後複数回圧縮される可能性がある場合、JavaScript形式で書き出してコード内に埋めることを推奨します。(改行等がエスケープ文字列に置き換えられるためライセンス表記の見た目は変わってしまいますが、JavaScriptの変数へ保存する形式で書き出すため再圧縮されてもライセンス文が残ります)
明日は先日社内で開催されたJavaScript hackathonでHaskellの講師をしていただいた@beketaさんです。