Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[refactor] rewrite WebCell 3 core API with latest ES/TS, MobX & vDOM #17

Merged
merged 7 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Commit preview
on:
push:
branches-ignore:
- main
jobs:
Build-and-Deploy:
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm
- name: Install & Build
run: |
pnpm i --frozen-lockfile
pnpm build

- uses: amondnet/vercel-action@v25
if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }}
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./docs
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ yarn.lock
dist/
.parcel-cache/
docs/
.vscode/settings.json
.vscode/settings.json
.vercel
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.eslintrc.json
jest.config.ts
test/
preview/
.parcel-cache/
Contributing.md
docs/
.husky/
.github/
.vscode/
.vscode/
.vercel/
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
auto-install-peers = false
42 changes: 21 additions & 21 deletions Migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@ import {
component,
+ observer,
- mixin,
+ WebCell,
createCell,
Fragment
- createCell,
- Fragment
} from 'web-cell';
+import { observable } from 'mobx';

-interface State {
+class State {
+ @observable
key: string;
- key: string;
+ accessor key = '';
}

@component({
tagName: 'my-tag'
})
+@observer
-export class MyTag extends mixin<{}, State>() {
+export class MyTag extends WebCell() {
+export class MyTag extends HTMLElement {
- state: Readonly<State> = {
- key: 'value'
- };
Expand All @@ -57,7 +57,7 @@ At the same time, `shouldUpdate() {}` life-cycle has been dropped. You just need
MobX's [`@observable`][4] & [`reaction()`][5] are awesome APIs to implement these above with clear codes, so we add `mobx` package as a dependency:

```shell
npm install mobx@5
npm install mobx
```

On the other hand, [`mobx-web-cell` adapter][6] has been merged into the core package. And cause of replacing **Prototype Overwrite** with **Class Inheritance** to refactor **Class Mixins**, `@observer` decorator should follow strict order to make observation work:
Expand All @@ -70,9 +70,8 @@ import {
- watch,
+ observer,
- mixin,
+ WebCell,
createCell,
Fragment
- createCell,
- Fragment
} from 'web-cell';
-import { observer } from 'mobx-web-cell';
+import { observable } from 'mobx';
Expand All @@ -86,11 +85,14 @@ export interface MyTagProps extends WebCellProps {
})
@observer
-export class MyTag extends mixin<MyTagProps>() {
+export class MyTag extends WebCell<MyTagProps>() {
+export class MyTag extends HTMLElement {
+ declare props: MyTagProps;

@attribute
- @watch
+ @observable
count = 0;
- count = 0;
+ accessor count = 0;

- render({ count }: MyTagProps) {
+ render() {
Expand All @@ -109,15 +111,14 @@ export interface MyTagProps extends WebCellProps {
import {
component,
- mixin
+ WebCell
} from 'web-cell';

@component({
tagName: 'my-tag',
- renderTarget: 'children'
})
-export class MyTag extends mixin() {
+export class MyTag extends WebCell() {
+export class MyTag extends HTMLElement {
}
```

Expand All @@ -127,7 +128,6 @@ import {
import {
component,
- mixin
+ WebCell
} from 'web-cell';

@component({
Expand All @@ -136,7 +136,7 @@ import {
+ mode: 'open'
})
-export class MyTag extends mixin() {
+export class MyTag extends WebCell() {
+export class MyTag extends HTMLElement {
}
```

Expand All @@ -149,7 +149,6 @@ This makes **Shadow CSS** to react with the data of component instances.
import {
component,
- mixin
+ WebCell
} from 'web-cell';

@component({
Expand All @@ -163,7 +162,7 @@ import {
- }
})
-export class MyTag extends mixin() {
+export class MyTag extends WebCell() {
+export class MyTag extends HTMLElement {
render() {
return <>
+ <style>
Expand All @@ -183,14 +182,15 @@ import {

[JSDoc's `@deprecated` hints][7] will lead your way to rename them:

1. `mixin()` => `WebCell()`
1. `mixin()` => `HTMLElement` & its Sub-classes
2. `mixinForm()` => `WebField()`
3. `@watch` => `@observable`
3. `@watch` => `@observable accessor`

## Appendix: v3 prototype

1. https://codesandbox.io/s/web-components-jsx-i7u60?file=/index.tsx
2. https://codesandbox.io/s/mobx-lite-791eg?file=/src/index.ts
1. [Legacy architecture](https://codesandbox.io/s/web-components-jsx-i7u60?file=/index.tsx)
2. [Modern architecture](https://codesandbox.io/s/mobx-web-components-pvn9rf?file=/src/WebComponent.ts)
3. [MobX lite](https://codesandbox.io/s/mobx-lite-791eg?file=/src/index.ts)

[1]: https://github.com/mobxjs/mobx/blob/mobx4and5/docs/refguide/observer-component.md#local-observable-state-in-class-based-components
[2]: https://blog.cloudboost.io/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e
Expand Down
97 changes: 63 additions & 34 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[Web Components][1] engine based on VDOM, [JSX][2], [MobX][12] & [TypeScript][3]

[![NPM Dependency](https://david-dm.org/EasyWebApp/WebCell.svg)][4]
[![NPM Dependency](https://img.shields.io/librariesio/github/EasyWebApp/WebCell.svg)][4]
[![CI & CD](https://github.com/EasyWebApp/WebCell/actions/workflows/main.yml/badge.svg)][5]

[![Anti 996 license](https://img.shields.io/badge/license-Anti%20996-blue.svg)][6]
Expand All @@ -17,21 +17,38 @@

[![NPM](https://nodei.co/npm/web-cell.png?downloads=true&downloadRank=true&stars=true)][11]

## Feature

### Engines comparison

| feature | WebCell 3 | WebCell 2 | React | Vue |
| :-----------: | :------------------: | :------------------: | :---------------------------: | :---------------------------------: |
| JS language | TypeScript 5 | TypeScript 4 | ECMAScript or TypeScript | ECMAScript or TypeScript |
| JS syntax | ES decorator stage-3 | ES decorator stage-2 | | |
| XML syntax | JSX import | JSX factory | JSX factory/import | HTML/Vue template or JSX (optional) |
| DOM API | Web components | Web components | HTML 5+ | HTML 5+ |
| view renderer | DOM renderer 2 | SnabbDOM | (built-in) | SnabbDOM (forked) |
| state API | MobX `@observable` | `this.state` | `this.state` or `useState()` | `this.$data` or `ref()` |
| props API | MobX `@observable` | `@watch` | `this.props` or `props => {}` | `this.$props` or `defineProps()` |
| state manager | MobX 6+ | MobX 4/5 | Redux | VueX |
| page router | JSX tags | JSX tags + JSON data | JSX tags | JSON data |
| asset bundler | Parcel 2 | Parcel 1 | webpack | Vite |

## Usage

Demo & **GitHub template**: https://web-cell.dev/scaffold/

### Project bootstrap

Command
#### Command

```shell
npm init -y
npm install web-cell
npm install parcel -D
npm install dom-renderer mobx web-cell
npm install parcel @parcel/config-default "@parcel/transformer-typescript-tsc" -D
```

`package.json`
#### `package.json`

```json
{
Expand All @@ -42,18 +59,37 @@ npm install parcel -D
}
```

[`tsconfig.json`](https://github.com/EasyWebApp/WebCell/blob/main/tsconfig.json)
#### `tsconfig.json`

```json
{
"compilerOptions": {
"target": "ES6",
"moduleResolution": "Node",
"useDefineForClassFields": true,
"jsx": "react-jsx",
"jsxImportSource": "dom-renderer"
}
}
```

`source/index.html`
#### `.parcelrc`

```json
{
"extends": "@parcel/config-default",
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}
```

#### `source/index.html`

```html
<script
crossorigin
src="https://polyfill.app/api/polyfill?features=es.array.flat,es.object.from-entries"
></script>
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/[email protected]/webcomponents-bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@webcomponents/[email protected]/custom-elements-es5-adapter.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ECMAScript.js"></script>
<script src="https://polyfill.web-cell.dev/feature/WebComponents.js"></script>
<script src="https://polyfill.web-cell.dev/feature/ElementInternals.js"></script>

<script src="source/SubTag.tsx"></script>
<script src="source/TestTag.tsx"></script>
Expand All @@ -67,16 +103,16 @@ npm install parcel -D
`source/SubTag.tsx`

```jsx
import { WebCellProps, WebCell, createCell, component } from 'web-cell';
import { WebCellProps, component } from 'web-cell';

export function InlineTag({ defaultSlot }: WebCellProps) {
return <span>{defaultSlot}</span>;
export function InlineTag({ children }: WebCellProps) {
return <span>{children}</span>;
}

@component({
tagName: 'sub-tag'
})
export class SubTag extends WebCell() {
export class SubTag extends HTMLElement {
render() {
return <InlineTag>test</InlineTag>;
}
Expand All @@ -87,17 +123,8 @@ export class SubTag extends WebCell() {

`source/TestTag.tsx`

```typescript
import {
WebCellProps,
WebCell,
createCell,
component,
attribute,
observer,
on,
Fragment
} from 'web-cell';
```tsx
import { WebCellProps, component, attribute, observer, on } from 'web-cell';
import { observable } from 'mobx';

import { SubTag } from './SubTag';
Expand All @@ -108,18 +135,20 @@ export interface TestTagProps extends WebCellProps {

class State {
@observable
status = '';
accessor status = '';
}

@component({
tagName: 'test-tag',
mode: 'open'
})
@observer
export class TestTag extends WebCell<TestTagProps>() {
export class TestTag extends HTMLElement {
declare props: TestTagProps;

@attribute
@observable
topic = 'Test';
accessor topic = 'Test';

state = new State();

Expand Down Expand Up @@ -167,7 +196,7 @@ export class TestTag extends WebCell<TestTagProps>() {
- [Element Internals](https://web.dev/more-capable-form-controls/)
- [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)
- [ECMAScript 6+](http://es6-features.org/)
- [TypeScript 4+][3]
- [TypeScript 5+][3]

## Life Cycle hooks

Expand Down Expand Up @@ -233,7 +262,7 @@ We recommend these libraries to use with WebCell:
[1]: https://www.webcomponents.org/
[2]: https://facebook.github.io/jsx/
[3]: https://www.typescriptlang.org
[4]: https://david-dm.org/EasyWebApp/WebCell
[4]: https://libraries.io/npm/web-cell
[5]: https://github.com/EasyWebApp/WebCell/actions/workflows/main.yml
[6]: https://github.com/996icu/996.ICU/blob/master/LICENSE
[7]: https://github.com/jaywcjlove/awesome-uikit
Expand Down
4 changes: 2 additions & 2 deletions source/Async.tsx → legacy/Async.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { observable } from 'mobx';

import { ComponentTag, WebCellProps, FunctionComponent } from './utility';
import { WebCellClass, WebCell } from './WebCell';
import { component, observer, reaction } from './decorator';
import { WebCellClass, WebCell } from '../source/WebCell';
import { component, observer, reaction } from '../source/decorator';
import { createCell } from './renderer';

export interface AsyncBoxProps extends WebCellProps {
Expand Down
Loading