WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.52k stars 4.21k forks source link

Tooling: Ensure consistent formatting in all scripts #30795

Closed gziolo closed 3 years ago

gziolo commented 3 years ago

What problem does this address?

Follow-up for #30714.

I tested npm run fixtures:regenerate with all the formatting applied and it looks like it uses different formatting so definitely something to look at:

Screen Shot 2021-04-13 at 10 45 25

It is not only ranges that is formatted differently but it also uses spaces for indentation ...

Follow-up for #30715.

npx prettier --write **/*.md doesn't play nicely with npm run docs:build. @nosolosw, is it possible to include formatting in the process of generating automated docs with @wordpress/docgen or is better to run code formatting as the post-processing step inside the shell script we use?

What is your proposed solution?

It looks like npm run fixtures:regenerate command conflicts with what npx prettier --write **/*.json does for all JSON files. We should update npm run fixtures:regenerate to produce output that is aligned with Prettier formatting.

npx prettier --write **/*.md doesn't play nicely with npm run docs:build. We should update @wordpress/docgen to align both.

oandregal commented 3 years ago

:wave: What would be some instructions to reproduce the issue?

Doing npx prettier --format **/*.md results in a console output that I don't know how to interpret:

# Managing Packages This repository uses [lerna] to manage WordPress modules and publish them as packages to [npm]. ## Creating a New Package 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-prerelease", "description": "Package description.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "wordpress" ], "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/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.13.10" }, "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 ``` 3. `README.md` file containing at least: - Package name - Package description - Installation details - Usage example - API documentation, if applicable ([more info](#maintaining-api-documentation)) - `Code is Poetry` logo (`

Code is Poetry.

