Skip to content

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

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 };
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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