Intro
For those unfamiliar, Gutenberg Blocks, or now known simply as Blocks, are the backbone behind the new reusable component-driven paradigm for WordPress.
For more info on Gutenberg, you can check out WordPress’s landing page.
Now, if you are reading this, there is a good chance that you are a WordPress developer, maybe even already familiar with Blocks and how they work. There are lots of dev guides out there on how to use Blocks (including my own notes, here), but I was recently having some issues using Blocks with the TypeScript programming language (which gets converted into JavaScript) and found much less out there to guide my way.
This post covers some steps I had to take to get TypeScript working with WP Gutenberg Blocks, as well as some issues that I ran into.
Steps
The first step, whether or not you end up using TypeScript, I would recommend to be to install and start using wp-scripts
(source code, docs) for orchestrating your build setup. You don’t have to do this, but it makes things a lot easier while still being extensible via configuration files and CLI arguments.
Now, assuming that you are using the wp-scripts
, or something similar, here are the general steps I had to take to get TypeScript working
- Install TypeScript and
ts-loader
locallynpm i -D ts-loader
npm i -D typescript
- Create a
tsconfig.json
file and adjust it (see below) - Modify the WebPack config to use
ts-loader
(see below) - Install types for packages that distribute them separately (i.e. through the
@types
monorepo) - Ambient type-declare for packages that are missing them (see below)
TSConfig
Since your WebPack config will override a lot of settings of your TSConfig, there is less to worry about, but still some settings that really matter.
{
"compilerOptions": {
// Note: Webpack config overrides this anyways
"outDir": "./dist",
// React compatibility
"jsx": "react-jsx",
// We want ESM supported in the output, because WebPack is going to bundle this anyways,
// and we want the tree-shaking support and some other benefits
"module": "ESNext",
"target": "ES5",
// == Now for some settings that are not *strictly* required, but I recommend:
// Embrace strong-typing!
"strict": true,
// Not strictly necessary, but nice
"allowSyntheticDefaultImports": true,
// Recommended
"moduleResolution": "node",
}
}
Modifying the WebPack Config
📄 WebPack has a guide on using TypeScript with WebPack.
For actually generating our final code that will be distributed, we are still going to be using WebPack, not the TypeScript compiler (tsc
). However, we have to tell WebPack about the TypeScript code we have added and give it a tool to load it – that is why we added ts-loader
as a devDependency.
Luckily for us, wp-scripts
not only allows us to use a custom WebPack config, but also exports the base config so that we can extend it instead of replacing it.
webpack.config.js
// This comes from https://github.com/WordPress/gutenberg/blob/trunk/packages/scripts/config/webpack.config.js
const wpConfig = require( '@wordpress/scripts/config/webpack.config' );
// Modified config
module.exports = {
// Since whatever object we export from this file will *replace* the default config, we need to
// make sure to constantly pull in the default values from WordPress's config file
...wpConfig,
// Point to your main file, wherever it is, which should now be TS
entry: `./src/index.ts`,
// We need to add a new rule to process `.ts` and `.tsx` files with `ts-loader
module: {
...defaultConfig.module,
rules: [
...defaultConfig.module.rules,
{
// Notice that this regex matches both `.ts` and `.tsx`
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
// You can specify any custom config
configFile: 'tsconfig.json',
// See note under "issues" for details
// Speeds up by skipping type-checking. You can still use TSC for that.
transpileOnly: true,
},
},
],
},
],
},
// This merges the extensions setting with whatever WP has in their config, but also adds TS
// extensions, and puts them first, to indicate preferred resolving over .js files
// @see https://webpack.js.org/configuration/resolve/
resolve: {
extensions: [ '.ts', '.tsx', ...(defaultConfig.resolve ? defaultConfig.resolve.extensions || ['.js', '.jsx'] : [])],
},
}
Issues
Other than getting the configuration setup, there were a few issues that I ran into that I feel would be helpful to share.
Untyped packages
- Some WordPress packages offer type definitions (either bundled directly, or, very frequently, distributed via
@types
, such as@types/wordpress__blocks
). However, there are still a bunch that are completely “untyped”, which will throw all kinds of errors in TypeScript, especially if you have strict rules on. - To address this, you either need to wait for types to be added upstream, do it yourself and make a PR to merge them (help the open-source community!), or, if you are in a rush, you can augment the types by using ambient declaration files.
- I’m not going to go into detail on how to do that here, since it is complicated, but I do have a comprehensive section on this in my TypeScript cheatsheet!
TS-Loader: Refuses to Stop Type-Checking node_modules
One frustrating issue that I ran into was that I simply could not get my build command to stop type-checking node_modules
– this is a big deal because A) it slows everything down, and B) it actually finds errors and will throw an exception. But it’s not my code!
Trying all the common settings had no effect, such as explicitly excluding node_modules through tsconfig
, excluding through the WebPack config, and passing explicit inputs.
However, I did eventually find the culprit, as well as two solutions.
The culprit for me was that I was using "checkJs": true
in my tsconfig.json
file, which apparently causes ts-loader
to ignore the exclude rules and start checking node_modules
. Although this issue is marked “Closed” on Github, I can assure you it still exists (even with TypeScript version 4.2+
).
Solutions:
- Recommendation: Run
ts-loader
with thetranspileOnly
setting changed totrue
.- This stops WebPack from type-checking your code entirely, which means that you should run
tsc
separately withtsc --noEmit
when you want to type-check - This also speeds up build and reload times!
- This stops WebPack from type-checking your code entirely, which means that you should run
- You can create a separate TS config file just to use with
ts-loader
, such astsconfig.webpack.json
, which has"checkJs": false
- Instead of copying and pasting the other settings, you can use TSC’s support for
extends
- Pass the selected config to
ts-loader
via theoptions.configFile
setting
- Instead of copying and pasting the other settings, you can use TSC’s support for
Using TypeScript Changed The Output Filenames
The last interesting issue that I encountered was that changing the entry point of my WebPack config to point to a TypeScript file changed all the output filenames from index.*
to main.*
. I’m not sure exactly what caused this internally (my guess would be something hidden in the WordPress Webpack base config that handles chunk names), but the fix was easy enough.
Just make sure that in the PHP side, when you register and enqueue assets for your block, you use the updated filenames that show up in the build output.
If you really want to keep the original filenames, you can override the output settings in the WebPack config: see how this sample repo solved it.
If you are looking for more resources on TypeScript + Blocks, there aren’t many guides out there, but this blog post by Kazmierczak is a nice succinct explanation, and they also have a sample repo.
Feel free to let me know if I’ve missed anything or you have tips of your own to share! Hope this helps someone!
There is a problem with the `webpack.config.js`: all instances of `defaultConfig` should be replaced with `wpConfig`.
To fix the filename problem, just add these lines to `module.exports` in `webpack.config.js`:
“`output: {
filename: “index.js”,
path: path.resolve(__dirname, “build”),
}“`
Don’t forget to `require(“path”)`.
Although by following this tutorial, while using `wp-scripts start` I get an error: `Uncaught TypeError: ReactDebugCurrentFrame$1 is undefined`. `wp-scripts build` works fine.