@lark.js/mvc 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ A TypeScript MVC framework designed for back-office single-page applications and
22
22
  - Template Syntax
23
23
  - Frame and the View Tree
24
24
  - Module Federation Micro-Frontend
25
- - Debugging and DevTools Bridge
25
+ - Debugging and Devtool Bridge
26
26
  - Public API Reference
27
27
  - Common Pitfalls
28
28
  - Recent API Changes
@@ -41,7 +41,7 @@ Third, zero runtime dependencies. `@babel/parser` / `@babel/types` are used only
41
41
 
42
42
  Fourth, real DOM diff. Templates compile to functions that produce HTML strings, which are parsed into temporary DOM via `document.implementation.createHTMLDocument` and then diffed against the live DOM using keyed comparison. The advantage is that context-sensitive tags like `<table>` / `<select>` / `<svg>` are handled by the native parser. The trade-off is that large templates incur parse overhead, and SSR is not supported.
43
43
 
44
- Fifth, debug-friendly. `window.__lark_Debug = true` enables Safeguard Proxy protection against cross-page pollution and accidental writes. `installFrameVisualizerBridge` exposes the Frame tree to visual DevTools via `postMessage`. A set of `window.__lark_*` global shortcuts cover Framework / State / Router / Frame / View and HMR helpers.
44
+ Fifth, debug-friendly. `window.__lark_Debug = true` enables Safeguard Proxy protection against cross-page pollution and accidental writes. `installFrameDevtoolBridge` exposes the Frame tree to Devtool via `postMessage`. A set of `window.__lark_*` global shortcuts cover Framework / State / Router / Frame / View and HMR helpers.
45
45
 
46
46
  Not suitable for: projects requiring SSR/streaming rendering, cross-platform needs like React Native, or projects needing off-the-shelf Chrome extension panels. For those, consider the React or Vue ecosystems.
47
47
 
