Skip to content

Commit

Permalink
Build: Output package type declarations (#18942)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
sirreal authored Mar 27, 2020
1 parent 4e17ff0 commit 65520ef
Show file tree
Hide file tree
Showing 52 changed files with 472 additions and 128 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
build
build-module
build-style
build-types
node_modules
gutenberg.zip

Expand All @@ -14,6 +15,7 @@ yarn.lock

playground/dist
.cache
*.tsbuildinfo

# Report generated from jest-junit
test/native/junit.xml
Expand Down
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ jobs:
script:
- npx eslint --parser-options=ecmaVersion:5 --no-eslintrc --no-ignore ./build/**/*.js

- name: Typecheck
install:
- npm ci
script:
- npm run build:package-types


- name: Build artifacts
install:
# A "full" install is executed, since `npm ci` does not always exit
Expand Down
34 changes: 34 additions & 0 deletions bin/packages/lint-staged-typecheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* External dependencies
*/
const _ = require( 'lodash' );
const path = require( 'path' );
const fs = require( 'fs' );
const execa = require( 'execa' );

/* eslint-disable no-console */

const repoRoot = path.join( __dirname, '..', '..' );
const tscPath = path.join( repoRoot, 'node_modules', '.bin', 'tsc' );

// lint-staged passes full paths to staged changes
const changedFiles = process.argv.slice( 2 );

// Transform changed files to package directories containing tsconfig.json
const changedPackages = _.uniq(
changedFiles.map( ( fullPath ) => {
const relativePath = path.relative( repoRoot, fullPath );
return path.join( ...relativePath.split( path.sep ).slice( 0, 2 ) );
} )
).filter( ( packageRoot ) =>
fs.existsSync( path.join( packageRoot, 'tsconfig.json' ) )
);

try {
execa.sync( tscPath, [ '--build', ...changedPackages ] );
} catch ( err ) {
console.error( err.stdout );
process.exitCode = 1;
}

/* eslint-enable no-console */
17 changes: 17 additions & 0 deletions bin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "ES6",
"lib": [ "ES6", "ES2020.string" ],
"rootDir": ".",
"declarationMap": false,

// We're not interested in the output, but we must generate
// something as part of a composite project. Use the
// ignored `.cache` file to hide tsbuildinfo and d.ts files.
"outDir": ".cache"
},
"files": [ "./api-docs/update-api-docs.js" ]
}
24 changes: 21 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 14 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@
"@storybook/addon-viewport": "5.3.2",
"@storybook/react": "5.3.2",
"@types/jest": "24.0.25",
"@types/lodash": "4.14.149",
"@types/qs": "6.9.1",
"@types/requestidlecallback": "0.3.1",
"@types/sprintf-js": "1.1.2",
"@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma",
"@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot",
"@wordpress/babel-preset-default": "file:packages/babel-preset-default",
Expand Down Expand Up @@ -167,16 +170,18 @@
"sprintf-js": "1.1.1",
"style-loader": "1.0.0",
"stylelint-config-wordpress": "13.1.0",
"typescript": "3.5.3",
"typescript": "3.8.3",
"uuid": "3.3.2",
"webpack": "4.42.0",
"worker-farm": "1.7.0"
},
"scripts": {
"prebuild": "npm run check-engines",
"clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style ./packages/*/node_modules",
"clean:packages": "rimraf \"./packages/*/@(build|build-module|build-style)\"",
"clean:package-types": "tsc --build --clean",
"prebuild:packages": "npm run clean:packages && lerna run build",
"build:packages": "node ./bin/packages/build.js",
"build:packages": "npm run build:package-types && node ./bin/packages/build.js",
"build:package-types": "tsc --build",
"build": "npm run build:packages && wp-scripts build",
"check-engines": "wp-scripts check-engines",
"check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2\" \"wp-scripts check-licenses --dev\"",
Expand All @@ -191,22 +196,21 @@
"fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit",
"fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate",
"format-js": "wp-scripts format-js",
"lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\" \"npm run lint-types\"",
"lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\"",
"lint-js": "wp-scripts lint-js",
"lint-js:fix": "npm run lint-js -- --fix",
"lint-php": "npm run wp-env run composer run-script lint",
"lint-pkg-json": "wp-scripts lint-pkg-json . 'packages/*/package.json'",
"lint-css": "wp-scripts lint-style '**/*.scss'",
"lint-css:fix": "npm run lint-css -- --fix",
"lint-types": "tsc",
"lint:md-js": "wp-scripts lint-md-js",
"lint:md-docs": "wp-scripts lint-md-docs",
"package-plugin": "./bin/build-plugin-zip.sh",
"pot-to-php": "./bin/pot-to-php.js",
"publish:check": "lerna updated",
"publish:dev": "npm run build:packages && lerna publish --dist-tag next",
"publish:legacy": "npm run build:packages && lerna publish --dist-tag legacy",
"publish:prod": "npm run build:packages && lerna publish",
"publish:dev": "npm run clean:package-types && npm run build:packages && lerna publish --dist-tag next",
"publish:legacy": "npm run clean:package-types && npm run build:packages && lerna publish --dist-tag legacy",
"publish:prod": "npm run clean:package-types && npm run build:packages && lerna publish",
"test": "npm run lint && npm run test-unit",
"test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js",
"test-e2e:watch": "npm run test-e2e -- --watch",
Expand Down Expand Up @@ -252,7 +256,8 @@
],
"packages/**/*.js": [
"node ./bin/api-docs/update-api-docs.js",
"node ./bin/api-docs/are-api-docs-unstaged.js"
"node ./bin/api-docs/are-api-docs-unstaged.js",
"node ./bin/packages/lint-staged-typecheck.js"
]
},
"wp-env": {
Expand Down
151 changes: 103 additions & 48 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,44 @@ This repository uses [lerna] to manage WordPress modules and publish them as pac
When creating a new package, you need to provide at least the following:

1. `package.json` based on the template:
```json
{
"name": "@wordpress/package-name",
"version": "1.0.0-beta.0",
"description": "Package description.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"wordpress"
],
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.8.3"
},
"publishConfig": {
"access": "public"
}
}
```
This assumes that your code is located in the `src` folder and will be transpiled with `Babel`.
```json
{
"name": "@wordpress/package-name",
"version": "1.0.0-beta.0",
"description": "Package description.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [ "wordpress" ],
"homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/package-name/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.8.3"
},
"publishConfig": {
"access": "public"
}
}
```
This assumes that your code is located in the `src` folder and will be transpiled with `Babel`.
2. `.npmrc` file which disables creating `package-lock.json` file for the package:
```
package-lock=false
```
```
package-lock=false
```
3. `README.md` file containing at least:
- Package name
- Package description
- Installation details
- Usage example
- `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>`)
- Package name
- Package description
- Installation details
- Usage example
- `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>`)

