LoginSignup
16
16

More than 5 years have passed since last update.

jspmを使ってシンプルな構成でES6アプリをつくる(ついでにElectron)

Last updated at Posted at 2015-08-25

成果物

ss_20150825.png

はじめに

「ES6」とタイトルに書きながら TypeScript です。
まあES6スーパーセットだし、設定ファイルをちょっと書き換えれば traceur も babel も使えるんで。

動機

  • ぼくのかんがえたさいきょうのElectron を読んでから、Electron をさわってみたくなった
  • 仕事で main-bower-files で取ってきたモジュール群を gulp-inject し、production 向けには useref して js を uglify、scss/less を csso などとしていたが、こんな自らのラビリンスエンジニアっぷりが心底嫌になった

つまりこういうことだ。

gulp を使う際は、

  1. gulp.src で対象ソースを選択
  2. pipe でコンパイルや minify など、やりたいことをやる
  3. pipe で gulp.dest で配置

のようなタスクを作ると思うが、これに依存関係解決が入ると相当な複雑さになる。

ぼくのかんがえたさいきょうのElectron では、bower は専ら bootstrap などのスタイル系のものの管理だけで利用していて、開発時・パッケージング時ともに main-bower-files で集められて html に inject される。

js は、開発時は babel で transpile しながら アウトプットを watch、パッケージングの際は packages.json の dependencies に書かれているものを自前の gulp タスクで集めてきて browserify で minify ということをやっている。

この中で、

  • main-bower-files や自前のタスクで依存ファイルを集めてくる
  • 依存ファイル を html に inject
  • アウトプットを watch

の部分をどうにかしたい。なぜなら、それこそが gulp タスク群が迷宮化する原因だからだ。

そこで jspm

元々 Angular2 は今回の Electron とは関係なく興味を持っていた。Electron で遊ぶために前述の「さいきょうのElectron」を、View のみをターゲットとする React から Angular1.x or Angular2 にカスタムする予定だったが、本家の 5 Min Quickstart に出てくる jspm ってなんだろうと調べていくうちに、es6 や TypeScript をランタイムでトランスパイルしながら表示できるということを知り、「これだ!」と感じて手を出すことになる。

メリットは、以下のように思われる

  • gulp でパズルを一切作ることなく、LiveReload 可能な開発環境を作ることができる
  • production 向けには、jspm bundle-sfx という、予めアプリで import している(つまり利用している)ものを芋づるで引っ張ってきて 1 ファイルにバンドルしてくれるという機能1がある

ボイラープレート詳解

開発時

今回作成したボイラープレートには個人的な使い勝手からいくつかタスクを足しているので数は多くなっているが、jspm + Angular2 に限れば、実際に開発を行うにあたって必要最小限のタスクは以下のみ。

gulp/serve.js
gulp.task('serve', function() {
  // start http server
  gulp.src(conf.paths.src)
    .pipe($.webserver({
      host: '0.0.0.0',
      port: 3000,
      livereload: true,
      open: 'http://localhost:3000/index.dev.html'
    }));
});

つまり gulp からは webserver だけ扱えばよい。あとは jspm がよしなにやってくれる。
gulp でのコンパイル不要、ソースディレクトリそのものである conf.paths.src をドキュメントルートにしているなど、TypeScript を利用しながらも驚異のシンプルさ。

electron-connect も使う場合はこちら。

gulp/build.js
/**
 * Transpile scripts for Electron
 *  src: js(es6) files for Electron
 *  dest: serve dir
 */
gulp.task('transpile:electron', function () {
  return gulp.src(conf.paths.srcElectron + "/**/*.js")
    .pipe($.plumber(conf.errorHandler))
    .pipe($.sourcemaps.init())
    .pipe($.babel({stage: 2}))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest(conf.paths.serve))
  ;
});
gulp/serve.js
/**
 * privides an electron server that loads an angular app run by JSPM
 *  root: serve dir
 *  approot: src dir for application
 *  watching:
 *      - src files for Electron
 *      - all ts,html,less files under src dir
 */
gulp.task('serve:electron', ['transpile:electron'], function () {
  // switch the pathToApp for BrowserWindow.loadUrl(url) according to the value of NODE_ENV
  $.env({
    vars: {
      APP_RELATIVE_PATH: path.relative(conf.paths.serve, conf.files.indexFileDev)
    }
  });

  var electron = electronServer.create({
    path: conf.paths.serve + "/main.js"
  });
  electron.start();

  // watch electron src and re-transpile
  gulp.watch([conf.paths.srcElectron + '/**/*.js'], ['transpile:electron']);
  // watch serve dir and restart electron
  gulp.watch([conf.paths.serve + '/**/*.js'], electron.restart);
  // watch app src and reload electron
  gulp.watch([
    conf.paths.src + '/*.ts',
    conf.paths.src + '/*.html',
    conf.paths.src + '/*.less',
    conf.paths.src + '/!(jspm_packages)/**/*.ts',
    conf.paths.src + '/!(jspm_packages)/**/*.html',
    conf.paths.src + '/!(jspm_packages)/**/*.less',
  ], electron.reload);
});

