LoginSignup
24
30

More than 5 years have passed since last update.

2017年4月版 jsライブラリ環境作成

Last updated at Posted at 2017-04-27

はじめに

jsライブラリを作成するにあたりwebpack周りを調べたメモです。

webpackの記事はググれば山のように見つかるのですが、
webpackは少し前に触って以来で、いつの間にかwebpack2がリリースされ記述方法が若干変わっており、それまで参考にした記事はwebpack1の書き方だったり、入門者にはなかなか戸惑う点がありました。

というわけで、またそういったノイズ記事を増やしてしまうことについてご了承ください。
2017年4月時点の内容ですので、近い将来また変わってくると思います。

ゴール

今回、やりたいことは以下です。

  • jsはBabelでes2015ベースで書く
  • jsはminifyさせる
  • cssはSassで書いて、jsにバンドルさせる
  • unitテストを書く
  • lintを入れる

環境

OS: macOS v10.12.4
node: v6.10.2
npm: v4.6.1

サンプル

最終的に出来たサンプルプロジェクトはこちらです。
https://github.com/darquro/webpack_sample

プロジェクト構造は以下のようになりました。

.
├── dist
│   ├── webpack_sample.js
│   ├── webpack_sample.js.map
│   └── webpack_sample.min.js
├── html
│   └── index.html
├── src
│   ├── css
│   │   └── style.scss
│   └── js
│       ├── friend.js
│       ├── main.js
│       └── person.js
├── test
│   ├── friend_test.js
│   └── mocha.opts
├── .babelrc
├── .eslintrc
├── package.json
├── postcss.config.js
└── webpack.config.js

動作確認はこちらで確認できます。
https://darquro.github.io/webpack_sample
ボタンをクリックイベント(js)とボタンのスタイル変更(css)だけ確認できるページです。

各npmパッケージのインストール手順の説明は省いていますので、 package.json から必要なものを確認してください。

"devDependencies": {
  "autoprefixer": "^6.7.7",
  "babel-core": "^6.24.1",
  "babel-eslint": "^7.2.3",
  "babel-loader": "^7.0.0",
  "babel-preset-es2015": "^6.24.1",
  "babel-preset-power-assert": "^1.0.0",
  "babel-register": "^6.24.1",
  "concurrently": "^3.4.0",
  "css-loader": "^0.28.0",
  "eslint": "^3.19.0",
  "eslint-config-airbnb-base": "^11.1.3",
  "eslint-plugin-import": "^2.2.0",
  "mocha": "^3.3.0",
  "postcss-loader": "^1.3.3",
  "postcss-smart-import": "^0.6.12",
  "power-assert": "^1.4.2",
  "precss": "^1.4.0",
  "style-loader": "^0.16.1",
  "webpack": "^2.4.1",
  "webpack-dev-server": "^2.4.4"
}

webpack

jsやcss、イメージなどをビルドしてまとめてくれるツール。デファクトスタンダードとなっている。
https://webpack.js.org/
webpack.png

webpack.config.jsに色々設定を記載します。
今回webpack 2.4.1を使用していますので、書き方がwebpack 1とは一部変わってます。
作成した設定はこのようになりました。

const Webpack = require('webpack');
const path = require('path');

const MINIFY = process.env.NODE_MINIFY === '1';

module.exports = {
  context: path.join(__dirname, '/src/js'),
  entry: {
    webpack_sample: './main.js',
  },
  output: {
    path: path.join(__dirname, '/dist'),
    filename: MINIFY ? '[name].min.js' : '[name].js',
    publicPath: '/assets/',
  },
  resolve: {
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules',
    ],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {},
        },
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          'postcss-loader',
        ],
      },
    ],
  },
  devtool: 'source-map',
  plugins: MINIFY ? [
    new Webpack.optimize.UglifyJsPlugin(),
  ] : [],
  devServer: {
    contentBase: 'html',
    port: 8080,
  },
};

順番に設定内容を説明していきたいと思います。
公式ドキュメントはこちら

context

context: path.join(__dirname, '/src/js'),

contextを指定すると、ビルドする対象のルートディレクトリを指定できます。
今回では src/js が起点となるディレクトリなので、このように設定しました。

entry

entry: {
  webpack_sample: './main.js',
},

