Skip to content

feat: add npm provider to support locally installed packages #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

rrroy5640
Copy link

Resolves #188

Description

This PR adds a new npm provider that allows Unifont to resolve fonts from locally installed npm packages (especially @fontsource packages) before attempting to fetch them from remote CDNs. This improves performance, reduces network requests, and provides better offline support.

Key Changes

  • Added a new npm provider that scans for installed @fontsource packages in node_modules
  • Implemented a mechanism to extract font information from local CSS files
  • Added a preferLocal option to Unifont configuration, which prioritizes the npm provider when set to true
  • Created comprehensive unit tests for the new provider
  • Added integration tests to verify the provider works correctly with other providers
  • Provided usage examples in the examples directory

Testing

The implementation includes:

  • Unit tests for the npm provider functionality
  • Integration tests that verify the provider works correctly in isolation and with other providers
  • Manual testing with various font packages

Usage Example

import { createUnifont, providers } from 'unifont'

// Method 1: Only use local fonts
const unifont = await createUnifont([
  providers.npm()
])

// Method 2: Prioritize local fonts, fall back to remote sources
const unifont = await createUnifont(
  [
    providers.npm(),  // Auto-detect installed font packages
    providers.google(), // Fall back to Google Fonts if not found locally
  ],
  { preferLocal: true } // Prioritize local packages
)

// Method 3: Specify which packages to check
const unifont = await createUnifont([
  providers.npm({
    packages: ['@fontsource/roboto', '@fontsource/open-sans']
  })
])

Additional Notes

  • The provider automatically scans for @fontsource packages if no packages are explicitly specified
  • Font URLs are converted to file:// protocol for local access
  • Metadata is added to indicate the font comes from npm and which package it comes from

@rrroy5640 rrroy5640 requested a review from danielroe as a code owner May 22, 2025 02:40
Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks amazing and I can't wait to get it merged!

a few comments.

Comment on lines 38 to 58
async function detectFontsourcePackages(baseDir: string): Promise<string[]> {
try {
// Check if @fontsource directory exists
const fontsourceDir = join(baseDir, '@fontsource')
if (!existsSync(fontsourceDir)) {
return []
}

// Read the directory to find installed font packages
const { readdir } = await import('node:fs/promises')
const files = await readdir(fontsourceDir)

return files.map(file => `@fontsource/${file}`)
}
catch (error) {
// In case of any error, return empty array
console.error('Failed to detect @fontsource packages:', error)
return []
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's instead try to check the package.json of the workspace - we can use pkg-types for this. And ideally I think we can also support other common fonts like cal-sans, geist, etc.

const fontFamily
= pkgJson.fontName
|| pkgJson.fontFamily
|| (pkgJson.name && pkgJson.name.replace(/^@fontsource\//, ''))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to remove the fontsource 'special-handling', if possible, and come up with a solution that works for fontsource but also conceivable supports auto-detection for other providers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for the feedback Daniel, I have just made the changes to support generic multiple font packages :)

@danielroe danielroe changed the title Feature/npm local fonts feat: add npm provider to support locally installed packages Jun 3, 2025
@florian-lefebvre
Copy link
Collaborator

I think this provider shouldn't be only called npm, but still mention fontsource eg. providers.fontsourceNpm

@florian-lefebvre
Copy link
Collaborator

Ah sorry I missed this is supposed to work non fontsource packages too

@qwerzl
Copy link
Collaborator

qwerzl commented Jun 5, 2025

I'm wondering if it's really necessary for us to explicitly support third-party packages like Geist. Perhaps a more scalable approach would be to expose an API like Next.js does and let packages integrate with us instead.

Support for Fontsource packages would be great though, given that they're already a well established and standardized system.

@rrroy5640
Copy link
Author

I'm wondering if it's really necessary for us to explicitly support third-party packages like Geist. Perhaps a more scalable approach would be to expose an API like Next.js does and let packages integrate with us instead.

Support for Fontsource packages would be great though, given that they're already a well established and standardized system.

Yeah that seems more scalable in long term, do you reckon we keep basic support for Fontsource and expose API for other packages? This hybrid approach can potentially bring extra complexity though, I'm not very experienced with this yet, I would let the reviewers make the reviewers make the decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

add 'npm' provider
4 participants