惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
S
Securelist
MyScale Blog
MyScale Blog
Jina AI
Jina AI
罗磊的独立博客
The Cloudflare Blog
美团技术团队
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
雷峰网
雷峰网
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
博客园 - 聂微东
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MongoDB | Blog
MongoDB | Blog
T
Tailwind CSS Blog
Attack and Defense Labs
Attack and Defense Labs
博客园_首页
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
D
Docker
Webroot Blog
Webroot Blog
N
Netflix TechBlog - Medium
T
Tor Project blog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
B
Blog
Recent Announcements
Recent Announcements
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
Security Latest
Security Latest
V2EX - 技术
V2EX - 技术
N
News | PayPal Newsroom
Microsoft Security Blog
Microsoft Security Blog

Jan Miksovsky’s blog

How and why I journal Code is more concise than configuration: comparing a sample blog in Web Origami and Eleventy Code is more expressive than configuration: comparing a sample blog in Web Origami and Eleventy Code is more coherent than configuration: comparing a sample blog in Web Origami and Eleventy Code is easier to follow than configuration: comparing a sample blog in Web Origami and Eleventy Who else would use a shared Electron library to create and deploy Netlify sites? Promoting a design and development tool through comics 2025 Web Origami year-end report Creating a simple blog in Python with Origami concepts
Fixing the under-appreciated JavaScript Map class and using it to construct a build system
2025-11-19 · via Jan Miksovsky’s blog

JavaScript has a Map class for holding key/value pairs, but it’s underused and underappreciated. If you fix the class’ limitations, you can use Map as a building block to create really interesting things.

A pattern of yellow blocks of color with black edges Photo: Cun Mo, Unsplash

A quick review of Map

The Map class lets you associate keys with values:

const m = new Map();

m.add("a", 1);
m.add("b", 2);

m.get("a"); // 1
m.get("b"); // 2
m.get("c"); // undefined

m.keys(); // "a", "b"
m.values(); // 1, 2

The Map class has a number of advantages over plain objects for storing data, but JavaScript syntax makes it easier to create and work with objects so Map doesn’t get used as often as it should.

Extending Map

Map also has one key advantage over a plain object: Map is a class you can extend, so you can expose any key/value data store as a Map.

For example, we can write a FileMap class that makes the contents of a file system folder available as a Map. This ignores the map’s built-in storage, and instead gets the keys and values from the file system:

import * as fs from "node:fs";
import path from "node:path";

export default class FileMap extends Map {
  constructor(dirname) {
    super();
    this.dirname = path.resolve(process.cwd(), dirname);
  }

  get(key) {
    const filePath = path.resolve(this.dirname, key);
    let stats;
    try {
      stats = fs.statSync(filePath);
    } catch (error) {
      if (error.code === "ENOENT") {
        return undefined; // File not found
      }
      throw error;
    }

    return stats.isDirectory()
      ? new this.constructor(filePath) // Return subdirectory as a map
      : fs.readFileSync(filePath); // Return file contents
  }

  *keys() {
    try {
      yield* fs.readdirSync(this.dirname);
    } catch (error) {
      if (error.code === "ENOENT") {
        // Directory doesn't exist yet; treat as empty
      } else {
        throw error;
      }
    }
  }
}

This is pretty amazing! You can easily work with files in code.

const markdown = new FileMap("./markdown");

markdown.keys(); // ["post1.md", "post2.md", …]
markdown.get("post1.md"); // "This is **post 1**."

Most of the time you work with files in code, you’re just reading a list of files and getting their contents, so something like this is much easier to work with than the full file system API.

Better yet, you can immediately pass this kind of file system Map to any code that understands a Map. That provides a desired separation between data storage and code that works on data.

Limitations to work around

Sadly, the Map class is cumbersome to extend: its standard methods like clear(), entries(), and values() will ignore your get() and keys() methods.

markdown.entries(); // empty array ☹️

For comparison, the Python language provides a Mapping abstract base class that is much more helpful than JavaScript’s Map. When you inherit from Mapping, you only need to define a small set of core methods, and the base class uses your definitions to provide the remaining methods.

To compensate for this and other issues with Map, we can create our own base class that inherits from Map but provides all the expected methods:

export default class BetterMapBase extends Map {
  // Override entries() method to call overridden get() and keys()
  *entries() {
    for (const key of this.keys()) {
      const value = this.get(key);
      yield [key, value];
    }
  }
  …
}

If we derive our FileMap class from this improved base class, the full set of Map methods work as expected:

markdown.entries(); // [["post1.md", <contents>], …] 🤩

Transforming a map

Once your data is in Map form, you can manipulate it in interesting ways without having to worry about how the original data is defined. For example, you can transform a map of markdown to a map of HTML.

const html = new HtmlMap(markdown);

html.keys(); // ["post1.html", "post2.html", …]
html.get("post1.html"); // "<p>This is <strong>post 1</strong>.</p>\n"

Transforming the values of data often implies a transformation of the keys, so maps are ideal representations of such operations.

Creating a build system with maps

This Map approach is a pattern you can explore in more detail. Because it’s a pattern, you can use it without taking on any new dependencies.

But you can take advantage of the Web Origami project’s async-tree library, which includes:

  • A better Map base class along these lines, called SyncMap
  • An asynchronous variant called AsyncMap that can represent network data sources
  • A more complete FileMap class that supports write operations
  • A large collection of map-based operations for concisely expressing transformations like the one shown above

You can use this library to construct things like a build system for generating the static files for a site. A sample blog project uses the library to represent the various stages of the build process as Map-based trees:

1. Content tree → 2. Site tree → 3. Build tree
  1. The source content in the file system is represented as a tree of Map objects.
  2. These are transformed with map-based operations and composed into the desired tree of site resources.
  3. The site tree is copied directly into a Map-based representation of the folder that will hold the build output.

The entire build.js build process is very concise and boils down to a copy operation:

import { FileMap, Tree } from "@weborigami/async-tree";
import site from "./site.js";

// Build process writes the site resources to the build folder
const buildTree = new FileMap(new URL("../build", import.meta.url).pathname);
await Tree.clear(buildTree); // Erase any existing files
await Tree.assign(buildTree, site); // Copy site to build folder

By using Map as the fundamental building block, this sample project generates the site’s static files using only two dependencies, the async-tree library and a markdown processor.

The AsyncMap variant lets you represent a network data source as an object that has the same methods as a Map but the methods are asynchronous. This lets you pull content from sources like Dropbox or Google Drive using a much simpler API, so you can incorporate content directly from the network into your build process.