astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Spark 2.0を使ってみた

山縣です。

新年あけましておめでとうございます。

本年もよろしくお願いいたします。

 去年弊社の福田が CDH のアップグレードをしてくれてSpark が1.6系になるとともに、並行してSparkの2.0が使えるようになりました。(Spark2.0の導入については福田の記事をどうぞ→ もう待てない、Spark2.0の導入と実践 - astamuse Lab)

 現在弊社の環境でインストールされている Spark2.0 はまだベータということもあり、当初は少し触ってみる程度に留めようと考えていました。しかし1系のSparkが1.5→1.6に上がったことで問題に遭遇したこともあり、いくつかのバッチは2.0で動かしています。

 今回は1.6や2.0を触っていて遭遇した問題や気になった点などについて書いていきたいと思います。

Spark 1.6 と Tungsten

 社内のSparkが1.6系にバージョンアップされ、自分のジョブの一つが途中で abort してしまう現象が起きてしまいました。ログを見ると複数のDataFrameをJOINする処理で OutOfMemory が出ています。

 原因が分からずいろいろ調査をしていたのですが、改めてログを見直すとTungsten が有効になっていることに気が付きました。

 実は自分の実行している Job では今まで以下のパラメータ指定で Tungsten を無効にしているはずでした。

--conf spark.sql.tungsten.enabled=false

 Tungsten は Spark 1.4 から導入されたSpark の性能を上げるための仕組みです。社内のSpark が 1.3系から1.5系に上がったときに、この Tungsten が原因と思われるエラーが起きたため、上記パラメータで Tungsten を無効にすることで回避していました。

 もしやと思ってSpark 1.6 のリリースノートを見てみると以下のような記述がありました。

The flag (spark.sql.tungsten.enabled) that turns off Tungsten mode and code generation has been removed. Tungsten mode and code generation are always enabled (SPARK-11644).

 

Tungstenモードとコード生成を無効にするフラグ spark.sql.tungsten.enabled は削除されました。Tungstenモードとコード生成は常に有効となります。(SPARK-11644)

 つまり Tungsten は1.6から常に有効で無効化することができなくなってしまったようです。

 その後エラーを回避するためパラメータの調整などを試みましたがうまくいかず、最終手段として Executor のメモリ量を多くすることで何とか回避はできました。

 とりあえず回避はできましたが、このやり方ではデータ量が増えたり、処理がより複雑になったりしたときに、問題が再発する恐れがあります。そこで試しにこのバッチを2.0へ移行してみました。

Spark 2.0 への書き換え

 1系から2系へのメジャーバージョンアップということで当初は結構大変かと身構えましたが、やってみると意外と問題は少なく小規模な修正で済みました。具体的には以下のような修正をしました。

  • Scala のバージョン変更…Scala が 2.10系から2.11系に変わりました。
  • Spark ライブラリの変更…build.sbt で指定するSpark ライブラリのバージョンをSpark2.0のものに変更しました。
  • SparkSession 対応…Sparkのプログラミングをする上でのエントリポイントとして新しくSparkSession が導入されました。spark-shellにおいてsqlContext:SqlContext が無くなったので spark:SparkSession からSqlContext を取るようにしました。
  • spark-csv の置き換え … CSVファイルの入出力をするためのライブラリspark-csv の機能が2.0から標準で含まれるようになったので spark-csv を依存ライブラリから外しました。合わせて csv の入出力周りの処理を修正しました。
  • registerTempTable … Dataframe を SparkSQLから使うときにDataFrameのregisterTempTable メソッドでテーブルとして登録します。2.0からはこのメソッドがDeprecatedになったので createOrReplaceTempView に変えました。 (注 2.0 からDataFrameクラスは無くなりDataset になったので正確には Dataset のメソッドになります)

 つらつらと書いてみましたが、どれも大した変更ではなく、予想していたよりも楽に移行ができました。実際にビルドして実行してみると1.6で落ちていたジョブは無事に完了することができました。

 ただジョブそのものはtaskのエラーもなく進んでいるのに、driverのコンソールにエラーログが大量に表示されたり、途中で止まった処理の情報がコンソールに残ってしまったり、ベータだからか、まだ挙動が少し安定していない雰囲気もありました。そういうこともあり2.0への移行は様子を見ながら必要に応じてと考えています。

 と、これを書いている時点で、Cloudera社から Spark 2.0 Release1 が出ていることに気が付きました。いずれ社内にも導入されると期待しています。

