Skip to content

Beef up docs around using class fields #516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 26 additions & 32 deletions packages/lit-dev-content/site/docs/components/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,34 @@ In the future when decorators become a native web platform feature, this may no

To use decorators with [TypeScript](https://www.typescriptlang.org/docs/handbook/decorators.html), enable the `experimentalDecorators` compiler option.

You should also ensure that the `useDefineForClassFields` setting is `false`. Note, this should only be required when the `target` is set to `esnext` or greater, but it's recommended to explicitly ensure this setting is `false`.

```json
"experimentalDecorators": true,
"useDefineForClassFields": false,
```

Enabling `emitDecoratorMetadata` is not required and not recommended.

### Using decorators with Babel { #decorators-babel }

If you're compiling JavaScript with [Babel](https://babeljs.io/docs/en/), you can enable decorators by adding the following plugins:
If you're compiling JavaScript with [Babel](https://babeljs.io/docs/en/), you can enable decorators by adding the following plugins and settings:

* [`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/en/babel-plugin-proposal-decorators).
* [`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/en/babel-plugin-proposal-decorators)
* [`@babel/plugin-proposal-class-properties`](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)

To enable the plugins, add code like this to your Babel configuration:
Note, the `@babel/plugin-proposal-class-properties` may not be required with the latest versions of Babel.

To set up the plugins, add code like this to your Babel configuration:

```js
assumptions = {
"setPublicClassFields": true
};

plugins = [
['@babel/plugin-proposal-decorators', {decoratorsBeforeExport: true}],
["@babel/plugin-proposal-class-properties", {"loose": true}],
["@babel/plugin-proposal-class-properties"],
];
```

Expand All @@ -101,42 +110,19 @@ Currently the older `legacy` mode of Babel decorators is not supported, but this

</div>

### Avoiding issues with class fields

Class fields are a [stage 3 proposal](https://github.com/tc39/proposal-decorators) for addition to the ECMAScript standard. They currently have a problematic interaction with the decorators proposal in some circumstances.

There are generally no issues when using TypeScript. However, it's important to ensure that the `useDefineForClassFields` setting in your `tsconfig` is set to false. This is currently the default setting.

When using Babel, class fields should only be used for properties that are defined with a decorator.

<div class="alert alert-info">
Using the `static properties` syntax along with class fields is not supported.
</div>

The following is ok:

```js
@property()
foo = 'bar';
```

but this is **not supported**:

```js
static properties = { foo: {} };
foo = 'bar';
```

### Using TypeScript with Babel
### Using decorators with TypeScript and Babel

When using TypeScript with Babel, it's important to order the TypeScript transform before the decorators transform in your Babel config as follows:

```js
{
"assumptions": {
"setPublicClassFields": true
},
"plugins":[
["@babel/plugin-transform-typescript", {"allowDeclareFields": true}],
["@babel/plugin-proposal-decorators", {"decoratorsBeforeExport": true}],
["@babel/plugin-proposal-class-properties", {"loose": true}],
["@babel/plugin-proposal-class-properties"],
]
}
```
Expand All @@ -153,3 +139,11 @@ constructor() {
this.foo = 'bar';
}
```

### Avoiding issues with class fields and decorators {#avoiding-issues-with-class-fields}

[Class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) have a problematic interaction with declaring reactive properties. See [Avoiding issues with class fields when declaring properties](/docs/components/properties/#avoiding-issues-with-class-fields) for more information.

The current decorators [stage 3 proposal](https://github.com/tc39/proposal-decorators) does not directly address this issue, but it should be solved as the proposal evolves and matures.

When using decorators, transpiler settings for Babel and TypeScript must be configured correctly as shown in the sections above for [TypeScript](#decorators-typescript) and [Babel](#decorators-babel).
29 changes: 26 additions & 3 deletions packages/lit-dev-content/site/docs/components/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,34 @@ class MyElement extends LitElement {

An empty option object is equivalent to specifying the default value for all options.

<div class="alert alert-info">
### Avoiding issues with class fields when declaring properties {#avoiding-issues-with-class-fields}

**If you're using the static properties field, initialize properties in the constructor**. Class field initializers won't work in this case.
[Class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields) have a problematic interaction with reactive properties. Class fields are defined on the element instance. Reactive properties are defined as accessors on the element prototype. According to the rules of JavaScript, an instance property takes precedence over and effectively hides a prototype property. This means that reactive property accessors do not function when class fields are used. When a property is set, the element does not update.

</div>
In **JavaScript** you **must not use class fields** when declaring reactive properties. Instead, properties must be initialized in the element constructor:

```js
constructor() {
super();
this.data = {};
}
```

For **TypeScript**, you **may use class fields** for declaring reactive properties as long as the `useDefineForClassFields` setting in your `tsconfig` is set to `false`. Note, this is not required for some configurations of TypeScript, but it's recommended to explicitly set it to `false`.

When compiling JavaScript with **Babel**, you **may use class fields** for declaring reactive properties as long as you set `setPublicClassFields` to `true` in the `assumptions` config of your `babelrc`. Note, for older versions of Babel, you also need to include the plugin `@babel/plugin-proposal-class-properties`:

```js
assumptions = {
"setPublicClassFields": true
};

plugins = [
["@babel/plugin-proposal-class-properties"],
];
```

For information about using class fields with **decorators**, see [Avoiding issues with class fields and decorators](/docs/components/decorators/#avoiding-issues-with-class-fields).

### Property options

Expand Down
14 changes: 11 additions & 3 deletions packages/lit-dev-content/site/docs/tools/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ The following JSON sample is a partial `tsconfig.json` that uses recommended opt
"lib": ["es2019", "dom"],
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"useDefineForClassFields": false
}
```

Note, setting `useDefineForClassFields` to `false` should only be required when the `target` is set to `esnext` or greater, but it's recommended to explicitly ensure this setting is `false`.

When compiling from TypeScript, you should include declaration files
(generated based on `declaration: true` above) for your component's types in the
`types` field of `package.json`, and ensure the `.d.ts` and `.d.ts.map` files
Expand Down Expand Up @@ -82,12 +85,17 @@ Configure Babel. For example:
**babel.config.js**

```js
const assumptions = {
"setPublicClassFields": true
};

const plugins = [
["@babel/plugin-proposal-class-properties", {"loose": true}],
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true } ],
["@babel/plugin-proposal-class-properties"],

];

module.exports = { plugins };
module.exports = { assumptions, plugins };
```

You can run Babel via a bundler plugin such as [@rollup/plugin-babel](https://www.npmjs.com/package/@rollup/plugin-babel), or from the command line. See the [Babel documentation](https://babeljs.io/docs/en/) for more information.
Expand Down
3 changes: 1 addition & 2 deletions packages/lit-dev-server/src/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export const pageRedirects = new Map([
// TODO(sorvell) https://github.com/lit/lit.dev/issues/455
['/msg/multiple-versions', '/docs/tools/requirements/'],
['/msg/polyfill-support-missing', '/docs/tools/requirements/#polyfills'],
// TODO(sorvell) https://github.com/lit/lit.dev/issues/462
['/msg/class-field-shadowing', '/docs/components/properties/#declare'],
['/msg/class-field-shadowing', '/docs/components/properties/#avoiding-issues-with-class-fields'],
// TODO(aomarks) Should we add something specifically about this issue?
['/msg/change-in-update', '/docs/components/properties/#when-properties-change'],
['/msg/deprecated-import-path', '/docs/releases/upgrade/#update-packages-and-import-paths'],
Expand Down