Skip to content

ESM/Vite SSR compatibility issue - "is not a function" error with import * as matter #181

@Malay-dev

Description

@Malay-dev

Description

Problem

When using gray-matter in an ESM project with Vite SSR (e.g., Astro), the module cannot be properly imported and called.

Environment

  • Node.js version: v20+
  • gray-matter version: 4.0.3
  • Bundler: Vite 6.x (via Astro 5.16.0)
  • Project type: ESM ("type": "module" in package.json)
  • TypeScript: 5.9.3
  • tsconfig: esModuleInterop: true, allowSyntheticDefaultImports: true

Error

TypeError: (0 , __vite_ssr_import_2__) is not a function
    at parseFrontmatter (eval at runInlinedModule (.../vite/dist/node/module-runner.js:1062:11), <anonymous>:73:45)

Reproduction Steps

  1. Create an ESM project with Vite or Astro:

    npm create astro@latest
  2. Install gray-matter:

    npm install gray-matter
  3. Import and use in a TypeScript file:

    import * as matter from 'gray-matter';
    // or
    import matter from 'gray-matter';
    
    const result = matter('---\ntitle: Test\n---\nContent');
    console.log(result.data); // Expected: { title: 'Test' }
  4. Run in Vite SSR mode:

    npm run dev

Expected Behavior

The import should work and matter() should be callable as a function.

Actual Behavior

  • import matter from 'gray-matter' fails with TypeScript error: "module has no default export"
  • import * as matter from 'gray-matter' imports a namespace object where matter() is not callable at runtime
  • The following workaround is required:
    const fn = (matter as any).default || matter;
    const result = fn(content);

Root Cause Analysis

The package only exports via CommonJS (module.exports = matter) with:

{
  "main": "index.js"
}

Missing:

  • No exports field for proper ESM support
  • No module field for bundlers
  • No .mjs entry point

The type definition uses export = matter which is the CommonJS-style export:

// gray-matter.d.ts line 114
export = matter

Suggested Fix

Add ESM support via the exports field in package.json:

{
  "main": "index.js",
  "module": "index.mjs",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js",
      "types": "./gray-matter.d.ts"
    }
  }
}

And create an ESM wrapper (index.mjs):

import matter from './index.js';
export default matter;
export const stringify = matter.stringify;
export const read = matter.read;
export const test = matter.test;
export const language = matter.language;

Update type definitions to support both:

// For ESM default import
declare const matter: MatterFunction;
export default matter;

// For named exports
export { stringify, read, test, language };

Current Workaround

import * as matter from 'gray-matter';

function parseFrontmatter(content: string) {
    // With `import * as matter`, the function may be on matter.default
    const parse = (matter as any).default || matter;
    const result = parse(content);
    return { meta: result.data, body: result.content };
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions