Edited at

HTMLファイルだけでCDNでBabelをリンクしてES2015/2016/2017を動かす

More than 1 year has passed since last update.


はじめに

ES2017にもなると、動かないブラウザもちらほら(IEとか)出てくるので、Babelで変換(コンパイル あるいは トランスパイル)してから動かすのが通常ですが、

npm インストールとか、webpackとか、環境つくりが少しめんどくさいです。npmスクリプトでやるのかとか、gulpか、とか、browser-syncに受け渡すにはどうするべきか、とか、とか、少しじゃなくて、だいぶめんどくさいですね。

どうせ数年もしたら、また違うパッケージャーとかタスクランナーとかが流行って、前のが廃れて使わなくなるんでしょう?

ならば、環境構築なんて無視してしまいましょう。

しかし、Babel変換は使いたい。

ということで、環境構築無しにBabel変換します。


HTMLだけで、babel変換します。

環境を整えなくても、CDN、つまりScriptタグにBabelをリンクすることで、Babelの機能が使えてES2017でコードをかけるので試しました。

開発中に便利に使えると思います。

ブラウザ上でES2017を変換してから動かすので、本番リリースなどには使わない方がいいでしょう。

そういう時には、環境構築してください。

Babelで変換しなくても、ChromeもFirefoxも結構ES2017には対応してきてるのですが、足りないのもあったりします。IE11はだいぶ対応してないみたいです。

なので、下記のBabelで変換するコードなら、各ブラウザの対応状況を気にせず、IE11も動作するので、安心して、ES2017を使えます。


ソースコード

Chrome/Firefox/IE11

いずれも、2018/02/27 最新版で、動作しました。Edgeは、ローカルファイルやローカルWebサーバーでは動かせないみたいなので試していませんが、多分普通に動くと思います。


index.html

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<script type="text/babel" data-presets="es2015,stage-2">

var isObject = function(value) {
if (
(Object.prototype.toString.call(value) === '[object Object]')
&& (!Array.isArray(value))
&& (value !== null)
&& (typeof value !== 'undefined')
) {
return true;
}
return false;
};

var check = function(a, b, message) {
if (a === b) {
return true;
}
if ((typeof message === 'undefined')
|| (message === null)) {
message = '';
} else {
message = 'Test:' + message + '\n';
}
message = message +
'A != B' + '\n' +
'A = ' + a + '\n' +
'B = ' + b;
alert(message);
return false;
};

