abcdefGets

ゲッツ!

jspmについて私が知っていること

今更感がありますが、jspmというツールについて私が知っていることです。

jspmとは

jspmとはブラウザ向けのモジュール管理システムです。

pros

npm、bower等と違いES6で導入されたモジュールのローディングシステムを利用して、
ランタイムでトランスパイル等を行う仕組みを導入しており、これまでのwatch等を利用して行っていたトランスパイルの手間を省けるのが大きなポイントとなります。
また、jspm install <モジュール名> でモジュールをjspm、npm、githubの3つのレポジトリからインストール可能な上、
モジュールは変換が行われ、ブラウザから直接SystemJSを利用してロード可能な状態でインストールされます。
なので必要なモジュールがあれば、node・ブラウザ等を特に気にせずインストールできます(できないものもあります)。

cons

jspm_packagesというディレクトリを新たに作成するため、node_modulesとは違うモジュールを管理する必要があります。
つまり同じモジュールがnpmとjspmで必要になっても両方にインストールする必要があるということです。
また、常にコンパイルを行うため、モジュールサイズが増えるとロードにかなり時間がかかるようになります。
さらに、使うモジュールにもよりますが、インストールしただけでは動かないモジュールも存在します。

使い方

インストール

npm install jspm@beta -g
npm install jspm@beta --save-dev

jspm-cliはグローバル環境にjspmを入れていても、自動でローカルにインストールされたjspmを使用します。
なんで両方インストールします。

初期化

現在jspmは0.16がstableとなっていますが、0.17βも存在しており、
0.16と0.17ではかなり大きな変更点があるので、0.17βを基準に説明します。

jspmではjspm.config.jsという設定ファイルをコマンドライン、ブラウザ共に使用します。
中身はこんな感じのjsファイルになっています。

SystemJS.config({
  paths: {
    "npm:": "jspm_packages/npm/",
    "test-package/": "src/"
  },
  browserConfig: {
    "baseURL": "/"
  },
  devConfig: {
    "map": {
      "plugin-babel": "npm:systemjs-plugin-babel@0.0.18"
    }
  },
  transpiler: "plugin-babel",
  packages: {
    "test-package": {
      "main": "test-package.js",
      "meta": {
        "*.js": {
          "loader": "plugin-babel"
        }
      }
    }
  }
});

SystemJS.config({
  packageConfigPaths: [
    "npm:@*/*.json",
    "npm:*.json"
  ],
  map: {},
  packages: {}
});

ただ、このファイルを0から作成する必要はなくて、jspm initというコマンドで自動生成されます。

jspm initを実行すると

最初に以下のように聞かれます。

f:id:dobaw20:20170109043540p:plain

もちろんYESで生成します。

次に設定方法を選びます。

f:id:dobaw20:20170109043830p:plain

設定方法はQuick Standard Customの三種類ありますが、特に特別な設定がなければQuickで問題ありません。
なのでQuickで設定を行います。

次にパッケージ名を決定します。個々で決定したパッケージ名は各モジュールのプレフィックスとして使用されます。

f:id:dobaw20:20170109044221p:plain

次はbaseURLの設定です。
baseURL自体は非常にわかりづらいですが、これはpackage.jsonからソースディレクトリへの相対パスとなります。
ここも特に変更はせず.で問題ないです。
ま、問題あったら後で変えようの精神。

f:id:dobaw20:20170109044921p:plain

どんどん行きます。
次は設定ファイルへのパスです。これは上記のbaseURL内に含まれる必要があります。つまり、baseURLがここのパスを包含する形になります。
たぶん、jspm initをpackage.jsonディレクトリで実行していると思うので、基本的には.で問題ないです。
次行きましょう

f:id:dobaw20:20170109045356p:plain

お次はdevの設定です。dev環境で設定ファイルを分ける場合はYesにしてください。
設定ファイルを分けた場合は、--devオプションを付けてインストールしたモジュールの設定は全てdev環境の設定ファイルに記述されます。

f:id:dobaw20:20170109050101p:plain

めんどくさいのでNoで!

ほいで次はブラウザ向けのパスの設定です。ブラウザ上でファイルをロードするときのプレフィックスに使われます。

f:id:dobaw20:20170109050803p:plain

当然デフォルトのままで。

次はNode local projectへのパスという意味不明な設定ですが、これは凄く単純な設定でパッケージからディレクトリへのエイリアス設定です。
ここでいうと、test-packageという名前がsrcへのエイリアスとなります。まあ使わなくてもいいので気にしないでください。

f:id:dobaw20:20170109051245p:plain

srcのままで

次は上記のブラウザ版ですが、2つに分かれているユースケースが勉強不足によりわからないのでsrcのままで。
画像は省略します。

次はjspm_packagesへのパス設定。ここは変える人いないんじゃないかな。そのまま!

f:id:dobaw20:20170109051519p:plain

次はブラウザからjspm_packagesへのパス。そのまま!
画像もわすれた!

次はエントリーポイントとなるモジュールファイルへのパス。srcディレクトリ内にある必要があります。

