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.
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.