Search code examples
node.jsreactjstypescriptgatsbyyarnpkg

Exporting Global Styles with Font Assets from a TypeScript->CommonJS module


I have a TypeScript React project organized as follows:

tsconfig.json
package.json
yarn.lock
lerna.json
node_modules/
packages/
  ui-library/
    package.json
    tsconfig.json
    typings.d.ts
    src/
      fonts/
        myfont.ttf
      components/
        GlobalStyle.tsx
    lib/ <-- `tsc` builds to here
  web-app/
   package.json
   src/
    components/
     

The web-app imports the ui-library as a commonjs module in its package.json. This is managed through Yarn Workspaces, hence node_modules being at the root instead of each folder. At one point, the web-app imports GlobalStyle from the ui-lib, which is where the issue occurs.

Error: Cannot find module '../fonts/myfont.ttf'
Require stack:
 - /Users/me/sources/myproject/packages/ui-library/lib/styles/GlobalStyle.js...

Here is the original import statement from ui-library/src/GlobalStyle.tsx that causes the issue:

import myFont from "../fonts/myfont.ttf";

const GlobalStyle = () => (
  <style global jsx>{`
    @font-face {
      font-family: "My font family";
      src: url(${myFont}) format("truetype");
    }
  ...</style>);

The issue is the require statement in the emitted GlobalStyle.js code, built when I run tsc in the ui-lib folder:

const MYFONT_ttf_1 = __importDefault(require("./myfont.ttf"));

I googled this issue online, and I found you had to add typings for compiling .ttf and other abnormal imports. So I created a typings.d.ts file with this declaration:

declare module "*.ttf" {
  const value: string;
  export default value;
}

And I included it on ui-library/tsconfig.json as follows:

{
  "extends": "../../tsconfig.json",
  "exclude": ["lib"],
  "include": ["src"],
  "files": ["./typings.d.ts"],
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "lib",
    "declaration": true,
    "declarationMap": true,
    "noEmit": false
  }
}

The tsconfig above extends the root tsconfig:

{
  "files": ["./typings.d.ts"],
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2019",
    "lib": ["dom", "es2019"],
    "strict": true,
    "jsx": "react",
    "moduleResolution": "node",
    "isolatedModules": true,
    "esModuleInterop": true,
    "noUnusedLocals": false,
    "forceConsistentCasingInFileNames": true,
    "strictNullChecks": true,
    "sourceMap": true,
    "noEmit": true,
    "declaration": false
  },
  "exclude": ["node_modules"]
}

We are using TypeScript 3.8. Let me know if there is any additional info to provide. My hunch is that I am either using the wrong module/target, or fundamentally misunderstanding some aspect of this.

More Details I should note the reason we are using CommonJS modules is because ts-node can only work with that. We pull in ts-node when we are doing our gatsby build, following the gist here in gatsby-config.js:

require("ts-node").register();

// Use a TypeScript version of gatsby-config.js.
module.exports = require("./gatsby-config.ts");

Maybe it's an impossible problem to solve, to get fonts imported through ts-node which is a server side environment? Confused on what the right approach is to export a module with fonts. Willing to get rid of ts-node, and leave the Gatsby config files as js.. but mostly just want to be able to import my fonts.


Solution

  • I solved this by just copying the fonts as part of build steps. Basically, fonts have their own pipeline. There may be better ways, but this works well enough.