entryには起点となるファイルの名前とパスを指定します。
今回のプロジェクトは webpack_sample という名前にしましたので、keyにwebpack_sample を指定し、パスはcontextで指定した場所から相対パスで起点となる main.js を指定しています。

output

output: {
  path: path.join(__dirname, '/dist'),
  filename: MINIFY ? '[name].min.js' : '[name].js',
  publicPath: '/assets/',
},

outputはビルドして出力先を指定します。
パスとしては dist ディレクトリに出力し、ファイル名は、 webpack_sample.js とminifyした webpack_sample.min.js を出力するようにしました。

MINIFY の変数については、npmのコマンドに、NODE_MINIFY=1 webpack を指定させ、一つの設定を流用しつつ、minifyする箇所だけ設定を分岐するようにしています。

publicPathについては、テスト時にローカルサーバーを立てて確認する際、htmlから参照できるパスを assets/webpack_sample.js というパスで参照出来るようにしています。

resolve

resolve: {
  modules: [
    path.resolve(__dirname, 'src'),
    'node_modules',
  ],
},

resolveを指定すると、jsでimportする際、相対パスで ../../foo/bar.js といったような面倒な指定に陥ることを防げます。 
今回では src ディレクトリが起点となっていますので、jsからは src 配下のディレクトリからのパスで指定することができるようになります。
node_modules は消してしまうとnpmライブラリが見れなくなるらしいので必ず指定します。

module.rules (js)

ここにloader系を指定します。
webpack1では、rulesではなくloadersという書き方ですので、webpack1の記事を見てるとハマります。

{
  test: /\.js$/,
  exclude: /(node_modules|bower_components)/,
  use: {
    loader: 'babel-loader',
    options: {},
  },
},

jsは今回Babelだけですので、babel-loaderを指定します。
書き方はbabel-loaderのドキュメントそのままです。
https://github.com/babel/babel-loader

optionに何も書いていないのは .babelrc に書いているからです。

module.rules (scss)

{
  test: /\.scss$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        importLoaders: 1,
      },
    },
    'postcss-loader',
  ],
},

css(scss)側は、 style-loadercss-loaderpostcss-loader の3つを使用しています。
書き方については、postcss-loaderのドキュメントのままです。
https://github.com/postcss/postcss-loader
webpackでSassのコンパイルを検索すると sass-loader を使用する記事が幾つか見つかりますが、
ベンダープレフィックスをつける autoprefixerpostcss-loader を使用する必要があり、移行したという記事がありましたので、 postcss-loader を使用しました。

devtool

devtool: 'source-map',

source-mapファイルを出力する設定です。
source-map 以外にも色々と指定があります。
https://webpack.js.org/configuration/devtool/#devtool

plugins

plugins: MINIFY ? [
  new Webpack.optimize.UglifyJsPlugin(),
] : [],

minifyする設定です。 MINIFY のフラグでそのままビルドしたjsとminifyしたjsの両方を出力するようにしています。
minifyするだけならこの指定をしなくても $ webpack -p の指定だけでminifyしてくれます。
将来的にminifyのオプションが何か増えるかもしれないという気もしたので、この指定にしました。

devServer

devServer: {
  contentBase: 'html',
  port: 8080,
},

webpack-dev-server コマンドでローカルサーバーを立ち上げるときの設定です。
ルートでhtmlディレクトリを指定しました。

Babel

Babelの設定は webpack.config.js にまとめることもできますが、 .babelrc ファイルに記載するようにしました。

{
  "presets": ["es2015", "babel-preset-power-assert"]
}

presetsで es2015babel-preset-power-assert を指定しています。
babel-preset-power-assert についてはunitテストで後述します。

PostCSS

PostCSSの設定もBabelと同じく webpack.config.js にまとめることもできますが、 postcss.config.js に記載しました。

/* eslint global-require: off */
module.exports = {
  plugins: [
    require('postcss-smart-import')({ /* ...options */ }),
    require('precss')({ /* ...options */ }),
    require('autoprefixer')({ /* ...options */ }),
  ],
};

Sassの記法を書けるようにするために、3つのプラグインを使用しています。
それぞれの説明については割愛します。
- postcss-smart-import: PostCSSで@import記法を使うためのプラグイン
- precss: PostCSSで変数定義などを使うためのプラグイン
- autoprefixer: PostCSSでベンダープレフィックスを使うプラグイン

sampleコード