後述するが、Electron 関連のソースは ぼくのかんがえたさいきょうのElectron と同様の手法で取り扱っているので、ここは babel が走る(transpile:electron タスク)。conf.paths.serve という、テンポラリなディレクトリを watch しなければいけないのも、今は慣れつつあるがラビリンスの滲出。

Production 向け

以下は、gulp/build.js からの抜粋。

gulp/build.js
/**
 * create Self-Executing (SFX) Bundles
 */
gulp.task('build:jspm:bundle-sfx', $.shell.task([
  // create command string with array.join()
  ['jspm bundle-sfx',
    paths.jspmBundleTargetModule,
    paths.jspmBundleOutFile,
    (process.env.JSPM_SFXOPTS_SKIP_SOURCE_MAPS == true) ? '--skip-source-maps' : '',
    (process.env.JSPM_SFXOPTS_MINIFY == true) ? '--minify' : ''
  ].join(' ')
]));

/**
 * build, minify and locate html files
 *  src: html files for application
 *  dest: dist dir
 */
gulp.task('build:html', function() {
  return gulp.src([
    // select all html files
    conf.paths.src + "/**/*.html",
    // exclude
    "!" + conf.files.indexFileDev
  ])
    .pipe($.plumber(conf.errorHandler))
    .pipe($.minifyHtml({
      empty: true,
      spare: true,
      quotes: true,
      conditionals: true
    }))
    .pipe(gulp.dest(conf.paths.dist));
});

/**
 * build, minify and locate style files
 *  src: less for application
 *  dest: dist dir
 */
gulp.task('build:style', function() {
  return gulp.src(conf.paths.src + "/index.less")
    .pipe($.plumber(conf.errorHandler))
    .pipe($.less())
    .pipe($.cssmin())
    .pipe(gulp.dest(conf.paths.dist));
});

build:jspm:bundle-sfx タスクは、少し見にくいが jspm コマンドの文字列を join で生成して shell に投げているだけ。

html, less はコンパイルして conf.paths.dist に配置しているだけで、inject や usemin は行っていないので比較的見やすいのではないかと思う。これらは、別途用意した production 用の index.html から静的なパスで参照されている。

asset に関するタスクはいらなかったので作らなかったが、もし画像ファイルを使いたいという場合も

hoge.ts
import 'some/image.png!image'

みたいにイメージを js に bundle できる。

惜しいのは build:style を作ってしまったことで、これは現在の jspm には、preprocess が必要なスタイルファイル(scss とか less とか stylus とか)用の loader がないことに起因する。css だけならイメージと同じように import 可能。
そのため今回作成したボイラープレートでは、開発環境では less.js を読み込んでクライアントサイドで transpile、production 向けには断腸の思いで上述の build:style を書いた。

この問題は、[QUEST] Less Plugin #241 にあるように、コミュニティによるプラグインの開発が待たれる。webpack くらい loader が豊富になれば更にタスクが減るだろう。

その他

serve:* タスクについて

gulp/serve.js を見ていただければわかると思うが、serve: 系のタスクをいくつか作っている。

serve は、素の jspm で動くので、ブラウザリロードの度に読み込むファイル数がとにかく多い。
逆に serve:dist は、production 用の構成を作っているので、ブラウザリロードは高速だが TypeScript の変更をトリガに jspm bundle-sfx が走るとかなり待たされる。ただし、html, less の編集だけならすこぶる早い。

という特徴を利用して、TypeScript の編集メインの作業なら serve、html や lessの編集メインなら serve:dist を使えばよい。

serve:electronserve と同じ戦略で、electron によってラップされる。
serve:dist:electron は最終確認用。watch 自体していない。

serve:dist タスクで今後やるかも?

まだ本格的なアプリ開発を行っていないため jspm で動的ロードする serve タスクでも十分な速度で LiveReload できてはいるが、今後規模が大きくなれば「部分的な再コンパイル」が必要になると思われる。

理想は、変更検知とバンドルファイルを3つほどにわけること。

  • Angular2 や moment のような外部のライブラリは vendor.js として、watch の対象から外す
  • アプリの core 部分は、単体で bundle する
  • アプリの core 部分以外(Angular2 ならコンポーネント類?)も、単体で bundle する

こうするとおそらく serve:dist しながら快適に TypeScript も編集できる。
ただ当然タスクのシンプルさを犠牲にすることになるが、そこは jspm の力をフルに使いたい。

$ jspm bundle-sfx vendor + vendorDev ./dist/vendor.js
$ jspm bundle-sfx appcore ./dist/vendor.js
$ jspm bundle-sfx app - appcore - vendor ./dist/vendor.js

のように arithmetic なバンドリングが可能なので、bower と inject を駆使するよりはずっとシンプルに書けるはず。
気が向いたら挑戦しようと思う。


  1. 正確には、jspm が依存している systemjs の機能 

16
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
16