ちょっとした処理なら JavaScript でも事足りるのですが、込み入った処理を書こうとすると保守性が残念なことになりがちです。TypeScript を導入してスパゲッティ化を抑えていきたいと思います。
今回実現することを簡単に言うと、 gulp で eslint を通して OK だったら webpack で ts を js に変換します。もちろんビルドには node.js が必要ですので、 node.js v10 を入れておく必要があります。Windows の場合は docker コンテナ立ち上げて実行した方が楽だと思います。
フロントのみの対応で、管理画面は対応しません。カスタマイズで導入するケースを想定しています。出力する js ファイルは ES5 に準拠したものにします。最近のブラウザであればこれで問題ないはずです。
パッケージのインストール
最初にもろもろインストールします。
npm i -D typescript webpack webpack-stream ts-loader es6-promise @types/jquery gulp-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier eslint-plugin-prettier eslint-config-prettier
typescript は名前の通り ts を js に変換するメインのパッケージです。
webpack は複数のファイルを1つの js ファイルにまとめてくれるもので、ts-loader は ts を webpack に取り込む際に使用します。webpack-stream は gulp から webpack を呼び出す際に使用します。
es6-promise は IE で Promise 使えるようにするために導入します。Ajax やイベント周りの可読性向上のためには Promise は外せません。
@types/jquery は jQuery の型を定義しているパッケージです。EC-CUBE では script タグで jQuery を読み込んでいますが、それを ts 側で利用するにはこの型定義が必要です。
その他は eslint 系です。コードがコーディング規約に沿って書かれているかチェックするツールです。
設定ファイル(webpack)
webpack.config.js ファイルを新規作成し、プロジェクトのルートフォルダに置きます。
'use strict';
var path = require('path');
var webpack = require('webpack');
var env = process.env.NODE_ENV || 'development';
let config = {
mode: env,
entry: {
app :'./html/template/default/assets/ts/app.ts'
},
output: {
path: path.resolve(__dirname, "html/template/default/assets/js"),
filename: '[name].js',
library: 'App', // グローバルスコープで被らない名前を付ける
libraryTarget: 'var'
},
resolve: {
modules: [
"node_modules"
],
extensions:['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
exclude: path.resolve(__dirname, "vendor")
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV' : JSON.stringify(env)
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.ProvidePlugin({
Promise: 'es6-promise',
}),
],
optimization: {
minimize: false
}
};
if (env === 'production') {
config.output.filename = '[name].min.js';
config.optimization.minimize = true;
} else {
config.devtool = 'inline-source-map';
}
module.exports = config;
/html/template/default/assets/ts/app.ts
を /html/template/default/assets/js/app.js
に変換するよう設定しています。NODE_ENV 環境変数が production の場合は minify した app.min.js を出力します。
この設定の場合、グローバルに App 変数が作成され、その中に app.ts で export した物が入ります。HTML などからは App.~ でアクセスできます。
設定ファイル(tsconfig)
次に tsconfig.json を新しく作成します。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["dom", "es2015.promise", "es5"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["html/template/default/assets/ts/**/*.ts"],
}
ts で使えるライブラリは ES5 と DOM を基本とし、追加で Promise を使えるようにしておきます。es6-promise プラグインが polyfill するので、ブラウザ上でも動作します。
設定ファイル(eslintrc)
続いて .eslintrc.js を作成します。
module.exports = {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
"plugins": [
"@typescript-eslint"
],
"env": { "es6": true },
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
// お好みのルール
"prettier/prettier": ["error", {
"singleQuote": true, // 文字列はシングルクォートで囲いたい
}],
}
};
リンターはどの言語でもそうですが、基本は標準の設定を使用し、細かい部分を好みで修正する方が楽だと思います。1からルール作るのは大変です。
設定ファイル(gulp)
最後に gulpfile.js を修正します。このファイルは既に存在するので、以下を追記してください。
const webpack = require('webpack');
const webpackStream = require('webpack-stream');
const webpackConfig = require('./webpack.config.js');
const eslint = require('gulp-eslint');
var tsSource = './html/template/default/assets/ts/**/*.ts';
var jsDestination = './html/template/default/assets/js';
gulp.task('webpack', function () {
gulp.src([tsSource])
.pipe(eslint({ useEslintrc: true }))
.pipe(eslint.format())
.pipe(webpackStream(webpackConfig, webpack).on('error', () => {
this.emit('end');
}))
.pipe(gulp.dest(jsDestination))
});
gulp.task('watch', ['webpack'], function () {
gulp.watch(tsSource, ['webpack'])
});
webpack タスクと watch タスクを追加しています。
webpack タスクは eslint を実行し、エラーがあればエラーを表示して終了します。正常であれば js ファイルを出力します。
watch タスクは ts ファイルの変更を監視し、変更があるたびに webpack タスクを実行します。
ts ファイル本体
/html/template/default/assets/ts/app.ts
をエントリポイントにしたので、そのファイルを作成し処理を書きます。内容については今回は割愛します。
ビルド
ビルド用のコマンドとして、 package.json に以下のカスタムスクリプトを追加しておきます。
{
"scripts": {
"ts:dev": "NODE_ENV=development gulp webpack",
"ts:build": "NODE_ENV=production gulp webpack",
"ts:watch": "NODE_ENV=development gulp watch"
}
}
開発中は npm run ts:dev
、本番用ファイルを生成する場合は npm run ts:dev
を実行します。
ReferenceError: internalBinding is not defined
エラーが出た場合は npm i natives@1.1.6 -D
を実行してからもう一度コマンドを叩いてください。
成功した場合は /html/template/default/assets/js
ディレクトリに app.js または app.min.js が生成されます。
js ファイルの読み込み
/default/default_frame.twig
の js を読み込んでいる箇所の最後に、以下を追記します。
{% if app.environment == 'dev'%}
<script src="{{ asset('assets/js/app.js') }}"></script>
{% else %}
<script src="{{ asset('assets/js/app.min.js') }}"></script>
{% endif %}
最後の設定
上記で追加したファイルは念のため .htaccess または web.config 等でアクセスできないように設定してください。URL を直接打つと見えてしまう場合があります。
まとめ
フロントエンドの開発規模が大きくなりコード量が多くなると、可読性を確保するためにファイルを分割したくなります。しかし Web の世界では読み込むファイル数が多くなるとその分通信が発生する(少なくとも HTTP/3 が普及するまではオーバーヘッドがそれなりに大きい)ため、ファイルは可能な限りまとめることが要求されます。webpack はこのファイルをまとめる動作を自動化できるので、ページの表示速度を重視する場合は必須です。TypeScript の静的型付けも、早い段階で不具合を発見する助けになります。
設定ファイルが多く導入は複雑ですが、大量の js を書くことが見えているならば、これらを導入するメリットは大きいです。参考にしていただければ幸いです。
今後
読み込むファイル数を減らしたいので、理想を言えば EC-CUBE 標準のスクリプト&CSSと jQuery も webpack でまとめたいところですが、テストも考慮すると手を付けづらいところではあります。
さらに言うと jQuery を使わずモダンなフロントエンドフレームワークを導入してみたいですが、彼らを導入すると SPA になりがちなので、 twig との共存が難しい気がします。もはや PHP は JSON を返すだけで十分な存在なのでしょうか。
参考にさせていただいたページ
- 脱TSLintして、ESLint TypeScript Plugin に移行する
- webpackでPromiseを使用する(IE対策)
- Prevent errors from breaking / crashing gulp watch
- gulp – internalBinding is not defined
各パッケージのバージョン
- EC-CUBE 4.0.3
- TypeScript 3.9.3
- webpack 4.43.0
- webpack-stream 5.2.1
- ts-loader 7.0.4
- es6-promise 4.2.8
- @types/jquery 3.3.38
- gulp-eslint 6.0.0
- @typescript-eslint/eslint-plugin 3.0.0
- @typescript-eslint/parser 3.0.0
- prettier 2.0.5
- eslint-plugin-prettier 3.1.3
- eslint-config-prettier 6.11.0