f:id:dobaw20:20170109051751p:plain

次はトランスパイラーの設定。typescript選ばないならbabel一択でいいのでは?typescriptの場合は別途tsをインストールすれば良いので、
一旦babelで

f:id:dobaw20:20170109051952p:plain

やっと終わった。

成功すると以下のようなログが出ます。多分

f:id:dobaw20:20170109052124p:plain

依存のインストール

jspm install <module name> [options]

基本はこれ。例としてreactをインストールする場合

jspm install react

これでreact.jsがインストールされます。

しかしこれでインストールできないモジュールもあります。
デフォルトではjspmはモジュールをjspmレジストリから探索します。しかし、jspmレジストリにあるモジュールはまだ数が少ない為、
対応していないモジュールも数多くあるのです。
その場合は

jspm install npm:js-cookie

というようにnpm:をプレフィックスとして付与します。
jspm組み込みのレポジトリとしてnpmの他にgithubもあります。
またレポジトリは拡張も可能で自分オリジナルのレポジトリにも対応可能です。

まともにインストールできないモジュール

npmのScoped Packageはjspmの仕様のようなのですが、期待したとおりにはインストールされません。
例えばcyclejsのcoreをインストールする場合

jspm install npm:@cycle/core

になるのですが、実際にインストールされる依存名はcoreになります。
つまり、namespaceが省略されてしまうのですね。
というわけでこのような場合はalias名を設定します。

jspm install @cycle/core=npm:@cycle/core

このように変更すれば@cycle/coreとしてモジュールがインストールされます。

使う

インストールした依存モジュールは普通にimportして使えます。

import * as react from 'React';

しかし困ったことにimport方法はやってみるまでわからないのです。
なのでマジで適当に書いてランタイムエラーがでるかチェックします… Reactは上記のimportでうまくいきますが、

import React from 'react';

だと駄目です…
なのでほんとにインストールしたモジュールの実装方法次第です。

その辺の情報は以下に詳しい
Babel と TypeScript の ES6 modules の import の解釈の違い

ビルドする

さて開発するときは全てのモジュールをロードして、ランタイムでコンパイルして使えばよかったのですが、production環境でそれはいただけないですよね。
なのでミニファイしろ。
ミニファイにはsystemjs-builderなるものを使います。

多分gulpのやつもあるはずなのだが、自分で作ってしまっているのでいつもそれを使っています。
調べたらあった。
Gulp SystemJS Build Tool

事前準備
npm isntall through2 async gulp-util lodash vinyl colors --save-dev

ソース

/**
 * @fileoverview
 * @author Taketoshi Aono
 */

'use strict';

const through = require('through2');
const fs = require('fs');
const Builder = require('systemjs-builder');
const async = require('async');
const path = require('path');
const gutil = require('gulp-util');
const _ = require('lodash');
const Vinyl = require('vinyl');


module.exports = function(options) {
  const builder = new Builder('./');
  const files = [];
  let mergedConfig = {};

  const configFiles = Array.isArray(options.configFile)? options.configFile: [options.configFile];
  configFiles.forEach(configFile => {
    const configStr = fs.readFileSync(configFile, 'utf8');
    Function('SystemJS', configStr)({config: function(config) {
      config.baseURL = options.baseURL;
      if (options.replace && config.map) {
        _.forIn(options.replace, function(v, k) {
          config.map[k] = v;
        });
      }
      mergedConfig = _.merge(mergedConfig, config);
    }});
  });
  builder.config(mergedConfig);

  function transform(file, encoding, callback) {
    files.push(file);
    callback();
  }


  function flush(callback) {
    const self = this;
    async.forEachSeries(files, function(file, next) {
      require('colors');
      const src = files[0].history[0];
      console.log(('Strart build process ').yellow.bold + src);
      builder.buildStatic(src, options.build || {})
        .then(function(output) {
          file.contents = new Buffer(output.source);
          if(options.build.sourceMaps === true) {
            self.push(new Vinyl({
              cwd: file.cwd,
              base: file.base,
              path: file.path + '.map',
              contents: new Buffer(output.sourceMap.toString())
            }));
          }
          self.push(file);
          next();
        })
        .catch(function(err) {
          console.log('Build error');
          console.log(err);
          next();
        });
    }, callback);
  }

  return through.obj(transform, flush);
};

これをplugin/build.jsに保存して、 

gulp.task('minify', () => {
  const build = require('./plugins/build');
  return gulp.src('lib/main.js')
    .pipe(build({
      configFile: ["jspm.config.js"],
      build: {
        minify: true,
        sourceMaps: false,
        mangle: true,
        browser: true,
        globalDefs: {
          DEBUG: false
        }
      }
    }))
    .pipe(gulp.dest("dist"));
});

みたいな感じで使ってます。

まとめ

インストール

npm install jspm@beta -g
npm install jspm@beta --save-dev

初期化

jspm init

モジュールのインストール

jspm install <何かモジュール>

ミニファイ

gulp minify

後半雑になっちゃいましたが仕方ないと思います。
だって大変なんだもん。

以上です。