さて、jsライブラリとしての基本的な設定周りが済んだので、今回のサンプルプロジェクトのコードについて説明します。
まずはPersonクラスとそれを継承したFriendクラスを用意しました。

class Person {
  constructor() {
    this.name = 'Person';
  }
  getName() {
    return `This is ${this.name}`;
  }
}
export default Person;
import Person from 'js/person';

class Friend extends Person {
  constructor(name) {
    super();
    this.name = name;
  }
  callName() {
    alert(`This is ${this.name}`);
  }
  getName() {
    return `This is ${this.name}`;
  }
}

export default Friend;

起点となるmain.jsは以下です。

import 'css/style.scss';
import Friend from 'js/friend';

window.Friend = Friend;

また、Sassファイルは以下です。

$btn-width: 300px;
$btn-hight: 150px;

button {
  &.btn {
    width: $btn-width;
    height: $btn-hight;
  }
}

テストのindex.htmlには、以下のようにボタンを配置して、そのクリックイベントとスタイルに今回のライブラリを使用するようになっています。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <script src="assets/webpack_sample.js"></script>
</head>
<body>
  <button class="btn" id="btn">click me</button>
  <script>
  var friend = new Friend('Taro');
  document.getElementById('btn').onclick = function(){
    friend.callName();
  };
  </script>
</body>
</html>

ビルドは、 webpack コマンドで、サーバー起動は webpack-dev-server を使用します。
そのままターミナルから実行するには、webpackのパスが通ってないので、

$ `npm bin`/webpack

とする必要がありますが、package.jsonのscriptにコマンドを登録しているので、それぞれ、

$ npm run build
$ npm run start

で出来るようにしました。

"scripts": {
  "build": "webpack && NODE_MINIFY=1 webpack",
  "start": "webpack-dev-server",
},

webpack-dev-server はファイルの更新も検知してくれます。

unitテスト

unitテストはテストフレームワークにmocha、アサーションライブラリにpower-assertを使用しました。

testディレクトリ配下がテストコードです。
まず mocha.opts を作成し、設定を記載します。

--recursive
--compilers js:babel-register

recursive の指定をすると再帰的にテストファイルを検索してくれるようです。
compilers js:babel-register はmochaでes2015で書くためのパッケージです。
また、power-assert側もes2015を解釈させるために、 babel-preset-power-assert が必要で、presetsを .babelrc で記載していました。

import assert from 'assert';
import Friend from 'js/friend';

describe('test Friend', () => {
  it('getName()', () => {
    const friend = new Friend('Taro');
    assert.equal('This is Taro', friend.getName());
  });
});

テストコードはFriendクラスのテストを一つ書きました。

package.jsonにtestコマンドを追加し、mocha側からのパスを解決させています。

"test": "NODE_PATH=$NODE_PATH:$PWD/src mocha"

lint

lintは以下のパッケージを使用しています。

  • eslint
  • eslint-plugin-import
  • eslint-config-airbnb-base

eslint-config-airbnb-baseはAirbnbのスタイルガイドをベースに少しカスタマイズをしました。

eslintの設定は .eslintrc ファイルに記述します。

{
  "extends": "airbnb-base",
  "env": {
    "es6": true,
    "node": true,
    "browser": true
  },
  "rules": {
    "import/no-unresolved": [2, { ignore: ["^js|^css"] }],
    "import/no-extraneous-dependencies": [0],
    "import/extensions": [0],
  },
  "globals": {
    /* MOCHA */
    "describe": false,
    "it": false,
    "before": false,
    "beforeEach": false,
    "after": false,
    "afterEach": false
  }
}

"browser": true とすることで、ブラウザのもつAPIをエラーにさせないようにしたり、
npmパッケージにないものもimportしてもエラーにならないように、今回のsrcディレクトリ配下は除外するようにしています。

また、mochaのAPIも除外しています。

ここだけでは拾いきれないファイル個別の設定は、ファイルの先頭にコメントを入れるなどで、eslintの固有設定もできます。
postcss.config.jsはglobal-requireをエラーにしないようにしています。

/* eslint global-require: off */

Atom

私はAtomをメインに使ってるので、Atom側のパッケージも入れます。

  • Linter
  • linter-eslint

をインストールすればeslintが動いてくれます。

さいごに

ゼロから環境構築まで色々調べながらでとても疲れました。
以上です!

24
30
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
24
30