Skip to content

Commit 65520ef

Browse files
authored
Build: Output package type declarations (#18942)
Output TypeScript declaration files (`.d.ts`) from package sources and expose them with published packages. - Upgrade to `typescript@3.8`. - Add bin for typechecking changed packages. - Remove `lint-types` script, replaced by `build:package-types`. - Restructure TypeScript configuration: - One base `tsconfig.base.json` that packages extend. This is not a project, it's only for extension. - A root `tsconfig.json` that references all typed packages. - Typed packages output their types at `build-types` and expose them in the published package. - Use _project references_ to work within the monorepo. See https://www.typescriptlang.org/docs/handbook/project-references.html - Fix/improve some type issues. TypeScript declaration files will be managed via `tsc --build`, the TypeScript compiler build tool for composite projects. `tsc` will manage determine the correct order to build packages so that dependencies are satisfied and rebuild only when necessary. Additional packages can opt-in to typing by (this is documented in a new section in `packages/README.md`): - Adding `tsconfig.json` in their package directory. - Updating their `package.json` to point to the types: `{ "types": "build-types" }`. - Adding a reference to the root `tsconfig.json`.
1 parent 4e17ff0 commit 65520ef

52 files changed

Lines changed: 472 additions & 128 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
build
33
build-module
44
build-style
5+
build-types
56
node_modules
67
gutenberg.zip
78

@@ -14,6 +15,7 @@ yarn.lock
1415

1516
playground/dist
1617
.cache
18+
*.tsbuildinfo
1719

1820
# Report generated from jest-junit
1921
test/native/junit.xml

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ jobs:
114114
script:
115115
- npx eslint --parser-options=ecmaVersion:5 --no-eslintrc --no-ignore ./build/**/*.js
116116

117+
- name: Typecheck
118+
install:
119+
- npm ci
120+
script:
121+
- npm run build:package-types
122+
123+
117124
- name: Build artifacts
118125
install:
119126
# A "full" install is executed, since `npm ci` does not always exit
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const _ = require( 'lodash' );
5+
const path = require( 'path' );
6+
const fs = require( 'fs' );
7+
const execa = require( 'execa' );
8+
9+
/* eslint-disable no-console */
10+
11+
const repoRoot = path.join( __dirname, '..', '..' );
12+
const tscPath = path.join( repoRoot, 'node_modules', '.bin', 'tsc' );
13+
14+
// lint-staged passes full paths to staged changes
15+
const changedFiles = process.argv.slice( 2 );
16+
17+
// Transform changed files to package directories containing tsconfig.json
18+
const changedPackages = _.uniq(
19+
changedFiles.map( ( fullPath ) => {
20+
const relativePath = path.relative( repoRoot, fullPath );
21+
return path.join( ...relativePath.split( path.sep ).slice( 0, 2 ) );
22+
} )
23+
).filter( ( packageRoot ) =>
24+
fs.existsSync( path.join( packageRoot, 'tsconfig.json' ) )
25+
);
26+
27+
try {
28+
execa.sync( tscPath, [ '--build', ...changedPackages ] );
29+
} catch ( err ) {
30+
console.error( err.stdout );
31+
process.exitCode = 1;
32+
}
33+
34+
/* eslint-enable no-console */

bin/tsconfig.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"extends": "../tsconfig.base.json",
3+
"compilerOptions": {
4+
"module": "commonjs",
5+
"esModuleInterop": true,
6+
"target": "ES6",
7+
"lib": [ "ES6", "ES2020.string" ],
8+
"rootDir": ".",
9+
"declarationMap": false,
10+
11+
// We're not interested in the output, but we must generate
12+
// something as part of a composite project. Use the
13+
// ignored `.cache` file to hide tsbuildinfo and d.ts files.
14+
"outDir": ".cache"
15+
},
16+
"files": [ "./api-docs/update-api-docs.js" ]
17+
}