//オブジェクトを文字列化する関数
//JSON.stringify を利用して
// {valueA: 123, valueB: "123"}
//という形式の文字列を作成する
var objToString = function(obj) {
var items = JSON.stringify(obj).split(',');
items = items.map(function(element, index, array) {
return element.replace(/(.+:)(.*)/,
function(string, capture1, capture2) {
return capture1.replace(/\"/g, '') + capture2;
}).replace(/:/g, ': ');
//[:]の前後でcapture1/2に分割して、
//その後に[:]の前だけ["]を削除して
//[:]は[: ]に置換
});
return items.join(', ');
//[,]は[, ]に置換
};

var test_objToString = function() {
check('{valueA: 123, valueB: "123"}',
objToString({valueA:123, valueB:"123"}));
}
test_objToString();

var consoleExt = {};
consoleExt.originalConsoleLog = console.log;
consoleExt.result = '';
consoleExt.delimiter = ';';
consoleExt.logOutput = true;

consoleExt.log = function(message) {
if (consoleExt.logOutput) {
consoleExt.originalConsoleLog(message);
}
if (isObject(message)) {
consoleExt.result += objToString(message) + consoleExt.delimiter;
} else {
consoleExt.result += message + consoleExt.delimiter;
}
};

consoleExt.hook = function() {
if (consoleExt.originalConsoleLog === console.log) {
console.log = consoleExt.log;
}
};
consoleExt.unhook = function() {
if (consoleExt.originalConsoleLog !== console.log) {
console.log = consoleExt.originalConsoleLog;
}
};

//下記から、ES2015/2016/2017の動作確認

consoleExt.hook();
consoleExt.logOutput = false;
consoleExt.result = '';

//ES2015 let
const test_let = () => {
let a = 1;
console.log(a); //1
{
let a = 2;
console.log(a); //2
}
console.log(a); //1
};
test_let();
check('1;2;1;', consoleExt.result);

//ES2015 const
consoleExt.result = '';
const test_const = () => {
let PI = 3;
console.log(PI); //3
{
const PI = 3.14;
console.log(PI); //3.14
}
console.log(PI); //3
};
test_const();
check('3;3.14;3;', consoleExt.result);

//ES2015 class
consoleExt.result = '';
const test_class = () => {
class ClassA {
constructor(arg1) {
this.prop1 = arg1;
}
method1() {
return this.prop1 + ' ClassA.method1';
}
static method2() {
return 'ClassA.method2';
}
}
class ClassB extends ClassA {
method1() {
return this.prop1 + ' ClassB.method1';
}
}

const classA = new ClassA('a');
console.log(classA.method1());
console.log(ClassA.method2());
const classB = new ClassB('b');
console.log(classB.method1());
console.log(ClassB.method2());
};
test_class();
check(
'a ClassA.method1;' +
'ClassA.method2;' +
'b ClassB.method1;' +
'ClassA.method2;' ,consoleExt.result);

//ES2015 arrow function
consoleExt.result = '';
const test_arrowFunction = () => {
let minus = (x, y) => { return x - y; };
console.log(minus(3, 2)); //1
let plus = (x, y) => x + y;
console.log(plus(10, 20)); //30
let plusOne = x => x + 1;
console.log(plusOne(4)); //5
};
test_arrowFunction();
check('1;30;5;', consoleExt.result);

//ES2015 メソッド定義省略構文
// メソッド定義 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/Method_definitions
consoleExt.result = '';
const test_methodDefinition = () => {
var obj = {
foo0 : function (){return 0;},
foo1(){return 1;},
["foo" + 2](){return 2;},
};
console.log(obj.foo0()); //0
console.log(obj.foo1()); //1
console.log(obj.foo2()); //2
};
test_methodDefinition();
check('0;1;2;', consoleExt.result);

//ES2015 分割代入
// 分割代入 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
consoleExt.result = '';
const test_destructuringAssignment = () => {
let a, b;
[a, b] = [1, 2]
console.log(a); //1
console.log(b); //2

let c;
[a, b, ...c] = [1, 2, 3, 4, 5];
console.log(a); //1
console.log(b); //2
console.log(c.toString()); // c=[3,4,5]

var x, y, z;
c = [x, y, z];
[a, b, ...c] = [1, 2, 3, 4, 5, 6];
console.log(c.toString()); // c=[3,4,5,6]
console.log(x); //undefined
console.log(y); //undefined
console.log(z); //undefined
//変数に入れたものの分割代入は不可

let d;
({a=0, b, d} = {a:1, b:2})
console.log(a); //1
console.log(b); //2
console.log(d); //undefined

({a=0, b, d} = {b:2})
console.log(a); //0
console.log(b); //2
console.log(d); //undefined

let e;
({a, b, d:e} = {a:1, b:2, c:3, d:4})
console.log(a); //1
console.log(b); //2
console.log(e); //4

//入れ替え
a = 2; b = 1;
[a, b] = [b, a];
console.log(a); //1
console.log(b); //2

var f = function() {
return [1, 2, 3];
};
[a, , b] = f();
console.log(a); //1
console.log(b); //3

const url = "https://developer.mozilla.org/en-US/Web/JavaScript";
const parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
const [, protocol, fullhost, fullpath] = parsedURL;
console.log(protocol);
console.log(fullhost);
console.log(fullpath);
};
test_destructuringAssignment();
check(
'1;2;' +
'1;2;3,4,5;' +
'3,4,5,6;undefined;undefined;undefined;' +
'1;2;undefined;' +
'0;2;undefined;' +
'1;2;4;' +
'1;2;' +
'1;3;' +
'https;developer.mozilla.org;en-US/Web/JavaScript;' +
'', consoleExt.result);

//ES2015 スプレッド演算子 配列展開
// スプレッド演算子 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_operator
consoleExt.result = '';
const test_spreadOperator = () => {
const arr = [1, 2, 3]
const f = function (x, y, z) {
return x + y + z;
};
console.log(f(...arr)); //6 f(1,2,3) と同様の結果
console.log([...arr, 4, 5].toString()); //1,2,3,4,5
};
test_spreadOperator();
check('6;1,2,3,4,5;', consoleExt.result);

//ES2015 可変長引数
// Rest parameters - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/rest_parameters
consoleExt.result = '';
const test_variableLengthArgument = () => {
var f = function (a, ...b) {
return 'A:' + a.toString() + ' B:' + b.toString();
}
console.log(f(1, 2, 3));
};
test_variableLengthArgument();
check('A:1 B:2,3;',consoleExt.result);

//ES2015 デフォルト引数
// デフォルト引数 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/Default_parameters
consoleExt.result = '';
const test_defaultArgument = () => {
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(2,2)); //4
console.log(multiply(2)); //2
};
test_defaultArgument();
check('4;2;', consoleExt.result);

