createShader(shaderCode, shaderType) {
        const shader = this.gl.createShader(shaderType);

        this.gl.shaderSource(shader, shaderCode);
        this.gl.compileShader(shader);

        // check shader compilation status only when not in production mode
        if(!this.renderer.production) {
            if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
                // shader debugging log as seen in THREE.js WebGLProgram source code
                const shaderTypeString = shaderType === this.gl.VERTEX_SHADER ? "vertex shader" : "fragment shader";
                const shaderSource = this.gl.getShaderSource(shader);
                let shaderLines = shaderSource.split('\n');

                for(let i = 0; i < shaderLines.length; i++) {
                    shaderLines[i] = (i + 1) + ': ' + shaderLines[i];
                }
                shaderLines = shaderLines.join("\n");

                throwWarning(this.type + ": Errors occurred while compiling the", shaderTypeString, ":\n", this.gl.getShaderInfoLog(shader));
                throwError(shaderLines);

                throwWarning(this.type + ": Will use a default", shaderTypeString);

                // use the library default shaders instead
                return this.createShader(shaderType === this.gl.VERTEX_SHADER ? this.defaultVsCode : this.defaultFsCode, shaderType);
            }
        }

        return shader;
    }
Compile our WebGL shaders based on our written shaders

     params:
     @shaderCode (string): shader code
     @shaderType (shaderType): WebGL shader type (vertex or fragment)

     returns:
     @shader (compiled shader): our compiled shader
*