mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-12 21:50:23 +08:00
fork @vue-reactivity/watch to fix bundler issue
@vue-reactivity/watch use tsup as its bundler, which create .mjs file as the ES module output. But webpack4 do not like .mjs extension which cause tools like create-react-app failed when using meta-ui. Even users can add some trick to let webpack4 bundle .mjs file, the watch function still not working. Refs: 1. https://github.com/facebook/create-react-app/issues/10356 2. https://github.com/formatjs/formatjs/issues/1395#issuecomment-518823361
This commit is contained in:
parent
aa6a643bf3
commit
fd86d9cc0e
@ -6,7 +6,8 @@
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
@ -18,10 +19,7 @@
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"indent": ["error", 2, { "flatTernaryExpressions": true, "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"react/prop-types": "off",
|
||||
"no-case-declarations": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
||||
"@typescript-eslint/parser": "^4.31.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-react": "^7.25.1",
|
||||
"husky": "^6.0.0",
|
||||
"lerna": "^4.0.0",
|
||||
|
@ -23,7 +23,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"test": "jest",
|
||||
"build": "tsup src/index.ts --format cjs,esm,iife --legacy-output --inject ./react-import.js",
|
||||
"build": "tsup src/index.ts --format cjs,esm,iife --legacy-output --inject ./react-import.js --clean --no-splitting --sourcemap",
|
||||
"typings": "tsc --emitDeclarationOnly --declarationDir typings",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"prepublish": "npm run build && npm run typings"
|
||||
@ -35,8 +35,8 @@
|
||||
"@emotion/styled": "^11",
|
||||
"@meta-ui/core": "^0.2.1",
|
||||
"@sinclair/typebox": "^0.20.5",
|
||||
"@vue-reactivity/watch": "^0.1.6",
|
||||
"@vue/reactivity": "^3.1.5",
|
||||
"@vue/shared": "^3.2.20",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"dayjs": "^1.10.6",
|
||||
"framer-motion": "^4",
|
||||
|
@ -3,7 +3,7 @@ import { css } from '@emotion/react';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { createComponent } from '@meta-ui/core';
|
||||
import { Button, VStack } from '@chakra-ui/react';
|
||||
import { watch } from '@vue-reactivity/watch';
|
||||
import { watch } from '../../../utils/watchReactivity';
|
||||
import { ComponentImplementation } from '../../../services/registry';
|
||||
import Slot from '../../_internal/Slot';
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
FormLabel,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { watch } from '@vue-reactivity/watch';
|
||||
import { watch } from '../../../utils/watchReactivity';
|
||||
import {
|
||||
FormControlContentCSS,
|
||||
FormControlCSS,
|
||||
|
@ -5,7 +5,7 @@ import { getSlots } from '../_internal/Slot';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { partial } from 'lodash';
|
||||
|
||||
const BaseGridLayout = React.lazy(() => import('../../components/_internal/GridLayout'));
|
||||
const BaseGridLayout = React.lazy(() => import('../_internal/GridLayout'));
|
||||
|
||||
const GridLayout: ComponentImplementation<Static<typeof PropsSchema>> = ({
|
||||
slotsMap,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StateManager } from './stateStore';
|
||||
import { ApiService } from './apiService';
|
||||
import { watch } from '@vue-reactivity/watch';
|
||||
import { watch } from '../utils/watchReactivity';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
export const DebugStore: React.FC<{ stateManager: StateManager }> = ({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { watch } from '@vue-reactivity/watch';
|
||||
import { watch } from '../utils/watchReactivity';
|
||||
import { merge } from 'lodash';
|
||||
import {
|
||||
RuntimeApplicationComponent,
|
||||
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { reactive } from '@vue/reactivity';
|
||||
import { watch } from '@vue-reactivity/watch';
|
||||
import { watch } from '../utils/watchReactivity';
|
||||
import { LIST_ITEM_EXP, LIST_ITEM_INDEX_EXP } from '../constants';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
@ -125,13 +125,13 @@ export class StateManager {
|
||||
return _.mapValues(obj, (val, key) => {
|
||||
return _.isArray(val)
|
||||
? val.map((innerVal, idx) => {
|
||||
return _.isPlainObject(innerVal)
|
||||
? this.mapValuesDeep(innerVal, fn, path.concat(key, idx))
|
||||
: fn({ value: innerVal, key, obj, path: path.concat(key, idx) });
|
||||
})
|
||||
return _.isPlainObject(innerVal)
|
||||
? this.mapValuesDeep(innerVal, fn, path.concat(key, idx))
|
||||
: fn({ value: innerVal, key, obj, path: path.concat(key, idx) });
|
||||
})
|
||||
: _.isPlainObject(val)
|
||||
? this.mapValuesDeep(val, fn, path.concat(key))
|
||||
: fn({ value: val, key, obj, path: path.concat(key) });
|
||||
? this.mapValuesDeep(val, fn, path.concat(key))
|
||||
: fn({ value: val, key, obj, path: path.concat(key) });
|
||||
});
|
||||
}
|
||||
|
||||
|
266
packages/runtime/src/utils/watchReactivity.ts
Normal file
266
packages/runtime/src/utils/watchReactivity.ts
Normal file
@ -0,0 +1,266 @@
|
||||
// forked from https://github.com/vue-reactivity/watch/blob/master/src/index.ts by Anthony Fu
|
||||
// ported from https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/apiWatch.ts by Evan You
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
|
||||
import {
|
||||
ComputedRef,
|
||||
effect,
|
||||
Ref,
|
||||
ReactiveEffectOptions,
|
||||
isReactive,
|
||||
isRef,
|
||||
stop,
|
||||
} from '@vue/reactivity';
|
||||
import { hasChanged, isArray, isFunction, isObject, NOOP, isPromise } from '@vue/shared';
|
||||
|
||||
export function callWithErrorHandling(fn: Function, type: string, args?: unknown[]) {
|
||||
let res;
|
||||
try {
|
||||
res = args ? fn(...args) : fn();
|
||||
} catch (err) {
|
||||
handleError(err, type);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function callWithAsyncErrorHandling(
|
||||
fn: Function | Function[],
|
||||
type: string,
|
||||
args?: unknown[]
|
||||
): any[] {
|
||||
if (isFunction(fn)) {
|
||||
const res = callWithErrorHandling(fn, type, args);
|
||||
if (res && isPromise(res)) {
|
||||
res.catch(err => {
|
||||
handleError(err, type);
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const values = [];
|
||||
for (let i = 0; i < fn.length; i++)
|
||||
values.push(callWithAsyncErrorHandling(fn[i], type, args));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
function handleError(err: unknown, type: String) {
|
||||
console.error(new Error(`[@vue-reactivity/watch]: ${type}`));
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
export function warn(message: string) {
|
||||
console.warn(createError(message));
|
||||
}
|
||||
|
||||
function createError(message: string) {
|
||||
return new Error(`[reactivue]: ${message}`);
|
||||
}
|
||||
|
||||
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;
|
||||
|
||||
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);
|
||||
|
||||
export type WatchCallback<V = any, OV = any> = (
|
||||
value: V,
|
||||
oldValue: OV,
|
||||
onInvalidate: InvalidateCbRegistrator
|
||||
) => any;
|
||||
|
||||
export type WatchStopHandle = () => void;
|
||||
|
||||
type MapSources<T> = {
|
||||
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||
? V
|
||||
: T[K] extends object
|
||||
? T[K]
|
||||
: never;
|
||||
};
|
||||
|
||||
type MapOldSources<T, Immediate> = {
|
||||
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||
? Immediate extends true
|
||||
? V | undefined
|
||||
: V
|
||||
: T[K] extends object
|
||||
? Immediate extends true
|
||||
? T[K] | undefined
|
||||
: T[K]
|
||||
: never;
|
||||
};
|
||||
|
||||
type InvalidateCbRegistrator = (cb: () => void) => void;
|
||||
const invoke = (fn: Function) => fn();
|
||||
const INITIAL_WATCHER_VALUE = {};
|
||||
|
||||
export interface WatchOptionsBase {
|
||||
/**
|
||||
* @depreacted ignored in `@vue-reactivity/watch` and will always be `sync`
|
||||
*/
|
||||
flush?: 'sync' | 'pre' | 'post';
|
||||
onTrack?: ReactiveEffectOptions['onTrack'];
|
||||
onTrigger?: ReactiveEffectOptions['onTrigger'];
|
||||
}
|
||||
|
||||
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
||||
immediate?: Immediate;
|
||||
deep?: boolean;
|
||||
}
|
||||
|
||||
// Simple effect.
|
||||
export function watchEffect(
|
||||
effect: WatchEffect,
|
||||
options?: WatchOptionsBase
|
||||
): WatchStopHandle {
|
||||
return doWatch(effect, null, options);
|
||||
}
|
||||
|
||||
// overload #1: array of multiple sources + cb
|
||||
// Readonly constraint helps the callback to correctly infer value types based
|
||||
// on position in the source array. Otherwise the values will get a union type
|
||||
// of all possible value types.
|
||||
export function watch<
|
||||
T extends Readonly<Array<WatchSource<unknown> | object>>,
|
||||
Immediate extends Readonly<boolean> = false
|
||||
>(
|
||||
sources: T,
|
||||
cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>
|
||||
): WatchStopHandle;
|
||||
|
||||
// overload #2: single source + cb
|
||||
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
||||
source: WatchSource<T>,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
options?: WatchOptions<Immediate>
|
||||
): WatchStopHandle;
|
||||
|
||||
// overload #3: watching reactive object w/ cb
|
||||
export function watch<T extends object, Immediate extends Readonly<boolean> = false>(
|
||||
source: T,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
options?: WatchOptions<Immediate>
|
||||
): WatchStopHandle;
|
||||
|
||||
// implementation
|
||||
export function watch<T = any>(
|
||||
source: WatchSource<T> | WatchSource<T>[],
|
||||
cb: WatchCallback<T>,
|
||||
options?: WatchOptions
|
||||
): WatchStopHandle {
|
||||
return doWatch(source, cb, options);
|
||||
}
|
||||
|
||||
function doWatch(
|
||||
source: WatchSource | WatchSource[] | WatchEffect,
|
||||
cb: WatchCallback | null,
|
||||
{ immediate, deep, onTrack, onTrigger }: WatchOptions = {}
|
||||
): WatchStopHandle {
|
||||
let getter: () => any;
|
||||
if (isArray(source) && !isReactive(source)) {
|
||||
getter = () =>
|
||||
// eslint-disable-next-line array-callback-return
|
||||
source.map(s => {
|
||||
if (isRef(s)) return s.value;
|
||||
else if (isReactive(s)) return traverse(s);
|
||||
else if (isFunction(s)) return callWithErrorHandling(s, 'watch getter');
|
||||
else warn('invalid source');
|
||||
});
|
||||
} else if (isRef(source)) {
|
||||
getter = () => source.value;
|
||||
} else if (isReactive(source)) {
|
||||
getter = () => source;
|
||||
deep = true;
|
||||
} else if (isFunction(source)) {
|
||||
if (cb) {
|
||||
// getter with cb
|
||||
getter = () => callWithErrorHandling(source, 'watch getter');
|
||||
} else {
|
||||
// no cb -> simple effect
|
||||
getter = () => {
|
||||
if (cleanup) cleanup();
|
||||
|
||||
return callWithErrorHandling(source, 'watch callback', [onInvalidate]);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
getter = NOOP;
|
||||
}
|
||||
|
||||
if (cb && deep) {
|
||||
const baseGetter = getter;
|
||||
getter = () => traverse(baseGetter());
|
||||
}
|
||||
|
||||
let cleanup: () => void;
|
||||
const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
|
||||
cleanup = (runner as any).options.onStop = () => {
|
||||
callWithErrorHandling(fn, 'watch cleanup');
|
||||
};
|
||||
};
|
||||
|
||||
let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE;
|
||||
const applyCb = cb
|
||||
? () => {
|
||||
const newValue = runner();
|
||||
if (deep || hasChanged(newValue, oldValue)) {
|
||||
// cleanup before running cb again
|
||||
if (cleanup) cleanup();
|
||||
|
||||
callWithAsyncErrorHandling(cb, 'watch callback', [
|
||||
newValue,
|
||||
// pass undefined as the old value when it's changed for the first time
|
||||
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
|
||||
onInvalidate,
|
||||
]);
|
||||
oldValue = newValue;
|
||||
}
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const scheduler = invoke;
|
||||
|
||||
const runner = effect(getter, {
|
||||
lazy: true,
|
||||
onTrack,
|
||||
onTrigger,
|
||||
scheduler: applyCb ? () => scheduler(applyCb) : scheduler,
|
||||
});
|
||||
|
||||
// initial run
|
||||
if (applyCb) {
|
||||
if (immediate) applyCb();
|
||||
else oldValue = runner();
|
||||
} else {
|
||||
runner();
|
||||
}
|
||||
|
||||
const stopWatcher = function () {
|
||||
stop(runner);
|
||||
};
|
||||
stopWatcher.effect = runner;
|
||||
return stopWatcher;
|
||||
}
|
||||
|
||||
function traverse(value: unknown, seen: Set<unknown> = new Set()) {
|
||||
if (!isObject(value) || seen.has(value)) return value;
|
||||
|
||||
seen.add(value);
|
||||
if (isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) traverse(value[i], seen);
|
||||
} else if (value instanceof Map) {
|
||||
value.forEach((_, key) => {
|
||||
// to register mutation dep for existing keys
|
||||
traverse(value.get(key), seen);
|
||||
});
|
||||
} else if (value instanceof Set) {
|
||||
value.forEach(v => {
|
||||
traverse(v, seen);
|
||||
});
|
||||
} else {
|
||||
for (const key of Object.keys(value)) traverse(value[key], seen);
|
||||
}
|
||||
return value;
|
||||
}
|
10
yarn.lock
10
yarn.lock
@ -3168,11 +3168,6 @@
|
||||
react-refresh "^0.10.0"
|
||||
resolve "^1.20.0"
|
||||
|
||||
"@vue-reactivity/watch@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@vue-reactivity/watch/-/watch-0.1.6.tgz#631c91319dee62724eb020e89dc2f868b480cb6d"
|
||||
integrity sha512-Se4D+1LAnn8B49MuRzWyZv2syb+9FTuxXKA8FF6gjZD08PdVwoVZG3ncZzqFFG2lVlzx03+rjOCtIJb+VkD3Qg==
|
||||
|
||||
"@vue/reactivity@^3.1.5":
|
||||
version "3.2.16"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.16.tgz#0d4253443d580c906508b0b05b2cd136d46bf4a2"
|
||||
@ -3185,6 +3180,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.16.tgz#a7f5e37e07ac68d4b7ea8ebeba515b46d205c524"
|
||||
integrity sha512-zpv8lxuatl3ruCJCsGzrO/F4+IlLug4jbu3vaIi/wJVZKQgnsW1R/xSRJMQS6K57cl4fT/2zkrYsWh1/6H7Esw==
|
||||
|
||||
"@vue/shared@^3.2.20":
|
||||
version "3.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.20.tgz#53746961f731a8ea666e3316271e944238dc31db"
|
||||
integrity sha512-FbpX+hD5BvXCQerEYO7jtAGHlhAkhTQ4KIV73kmLWNlawWhTiVuQxizgVb0BOkX5oG9cIRZ42EG++d/k/Efp0w==
|
||||
|
||||
JSONStream@^1.0.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user