Structures and Arrays

A struct groups values into a new type whose name doubles as a constructor; no typedef is needed. GLSL ES 1.00 arrays cannot be initialized or const, and fragment shaders are guaranteed only constant or loop-index subscripts. GLSL ES 3.00 adds array constructors, const arrays, .length() and any integer subscript, but no arrays of arrays (GLSL ES 3.10):

A light structure and array operationsJavaScript
const lights = `
  struct Light { vec3 color; float intensity; };
  const Light lamps[2] = Light[2](Light(vec3(1.0, 0.9, 0.7), 0.8),
                                  Light(vec3(0.3, 0.5, 1.0), 0.4));`;
show(`vec3 sum = vec3(0.0);
      for (int i = 0; i < lamps.length(); i++) sum += lamps[i].color * lamps[i].intensity;
      result = vec4(sum, float(lamps.length()));`, lights);
show('float a[2][2]; result = vec4(0.0);');
Output
0.92, 0.92, 0.96, 2
ERROR: 0:3: '[]' : arrays of arrays supported in GLSL ES 3.10 and above only

Vertex shader inputs cannot be arrays or structs, and fragment shader outputs cannot be structs.

A const array of Light structs looped with .length() to light a wall of book spinesHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; }
  canvas { display: block; width: 100%; max-width: 600px; }
</style>
<canvas id="view" width="600" height="300"></canvas>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const vs = `#version 300 es
void main() {           // a single triangle that covers the whole canvas
  gl_Position = vec4(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0, 0.0, 1.0);
}`;
const fs = `#version 300 es
precision highp float;
uniform float uTime;
out vec4 outColor;

// A struct's name doubles as its constructor.
struct Light { vec3 color; float intensity; vec2 position; };

// GLSL ES 3.00: const arrays with an array constructor.
const Light lamps[3] = Light[3](
  Light(vec3(1.0, 0.85, 0.6), 0.9, vec2(110.0, 250.0)),
  Light(vec3(0.35, 0.55, 1.0), 0.7, vec2(300.0, 270.0)),
  Light(vec3(1.0, 0.45, 0.35), 0.8, vec2(490.0, 250.0)));

const vec3 spines[5] = vec3[](vec3(0.12, 0.37, 0.55), vec3(0.36, 0.25, 0.6),
  vec3(0.88, 0.6, 0.06), vec3(0.25, 0.49, 0.23), vec3(0.71, 0.27, 0.18));

void main() {
  vec2 p = gl_FragCoord.xy;
  // Book spines: pick a colour from the array with a computed (non-constant) index.
  int book = int(p.x / 30.0);
  float height = 150.0 + 40.0 * fract(sin(float(book) * 12.9898) * 43758.5);
  vec3 base = p.y > 40.0 && p.y < 40.0 + height && mod(p.x, 30.0) > 3.0
    ? spines[book % spines.length()]
    : vec3(0.82, 0.78, 0.7);                       // the wall behind the books
  if (p.y < 40.0) base = vec3(0.5, 0.36, 0.24);   // the shelf board

  vec3 light = vec3(0.4);                          // ambient
  for (int i = 0; i < lamps.length(); i++) {       // .length() is new in GLSL ES 3.00
    vec2 pos = lamps[i].position + vec2(sin(uTime + float(i) * 2.0) * 40.0, 0.0);
    float d = distance(p, pos) / 180.0;
    light += lamps[i].color * lamps[i].intensity / (1.0 + d * d * 4.0);
  }
  outColor = vec4(base * light, 1.0);
}`;
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
  const s = gl.createShader(type);
  gl.shaderSource(s, src); gl.compileShader(s);
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
  gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
gl.bindVertexArray(gl.createVertexArray());
const uTime = gl.getUniformLocation(program, 'uTime');
function frame(ms) {
  gl.uniform1f(uTime, ms * 0.001);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>