Gregg Tavares 7 years ago
parent
commit
41824d6929

+ 2 - 0
.eslintrc.json

@@ -48,7 +48,9 @@
     "no-underscore-dangle": 2,
     "no-unused-expressions": 2,
     "no-use-before-define": 0,
+    "no-var": 2,
     "no-with": 2,
+    "prefer-const": 2,
     "consistent-return": 2,
     "curly": [2, "all"],
     "no-extra-parens": [2, "functions"],

+ 8 - 9
Gruntfile.js

@@ -27,10 +27,12 @@ module.exports = function(grunt) {
         src: [
           'threejs/resources/*.js',
         ],
-        options: {
-          //configFile: 'build/conf/eslint.json',
-          //rulesdir: ['build/rules'],
-        },
+      },
+      support: {
+        src: [
+          'Gruntfile.js',
+          'build/js/build.js',
+        ],
       },
       examples: {
         src: [
@@ -39,9 +41,6 @@ module.exports = function(grunt) {
           '!threejs/lessons/resources/prettify.js',
           'threejs/lessons/resources/*.html',
         ],
-        options: {
-//          configFile: 'build/conf/eslint-examples.json',
-        },
       },
     },
     copy: {
@@ -60,8 +59,8 @@ module.exports = function(grunt) {
   });
 
   grunt.registerTask('buildlessons', function() {
-    var buildStuff = require('./build/js/build');
-    var finish = this.async();
+    const buildStuff = require('./build/js/build');
+    const finish = this.async();
     buildStuff().then(function() {
         finish();
     }).done();

+ 147 - 146
build/js/build.js

@@ -1,10 +1,11 @@
+/*eslint-env node*/
+/*eslint no-console: 0*/
 
-module.exports = function () { // wrapper in case we're in module_context mode
+'use strict';
 
-"use strict";
+module.exports = function() { // wrapper in case we're in module_context mode
 
-const args       = require('minimist')(process.argv.slice(2));
-const cache      = new (require('inmemfilecache'));
+const cache      = new (require('inmemfilecache'))();
 const Feed       = require('feed');
 const fs         = require('fs');
 const glob       = require('glob');
@@ -20,7 +21,7 @@ const url        = require('url');
 
 //process.title = "build";
 
-var executeP = Promise.denodeify(utils.execute);
+const executeP = Promise.denodeify(utils.execute);
 
 marked.setOptions({
   rawHtml: true,
@@ -35,7 +36,7 @@ function applyObject(src, dst) {
 }
 
 function mergeObjects() {
-  var merged = {};
+  const merged = {};
   Array.prototype.slice.call(arguments).forEach(function(src) {
     applyObject(src, merged);
   });
@@ -43,26 +44,26 @@ function mergeObjects() {
 }
 
 function readFile(fileName) {
-  return cache.readFileSync(fileName, "utf-8");
+  return cache.readFileSync(fileName, 'utf-8');
 }
 
 function writeFileIfChanged(fileName, content) {
   if (fs.existsSync(fileName)) {
-    var old = readFile(fileName);
-    if (content == old) {
+    const old = readFile(fileName);
+    if (content === old) {
       return;
     }
   }
   fs.writeFileSync(fileName, content);
-  console.log("Wrote: " + fileName);
-};
+  console.log('Wrote: ' + fileName);
+}
 
 function copyFile(src, dst) {
   writeFileIfChanged(dst, readFile(src));
 }
 
 function replaceParams(str, params) {
-  var template = Handlebars.compile(str);
+  const template = Handlebars.compile(str);
   if (Array.isArray(params)) {
     params = mergeObjects.apply(null, params.slice().reverse());
   }
@@ -84,8 +85,8 @@ function encodeQuery(query) {
   if (!query) {
     return '';
   }
-  return '?' + query.split("&").map(function(pair) {
-    return pair.split("=").map(function (kv) {
+  return '?' + query.split('&').map(function(pair) {
+    return pair.split('=').map(function(kv) {
       return encodeURIComponent(decodeURIComponent(kv));
     }).join('=');
   }).join('&');
@@ -98,12 +99,12 @@ function encodeUrl(src) {
 }
 
 function TemplateManager() {
-  var templates = {};
+  const templates = {};
 
   this.apply = function(filename, params) {
-    var template = templates[filename];
+    let template = templates[filename];
     if (!template) {
-      var template = Handlebars.compile(readFile(filename));
+      template = Handlebars.compile(readFile(filename));
       templates[filename] = template;
     }
 
@@ -115,12 +116,12 @@ function TemplateManager() {
   };
 }
 
-var templateManager = new TemplateManager();
+const templateManager = new TemplateManager();
 
 Handlebars.registerHelper('include', function(filename, options) {
-  var context;
+  let context;
   if (options && options.hash && options.hash.filename) {
-    var varName = options.hash.filename;
+    const varName = options.hash.filename;
     filename = options.data.root[varName];
     context = options.hash;
   } else {
@@ -130,8 +131,8 @@ Handlebars.registerHelper('include', function(filename, options) {
 });
 
 Handlebars.registerHelper('example', function(options) {
-  options.hash.width   = options.hash.width  ? "width:  " + options.hash.width  + "px;" : "";
-  options.hash.height  = options.hash.height ? "height: " + options.hash.height + "px;" : "";
+  options.hash.width   = options.hash.width  ? 'width:  ' + options.hash.width  + 'px;' : '';
+  options.hash.height  = options.hash.height ? 'height: ' + options.hash.height + 'px;' : '';
   options.hash.caption = options.hash.caption || options.data.root.defaultExampleCaption;
   options.hash.examplePath = options.data.root.examplePath;
   options.hash.encodedUrl = encodeURIComponent(encodeUrl(options.hash.url));
@@ -139,31 +140,31 @@ Handlebars.registerHelper('example', function(options) {
   options.hash.params = encodeParams({
     startPane: options.hash.startPane,
   });
-  return templateManager.apply("build/templates/example.template", options.hash);
+  return templateManager.apply('build/templates/example.template', options.hash);
 });
 
 Handlebars.registerHelper('diagram', function(options) {
 
-  options.hash.width  = options.hash.width || "400";
-  options.hash.height = options.hash.height || "300";
+  options.hash.width  = options.hash.width || '400';
+  options.hash.height = options.hash.height || '300';
   options.hash.examplePath = options.data.root.examplePath;
-  options.hash.className = options.hash.className || "";
+  options.hash.className = options.hash.className || '';
   options.hash.url = encodeUrl(options.hash.url);
 
-  return templateManager.apply("build/templates/diagram.template", options.hash);
+  return templateManager.apply('build/templates/diagram.template', options.hash);
 });
 
 Handlebars.registerHelper('image', function(options) {
 
   options.hash.examplePath = options.data.root.examplePath;
-  options.hash.className = options.hash.className || "";
-  options.hash.caption = options.hash.caption || "";
+  options.hash.className = options.hash.className || '';
+  options.hash.caption = options.hash.caption || '';
 
   if (options.hash.url.substring(0, 4) === 'http') {
-    options.hash.examplePath = "";
+    options.hash.examplePath = '';
   }
 
-  return templateManager.apply("build/templates/image.template", options.hash);
+  return templateManager.apply('build/templates/image.template', options.hash);
 });
 
 Handlebars.registerHelper('selected', function(options) {
@@ -172,7 +173,7 @@ Handlebars.registerHelper('selected', function(options) {
   const re = options.hash.re;
   const sub = options.hash.sub;
 
-  let a = this[key];
+  const a = this[key];
   let b = options.data.root[value];
 
   if (re) {
@@ -187,27 +188,27 @@ function slashify(s) {
   return s.replace(/\\/g, '/');
 }
 
-var Builder = function(outBaseDir, options) {
+const Builder = function(outBaseDir, options) {
 
-  var g_articlesByLang = {};
-  var g_articles = [];
-  var g_langInfo;
-  var g_langDB = {};
-  var g_outBaseDir = outBaseDir;
-  var g_origPath = options.origPath;
+  const g_articlesByLang = {};
+  let g_articles = [];
+  let g_langInfo;
+  const g_langDB = {};
+  const g_outBaseDir = outBaseDir;
+  const g_origPath = options.origPath;
 
   // This are the english articles.
-  var g_origArticles = glob.sync(path.join(g_origPath, "*.md")).map(a => path.basename(a)).filter(a => a !== 'index.md');
+  const g_origArticles = glob.sync(path.join(g_origPath, '*.md')).map(a => path.basename(a)).filter(a => a !== 'index.md');
 
-  var extractHeader = (function() {
-    var headerRE = /([A-Z0-9_-]+): (.*?)$/i;
+  const extractHeader = (function() {
+    const headerRE = /([A-Z0-9_-]+): (.*?)$/i;
 
     return function(content) {
-      var metaData = { };
-      var lines = content.split("\n");
-      while (true) {
-        var line = lines[0].trim();
-        var m = headerRE.exec(line);
+      const metaData = { };
+      const lines = content.split('\n');
+      for (;;) {
+        const line = lines[0].trim();
+        const m = headerRE.exec(line);
         if (!m) {
           break;
         }
@@ -215,31 +216,31 @@ var Builder = function(outBaseDir, options) {
         lines.shift();
       }
       return {
-        content: lines.join("\n"),
+        content: lines.join('\n'),
         headers: metaData,
       };
     };
   }());
 
-  var parseMD = function(content) {
+  const parseMD = function(content) {
     return extractHeader(content);
   };
 
-  var loadMD = function(contentFileName) {
-    var content = cache.readFileSync(contentFileName, "utf-8");
+  const loadMD = function(contentFileName) {
+    const content = cache.readFileSync(contentFileName, 'utf-8');
     return parseMD(content);
   };
 
   function extractHandlebars(content) {
-    var tripleRE = /\{\{\{.*?\}\}\}/g;
-    var doubleRE = /\{\{\{.*?\}\}\}/g;
+    const tripleRE = /\{\{\{.*?\}\}\}/g;
+    const doubleRE = /\{\{\{.*?\}\}\}/g;
 
-    var numExtractions = 0;
-    var extractions = {
+    let numExtractions = 0;
+    const extractions = {
     };
 
     function saveHandlebar(match) {
-      var id = "==HANDLEBARS_ID_" + (++numExtractions) + "==";
+      const id = '==HANDLEBARS_ID_' + (++numExtractions) + '==';
       extractions[id] = match;
       return id;
     }
@@ -254,12 +255,12 @@ var Builder = function(outBaseDir, options) {
   }
 
   function insertHandlebars(info, content) {
-    var handlebarRE = /==HANDLEBARS_ID_\d+==/g;
+    const handlebarRE = /==HANDLEBARS_ID_\d+==/g;
 
     function restoreHandlebar(match) {
-      var value = info.extractions[match];
+      const value = info.extractions[match];
       if (value === undefined) {
-        throw new Error("no match restoring handlebar for: " + match);
+        throw new Error('no match restoring handlebar for: ' + match);
       }
       return value;
     }
@@ -269,22 +270,22 @@ var Builder = function(outBaseDir, options) {
     return content;
   }
 
-  var applyTemplateToContent = function(templatePath, contentFileName, outFileName, opt_extra, data) {
+  const applyTemplateToContent = function(templatePath, contentFileName, outFileName, opt_extra, data) {
     // Call prep's Content which parses the HTML. This helps us find missing tags
     // should probably call something else.
     //Convert(md_content)
-    var metaData = data.headers;
-    var content = data.content;
+    const metaData = data.headers;
+    const content = data.content;
     //console.log(JSON.stringify(metaData, undefined, "  "));
-    var info = extractHandlebars(content);
-    var html = marked(info.content);
+    const info = extractHandlebars(content);
+    let html = marked(info.content);
     html = insertHandlebars(info, html);
     html = replaceParams(html, [opt_extra, g_langInfo]);
     const relativeOutName = slashify(outFileName).substring(g_outBaseDir.length);
     const langs = Object.keys(g_langDB).map((name) => {
       const lang = g_langDB[name];
       const url = slashify(path.join(lang.basePath, path.basename(outFileName)))
-         .replace("index.html", "")
+         .replace('index.html', '')
          .replace(/^\/threejs\/lessons\/$/, '/');
       return {
         lang: lang.lang,
@@ -296,49 +297,49 @@ var Builder = function(outBaseDir, options) {
     metaData['langs'] = langs;
     metaData['src_file_name'] = slashify(contentFileName);
     metaData['dst_file_name'] = relativeOutName;
-    metaData['basedir'] = "";
+    metaData['basedir'] = '';
     metaData['toc'] = opt_extra.toc;
     metaData['templateOptions'] = opt_extra.templateOptions;
     metaData['langInfo'] = g_langInfo;
-    metaData['url'] = "http://threejsfundamentals.org" + relativeOutName;
+    metaData['url'] = 'http://threejsfundamentals.org' + relativeOutName;
     metaData['relUrl'] = relativeOutName;
-    metaData['screenshot'] = "http://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg";
-    var basename = path.basename(contentFileName, ".md");
-    [".jpg", ".png"].forEach(function(ext) {
-      var filename = path.join("threejs", "lessons", "screenshots", basename + ext);
+    metaData['screenshot'] = 'http://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg';
+    const basename = path.basename(contentFileName, '.md');
+    ['.jpg', '.png'].forEach(function(ext) {
+      const filename = path.join('threejs', 'lessons', 'screenshots', basename + ext);
       if (fs.existsSync(filename)) {
-        metaData['screenshot'] = "http://threejsfundamentals.org/threejs/lessons/screenshots/" + basename + ext;
+        metaData['screenshot'] = 'http://threejsfundamentals.org/threejs/lessons/screenshots/' + basename + ext;
       }
     });
-    var output = templateManager.apply(templatePath, metaData);
+    const output = templateManager.apply(templatePath, metaData);
     writeFileIfChanged(outFileName, output);
 
     return metaData;
   };
 
-  var applyTemplateToFile = function(templatePath, contentFileName, outFileName, opt_extra) {
-    console.log("processing: ", contentFileName);
+  const applyTemplateToFile = function(templatePath, contentFileName, outFileName, opt_extra) {
+    console.log('processing: ', contentFileName);
     opt_extra = opt_extra || {};
-    var data = loadMD(contentFileName);
-    var metaData= applyTemplateToContent(templatePath, contentFileName, outFileName, opt_extra, data);
+    const data = loadMD(contentFileName);
+    const metaData = applyTemplateToContent(templatePath, contentFileName, outFileName, opt_extra, data);
     g_articles.push(metaData);
   };
 
-  var applyTemplateToFiles = function(templatePath, filesSpec, extra) {
-    var files = glob.sync(filesSpec).sort();
+  const applyTemplateToFiles = function(templatePath, filesSpec, extra) {
+    const files = glob.sync(filesSpec).sort();
     files.forEach(function(fileName) {
-      var ext = path.extname(fileName);
-      var baseName = fileName.substr(0, fileName.length - ext.length);
-      var outFileName = path.join(outBaseDir, baseName + ".html");
+      const ext = path.extname(fileName);
+      const baseName = fileName.substr(0, fileName.length - ext.length);
+      const outFileName = path.join(outBaseDir, baseName + '.html');
       applyTemplateToFile(templatePath, fileName, outFileName, extra);
     });
 
   };
 
-  var addArticleByLang = function(article, lang) {
-    var filename = path.basename(article.dst_file_name);
-    var articleInfo = g_articlesByLang[filename];
-    var url = "http://threejsfundamentals.org" + article.dst_file_name;
+  const addArticleByLang = function(article, lang) {
+    const filename = path.basename(article.dst_file_name);
+    let articleInfo = g_articlesByLang[filename];
+    const url = 'http://threejsfundamentals.org' + article.dst_file_name;
     if (!articleInfo) {
       articleInfo = {
         url: url,
@@ -353,9 +354,9 @@ var Builder = function(outBaseDir, options) {
     });
   };
 
-  var getLanguageSelection = function(lang) {
-    var lessons = lang.lessons || ("threejs/lessons/" + lang.lang);
-    var langInfo = hanson.parse(fs.readFileSync(path.join(lessons, "langinfo.hanson"), {encoding: "utf8"}));
+  const getLanguageSelection = function(lang) {
+    const lessons = lang.lessons || ('threejs/lessons/' + lang.lang);
+    const langInfo = hanson.parse(fs.readFileSync(path.join(lessons, 'langinfo.hanson'), {encoding: 'utf8'}));
     langInfo.langCode = langInfo.langCode || lang.lang;
     langInfo.home = lang.home || ('/' + lessons + '/');
     g_langDB[lang.lang] = {
@@ -371,50 +372,50 @@ var Builder = function(outBaseDir, options) {
   };
 
   this.process = function(options) {
-    console.log("Processing Lang: " + options.lang);
-    options.lessons     = options.lessons     || ("threejs/lessons/" + options.lang);
-    options.toc         = options.toc         || ("threejs/lessons/" + options.lang + "/toc.html");
-    options.template    = options.template    || "build/templates/lesson.template";
-    options.examplePath = options.examplePath === undefined ? "/threejs/lessons/" : options.examplePath;
+    console.log('Processing Lang: ' + options.lang);
+    options.lessons     = options.lessons     || ('threejs/lessons/' + options.lang);
+    options.toc         = options.toc         || ('threejs/lessons/' + options.lang + '/toc.html');
+    options.template    = options.template    || 'build/templates/lesson.template';
+    options.examplePath = options.examplePath === undefined ? '/threejs/lessons/' : options.examplePath;
 
     g_articles = [];
     g_langInfo = g_langDB[options.lang].langInfo;
 
-    applyTemplateToFiles(options.template, path.join(options.lessons, "threejs*.md"), options);
+    applyTemplateToFiles(options.template, path.join(options.lessons, 'threejs*.md'), options);
 
     // generate place holders for non-translated files
-    var articlesFilenames = g_articles.map(a => path.basename(a.src_file_name));
-    var missing = g_origArticles.filter(name => articlesFilenames.indexOf(name) < 0);
+    const articlesFilenames = g_articles.map(a => path.basename(a.src_file_name));
+    const missing = g_origArticles.filter(name => articlesFilenames.indexOf(name) < 0);
     missing.forEach(name => {
       const ext = path.extname(name);
       const baseName = name.substr(0, name.length - ext.length);
-      const outFileName = path.join(outBaseDir, options.lessons, baseName + ".html");
+      const outFileName = path.join(outBaseDir, options.lessons, baseName + '.html');
       const data = Object.assign({}, loadMD(path.join(g_origPath, name)));
       data.content = g_langInfo.missing;
       const extra = {
-        origLink: '/' + slashify(path.join(g_origPath, baseName + ".html")),
+        origLink: '/' + slashify(path.join(g_origPath, baseName + '.html')),
         toc: options.toc,
       };
-      console.log("  generating missing:", outFileName);
+      console.log('  generating missing:', outFileName);
       applyTemplateToContent(
-          "build/templates/missing.template",
-          path.join(options.lessons, "langinfo.hanson"),
+          'build/templates/missing.template',
+          path.join(options.lessons, 'langinfo.hanson'),
           outFileName,
           extra,
           data);
     });
 
     function utcMomentFromGitLog(result) {
-      const dateStr = result.stdout.split("\n")[0].trim();
-      let utcDateStr = dateStr
-        .replace(/"/g, "")   // WTF to these quotes come from!??!
-        .replace(" ", "T")
-        .replace(" ", "")
+      const dateStr = result.stdout.split('\n')[0].trim();
+      const utcDateStr = dateStr
+        .replace(/"/g, '')   // WTF to these quotes come from!??!
+        .replace(' ', 'T')
+        .replace(' ', '')
         .replace(/(\d\d)$/, ':$1');
       return moment.utc(utcDateStr);
     }
 
-    const tasks = g_articles.map((article, ndx) => {
+    const tasks = g_articles.map((article) => {
       return function() {
         return executeP('git', [
           'log',
@@ -426,7 +427,7 @@ var Builder = function(outBaseDir, options) {
           article.dateAdded = utcMomentFromGitLog(result);
         });
       };
-    }).concat(g_articles.map((article, ndx) => {
+    }).concat(g_articles.map((article) => {
        return function() {
          return executeP('git', [
            'log',
@@ -443,14 +444,14 @@ var Builder = function(outBaseDir, options) {
     return tasks.reduce(function(cur, next){
         return cur.then(next);
     }, Promise.resolve()).then(function() {
-      var articles = g_articles.filter(function(article) {
-        return article.dateAdded != undefined;
+      let articles = g_articles.filter(function(article) {
+        return article.dateAdded !== undefined;
       });
       articles = articles.sort(function(a, b) {
         return b.dateAdded - a.dateAdded;
       });
 
-      var feed = new Feed({
+      const feed = new Feed({
         title:          g_langInfo.title,
         description:    g_langInfo.description,
         link:           g_langInfo.link,
@@ -464,11 +465,11 @@ var Builder = function(outBaseDir, options) {
         },
       });
 
-      articles.forEach(function(article, ndx) {
+      articles.forEach(function(article) {
         feed.addItem({
           title:          article.title,
-          link:           "http://threejsfundamentals.org" + article.dst_file_name,
-          description:    "",
+          link:           'http://threejsfundamentals.org' + article.dst_file_name,
+          description:    '',
           author: [
             {
               name:       'threejsfundamenals contributors',
@@ -486,8 +487,8 @@ var Builder = function(outBaseDir, options) {
       });
 
       try {
-        const outPath = path.join(g_outBaseDir, options.lessons, "atom.xml");
-        console.log("write:", outPath);
+        const outPath = path.join(g_outBaseDir, options.lessons, 'atom.xml');
+        console.log('write:', outPath);
         writeFileIfChanged(outPath, feed.atom1());
       } catch (err) {
         return Promise.reject(err);
@@ -496,30 +497,30 @@ var Builder = function(outBaseDir, options) {
     }).then(function() {
       // this used to insert a table of contents
       // but it was useless being auto-generated
-      applyTemplateToFile("build/templates/index.template", path.join(options.lessons, "index.md"), path.join(g_outBaseDir, options.lessons, "index.html"), {
-        table_of_contents: "",
+      applyTemplateToFile('build/templates/index.template', path.join(options.lessons, 'index.md'), path.join(g_outBaseDir, options.lessons, 'index.html'), {
+        table_of_contents: '',
         templateOptions: g_langInfo,
       });
       return Promise.resolve();
     }, function(err) {
-      console.error("ERROR!:");
+      console.error('ERROR!:');
       console.error(err);
       if (err.stack) {
         console.error(err.stack);
       }
       throw new Error(err.toString());
     });
-  }
+  };
 
   this.writeGlobalFiles = function() {
-    var sm = sitemap.createSitemap ({
+    const sm = sitemap.createSitemap({
       hostname: 'http://threejsfundamentals.org',
       cacheTime: 600000,
     });
-    var articleLangs = { };
+    const articleLangs = { };
     Object.keys(g_articlesByLang).forEach(function(filename) {
-      var article = g_articlesByLang[filename];
-      var langs = {};
+      const article = g_articlesByLang[filename];
+      const langs = {};
       article.links.forEach(function(link) {
         langs[link.lang] = true;
       });
@@ -532,55 +533,55 @@ var Builder = function(outBaseDir, options) {
     // };
     // var langJS = "window.langDB = " + JSON.stringify(langInfo, null, 2);
     // writeFileIfChanged(path.join(g_outBaseDir, "langdb.js"), langJS);
-    writeFileIfChanged(path.join(g_outBaseDir, "sitemap.xml"), sm.toString());
-    copyFile(path.join(g_outBaseDir, "threejs/lessons/atom.xml"), path.join(g_outBaseDir, "atom.xml"));
-    copyFile(path.join(g_outBaseDir, "threejs/lessons/index.html"), path.join(g_outBaseDir, "index.html"));
+    writeFileIfChanged(path.join(g_outBaseDir, 'sitemap.xml'), sm.toString());
+    copyFile(path.join(g_outBaseDir, 'threejs/lessons/atom.xml'), path.join(g_outBaseDir, 'atom.xml'));
+    copyFile(path.join(g_outBaseDir, 'threejs/lessons/index.html'), path.join(g_outBaseDir, 'index.html'));
 
-    applyTemplateToFile("build/templates/index.template", "contributors.md", path.join(g_outBaseDir, "contributors.html"), {
-      table_of_contents: "",
-      templateOptions: "",
+    applyTemplateToFile('build/templates/index.template', 'contributors.md', path.join(g_outBaseDir, 'contributors.html'), {
+      table_of_contents: '',
+      templateOptions: '',
     });
   };
 
 
 };
 
-var b = new Builder("out", {
-  origPath: "threejs/lessons",  // english articles
+const b = new Builder('out', {
+  origPath: 'threejs/lessons',  // english articles
 });
 
-var readdirs = function(dirpath) {
-  var dirsOnly = function(filename) {
-    var stat = fs.statSync(filename);
+const readdirs = function(dirpath) {
+  const dirsOnly = function(filename) {
+    const stat = fs.statSync(filename);
     return stat.isDirectory();
   };
 
-  var addPath = function(filename) {
+  const addPath = function(filename) {
     return path.join(dirpath, filename);
   };
 
-  return fs.readdirSync("threejs/lessons")
+  return fs.readdirSync('threejs/lessons')
       .map(addPath)
       .filter(dirsOnly);
 };
 
-var isLangFolder = function(dirname) {
-  var filename = path.join(dirname, "langinfo.hanson");
+const isLangFolder = function(dirname) {
+  const filename = path.join(dirname, 'langinfo.hanson');
   return fs.existsSync(filename);
 };
 
 
-var pathToLang = function(filename) {
+const pathToLang = function(filename) {
   return {
     lang: path.basename(filename),
   };
 };
 
-var langs = [
+let langs = [
   // English is special (sorry it's where I started)
   {
-    template: "build/templates/lesson.template",
-    lessons: "threejs/lessons",
+    template: 'build/templates/lesson.template',
+    lessons: 'threejs/lessons',
     lang: 'en',
     toc: 'threejs/lessons/toc.html',
     examplePath: '/threejs/lessons/',
@@ -588,13 +589,13 @@ var langs = [
   },
 ];
 
-langs = langs.concat(readdirs("threejs/lessons")
+langs = langs.concat(readdirs('threejs/lessons')
     .filter(isLangFolder)
     .map(pathToLang));
 
 b.preProcess(langs);
 
-var tasks = langs.map(function(lang) {
+const tasks = langs.map(function(lang) {
   return function() {
     return b.process(lang);
   };

+ 21 - 20
build/js/utils.js

@@ -28,41 +28,42 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-"use strict";
 
-var fs = require('fs');
-var path = require('path');
+/*eslint-env node*/
+/*eslint no-console: 0*/
 
-var execute = function(cmd, args, callback) {
-  var spawn = require('child_process').spawn;
+'use strict';
 
-  var proc = spawn(cmd, args);
-  var stdout = [];
-  var stderr = [];
+const execute = function(cmd, args, callback) {
+  const spawn = require('child_process').spawn;
+
+  const proc = spawn(cmd, args);
+  let stdout = [];
+  let stderr = [];
 
   proc.stdout.setEncoding('utf8');
-  proc.stdout.on('data', function (data) {
-      var str = data.toString()
-      var lines = str.split(/(\r?\n)/g);
+  proc.stdout.on('data', function(data) {
+      const str = data.toString();
+      const lines = str.split(/(\r?\n)/g);
       stdout = stdout.concat(lines);
   });
 
   proc.stderr.setEncoding('utf8');
-  proc.stderr.on('data', function (data) {
-      var str = data.toString()
-      var lines = str.split(/(\r?\n)/g);
+  proc.stderr.on('data', function(data) {
+      const str = data.toString();
+      const lines = str.split(/(\r?\n)/g);
       stderr = stderr.concat(lines);
   });
 
-  proc.on('close', function (code) {
-    var result = {stdout: stdout.join("\n"), stderr: stderr.join("\n")};
-    if (parseInt(code) != 0) {
-      callback("exit code " + code, result)
+  proc.on('close', function(code) {
+    const result = {stdout: stdout.join('\n'), stderr: stderr.join('\n')};
+    if (parseInt(code) !== 0) {
+      callback('exit code ' + code, result);
     } else {
-      callback(null, result)
+      callback(null, result);
     }
   });
-}
+};
 
 exports.execute = execute;
 

+ 7 - 7
threejs/lessons/resources/lesson.js

@@ -276,9 +276,9 @@ $(document).ready(function($){
     return a;
   });
 
-  var linkImgs = function(bigHref) {
+  const linkImgs = function(bigHref) {
     return function() {
-      var a = document.createElement('a');
+      const a = document.createElement('a');
       a.href = bigHref;
       a.title = this.alt;
       a.className = this.className;
@@ -289,14 +289,14 @@ $(document).ready(function($){
       return a;
     };
   };
-  var linkSmallImgs = function(ext) {
+  const linkSmallImgs = function(ext) {
     return function() {
-      var src = this.src;
+      const src = this.src;
       return linkImgs(src.substr(0, src.length - 7) + ext);
     };
   };
-  var linkBigImgs = function() {
-    var src = $(this).attr('big');
+  const linkBigImgs = function() {
+    const src = $(this).attr('big');
     return linkImgs(src);
   };
   $('img[big$=".jpg"]').wrap(linkBigImgs);
@@ -312,7 +312,7 @@ $(document).ready(function($){
     window.prettyPrint();
   }
 
-  var params = getQueryParams();
+  const params = getQueryParams();
   if (params.doubleSpace || params.doublespace) {
     document.body.className = document.body.className + ' doubleSpace';
   }

+ 3 - 2
threejs/lessons/resources/threejs-primitives.js

@@ -121,7 +121,6 @@ function main() {
           u = u * 2;
 
           let x;
-          let y;
           let z;
 
           if (u < Math.PI) {
@@ -132,7 +131,7 @@ function main() {
               z = -8 * Math.sin(u);
           }
 
-          y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);
+          const y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);
 
           target.set(x, y, z).multiplyScalar(0.75);
         }
@@ -516,6 +515,8 @@ function main() {
       const top    = rect.top * pixelRatio;
 
       if (width !== oldWidth || height !== oldHeight) {
+        oldWidth = width;
+        oldHeight = height;
         controls.handleResize();
       }
       controls.update();

+ 51 - 51
threejs/resources/editor.js

@@ -10,9 +10,9 @@ function getQuery(s) {
   if (s[0] === '?' ) {
     s = s.substring(1);
   }
-  var query = {};
+  const query = {};
   s.split('&').forEach(function(pair) {
-      var parts = pair.split('=').map(decodeURIComponent);
+      const parts = pair.split('=').map(decodeURIComponent);
       query[parts[0]] = parts[1];
   });
   return query;
@@ -20,7 +20,7 @@ function getQuery(s) {
 
 function getSearch(url) {
   // yea I know this is not perfect but whatever
-  var s = url.indexOf('?');
+  const s = url.indexOf('?');
   return s < 0 ? {} : getQuery(url.substring(s));
 }
 
@@ -33,10 +33,10 @@ const getFQUrl = (function() {
 }());
 
 function getHTML(url, callback) {
-  var req = new XMLHttpRequest();
+  const req = new XMLHttpRequest();
   req.open('GET', url, true);
   req.addEventListener('load', function() {
-    var success = req.status === 200 || req.status === 0;
+    const success = req.status === 200 || req.status === 0;
     callback(success ? null : 'could not load: ' + url, req.responseText);
   });
   req.addEventListener('timeout', function() {
@@ -49,13 +49,13 @@ function getHTML(url, callback) {
 }
 
 function fixSourceLinks(url, source) {
-  var srcRE = /(src=)"(.*?)"/g;
-  var linkRE = /(href=)"(.*?")/g;
-  var imageSrcRE = /((?:image|img)\.src = )"(.*?)"/g;
-  var loaderLoadRE = /(loader\.load)\(('|")(.*?)('|")/g;
+  const srcRE = /(src=)"(.*?)"/g;
+  const linkRE = /(href=)"(.*?")/g;
+  const imageSrcRE = /((?:image|img)\.src = )"(.*?)"/g;
+  const loaderLoadRE = /(loader\.load)\(('|")(.*?)('|")/g;
 
-  var u = new URL(window.location.origin + url);
-  var prefix = u.origin + dirname(u.pathname);
+  const u = new URL(window.location.origin + url);
+  const prefix = u.origin + dirname(u.pathname);
 
   function addPrefix(url) {
     return url.indexOf('://') < 0 ? (prefix + url) : url;
@@ -73,11 +73,11 @@ function fixSourceLinks(url, source) {
   return source;
 }
 
-var g = {
+const g = {
   html: '',
 };
 
-var htmlParts = {
+const htmlParts = {
   js: {
     language: 'javascript',
   },
@@ -91,14 +91,14 @@ var htmlParts = {
 
 function forEachHTMLPart(fn) {
   Object.keys(htmlParts).forEach(function(name, ndx) {
-    var info = htmlParts[name];
+    const info = htmlParts[name];
     fn(info, ndx, name);
   });
 }
 
 
 function getHTMLPart(re, obj, tag) {
-  var part = '';
+  let part = '';
   obj.html = obj.html.replace(re, function(p0, p1) {
     part = p1;
     return tag;
@@ -111,35 +111,35 @@ function parseHTML(url, html) {
 
   html = html.replace(/<div class="description">[^]*?<\/div>/, '');
 
-  var styleRE = /<style>([^]*?)<\/style>/i;
-  var titleRE = /<title>([^]*?)<\/title>/i;
-  var bodyRE = /<body>([^]*?)<\/body>/i;
-  var inlineScriptRE = /<script>([^]*?)<\/script>/i;
-  var externalScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script\s*src\s*=\s*"(.*?)"\s*>\s*<\/script>/ig;
-  var dataScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script (.*?)>([^]*?)<\/script>/ig;
-  var cssLinkRE = /<link ([^>]+?)>/g;
-  var isCSSLinkRE = /type="text\/css"|rel="stylesheet"/;
-  var hrefRE = /href="([^"]+)"/;
-
-  var obj = { html: html };
+  const styleRE = /<style>([^]*?)<\/style>/i;
+  const titleRE = /<title>([^]*?)<\/title>/i;
+  const bodyRE = /<body>([^]*?)<\/body>/i;
+  const inlineScriptRE = /<script>([^]*?)<\/script>/i;
+  const externalScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script\s*src\s*=\s*"(.*?)"\s*>\s*<\/script>/ig;
+  const dataScriptRE = /(<!--(?:(?!-->)[\s\S])*?-->\n){0,1}<script (.*?)>([^]*?)<\/script>/ig;
+  const cssLinkRE = /<link ([^>]+?)>/g;
+  const isCSSLinkRE = /type="text\/css"|rel="stylesheet"/;
+  const hrefRE = /href="([^"]+)"/;
+
+  const obj = { html: html };
   htmlParts.css.source = getHTMLPart(styleRE, obj, '<style>\n${css}</style>');
   htmlParts.html.source = getHTMLPart(bodyRE, obj, '<body>${html}</body>');
   htmlParts.js.source = getHTMLPart(inlineScriptRE, obj, '<script>${js}</script>');
   html = obj.html;
 
-  var tm = titleRE.exec(html);
+  const tm = titleRE.exec(html);
   if (tm) {
     g.title = tm[1];
   }
 
-  var scripts = '';
+  let scripts = '';
   html = html.replace(externalScriptRE, function(p0, p1, p2) {
     p1 = p1 || '';
     scripts += '\n' + p1 + '<script src="' + p2 + '"></script>';
     return '';
   });
 
-  var dataScripts = '';
+  let dataScripts = '';
   html = html.replace(dataScriptRE, function(p0, p1, p2, p3) {
     p1 = p1 || '';
     dataScripts += '\n' + p1 + '<script ' + p2 + '>' + p3 + '</script>';
@@ -159,10 +159,10 @@ function parseHTML(url, html) {
   // query params but that only works in Firefox >:(
   html = html.replace('</head>', '<script id="hackedparams">window.hackedParams = ${hackedParams}\n</script>\n</head>');
 
-  var links = '';
+  let links = '';
   html = html.replace(cssLinkRE, function(p0, p1) {
     if (isCSSLinkRE.test(p1)) {
-      var m = hrefRE.exec(p1);
+      const m = hrefRE.exec(p1);
       if (m) {
         links += `@import url("${m[1]}");\n`;
       }
@@ -183,7 +183,7 @@ function cantGetHTML(e) {  // eslint-disable-line
 }
 
 function main() {
-  var query = getQuery();
+  const query = getQuery();
   g.url = getFQUrl(query.url);
   g.query = getSearch(g.url);
   getHTML(query.url, function(err, html) {
@@ -201,29 +201,29 @@ function main() {
 }
 
 
-var blobUrl;
+let blobUrl;
 function getSourceBlob(options) {
   options = options || {};
   if (blobUrl) {
     URL.revokeObjectURL(blobUrl);
   }
-  var source = g.html;
+  let source = g.html;
   source = source.replace('${hackedParams}', JSON.stringify(g.query));
   source = source.replace('${html}', htmlParts.html.editor.getValue());
   source = source.replace('${css}', htmlParts.css.editor.getValue());
   source = source.replace('${js}', htmlParts.js.editor.getValue());
   source = source.replace('<head>', '<head>\n<script match="false">threejsLessonSettings = ' + JSON.stringify(options) + ';</script>');
 
-  var scriptNdx = source.indexOf('<script>');
+  const scriptNdx = source.indexOf('<script>');
   g.numLinesBeforeScript = (source.substring(0, scriptNdx).match(/\n/g) || []).length;
 
-  var blob = new Blob([source], {type: 'text/html'});
+  const blob = new Blob([source], {type: 'text/html'});
   blobUrl = URL.createObjectURL(blob);
   return blobUrl;
 }
 
 function dirname(path) {
-  var ndx = path.lastIndexOf('/');
+  const ndx = path.lastIndexOf('/');
   return path.substring(0, ndx + 1);
 }
 
@@ -359,22 +359,22 @@ function toggleFullscreen() {
 
 function run(options) {
   g.setPosition = false;
-  var url = getSourceBlob(options);
+  const url = getSourceBlob(options);
   g.iframe.src = url;
 }
 
 function addClass(elem, className) {
-  var parts = elem.className.split(' ');
+  const parts = elem.className.split(' ');
   if (parts.indexOf(className) < 0) {
     elem.className = elem.className + ' ' + className;
   }
 }
 
 function removeClass(elem, className) {
-  var parts = elem.className.split(' ');
-  var numParts = parts.length;
+  const parts = elem.className.split(' ');
+  const numParts = parts.length;
   for (;;) {
-    var ndx = parts.indexOf(className);
+    const ndx = parts.indexOf(className);
     if (ndx < 0) {
       break;
     }
@@ -415,7 +415,7 @@ function addRemoveClass(elem, className, add) {
 
 function toggleSourcePane(pressedButton) {
   forEachHTMLPart(function(info) {
-    var pressed = pressedButton === info.button;
+    const pressed = pressedButton === info.button;
     if (pressed && !info.showing) {
       addClass(info.button, 'show');
       info.parent.style.display = 'block';
@@ -434,7 +434,7 @@ function showingResultPane() {
   return g.result.style.display !== 'none';
 }
 function toggleResultPane() {
-  var showing = showingResultPane();
+  const showing = showingResultPane();
   g.result.style.display = showing ? 'none' : 'block';
   addRemoveClass(g.resultButton, 'show', !showing);
   showOtherIfAllPanesOff();
@@ -442,7 +442,7 @@ function toggleResultPane() {
 }
 
 function showOtherIfAllPanesOff() {
-  var paneOn = showingResultPane();
+  let paneOn = showingResultPane();
   forEachHTMLPart(function(info) {
     paneOn = paneOn || info.showing;
   });
@@ -450,7 +450,7 @@ function showOtherIfAllPanesOff() {
 }
 
 function getActualLineNumberAndMoveTo(lineNo, colNo) {
-  var actualLineNo = lineNo - g.numLinesBeforeScript;
+  const actualLineNo = lineNo - g.numLinesBeforeScript;
   if (!g.setPosition) {
     // Only set the first position
     g.setPosition = true;
@@ -480,12 +480,12 @@ function runEditor(parent, source, language) {
 }
 
 function start() {
-  var query = getQuery();
-  var parentQuery = getQuery(window.parent.location.search);
-  var isSmallish = window.navigator.userAgent.match(/Android|iPhone|iPod|Windows Phone/i);
-  var isEdge = window.navigator.userAgent.match(/Edge/i);
+  const query = getQuery();
+  const parentQuery = getQuery(window.parent.location.search);
+  const isSmallish = window.navigator.userAgent.match(/Android|iPhone|iPod|Windows Phone/i);
+  const isEdge = window.navigator.userAgent.match(/Edge/i);
   if (isEdge || isSmallish || parentQuery.editor === 'false') {
-    var url = query.url;
+    const url = query.url;
     window.location.href = url;
   } else {
     require.config({ paths: { 'vs': '/monaco-editor/min/vs' }});

+ 94 - 94
threejs/resources/threejs-lessons-helper.js

@@ -44,7 +44,7 @@
 }(this, function() {
   'use strict';  // eslint-disable-line
 
-  var topWindow = this;
+  const topWindow = this;
 
   /**
    * Check if the page is embedded.
@@ -87,10 +87,10 @@
    * @memberOf module:webgl-utils
    */
   function showNeedWebGL(canvas) {
-    var doc = canvas.ownerDocument;
+    const doc = canvas.ownerDocument;
     if (doc) {
-      var div = doc.createElement('div');
-      div.innerHTML = `
+      const temp = doc.createElement('div');
+      temp.innerHTML = `
         <div style="
            position: absolute;
            left: 0;
@@ -110,15 +110,15 @@
           </div>
         </div>
       `;
-      div = div.querySelector('div');
+      const div = temp.querySelector('div');
       doc.body.appendChild(div);
     }
   }
 
-  var origConsole = {};
+  const origConsole = {};
 
   function setupConsole() {
-    var parent = document.createElement('div');
+    const parent = document.createElement('div');
     parent.className = 'console';
     Object.assign(parent.style, {
       fontFamily: 'monospace',
@@ -172,7 +172,7 @@
    * @param {HTMLCanvasElement} canvas a canvas element.
    * @memberOf module:webgl-utils
    */
-  var setupLesson = function(canvas) {
+  let setupLesson = function(canvas) {
     // only once
     setupLesson = function() {};
 
@@ -181,7 +181,7 @@
           // the default is to do nothing. Preventing the default
           // means allowing context to be restored
           e.preventDefault();
-          var div = document.createElement('div');
+          const div = document.createElement('div');
           div.className = 'contextlost';
           div.innerHTML = '<div>Context Lost: Click To Reload</div>';
           div.addEventListener('click', function() {
@@ -210,9 +210,9 @@
     if (!isInIFrame(window)) {
       return;
     }
-    var iframes = window.parent.document.getElementsByTagName('iframe');
-    for (var ii = 0; ii < iframes.length; ++ii) {
-      var iframe = iframes[ii];
+    const iframes = window.parent.document.getElementsByTagName('iframe');
+    for (let ii = 0; ii < iframes.length; ++ii) {
+      const iframe = iframes[ii];
       if (iframe.contentDocument === window.document) {
         return iframe;  // eslint-disable-line
       }
@@ -227,14 +227,14 @@
    */
   function isFrameVisible(window) {
     try {
-      var iframe = getIFrameForWindow(window);
+      const iframe = getIFrameForWindow(window);
       if (!iframe) {
         return true;
       }
 
-      var bounds = iframe.getBoundingClientRect();
-      var isVisible = bounds.top < window.parent.innerHeight && bounds.bottom >= 0 &&
-                      bounds.left < window.parent.innerWidth && bounds.right >= 0;
+      const bounds = iframe.getBoundingClientRect();
+      const isVisible = bounds.top < window.parent.innerHeight && bounds.bottom >= 0 &&
+                        bounds.left < window.parent.innerWidth && bounds.right >= 0;
 
       return isVisible && isFrameVisible(window.parent);
     } catch (e) {
@@ -248,10 +248,10 @@
    * @return {boolean} true if element is on screen.
    */
   function isOnScreen(element) {
-    var isVisible = true;
+    let isVisible = true;
 
     if (element) {
-      var bounds = element.getBoundingClientRect();
+      const bounds = element.getBoundingClientRect();
       isVisible = bounds.top < topWindow.innerHeight && bounds.bottom >= 0;
     }
 
@@ -263,7 +263,7 @@
     topWindow.requestAnimationFrame = (function(oldRAF) {
 
       return function(callback, element) {
-        var handler = function() {
+        const handler = function() {
           if (isOnScreen(element)) {
             oldRAF(callback, element);
           } else {
@@ -306,19 +306,19 @@
   /**
    * Types of contexts we have added to map
    */
-  var mappedContextTypes = {};
+  const mappedContextTypes = {};
 
   /**
    * Map of numbers to names.
    * @type {Object}
    */
-  var glEnums = {};
+  const glEnums = {};
 
   /**
    * Map of names to numbers.
    * @type {Object}
    */
-  var enumStringToValue = {};
+  const enumStringToValue = {};
 
   /**
    * Initializes this module. Safe to call more than once.
@@ -329,7 +329,7 @@
   function addEnumsForContext(ctx, type) {
     if (!mappedContextTypes[type]) {
       mappedContextTypes[type] = true;
-      for (var propertyName in ctx) {
+      for (const propertyName in ctx) {
         if (typeof ctx[propertyName] === 'number') {
           glEnums[ctx[propertyName]] = propertyName;
           enumStringToValue[propertyName] = ctx[propertyName];
@@ -339,9 +339,9 @@
   }
 
   function enumArrayToString(enums) {
+    const enumStrings = [];
     if (enums.length) {
-      var enumStrings = [];
-      for (var i = 0; i < enums.length; ++i) {
+      for (let i = 0; i < enums.length; ++i) {
         enums.push(glEnumToString(enums[i]));  // eslint-disable-line
       }
       return '[' + enumStrings.join(', ') + ']';
@@ -351,10 +351,10 @@
 
   function makeBitFieldToStringFunc(enums) {
     return function(value) {
-      var orResult = 0;
-      var orEnums = [];
-      for (var i = 0; i < enums.length; ++i) {
-        var enumValue = enumStringToValue[enums[i]];
+      let orResult = 0;
+      const orEnums = [];
+      for (let i = 0; i < enums.length; ++i) {
+        const enumValue = enumStringToValue[enums[i]];
         if ((value & enumValue) !== 0) {
           orResult |= enumValue;
           orEnums.push(glEnumToString(enumValue));  // eslint-disable-line
@@ -368,7 +368,7 @@
     };
   }
 
-  var destBufferBitFieldToString = makeBitFieldToStringFunc([
+  const destBufferBitFieldToString = makeBitFieldToStringFunc([
     'COLOR_BUFFER_BIT',
     'DEPTH_BUFFER_BIT',
     'STENCIL_BUFFER_BIT',
@@ -388,7 +388,7 @@
    *
    * @type {!Object.<number, (!Object.<number, string>|function)}
    */
-  var glValidEnumContexts = {
+  const glValidEnumContexts = {
     // Generic setters and getters
 
     'enable': {1: { 0:true }},
@@ -598,7 +598,7 @@
    * @return {string} The string version of the enum.
    */
   function glEnumToString(value) {
-    var name = glEnums[value];
+    const name = glEnums[value];
     return (name !== undefined) ? ('gl.' + name) :
         ('/*UNKNOWN WebGL ENUM*/ 0x' + value.toString(16) + '');
   }
@@ -613,11 +613,11 @@
    * @return {string} The value as a string.
    */
   function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
-    var funcInfos = glValidEnumContexts[functionName];
+    const funcInfos = glValidEnumContexts[functionName];
     if (funcInfos !== undefined) {
-      var funcInfo = funcInfos[numArgs];
+      const funcInfo = funcInfos[numArgs];
       if (funcInfo !== undefined) {
-        var argType = funcInfo[argumentIndex];
+        const argType = funcInfo[argumentIndex];
         if (argType) {
           if (typeof argType === 'function') {
             return argType(value);
@@ -646,9 +646,9 @@
    */
   function glFunctionArgsToString(functionName, args) {
     // apparently we can't do args.join(",");
-    var argStrs = [];
-    var numArgs = args.length;
-    for (var ii = 0; ii < numArgs; ++ii) {
+    const argStrs = [];
+    const numArgs = args.length;
+    for (let ii = 0; ii < numArgs; ++ii) {
       argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
     }
     return argStrs.join(', ');
@@ -684,30 +684,30 @@
    */
   function makeDebugContext(ctx, options) {
     options = options || {};
-    var errCtx = options.errCtx || ctx;
-    var onFunc = options.funcFunc;
-    var sharedState = options.sharedState || {
+    const errCtx = options.errCtx || ctx;
+    const onFunc = options.funcFunc;
+    const sharedState = options.sharedState || {
       numDrawCallsRemaining: options.maxDrawCalls || -1,
       wrappers: {},
     };
     options.sharedState = sharedState;
 
-    var errorFunc = options.errorFunc || function(err, functionName, args) {
+    const errorFunc = options.errorFunc || function(err, functionName, args) {
       console.error("WebGL error " + glEnumToString(err) + " in " + functionName +  // eslint-disable-line
           '(' + glFunctionArgsToString(functionName, args) + ')');
     };
 
     // Holds booleans for each GL error so after we get the error ourselves
     // we can still return it to the client app.
-    var glErrorShadow = { };
-    var wrapper = {};
+    const glErrorShadow = { };
+    const wrapper = {};
 
     function removeChecks() {
       Object.keys(sharedState.wrappers).forEach(function(name) {
-        var pair = sharedState.wrappers[name];
-        var wrapper = pair.wrapper;
-        var orig = pair.orig;
-        for (var propertyName in wrapper) {
+        const pair = sharedState.wrappers[name];
+        const wrapper = pair.wrapper;
+        const orig = pair.orig;
+        for (const propertyName in wrapper) {
           if (typeof wrapper[propertyName] === 'function') {
             wrapper[propertyName] = orig[propertyName].bind(orig);
           }
@@ -727,13 +727,13 @@
 
     // Makes a function that calls a WebGL function and then calls getError.
     function makeErrorWrapper(ctx, functionName) {
-      var check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
+      const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
       return function() {
         if (onFunc) {
           onFunc(functionName, arguments);
         }
-        var result = ctx[functionName].apply(ctx, arguments);
-        var err = errCtx.getError();
+        const result = ctx[functionName].apply(ctx, arguments);
+        const err = errCtx.getError();
         if (err !== 0) {
           glErrorShadow[err] = true;
           errorFunc(err, functionName, arguments);
@@ -745,12 +745,12 @@
 
     function makeGetExtensionWrapper(ctx, wrapped) {
       return function() {
-        var extensionName = arguments[0];
-        var ext = sharedState.wrappers[extensionName];
+        const extensionName = arguments[0];
+        let ext = sharedState.wrappers[extensionName];
         if (!ext) {
           ext = wrapped.apply(ctx, arguments);
           if (ext) {
-            var origExt = ext;
+            const origExt = ext;
             ext = makeDebugContext(ext, options);
             sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
             addEnumsForContext(origExt, extensionName);
@@ -762,12 +762,12 @@
 
     // Make a an object that has a copy of every property of the WebGL context
     // but wraps all functions.
-    for (var propertyName in ctx) {
+    for (const propertyName in ctx) {
       if (typeof ctx[propertyName] === 'function') {
         if (propertyName !== 'getExtension') {
           wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
         } else {
-          var wrapped = makeErrorWrapper(ctx, propertyName);
+          const wrapped = makeErrorWrapper(ctx, propertyName);
           wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
         }
       } else {
@@ -778,7 +778,7 @@
     // Override the getError function with one that returns our saved results.
     if (wrapper.getError) {
       wrapper.getError = function() {
-        for (var err in glErrorShadow) {
+        for (const err in glErrorShadow) {
           if (glErrorShadow.hasOwnProperty(err)) {
             if (glErrorShadow[err]) {
               glErrorShadow[err] = false;
@@ -803,11 +803,11 @@
   function captureJSErrors() {
     // capture JavaScript Errors
     window.addEventListener('error', function(e) {
-      var msg = e.message || e.error;
-      var url = e.filename;
-      var lineNo = e.lineno || 1;
-      var colNo = e.colno || 1;
-      var isUserScript = (url === window.location.href);
+      const msg = e.message || e.error;
+      const url = e.filename;
+      let lineNo = e.lineno || 1;
+      const colNo = e.colno || 1;
+      const isUserScript = (url === window.location.href);
       if (isUserScript) {
         try {
           lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
@@ -822,8 +822,8 @@
 
   // adapted from http://stackoverflow.com/a/2401861/128511
   function getBrowser() {
-    var userAgent = navigator.userAgent;
-    var m = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
+    const userAgent = navigator.userAgent;
+    let m = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
     if (/trident/i.test(m[1])) {
       m = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
       return {
@@ -832,7 +832,7 @@
       };
     }
     if (m[1] === 'Chrome') {
-      var temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/);
+      const temp = userAgent.match(/\b(OPR|Edge)\/(\d+)/);
       if (temp) {
         return {
           name: temp[1].replace('OPR', 'Opera'),
@@ -841,7 +841,7 @@
       }
     }
     m = m[2] ? [m[1], m[2]] : [navigator.appName, navigator.appVersion, '-?'];
-    var version = userAgent.match(/version\/(\d+)/i);
+    const version = userAgent.match(/version\/(\d+)/i);
     if (version) {
       m.splice(1, 1, version[1]);
     }
@@ -851,16 +851,16 @@
     };
   }
 
-  var isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
+  const isWebGLRE = /^(webgl|webgl2|experimental-webgl)$/i;
   function installWebGLLessonSetup() {
     HTMLCanvasElement.prototype.getContext = (function(oldFn) {
       return function() {
-        var type = arguments[0];
-        var isWebGL = isWebGLRE.test(type);
+        const type = arguments[0];
+        const isWebGL = isWebGLRE.test(type);
         if (isWebGL) {
           setupLesson(this);
         }
-        var ctx = oldFn.apply(this, arguments);
+        const ctx = oldFn.apply(this, arguments);
         if (!ctx && isWebGL) {
           showNeedWebGL(this);
         }
@@ -873,16 +873,16 @@
     // capture GL errors
     HTMLCanvasElement.prototype.getContext = (function(oldFn) {
       return function() {
-        var ctx = oldFn.apply(this, arguments);
+        let ctx = oldFn.apply(this, arguments);
         // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext
         // but that might fail if wrapped by debugging extension
         if (ctx && ctx.bindTexture) {
           ctx = makeDebugContext(ctx, {
             maxDrawCalls: 100,
             errorFunc: function(err, funcName, args) {
-              var numArgs = args.length;
-              var enumedArgs = [].map.call(args, function(arg, ndx) {
-                var str = glFunctionArgToString(funcName, numArgs, ndx, arg);
+              const numArgs = args.length;
+              const enumedArgs = [].map.call(args, function(arg, ndx) {
+                let str = glFunctionArgToString(funcName, numArgs, ndx, arg);
                 // shorten because of long arrays
                 if (str.length > 200) {
                   str = str.substring(0, 200) + '...';
@@ -890,18 +890,18 @@
                 return str;
               });
 
-              var browser = getBrowser();
-              var lineNdx;
-              var matcher;
+              const browser = getBrowser();
+              let lineNdx;
+              let matcher;
               if ((/chrome|opera/i).test(browser.name)) {
                 lineNdx = 3;
                 matcher = function(line) {
-                  var m = /at ([^(]+)*\(*(.*?):(\d+):(\d+)/.exec(line);
+                  const m = /at ([^(]+)*\(*(.*?):(\d+):(\d+)/.exec(line);
                   if (m) {
-                    var userFnName = m[1];
-                    var url = m[2];
-                    var lineNo = parseInt(m[3]);
-                    var colNo = parseInt(m[4]);
+                    let userFnName = m[1];
+                    let url = m[2];
+                    const lineNo = parseInt(m[3]);
+                    const colNo = parseInt(m[4]);
                     if (url === '') {
                       url = userFnName;
                       userFnName = '';
@@ -918,11 +918,11 @@
               } else if ((/firefox|safari/i).test(browser.name)) {
                 lineNdx = 2;
                 matcher = function(line) {
-                  var m = /@(.*?):(\d+):(\d+)/.exec(line);
+                  const m = /@(.*?):(\d+):(\d+)/.exec(line);
                   if (m) {
-                    var url = m[1];
-                    var lineNo = parseInt(m[2]);
-                    var colNo = parseInt(m[3]);
+                    const url = m[1];
+                    const lineNo = parseInt(m[2]);
+                    const colNo = parseInt(m[3]);
                     return {
                       url: url,
                       lineNo: lineNo,
@@ -933,21 +933,21 @@
                 };
               }
 
-              var lineInfo = '';
+              let lineInfo = '';
               if (matcher) {
                 try {
-                  var error = new Error();
-                  var lines = error.stack.split('\n');
+                  const error = new Error();
+                  const lines = error.stack.split('\n');
                   // window.fooLines = lines;
                   // lines.forEach(function(line, ndx) {
                   //   origConsole.log("#", ndx, line);
                   // });
-                  var info = matcher(lines[lineNdx]);
+                  const info = matcher(lines[lineNdx]);
                   if (info) {
-                    var lineNo = info.lineNo;
-                    var colNo = info.colNo;
-                    var url = info.url;
-                    var isUserScript = (url === window.location.href);
+                    let lineNo = info.lineNo;
+                    const colNo = info.colNo;
+                    const url = info.url;
+                    const isUserScript = (url === window.location.href);
                     if (isUserScript) {
                       lineNo = window.parent.getActualLineNumberAndMoveTo(lineNo, colNo);
                     }

+ 1 - 2
threejs/threejs-primitives.html

@@ -183,7 +183,6 @@ function main() {
       u = u * 2;
 
       let x;
-      let y;
       let z;
 
       if (u < Math.PI) {
@@ -194,7 +193,7 @@ function main() {
           z = -8 * Math.sin(u);
       }
 
-      y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);
+      const y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);
 
       target.set(x, y, z).multiplyScalar(0.75);
     }