Module Pattern logo

Module Pattern

Organization
PatternsDev
module-pattern

Teaches the module pattern for code organization and encapsulation. Use when structuring JavaScript into reusable, maintainable pieces with clear public and private boundaries.

Overview

PublisherPatternsDev
Repositoryskills
Skill namemodule-pattern
Stars
250
Forks
27
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by PatternsDev on GitHub. Read the source before you install it.

Installation

Install the Module Pattern AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/PatternsDev/skills.git /tmp/skills
mkdir -p .claude/skills
cp -r /tmp/skills/javascript/module-pattern .claude/skills/module-pattern
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Module Pattern in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Module Pattern on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Module Pattern is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Module Pattern

Table of Contents

As your application and codebase grow, it becomes increasingly important to keep your code maintainable and separated. The module pattern allows you to split up your code into smaller, reusable pieces.

Besides being able to split your code into smaller reusable pieces, modules allow you to keep certain values within your file private. Declarations within a module are scoped (encapsulated) to that module, by default. If we don't explicitly export a certain value, that value is not available outside that module. This reduces the risk of name collisions for values declared in other parts of your codebase, since the values are not available on the global scope.

When to Use

  • Use this when you need to organize code into maintainable, encapsulated units
  • This is helpful when you want to keep certain values private to a module and avoid global scope pollution
  • Use this to enable tree-shaking and reduce bundle sizes

When NOT to Use

  • When ES2015 native modules with static import/export are available — prefer static imports for better tooling and tree-shaking
  • When the IIFE-based module pattern is used purely for encapsulation in a codebase that already uses a bundler
  • For trivial scripts where module overhead adds unnecessary complexity

Instructions

  • Use ES2015 import/export syntax for module definitions
  • Use named exports for multiple values and default exports for the primary value of a module
  • Keep non-exported values private to reduce naming collision risks
  • Use dynamic import() for on-demand module loading to reduce initial bundle size

Details

ES2015 Modules

ES2015 introduced built-in JavaScript modules. A module is a file containing JavaScript code, with some difference in behavior compared to a normal script.

Let's look at an example of a module called math.js, containing mathematical functions.

js
export function add(x, y) {
  return x + y;
}

export function multiply(x) {
  return x * 2;
}

export function subtract(x, y) {
  return x - y;
}

export function square(x) {
  return x * x;
}

We have a math.js file containing some simple mathematical logic. We have functions that allow users to add, multiply, subtract, and get the square of values that they pass.

In order to make the functions from math.js available to other files, we first have to export them. In order to export code from a module, we can use the export keyword. One way of exporting the functions, is by using named exports: we can simply add the export keyword in front of the parts that we want to publicly expose.

We can then import the values in another file using the import keyword. To let JavaScript know from which module we want to import these functions, we need to add a from value and the relative path to the module.

js
import { add, multiply, subtract, square } from "./math.js";

A great benefit of having modules, is that we only have access to the values that we explicitly exported using the export keyword. Values that we didn't explicitly export using the export keyword, are only available within that module.

Let's create a value that should only be referenceable within the math.js file, called privateValue.

js
const privateValue = "This is a value private to the module!";

export function add(x, y) {
  return x + y;
}

export function multiply(x) {
  return x * 2;
}

export function subtract(x, y) {
  return x - y;
}

export function square(x) {
  return x * x;
}

Notice how we didn't add the export keyword in front of privateValue. Since we didn't export the privateValue variable, we don't have access to this value outside of the math.js module!

By keeping the value private to the module, there is a reduced risk of accidentally polluting the global scope. You don't have to fear that you will accidentally overwrite values created by developers using your module, that may have had the same name as your private value: it prevents naming collisions.

Sometimes, the names of the exports could collide with local values. In this case, we can rename the imported values, by using the as keyword.

js
import {
  add as addValues,
  multiply as multiplyValues,
  subtract,
  square,
} from "./math.js";

function add(...args) {
  return args.reduce((acc, cur) => cur + acc);
}

