Edited at

babelを使ってES2015でJScriptを書こう

More than 1 year has passed since last update.

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


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-literalstransform-es3-property-literalstransform-jscriptを適用します。

JScriptでは以下のような、予約語のプロパティを使ったコードはエラーになってしまいます。

var hoge = {default: 'hoge'}; // エラー

WScript.Echo(hoge.default); // エラー

そのため、transform-es3-member-expression-literalstransform-es3-property-literalsを適用し、

以下のようなコードに自動的に変換されるようにします。

var hoge = {'default': 'hoge'}; // OK

WScript.Echo(hoge['default']); // OK

transform-jscriptはJScriptのバグを回避してくれるものらしいです。


timers.js

JScriptにはsetTimeoutなどがありません。

しかし、htmlfileオブジェクトのsetTimeoutが使えるようですので、無理やり使えるようにしてあげます。

webpackでビルドされてもグローバル空間に展開されるようにvarなどの修飾子は付けていません。


timers.js

/*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


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に書いておくと便利だと思います。


package.json

{

"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に渡すオプションです。


webpack.config.js

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_ie8compressmangleoutputfalseに設定して、IE8のサポートを有効にします。

UglifyJS 2が2.6系の場合(webpack 1.13.2までを使用する場合)は、IE8のサポートがデフォルトで有効なので設定しなくても問題ないです。

IE8のサポートが有効な場合、予約語のプロパティは変換しないようにしてくれます。

また、outputではascii_onlytrueにしています。

Jscriptはユニコードをうまく扱うことができないため、asciiで出力するようにします。


おわりに

ES2015までの多くの機能が使えるようになりました。

もともとがES3なので、polyfillではカバーできない部分もあると思うので、エラーが発生するメソッドや構文などもあるかと思います。

その点に注意しながらコードを書くといいと思います。


参考