package-lock.json

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@
8686
"@storybook/addon-viewport": "5.3.2",
8787
"@storybook/react": "5.3.2",
8888
"@types/jest": "24.0.25",
89+
"@types/lodash": "4.14.149",
90+
"@types/qs": "6.9.1",
8991
"@types/requestidlecallback": "0.3.1",
92+
"@types/sprintf-js": "1.1.2",
9093
"@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma",
9194
"@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot",
9295
"@wordpress/babel-preset-default": "file:packages/babel-preset-default",
@@ -167,16 +170,18 @@
167170
"sprintf-js": "1.1.1",
168171
"style-loader": "1.0.0",
169172
"stylelint-config-wordpress": "13.1.0",
170-
"typescript": "3.5.3",
173+
"typescript": "3.8.3",
171174
"uuid": "3.3.2",
172175
"webpack": "4.42.0",
173176
"worker-farm": "1.7.0"
174177
},
175178
"scripts": {
176179
"prebuild": "npm run check-engines",
177-
"clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style ./packages/*/node_modules",
180+
"clean:packages": "rimraf \"./packages/*/@(build|build-module|build-style)\"",
181+
"clean:package-types": "tsc --build --clean",
178182
"prebuild:packages": "npm run clean:packages && lerna run build",
179-
"build:packages": "node ./bin/packages/build.js",
183+
"build:packages": "npm run build:package-types && node ./bin/packages/build.js",
184+
"build:package-types": "tsc --build",
180185
"build": "npm run build:packages && wp-scripts build",
181186
"check-engines": "wp-scripts check-engines",
182187
"check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"",
@@ -191,22 +196,21 @@
191196
"fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit",
192197
"fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate",
193198
"format-js": "wp-scripts format-js",
194-
"lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\" \"npm run lint-types\"",
199+
"lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\"",
195200
"lint-js": "wp-scripts lint-js",
196201
"lint-js:fix": "npm run lint-js -- --fix",
197202
"lint-php": "npm run wp-env run composer run-script lint",
198203
"lint-pkg-json": "wp-scripts lint-pkg-json . 'packages/*/package.json'",
199204
"lint-css": "wp-scripts lint-style '**/*.scss'",
200205
"lint-css:fix": "npm run lint-css -- --fix",
201-
"lint-types": "tsc",
202206
"lint:md-js": "wp-scripts lint-md-js",
203207
"lint:md-docs": "wp-scripts lint-md-docs",
204208
"package-plugin": "./bin/build-plugin-zip.sh",
205209
"pot-to-php": "./bin/pot-to-php.js",
206210
"publish:check": "lerna updated",
207-
"publish:dev": "npm run build:packages && lerna publish --dist-tag next",
208-
"publish:legacy": "npm run build:packages && lerna publish --dist-tag legacy",
209-
"publish:prod": "npm run build:packages && lerna publish",
211+
"publish:dev": "npm run clean:package-types && npm run build:packages && lerna publish --dist-tag next",
212+
"publish:legacy": "npm run clean:package-types && npm run build:packages && lerna publish --dist-tag legacy",
213+
"publish:prod": "npm run clean:package-types && npm run build:packages && lerna publish",
210214
"test": "npm run lint && npm run test-unit",
211215
"test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js",
212216
"test-e2e:watch": "npm run test-e2e -- --watch",
@@ -252,7 +256,8 @@
252256
],
253257
"packages/**/*.js": [
254258
"node ./bin/api-docs/update-api-docs.js",
255-
"node ./bin/api-docs/are-api-docs-unstaged.js"
259+
"node ./bin/api-docs/are-api-docs-unstaged.js",
260+
"node ./bin/packages/lint-staged-typecheck.js"
256261
]
257262
},
258263
"wp-env": {

packages/README.md

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,44 @@ This repository uses [lerna] to manage WordPress modules and publish them as pac
77
When creating a new package, you need to provide at least the following:
88

99
1. `package.json` based on the template:
10-
```json
11-
{
12-
"name": "@wordpress/package-name",
13-
"version": "1.0.0-beta.0",
14-
"description": "Package description.",
15-
"author": "The WordPress Contributors",
16-
"license": "GPL-2.0-or-later",
17-
"keywords": [
18-
"wordpress"
19-
],
20-
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md",
21-
"repository": {
22-
"type": "git",
23-
"url": "https://github.com/WordPress/gutenberg.git"
24-
},
25-
"bugs": {
26-
"url": "https://github.com/WordPress/gutenberg/issues"
27-
},
28-
"main": "build/index.js",
29-
"module": "build-module/index.js",
30-
"react-native": "src/index",
31-
"dependencies": {
32-
"@babel/runtime": "^7.8.3"
33-
},
34-
"publishConfig": {
35-
"access": "public"
36-
}
37-
}
38-
```
39-
This assumes that your code is located in the `src` folder and will be transpiled with `Babel`.
10+
```json
11+
{
12+
"name": "@wordpress/package-name",
13+
"version": "1.0.0-beta.0",
14+
"description": "Package description.",
15+
"author": "The WordPress Contributors",
16+
"license": "GPL-2.0-or-later",
17+
"keywords": [ "wordpress" ],
18+
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md",
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/WordPress/gutenberg.git"
22+
},
23+
"bugs": {
24+
"url": "https://github.com/WordPress/gutenberg/issues"
25+
},
26+
"main": "build/index.js",
27+
"module": "build-module/index.js",
28+
"react-native": "src/index",
29+
"dependencies": {
30+
"@babel/runtime": "^7.8.3"
31+
},
32+
"publishConfig": {
33+
"access": "public"
34+
}
35+
}
36+
```
37+
This assumes that your code is located in the `src` folder and will be transpiled with `Babel`.
4038
2. `.npmrc` file which disables creating `package-lock.json` file for the package:
41-
```
42-
package-lock=false
43-
```
39+
```
40+
package-lock=false
41+
```
4442
3. `README.md` file containing at least:
45-
- Package name
46-
- Package description
47-
- Installation details
48-
- Usage example
49-
- `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`)
43+
- Package name
44+
- Package description
45+
- Installation details
46+
- Usage example
47+
- `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`)
5048

