node のexpress で Closure Library
node で Closure Library をやる一例です。
やること
- express プロジェクトを作る
- 使うnodeモジュールのインストール
- Closure Library を落とす
- Closure Compiler を落とす
- 実験用のapp.jsコードを置く
- production モードとそうでないモードを分ける
- Cakefileにタスクを書く
- 配備してみる
1. まず、express プロジェクトをつくります
$ express my_project
$ cd my_project
2. ./package.json を編集して、こんなふうにします。
/* ./package.json */ { "name": "application-name" , "version": "0.0.1" , "private": true , "dependencies": { "express": "2.5.8" , "jade": ">= 0.0.1" , "q": ">= 0.8.2" // new , "muffin": ">= 0.2.7" // new , "underscore": "" // new } } 追記:underscoreを追加
nodeモジュールをインストールします。
$ npm install ... q@0.8.2 ./node_modules/q └── event-queue@0.2.0 jade@0.20.3 ./node_modules/jade ├── mkdirp@0.3.0 └── commander@0.5.2 express@2.5.8 ./node_modules/express ├── mime@1.2.4 ├── mkdirp@0.3.0 ├── qs@0.4.2 └── connect@1.8.5 muffin@0.2.7 ./node_modules/muffin ├── temp@0.2.0 ├── coffee-script@1.1.3 ├── docco@0.3.0 ├── uglify-js@1.1.0 ├── q-fs@0.1.17 (q-io@0.0.10 qq@0.3.2 fs-boot@0.0.7) ├── q@0.7.1 (event-queue@0.2.0) ├── glob@3.0.1 (graceful-fs@1.1.5 fast-list@1.0.2 inherits@1.0.0 minimatch@0.1.5) └── prompt@0.1.12
3. Closure Library を落っことします。
$ svn checkout http://closure-library.googlecode.com/svn/trunk/ ./public/closure-library
publicフォルダの下には、置きたくないんですけどね。でも置きます。public/closure-library/closure/goog/base.js をhttpで読まないといけないので、ここに置いているんです。ハードリンクとか貼る人もいるんでしょうか?
4. Closure Compiler を落っことします。
$ mkdir -p library/closure-compiler $ wget -P library/closure-compiler/ http://closure-compiler.googlecode.com/files/compiler-latest.tar.gz $ tar -C library/closure-compiler/ -xf library/closure-compiler/compiler-latest.tar.gz $ ls library/closure-compiler/ COPYING compiler-latest.tar.gz README compiler.jar
ふーっ。ライブラリとかの準備はできました。
5. 次に、自分で書くクライアントサイドのコードを置く場所をつくります。あと、実験用のコードも置いておきます。
$ mkdir -p resources/client $ touch resources/client/app.js
./resources/client/app.js には試しにこう書いておきます。
/* ./resources/client/app.js */ goog.provide('my.app'); goog.require('goog.dom'); my.app = function () { var elm = goog.dom.createDom('h1', { style: 'font-size: 600%' }, 'yeah..'); goog.dom.append(document.body, elm); }; goog.exportSymbol('my.app', my.app);
./resources/client 配下にファイルを並べて、goog.provide/ goog.require していく感じです。
6. production モードと開発モードの区別ができるようにします。
作戦としては、
- node を「NODE_ENV=production node app.js」で起動したときは本番用(JSを小さくします)。
- ただの「node app.js」で起動したときは、開発用(ビルドにできるだけ時間をかけないようにします)。
まず、サーバ側のコードの修正です。
./routes/index.js
/* ./routes/index.js */ exports.index = function(req, res){ res.render('index', { title: 'Express' , IS_PRODUCTION: process.env.NODE_ENV === 'production' // new }) };
次に、この値をHTMLテンプレート側で拾って、クライアント側のjsファイルの読み分けをします。
// ./view/layout.jade !!! html head title= title link(rel='stylesheet', href='/stylesheets/style.css') // new code - if (IS_PRODUCTION) script(src='/javascripts/app-min.js') - else script(src='/closure-library/closure/goog/base.js') script(src='/javascripts/app.js') // modified body(onload='my.app()')!= body
OK。だと思います。ついでに、onload でスクリプトを実行するように書いておきました。
7. Cakefile にタスクを書いて、./public/javascript/app.js を作ってもらいます。
以下、muffin を使いたいのでcoffeescriptが必要です。coffeescriptをインストールしておく必要があります(npm install -g coffee-script)。
プロジェクト直下に Cakefile というファイルを作って、こんなファイルにします。
※修正しました。muffinだとoutputするやりかたが分からなかったのでネイティブのプロセス使った。
Cakefile
# Include required libraries. muffin = require 'muffin' child = require 'child_process' Q = require 'q' _ = require 'underscore' # Options. option '-w', '--watch', 'Keep watching file modifying.' option '-c', '--compile', 'Compile client scripts as ADVANCED_OPTIMIZATIONS.' # Constants. commonArgs = " public/closure-library/closure/bin/build/closurebuilder.py --root=./public/closure-library/ --root=./resources/client/ --namespace=\"my.app\" " COMMAND = BUILD: commonArgs + " --output_mode=script --output_file=./public/javascripts/app.js " COMPILE: commonArgs + " --output_mode=compiled --output_file=./public/javascripts/app-min.js --compiler_jar=./library/closure-compiler/compiler.jar --compiler_flags=\"--compilation_level=ADVANCED_OPTIMIZATIONS\"" # Tasks. stamp = ( -> startTime = -1 return { start: -> startTime = Date.now() if startTime is -1 end: -> t = if startTime isnt -1 then Date.now() - startTime else 0 startTime = -1 return t } )() execCommand = (command) -> stamp.start() child.exec command, (error, stdout, stderr) -> console.log """ \n\n====== #{stderr} --IT TOOK #{stamp.end() / 1000} sec.--\n """ task 'build', 'Make closure builder build scripts.', (options) -> command = if options.compile then COMMAND.COMPILE else COMMAND.BUILD if !options.watch execCommand(command) else isFirst = true muffin.run files: './**/*' options: options map: 'client/.*?\.js': (matches) -> return if isFirst execCommand(command) after: -> execCommand(command) if isFirst isFirst = false
これで、cakeタスクが使えるようになりました。
- 開発用に、更新監視しながらビルドするとき: cake -w build
- 本番用にコンパイルするとき。:cake -c build
8. ビルドしてみましょう。
$ cake build The "sys" module is now called "util". It should have a similar interface. public/closure-library/closure/bin/build/closurebuilder.py: Scanning paths... public/closure-library/closure/bin/build/closurebuilder.py: 797 sources scanned. public/closure-library/closure/bin/build/closurebuilder.py: Building dependency tree.. $ node app.js
本番環境用はどうでしょうか?
$ cake -c build The "sys" module is now called "util". It should have a similar interface. public/closure-library/closure/bin/build/closurebuilder.py: Scanning paths... public/closure-library/closure/bin/build/closurebuilder.py: 797 sources scanned. public/closure-library/closure/bin/build/closurebuilder.py: Building dependency tree.. public/closure-library/closure/bin/build/closurebuilder.py: Compiling with the following command: java -jar ./library/closure-compiler/compiler.jar --js public/closure-library/closure/goog/base.js --js public/closure-library/closure/goog/debug/error.js --js public/closure-library/closure/goog/string/string.js --js public/closure-library/closure/goog/asserts/asserts.js --js public/closure-library/closure/goog/array/array.js --js public/closure-library/closure/goog/dom/classes.js --js public/closure-library/closure/goog/object/object.js --js public/closure-library/closure/goog/dom/tagname.js --js public/closure-library/closure/goog/useragent/useragent.js --js public/closure-library/closure/goog/math/size.js --js public/closure-library/closure/goog/dom/browserfeature.js --js public/closure-library/closure/goog/math/coordinate.js --js public/closure-library/closure/goog/dom/dom.js --js resources/client/app.js --compilation_level=ADVANCED_OPTIMIZATIONS public/closure-library/closure/bin/build/closurebuilder.py: JavaScript compilation succeeded. $ NODE_ENV=production node app.js
-
-
- -
-
追記:Cakefileをちょっと修正しました。深い階層のjsを更新したら、階層の数だけ無駄にcompileしてた。