function multiply(...args) {
  return args.reduce((acc, cur) => cur * acc);
}

/* From math.js module */
addValues(7, 8);
multiplyValues(8, 9);
subtract(10, 3);
square(3);

/* From index.js file */
add(8, 9, 2, 10);
multiply(8, 9, 2, 10);

Besides named exports, you can also use a default export. You can only have one default export per module.

js
export default function add(x, y) {
  return x + y;
}

export function multiply(x) {
  return x * 2;
}

export function subtract(x, y) {
  return x - y;
}

export function square(x) {
  return x * x;
}

The difference between named exports and default exports, is the way the value is exported from the module, effectively changing the way we have to import the value.

Previously, we had to use the brackets for our named exports: import { module } from 'module'. With a default export, we can import the value without the brackets: import module from 'module'.

js
import add, { multiply, subtract, square } from "./math.js";

add(7, 8);
multiply(8, 9);
subtract(10, 3);
square(3);

Since JavaScript knows that this value is always the value that was exported by default, we can give the imported default value another name than the name we exported it with.

We can also import all exports from a module, meaning all named exports and the default export, by using an asterisk * and giving the name we want to import the module as.

js
import * as math from "./math.js";

math.default(7, 8);
math.multiply(8, 9);
math.subtract(10, 3);
math.square(3);

In this case, we're importing all exports from a module. Be careful when doing this, since you may end up unnecessarily importing values.

Using the * only imports all exported values. Values private to the module are still not available in the file that imports the module, unless you explicitly exported them.

React

When building applications with React, you often have to deal with a large amount of components. Instead of writing all of these components in one file, we can separate the components in their own files, essentially creating a module for each component.

We can split components into separate files:

  • TodoList.js for the List component
  • Button.js for the customized Button component
  • Input.js for the customized Input component

Throughout the app, we don't want to use the default Button and Input component, imported from a UI library. Instead, we want to use our custom version of the components, by adding custom styles to it defined in the styles object in their files. Rather than importing the default Button and Input component each time in our application and adding custom styles to it over and over, we can now simply import the default Button and Input component once, add styles, and export our custom component.

Notice how we can have an object called style in both Button.js and Input.js. Since this value is module-scoped, we can reuse the variable name without risking a name collision.

Dynamic import

When importing all modules on the top of a file, all modules get loaded before the rest of the file. In some cases, we only need to import a module based on a certain condition. With a dynamic import, we can import modules on demand.

js
import("module").then((module) => {
  module.default();
  module.namedExport();
});

// Or with async/await
(async () => {
  const module = await import("module");
  module.default();
  module.namedExport();
})();

By dynamically importing modules, we can reduce the page load time. We only have to load, parse, and compile the code that the user really needs, when the user needs it.

Besides being able to import modules on-demand, the import() function can receive an expression. It allows us to pass template literals, in order to dynamically load modules based on a given value.

js
const res = await import(`../assets/dog${num}.png`);

This way, we're not dependent on hard-coded module paths. It adds flexibility to the way you can import modules based on user input, data received from an external source, the result of a function, and so on.

With the module pattern, we can encapsulate parts of our code that should not be publicly exposed. This prevents accidental name collision and global scope pollution, which makes working with multiple dependencies and namespaces less risky. In order to be able to use ES2015 modules in all JavaScript runtimes, a transpiler such as Babel is needed.

Source

Frequently asked questions

What does the Module Pattern AI skill do?

Teaches the module pattern for code organization and encapsulation. Use when structuring JavaScript into reusable, maintainable pieces with clear public and private boundaries.

Why use Module Pattern on TypingMind?

Because you install it once and use it with any model. Module Pattern is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Module Pattern in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/PatternsDev/skills/tree/main/javascript/module-pattern. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Module Pattern?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Module Pattern?

As many as you like. As long as a model supports skills, you can use Module Pattern with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Module Pattern AI skill free?

Yes. It is published on GitHub by PatternsDev under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