//ES2015 Template String
// テンプレート文字列 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings
consoleExt.result = '';
const test_templateString = () => {
const a = 'abc';
const b = `123
${a}
678`
;
console.log(b); //123\n abc\n 678
};
test_templateString();
check('123\n abc\n 678;', consoleExt.result);

//ES2015 for of
// for...of - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of
consoleExt.result = '';
const test_forOf = () => {

let result = 0;
let iterable = [10, 20, 30];
for (const value of iterable) {
result += value;
}
console.log(result); //60

result = '';
iterable = "boo";
for (const value of iterable) {
result += value + '-';
}
console.log(result); //b-o-o-

result = '';
iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (let entry of iterable) {
result += entry.toString();
}
console.log(result); //a,1b,2c,3
result = '';
for (let [key, value] of iterable) {
result += key + ':' + value + ' ';
}
console.log(result); //a:1 b:2 c:3

result = '';
iterable = new Set([1, 1, 2, 2, 3, 3]);
for (let value of iterable) {
result += value;
}
console.log(result); //123

result = '';
function* fibonacci() { // ジェネレーター関数
let [prev, curr] = [1, 1];
while (true) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
result += n.toString() + ' ';
// 100でシーケンスを打ち切る
if (n >= 100) {
break;
}
}
console.log(result);

};
test_forOf();
check(
'60;b-o-o-;a,1b,2c,3;a:1 b:2 c:3 ;123;' +
'2 3 5 8 13 21 34 55 89 144 ;',consoleExt.result);

//ES2015 Object.assign
// Object.assign() - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
consoleExt.result = '';
const test_objectAssign = () => {
var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy.a); //1

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(Object.entries(obj).toString()); //a,1,b,2,c,3
//targetオブジェクト自身が変化する
console.log(Object.entries(o1).toString()); //a,1,b,2,c,3
console.log(Object.entries(o2).toString()); //b,2
console.log(Object.entries(o3).toString()); //c,3

//プロトタイプチェーン上のプロパティや列挙不可能なプロパティはコピーされない
var obj = Object.create({ foo: 1 }, { // fooは継承されたプロパティ
bar: {
value: 2 // barは列挙不可能なプロパティ
},
baz: {
value: 3,
enumerable: true // bazは直接所有で列挙可能なプロパティ
}
});
var copy = Object.assign({}, obj);
console.log(Object.entries(copy).toString()); //baz,3

//プリミティブ型はオブジェクトにラップされる
var v1 = 'abc';
var v2 = true;
var v3 = 10;
// var v4 = Symbol('foo');
var obj = Object.assign({}, v1, null, v2, undefined, v3);
console.log(Object.entries(obj).toString()); //0,a,1,b,2,c
};
test_objectAssign();
check(
'1;' +
'a,1,b,2,c,3;' +
'a,1,b,2,c,3;' +
'b,2;' +
'c,3;' +
'baz,3;' +
'0,a,1,b,2,c;' +
'', consoleExt.result);