## Managing Dependencies

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

#### Adding New Dependencies

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.

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

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:
1. First, remove the existing dependency as described in the previous section.
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.


1. First, remove the existing dependency as described in the previous section.
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.

### Development Dependencies

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.
Expand All @@ -119,16 +118,16 @@ _Example:_

### Bug Fix

- Fixed an off-by-one error with the `sum` function.
- Fixed an off-by-one error with the `sum` function.
```

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.

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

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.

Expand Down Expand Up @@ -190,5 +189,61 @@ npm run publish:legacy

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

## TypeScript

The [TypeScript](http://www.typescriptlang.org/) language is a typed superset of JavaScript that compiles to plain JavaScript.
Gutenberg does not use the TypeScript language, however TypeScript has powerful tooling that can be applied to JavaScript projects.

Gutenberg uses TypeScript for several reasons, including:

- Powerful editor integrations improve developer experience.
- Type system can detect some issues and lead to more robust software.
- Type declarations can be produced to allow other projects to benefit from these advantages as well.

### Using TypeScript

Gutenberg uses TypeScript by running the TypeScript compiler (`tsc`) on select packages.
These packages benefit from type checking and produced type declarations in the published packages.

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.
The changes will indicate that the package has opted-in and will be included in the TypeScript build process.

A `tsconfig.json` file should look like the following (comments are not necessary):

```jsonc
{
// Extends a base configuration common to most packages
"extends": "../../tsconfig.base.json",

// Options for the TypeScript compiler
// We'll usually set our `rootDir` and `declarationDir` as follows, which is specific
// to each project.
"compilerOptions": {
"rootDir": "src",
"declarationDir": "build-types"
},

// Which source files should be included
"include": [ "src/**/*" ],

// Other WordPress package dependencies that have opted-in to TypeScript should be listed
// here. In this case, our package depends on `@wordpress/dom-ready`.
"references": [ { "path": "../dom-ready" } ]
}
```

Type declarations will be produced in the `build-types` which should be included in the published package.
For consumers to use the published type declarations, we'll set the `types` field in `package.json`:

```json
{
"main": "build/index.js",
"main-module": "build-module/index.js",
"types": "build-types"
}
```

Ensure that the `build-types` directory will be included in the published package, for example if a `files` field is declared.

[lerna]: https://lerna.js.org/
[npm]: https://www.npmjs.com/
Loading

0 comments on commit 65520ef

Please sign in to comment.