Windowsで使えるJScriptはES3ぐらいまでしか使えず、非常につらいです。
まあそもそもJscriptを書くこと自体あまりないと思いますが。
今回はbabelとwebpackを使ってES2015+でJScriptを書けるようにしてみました。
準備
パッケージインストール
$ npm i -D json3 babel-core babel-loader babel-polyfill babel-preset-latest \
babel-plugin-transform-es3-member-expression-literals babel-plugin-transform-es3-property-literals \
babel-plugin-transform-jscript \
imports-loader webpack@1.13.2
webpackは1.13.2を使用します。
2系はie8のサポートを切るらしいので、jscriptが動かないと思います。
また、1.13.3以降は依存しているUglifyJS 2が2.7系で、圧縮したコードがie8でうまく動きません。
UglifyJS 2のバグが直れば1.14でも大丈夫だと思います。
JScriptではJSONオブジェクトが存在しないため、json3をインストールして使えるようにします。
他はwebpackとかbabel関連のパッケージです。
webpack.config.js
module.exports = {
entry: __dirname + '/script.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query:{
presets: ['latest']
}
},
{
test: /\.js$/,
loader: 'babel',
query: {
plugins: [
'transform-es3-member-expression-literals',
'transform-es3-property-literals',
'transform-jscript',
]
}
}
]
},
resolve: {
extensions: ['', '.js']
}
};
自分で書くコードにはbabel-preset-latest
を適用します。
読み込まれるすべてのjsにはtransform-es3-member-expression-literalsとtransform-es3-property-literals、transform-jscriptを適用します。
JScriptでは以下のような、予約語のプロパティを使ったコードはエラーになってしまいます。
var hoge = {default: 'hoge'}; // エラー
WScript.Echo(hoge.default); // エラー
そのため、transform-es3-member-expression-literalsとtransform-es3-property-literalsを適用し、
以下のようなコードに自動的に変換されるようにします。
var hoge = {'default': 'hoge'}; // OK
WScript.Echo(hoge['default']); // OK
transform-jscriptはJScriptのバグを回避してくれるものらしいです。
timers.js
JScriptにはsetTimeoutなどがありません。
しかし、htmlfile
オブジェクトのsetTimeoutが使えるようですので、無理やり使えるようにしてあげます。
webpackでビルドされてもグローバル空間に展開されるようにvar
などの修飾子は付けていません。
/*global ActiveXObject*/
const htmlfile = new ActiveXObject('htmlfile');
const window = htmlfile.parentWindow;
setTimeout = function wrapSetTimeout(f, t) {
return window.setTimeout(f, t);
};
clearTimeout = function wrapClearTimeout(id) {
return window.clearTimeout(id);
};
setInterval = function wrapSetInterval(f, t) {
return window.setInterval(f, t);
};
clearInterval = function wrapClearInterval(id) {
return window.clearInterval(id);
};
script.js
require('./timers.js');
JSON = require('imports?this=>window,this=>global!json3');
require('babel-polyfill');
(() => {
const text = JSON.stringify([1, 2, 3].map(x => x * 10)).repeat(2);
WScript.Echo(text); // [10,20,30][10,20,30]
})();
先ほどのtimers.js
を読み込んでいます。
また、polyfillを有効にするためにbabel-polyfillを読み込んでいます。
JSONオブジェクトは変数に入れて使います。
requireするときにimports-loaderの機能を使っています。
thisをwindowやglobalに設定して、ビルドインオブジェクトにJSON関連のメソッドを追加できるようにします。
また、グローバル空間に配置することで、他のライブラリからJSONが使えるようにしてあげます。
babel-polyfill
ではすべてのpolyfillを読み込むため、ファイルサイズが大きくなります。
もし使わないpolyfillは省きたい場合、core-js
のパッケージを読み込むようにするといいと思います。
$ npm i -D core-js
JSON = require('imports?this=>global!json3');
require('core-js/es5');
require('core-js/es6/object');
require('core-js/es6/function');
require('core-js/es6/array');
require('core-js/es6/string');
require('core-js/es6/promise');
1つのファイルにまとめる
$ $(npm bin)/webpack
dist/bundle.jsに出力されます。
npm run scriptに書いておくと便利だと思います。
{
"scripts": {
"build": "webpack",
"watch": "webpack --watch"
}
}
$ npm run build
JScriptではできなさそうなこと
getterとsetter
以下のコードのようなgetterやsetterは使えなさそうです。
var foo = {
get bar() {
return "bar";
}
};
transform-es5-property-mutatorsを使えば変換はしてくれますが、
変換後に使用されるObject.defineProperty()のpolyfillがgetterやsetterに対応してません。
圧縮の設定
圧縮の設定で重要なのは、UglifyJsPluginに渡すオプションです。
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
screw_ie8: false,
},
mangle: {
screw_ie8: false,
},
output: {
screw_ie8: false,
ascii_only: true
}
}),
new webpack.optimize.AggressiveMergingPlugin(),
],
UglifyJS 2が2.7以降の場合(webpack 1.13.3以降を使用する場合)は、screw_ie8
をcompress
、mangle
、output
でfalse
に設定して、IE8のサポートを有効にします。
UglifyJS 2が2.6系の場合(webpack 1.13.2までを使用する場合)は、IE8のサポートがデフォルトで有効なので設定しなくても問題ないです。
IE8のサポートが有効な場合、予約語のプロパティは変換しないようにしてくれます。
また、output
ではascii_only
をtrue
にしています。
Jscriptはユニコードをうまく扱うことができないため、asciiで出力するようにします。
おわりに
ES2015までの多くの機能が使えるようになりました。
もともとがES3なので、polyfillではカバーできない部分もあると思うので、エラーが発生するメソッドや構文などもあるかと思います。
その点に注意しながらコードを書くといいと思います。