Dataset について

 Dataset はSpark 1.6から導入されたSparkの新しいデータ形式です。Dataset は従来のDataFrame を拡張し、型パラメータを持ちます。

scala> import org.apache.spark.sql.{Dataset,Row}

scala> case class Msg(id:Int, msg:String)

scala> val ds:Dataset[Msg] = Seq(Msg(1, "I have a pen."),Msg(2, "I have an apple."),Msg(3, "Do you have a pen?")).toDS()
ds: org.apache.spark.sql.Dataset[Msg] = [id: int, msg: string]

 型パラメータを持つことでタイプセーフなプログラミングが可能となりました。従来 DataFrame を RDDに変換したりmap()foreach()などで処理する場合、DataFrame の各レコードは Row というクラスで表され、カラムからデータを取り出すときはRowクラスの getAs[T]()getString(), getInt() などのメソッドでデータを変換して取り出す必要がありました。

 Dataset では型パラメータとしてcase class などを指定することが可能で、各行を取り出すとき case class のインスタンスとして取り出すことが可能です。

scala> ds.map(x => x.msg).collect //x の型は Msg
res11: Array[String] = Array(I have a pen., I have an apple., Do you have a pen?)

 DataFrame では、例えばテーブルAのDataframe (dfA)もテーブルBのDataframe(dfB) も同じDataFrame のインスタンスなので、dfAに対して処理する関数を間違えてdfBに適用したとしてもコンパイルエラーになりません。

 しかしDataset を使えば型パラメータが違うのでコンパイル時にバグを発見することができます。下記のように Dataset[Msg] に対して処理する関数procDSMsg()に Dataset[Person] を渡すと引数の型が違うのでエラーとなります。

scala> case class Person(id:Int, name:String)

scala> val ds2 = Seq(Person(1, "Taro"), Person(2, "Jiro"), Person(3, "Subro")).toDS
ds2: org.apache.spark.sql.Dataset[Person] = [id: int, name: string] 

scala> def procDSMsg(ds:Dataset[Msg]):Unit = println("Hello")

scala> procDSMsg(ds)
Hello

scala> procDSMsg(ds2) // 引数の型が違うのでエラーとなる

<console>:35: error: type mismatch;
found : org.apache.spark.sql.Dataset[Person]
required: org.apache.spark.sql.Dataset[Msg]
procDSMsg(ds2)

 なお 2.0 からは 従来までのDataFrame はクラスとしては無くなり、Dataset に統合されました。DataFrame は以下の通り定義されています。

type DataFrame = Dataset[Row]

 つまりDataFrame は Rowを型パラメータとしてもつDatasetということになります。

Dataset用のCase classを半自動生成する

 Dataset によりタイプセーフなプログラミングが可能になりましたが、一方でこの case class 誰が作るの?テーブルごとに作るのめんどくさいので自動生成したい、と思うのは自然なことかと思います。

 ということでどうしようかなと思って考えたのですが、はじめに思い付いたのはDBライブラリのクラスの自動生成機能を使うことです。

 普段利用させていただいている ScalikeJDBC にも sbt のプラグインとしてclassを生成する scalikejdbcGen があります。試しに一つのテーブル用のclassを生成し、必要なところだけを抜き出して試してみました。しかし結果はうまくいきませんでした。理由はscalaikejdbcGen ではDBのカラム名の記法が sneak(例 abc_def_ghi) の場合に、対応するメンバ変数の名前を camel (例 abcDefGhi) にしてくれるのですが、Dataset 側でそういう変換には対応してくれないからでした。また、よくよく考えるとこの方法だと JDBCに対応したRDBMS以外のデータソースに対応できないという問題がありました。

 そこで次に思い付いたのが DataFrame/Datasetが保有しているスキーマ情報から生成することです。

 DataFrame/Dataset は schema:StructType に、DataFrame/Dataset を構成するカラム情報を保有しています。 StructType は各カラムのカラム名やデータ型などの情報を表すStructField の配列(Array)をデータとして持っていますのでこのデータを使えば case class のメンバーを定義できるはずです。

scala> ds.schema.fields.foreach(println)

StructField(id,IntegerType,false)
StructField(msg,StringType,true)

 そこで、case class を生成する Schema2CaseClass というクラスを作ってみました。

 試すにはリンク先のソースコードをコピーして spark-shell の paste で実行します。

scala> :paste
// Entering paste mode (ctrl-D to finish)
import org.apache.spark.sql.types._