@@ -220,7 +220,7 @@ const config: FrameworkConfig = {
220
220
  Framework.boot(config);
221
221
  ```
222
222
 
223
- `Framework.boot()` executes the following steps in order (order is correctness-sensitive): merge user config (including `routeMode`), inject config into Router (which determines history/hash mode), set EventDelegator's frame getter, subscribe to Router/State `changed` events, mark Framework/Router/State as booted, install the Frame Visualizer Bridge, create the root Frame via `Frame.createRoot(config.rootId)`, call `Router._bind()` to bind route events (popstate for history mode, hashchange + popstate for hash mode) and trigger the first `diff()`, and finally mount `defaultView` if Router has not mounted a view. Step seven must precede step eight because the first `diff()` may immediately trigger `CHANGED` followed by `Frame.getRoot()`, and if the root Frame does not exist it degrades to rendering against the wrong element.
223
+ `Framework.boot()` executes the following steps in order (order is correctness-sensitive): merge user config (including `routeMode`), inject config into Router (which determines history/hash mode), set EventDelegator's frame getter, subscribe to Router/State `changed` events, mark Framework/Router/State as booted, install the Frame Devtool Bridge, create the root Frame via `Frame.createRoot(config.rootId)`, call `Router._bind()` to bind route events (poptate for history mode, hashchange + popstate for hash mode) and trigger the first `diff()`, and finally mount `defaultView` if Router has not mounted a view. Step seven must precede step eight because the first `diff()` may immediately trigger `CHANGED` followed by `Frame.getRoot()`, and if the root Frame does not exist it degrades to rendering against the wrong element.
224
224
 
225
225
  ## Three Data Pipelines: Updater / State / Store
226
226
 
@@ -832,7 +832,7 @@ new ModuleFederationPlugin({
832
832
 
833
833
  `splitChunks.chunks` must be `"async"`. Using `"all"` extracts `@lark.js/mvc` into a separate vendor chunk, breaking MF shared scope initialization (`ScriptExternalLoadError: Loading script failed`).
834
834
 
835
- ## Debugging and DevTools Bridge
835
+ ## Debugging and Devtool Bridge
836
836
 
837
837
  ### Global Objects
838
838
 
@@ -857,15 +857,15 @@ Set `window.__lark_Debug = true` before boot, and the framework wraps `State.get
857
857
  - Warns when reading data written by another page (potential cross-page pollution).
858
858
  - Warns immediately when assigning directly to objects returned by `State.get()` (deduplicated by key); the correct approach is `State.set(patch)` + `State.digest()`.
859
859
 
860
- ### Frame Visualizer Bridge
860
+ ### Frame Devtool Bridge
861
861
 
862
- `installFrameVisualizerBridge()` is automatically installed during `Framework.boot`, listening for `window` message events and communicating with DevTools via postMessage:
862
+ `installFrameDevtoolBridge()` is automatically installed during `Framework.boot`, listening for `window` message events and communicating with Devtool via postMessage:
863
863
 
864
- - `LARK_VIS_PING` — responds with `LARK_VIS_PONG` to confirm this page is a Lark application.
865
- - `LARK_VIS_REQUEST_TREE` — responds with `LARK_VIS_TREE` carrying `SerializedFrameTree`.
866
- - Internally listens to `Frame.on('add' | 'remove')` and automatically pushes `LARK_VIS_TREE_DELTA`; JSON.stringify is compared with `lastTreeJson` before pushing to avoid flooding when nothing changed.
864
+ - `LARK_DEVTOOL_PING` — responds with `LARK_DEVTOOL_PONG` to confirm this page is a Lark application.
865
+ - `LARK_DEVTOOL_REQUEST_TREE` — responds with `LARK_DEVTOOL_TREE` carrying `SerializedFrameTree`.
866
+ - Internally listens to `Frame.on('add' | 'remove')` and automatically pushes `LARK_DEVTOOL_TREE_DELTA`; JSON.stringify is compared with `lastTreeJson` before pushing to avoid flooding when nothing changed.
867
867
 
868
- The `lark-visual` sub-project in this repository is the paired visual DevTools that loads the target application via iframe to display the real-time Frame tree.
868
+ The `lark-devtool` sub-project in this repository is the paired Devtool that loads the target application via iframe to display the real-time Frame tree.
869
869
 
870
870
  ## Public API Reference
871
871
 
@@ -927,28 +927,6 @@ The `lark-visual` sub-project in this repository is the paired visual DevTools t
927
927
  19. MF view paths use the remote project name as prefix: `v-lark="remote-app/views/home"` triggers async loading via `FrameworkConfig.require` when unregistered; `@lark.js/mvc` must be `singleton: true`.
928
928
  20. `splitChunks.chunks` must be `"async"` in MF projects: `"all"` breaks shared scope initialization.
929
929
 
930
- ## Recent API Changes
931
-
932
- - Store rewrite (zustand-style):
933
- - `defineStore(name, (store) => body)` replaced by `create(name, (set, get) => body)`. `defineStore` retained as deprecated alias.
934
- - `store.key = value` (Proxy write) replaced by `set({ key: value })`.
935
- - `store.key` reads in actions replaced by `get().key`.
936
- - `useStore(view)` + `store.observe(view, keys?)` replaced by `bindStore(view, store, selector?)`.
937
- - `useStore()` (read-only access) replaced by `store.getState()`.
938
- - `store.observe(undefined, keys, cb)` (internal reaction) replaced by `store.subscribe((state, prev) => ...)`.
939
- - Removed: `multi()`, `cell()`, `observeCell()`, `cloneStore()`, `getStore()`, `delStore()`, `getUseStore()`, `isStoreActive()`, `createState()`, `shallowSet()`, `lazySet()`, `cloneData()`, `isState()`, `storeMark`, `storeUnmark`, `getPlatform`, `Platform`, `StoreConfig`, `ObservePayload`, `StoreMethods`, `LarkUseStore`, `ReactUseStore`, `NodeUseStore`.
940
- - Router history mode support:
941
- - Added `FrameworkConfig.routeMode` (`"history"` default, `"hash"` optional).
942
- - In history mode, path comes from `window.location.pathname`, params from search query string.
943
- - Added `useUrlState(view, initialState?)` for URL parameter state sync.
944
- - `ChangeEvent.keys` changed to `ReadonlySet<string>` (was `Record<string, 1>`). Use `keys.has("foo")` instead of `keys.foo`.
945
- - `StateInterface.diff()` returns `ReadonlySet<string>`.
946
- - `Updater.set/digest`, `State.set/digest`, `setData` `excludes?` changed to `ReadonlySet<string>` (was `Set<string>`).
947
- - `Frame.root(id)` `@deprecated`. Read via `Frame.getRoot()`, create singleton via `Frame.createRoot(id)`, independent mount via `new Frame(id)`.
948
- - `Updater.parse` no longer evals; only supports safe paths and literals.
949
- - `mark.ts` no longer writes magic keys to host objects; uses module-level `WeakMap`, works on `Object.freeze`d objects.
950
- - `Cache.del` immediately removes from `entries` array and `lookup` Map (previously left tombstones until next eviction).
951
-
952
930
  ## Comparison with Vue 3 / React 19
953
931
 
954
932
  ### vs Vue 3
@@ -0,0 +1,108 @@
1
+ // src/common.ts
2
+ var globalCounter = 0;
3
+ var SPLITTER = String.fromCharCode(30);
4
+ var RouterEvents = {
5
+ CHANGE: "change",
6
+ CHANGED: "changed",
7
+ PAGE_UNLOAD: "page_unload"
8
+ };
9
+ var LARK_VIEW = "v-lark";
10
+ var EVENT_METHOD_REGEXP = new RegExp(
11
+ `(?:([\\w-]+)${SPLITTER})?([^(]+)\\(([\\s\\S]*?)?\\)`
12
+ );
13
+ var VIEW_EVENT_METHOD_REGEXP = /^(\$?)([\w]*)<(.*?)>(?:<([\w ,]*)>)?$/;
14
+ var URL_TRIM_HASH_REGEXP = /(?:^.*\/\/[^/]+|#.*$)/gi;
15
+ var URL_TRIM_QUERY_REGEXP = /^[^#]*#?!?/;
16
+ var URL_PARAM_REGEXP = /([^=&?/#]+)=?([^&#?]*)/g;
17
+ var IS_URL_PARAMS = /(?!^)=|&/;
18
+ var URL_QUERY_HASH_REGEXP = /[#?].*$/;
19
+ var SVG_NS = "http://www.w3.org/2000/svg";
20
+ var MATH_NS = "http://www.w3.org/1998/Math/MathML";
21
+ var TAG_NAME_REGEXP = /<([a-z][^/\0>\x20\t\r\n\f]+)/i;
22
+ var V_TEXT_NODE = 0;
23
+ var VDOM_NS_MAP = {
24
+ svg: SVG_NS,
25
+ math: MATH_NS
26
+ };
27
+ function nextCounter() {
28
+ return ++globalCounter;
29
+ }
30
+ var HTML_ENT_MAP = {
31
+ "&": "amp",
32
+ "<": "lt",
33
+ ">": "gt",
34
+ '"': "#34",
35
+ "'": "#39",
36
+ "`": "#96"
37
+ };
38
+ var HTML_ENT_REGEXP = /[&<>"'`]/g;
39
+ function strSafe(v) {
40
+ return String(v == null ? "" : v);
41
+ }
42
+ function encodeHTML(v) {
43
+ return String(v == null ? "" : v).replace(
44
+ HTML_ENT_REGEXP,
45
+ (m) => "&" + HTML_ENT_MAP[m] + ";"
46
+ );
47
+ }
48
+ var URI_ENT_MAP = {
49
+ "!": "%21",
50
+ "'": "%27",
51
+ "(": "%28",
52
+ ")": "%29",
53
+ "*": "%2A"
54
+ };
55
+ var URI_ENT_REGEXP = /[!')(*]/g;
56
+ function encodeURIExtra(v) {
57
+ return encodeURIComponent(strSafe(v)).replace(
58
+ URI_ENT_REGEXP,
59
+ (m) => URI_ENT_MAP[m]
60
+ );
61
+ }
62
+ var QUOTE_ENT_REGEXP = /['"\\]/g;
63
+ function encodeQuote(v) {
64
+ return strSafe(v).replace(QUOTE_ENT_REGEXP, "\\$&");
65
+ }
66
+ function refFn(ref, value, key) {
67
+ const counter = ref[SPLITTER];
68
+ for (let i = counter; --i; ) {
69
+ key = SPLITTER + i;
70
+ if (ref[key] === value) return key;
71
+ }
72
+ key = SPLITTER + ref[SPLITTER]++;
73
+ ref[key] = value;
74
+ return key;
75
+ }
76
+ function isRefToken(s) {
77
+ if (s.length < 2 || s[0] !== SPLITTER) return false;
78
+ for (let i = 1; i < s.length; i++) {
79
+ const c = s.charCodeAt(i);
80
+ if (c < "0".charCodeAt(0) || c > "9".charCodeAt(0)) return false;
81
+ }
82
+ return true;
83
+ }
84
+
85
+ export {
86
+ SPLITTER,
87
+ RouterEvents,
88
+ LARK_VIEW,
89
+ EVENT_METHOD_REGEXP,
90
+ VIEW_EVENT_METHOD_REGEXP,
91
+ URL_TRIM_HASH_REGEXP,
92
+ URL_TRIM_QUERY_REGEXP,
93
+ URL_PARAM_REGEXP,
94
+ IS_URL_PARAMS,
95
+ URL_QUERY_HASH_REGEXP,
96
+ SVG_NS,
97
+ MATH_NS,
98
+ TAG_NAME_REGEXP,
99
+ V_TEXT_NODE,
100
+ VDOM_NS_MAP,
101
+ nextCounter,
102
+ strSafe,
103
+ encodeHTML,
104
+ encodeURIExtra,
105
+ encodeQuote,
106
+ refFn,
107
+ isRefToken
108
+ };