//ES2015 Map
// Map - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map
consoleExt.result = '';
const test_map = () => {
var myMap = new Map();
var keyString = "文字列",
keyObj = {},
keyFunc = function () {};
// 値を設定する
myMap.set(keyString, "'文字列' と関連付けられた値");
myMap.set(keyObj, "keyObj と関連付けられた値");
myMap.set(keyFunc, "keyFunc と関連付けられた値");
console.log(myMap.size); //3
// 値を取得する
console.log(myMap.get(keyString)); //'文字列' と関連付けられた値
console.log(myMap.get(keyObj)); //keyObj と関連付けられた値
console.log(myMap.get(keyFunc)); //keyFunc と関連付けられた値
console.log(myMap.get("文字列")); //'文字列' と関連付けられた値
console.log(myMap.get({})); //undefined
console.log(myMap.get(function() {})); //undefined

var myMap = new Map();
myMap.set(NaN, "not a number");
console.log(myMap.get(NaN)); //not a number
var otherNaN = Number("foo");
console.log(myMap.get(otherNaN)); //not a number

var result = '';
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (var [key, value] of myMap) {
result += key + " = " + value + ' ';
}
console.log(result); //0 = zero 1 = one
var result = '';
for (var key of myMap.keys()) {
result += key + ' ';
}
console.log(result); //0 1
var result = '';
for (var value of myMap.values()) {
result += value + ' ';
}
console.log(result); //zero one
var result = '';
for (var [key, value] of myMap.entries()) {
result += key + " = " + value + ' ';
}
console.log(result); //0 = zero 1 = one

var result = '';
myMap.forEach(function(value, key) {
result += key + " = " + value + ' ';
}, myMap)
console.log(result); //0 = zero 1 = one

var kvArray = [["キー1", "値1"], ["キー2", "値2"]];
var myMap = new Map(kvArray);
console.log(myMap.get("キー1")); //値1

console.log(([...myMap].toString())); //キー1,値1,キー2,値2
console.log(([...myMap.keys()].toString())); //キー1,キー2

};
test_map();
check(
'3;' +
"'文字列' と関連付けられた値;" +
"keyObj と関連付けられた値;" +
"keyFunc と関連付けられた値;" +
"'文字列' と関連付けられた値;" +
'undefined;' +
'undefined;' +
'not a number;' +
'not a number;' +
'0 = zero 1 = one ;' +
'0 1 ;' +
'zero one ;' +
'0 = zero 1 = one ;' +
'0 = zero 1 = one ;' +
'値1;' +
'キー1,値1,キー2,値2;' +
'キー1,キー2;' +
'', consoleExt.result);

//ES2015 Iterator/Generator
// JavaScript の ジェネレータ を極める! - Qiita
// https://qiita.com/kura07/items/d1a57ea64ef5c3de8528
const test_iteratorGenerator = () => {
consoleExt.result = '';
var ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for(var num of ary) { console.log(num); }
check('1;2;3;4;5;6;7;8;9;10;', consoleExt.result);

consoleExt.result = '';
function* gfn1(from, to){ while(from <= to) yield from++; }
var g = gfn1(1, 10);
for(var num of g) { console.log(num); }
check('1;2;3;4;5;6;7;8;9;10;', consoleExt.result);

consoleExt.result = '';
function* gfn2(){
var a = yield 0;
yield* [1, a, 5];
}
var g = gfn2();
console.log( g.next() );
console.log( g.next(3) );
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
check(
'{value: 0, done: false};' +
'{value: 1, done: false};' +
'{value: 3, done: false};' +
'{value: 5, done: false};' +
'{done: true};'
, consoleExt.result);

consoleExt.result = '';
function* gfn3(n){
n++;
yield n;
n *= 2;
yield n;
n = 0;
yield n;
}
var g = gfn3(10);
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
check(
'{value: 11, done: false};' +
'{value: 22, done: false};' +
'{value: 0, done: false};' +
'{done: true};'
, consoleExt.result);

consoleExt.result = '';
function* gfn4(){
var a = yield "first";
var b = yield "second";
yield a + b;
}
var g = gfn4();
console.log( g.next() );
console.log( g.next(3) );
console.log( g.next(5) );
console.log( g.next() );
check(
'{value: "first", done: false};' +
'{value: "second", done: false};' +
'{value: 8, done: false};' +
'{done: true};'
, consoleExt.result);

consoleExt.result = '';
function* gfn5(){
yield* [1, 3, 5];
}
var g = gfn5();
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
check(
'{value: 1, done: false};' +
'{value: 3, done: false};' +
'{value: 5, done: false};' +
'{done: true};'
, consoleExt.result);

consoleExt.result = '';
function* gfn6(){
yield* "あいう";
}
var g = gfn6();
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
console.log( g.next() );
check(
'{value: "あ", done: false};' +
'{value: "い", done: false};' +
'{value: "う", done: false};' +
'{done: true};'
, consoleExt.result);

consoleExt.result = '';
function* gfn7(){
yield 1;
yield* [2, 1, 2];
}
for(var num of gfn7()) console.log(num);
check('1;2;1;2;', consoleExt.result);
consoleExt.result = '';
console.log( [...gfn7()] ); // [1, 2, 1, 2]
check('1,2,1,2;', consoleExt.result);
consoleExt.result = '';
console.log( Math.max(...gfn7()) ); // 2
check('2;', consoleExt.result);
consoleExt.result = '';
var [a, b, c, d] = gfn7();
console.log(a);
console.log(b);
console.log(c);
console.log(d);
check('1;2;1;2;', consoleExt.result);
consoleExt.result = '';
};

