春ですね。^ ^
なにか新しいことをはじめたくなりますね。^ ^
前回はExpressの導入を行いました。
今回は、webpackを導入します。
ゴールまでの道のり
- VPSサーバ借りる
- Ubuntu入れる
- Nginx入れる
- nodebrew、Node.js入れる
- nmp入れる
- git入れる
- Express入れる ← 前回はここまで
- webpack入れる ← 今回はここ!
- サイトつくる
今回のリポジトリ
手っ取り早くwebpackを試してみたい方は、リポジトリをcloneして、
npm install
npm start
を試してみてください。
https://github.com/kimizuka/webpack-sample-jsgithub.com
webpackとは
開発時につかったモジュールを束ねてくれる、モジュールバンドラーです。
公式サイトのディスクリプションにもこう書いてあります。
webpack is a module bundler. It packs CommonJs/AMD modules i. e. for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand.
webpackはモジュールバンドラーだぜ。それはCommonJs / AMDモジュールをパックすることができるんだ。例えばブラウザのためにね。だから、コードベースを複数のバンドルに分割することができる。そう。バンドルは必要に応じてロードできるしね。
なるほど。いけてるモジュールバンドラーってことですね。 ^ ^
つまりどういうこと?
サーバサイドのJavaScript、node.jsにはrequireというとても便利な機能があります。(余談ですがTitaniumにもありました。require。)
これによって、容易にモジュールを読み込むことができたわけです。
しかし、フロントエンドのJavaScriptにはこれまで同様の仕組みがなく、
scriptタグで読み込んでいたわけです。ライブラリを。
例えば、jQueryを読み込む場合は、
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
という感じに。
しかし、もしもフロントエンドでrequireがつかえたならば、
const $ = require("jquery");
という感じで読み込めるわけです。^ ^
ここまで読んで、「フロントエンドでもrequireがつかえたらいいなぁ」と思ったあなたに、Good News と Bad News があります。
GoodNewsは、ES2015でimportとexportによるモジュール構文が策定されたこと。
これによって、フロントエンドでもrequireと同じような機能がつかえるようになりました。
先程のjQueryの例だと、
import $ from "jquery";
という感じで読み込めるようになったんですね。^ ^
詳しくは、こちら を見てみましょう。
Bad newsとしては、import文をサポートしているブラウザが、Edge、Mobile Safariぐらいしかないこと。
現状ではまだまだつかえる段階に無いわけです。
しかし、そんな現状でもフロントエンドでimportをつかう方法があるのです。
それは、依存関係を精査して1つのファイルにconcatしてしまうこと。
人間業ではありませんが、コンピュータならできるはず。
そんな感じのツールで有名なのが、Browserify。
詳しくは説明しませんが、requireをつかったJavaScriptファイルをいい感じに1つに結合してくれます。
そして、でました、webpack。
webpackはrequireをつかったJavaScriptファイルどころか、CSSファイルや画像もJavaScriptとして読み込めるようにしてしまうツールなのです。
しかし、僕はCSSはCSSファイルとして管理したいし、画像は画像で管理したいタイプなので、
今回はJavaScriptの結合ツールとして活用することにします。
webpackのセットアップ
前置きがとても長くなりました。では具体的にはどうすれば良いのか。すすめていきましょう。
まずは前回までに構築したExpressの環境を忘れて、まっさらな状態で試してみます。
node.js、npmが入っていることを前提とします。
適当なディレクトリをつくって移動
mkdir webpack-sample-js
cd webpack-sample-js
ソース用のディレクトリと書き出し用のディレクトリをつくる
mkdir _src
mkdir _src/js
nkdir public
mkdir public/js
package.jsonつくる
npm init -y
webpackをインストール
npm install --save webpack
babelをインストール
npm install --save babel-core
npm install --save babel-preset-es2015
.babelrcにbabelのバージョンを記載
vim .babelrc
.babelrc
{ "presets": ["es2015"] }
babel-loaderをインストール
npm install --save babel-loader
コンフィグファイルをつくる
vim webpack.config.babel.js
webpack.config.js でも良いんですが、 webpack.config.babel.js という名前にしておくと、ES2016で書くことができます。
webpack.config.babel.js
module.exports = [{ // 後々のことを考えて配列にしておく entry: { index : __dirname + "/_src/js/index.js" }, output: { path: __dirname + "/public/js/", filename: "[name].bundle.js" }, module: { rules: [{ test: /\.js$/, exclude: [ /node_modules/ ], use: [{ loader: "babel-loader", options: { presets: [ "es2015" ] } }], }] } }];
_src/js/index.jsを用意
vim _src/js/index.js
_src/js/index.js
(() => { alert("HELLO."); })();
とりあえずアローファンクションをつかってみます。
package.jsonにタスクを追加
npm start で webpack --watch を実行するようにします。
_src/js/のファイルを更新する度に、public/js/以下に書き出されます。
vim package.json
package.json
{ "name": "webpack-sample-js", "version": "1.0.0", "main": "app.js", "license": "MIT", "dependencies": { "babel-core": "^6.24.1", "babel-loader": "^6.4.1", "babel-preset-es2015": "^6.24.1", "webpack": "^2.4.1" }, "scripts": { "start": "webpack --watch" } }
実行!
npm start
public/js/index.bundle.js
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; (function () { alert("HELLO."); })(); /***/ }) /******/ ]);
書き出されました。トランスコンパイルされていますね。
せっかくなので、import / exportをつかってみましょう。
vim _src/js/index.js
_src/js/index.js
import hello from "./hello.js"; hello.say();
vim _src/js/hello.js
_src/js/hello.js
export default class Hello { static say() { alert("HELLO."); } }
public/js/index.bundle.jsを確認すると、
public/js/index.bundle.js
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _hello = __webpack_require__(1); var _hello2 = _interopRequireDefault(_hello); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log(_hello2.default); _hello2.default.say(); /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Hello = function () { function Hello() { _classCallCheck(this, Hello); } _createClass(Hello, null, [{ key: "say", value: function say() { alert("HELLO."); } }]); return Hello; }(); exports.default = Hello; /***/ }) /******/ ]);
ばっちりです。と言いたいところですが、もはや肉眼ではわからないので確認用ページをつくって確認します。
vim public/index.html
public/index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>HELLO</title> </head> <body> <script src="./js/index.bundle.js"></script> </body> </html>
で、ブラウザにpublic/index.htmlを開いてみると、
しっかりアラートが表示されたので良しとしましょう。
で、どうせ肉眼で確認できなくなるのであればMinifyしたいと思うところ。
webpack.config.babel.jsを編集して、webpack.optimize.UglifyJsPluginを追加します。
vim webpack.config.babel.js
webpack.config.babel.js
import webpack from "webpack"; // 追加 module.exports = [{ entry: { index : __dirname + "/_src/js/index.js" }, output: { path: __dirname + "/public/js/", filename: "[name].bundle.js" }, module: { rules: [{ test: /\.js$/, exclude: [ /node_modules/ ], use: [{ loader: "babel-loader", options: { presets: [ "es2015" ] } }], }] }, plugins: [ new webpack.optimize.UglifyJsPlugin() // 追加 ] }];
で、npm startしなおすと、
public/js/index.bundle.js
!function(e){function n(r){if(t[r])return t[r].exports;var u=t[r]={i:r,l:!1,exports:{}};return e[r].call(u.exports,u,u.exports,n),u.l=!0,u.exports}var t={};n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=1)}([function(e,n,t){"use strict";function r(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var u=function(){function e(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(n,t,r){return t&&e(n.prototype,t),r&&e(n,r),n}}(),o=function(){function e(){r(this,e)}return u(e,null,[{key:"say",value:function(){alert("HELLO.")}}]),e}();n.default=o},function(e,n,t){"use strict";var r=t(0),u=function(e){return e&&e.__esModule?e:{default:e}}(r);console.log(u.default),u.default.say()}]);
てってれー。無事にMinifyされました。
すごく長くなったんで、SCSSをCSSにコンパイルするところは次回にしましょう。
最終的なファイル構成
webpack-sample-js/
└ _src/
└ js/
└ hello.js
└ index.js
└ nodemodules/
└ ...
└ ...
└ package.json
└ public/
└ index.html
└ js/
└ index.bundle.js
└ webpack.config.babel.js
おまけ
webpackのことをしらべると、webpack1の情報とwebpack2の情報が混ざって出てきてしまうのですが、
webpack.babel.js もとい webpack.config.babel.js で、moduleを指定するところをみると1なのか2なのかがまるわかりです。
module: { loaders: [{...
と、loadersで書いてあるのが1、
module: { rules: [{...
と、rulesで書いてあるのが2です。
僕は2で書くことを心がけました。
そしてこのサイトが大変参考になりました。英語なので雰囲気しかわかりませんでしたが。
blog.madewithenvy.com