class Schema2CaseClass {
...
...
// Exiting paste mode, now interpreting.

import org.apache.spark.sql.types._
defined class Schema2CaseClass

使い方は以下のようになります。

scala> val df = Seq(Msg(1, "I have a pen."),Msg(2, "I have an apple."),Msg(3, "Do you have a pen?")).toDF()

scala> val s2cc = new Schema2CaseClass
scala> import s2cc.implicits._

scala> println(s2cc.schemaToCaseClass(df.schema, "Msg2"))
case class Msg2 (
    id:Int,
    msg:Option[String]
)

 上記のようにDataFrameのスキーマ情報から Msg2 という case class を生成したので実際に試してみます。

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Msg2 (
    id:Int,
    msg:Option[String]
)

// Exiting paste mode, now interpreting.

defined class Msg2

scala> val ds = df.as[Msg2]
ds: org.apache.spark.sql.Dataset[Msg2] = [id: int, msg: string]

scala> ds.collect
res14: Array[Msg2] = Array(Msg2(1,Some(I have a pen.)), Msg2(2,Some(I have an apple.)), Msg2(3,Some(Do you have a pen?)))

 上記のように Msg2 を 型パラメータとして指定してDataset を作ることができました。

 生成されたMsg2 は msg がOption[String]型になっています。これは元のDataFrameのスキーマ定義で nullable が設定されているからです。

 Optionが適切に処理されるのか確認してみます。

scala> val df = Seq(Msg(1, "I have a pen."),Msg(2, "I have an apple."),Msg(3, null)).toDF()
df: org.apache.spark.sql.DataFrame = [id: int, msg: string]

scala> val ds = df.as[Msg2]
ds: org.apache.spark.sql.Dataset[Msg2] = [id: int, msg: string]

scala> ds.collect
res16: Array[Msg2] = Array(Msg2(1,Some(I have a pen.)), Msg2(2,Some(I have an apple.)), Msg2(3,None))

scala> ds.filter(_.msg.isDefined).collect
res17: Array[Msg2] = Array(Msg2(1,Some(I have a pen.)), Msg2(2,Some(I have an apple.)))

 上記のように元データが nullの場合は None と変換され、filterなどで処理ができます。

DatasetとDataFrame の関係

 Datasetが出たので今後は DataFrame から Dataset に移行が進んでDataFrameは使われなくなっていくのかなと思っていました。しかし実際に使ってみると DataFrame も今後も使われていくという印象を受けました。 Dataset は確かにタイプセーフで良いのですが、例えばJoin した結果などで、それ自体が処理を分割するための中間的なデータであったり、一時的にしか使われないようなデータに対して、わざわざ case class を定義する必要は無いのではと思います。 また SparkSession.sql は DataFrame を返します。 2.0からDataFrameをDataset[Row] としたことでDataFrame/Dataset間がシームレスに使えるようになったことも合わせると今後ともDataFrameとDataset をうまく使い分けていくのが良いのかなと考えています。

終わりに

 2016年の Spark は2.0のメジャーバージョンアップも含めて非常に高速に進化していった印象を受けました。年末にはすでに2.1.0も出るなど相変わらずバージョンアップが激しいです。