//ES2016 Exponentiation Operator (べき乗演算子)
// 算術演算子 - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators
consoleExt.result = '';
const test_exponentiationOperator = () => {
console.log(2 ** 8); //256
};
test_exponentiationOperator();
check('256;', consoleExt.result);

//ES2016 Array.includes]
// Array.prototype.includes() - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
consoleExt.result = '';
const test_arrayIncludes = () => {
const arr = ['abc', 'def', 'ghi'];
console.log(arr.includes('def'));
console.log(arr.includes('abcd'));
};
test_arrayIncludes();
check('true;false;', consoleExt.result);

//ES2017 pasStart/padEnd
// String.prototype.padStart() - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
consoleExt.result = '';
const test_padStartEnd = () => {
console.log('abc'.padStart(5, '*') );
console.log('abc'.padStart(10, 'def') );
console.log('abc'.padEnd(5, '*') );
console.log('abc'.padEnd(10, 'def') );
};
test_padStartEnd();
check(
'**abc;' +
'defdefdabc;' +
'abc**;' +
'abcdefdefd;' +
'', consoleExt.result);

//ES2017 Object.values/entries
// Object.values() - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/values
consoleExt.result = '';
const test_objectValuesEntries = () => {
console.log(Object.keys({a:1, b:2}).toString() ); //a,b
console.log(Object.values({a:1, b:2}).toString() ); //1,2
console.log(Object.entries({a:1, b:2}).toString() ); //a,1,b,2
};
test_objectValuesEntries();
check(
'a,b;' +
'1,2;' +
'a,1,b,2;' +
'', consoleExt.result);

//ES2017 Object.
// Object.getOwnPropertyDescriptors() - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
consoleExt.result = '';
const test_objectGetOwnPropertyDescriptors = () => {
const obj = { prop: 123 };
console.log(
Object.entries(
Object.getOwnPropertyDescriptor(obj, "prop")
).toString());
//value,123,writable,true,enumerable,true,configurable,true

console.log(
Object.entries(
Object.getOwnPropertyDescriptors(obj).prop
).toString());
//value,123,writable,true,enumerable,true,configurable,true
};
test_objectGetOwnPropertyDescriptors();
check(
'value,123,writable,true,enumerable,true,configurable,true;' +
'value,123,writable,true,enumerable,true,configurable,true;' +
'', consoleExt.result);

//ES2017 Trailing commas in function parameter lists and calls
consoleExt.result = '';
const test_functionParamTrailingCommas = () => {
const add = (
x,
y,
) => {
return x + y;
};
console.log(add(10,20)); //30
};
test_functionParamTrailingCommas();
check('30;', consoleExt.result);

//ES2017 Async Functions
// async function - JavaScript | MDN
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
consoleExt.result = '';
const test_asyncFunction = async () => {
(async () => {
console.log('async start');
await new Promise((resolve) => setTimeout(resolve, 1000));
// 1秒後に実行される
console.log('async end');

check('async start;async end;', consoleExt.result);

})().catch((error) => console.log(error));
};
test_asyncFunction();

alert('test finish');

</script>

</head>
<body>
</body>
</html>



Babel ver5

CDNでリンクしているのは、Babel ver6 です。

Babel ver5 で動作させる場合は先頭を次のように変更してください。

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<script type="text/babel">

※最初の投稿では ver5 でしか動作させられなかったのですが、スタック・オーバーフローで聞いたところ、hinaloeさんに教えていただきました。とても感謝です。ですので、ソースはver6 のものに差し替えました。


参考


ECMAScript仕様

ES2015(ES6):次世代JavaScriptはもう実戦投入出来るか?|もっこりJavaScript|ANALOGIC(アナロジック)

http://analogic.jp/es2015_introduction/

ES2015(ES6) 入門 - Qiita

https://qiita.com/soarflat/items/b251caf9cb59b72beb9b

ES2016 / ES2017の最新動向を追ってみた - Qiita

https://qiita.com/yuyake0084/items/3c901f37ed7333d4da16


このテストコード実装のために利用したコード

[Sy] npmを使わずにReactの開発環境を構築する方法(CDNで配信されているReactとbabel-coreを利用) | Syntax Error.

https://utano.jp/entry/2016/07/react-js-load-from-cdn/

JavaScript console.log を書き換えて出力内容を横取りする。 - Qiita

https://qiita.com/standard-software/items/5636cc69970a5be00bc0

JavaScriptで(そしてどんな言語でも同じで)世界一簡単なテストフレームワークを作って使おう - Qiita

https://qiita.com/standard-software/items/559d871794bfa38651f4