5149
## Managing Dependencies
5250

@@ -57,7 +55,7 @@ There are two types of dependencies that you might want to add to one of the exi
5755
Production dependencies are stored in the `dependencies` section of the package’s `package.json` file.
5856

5957
#### Adding New Dependencies
60-
58+
6159
The simplest way to add a production dependency to one of the packages is to run a very convenient [lerna add](https://github.com/lerna/lerna/tree/master/commands/add#readme) command from the root of the project.
6260

6361
_Example:_
@@ -91,9 +89,10 @@ Next, you need to run `npm install` in the root of the project to ensure that `p
9189
#### Updating Existing Dependencies
9290

9391
This is the most confusing part of working with [lerna] which causes a lot of hassles for contributors. The most successful strategy so far is to do the following:
94-
1. First, remove the existing dependency as described in the previous section.
95-
2. Next, add the same dependency back as described in the first section of this chapter. This time it wil get the latest version applied unless you enforce a different version explicitly.
96-
92+
93+
1. First, remove the existing dependency as described in the previous section.
94+
2. Next, add the same dependency back as described in the first section of this chapter. This time it wil get the latest version applied unless you enforce a different version explicitly.
95+
9796
### Development Dependencies
9897

9998
In contrast to production dependencies, development dependencies shouldn't be stored in individual WordPress packages. Instead they should be installed in the project's `package.json` file using the usual `npm install` command. In effect, all development tools are configured to work with every package at the same time to ensure they share the same characteristics and integrate correctly with each other.
@@ -119,16 +118,16 @@ _Example:_
119118

120119
### Bug Fix
121120

122-
- Fixed an off-by-one error with the `sum` function.
121+
- Fixed an off-by-one error with the `sum` function.
123122
```
124123

125124
There are a number of common release subsections you can follow. Each is intended to align to a specific meaning in the context of the [Semantic Versioning (`semver`) specification](https://semver.org/) the project adheres to. It is important that you describe your changes accurately, since this is used in the packages release process to help determine the version of the next release.
126125

127-
- "Breaking Change" - A backwards-incompatible change which requires specific attention of the impacted developers to reconcile (requires a major version bump).
128-
- "New Feature" - The addition of a new backwards-compatible function or feature to the existing public API (requires a minor verison bump).
129-
- "Enhancement" - Backwards-compatible improvements to existing functionality (requires a minor version bump).
130-
- "Bug Fix" - Resolutions to existing buggy behavior (requires a patch version bump).
131-
- "Internal" - Changes which do not have an impact on the public interface or behavior of the module (requires a patch version bump).
126+
- "Breaking Change" - A backwards-incompatible change which requires specific attention of the impacted developers to reconcile (requires a major version bump).
127+
- "New Feature" - The addition of a new backwards-compatible function or feature to the existing public API (requires a minor verison bump).
128+
- "Enhancement" - Backwards-compatible improvements to existing functionality (requires a minor version bump).
129+
- "Bug Fix" - Resolutions to existing buggy behavior (requires a patch version bump).
130+
- "Internal" - Changes which do not have an impact on the public interface or behavior of the module (requires a patch version bump).
132131

133132
While other section naming can be used when appropriate, it's important that are expressed clearly to avoid confusion for both the packages releaser and third-party consumers.
134133

@@ -190,5 +189,61 @@ npm run publish:legacy
190189

191190
This is usually necessary when adding bug fixes or security patches to the earlier versions of WordPress.
192191

192+
## TypeScript
193+
194+
The [TypeScript](http://www.typescriptlang.org/) language is a typed superset of JavaScript that compiles to plain JavaScript.
195+
Gutenberg does not use the TypeScript language, however TypeScript has powerful tooling that can be applied to JavaScript projects.
196+
197+
Gutenberg uses TypeScript for several reasons, including:
198+
199+
- Powerful editor integrations improve developer experience.
200+
- Type system can detect some issues and lead to more robust software.
201+
- Type declarations can be produced to allow other projects to benefit from these advantages as well.
202+
203+
### Using TypeScript
204+
205+
Gutenberg uses TypeScript by running the TypeScript compiler (`tsc`) on select packages.
206+
These packages benefit from type checking and produced type declarations in the published packages.
207+
208+
To opt-in to TypeScript tooling, packages should include a `tsconfig.json` file in the package root and add an entry to the root `tsconfig.json` references.
209+
The changes will indicate that the package has opted-in and will be included in the TypeScript build process.
210+
211+
A `tsconfig.json` file should look like the following (comments are not necessary):
212+
213+
```jsonc
214+
{
215+
// Extends a base configuration common to most packages
216+
"extends": "../../tsconfig.base.json",
217+
218+
// Options for the TypeScript compiler
219+
// We'll usually set our `rootDir` and `declarationDir` as follows, which is specific
220+
// to each project.
221+
"compilerOptions": {
222+
"rootDir": "src",
223+
"declarationDir": "build-types"
224+
},
225+
226+
// Which source files should be included
227+
"include": [ "src/**/*" ],
228+
229+
// Other WordPress package dependencies that have opted-in to TypeScript should be listed
230+
// here. In this case, our package depends on `@wordpress/dom-ready`.
231+
"references": [ { "path": "../dom-ready" } ]
232+
}
233+
```
234+
235+
Type declarations will be produced in the `build-types` which should be included in the published package.
236+
For consumers to use the published type declarations, we'll set the `types` field in `package.json`:
237+
238+
```json
239+
{
240+
"main": "build/index.js",
241+
"main-module": "build-module/index.js",
242+
"types": "build-types"
243+
}
244+
```
245+
246+
Ensure that the `build-types` directory will be included in the published package, for example if a `files` field is declared.
247+
193248
[lerna]: https://lerna.js.org/
194249
[npm]: https://www.npmjs.com/

0 commit comments

Comments
 (0)