TypeScript появился еще до того, как в JavaScript, на котором он основан, появились какие либо модули, не то что нативные, даже эмуляций вроде commonjs или amd тогда еще не было. В те времена было в норме просто обернуть содержимое файла в замыкание, а потом либо просто подгрузить все файлы через тэг script либо просто соединить эти файлы в один. Наружу же высвечивалась одна единственная переменная, содержащая все публичное апи такого модуля, ее ложили или в глобальный объект или в другую такую-же переменную.
Конструкции module и namespace позволяют упростить создание таких переменных, избавив разработчика от написания однотипного кода. В этом плане они по сути делают одно и то же.
Сейчас это не рекомендованное использование, и стандартный плагин typescript к eslint с настройками по умолчанию запрещает эти конструкции.
Второе применение - это файлы деклараций.
namespace позволяет объявить объект, но несколько особенный в рамках типизации, он может содержать в себе любые сущности языка и экспортировать не которые из них, в том числе типы, что недоступно для декларации обычного объектного типа.
declare namespace XXX {
// по сути просто поле XXX.a
let a: number;
// то же поле XXX.b но уже const
// современный ts позволяет делать поля readonly, но раньше так было нельзя
const b: number;
// функция, по сути метод XXX.c()
function c(): void;
// а вот вложенный тип через тип объекта не объявить, а в namespace можно
type T = number | string;
}
module позволяют объявить виртуальные модули, о типах которых typescript не знает, например потому что они генерируются сборщиками вроде webpack. Яркий пример тут это css-модули или картинки, которые можно импортировать благодаря webpack, но typescript ничего не знает о их типах, поэтому нужно объявить их в глобальных декларациях:
declare module '*.png' {
const url: string;
export default url;
}
declare module '*.css' {
const classNames: Record<string, string>;
export default classNames;
}