 今年もどのように進化していくのか楽しみなプロダクトです。

特許とその制度について 特許出願および実用新案登録出願

お久しぶりです。主に特許関連のデータ処理を担当しているBTと申します。 前回、特許及び実用新案の概要についてご説明させて頂きましたが、今回は日本国内における「特許出願」および「実用新案登録出願」についてご説明いたします。 宜しくお願いいたします。

特許出願

まずは、出願から特許をうけるまでの基本的な流れは、以下の図のようになります。

f:id:astamuse:20170118114118j:plain

このうち、「特許出願」について説明します。

発明に対して日本国で特許を受けるためには、特許庁に「特許出願」をする必要があります。その際に以下の書類をそろえて提出する必要があります。

f:id:astamuse:20170118114126j:plain

それぞれの書類について説明していきます。

「願書」

「願書」には発明の内容そのものではなく、発明者の住所と氏名、出願人の住所又は居所と氏名又は名称、添付物件の目録(この「願書」以外の書類の一覧)などを記載します。さらに出願人が外国人や日本に営業所を持たない外国の企業の場合は、代理人(要は日本の弁理士)の住所と氏名も記載することが必要です。

「明細書」

「明細書」には、「発明の詳細な説明」をもうけて、特許を受けようとする発明やその他の発明(特許を受けるまでは考えていないが、特許を受けようとする発明と同時にした関連する発明など)について説明を記載します。その発明の属する技術分野における通常の知識を有する者(つまり、その分野に従事している技術者など)が「明細書」を読んで発明を実施(発明品の製造など)出来る程度に明確かつ十分な内容を記載することが必要とされ、読んでも理解不能であるとか、発明を再現できないような曖昧な記載は認められません。記載する内容としては、発明に到る課題、課題を解決するための手段、発明の効果、発明の具体例である実施例、関連発明(「特許出願」の時に把握している関連する他の発明)などがあり、後で述べる「特許請求の範囲」とは異なり一般的な文章で記載します。

「特許請求の範囲」

「特許請求の範囲」には、特許を受けたい発明について、その発明を特定する為に必要な全ての事項を記載する必要があります。発明が複数ある場合は、「請求項1、請求項2、・・・」のように発明ごとに分けて記載することができますが、一つの「特許出願」の「特許請求の範囲」に含まれる複数の発明は、一定の技術的関係を有する必要があります。これは一つの出願にのなかに無関係な発明が複数含まれていると、その出願の審査や第三者が出願内容を参照する際に混乱をきたすためです。

「特許請求の範囲」は、独特の文体で書かれます(中にはそうなっていない出願もありますが、それは弁理士や特許事務所を代理人とせずに直接出願しているか、弁理士や特許事務所のチェックを受けていない場合が多いようです)。一つの請求項は一つの文で記載するため、構成要素が多い発明や手順などが多い発明の場合、一つの請求項が延々と数ページにわたって続くこともあります。一般的にはとても読みにくい文体のために、特許に関する文書を読んだり書いたりするのが苦手という人も多いと思います。「特許請求の範囲」は、もし特許を受けた場合にその特許の権利範囲を決めるための法的文書としての性質を持つことから、曖昧さを排除して権利範囲を明確にする法律的に有効な文章であることが望ましく、このような文体にせざるおえないという事情があります。

また、「特許請求の範囲」はの記載には、以下の点を守る必要があります。 * 請求項に記載した特許を受けようとする発明が「明細書」の「発明の詳細な説明」に記載されている必要があります。発明について「特許請求の範囲」に記載するだけではなく、「明細書」に方にも記載(通常はより詳細な説明と共に)すること * 特許を受けようとする発明が明確に記載されていること * 請求項ごとの特許を受けようとする発明の記載が簡潔であること * その他経済産業省令で定めるところにより記載されていること

「図面」

「図面」は、「特許出願」における他の出願書類と違って必ず必要というわけではありません。図がないと説明が難しい物の発明では無く図がなくても説明が可能な方法の発明や、化学など発明の分野によっては図をそれほど必要としない分野などで、「図面」を添付しない出願が多いことがありますが、特許を受けようする発明について解りやすく(特に「特許出願」を審査する審査官により深く理解してもらうために)図を用いることが望ましいため、一般的には「図面」が無い出願はほとんどありません。

「要約書」

「要約書」は、「明細書」、「特許請求の範囲」、「図面」に記載した発明の概要を記載したものです。これは、「特許出願」の数の増大や技術の高度化・複雑化によって情報へのアクセスが困難になっている状況から、出願した内容へのアクセスを容易にして利便性を高めるために必要とされているものです。その性質上、「要約書」の記載内容が「明細書」、「特許請求の範囲」、「図面」に記載した発明とかけ離れていたり、不明瞭である場合は、特許庁によって内容が書き換えられることがあります。

その他の申請書など

その他「特許出願」にあたり特例を受けたい場合は、それぞれの特例の申請書を提出する必要があります。特例の内容については次回に説明したいと思います。

実用新案登録出願

まずは、出願から実用新案登録をうけるまでの基本的な流れは、以下の図のようになります。

f:id:astamuse:20170118114131j:plain

このうち、「実用新案登録出願」について説明します。

発明に対して日本国で特許を受けるためには、特許庁に「実用新案登録出願」をする必要があります。その際に以下の書類をそろえて提出する必要があります。

f:id:astamuse:20170118114135j:plain

「実用新案登録出願」の場合に特許庁に提出する書類は、上記の「特許出願」とほとんど(但し、「特許請求の範囲」は「実用新案登録請求の範囲」という書類名に変わります)同じですが、「実用新案登録出願」の場合は「特許出願」で提出が必須では無かった「図面」が必須となっています。これは、実用新案ではその保護対象が物品の形状、構造又は組合せに限定されており、考案(特許における発明に該当)の説明に「図面」が必要とされているためです。

まとめ

以上、特許と実用新案の出願について説明をしてきました。次回は、出願において申請することが出来る特例や通常出願とは違う特殊な出願について見ていきたいと思います。

最後になりましたが、 アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ 採用サイト からご応募ください。

参考にした資料など

複数サイトを運営する上でのフロントエンドのちょっとしたノウハウ

こんにちは。 デザイン部でフロントエンドエンジニアをしているkitoです。
弊社は、転職ナビというドメインの異なるサイトを300サイト以上運営しています。レアケースであるとは思いますが、運営上のノウハウの一部をご紹介したいと思います。
今回はスタイルシートを量産するフローを書かせて頂きます。

jsonファイルから複数のcssを作成する

転職ナビは数百サイトありますが、ひとつのアプリケーションでほぼ同じテンプレートが使われ、静的なアセットは原則として共有されています。
しかし、それぞれのサイトに固有のスタイルシートを適用しなければならない場面がどうしてもあります。 弊社では、サイト固有のスタイルシートをjsonファイルのデータから量産することでその問題に対処していますが、そのjsonの元ファイルはコミュニケーション上使い勝手のよいcsvファイルでデータが管理されています。 従って、まずcsvファイルをjsonに変換します。

下記のようなcsvファイル、prefecture.csvがあったとします。これをprefecture.jsonに変換します。
domainは文字通りサイトドメインで、R,G,BはそれぞれサイトカラーのRGBの値です。

domain R G B name
hokkaido 0 11 111 北海道
aomori 32 66 66 青森
iwate 4 15 43 岩手
miyagi 55 180 8 宮城
akita 10 20 30 秋田
yamagata 90 0 0 山形
fukushima 10 10 10 福島
ibaraki 40 30 12 茨城
tochigi 68 11 54 栃木
gunma 250 100 40 群馬

csvtojsonというモジュールを使いたいので下記コマンドでインストールしてください。

npm install --save csvtojson

以下作成していくディレクトリは以下のようになります。

├── package.json
├── converter.js
├── template.js
├── prefecture.json
├── Gruntfile.js
├── dev
│   └── unique
│        ├── akita
│        │   └── styles
│        │        ├── sp_unique.scss
│        │        └── unique.scss
│        ├── aomori
│        │   └── styles
│        │        ├── sp_unique.scss
│        │        └── unique.scss
│        以下略
├── tasks
│   └── prefecture_styles.js
├── unique
│   ├── akita
│   │   └── styles
│   │        ├── sp_unique.css
│   │        └── unique.css
│   ├── aomori
│   │   └── styles
│   │        ├── sp_unique.css
│   │        └── unique.css
│   以下略
└── node_modules

converter.jsを作成して下記のコードを実行してください。csvファイルをjsonに変換します。

var fs = require('fs');
var csv = require("csvtojson");
var converter = csv({});
var csvFile = './prefecture.csv';
var jsonFile = './prefecture.json';

fs.createReadStream(csvFile).pipe(converter);
converter.on("end_parsed", function (jsonArray) { 
  fs.writeFile( jsonFile, JSON.stringify( jsonArray, null, '    ' ),'utf8');
  console.log('csv to json is complete !');
});

下記のようなprefecture.jsonが作成されたと思います。

[
    {
        "domain": "hokkaido",
        "R": 0,
        "G": 11,
        "B": 111,
        "name": "北海道"
    },
    {
        "domain": "aomori",
        "R": 32,
        "G": 66,
        "B": 66,
        "name": "青森"
    },
    以下略
]

弊社はsassを導入しているのでprefecture.jsonからgruntを通じてsassを作成し、それを元にcssを作成するフローになります。
二度手間のように感じるかもしれませんが、他のsassをimportしたり、ユニークなカラーを変数に設定できたりと、使い勝手がよいからです。

sassを量産するprefecture_styles.jsというGruntプラグインを作成します。
tasksディレクトリに、prefecture_styles.jsを作成して下記コードを記入してください。prefecture.jsonと後で作成するテンプレートを元にsassを量産します。

module.exports = function(grunt) {
    var fs = require('fs');
    var json = grunt.config('prefecture_styles').json;
    var data = JSON.parse(fs.readFileSync(json, 'utf8'));

    grunt.registerTask('prefecture_styles', 'task', function() {

        var dir = grunt.config('prefecture_styles').dir;
        var stylesDir = grunt.config('prefecture_styles').stylesDir;
        var name = grunt.config('prefecture_styles').name;
        var template = grunt.config('prefecture_styles').template;
        var sasstmp = require(template);

        Object.keys(data).forEach(function(key, index) {
            var domain = data[key]["domain"]
            var R = data[key]["R"]
            var G = data[key]["G"]
            var B = data[key]["B"]
            var sassObj = new sasstmp(R,G,B);

            name.forEach(function(val, index) {
                grunt.file.write(dir + domain + stylesDir + name[0],sassObj.pc , function(err) {});
                grunt.file.write(dir + domain + stylesDir + name[1],sassObj.sp , function(err) {});
            });
        });
        console.log('generated sass!')
    });
};

grunt.config('prefecture_styles').〇〇で、Gruntfile.jsから設定を読み込んでいます。
jsonはスタイルシートの値が入ったjsonファイル(ここではprefecture.json)、dirはベースとなるディレクトリ、stylesDirはsassが入るディレクトリ名、nameはsassの名前、templateは量産するsassのテンプレートになります。
Object.keys(data).forEachでprefecture.jsonから値を取り出して、それぞれ変数に代入し、grunt.file.writeでpc用とsp用のsassを作成&記述しています。

元となるテンプレート、template.jsを作成します。

var sasstmp = function(R,G,B) {
  this.pc = '@charset "utf-8";\n' + '$uniqueSiteColor:rgb(' + R + ',' + G + ',' + B + ');\n' + '.siteColor{ background-color:rgb(' + R + ',' + G + ',' + B + ')};\n';
  this.sp = '@charset "utf-8";\n' + '$uniqueSiteColor:rgb(' + R + ',' + G + ',' + B + ');\n' + '.siteColor{ background-color:rgb(' + R + ',' + G + ',' + B + ')};\n';
}

module.exports = sasstmp;

次にGruntfile.jsの設定に移ります。
(gruntをインストールについては割愛します。)
下記gruntプラグインをインストールしてください。

npm install grunt-contrib-compass --save-dev
npm install grunt-contrib-clean --save-dev

grunt-contrib-compassはsassをcssにコンパイルするプラグイン、grunt-contrib-cleanはファイルを削除をするプラグインです。

Gruntfile.jsを下記のように記述してください。 実際に業務で使うときのGruntfile.jsは、sassの変更をwatchしたりwebpackを使ってjsをバンドルしたりと他のタスクを追加することになりますが、今回は必要最小限の構成にしてあります。

var grunt = require('grunt');

module.exports = function(grunt) {

    grunt.initConfig({

        pkg: grunt.file.readJSON('package.json'),

        clean: {
            build: {
                src: [
                    '.sass-cache',
                    './dev/build/unique/'
                ]
            },
            release: {
                src: [
                    '.sass-cache',
                    './unique/**/styles/*.css'
                ]
            }
        },

        prefecture_styles: {
            dir: './dev/unique/',
            json: './prefecture.json',
            template: '../template.js',
            stylesDir: '/styles/',
            name: [
                'unique.scss',
                'sp_unique.scss'
            ]
        },

        compass: {
            prod: {
                options: {
                    sassDir: './dev/unique',
                    cssDir: './unique',
                    environment: 'production'
                }
            }
        }
    });


    //ローカルにあるprefecture_styles.jsを読み込んでいる。
    grunt.task.loadTasks('tasks');

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-compass');
    grunt.loadNpmTasks('grunt-contrib-clean');

    grunt.registerTask('build', ['clean:release', ' prefecture_styles', 'compass:prod']);

};

grunt.initConfig({})の中に prefecture_styles.jsの設定を記述します。
compass:{}には prefecture_styles.js作成したsassをcssにコンパイルする設定を行っています。
grunt.task.loadTasks('tasks')は、コメントにあるように先程作成したprefecture_styles.jsをロードしています。
Nodeモジュールとして公開しておけば、grunt.loadNpmTasks(' prefecture_styles')で読み込めるでしょう。

下記でGruntを実行してください。

grunt build

uniqueディレクトリが作成され、それぞれのドメイン名以下に、unique.css、sp_unique.cssが作成されていれば成功です。
cssを量産するタスクを実施する際に、本稿を参考にして頂ければ幸いです。今回は以上になります。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

Copyright © astamuse company, ltd. all rights reserved.