`) 4. `CHANGELOG.md` file containing at least: ``` ## Unreleased Initial release. ``` ## Managing Dependencies There are two types of dependencies that you might want to add to one of the existing WordPress packages. ### Production Dependencies 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/HEAD/commands/add#readme) command from the root of the project. _Example:_ ```bash lerna add lodash packages/a11y ``` This command adds the latest version of `lodash` as a dependency to the `@wordpress/a11y` package, which is located in `packages/a11y` folder. #### Removing Existing Dependencies Removing a dependency from one of the WordPress packages requires some manual work. You need to remove the line in the corresponding `dependencies` section of the `package.json` file. _Example:_ ```diff +++ b/packages/scripts/package.json @@ -43,7 +43,6 @@ "check-node-version": "^4.1.0", "cross-spawn": "^5.1.0", "eslint": "^7.1.0", - "jest": "^26.6.3", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", ``` Next, you need to run `npm install` in the root of the project to ensure that `package-lock.json` file gets properly regenerated. #### 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. ### 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. _Example:_ ```bash npm install glob --save-dev ``` This commands adds the latest version of `glob` as a development dependency to the `package.json` file. It has to be executed from the root of the project. ## Maintaining API documentation Each public API change needs to be reflected in the corresponding API documentation. To ensure that code and documentation are in sync automatically, Gutenberg has developed a few utilities. Packages can add the following HTML comment within their top-level `README.md`: ```markdown Content within the HTML comment will be replaced by the generated documentation. `. ``` Each time there is a commit to the public API of the package the `README.md` will be updated and kept in sync. The above snippet within the package's `README.md` signals the Gutenberg utilities to go to `src/index.js` and extract the JSDoc comments of the export statements into a more friendly format. Packages may want to use a different source file or add the exports of many files into the same `README.md` (see `packages/core-data/README.md` as an example); they can do so by adding the relative path to be used as source into the HTML comment: ```markdown Content within the HTML comment will be replaced by the generated documentation. `. ``` ## Maintaining Changelogs In maintaining dozens of npm packages, it can be tough to keep track of changes. To simplify the release process, each package includes a `CHANGELOG.md` file which details all published releases and the unreleased ("Unreleased") changes, if any exist. For each pull request, you should always include relevant changes in a "Unreleased" heading at the top of the file. You should add the heading if it doesn't already exist. _Example:_ ```md ## Unreleased ### Bug Fix - 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 version 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. When in doubt, refer to [Semantic Versioning specification](https://semver.org/). If you are publishing new versions of packages, note that there are versioning recommendations outlined in the [Gutenberg Release Process document](/docs/contributors/release.md) which prescribe _minimum_ version bumps for specific types of releases. The chosen version should be the greater of the two between the semantic versioning and Gutenberg release minimum version bumps. ## Releasing Packages Lerna automatically releases all outdated packages. To check which packages are outdated and will be released, type `npm run publish:check` from the branch that keeps the code for a given `wp/X.Y` release type (example `wp/5.7`). If you have the ability to publish packages, you _must_ have [2FA enabled](https://docs.npmjs.com/getting-started/using-two-factor-authentication) on your [npm account][npm]. ### Before Releasing Confirm that you're logged in to [npm], by running `npm whoami`. If you're not logged in, run `npm adduser` to login. If you're publishing a new package, ensure that its `package.json` file contains the correct `publishConfig` settings: ```json { "publishConfig": { "access": "public" } } ``` You can check your package configs by running `npm run lint-pkg-json`. ### Development Release Run the following commands from the `trunk` branch to publish to npm (with a `next` dist tag) a development version of the packages. ```bash npm install ./bin/plugin/cli.js npm-next ``` See more details in [Development Releases](/docs/contributors/code/release.md#development-releases) section of the Gutenberg release process documentation. ### Production Release To release a production version for the outdated packages, run the following commands from the `trunk` branch: ```bash npm install ./bin/plugin/cli.js npm-latest ``` See more details in [Synchronizing WordPress Trunk](/docs/contributors/code/release.md#synchronizing-wordpress-trunk) section of the Gutenberg release process documentation. ### Legacy Patch Release To release a patch for the older major or minor version of packages, run the following commands from the corresponding `wp/X.Y` (example `wp/5.7`) release branch: ```bash npm install npm run publish:patch ``` This is usually necessary when adding bug fixes or security patches to the earlier versions of WordPress. This will publish only a patch version of the built packages. This is useful for backpublishing certain packages to WordPress. See more details in [Minor WordPress Releases](/docs/contributors/code/release.md#minor-wordpress-releases) section of the Gutenberg release process documentation. ## 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/ ## Optimizing for bundlers In order for bundlers to tree-shake packages effectively, they often need to know whether a package includes side effects in its code. This is done through the `sideEffects` field in the package's `package.json`. If your package has no side effects, simply set the field to `false`: ```json { "name": "package", "sideEffects": false } ``` If your package includes a few files with side effects, you can list them instead: ```json { "name": "package", "sideEffects": [ "file-with-side-effects.js", "another-file-with-side-effects.js" ] } ``` Please consult the [side effects documentation](./side-effects.md) for more information on identifying and declaring side effects. # Side effects ## What are side effects? Many `@wordpress` packages, such as UI-focused ones that register blocks or data stores, make use of side effects in their code. A side effect, in an ES module context, is code that performs some externally-visible behavior (that is, behavior which is visible outside the module) when the module is loaded. Here is an example: ```js import { registerStore } from '@wordpress/data'; const store = registerStore( STORE_NAME, { // ... } ); ``` `registerStore` is being called at the top level, which means that it will run as soon as the module first gets imported. These changes are visible externally, since things are being modified in an external store, that lives in another module. Other examples of side effects include setting globals on `window`, or adding browser behavior through polyfills. However, if this were to happen inside of an `init` function that doesn't get called on module load, then that would no longer be a side effect: ```js import { registerStore } from '@wordpress/data'; export function init() { const store = registerStore( STORE_NAME, { // ... } ); } // `init` doesn't get called at the top level of the module, // therefore importing the module doesn't cause side effects. ``` Declaring a variable or performing any modification at the top level that only affects the current module isn't a side effect either, since it's contained to the module: ```js import list from './list'; // Not a side effect. let localVariable = []; // Not a side effect, either. for ( const entry of list ) { localVariable.push( processListEntry( entry ) ); } ``` ## The influence of side effects on bundling Modern bundlers have the concept of tree-shaking, where unused code is removed from the final bundles, as it's not necessary. This becomes important in libraries that offer a lot of different functionality, since consumers of that library may only be using a small portion of it, and don't want their bundles to be larger than necessary. These libraries should thus take steps to ensure they can indeed be correctly tree-shaken, and `@wordpress` packages are no exception. This brings us back to side effects. As we've seen, side effects are code that runs simply by virtue of importing a module, and has an external influence of some sort. This means that the code cannot be tree-shaken away; it needs to run, because it changes things outside of the module that may be needed elsewhere. Unfortunately, side effects are hard to determine automatically, and some bundlers err on the side of caution, assuming that every module potentially has side effects. This becomes a problem for `index` modules which re-export things from other modules, as that effectively means everything in there must now be bundled together: ```js // index.js export { a, b } from './module1'; export { c, d, e } from './module2'; export { f } from './module3'; // Nothing can be tree-shaken away, because the bundler doesn't know if // this or the re-exported modules have any side effects. ``` ## Telling bundlers about side effects Since bundlers can't figure out side effects for themselves, we need to explicitly declare them. That's done in a package's `package.json`. For example, if a package has no side effects, it can simply set `sideEffects` to `false`: ```json { "name": "package", "sideEffects": false } ``` If it has a few files with side effects, it can list them: ```json { "name": "package", "sideEffects": [ "dist/store/index.js", "dist/polyfill/index.js" ] } ``` This allows the bundler to assume that only the modules that were declared have side effects, and _nothing else does_. Of course, this means that we need to be careful to include everything that _does_ have side effects, or problems can arise in applications that make use of the package. # Storybook Storybook is an open-source tool that provides a sandbox to develop and visualize components in isolation. See the [Storybook site](https://storybook.js.org/) for more information about the tool. The Gutenberg project uses Storybook to view and work with the UI components developed in the WordPress packages. View online at: https://wordpress.github.io/gutenberg/ Run locally in your development environment running: `npm run storybook:dev` from the top-level Gutenberg directory.
gziolo commented 3 years ago

It should be npx prettier --write **/*.md, sorry about that 🙈

oandregal commented 3 years ago

Ok, took a look at this, my braindump:

Does this help?

gziolo commented 3 years ago

Thank you @nosolosw. It looks like we can collect updated files and run Prettier on all of them in one call or individually after each file gets updated 👍🏻

gziolo commented 3 years ago

Now that #33498 and #33502 landed, the issue no longer exists 🎉