mirror of
https://github.com/jlengrand/open-wc.git
synced 2026-03-10 00:21:18 +00:00
feat(dev-server-hmr): add web component HMR plugin
This commit is contained in:
5
.changeset/thin-pumpkins-sleep.md
Normal file
5
.changeset/thin-pumpkins-sleep.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@open-wc/dev-server-wc-hmr': patch
|
||||
---
|
||||
|
||||
first release
|
||||
@@ -6,3 +6,4 @@ stats.html
|
||||
/packages/**/test-node/**/snapshots
|
||||
/packages/demoing-storybook/storybook-static/**/*
|
||||
/packages/**/demo/**/*
|
||||
/packages/dev-server-hmr/src/patches/**/*
|
||||
@@ -2,6 +2,8 @@ module.exports = {
|
||||
extends: ['./packages/eslint-config/index.js', require.resolve('eslint-config-prettier')],
|
||||
rules: {
|
||||
'lit/no-useless-template-literals': 'off',
|
||||
'consistent-return': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
240
docs/docs/development/hot-module-replacement.md
Normal file
240
docs/docs/development/hot-module-replacement.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Development >> Hot Module Replacement ||50
|
||||
|
||||
> This project is currently experimental. Try it out and let us know what you think!
|
||||
|
||||
Plugin for "hot module replacement" or "fast refresh" with web components.
|
||||
|
||||
Keeps track of web component definitions in your code, and updates them at runtime on change. This is faster than a full page reload and preserves the page's state.
|
||||
|
||||
HMR requires the web component base class to implement a `hotReplaceCallback`.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package:
|
||||
|
||||
```
|
||||
npm i --save-dev @web/dev-server-hmr
|
||||
```
|
||||
|
||||
Add the plugin to your `web-dev-server-config.mjs`:
|
||||
|
||||
```js
|
||||
import { hmrPlugin } from '@open-wc/dev-server-hmr';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
## Implementations
|
||||
|
||||
### Vanilla
|
||||
|
||||
For vanilla web component projects that don't implement any base class or library this plugin should detect your components correctly. Read more below on how to implement the `hotReplaceCallback`.
|
||||
|
||||
### LitElement
|
||||
|
||||
LitElement v2 supports HMR with a small code patch included in the preset. There is no support yet for the v3 prerelease.
|
||||
|
||||
```js
|
||||
import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
presets: [presets.litElement],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### FAST Element
|
||||
|
||||
We have experimental support for FAST element using a small code patch included in the preset. This might not cover all use cases yet, let us know if you run into any issues!
|
||||
|
||||
```js
|
||||
import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
presets: [presets.fastElement],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Other libraries
|
||||
|
||||
If you know any other libraries that work correctly with HMR we can add presets for them here. Presets help by configuring the detection of base classes, decorators, and/or runtime code patches.
|
||||
|
||||
## Detecting web components
|
||||
|
||||
To "hot replace" an edited web component we have to be able to detect component definitions in your code. By default we look for usage of `customElements.define` and extending from `HTMLElement`.
|
||||
|
||||
For other use cases, you can specify base classes or decorators to indicate component definitions. We have some presets shown above which configure this for libraries that we know to work with HMR.
|
||||
|
||||
### Base classes
|
||||
|
||||
The base class option detects web components that extend a specific base class. The base class can be matched by name, or as an import from a specific module.
|
||||
|
||||
```js
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
baseClasses: [
|
||||
// anything that extends a class called MyElement
|
||||
{ name: 'MyElement' },
|
||||
// anything that extends a class called MyElement imported from 'my-element'
|
||||
{ name: 'MyElement', import: 'my-element' },
|
||||
// anything that extends a class called MyElement imported from './src/MyElement.js' (relative to current working directory)
|
||||
{ name: 'MyElement', import: './src/MyElement.js' },
|
||||
// anything that extends a default importeded class from 'my-element'
|
||||
{ name: 'default', import: 'my-element' },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Decorator
|
||||
|
||||
The plugin can also detect web components defined using decorators. The decorators can be matched by name, or as an import from a specific module.
|
||||
|
||||
```js
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
decorators: [
|
||||
// any class that uses a decorator called customElement
|
||||
{ name: 'customElement' },
|
||||
// any class that uses a decorator called customElement imported from 'my-element'
|
||||
{ name: 'customElement', import: 'my-element' },
|
||||
// any class that uses a decorator called customElement imported from './src/MyElement.js' (relative to current working directory)
|
||||
{ name: 'customElement', import: './src/MyElement.js' },
|
||||
// any class that uses a decorator default imported from 'my-element'
|
||||
{ name: 'default', import: 'my-element' },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
We don't currently support function based web components. Let us know if you have ideas on how this could work!
|
||||
|
||||
## Limitations
|
||||
|
||||
HMR workflows are not perfect. We're overwriting and moving around code at runtime. It breaks assumptions you normally make about your code. We recommended periodically doing a full refresh of the page, especially when you encounter strange behavior.
|
||||
|
||||
The following limitations should be kept in mind when working with open-wc HMR:
|
||||
|
||||
- Modules containing web components are re-imported under a new name and only the web component class is replaced. Side effects are triggered again but exported symbols are not updated.
|
||||
- Constructors for already created elements are not re-run when a class is replaced. Otherwise it would reset the properties of your element. This does mean that newly added properties don't show up.
|
||||
- Instance class fields act like properties defined in a constructor, and newly added or changed class fields are not hot replaced.
|
||||
- Newly created elements do use the new constructors and class fields.
|
||||
|
||||
> Did you run into other limitations? Let us know so we can improve this list.
|
||||
|
||||
## Implementing HMR
|
||||
|
||||
When hot replacing a web component class we can't replace the actual class. The custom element registry doesn't allow re-registration and we want to preserve the state of already rendered components. Instead, we patch the initial class with the properties from the updates class.
|
||||
|
||||
This updating logic can be different for each base class, and it can be implemented using the `hotReplaceCallback`.
|
||||
|
||||
This is the default implementation:
|
||||
|
||||
```js
|
||||
function updateObjectMembers(currentObj, newObj) {
|
||||
const currentProperties = new Set(Object.getOwnPropertyNames(hmrClass));
|
||||
const newProperties = new Set(Object.getOwnPropertyNames(newClass));
|
||||
|
||||
// add new and overwrite existing properties/methods
|
||||
for (const prop of Object.getOwnPropertyNames(newClass)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(newClass, prop);
|
||||
if (descriptor && descriptor.configurable) {
|
||||
Object.defineProperty(hmrClass, prop, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// delete removed properties
|
||||
for (const existingProp of currentProperties) {
|
||||
if (!newProperties.has(existingProp)) {
|
||||
try {
|
||||
delete hmrClass[existingProp];
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyElement extends HTMLElement {
|
||||
// static callback, called once when a class updates
|
||||
static hotReplaceCallback(newClass) {
|
||||
updateObjectMembers(this, newClass);
|
||||
updateObjectMembers(this.prototype, newClass.prototype);
|
||||
}
|
||||
|
||||
// instance callback, called for each connected element
|
||||
hotReplaceCallback() {
|
||||
// this should kick off re-rendering
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Static callback
|
||||
|
||||
The static `hotReplaceCallback` callback is called once for each replacement on the initial class of the component. This is where you can copy over properties from the new class to the existing class.
|
||||
|
||||
Implementing this callback is not mandatory, by default we copy over properties of the new class to the existing class. If this is not sufficient, you can customize this logic.
|
||||
|
||||
### Instance callback
|
||||
|
||||
The instance callback is called on each connected element implementing the replaced class. Implementing this is necessary to do some work at the instance level, such as trigger a re-render or style update.
|
||||
|
||||
When the instance callback is called, all the class members (properties, methods, etc.) have already been updated. So it could be as simple as kicking off the regular updating/rendering pipeline.
|
||||
|
||||
### Patching
|
||||
|
||||
If you don't want to include the HMR code in your production code, you could patch in the callbacks externally:
|
||||
|
||||
```js
|
||||
import { MyElement } from 'my-element';
|
||||
|
||||
MyElement.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
// code for the static callback
|
||||
};
|
||||
|
||||
MyElement.prototype.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
// code for the instance callback
|
||||
};
|
||||
```
|
||||
|
||||
Make sure this code is loaded before any of your components are loaded. You could also do this using the `patch` option in the config:
|
||||
|
||||
```js
|
||||
import { hmrPlugin } from '@open-wc/dev-server-hmr';
|
||||
|
||||
const myElementPatch = `
|
||||
import { MyElement } from 'my-element';
|
||||
|
||||
MyElement.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
// code for the static callback
|
||||
};
|
||||
|
||||
MyElement.prototype.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
// code for the instance callback
|
||||
};
|
||||
`;
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
include: ['src/**/*'],
|
||||
baseClasses: [{ name: 'MyElement', import: 'my-element' }],
|
||||
patches: [myElementPatch],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
@@ -44,7 +44,7 @@
|
||||
"@types/eslint": "^7.2.4",
|
||||
"@types/estree": "^0.0.45",
|
||||
"@types/parse5-htmlparser2-tree-adapter": "^5.0.1",
|
||||
"@web/dev-server": "^0.0.19",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/test-runner": "^0.9.5",
|
||||
"@web/test-runner-playwright": "^0.6.4",
|
||||
"babel-eslint": "^10.0.3",
|
||||
|
||||
5
packages/dev-server-hmr/README.md
Normal file
5
packages/dev-server-hmr/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Demoing via storybook
|
||||
|
||||
Plugin for "hot module replacement" or "fast refresh" with web components.
|
||||
|
||||
See [our website](https://open-wc.org/docs/development/hot-module-replacement/) for full documentation or view the docs [on github](../../docs/docs/development/hot-module-replacement.md)
|
||||
6
packages/dev-server-hmr/demo/fast-element/index.html
Normal file
6
packages/dev-server-hmr/demo/fast-element/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<todo-app></todo-app>
|
||||
<script type="module" src="./src/todo-app.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
15
packages/dev-server-hmr/demo/fast-element/server.config.mjs
Normal file
15
packages/dev-server-hmr/demo/fast-element/server.config.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
import { esbuildPlugin } from '@web/dev-server-esbuild';
|
||||
import { hmrPlugin, presets } from '../../index.mjs';
|
||||
|
||||
export default {
|
||||
open: 'packages/dev-server-hmr/demo/fast-element/',
|
||||
rootDir: '../..',
|
||||
nodeResolve: true,
|
||||
plugins: [
|
||||
esbuildPlugin({ ts: true }),
|
||||
hmrPlugin({
|
||||
exclude: ['**/*/node_modules/**/*'],
|
||||
presets: [presets.fastElement],
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { css } from '@microsoft/fast-element';
|
||||
|
||||
export const sharedStyles = css`
|
||||
.shared-template {
|
||||
color: green;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { html } from '@microsoft/fast-element';
|
||||
|
||||
export const sharedTemplate = html`
|
||||
<p class="shared-template">Shared template</p>
|
||||
`;
|
||||
15
packages/dev-server-hmr/demo/fast-element/src/todo-app.ts
Normal file
15
packages/dev-server-hmr/demo/fast-element/src/todo-app.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FASTElement, customElement, html } from '@microsoft/fast-element';
|
||||
import './todo-list.js';
|
||||
|
||||
const template = html<TodoApp>`
|
||||
<h1>Todo app</h1>
|
||||
<todo-list></todo-list>
|
||||
`;
|
||||
|
||||
@customElement({
|
||||
name: 'todo-app',
|
||||
template,
|
||||
})
|
||||
class TodoApp extends FASTElement {
|
||||
static definition = { name: 'todo-app', template };
|
||||
}
|
||||
50
packages/dev-server-hmr/demo/fast-element/src/todo-item.ts
Normal file
50
packages/dev-server-hmr/demo/fast-element/src/todo-item.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FASTElement, customElement, observable, html, css } from '@microsoft/fast-element';
|
||||
|
||||
const styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.delete {
|
||||
border: none;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const template = html<TodoItem>`
|
||||
<div>
|
||||
<input
|
||||
id="checkbox"
|
||||
type="checkbox"
|
||||
:checked=${x => x.checked}
|
||||
@change=${(x, c) => x._onCheckedChanged(c.event)}
|
||||
/>
|
||||
<label for="checkbox" class="message">${x => x.message}</label>
|
||||
</div>
|
||||
<button class="delete" aria-label="Delete" @click=${x => x._onDelete()}>❌</button>
|
||||
`;
|
||||
|
||||
@customElement({ name: 'todo-item', template, styles })
|
||||
class TodoItem extends FASTElement {
|
||||
@observable checked = false;
|
||||
@observable message = '';
|
||||
|
||||
_onCheckedChanged(e: Event) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('checked-changed', { detail: (e.target as HTMLInputElement).checked }),
|
||||
);
|
||||
}
|
||||
|
||||
_onDelete() {
|
||||
this.dispatchEvent(new Event('delete'));
|
||||
}
|
||||
}
|
||||
94
packages/dev-server-hmr/demo/fast-element/src/todo-list.ts
Normal file
94
packages/dev-server-hmr/demo/fast-element/src/todo-list.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { FASTElement, customElement, observable, repeat, html, css } from '@microsoft/fast-element';
|
||||
import { sharedTemplate } from './sharedTemplate';
|
||||
import { sharedStyles } from './sharedStyles';
|
||||
import './todo-item.js';
|
||||
|
||||
interface TodoItem {
|
||||
message: string;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
const styles = [
|
||||
sharedStyles,
|
||||
css`
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.add > * {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
const template = html<TodoList>`
|
||||
<ul>
|
||||
${repeat(
|
||||
x => x.items,
|
||||
html<TodoItem, TodoList>`
|
||||
<li>
|
||||
<todo-item
|
||||
:message=${x => x.message}
|
||||
:checked=${x => x.checked}
|
||||
data-i=${(_, c) => c.index}
|
||||
@checked-changed=${(_, c) => c.parent._onCheckedChanged(c.event as any)}
|
||||
@delete=${(_, c) => c.parent._onDelete(c.event)}
|
||||
></todo-item>
|
||||
</li>
|
||||
`,
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div class="add">
|
||||
<input id="input" placeholder="Add a TODO" autocomplete="off" />
|
||||
<button @click=${x => x._addTodo()}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${sharedTemplate}
|
||||
`;
|
||||
|
||||
@customElement({ name: 'todo-list', template, styles })
|
||||
class TodoList extends FASTElement {
|
||||
@observable items: TodoItem[] = [
|
||||
{ message: 'Do A', checked: true },
|
||||
{ message: 'Do B', checked: false },
|
||||
{ message: 'Do C', checked: true },
|
||||
{ message: 'Do D', checked: false },
|
||||
{ message: 'Do E', checked: false },
|
||||
];
|
||||
|
||||
_onCheckedChanged(e: Event & { detail: boolean }) {
|
||||
const { i: iString } = (e.target as HTMLElement).dataset;
|
||||
const i = Number(iString);
|
||||
const item = this.items[i];
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1, { ...item, checked: e.detail });
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_onDelete(e: Event) {
|
||||
const { i: iString } = (e.target as HTMLElement).dataset;
|
||||
const i = Number(iString);
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1);
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_addTodo() {
|
||||
const input = this.shadowRoot!.getElementById('input') as HTMLInputElement;
|
||||
const message = input.value;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
input.value = '';
|
||||
this.items = [...this.items, { message, checked: false }];
|
||||
}
|
||||
}
|
||||
12
packages/dev-server-hmr/demo/fast-element/tsconfig.json
Normal file
12
packages/dev-server-hmr/demo/fast-element/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module":"ESNext",
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
6
packages/dev-server-hmr/demo/lit-element-ts/index.html
Normal file
6
packages/dev-server-hmr/demo/lit-element-ts/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<todo-app></todo-app>
|
||||
<script type="module" src="./src/todo-app.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { esbuildPlugin } from '@web/dev-server-esbuild';
|
||||
import { hmrPlugin, presets } from '../../index.mjs';
|
||||
|
||||
export default {
|
||||
open: 'packages/dev-server-hmr/demo/lit-element-ts/',
|
||||
rootDir: '../..',
|
||||
nodeResolve: true,
|
||||
plugins: [
|
||||
esbuildPlugin({ ts: true }),
|
||||
hmrPlugin({
|
||||
exclude: ['**/*/node_modules/**/*'],
|
||||
presets: [presets.litElement],
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { css } from 'lit-element';
|
||||
|
||||
export const sharedStyles = css`
|
||||
.shared-template {
|
||||
color: green;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
import { html } from 'lit-element';
|
||||
|
||||
export const sharedTemplate = html`<p class="shared-template">Shared template</p>`;
|
||||
12
packages/dev-server-hmr/demo/lit-element-ts/src/todo-app.ts
Normal file
12
packages/dev-server-hmr/demo/lit-element-ts/src/todo-app.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LitElement, customElement, html } from 'lit-element';
|
||||
import './todo-list.js';
|
||||
|
||||
@customElement('todo-app')
|
||||
class TodoApp extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<h1>Todo app</h1>
|
||||
<todo-list></todo-list>
|
||||
`;
|
||||
}
|
||||
}
|
||||
55
packages/dev-server-hmr/demo/lit-element-ts/src/todo-item.ts
Normal file
55
packages/dev-server-hmr/demo/lit-element-ts/src/todo-item.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { LitElement, property, customElement, html, css } from 'lit-element';
|
||||
|
||||
@customElement('todo-item')
|
||||
class TodoItem extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.delete {
|
||||
border: none;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
checked = false;
|
||||
@property({ type: String })
|
||||
message?: string;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
<input
|
||||
id="checkbox"
|
||||
type="checkbox"
|
||||
.checked=${!!this.checked}
|
||||
@change=${this._onCheckedChanged}
|
||||
/>
|
||||
<label for="checkbox" class="message">${this.message}</label>
|
||||
</div>
|
||||
|
||||
<button class="delete" aria-label="Delete" @click=${this._onDelete}>❌</button>
|
||||
`;
|
||||
}
|
||||
|
||||
_onCheckedChanged(e: any) {
|
||||
this.dispatchEvent(new CustomEvent('checked-changed', { detail: e.target.checked }));
|
||||
}
|
||||
|
||||
_onDelete() {
|
||||
this.dispatchEvent(new Event('delete'));
|
||||
}
|
||||
}
|
||||
93
packages/dev-server-hmr/demo/lit-element-ts/src/todo-list.ts
Normal file
93
packages/dev-server-hmr/demo/lit-element-ts/src/todo-list.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { LitElement, customElement, property, html, css } from 'lit-element';
|
||||
import { sharedTemplate } from './sharedTemplate';
|
||||
import { sharedStyles } from './sharedStyles';
|
||||
import './todo-item.js';
|
||||
|
||||
@customElement('todo-list')
|
||||
class TodoList extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.add > * {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@property({ type: Array })
|
||||
items = [
|
||||
{ message: 'Do A', checked: true },
|
||||
{ message: 'Do B', checked: false },
|
||||
{ message: 'Do C', checked: true },
|
||||
{ message: 'Do D', checked: false },
|
||||
{ message: 'Do E', checked: false },
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${this.items.map(
|
||||
(item, i) =>
|
||||
html`
|
||||
<li>
|
||||
<todo-item
|
||||
.message=${item.message}
|
||||
.checked=${item.checked}
|
||||
data-i=${i}
|
||||
@checked-changed=${this._onCheckedChanged}
|
||||
@delete=${this._onDelete}
|
||||
></todo-item>
|
||||
</li>
|
||||
`,
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div class="add">
|
||||
<input id="input" placeholder="Add a TODO" autocomplete="off" /><button
|
||||
@click=${this._addTodo}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${sharedTemplate}
|
||||
`;
|
||||
}
|
||||
|
||||
_onCheckedChanged(e: any) {
|
||||
const { i } = e.target.dataset;
|
||||
const item = this.items[i];
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1, { ...item, checked: e.detail });
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_onDelete(e) {
|
||||
const { i } = e.target.dataset;
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1);
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_addTodo() {
|
||||
const input = this.shadowRoot!.getElementById('input') as HTMLInputElement;
|
||||
const message = input.value;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
input.value = '';
|
||||
this.items = [...this.items, { message, checked: false }];
|
||||
}
|
||||
}
|
||||
12
packages/dev-server-hmr/demo/lit-element-ts/tsconfig.json
Normal file
12
packages/dev-server-hmr/demo/lit-element-ts/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"target": "ESNEXT" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module":"ESNext",
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
6
packages/dev-server-hmr/demo/lit-element/index.html
Normal file
6
packages/dev-server-hmr/demo/lit-element/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<todo-app></todo-app>
|
||||
<script type="module" src="./src/todo-app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
packages/dev-server-hmr/demo/lit-element/server.config.mjs
Normal file
13
packages/dev-server-hmr/demo/lit-element/server.config.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
import { hmrPlugin, presets } from '../../index.mjs';
|
||||
|
||||
export default {
|
||||
open: 'packages/dev-server-hmr/demo/lit-element/',
|
||||
rootDir: '../..',
|
||||
nodeResolve: true,
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
exclude: ['**/*/node_modules/**/*'],
|
||||
presets: [presets.litElement],
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { css } from 'lit-element';
|
||||
|
||||
export const sharedStyles = css`
|
||||
.shared-template {
|
||||
color: green;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,3 @@
|
||||
import { html } from 'lit-element';
|
||||
|
||||
export const sharedTemplate = html`<p class="shared-template">Shared template</p>`;
|
||||
13
packages/dev-server-hmr/demo/lit-element/src/todo-app.js
Normal file
13
packages/dev-server-hmr/demo/lit-element/src/todo-app.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { LitElement, html } from 'lit-element';
|
||||
import './todo-list.js';
|
||||
|
||||
class TodoApp extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<h1>Todo app</h1>
|
||||
<todo-list></todo-list>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('todo-app', TodoApp);
|
||||
57
packages/dev-server-hmr/demo/lit-element/src/todo-item.js
Normal file
57
packages/dev-server-hmr/demo/lit-element/src/todo-item.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
|
||||
class TodoItem extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.delete {
|
||||
border: none;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
checked: { type: Boolean },
|
||||
message: { type: String },
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
<input
|
||||
id="checkbox"
|
||||
type="checkbox"
|
||||
.checked=${!!this.checked}
|
||||
@change=${this._onCheckedChanged}
|
||||
/>
|
||||
<label for="checkbox" class="message">${this.message}</label>
|
||||
</div>
|
||||
<button class="delete" aria-label="Delete" @click=${this._onDelete}>❌</button>
|
||||
`;
|
||||
}
|
||||
|
||||
_onCheckedChanged(e) {
|
||||
this.dispatchEvent(new CustomEvent('checked-changed', { detail: e.target.checked }));
|
||||
}
|
||||
|
||||
_onDelete() {
|
||||
this.dispatchEvent(new Event('delete'));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('todo-item', TodoItem);
|
||||
100
packages/dev-server-hmr/demo/lit-element/src/todo-list.js
Normal file
100
packages/dev-server-hmr/demo/lit-element/src/todo-list.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
import { sharedTemplate } from './sharedTemplate.js';
|
||||
import { sharedStyles } from './sharedStyles.js';
|
||||
import './todo-item.js';
|
||||
|
||||
class TodoList extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.add > * {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return { items: { type: Object } };
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.items = [
|
||||
{ message: 'Do A', checked: true },
|
||||
{ message: 'Do B', checked: false },
|
||||
{ message: 'Do C', checked: true },
|
||||
{ message: 'Do D', checked: false },
|
||||
{ message: 'Do E', checked: false },
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${this.items.map(
|
||||
(item, i) =>
|
||||
html`
|
||||
<li>
|
||||
<todo-item
|
||||
.message=${item.message}
|
||||
.checked=${item.checked}
|
||||
data-i=${i}
|
||||
@checked-changed=${this._onCheckedChanged}
|
||||
@delete=${this._onDelete}
|
||||
></todo-item>
|
||||
</li>
|
||||
`,
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div class="add">
|
||||
<input id="input" placeholder="Add a TODO" autocomplete="off" /><button
|
||||
@click=${this._addTodo}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${sharedTemplate}
|
||||
`;
|
||||
}
|
||||
|
||||
_onCheckedChanged(e) {
|
||||
const { i } = e.target.dataset;
|
||||
const item = this.items[i];
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1, { ...item, checked: e.detail });
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_onDelete(e) {
|
||||
const { i } = e.target.dataset;
|
||||
const newItems = this.items.slice();
|
||||
newItems.splice(i, 1);
|
||||
this.items = newItems;
|
||||
}
|
||||
|
||||
_addTodo() {
|
||||
const input = this.shadowRoot.getElementById('input');
|
||||
const message = input.value;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
input.value = '';
|
||||
this.items = [...this.items, { message, checked: false }];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('todo-list', TodoList);
|
||||
8
packages/dev-server-hmr/demo/vanilla/index.html
Normal file
8
packages/dev-server-hmr/demo/vanilla/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head></head>
|
||||
|
||||
<body>
|
||||
<vanilla-app></vanilla-app>
|
||||
<script type="module" src="./src/vanilla-app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
18
packages/dev-server-hmr/demo/vanilla/server.config.mjs
Normal file
18
packages/dev-server-hmr/demo/vanilla/server.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { hmrPlugin } from '../../index.mjs';
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default {
|
||||
open: 'demo/vanilla/',
|
||||
nodeResolve: true,
|
||||
plugins: [
|
||||
hmrPlugin({
|
||||
exclude: ['**/*/node_modules/**/*'],
|
||||
baseClasses: [
|
||||
{ name: 'SharedElement', import: path.resolve(dirname, './src/SharedElement.js') },
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
24
packages/dev-server-hmr/demo/vanilla/src/BaseClass.js
Normal file
24
packages/dev-server-hmr/demo/vanilla/src/BaseClass.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export class BaseClass extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
hotReplaceCallback() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
update() {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>${this.styles()}</style>
|
||||
${this.render()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { BaseClass } from './BaseClass.js';
|
||||
|
||||
export class VanillaElementBase extends BaseClass {
|
||||
styles() {
|
||||
return `
|
||||
.base {
|
||||
color: blue;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
<p class="base">Vanilla element base</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
5
packages/dev-server-hmr/demo/vanilla/src/sharedStyles.js
Normal file
5
packages/dev-server-hmr/demo/vanilla/src/sharedStyles.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const sharedStyles = `
|
||||
.shared-template {
|
||||
color: green;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export const sharedTemplate = '<p class="shared-template">Shared template</p>';
|
||||
32
packages/dev-server-hmr/demo/vanilla/src/vanilla-app.js
Normal file
32
packages/dev-server-hmr/demo/vanilla/src/vanilla-app.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { BaseClass } from './BaseClass.js';
|
||||
import { sharedTemplate } from './sharedTemplate.js';
|
||||
import { sharedStyles } from './sharedStyles.js';
|
||||
import './vanilla-message.js';
|
||||
import './vanilla-element.js';
|
||||
|
||||
class VanillaApp extends BaseClass {
|
||||
styles() {
|
||||
return `
|
||||
${sharedStyles}
|
||||
|
||||
h1 {
|
||||
color: blue;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
<h1>Vanilla app</h1>
|
||||
${sharedTemplate}
|
||||
|
||||
<vanilla-message message="Hello"></vanilla-message>
|
||||
<vanilla-message message="World"></vanilla-message>
|
||||
<vanilla-message message="Goodbye"></vanilla-message>
|
||||
|
||||
<vanilla-element></vanilla-element>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('vanilla-app', VanillaApp);
|
||||
22
packages/dev-server-hmr/demo/vanilla/src/vanilla-element.js
Normal file
22
packages/dev-server-hmr/demo/vanilla/src/vanilla-element.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { VanillaElementBase } from './VanillaElementBase.js';
|
||||
|
||||
class VanillaElement extends VanillaElementBase {
|
||||
styles() {
|
||||
return `
|
||||
${super.styles()}
|
||||
|
||||
.element {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
${super.render()}
|
||||
<p class="element">Vanilla element</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('vanilla-element', VanillaElement);
|
||||
39
packages/dev-server-hmr/demo/vanilla/src/vanilla-message.js
Normal file
39
packages/dev-server-hmr/demo/vanilla/src/vanilla-message.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { BaseClass } from './BaseClass.js';
|
||||
|
||||
class VanillaMessage extends BaseClass {
|
||||
static observedAttributes = ['message'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
this[name] = newValue;
|
||||
}
|
||||
|
||||
set message(value) {
|
||||
this._message = value;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get message() {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
styles() {
|
||||
return `
|
||||
p {
|
||||
color: blue;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return `
|
||||
<p>Message: ${this.message}</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('vanilla-message', VanillaMessage);
|
||||
5
packages/dev-server-hmr/index.mjs
Normal file
5
packages/dev-server-hmr/index.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import module from './src/index.js';
|
||||
|
||||
const { hmrPlugin, presets, WC_HMR_MODULE_RUNTIME } = module;
|
||||
|
||||
export { hmrPlugin, presets, WC_HMR_MODULE_RUNTIME };
|
||||
63
packages/dev-server-hmr/package.json
Normal file
63
packages/dev-server-hmr/package.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@open-wc/dev-server-wc-hmr",
|
||||
"version": "0.0.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "Plugin for HMR with web components",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/open-wc/open-wc.git",
|
||||
"directory": "packages/dev-server-wc-hmr"
|
||||
},
|
||||
"author": "open-wc",
|
||||
"homepage": "https://github.com/open-wc/open-wc/packages/dev-server-wc-hmr",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start:fast": "wds --config demo/fast-element/server.config.mjs",
|
||||
"start:lit": "wds --config demo/lit-element/server.config.mjs",
|
||||
"start:lit-ts": "wds --config demo/lit-element-ts/server.config.mjs",
|
||||
"start:vanilla": "wds --config demo/vanilla/server.config.mjs",
|
||||
"test": "npm run test:node",
|
||||
"test:node": "mocha test-node --recursive",
|
||||
"test:watch": "npm run test:node -- --watch --watchfiles test"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"keywords": [
|
||||
"web",
|
||||
"dev server",
|
||||
"hmr",
|
||||
"hot",
|
||||
"module",
|
||||
"replacement",
|
||||
"reload",
|
||||
"web",
|
||||
"components"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.12.1",
|
||||
"@babel/plugin-syntax-top-level-await": "^7.12.1",
|
||||
"@web/dev-server-core": "^0.2.18",
|
||||
"@web/dev-server-hmr": "^0.1.6",
|
||||
"picomatch": "^2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@microsoft/fast-element": "^0.19.1",
|
||||
"@types/babel__core": "^7.1.12",
|
||||
"@types/babel__generator": "^7.6.2",
|
||||
"@types/babel__parser": "^7.1.1",
|
||||
"@types/babel__traverse": "^7.0.15",
|
||||
"@types/picomatch": "^2.2.1",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-esbuild": "^0.2.8",
|
||||
"lit-element": "^2.4.0",
|
||||
"mocha": "^8.2.1"
|
||||
}
|
||||
}
|
||||
191
packages/dev-server-hmr/src/babel/babelPluginWcHmr.js
Normal file
191
packages/dev-server-hmr/src/babel/babelPluginWcHmr.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/** @typedef {import('@babel/core').PluginObj} PluginObj */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
/**
|
||||
* @typedef {object} BaseClass
|
||||
* @property {string} name
|
||||
* @property {string} [import]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Decorator
|
||||
* @property {string} name
|
||||
* @property {string} [import]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} BabelPluginWcHmrOptions
|
||||
* @property {string} rootDir
|
||||
* @property {BaseClass[]} [baseClasses]
|
||||
* @property {Decorator[]} [decorators]
|
||||
* @property {string[]} [patches]
|
||||
*/
|
||||
|
||||
const { types } = require('@babel/core');
|
||||
const path = require('path');
|
||||
|
||||
const { findDefinedCustomElement } = require('./customElementsDefine');
|
||||
const { findDecoratedCustomElement } = require('./decorators');
|
||||
const { injectRegisterClass, injectRuntime } = require('./inject');
|
||||
const { parseOptions, singlePath, addToSet } = require('./utils');
|
||||
const { getImportedVariableNames } = require('./getImportedVariableNames');
|
||||
const { implementsBaseClass } = require('./class');
|
||||
const { createError } = require('../utils');
|
||||
|
||||
/** @returns {PluginObj} */
|
||||
function babelPluginWcHmr() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(program) {
|
||||
if (!this.filename) throw createError('Missing filename');
|
||||
const resolvedFilename = path.resolve(this.filename);
|
||||
const opts = parseOptions(/** @type {BabelPluginWcHmrOptions} */ (this.opts));
|
||||
const baseClasses = opts.baseClasses || [];
|
||||
const decorators = opts.decorators || [];
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const baseClassNames = new Set();
|
||||
/** @type {Set<string>} */
|
||||
const decoratorNames = new Set();
|
||||
/** @type {Set<string>} */
|
||||
const injectedClassNames = new Set();
|
||||
let injectedRegister = false;
|
||||
|
||||
/**
|
||||
* @param {NodePath<any>} nodePath
|
||||
* @param {string} name
|
||||
* @param {boolean} insertAfter
|
||||
*/
|
||||
function maybeInjectRegister(nodePath, name, insertAfter = false) {
|
||||
if (injectedClassNames.has(name)) {
|
||||
return;
|
||||
}
|
||||
injectRegisterClass(nodePath, name, insertAfter);
|
||||
injectedRegister = true;
|
||||
injectedClassNames.add(name);
|
||||
}
|
||||
|
||||
// add decorators that don't require their import to be checked
|
||||
addToSet(
|
||||
baseClassNames,
|
||||
baseClasses.filter(b => !b.import).map(b => b.name),
|
||||
);
|
||||
addToSet(
|
||||
decoratorNames,
|
||||
decorators.filter(c => !c.import).map(d => d.name),
|
||||
);
|
||||
|
||||
program.traverse({
|
||||
ImportDeclaration(importDecl) {
|
||||
// find all base classes and decorators that match this import
|
||||
const result = getImportedVariableNames(
|
||||
baseClasses,
|
||||
decorators,
|
||||
importDecl,
|
||||
resolvedFilename,
|
||||
opts.rootDir,
|
||||
);
|
||||
addToSet(baseClassNames, result.baseClassNames);
|
||||
addToSet(decoratorNames, result.decoratorNames);
|
||||
},
|
||||
});
|
||||
|
||||
program.traverse({
|
||||
CallExpression(callExpr) {
|
||||
const callee = callExpr.get('callee');
|
||||
const args = callExpr.get('arguments');
|
||||
if (!singlePath(callee) || !Array.isArray(args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callee.isMemberExpression()) {
|
||||
// this might be a customElements.define call
|
||||
const definedCustomElement = findDefinedCustomElement(callee, args);
|
||||
if (!definedCustomElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (definedCustomElement.isIdentifier()) {
|
||||
maybeInjectRegister(callExpr, definedCustomElement.node.name);
|
||||
}
|
||||
|
||||
if (definedCustomElement.isClassExpression()) {
|
||||
// take inline class expression out of the define so that it can be registered
|
||||
const id = callExpr.scope.generateUidIdentifierBasedOnNode(
|
||||
definedCustomElement.node,
|
||||
);
|
||||
const { name } = id;
|
||||
|
||||
if (!injectedClassNames.has(name)) {
|
||||
callExpr.insertBefore(
|
||||
types.variableDeclaration('const', [
|
||||
types.variableDeclarator(id, definedCustomElement.node),
|
||||
]),
|
||||
);
|
||||
definedCustomElement.replaceWith(id);
|
||||
maybeInjectRegister(callExpr, name);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoratorNames.size > 0) {
|
||||
if (callee.isIdentifier()) {
|
||||
// this might be a decorated class
|
||||
const decoratedCustomElement = findDecoratedCustomElement(
|
||||
decoratorNames,
|
||||
callee,
|
||||
args,
|
||||
);
|
||||
if (!decoratedCustomElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoratedCustomElement.isIdentifier()) {
|
||||
maybeInjectRegister(callExpr.parentPath, decoratedCustomElement.node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ClassDeclaration(classDecl) {
|
||||
// this is a class declaration like class A extends HTMLElement {}
|
||||
if (implementsBaseClass(classDecl, baseClassNames)) {
|
||||
maybeInjectRegister(classDecl, classDecl.node.id.name, true);
|
||||
}
|
||||
},
|
||||
|
||||
ClassExpression(classExpr) {
|
||||
const { parent, parentPath: varDeclarator } = classExpr;
|
||||
if (!parent || varDeclarator.isVariableDeclarator()) {
|
||||
return;
|
||||
}
|
||||
if (!varDeclarator || varDeclarator.parentPath.isVariableDeclaration()) {
|
||||
return;
|
||||
}
|
||||
const id = varDeclarator.get('id');
|
||||
if (!singlePath(id) || !id.isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const injectScope = varDeclarator.parentPath;
|
||||
if (!injectScope) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is a class expression assignment like const A = class B extends HTMLElement {}
|
||||
if (implementsBaseClass(classExpr, baseClassNames)) {
|
||||
maybeInjectRegister(classExpr, id.node.name, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (injectedRegister) {
|
||||
injectRuntime(opts, program);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = babelPluginWcHmr;
|
||||
40
packages/dev-server-hmr/src/babel/babelTransform.js
Normal file
40
packages/dev-server-hmr/src/babel/babelTransform.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { transformAsync } = require('@babel/core');
|
||||
|
||||
const babelPluginWcHmr = require('./babelPluginWcHmr');
|
||||
const { createError } = require('../utils');
|
||||
|
||||
/** @typedef {import('./babelPluginWcHmr').BabelPluginWcHmrOptions} BabelPluginWcHmrOptions */
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {string} filename
|
||||
* @param {BabelPluginWcHmrOptions} options
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function babelTransform(code, filename, options) {
|
||||
const largeFile = code.length > 100000;
|
||||
const result = await transformAsync(code, {
|
||||
caller: {
|
||||
name: '@open-wc/dev-server-hmr',
|
||||
supportsStaticESM: true,
|
||||
},
|
||||
plugins: [
|
||||
[babelPluginWcHmr, options],
|
||||
require.resolve('@babel/plugin-syntax-class-properties'),
|
||||
require.resolve('@babel/plugin-syntax-import-assertions'),
|
||||
require.resolve('@babel/plugin-syntax-top-level-await'),
|
||||
],
|
||||
filename,
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
compact: largeFile,
|
||||
sourceType: 'module',
|
||||
});
|
||||
|
||||
if (!result?.code) {
|
||||
throw createError(`Failed to babel transform ${filename}`);
|
||||
}
|
||||
return result.code;
|
||||
}
|
||||
|
||||
module.exports = { babelTransform };
|
||||
37
packages/dev-server-hmr/src/babel/class.js
Normal file
37
packages/dev-server-hmr/src/babel/class.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @typedef {import('@babel/types').ClassDeclaration} ClassDeclaration */
|
||||
/** @typedef {import('@babel/types').ClassExpression} ClassExpression */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
const { singlePath } = require('./utils');
|
||||
|
||||
/**
|
||||
* @param {NodePath<ClassDeclaration> | NodePath<ClassExpression>} classDeclOrExpr
|
||||
* @returns {NodePath<any> | undefined}
|
||||
*/
|
||||
function walkClassMixins(classDeclOrExpr) {
|
||||
let el = classDeclOrExpr.get('superClass');
|
||||
// walk possible mixin functions
|
||||
while (singlePath(el) && el.isCallExpression()) {
|
||||
const result = el.get('arguments');
|
||||
if (Array.isArray(result)) {
|
||||
[el] = result;
|
||||
}
|
||||
}
|
||||
|
||||
return singlePath(el) ? el : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NodePath<ClassDeclaration> | NodePath<ClassExpression>} classDeclOrExpr
|
||||
* @param {Set<string>} baseClassNames
|
||||
*/
|
||||
function implementsBaseClass(classDeclOrExpr, baseClassNames) {
|
||||
const el = walkClassMixins(classDeclOrExpr);
|
||||
|
||||
if (el && el.isIdentifier()) {
|
||||
const { name } = el.node;
|
||||
return baseClassNames.has(name);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { implementsBaseClass };
|
||||
85
packages/dev-server-hmr/src/babel/customElementsDefine.js
Normal file
85
packages/dev-server-hmr/src/babel/customElementsDefine.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/** @typedef {import('@babel/types').MemberExpression} MemberExpression */
|
||||
/** @typedef {import('@babel/types').CallExpression} CallExpression */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
const { resolvePath, singlePath } = require('./utils');
|
||||
|
||||
const GLOBALS = ['window', 'self', 'globalThis'];
|
||||
|
||||
/**
|
||||
* @param {NodePath<MemberExpression>} callee
|
||||
* @param {NodePath<unknown>[]} args
|
||||
*/
|
||||
function isDefineCall(callee, args) {
|
||||
const property = callee.get('property');
|
||||
|
||||
if (!singlePath(property) || !property.isIdentifier() || property.node.name !== 'define') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args || !Array.isArray(args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return args.length >= 2 && (args[1].isIdentifier() || args[1].isClassExpression());
|
||||
}
|
||||
|
||||
/** @param {NodePath<MemberExpression>} memberExpr */
|
||||
function isCallOnCustomElementObject(memberExpr) {
|
||||
const object = memberExpr.get('object');
|
||||
if (!singlePath(object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (object.isIdentifier()) {
|
||||
// we are dealing with <something>.define(), check if <something> references global customElements
|
||||
const resolvedIdPath = resolvePath(object);
|
||||
if (!resolvedIdPath || !resolvedIdPath.isIdentifier()) {
|
||||
return false;
|
||||
}
|
||||
return resolvedIdPath.node.name === 'customElements';
|
||||
}
|
||||
|
||||
if (object.isMemberExpression()) {
|
||||
const property = object.get('property');
|
||||
if (
|
||||
!singlePath(property) ||
|
||||
!property.isIdentifier() ||
|
||||
property.node.name !== 'customElements'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// we are dealing with <something>.customElements.define, check if <something> is the global scope
|
||||
|
||||
const subObject = object.get('object');
|
||||
if (!singlePath(subObject) || !subObject.isIdentifier()) {
|
||||
return false;
|
||||
}
|
||||
const resolvedIdPath = resolvePath(subObject);
|
||||
return (
|
||||
resolvedIdPath && resolvedIdPath.isIdentifier() && GLOBALS.includes(resolvedIdPath.node.name)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param {NodePath<any>[]} args */
|
||||
function getDefinedClass(args) {
|
||||
if (!args || !Array.isArray(args)) {
|
||||
return;
|
||||
}
|
||||
return args[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NodePath<MemberExpression>} memberExpr
|
||||
* @param {NodePath<any>[]} args
|
||||
*/
|
||||
function findDefinedCustomElement(memberExpr, args) {
|
||||
if (isDefineCall(memberExpr, args) && isCallOnCustomElementObject(memberExpr)) {
|
||||
return getDefinedClass(args);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { findDefinedCustomElement };
|
||||
61
packages/dev-server-hmr/src/babel/decorators.js
Normal file
61
packages/dev-server-hmr/src/babel/decorators.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @typedef {import('@babel/types').Identifier} Identifier */
|
||||
/** @typedef {import('./babelPluginWcHmr').Decorator} Decorator */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
const { singlePath } = require('./utils');
|
||||
|
||||
/**
|
||||
* @param {Set<string>} decoratorNames
|
||||
* @param {NodePath<Identifier>} callee
|
||||
* @param {NodePath<any>[]} args
|
||||
*/
|
||||
function findCompiledTsDecoratedCustomElement(decoratorNames, callee, args) {
|
||||
// is this a decorator helper function?
|
||||
if (callee.node.name !== '__decorate') {
|
||||
return;
|
||||
}
|
||||
|
||||
const [arrayExpr, decoratedClass] = args;
|
||||
// are we decorating an identifier (of a class)
|
||||
if (!singlePath(decoratedClass) || !decoratedClass.isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
// is the first parameter an array of decorator functions?
|
||||
if (!singlePath(arrayExpr) || !arrayExpr.isArrayExpression()) {
|
||||
return;
|
||||
}
|
||||
const elements = arrayExpr.get('elements');
|
||||
if (!Array.isArray(elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find a decorator function called customElement
|
||||
const decoratorCall = elements.find(e => {
|
||||
if (!e.isCallExpression()) {
|
||||
return false;
|
||||
}
|
||||
const eCallee = e.get('callee');
|
||||
if (!singlePath(eCallee) || !eCallee.isIdentifier()) {
|
||||
return false;
|
||||
}
|
||||
return decoratorNames.has(eCallee.node.name);
|
||||
});
|
||||
|
||||
if (!decoratorCall) {
|
||||
return;
|
||||
}
|
||||
return decoratedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Set<string>} decoratorNames
|
||||
* @param {NodePath<Identifier>} callee
|
||||
* @param {NodePath<any>[]} args
|
||||
* @returns {NodePath<any> | undefined}
|
||||
*/
|
||||
function findDecoratedCustomElement(decoratorNames, callee, args) {
|
||||
// TODO: add non-compiled decorator when it becomes stage 3 and properly supported by babel
|
||||
return findCompiledTsDecoratedCustomElement(decoratorNames, callee, args);
|
||||
}
|
||||
|
||||
module.exports = { findDecoratedCustomElement };
|
||||
100
packages/dev-server-hmr/src/babel/getImportedVariableNames.js
Normal file
100
packages/dev-server-hmr/src/babel/getImportedVariableNames.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/** @typedef {import('@babel/types').ImportDefaultSpecifier} ImportDefaultSpecifier */
|
||||
/** @typedef {import('@babel/types').ImportNamespaceSpecifier} ImportNamespaceSpecifier */
|
||||
/** @typedef {import('@babel/types').ImportSpecifier} ImportSpecifier */
|
||||
/** @typedef {import('@babel/types').ImportDeclaration} ImportDeclaration */
|
||||
/** @typedef {import('@babel/types').StringLiteral} StringLiteral */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath */
|
||||
/** @typedef {import('./babelPluginWcHmr').BaseClass} BaseClass */
|
||||
/** @typedef {import('./babelPluginWcHmr').Decorator} Decorator */
|
||||
|
||||
const path = require('path');
|
||||
const { singlePath } = require('./utils');
|
||||
|
||||
/**
|
||||
* @param {string} importSpecifier
|
||||
* @param {string} importPath
|
||||
* @param {string} filename
|
||||
* @param {string} rootDir
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isMatchingImport(importSpecifier, importPath, filename, rootDir) {
|
||||
// is this a non-bare import
|
||||
if (
|
||||
importSpecifier.startsWith('../') ||
|
||||
importSpecifier.startsWith('./') ||
|
||||
importSpecifier.startsWith('/')
|
||||
) {
|
||||
const partialImportFilePath = importSpecifier.split('/').join(path.sep);
|
||||
const joinBase = importSpecifier.startsWith('/') ? rootDir : path.dirname(filename);
|
||||
const importFilePath = path.join(joinBase, partialImportFilePath);
|
||||
|
||||
return importFilePath === importPath || `${importFilePath}.js` === importPath;
|
||||
}
|
||||
|
||||
// this is a bare import
|
||||
return importPath === importSpecifier || `${importPath}.js` === importSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseClass[]} baseClasses
|
||||
* @param {Decorator[]} decorators
|
||||
* @param {string} importSpecifier
|
||||
* @param {string} filename
|
||||
* @param {string} rootDir
|
||||
*/
|
||||
function getMatchesForImport(baseClasses, decorators, importSpecifier, filename, rootDir) {
|
||||
return {
|
||||
baseClasses: baseClasses.filter(
|
||||
baseClass =>
|
||||
baseClass.import && isMatchingImport(importSpecifier, baseClass.import, filename, rootDir),
|
||||
),
|
||||
decorators: decorators.filter(
|
||||
decorator =>
|
||||
decorator.import && isMatchingImport(importSpecifier, decorator.import, filename, rootDir),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseClass[]} baseClasses
|
||||
* @param {Decorator[]} decorators
|
||||
* @param {NodePath<ImportDeclaration>} importDeclaration
|
||||
* @param {string} filename
|
||||
* @param {string} rootDir
|
||||
*/
|
||||
function getImportedVariableNames(baseClasses, decorators, importDeclaration, filename, rootDir) {
|
||||
const importSpecifier = importDeclaration.node.source.value;
|
||||
const matches = getMatchesForImport(baseClasses, decorators, importSpecifier, filename, rootDir);
|
||||
/** @type {string[]} */
|
||||
const baseClassNames = [];
|
||||
const decoratorNames = [];
|
||||
|
||||
if (matches.baseClasses.length || matches.decorators.length) {
|
||||
for (const specifier of importDeclaration.get('specifiers')) {
|
||||
if (specifier.isImportDefaultSpecifier()) {
|
||||
if (matches.baseClasses.some(cl => cl.name === 'default')) {
|
||||
baseClassNames.push(specifier.node.local.name);
|
||||
}
|
||||
if (matches.decorators.some(cl => cl.name === 'default')) {
|
||||
decoratorNames.push(specifier.node.local.name);
|
||||
}
|
||||
} else if (specifier.isImportSpecifier()) {
|
||||
const imported = specifier.get('imported');
|
||||
if (singlePath(imported)) {
|
||||
const importedName = imported.isIdentifier()
|
||||
? imported.node.name
|
||||
: /** @type {StringLiteral} */ (imported.node).value;
|
||||
if (matches.baseClasses.some(cl => cl.name === importedName)) {
|
||||
baseClassNames.push(specifier.node.local.name);
|
||||
}
|
||||
if (matches.decorators.some(cl => cl.name === importedName)) {
|
||||
decoratorNames.push(specifier.node.local.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { baseClassNames, decoratorNames };
|
||||
}
|
||||
|
||||
module.exports = { getImportedVariableNames };
|
||||
40
packages/dev-server-hmr/src/babel/inject.js
Normal file
40
packages/dev-server-hmr/src/babel/inject.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/** @typedef {import('@babel/types').Program} Program */
|
||||
/** @typedef {import('./babelPluginWcHmr').BabelPluginWcHmrOptions} BabelPluginWcHmrOptions */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
const { parse } = require('@babel/core');
|
||||
const { WC_HMR_NAMESPACE, WC_HMR_MODULE_PATCH, WC_HMR_MODULE_RUNTIME } = require('../constants');
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {NodePath<T>} nodePath
|
||||
* @param {string} name
|
||||
* @param {boolean} insertAfter
|
||||
*/
|
||||
function injectRegisterClass(nodePath, name, insertAfter) {
|
||||
const toInject = parse(`${WC_HMR_NAMESPACE}.register(import.meta.url, ${name})`);
|
||||
if (!toInject) throw new TypeError('Failed to parse');
|
||||
if (insertAfter) {
|
||||
nodePath.insertAfter(toInject);
|
||||
} else {
|
||||
nodePath.insertBefore(toInject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BabelPluginWcHmrOptions} options
|
||||
* @param {NodePath<Program>} program
|
||||
*/
|
||||
function injectRuntime(options, program) {
|
||||
const patch =
|
||||
options.patches && options.patches.length > 0 ? `import '${WC_HMR_MODULE_PATCH}'; ` : '';
|
||||
const toInject = parse(
|
||||
`${patch}import * as ${WC_HMR_NAMESPACE} from '${WC_HMR_MODULE_RUNTIME}'; if(import.meta.hot) { import.meta.hot.accept(); }`,
|
||||
);
|
||||
|
||||
if (toInject) {
|
||||
program.node.body.unshift(/** @type {any} */ (toInject));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { injectRegisterClass, injectRuntime };
|
||||
45
packages/dev-server-hmr/src/babel/utils.js
Normal file
45
packages/dev-server-hmr/src/babel/utils.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/** @typedef {import('./babelPluginWcHmr').BabelPluginWcHmrOptions} BabelPluginWcHmrOptions */
|
||||
/** @template T @typedef {import('@babel/core').NodePath<T>} NodePath<T> */
|
||||
|
||||
const { createError } = require('../utils');
|
||||
|
||||
/**
|
||||
* @param {BabelPluginWcHmrOptions} options
|
||||
* @returns {BabelPluginWcHmrOptions}
|
||||
*/
|
||||
function parseOptions(options) {
|
||||
if (!options) throw createError('Missing babel plugin options');
|
||||
if (!options.rootDir) throw createError('Missing rootDir in babel plugin options');
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {NodePath<T>} nodePath
|
||||
*/
|
||||
function resolvePath(nodePath) {
|
||||
const pathCast = /** @type {any} */ (nodePath);
|
||||
return /** @type {NodePath<unknown>} */ (pathCast.resolve());
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {NodePath<T> | NodePath<T>[]} nodePath
|
||||
* @returns {nodePath is NodePath<T>}
|
||||
*/
|
||||
function singlePath(nodePath) {
|
||||
return nodePath && !Array.isArray(nodePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Set<T>} set
|
||||
* @param {T[]} elements
|
||||
*/
|
||||
function addToSet(set, elements) {
|
||||
for (const e of elements) {
|
||||
set.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { parseOptions, resolvePath, singlePath, addToSet };
|
||||
11
packages/dev-server-hmr/src/constants.js
Normal file
11
packages/dev-server-hmr/src/constants.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const WC_HMR_NAMESPACE = '__$wc_hmr$__';
|
||||
const WC_HMR_MODULE_PREFIX = '/__web-dev-server__/wc-hmr/';
|
||||
const WC_HMR_MODULE_PATCH = `${WC_HMR_MODULE_PREFIX}patch.js`;
|
||||
const WC_HMR_MODULE_RUNTIME = `${WC_HMR_MODULE_PREFIX}runtime.js`;
|
||||
|
||||
module.exports = {
|
||||
WC_HMR_NAMESPACE,
|
||||
WC_HMR_MODULE_PREFIX,
|
||||
WC_HMR_MODULE_PATCH,
|
||||
WC_HMR_MODULE_RUNTIME,
|
||||
};
|
||||
149
packages/dev-server-hmr/src/hmrPlugin.js
Normal file
149
packages/dev-server-hmr/src/hmrPlugin.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/** @typedef {import('@web/dev-server-core').Plugin} DevServerPlugin */
|
||||
/** @typedef {import('./utils').Matcher} Matcher */
|
||||
|
||||
/**
|
||||
* @typedef {object} BaseClass
|
||||
* @property {string} name
|
||||
* @property {string} [import]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Decorator
|
||||
* @property {string} name
|
||||
* @property {string} [import]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Preset
|
||||
* @property {BaseClass[]} baseClasses
|
||||
* @property {Decorator[]} decorators
|
||||
* @property {string} patch
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} WcHmrPluginConfig
|
||||
* @property {string[]} [include]
|
||||
* @property {string[]} [exclude]
|
||||
* @property {Preset[]} [presets]
|
||||
* @property {BaseClass[]} [baseClasses]
|
||||
* @property {Decorator[]} [decorators]
|
||||
* @property {string[]} [patches]
|
||||
*/
|
||||
|
||||
const { getRequestFilePath, PluginSyntaxError } = require('@web/dev-server-core');
|
||||
const { hmrPlugin: createBaseHmrPlugin } = require('@web/dev-server-hmr');
|
||||
|
||||
const { WC_HMR_MODULE_PREFIX, WC_HMR_MODULE_RUNTIME, WC_HMR_MODULE_PATCH } = require('./constants');
|
||||
const { parseConfig, createMatchers, createError } = require('./utils');
|
||||
const { babelTransform } = require('./babel/babelTransform');
|
||||
const { wcHmrRuntime } = require('./wcHmrRuntime');
|
||||
|
||||
/**
|
||||
* @param {WcHmrPluginConfig} pluginConfig
|
||||
* @returns {DevServerPlugin}
|
||||
*/
|
||||
function hmrPlugin(pluginConfig) {
|
||||
const baseHmrPlugin = createBaseHmrPlugin();
|
||||
const parsedPluginConfig = parseConfig(pluginConfig);
|
||||
|
||||
/** @type {string} */
|
||||
let rootDir;
|
||||
/** @type {Matcher} */
|
||||
let matchInclude = () => true;
|
||||
/** @type {Matcher} */
|
||||
let matchExclude = () => false;
|
||||
|
||||
return {
|
||||
name: 'wc-hmr',
|
||||
injectWebSocket: true,
|
||||
|
||||
resolveImport(...args) {
|
||||
const { source } = args[0];
|
||||
if (source.startsWith(WC_HMR_MODULE_PREFIX)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
return baseHmrPlugin.resolveImport?.(...args);
|
||||
},
|
||||
|
||||
serve(...args) {
|
||||
const context = args[0];
|
||||
if (context.path === WC_HMR_MODULE_RUNTIME) {
|
||||
return wcHmrRuntime;
|
||||
}
|
||||
|
||||
if (context.path === WC_HMR_MODULE_PATCH) {
|
||||
return pluginConfig.patches?.join('\n') || '';
|
||||
}
|
||||
|
||||
return baseHmrPlugin.serve?.(...args);
|
||||
},
|
||||
|
||||
serverStart(...args) {
|
||||
if (args[0].config.plugins?.find(pl => pl.name === 'hmr')) {
|
||||
throw createError(
|
||||
`Cannot include both @web/dev-server-hmr and @open-wc/dev-server-hmr plugins.`,
|
||||
);
|
||||
}
|
||||
|
||||
rootDir = args[0].config.rootDir;
|
||||
if (parsedPluginConfig.include) {
|
||||
matchInclude = createMatchers(rootDir, parsedPluginConfig.include);
|
||||
}
|
||||
|
||||
if (parsedPluginConfig.exclude) {
|
||||
matchExclude = createMatchers(rootDir, parsedPluginConfig.exclude);
|
||||
}
|
||||
|
||||
return baseHmrPlugin.serverStart?.(...args);
|
||||
},
|
||||
|
||||
async transform(...args) {
|
||||
const context = args[0];
|
||||
if (!context.response.is('js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = getRequestFilePath(context, rootDir);
|
||||
if (
|
||||
matchInclude(filePath) &&
|
||||
!matchExclude(filePath) &&
|
||||
!filePath.startsWith('__web-dev-server__')
|
||||
) {
|
||||
try {
|
||||
context.body = await babelTransform(context.body, filePath, {
|
||||
baseClasses: parsedPluginConfig.baseClasses,
|
||||
decorators: parsedPluginConfig.decorators,
|
||||
patches: parsedPluginConfig.patches,
|
||||
rootDir,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.name === 'SyntaxError') {
|
||||
// forward babel error to dev server
|
||||
const strippedMsg = error.message.replace(new RegExp(`${filePath} ?:? ?`, 'g'), '');
|
||||
throw new PluginSyntaxError(strippedMsg, filePath, error.code, error.loc, error.pos);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return baseHmrPlugin.transform?.(...args);
|
||||
},
|
||||
|
||||
// forward all other plugin hooks
|
||||
serverStop(...args) {
|
||||
return baseHmrPlugin.serverStop?.(...args);
|
||||
},
|
||||
transformCacheKey(...args) {
|
||||
return baseHmrPlugin.transformCacheKey?.(...args);
|
||||
},
|
||||
transformImport(...args) {
|
||||
return baseHmrPlugin.transformImport?.(...args);
|
||||
},
|
||||
resolveMimeType(...args) {
|
||||
return baseHmrPlugin.resolveMimeType?.(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { hmrPlugin };
|
||||
12
packages/dev-server-hmr/src/index.js
Normal file
12
packages/dev-server-hmr/src/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { hmrPlugin } = require('./hmrPlugin');
|
||||
const { litElement } = require('./presets/litElement');
|
||||
const { fastElement } = require('./presets/fastElement');
|
||||
const { WC_HMR_MODULE_RUNTIME } = require('./constants');
|
||||
|
||||
const presets = { litElement, fastElement };
|
||||
|
||||
module.exports = {
|
||||
hmrPlugin,
|
||||
presets,
|
||||
WC_HMR_MODULE_RUNTIME,
|
||||
};
|
||||
20
packages/dev-server-hmr/src/presets/fastElement.js
Normal file
20
packages/dev-server-hmr/src/presets/fastElement.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { WC_HMR_MODULE_RUNTIME } = require('../constants');
|
||||
|
||||
const patch = `import { FASTElement, FASTElementDefinition } from '@microsoft/fast-element';
|
||||
import { updateClassMembers } from '${WC_HMR_MODULE_RUNTIME}';
|
||||
|
||||
FASTElement.prototype.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
const newDefinition = FASTElementDefinition.forType(newClass);
|
||||
if (newDefinition) {
|
||||
this.$fastController.styles = newDefinition.styles;
|
||||
this.$fastController.template = newDefinition.template;
|
||||
}
|
||||
};`;
|
||||
|
||||
const fastElement = {
|
||||
decorators: [{ name: 'customElement', import: '@microsoft/fast-element' }],
|
||||
baseClasses: [{ name: 'FASTElement', import: '@microsoft/fast-element' }],
|
||||
patch,
|
||||
};
|
||||
|
||||
module.exports = { fastElement };
|
||||
41
packages/dev-server-hmr/src/presets/litElement.js
Normal file
41
packages/dev-server-hmr/src/presets/litElement.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { WC_HMR_MODULE_RUNTIME } = require('../constants');
|
||||
|
||||
const patch = `import { LitElement } from 'lit-element';
|
||||
import { updateClassMembers } from '${WC_HMR_MODULE_RUNTIME}';
|
||||
const supportsAdoptingStyleSheets = (window.ShadowRoot) &&
|
||||
(window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) &&
|
||||
('adoptedStyleSheets' in Document.prototype) &&
|
||||
('replace' in CSSStyleSheet.prototype);
|
||||
|
||||
// static callback
|
||||
LitElement.hotReplaceCallback = function hotReplaceCallback(newClass) {
|
||||
newClass.finalize();
|
||||
updateClassMembers(this, newClass);
|
||||
this.finalize();
|
||||
};
|
||||
|
||||
// instance callback
|
||||
LitElement.prototype.hotReplaceCallback = function hotReplaceCallback() {
|
||||
if (!supportsAdoptingStyleSheets) {
|
||||
const nodes = Array.from(this.renderRoot.children);
|
||||
for (const node of nodes) {
|
||||
if (node.tagName.toLowerCase() === 'style') {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.constructor._getUniqueStyles();
|
||||
if (window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot) {
|
||||
this.adoptStyles();
|
||||
}
|
||||
this.requestUpdate();
|
||||
};`;
|
||||
|
||||
const litElement = {
|
||||
decorators: [{ name: 'customElement', import: 'lit-element' }],
|
||||
baseClasses: [{ name: 'LitElement', import: 'lit-element' }],
|
||||
patch,
|
||||
};
|
||||
|
||||
module.exports = { litElement };
|
||||
87
packages/dev-server-hmr/src/utils.js
Normal file
87
packages/dev-server-hmr/src/utils.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
const picoMatch = require('picomatch');
|
||||
const { isAbsolute, posix, sep } = require('path');
|
||||
|
||||
/** @typedef {(path: string) => boolean} Matcher */
|
||||
/** @typedef {import('./hmrPlugin').WcHmrPluginConfig} WcHmrPluginConfig */
|
||||
|
||||
/**
|
||||
* @param {string} msg
|
||||
*/
|
||||
function createError(msg) {
|
||||
return new Error(`[@open-wc/dev-server-hmr] ${msg}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WcHmrPluginConfig} config
|
||||
* @param {keyof WcHmrPluginConfig} prop
|
||||
*/
|
||||
function assertOptionalArray(config, prop) {
|
||||
if (config[prop] != null && !Array.isArray(config[prop])) {
|
||||
throw createError(`Option ${prop} must be an array`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WcHmrPluginConfig} config
|
||||
*/
|
||||
function parseConfig(config) {
|
||||
if (!Array.isArray(config.include) && !Array.isArray(config.exclude)) {
|
||||
throw createError('Must provide either an "include" or "exclude" pattern in config.');
|
||||
}
|
||||
assertOptionalArray(config, 'include');
|
||||
assertOptionalArray(config, 'exclude');
|
||||
assertOptionalArray(config, 'patches');
|
||||
assertOptionalArray(config, 'baseClasses');
|
||||
assertOptionalArray(config, 'decorators');
|
||||
assertOptionalArray(config, 'presets');
|
||||
|
||||
config.baseClasses = config.baseClasses || [];
|
||||
config.baseClasses.push({ name: 'HTMLElement' });
|
||||
|
||||
if (config.presets) {
|
||||
for (const preset of config.presets) {
|
||||
if (preset.patch) {
|
||||
config.patches = config.patches || [];
|
||||
config.patches.push(preset.patch);
|
||||
}
|
||||
if (preset.baseClasses) {
|
||||
config.baseClasses.push(...preset.baseClasses);
|
||||
}
|
||||
if (preset.decorators) {
|
||||
config.decorators = config.decorators || [];
|
||||
config.decorators.push(...preset.decorators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} rootDir
|
||||
* @param {string} pattern
|
||||
* @returns {Matcher}
|
||||
*/
|
||||
function createMatcher(rootDir, pattern) {
|
||||
const matcherRootDir = rootDir.split(sep).join('/');
|
||||
const resolvedPattern =
|
||||
!isAbsolute(pattern) && !pattern.startsWith('*')
|
||||
? posix.join(matcherRootDir, pattern)
|
||||
: pattern;
|
||||
return picoMatch(resolvedPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} rootDir
|
||||
* @param {string[]} patterns
|
||||
* @returns {Matcher}
|
||||
*/
|
||||
function createMatchers(rootDir, patterns) {
|
||||
const matchers = patterns.map(p => createMatcher(rootDir, p));
|
||||
return function matcher(path) {
|
||||
return matchers.some(m => m(path));
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createMatcher, createMatchers, parseConfig, createError };
|
||||
96
packages/dev-server-hmr/src/wcHmrRuntime.js
Normal file
96
packages/dev-server-hmr/src/wcHmrRuntime.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const wcHmrRuntime = `
|
||||
// override global define to allow double registrations
|
||||
const originalDefine = window.customElements.define;
|
||||
window.customElements.define = (name, ...rest) => {
|
||||
if (!window.customElements.get(name)) {
|
||||
originalDefine.call(window.customElements, name, ...rest);
|
||||
}
|
||||
};
|
||||
|
||||
const registry = new Map();
|
||||
|
||||
function createClassKey(importMetaUrl, className) {
|
||||
const modulePath = new URL(importMetaUrl).pathname;
|
||||
return \`\${modulePath}:\${className}\`;
|
||||
}
|
||||
|
||||
export function register(importMetaUrl, hmrClass) {
|
||||
const key = createClassKey(importMetaUrl, hmrClass.name);
|
||||
const hotReplaceCallback = registry.get(key);
|
||||
if (hotReplaceCallback) {
|
||||
// class is already registered, call the replace function registered below
|
||||
hotReplaceCallback(hmrClass);
|
||||
return;
|
||||
}
|
||||
// class is not yet registered, set it up and register an update callback
|
||||
const connectedElements = trackConnectedElements(hmrClass);
|
||||
|
||||
// register a callback for this class later patch in updates to the class
|
||||
registry.set(key, async newClass => {
|
||||
// wait 1 microtask to allow the class definition to propagate
|
||||
await 0;
|
||||
|
||||
if (hmrClass.hotReplaceCallback) {
|
||||
// class has implemented a callback, use that
|
||||
hmrClass.hotReplaceCallback(newClass);
|
||||
} else {
|
||||
// otherwise apply default update
|
||||
updateClassMembers(hmrClass, newClass);
|
||||
}
|
||||
|
||||
for (const element of connectedElements) {
|
||||
if (element.hotReplaceCallback) {
|
||||
element.hotReplaceCallback(newClass);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function trackConnectedElements(hmrClass) {
|
||||
const connectedElements = new Set();
|
||||
const originalCb = hmrClass.prototype.connectedCallback;
|
||||
hmrClass.prototype.connectedCallback = function connectedCallback(...args) {
|
||||
if (originalCb) {
|
||||
originalCb.call(this, ...args);
|
||||
}
|
||||
connectedElements.add(this);
|
||||
};
|
||||
|
||||
const originalDcb = hmrClass.prototype.disconnectedCallback;
|
||||
hmrClass.prototype.disconnectedCallback = function disconnectedCallback(...args) {
|
||||
if (originalDcb) {
|
||||
originalDcb.call(this, ...args);
|
||||
}
|
||||
connectedElements.delete(this);
|
||||
};
|
||||
return connectedElements;
|
||||
}
|
||||
|
||||
const preserved = ['connectedCallback', 'disconnectedCallback', 'observedAttributes'];
|
||||
|
||||
export function updateClassMembers(hmrClass, newClass) {
|
||||
updateObjectMembers(hmrClass, newClass);
|
||||
updateObjectMembers(hmrClass.prototype, newClass.prototype);
|
||||
}
|
||||
|
||||
export function updateObjectMembers(hmrClass, newClass) {
|
||||
const currentProperties = new Set(Object.getOwnPropertyNames(hmrClass));
|
||||
const newProperties = new Set(Object.getOwnPropertyNames(newClass));
|
||||
|
||||
for (const prop of Object.getOwnPropertyNames(newClass)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(newClass, prop);
|
||||
if (descriptor && descriptor.configurable) {
|
||||
Object.defineProperty(hmrClass, prop, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
for (const existingProp of currentProperties) {
|
||||
if (!preserved.includes(existingProp) && !newProperties.has(existingProp)) {
|
||||
try {
|
||||
delete hmrClass[existingProp];
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
module.exports = { wcHmrRuntime };
|
||||
126
packages/dev-server-hmr/test-node/babel/base-class.test.js
Normal file
126
packages/dev-server-hmr/test-node/babel/base-class.test.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const { expect } = require('chai');
|
||||
const path = require('path');
|
||||
const { banner, transform, rootDir } = require('./utils');
|
||||
|
||||
describe('babelPluginWcHmr - detecting base class', () => {
|
||||
it('global base class', () => {
|
||||
const code = `class Foo extends MyElement {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'MyElement' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
class Foo extends MyElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);`);
|
||||
});
|
||||
|
||||
it('named import', () => {
|
||||
const code = `import { MyElement } from 'my-element'; class Foo extends MyElement {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'MyElement', import: 'my-element' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { MyElement } from 'my-element';
|
||||
|
||||
class Foo extends MyElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);`);
|
||||
});
|
||||
|
||||
it('unmatched import', () => {
|
||||
const code = `import { MyElement } from 'not-my-element'; class Foo extends MyElement {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'MyElement', import: 'my-element' }] });
|
||||
|
||||
expect(result).to.equal(`import { MyElement } from 'not-my-element';
|
||||
|
||||
class Foo extends MyElement {}`);
|
||||
});
|
||||
|
||||
it('default import', () => {
|
||||
const code = `import BaseElement from 'base-element'; class Foo extends BaseElement {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'default', import: 'base-element' }] });
|
||||
expect(result).to.equal(`${banner}
|
||||
import BaseElement from 'base-element';
|
||||
|
||||
class Foo extends BaseElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);`);
|
||||
});
|
||||
|
||||
it('mixins', () => {
|
||||
const code = `import { MyElement } from 'my-element'; class Foo extends MixA(MixB(MyElement)) {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'MyElement', import: 'my-element' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { MyElement } from 'my-element';
|
||||
|
||||
class Foo extends MixA(MixB(MyElement)) {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);`);
|
||||
});
|
||||
|
||||
it('multiple classes', () => {
|
||||
const code = `import { MyElement } from 'my-element';
|
||||
class A extends MyElement {}
|
||||
class B {}
|
||||
class C extends HTMLElement {}
|
||||
class D extends MyElement {}`;
|
||||
const result = transform(code, { baseClasses: [{ name: 'MyElement', import: 'my-element' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { MyElement } from 'my-element';
|
||||
|
||||
class A extends MyElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, A);
|
||||
|
||||
class B {}
|
||||
|
||||
class C extends HTMLElement {}
|
||||
|
||||
class D extends MyElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, D);`);
|
||||
});
|
||||
|
||||
it('multiple base class definitions', () => {
|
||||
const code = `import { ElementA } from 'element-a';
|
||||
import ElementB from 'element-b';
|
||||
class A extends ElementA {}
|
||||
class B extends ElementB {}
|
||||
class C extends HTMLElement {}`;
|
||||
const result = transform(code, {
|
||||
baseClasses: [
|
||||
{ name: 'ElementA', import: 'element-a' },
|
||||
{ name: 'default', import: 'element-b' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { ElementA } from 'element-a';
|
||||
import ElementB from 'element-b';
|
||||
|
||||
class A extends ElementA {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, A);
|
||||
|
||||
class B extends ElementB {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, B);
|
||||
|
||||
class C extends HTMLElement {}`);
|
||||
});
|
||||
|
||||
it('base class with a specific import path', () => {
|
||||
const code = "import { MyElement } from '../MyElement.js'; class Foo extends MyElement {}";
|
||||
const result = transform(code, {
|
||||
baseClasses: [{ name: 'MyElement', import: path.join(rootDir, 'MyElement.js') }],
|
||||
});
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { MyElement } from '../MyElement.js';
|
||||
|
||||
class Foo extends MyElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);`);
|
||||
});
|
||||
});
|
||||
138
packages/dev-server-hmr/test-node/babel/customElements.test.js
Normal file
138
packages/dev-server-hmr/test-node/babel/customElements.test.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const { expect } = require('chai');
|
||||
const { banner, transform } = require('./utils');
|
||||
|
||||
describe('babelPluginWcHmr - detecting customElements.define', () => {
|
||||
it('injects registration when detecting a customElements.define', () => {
|
||||
const code = `class Foo extends HTMLElement {}\ncustomElements.define('x-foo', Foo);`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(
|
||||
`${banner}
|
||||
class Foo extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
customElements.define('x-foo', Foo);`,
|
||||
);
|
||||
});
|
||||
|
||||
it('injects registration when detecting a window.customElements.define', () => {
|
||||
const code = `class Foo extends HTMLElement {}\nwindow.customElements.define('x-foo', Foo);`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(
|
||||
`${banner}
|
||||
class Foo extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
window.customElements.define('x-foo', Foo);`,
|
||||
);
|
||||
});
|
||||
|
||||
it('injects registration when detecting a globalThis.customElements.define', () => {
|
||||
const code = `class Foo extends HTMLElement {}\nglobalThis.customElements.define('x-foo', Foo);`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(
|
||||
`${banner}
|
||||
class Foo extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
globalThis.customElements.define('x-foo', Foo);`,
|
||||
);
|
||||
});
|
||||
|
||||
it('injects multiple registrations', () => {
|
||||
const code =
|
||||
"class Foo extends HTMLElement {}\ncustomElements.define('x-foo', Foo);\n" +
|
||||
"class Bar extends HTMLElement {}\ncustomElements.define('x-bar', Bar);" +
|
||||
"class Baz extends HTMLElement {}\ncustomElements.define('x-baz', Baz);";
|
||||
// console.log(code);
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(
|
||||
`${banner}
|
||||
class Foo extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
customElements.define('x-foo', Foo);
|
||||
|
||||
class Bar extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Bar);
|
||||
|
||||
customElements.define('x-bar', Bar);
|
||||
|
||||
class Baz extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Baz);
|
||||
|
||||
customElements.define('x-baz', Baz);`,
|
||||
);
|
||||
});
|
||||
|
||||
it('can configure a patch to be injected', () => {
|
||||
const code = `class Foo extends HTMLElement {}\ncustomElements.define('x-foo', Foo);`;
|
||||
const result = transform(code, { patches: ['x.js'] });
|
||||
expect(result).to.equal(
|
||||
`import '/__web-dev-server__/wc-hmr/patch.js';
|
||||
${banner}
|
||||
class Foo extends HTMLElement {}
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
customElements.define('x-foo', Foo);`,
|
||||
);
|
||||
});
|
||||
|
||||
it('injects registration when detecting an inline class', () => {
|
||||
const code = `customElements.define('x-foo', class Foo extends HTMLElement {});`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(`${banner}
|
||||
const _Foo = class Foo extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, _Foo);
|
||||
|
||||
customElements.define('x-foo', _Foo);`);
|
||||
});
|
||||
|
||||
it('injects registration when detecting an anonymous inline class', () => {
|
||||
const code = `customElements.define('x-foo', class extends HTMLElement {});`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(`${banner}
|
||||
const _ref = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, _ref);
|
||||
|
||||
customElements.define('x-foo', _ref);`);
|
||||
});
|
||||
|
||||
it('deconflicts variable names', () => {
|
||||
const code = `const Foo = 1; const _Foo = 2; customElements.define('x-foo', class Foo extends HTMLElement {});`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(`${banner}
|
||||
const Foo = 1;
|
||||
const _Foo = 2;
|
||||
|
||||
const _Foo2 = class Foo extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, _Foo2);
|
||||
|
||||
customElements.define('x-foo', _Foo2);`);
|
||||
});
|
||||
|
||||
it('respects the custom element define scope', () => {
|
||||
const code = `console.log("x"); (function () { customElements.define('x-foo', class extends HTMLElement {}); })();`;
|
||||
const result = transform(code);
|
||||
expect(result).to.equal(`${banner}
|
||||
console.log("x");
|
||||
|
||||
(function () {
|
||||
const _ref = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, _ref);
|
||||
|
||||
customElements.define('x-foo', _ref);
|
||||
})();`);
|
||||
});
|
||||
});
|
||||
115
packages/dev-server-hmr/test-node/babel/decorators.test.js
Normal file
115
packages/dev-server-hmr/test-node/babel/decorators.test.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const { expect } = require('chai');
|
||||
const path = require('path');
|
||||
const { banner, transform, rootDir } = require('./utils');
|
||||
|
||||
describe('babelPluginWcHmr - detecting decorators', () => {
|
||||
it('compiled decorator with decorator name', () => {
|
||||
const code = `
|
||||
function __decorate() {}
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`;
|
||||
const result = transform(code, { decorators: [{ name: 'customElement' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`);
|
||||
});
|
||||
|
||||
it('handles multiple decorators', () => {
|
||||
const code = `
|
||||
function __decorate() {}
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([x(), y(), customElement('x-foo')], Foo);`;
|
||||
const result = transform(code, { decorators: [{ name: 'customElement' }] });
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
Foo = __decorate([x(), y(), customElement('x-foo')], Foo);`);
|
||||
});
|
||||
|
||||
it('does not inject for a decorator with a different name', () => {
|
||||
const code = `function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([notCustomElement('x-foo')], Foo);`;
|
||||
const result = transform(code, { decorators: [{ name: 'customElement' }] });
|
||||
expect(result).to.equal(code);
|
||||
});
|
||||
|
||||
it('compiled decorator with a bare import', () => {
|
||||
const code = `
|
||||
import {customElement} from 'my-package';
|
||||
function __decorate() {}
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`;
|
||||
const result = transform(code, {
|
||||
decorators: [{ name: 'customElement', import: 'my-package' }],
|
||||
});
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { customElement } from 'my-package';
|
||||
|
||||
function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`);
|
||||
});
|
||||
|
||||
it('compiled decorator with a default import', () => {
|
||||
const code = `
|
||||
import customElement from 'my-package';
|
||||
function __decorate() {}
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`;
|
||||
const result = transform(code, {
|
||||
decorators: [{ name: 'default', import: 'my-package' }],
|
||||
});
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import customElement from 'my-package';
|
||||
|
||||
function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
Foo = __decorate([customElement('x-foo')], Foo);`);
|
||||
});
|
||||
|
||||
it('compiled decorator with a specific import path', () => {
|
||||
const code = `
|
||||
import { defineElement } from '../defineElement.js';
|
||||
function __decorate() {}
|
||||
let Foo = class extends HTMLElement {};
|
||||
Foo = __decorate([defineElement('x-foo')], Foo);`;
|
||||
const result = transform(code, {
|
||||
decorators: [{ name: 'defineElement', import: path.join(rootDir, 'defineElement.js') }],
|
||||
});
|
||||
|
||||
expect(result).to.equal(`${banner}
|
||||
import { defineElement } from '../defineElement.js';
|
||||
|
||||
function __decorate() {}
|
||||
|
||||
let Foo = class extends HTMLElement {};
|
||||
|
||||
__$wc_hmr$__.register(import.meta.url, Foo);
|
||||
|
||||
Foo = __decorate([defineElement('x-foo')], Foo);`);
|
||||
});
|
||||
});
|
||||
27
packages/dev-server-hmr/test-node/babel/utils.js
Normal file
27
packages/dev-server-hmr/test-node/babel/utils.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { transformSync } = require('@babel/core');
|
||||
const path = require('path');
|
||||
const babelPluginWcHmr = require('../../src/babel/babelPluginWcHmr');
|
||||
|
||||
const banner = `import * as __$wc_hmr$__ from '/__web-dev-server__/wc-hmr/runtime.js';
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept();
|
||||
}
|
||||
`;
|
||||
|
||||
const rootDir = path.join(process.cwd(), 'virtual-project');
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
function transform(code, extraOptions) {
|
||||
return transformSync(code, {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: path.join(rootDir, 'src', 'foo.js'),
|
||||
plugins: [[babelPluginWcHmr, { rootDir, ...extraOptions }]],
|
||||
}).code;
|
||||
}
|
||||
|
||||
module.exports = { banner, transform, rootDir };
|
||||
24
packages/dev-server-hmr/tsconfig.json
Normal file
24
packages/dev-server-hmr/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
// Don't edit this file directly. It is generated by /scripts/update-package-configs.ts
|
||||
|
||||
{
|
||||
"extends": "../../tsconfig.node-base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "./types",
|
||||
"rootDir": ".",
|
||||
"composite": true,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"references": [],
|
||||
"include": [
|
||||
"src",
|
||||
"*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"types"
|
||||
]
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"@types/babel__core": "^7.1.3",
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^5.0.0",
|
||||
"@web/dev-server": "^0.0.19",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-storybook": "^0.1.3",
|
||||
"babel-loader": "^8.0.0",
|
||||
"mocha-chai-snapshot": "^1.0.0"
|
||||
|
||||
@@ -80,6 +80,7 @@ packageDirnameMap.forEach((packageDirname, packageName) => {
|
||||
rootDir: '.',
|
||||
composite: true,
|
||||
allowJs: true,
|
||||
strict: pkg.strict,
|
||||
checkJs: pkg.type === 'js' ? true : undefined,
|
||||
emitDeclarationOnly: pkg.type === 'js' ? true : undefined,
|
||||
},
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"path": "./packages/polyfills-loader/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./packages/dev-server-hmr/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./packages/import-maps-resolve/tsconfig.json"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const packages = [
|
||||
{ name: 'polyfills-loader', type: 'js', environment: 'node' },
|
||||
{ name: 'dev-server-hmr', type: 'js', environment: 'node', strict: true },
|
||||
{ name: 'import-maps-resolve', type: 'js', environment: 'node' },
|
||||
{ name: 'scoped-elements', type: 'js', environment: 'browser' },
|
||||
{ name: 'testing-helpers', type: 'js', environment: 'browser' },
|
||||
|
||||
341
yarn.lock
341
yarn.lock
@@ -361,16 +361,16 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@*", "@babel/parser@^7.12.5":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
|
||||
integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
|
||||
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.12.0", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3", "@babel/parser@^7.4.2", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.7.0", "@babel/parser@^7.9.6":
|
||||
version "7.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd"
|
||||
integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==
|
||||
|
||||
"@babel/parser@^7.12.5":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
|
||||
integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
|
||||
|
||||
"@babel/plugin-proposal-async-generator-functions@^7.12.1":
|
||||
version "7.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e"
|
||||
@@ -522,6 +522,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.10.4"
|
||||
|
||||
"@babel/plugin-syntax-import-assertions@^7.12.1":
|
||||
version "7.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.12.1.tgz#fd96f8f7bb681fa7dacfe470fa41add68f7f241f"
|
||||
integrity sha512-eyHdg2/DBrQfPgT7URyQacH0BuR2yJoKPkc5yQwRSh6yfA+77A5JRRGxwSOWHpsitWxtimHfPqJ+sZA2DuDJLg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.10.4"
|
||||
|
||||
"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.2.0":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
|
||||
@@ -1735,6 +1742,13 @@
|
||||
dependencies:
|
||||
extend "3.0.2"
|
||||
|
||||
"@mdn/browser-compat-data@^2.0.7":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-2.0.7.tgz#72ec37b9c1e00ce0b4e0309d753be18e2da12ee3"
|
||||
integrity sha512-GeeM827DlzFFidn1eKkMBiqXFD2oLsnZbaiGhByPl0vcapsRzUL+t9hDoov1swc9rB2jw64R+ihtzC8qOE9wXw==
|
||||
dependencies:
|
||||
extend "3.0.2"
|
||||
|
||||
"@mdx-js/loader@^1.5.1":
|
||||
version "1.6.19"
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.19.tgz#2b90dee54b6f959539f310776f18fe24e1f15cc5"
|
||||
@@ -1809,6 +1823,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.21.tgz#25f97a0a1b76e78c16ae5d98c6c73e1be8d89e39"
|
||||
integrity sha512-6sANhqfEHu6gdHZSrzDjN18Y48mIon8f2Os6J+IFmMHN0IhNG/0PUIIsI07kA1sZ9t6vgZNBloVmcDa5WOSe6A==
|
||||
|
||||
"@microsoft/fast-element@^0.19.1":
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-0.19.1.tgz#7b7cddc844168eb168f75e41ea63d23d942667cb"
|
||||
integrity sha512-qfEqj4s7E3B0qsNRoiQyKF682fdpEbCINV/GS6tE/ZOFC/B0CKpcZjk3mWyw8XAfsLA50E4YuR9Hn8SuMc2sFg==
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
|
||||
@@ -3141,6 +3160,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__code-frame/-/babel__code-frame-7.0.2.tgz#e0c0f1648cbc09a9d4e5b4ed2ae9a6f7c8f5aeb0"
|
||||
integrity sha512-imO+jT/yjOKOAS5GQZ8SDtwiIloAGGr6OaZDKB0V5JVaSfGZLat5K5/ZRtyKW6R60XHV3RHYPTFfhYb+wDKyKg==
|
||||
|
||||
"@types/babel__core@^7.1.12":
|
||||
version "7.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
|
||||
integrity sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
"@types/babel__generator" "*"
|
||||
"@types/babel__template" "*"
|
||||
"@types/babel__traverse" "*"
|
||||
|
||||
"@types/babel__core@^7.1.3":
|
||||
version "7.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40"
|
||||
@@ -3152,13 +3182,20 @@
|
||||
"@types/babel__template" "*"
|
||||
"@types/babel__traverse" "*"
|
||||
|
||||
"@types/babel__generator@*":
|
||||
"@types/babel__generator@*", "@types/babel__generator@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8"
|
||||
integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@types/babel__parser@^7.1.1":
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__parser/-/babel__parser-7.1.1.tgz#39b311e54697636b0b98127700b5f01f5e1e85f4"
|
||||
integrity sha512-baSzIb0QQOUQSglfR9gwXVSbHH91YvY00C9Zjq6E7sPdnp8oyPyUsonIj3SF4wUl0s96vR/kyWeVv30gmM/xZw==
|
||||
dependencies:
|
||||
"@babel/parser" "*"
|
||||
|
||||
"@types/babel__template@*":
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.3.tgz#b8aaeba0a45caca7b56a5de9459872dde3727214"
|
||||
@@ -3167,7 +3204,7 @@
|
||||
"@babel/parser" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@types/babel__traverse@*":
|
||||
"@types/babel__traverse@*", "@types/babel__traverse@^7.0.15":
|
||||
version "7.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03"
|
||||
integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==
|
||||
@@ -3627,6 +3664,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/path-is-inside/-/path-is-inside-1.0.0.tgz#02d6ff38975d684bdec96204494baf9f29f0e17f"
|
||||
integrity sha512-hfnXRGugz+McgX2jxyy5qz9sB21LRzlGn24zlwN2KEgoPtEvjzNRrLtUkOOebPDPZl3Rq7ywKxYvylVcEZDnEw==
|
||||
|
||||
"@types/picomatch@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/picomatch/-/picomatch-2.2.1.tgz#f9e5a5e6ad03996832975ab7eadfa35791ca2a8f"
|
||||
integrity sha512-26/tQcDmJXYHiaWAAIjnTVL5nwrT+IVaqFZIbBImAuKk/r/j1r/1hmZ7uaOzG6IknqP3QHcNNQ6QO8Vp28lUoA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
@@ -3896,6 +3938,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@ungap/from-entries/-/from-entries-0.2.1.tgz#7e86196b8b2e99d73106a8f25c2a068326346354"
|
||||
integrity sha512-CAqefTFAfnUPwYqsWHXpOxHaq1Zo5UQ3m9Zm2p09LggGe57rqHoBn3c++xcoomzXKynAUuiBMDUCQvKMnXjUpA==
|
||||
|
||||
"@ungap/promise-all-settled@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
|
||||
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
|
||||
|
||||
"@vue/compiler-core@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.2.tgz#7790b7a1fcbba5ace4d81a70ce59096fa5c95734"
|
||||
@@ -4023,6 +4070,29 @@
|
||||
picomatch "^2.2.2"
|
||||
ws "^7.3.1"
|
||||
|
||||
"@web/dev-server-core@^0.2.17", "@web/dev-server-core@^0.2.18":
|
||||
version "0.2.18"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-core/-/dev-server-core-0.2.18.tgz#c96bd373a9aa10429eceb13387202fd5ded06f4d"
|
||||
integrity sha512-cgZuB0eG6i1wYZydVEd+g5J8Qa0EyRcZcPk3977+E298kOZ/2XPpM1tattDBQHu9J1afl9gX9bW80OH0iuTMhQ==
|
||||
dependencies:
|
||||
"@types/koa" "^2.11.6"
|
||||
"@types/ws" "^7.2.6"
|
||||
"@web/parse5-utils" "^1.0.0"
|
||||
chokidar "^3.4.3"
|
||||
clone "^2.1.2"
|
||||
es-module-lexer "^0.3.26"
|
||||
get-stream "^6.0.0"
|
||||
is-stream "^2.0.0"
|
||||
isbinaryfile "^4.0.6"
|
||||
koa "^2.13.0"
|
||||
koa-etag "^3.0.0"
|
||||
koa-static "^5.0.0"
|
||||
lru-cache "^5.1.1"
|
||||
mime-types "^2.1.27"
|
||||
parse5 "^6.0.1"
|
||||
picomatch "^2.2.2"
|
||||
ws "^7.3.1"
|
||||
|
||||
"@web/dev-server-esbuild@^0.2.4":
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-esbuild/-/dev-server-esbuild-0.2.6.tgz#b9a73a0bcbe6bb6ede4e3409b8e87ac781c92d5d"
|
||||
@@ -4034,15 +4104,33 @@
|
||||
parse5 "^6.0.1"
|
||||
ua-parser-js "^0.7.21"
|
||||
|
||||
"@web/dev-server-rollup@^0.2.10":
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-rollup/-/dev-server-rollup-0.2.10.tgz#d0e902b997f9cc445ec80fc2db5e93e27ffbdcae"
|
||||
integrity sha512-jJHxHiircfj7SngFmwa8Rlc2n4o2MB+mLY8y4JyIXJG00ifgxNU6kSjbV/5shPAzzN6MD/H60EkE7n7kYfuAig==
|
||||
"@web/dev-server-esbuild@^0.2.8":
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-esbuild/-/dev-server-esbuild-0.2.8.tgz#1ce7f83c670f36e0e98ee2ac88ba1fec53f8fae1"
|
||||
integrity sha512-7HNIcoUVgVI7L/XGx/cSPGUqsIok41ucgxfIB+JYCAg+R0M9Jt8N7WQMfQFlHpoDLmqNpXuH+MYbY393u6hd6Q==
|
||||
dependencies:
|
||||
"@web/dev-server-core" "^0.2.11"
|
||||
"@mdn/browser-compat-data" "^2.0.7"
|
||||
"@web/dev-server-core" "^0.2.2"
|
||||
esbuild "^0.8.12"
|
||||
parse5 "^6.0.1"
|
||||
ua-parser-js "^0.7.22"
|
||||
|
||||
"@web/dev-server-hmr@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-hmr/-/dev-server-hmr-0.1.6.tgz#5a8e8b2c02dcdf9392d001b11cb010acd4f92ce0"
|
||||
integrity sha512-J3wQ46OeWMA5I48fyPNygbgbCLUIxvysAOanXMCVkuYen3ybWQ+T60CKqwYXGEOaRK3aWqxiyNFyHHvJzjeSIA==
|
||||
dependencies:
|
||||
"@web/dev-server-core" "^0.2.18"
|
||||
|
||||
"@web/dev-server-rollup@^0.2.11":
|
||||
version "0.2.11"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server-rollup/-/dev-server-rollup-0.2.11.tgz#daa65b9e2f0eaeaa9d7c6d39d0943d494eda8bb7"
|
||||
integrity sha512-cqeXC93BElaKIV0Bel05M86Or5HQKem7FuBjfoMMlgp0irt6ZYCovYe7l2rG0Hw+/MCYaNs+uxjMK5UYLuWMBw==
|
||||
dependencies:
|
||||
"@web/dev-server-core" "^0.2.17"
|
||||
chalk "^4.1.0"
|
||||
parse5 "^6.0.1"
|
||||
rollup "^2.20.0"
|
||||
rollup "^2.33.2"
|
||||
whatwg-url "^8.1.0"
|
||||
|
||||
"@web/dev-server-rollup@^0.2.5", "@web/dev-server-rollup@^0.2.9":
|
||||
@@ -4103,27 +4191,26 @@
|
||||
open "^7.1.0"
|
||||
portfinder "^1.0.27"
|
||||
|
||||
"@web/dev-server@^0.0.19":
|
||||
version "0.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server/-/dev-server-0.0.19.tgz#cf473079269fa2fdd6eee728f7a67a0648ec5e30"
|
||||
integrity sha512-e+mFEaXB/Fax8OcFT9+b/Z/4NqG/qr6edITgQdk3WxW5O+HYF0ramsZ6grSCwS9tly+/QfTlCnLJCMPOcibqYA==
|
||||
"@web/dev-server@^0.0.24":
|
||||
version "0.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@web/dev-server/-/dev-server-0.0.24.tgz#891de7da5497a538d74b50b7c99a492a1f5661b3"
|
||||
integrity sha512-vBvI6Q3h7oiTq2G9j6Xlj4LkCpTulSycEDvaFCrHwadDvKLoQLPFqm1wv4A8mvjU0zm6Mjs5X5eZndNJU0naIA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
"@rollup/plugin-node-resolve" "^8.4.0"
|
||||
"@types/command-line-args" "^5.0.0"
|
||||
"@web/config-loader" "^0.1.1"
|
||||
"@web/dev-server-cli" "^0.0.3"
|
||||
"@web/dev-server-core" "^0.2.15"
|
||||
"@web/dev-server-rollup" "^0.2.10"
|
||||
camelcase "^6.0.0"
|
||||
"@web/dev-server-core" "^0.2.18"
|
||||
"@web/dev-server-rollup" "^0.2.11"
|
||||
camelcase "^6.2.0"
|
||||
chalk "^4.1.0"
|
||||
command-line-args "^5.1.1"
|
||||
command-line-usage "^6.1.0"
|
||||
command-line-usage "^6.1.1"
|
||||
debounce "^1.2.0"
|
||||
deepmerge "^4.2.2"
|
||||
ip "^1.1.5"
|
||||
open "^7.1.0"
|
||||
portfinder "^1.0.27"
|
||||
open "^7.3.0"
|
||||
portfinder "^1.0.28"
|
||||
|
||||
"@web/parse5-utils@^1.0.0", "@web/parse5-utils@^1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -4662,16 +4749,16 @@ ansi-colors@3.2.3:
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
|
||||
integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
|
||||
|
||||
ansi-colors@4.1.1, ansi-colors@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
ansi-colors@^3.0.0:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
|
||||
|
||||
ansi-colors@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||
@@ -6310,6 +6397,11 @@ camelcase@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.1.0.tgz#27dc176173725fb0adf8a48b647f4d7871944d78"
|
||||
integrity sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==
|
||||
|
||||
camelcase@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
|
||||
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
||||
|
||||
can-use-dom@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a"
|
||||
@@ -6527,6 +6619,21 @@ cheerio@^1.0.0-rc.3:
|
||||
lodash "^4.15.0"
|
||||
parse5 "^3.0.1"
|
||||
|
||||
chokidar@3.4.3, chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
|
||||
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.0"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.5.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chokidar@^2.0.4, chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
@@ -6546,21 +6653,6 @@ chokidar@^2.0.4, chokidar@^2.1.8:
|
||||
optionalDependencies:
|
||||
fsevents "^1.2.7"
|
||||
|
||||
chokidar@^3.0.0, chokidar@^3.0.2, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
|
||||
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.0"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.5.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chownr@^1.1.1, chownr@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
@@ -6960,6 +7052,16 @@ command-line-usage@^6.1.0:
|
||||
table-layout "^1.0.0"
|
||||
typical "^5.2.0"
|
||||
|
||||
command-line-usage@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f"
|
||||
integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==
|
||||
dependencies:
|
||||
array-back "^4.0.1"
|
||||
chalk "^2.4.2"
|
||||
table-layout "^1.0.1"
|
||||
typical "^5.2.0"
|
||||
|
||||
commander@^2.19.0, commander@^2.2.0, commander@^2.20.0, commander@^2.20.3, commander@^2.3.0, commander@^2.8.1:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
@@ -7670,7 +7772,7 @@ debug@3.2.6, debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
debug@4, debug@4.2.0, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
|
||||
integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
|
||||
@@ -7704,6 +7806,11 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||
|
||||
decamelize@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
|
||||
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
|
||||
|
||||
decimal.js@^10.2.0:
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
|
||||
@@ -8102,7 +8209,7 @@ diff@3.5.0, diff@^3.5.0:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
diff@^4.0.2:
|
||||
diff@4.0.2, diff@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
@@ -8761,6 +8868,11 @@ es-module-lexer@^0.3.13, es-module-lexer@^0.3.24, es-module-lexer@^0.3.25:
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.25.tgz#24a1abcb9c5dc96923a8e42be033b801f788de06"
|
||||
integrity sha512-H9VoFD5H9zEfiOX2LeTWDwMvAbLqcAyA2PIb40TOAvGpScOjit02oTGWgIh+M0rx2eJOKyJVM9wtpKFVgnyC3A==
|
||||
|
||||
es-module-lexer@^0.3.26:
|
||||
version "0.3.26"
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.26.tgz#7b507044e97d5b03b01d4392c74ffeb9c177a83b"
|
||||
integrity sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==
|
||||
|
||||
es-module-shims@^0.4.6:
|
||||
version "0.4.7"
|
||||
resolved "https://registry.yarnpkg.com/es-module-shims/-/es-module-shims-0.4.7.tgz#1419b65bbd38dfe91ab8ea5d7b4b454561e44641"
|
||||
@@ -8790,6 +8902,11 @@ esbuild@^0.7.15:
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.7.22.tgz#9149b903f8128b7c45a754046c24199d76bbe08e"
|
||||
integrity sha512-B43SYg8LGWYTCv9Gs0RnuLNwjzpuWOoCaZHTWEDEf5AfrnuDMerPVMdCEu7xOdhFvQ+UqfP2MGU9lxEy0JzccA==
|
||||
|
||||
esbuild@^0.8.12:
|
||||
version "0.8.12"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.12.tgz#bea417dbb228a971a5df255cb1b8acf5ea96ef52"
|
||||
integrity sha512-SWCUodYFOkKcu2Jbum12NbD98MoQq2EWWvHfQtpR2C9RkPzRr1BE4cavYjQpQV01hBR19UAftXl0TVLjka/i3Q==
|
||||
|
||||
escalade@^3.1.0, escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
@@ -9614,6 +9731,14 @@ find-up@3.0.0, find-up@^3.0.0:
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
find-up@5.0.0, find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||
dependencies:
|
||||
locate-path "^6.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-up@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||
@@ -9637,14 +9762,6 @@ find-up@^4.0.0, find-up@^4.1.0:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||
dependencies:
|
||||
locate-path "^6.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-versions@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e"
|
||||
@@ -9676,6 +9793,11 @@ flat@^4.1.0:
|
||||
dependencies:
|
||||
is-buffer "~2.0.3"
|
||||
|
||||
flat@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
||||
|
||||
flatted@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||
@@ -10164,7 +10286,7 @@ glob@7.1.3:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
@@ -12222,7 +12344,7 @@ js-yaml@3.13.1:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^3.11.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.6.1:
|
||||
js-yaml@3.14.0, js-yaml@^3.11.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.6.1:
|
||||
version "3.14.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
||||
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
||||
@@ -12816,7 +12938,7 @@ listr2@^2.6.0:
|
||||
rxjs "^6.6.2"
|
||||
through "^2.3.8"
|
||||
|
||||
lit-element@^2.0.1, lit-element@^2.2.1, lit-element@^2.3.1, lit-element@~2.4.0:
|
||||
lit-element@^2.0.1, lit-element@^2.2.1, lit-element@^2.3.1, lit-element@^2.4.0, lit-element@~2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.4.0.tgz#b22607a037a8fc08f5a80736dddf7f3f5d401452"
|
||||
integrity sha512-pBGLglxyhq/Prk2H91nA0KByq/hx/wssJBQFiYqXhGDvEnY31PRGYf1RglVzyLeRysu0IHm2K0P196uLLWmwFg==
|
||||
@@ -13137,6 +13259,13 @@ log-symbols@2.2.0:
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
|
||||
log-symbols@4.0.0, log-symbols@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
|
||||
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
|
||||
log-symbols@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
|
||||
@@ -13151,13 +13280,6 @@ log-symbols@^3.0.0:
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
|
||||
log-symbols@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
|
||||
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
|
||||
log-update@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
||||
@@ -13895,6 +14017,37 @@ mocha@^6.2.2:
|
||||
yargs-parser "13.1.2"
|
||||
yargs-unparser "1.6.0"
|
||||
|
||||
mocha@^8.2.1:
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39"
|
||||
integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==
|
||||
dependencies:
|
||||
"@ungap/promise-all-settled" "1.1.2"
|
||||
ansi-colors "4.1.1"
|
||||
browser-stdout "1.3.1"
|
||||
chokidar "3.4.3"
|
||||
debug "4.2.0"
|
||||
diff "4.0.2"
|
||||
escape-string-regexp "4.0.0"
|
||||
find-up "5.0.0"
|
||||
glob "7.1.6"
|
||||
growl "1.10.5"
|
||||
he "1.2.0"
|
||||
js-yaml "3.14.0"
|
||||
log-symbols "4.0.0"
|
||||
minimatch "3.0.4"
|
||||
ms "2.1.2"
|
||||
nanoid "3.1.12"
|
||||
serialize-javascript "5.0.1"
|
||||
strip-json-comments "3.1.1"
|
||||
supports-color "7.2.0"
|
||||
which "2.0.2"
|
||||
wide-align "1.1.3"
|
||||
workerpool "6.0.2"
|
||||
yargs "13.3.2"
|
||||
yargs-parser "13.1.2"
|
||||
yargs-unparser "2.0.0"
|
||||
|
||||
mock-require@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946"
|
||||
@@ -14010,6 +14163,11 @@ nan@^2.12.1:
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
|
||||
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
|
||||
|
||||
nanoid@3.1.12:
|
||||
version "3.1.12"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
|
||||
integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
@@ -14628,7 +14786,7 @@ open@^6.3.0:
|
||||
dependencies:
|
||||
is-wsl "^1.1.0"
|
||||
|
||||
open@^7.0.0, open@^7.0.3, open@^7.0.4, open@^7.1.0:
|
||||
open@^7.0.0, open@^7.0.3, open@^7.0.4, open@^7.1.0, open@^7.3.0:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69"
|
||||
integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==
|
||||
@@ -15377,7 +15535,7 @@ popper.js@^1.14.4, popper.js@^1.14.7, popper.js@^1.15.0:
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
|
||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||
|
||||
portfinder@^1.0.13, portfinder@^1.0.21, portfinder@^1.0.26, portfinder@^1.0.27:
|
||||
portfinder@^1.0.13, portfinder@^1.0.21, portfinder@^1.0.26, portfinder@^1.0.27, portfinder@^1.0.28:
|
||||
version "1.0.28"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
|
||||
integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
|
||||
@@ -17361,6 +17519,13 @@ rollup@^2.33.1:
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
rollup@^2.33.2:
|
||||
version "2.33.3"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.33.3.tgz#ae72ce31f992b09a580072951bfea76e9df17342"
|
||||
integrity sha512-RpayhPTe4Gu/uFGCmk7Gp5Z9Qic2VsqZ040G+KZZvsZYdcuWaJg678JeDJJvJeEQXminu24a2au+y92CUWVd+w==
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
rsvp@^4.8.4:
|
||||
version "4.8.5"
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
@@ -17645,6 +17810,13 @@ sentence-case@^3.0.3:
|
||||
tslib "^1.10.0"
|
||||
upper-case-first "^2.0.1"
|
||||
|
||||
serialize-javascript@5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
|
||||
@@ -18613,7 +18785,7 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
@@ -18654,6 +18826,13 @@ supports-color@6.0.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@7.2.0, supports-color@^7.0.0, supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
|
||||
@@ -18678,13 +18857,6 @@ supports-color@^6.1.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.0.0, supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-hyperlinks@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7"
|
||||
@@ -18729,7 +18901,7 @@ systemjs@^6.3.1, systemjs@^6.7.1:
|
||||
resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.7.1.tgz#3db5036f450180a0701e078fbb5b434a690026f0"
|
||||
integrity sha512-Q78H/SYy9ErC8PH8r9vA/FcQ3X+Hf33dOpx2JKP/Ma6f2gHuSScPFuCKZH+6CIj7EsIJlzODxSG4mMIpjOh5oA==
|
||||
|
||||
table-layout@^1.0.0:
|
||||
table-layout@^1.0.0, table-layout@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9"
|
||||
integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==
|
||||
@@ -19384,7 +19556,7 @@ typical@^5.0.0, typical@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
|
||||
integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
|
||||
|
||||
ua-parser-js@^0.7.18, ua-parser-js@^0.7.21:
|
||||
ua-parser-js@^0.7.18, ua-parser-js@^0.7.21, ua-parser-js@^0.7.22:
|
||||
version "0.7.22"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
|
||||
integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==
|
||||
@@ -20255,7 +20427,7 @@ which@1.3.1, which@^1.2.9, which@^1.3.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1, which@^2.0.2:
|
||||
which@2.0.2, which@^2.0.1, which@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
@@ -20519,6 +20691,11 @@ worker-rpc@^0.1.0:
|
||||
dependencies:
|
||||
microevent.ts "~0.1.1"
|
||||
|
||||
workerpool@6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438"
|
||||
integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==
|
||||
|
||||
wrap-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131"
|
||||
@@ -20746,6 +20923,16 @@ yargs-unparser@1.6.0:
|
||||
lodash "^4.17.15"
|
||||
yargs "^13.3.0"
|
||||
|
||||
yargs-unparser@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
|
||||
integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
|
||||
dependencies:
|
||||
camelcase "^6.0.0"
|
||||
decamelize "^4.0.0"
|
||||
flat "^5.0.2"
|
||||
is-plain-obj "^2.1.0"
|
||||
|
||||
yargs@13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
|
||||
|
||||
Reference in New Issue
Block a user