diff --git a/package.json b/package.json
index ac31ba9919..a50089f1f2 100644
--- a/package.json
+++ b/package.json
@@ -82,6 +82,7 @@
"@makotot/ghostui": "^2.0.0",
"@stackblitz/sdk": "^1.9.0",
"@swc/core": "1.4.2",
+ "@types/hash-sum": "^1.0.0",
"@types/hast": "^2.3.5",
"@types/mdast": "^3.0.12",
"@umijs/bundler-utils": "^4.0.84",
@@ -96,10 +97,12 @@
"dumi-afx-deps": "^1.0.0-alpha.19",
"dumi-assets-types": "2.0.0-alpha.0",
"enhanced-resolve": "^5.15.0",
+ "es-module-lexer": "^1.5.0",
"estree-util-to-js": "^1.2.0",
"estree-util-visit": "^1.2.1",
"file-system-cache": "^2.4.3",
"github-slugger": "^1.5.0",
+ "hash-sum": "^2.0.0",
"hast-util-is-element": "^2.1.3",
"hast-util-raw": "^8.0.0",
"hast-util-to-estree": "^2.3.3",
@@ -168,6 +171,7 @@
"eslint": "^8.46.0",
"fast-glob": "^3.3.1",
"father": "^4.3.0",
+ "happy-dom": "^14.3.9",
"highlight-words-core": "^1.2.2",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4187285831..c64c077497 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
importers:
.:
@@ -20,6 +16,9 @@ importers:
'@swc/core':
specifier: 1.4.2
version: 1.4.2
+ '@types/hash-sum':
+ specifier: ^1.0.0
+ version: 1.0.0
'@types/hast':
specifier: ^2.3.5
version: 2.3.5
@@ -62,6 +61,9 @@ importers:
enhanced-resolve:
specifier: ^5.15.0
version: 5.15.0
+ es-module-lexer:
+ specifier: ^1.5.0
+ version: 1.5.0
estree-util-to-js:
specifier: ^1.2.0
version: 1.2.0
@@ -74,6 +76,9 @@ importers:
github-slugger:
specifier: ^1.5.0
version: 1.5.0
+ hash-sum:
+ specifier: ^2.0.0
+ version: 2.0.0
hast-util-is-element:
specifier: ^2.1.3
version: 2.1.3
@@ -273,6 +278,9 @@ importers:
father:
specifier: ^4.3.0
version: 4.3.0(@types/node@18.17.1)(styled-components@6.1.8)(webpack@5.89.0)
+ happy-dom:
+ specifier: ^14.3.9
+ version: 14.3.9
highlight-words-core:
specifier: ^1.2.2
version: 1.2.2
@@ -308,7 +316,7 @@ importers:
version: 5.0.4
vitest:
specifier: ^0.33.0
- version: 0.33.0(sass@1.64.1)
+ version: 0.33.0(happy-dom@14.3.9)(sass@1.64.1)
assets-types: {}
@@ -876,7 +884,7 @@ packages:
peerDependencies:
react: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
json2mq: 0.2.0
react: 18.2.0
@@ -2937,7 +2945,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -2946,7 +2954,7 @@ packages:
resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==}
engines: {node: '>=8.x'}
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
/@rc-component/mutate-observer@1.1.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==}
@@ -2955,7 +2963,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -3038,6 +3046,7 @@ packages:
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
+ dev: false
/@rc-component/trigger@1.18.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-jRLYgFgjLEPq3MvS87fIhcfuywFSRDaDrYw1FLku7Cm4esszvzTbA0JBsyacAyLrK9rF3TiHFcvoEDMzoD3CTA==}
@@ -4425,7 +4434,7 @@ packages:
axios: 0.27.2
babel-plugin-import: 1.13.8
dayjs: 1.11.10
- dva-core: 2.0.4(redux@4.2.1)
+ dva-core: 2.0.4(redux@3.7.2)
dva-immer: 1.0.1(dva@2.5.0-beta.2)
dva-loading: 3.0.24(dva-core@2.0.4)
event-emitter: 0.3.5
@@ -4545,7 +4554,7 @@ packages:
react-refresh: 0.14.0
schema-utils: 3.3.0
source-map: 0.7.4
- webpack: 5.89.0(@swc/core@1.4.2)
+ webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
/@umijs/renderer-react@4.0.84(react-dom@18.1.0)(react@18.1.0):
resolution: {integrity: sha512-0SDMuLsBpXmdNzubwke0ihq1tvlhDumZn0BJ0JC7xavOmx9bx6jWo3BRrBn1BfkUoCJC8E4C8ZmDJMKrmQ7BUA==}
@@ -6607,7 +6616,7 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.33)
postcss-value-parser: 4.2.0
semver: 7.5.4
- webpack: 5.89.0(@swc/core@1.4.2)
+ webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
/css-prefers-color-scheme@6.0.3(postcss@8.4.29):
resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==}
@@ -7104,7 +7113,7 @@ packages:
warning: 3.0.0
dev: true
- /dva-core@2.0.4(redux@4.2.1):
+ /dva-core@2.0.4(redux@3.7.2):
resolution: {integrity: sha512-Zh39llFyItu9HKXKfCZVf9UFtDTcypdAjGBew1S+wK8BGVzFpm1GPTdd6uIMeg7O6STtCvt2Qv+RwUut1GFynA==}
peerDependencies:
redux: 4.x
@@ -7114,7 +7123,7 @@ packages:
global: 4.4.0
invariant: 2.2.4
is-plain-object: 2.0.4
- redux: 4.2.1
+ redux: 3.7.2
redux-saga: 0.16.2
warning: 3.0.0
dev: true
@@ -7135,7 +7144,7 @@ packages:
dva-core: ^1.1.0 || ^1.5.0-0 || ^1.6.0-0
dependencies:
'@babel/runtime': 7.23.1
- dva-core: 2.0.4(redux@4.2.1)
+ dva-core: 2.0.4(redux@3.7.2)
dev: true
/dva@2.5.0-beta.2(react-dom@18.2.0)(react@18.2.0):
@@ -7362,12 +7371,8 @@ packages:
iterator.prototype: 1.1.2
safe-array-concat: 1.0.1
- /es-module-lexer@1.3.1:
- resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==}
- dev: true
-
- /es-module-lexer@1.4.1:
- resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==}
+ /es-module-lexer@1.5.0:
+ resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==}
/es-set-tostringtag@2.0.1:
resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
@@ -8353,7 +8358,7 @@ packages:
semver: 7.5.4
tapable: 2.2.1
typescript: 5.0.4
- webpack: 5.89.0(@swc/core@1.4.2)
+ webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
/fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.89.0):
resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==}
@@ -8759,6 +8764,15 @@ packages:
/handle-thing@2.0.1:
resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==}
+ /happy-dom@14.3.9:
+ resolution: {integrity: sha512-0kPQchwthekcYpYN8CvCiq+/z5bqFYDLbTxZ+yDLwT8AFRVJDFadShHRxp3VAZRy7a5isOZ1j/LzsU1dtAIZMQ==}
+ engines: {node: '>=16.0.0'}
+ dependencies:
+ entities: 4.5.0
+ webidl-conversions: 7.0.0
+ whatwg-mimetype: 3.0.0
+ dev: true
+
/hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
@@ -9111,7 +9125,7 @@ packages:
lodash: 4.17.21
pretty-error: 4.0.0
tapable: 2.2.1
- webpack: 5.89.0(@swc/core@1.4.2)
+ webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
dev: false
/html2sketch@1.0.2:
@@ -12846,6 +12860,11 @@ packages:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
+ /punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+ dev: true
+
/q@1.5.1:
resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==}
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
@@ -12934,7 +12953,7 @@ packages:
dependencies:
loader-utils: 2.0.4
schema-utils: 3.3.0
- webpack: 5.89.0(@swc/core@1.4.2)
+ webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
dev: false
/rc-align@4.0.15(react-dom@18.2.0)(react@18.2.0):
@@ -12989,7 +13008,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -13029,7 +13048,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
'@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
@@ -13310,7 +13329,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
@@ -13376,8 +13395,8 @@ packages:
moment:
optional: true
dependencies:
- '@babel/runtime': 7.23.2
- '@rc-component/trigger': 1.18.1(react-dom@18.2.0)(react@18.2.0)
+ '@babel/runtime': 7.23.8
+ '@rc-component/trigger': 1.18.2(react-dom@18.2.0)(react@18.2.0)
classnames: 2.3.2
dayjs: 1.11.10
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
@@ -13390,7 +13409,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -13403,7 +13422,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -13456,7 +13475,7 @@ packages:
react: '>=16.0.0'
react-dom: '>=16.0.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
@@ -13534,7 +13553,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -13546,7 +13565,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -13748,7 +13767,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
@@ -13837,7 +13856,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.23.2
+ '@babel/runtime': 7.23.8
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0)(react@18.2.0)
@@ -15545,31 +15564,6 @@ packages:
serialize-javascript: 6.0.2
terser: 5.27.0
webpack: 5.89.0(@swc/core@1.4.2)(esbuild@0.19.11)
- dev: false
-
- /terser-webpack-plugin@5.3.10(@swc/core@1.4.2)(webpack@5.89.0):
- resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
- engines: {node: '>= 10.13.0'}
- peerDependencies:
- '@swc/core': '*'
- esbuild: '*'
- uglify-js: '*'
- webpack: ^5.1.0
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- esbuild:
- optional: true
- uglify-js:
- optional: true
- dependencies:
- '@jridgewell/trace-mapping': 0.3.22
- '@swc/core': 1.4.2
- jest-worker: 27.5.1
- schema-utils: 3.3.0
- serialize-javascript: 6.0.2
- terser: 5.27.0
- webpack: 5.89.0(@swc/core@1.4.2)
/terser@5.20.0:
resolution: {integrity: sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==}
@@ -15724,7 +15718,7 @@ packages:
/tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
dependencies:
- punycode: 2.3.0
+ punycode: 2.3.1
dev: true
/transformation-matrix@2.15.0:
@@ -16252,7 +16246,7 @@ packages:
engines: {node: '>=14.19.0'}
dependencies:
'@rollup/pluginutils': 5.0.4
- es-module-lexer: 1.3.1
+ es-module-lexer: 1.5.0
magic-string: 0.30.3
unplugin: 1.5.0
transitivePeerDependencies:
@@ -16602,7 +16596,7 @@ packages:
fsevents: 2.3.3
dev: true
- /vitest@0.33.0(sass@1.64.1):
+ /vitest@0.33.0(happy-dom@14.3.9)(sass@1.64.1):
resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -16646,6 +16640,7 @@ packages:
cac: 6.7.14
chai: 4.3.9
debug: 4.3.4
+ happy-dom: 14.3.9
local-pkg: 0.4.3
magic-string: 0.30.3
pathe: 1.1.1
@@ -16813,6 +16808,11 @@ packages:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true
+ /webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+ dev: true
+
/webpack-sources@3.2.3:
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
engines: {node: '>=10.13.0'}
@@ -16821,45 +16821,6 @@ packages:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
dev: true
- /webpack@5.89.0(@swc/core@1.4.2):
- resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==}
- engines: {node: '>=10.13.0'}
- hasBin: true
- peerDependencies:
- webpack-cli: '*'
- peerDependenciesMeta:
- webpack-cli:
- optional: true
- dependencies:
- '@types/eslint-scope': 3.7.7
- '@types/estree': 1.0.5
- '@webassemblyjs/ast': 1.11.6
- '@webassemblyjs/wasm-edit': 1.11.6
- '@webassemblyjs/wasm-parser': 1.11.6
- acorn: 8.11.3
- acorn-import-assertions: 1.9.0(acorn@8.11.3)
- browserslist: 4.22.2
- chrome-trace-event: 1.0.3
- enhanced-resolve: 5.15.0
- es-module-lexer: 1.4.1
- eslint-scope: 5.1.1
- events: 3.3.0
- glob-to-regexp: 0.4.1
- graceful-fs: 4.2.11
- json-parse-even-better-errors: 2.3.1
- loader-runner: 4.3.0
- mime-types: 2.1.35
- neo-async: 2.6.2
- schema-utils: 3.3.0
- tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(@swc/core@1.4.2)(webpack@5.89.0)
- watchpack: 2.4.0
- webpack-sources: 3.2.3
- transitivePeerDependencies:
- - '@swc/core'
- - esbuild
- - uglify-js
-
/webpack@5.89.0(@swc/core@1.4.2)(esbuild@0.19.11):
resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==}
engines: {node: '>=10.13.0'}
@@ -16880,7 +16841,7 @@ packages:
browserslist: 4.22.2
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
- es-module-lexer: 1.4.1
+ es-module-lexer: 1.5.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
@@ -16898,12 +16859,16 @@ packages:
- '@swc/core'
- esbuild
- uglify-js
- dev: false
/whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
dev: true
+ /whatwg-mimetype@3.0.0:
+ resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
+ engines: {node: '>=12'}
+ dev: true
+
/whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
dependencies:
@@ -17140,3 +17105,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
diff --git a/src/client/misc/reactDemoCompiler.ts b/src/client/misc/reactDemoCompiler.ts
index 25eff521be..5fd335175f 100644
--- a/src/client/misc/reactDemoCompiler.ts
+++ b/src/client/misc/reactDemoCompiler.ts
@@ -1,10 +1,12 @@
-import { transform } from 'sucrase';
+import { transform, type Transform } from 'sucrase';
import type { IDemoCompileFn } from '../theme-api/types';
-const compile: IDemoCompileFn = async (code) => {
- return transform(code, {
- transforms: ['typescript', 'jsx', 'imports'],
- }).code;
+const compile: IDemoCompileFn = async (code, { modules }) => {
+ const transforms: Transform[] = ['typescript', 'jsx'];
+ if (modules === 'cjs') {
+ transforms.push('imports');
+ }
+ return transform(code, { transforms }).code;
};
export default compile;
diff --git a/src/client/pages/Demo/index.ts b/src/client/pages/Demo/index.ts
index c90b96f5c9..8b2b67ccce 100644
--- a/src/client/pages/Demo/index.ts
+++ b/src/client/pages/Demo/index.ts
@@ -1,13 +1,15 @@
import { useDemo, useLiveDemo, useParams } from 'dumi';
-import { ComponentType, createElement, useEffect, type FC } from 'react';
+import { createElement, useEffect, type ComponentType } from 'react';
import { useRenderer } from '../../theme-api/useRenderer';
import './index.less';
-const DemoRenderPage: FC = () => {
- const { id } = useParams();
- const demo = useDemo(id!);
+const DemoRenderPage = () => {
+ const params = useParams();
+ const id = params.id!;
- const canvasRef = useRenderer(demo!);
+ const demo = useDemo(id)!;
+
+ const canvasRef = useRenderer(Object.assign(demo, { id }));
const { component, renderOpts } = demo || {};
@@ -15,7 +17,7 @@ const DemoRenderPage: FC = () => {
node: liveDemoNode,
setSource,
error: liveDemoError,
- } = useLiveDemo(id!);
+ } = useLiveDemo(id);
const finalNode =
liveDemoNode ||
diff --git a/src/client/theme-api/__tests__/sandbox.spec.ts b/src/client/theme-api/__tests__/sandbox.spec.ts
new file mode 100644
index 0000000000..618a5568fd
--- /dev/null
+++ b/src/client/theme-api/__tests__/sandbox.spec.ts
@@ -0,0 +1,43 @@
+// @vitest-environment happy-dom
+import * as lexer from 'es-module-lexer';
+import { rewriteExports } from '../sandbox';
+
+async function getModule(iife: string) {
+ const s = iife.indexOf('blob:');
+ const e = iife.indexOf("');", s);
+ const blob = iife.substring(s, e);
+ return await (await fetch(blob)).text();
+}
+
+// happy-dom/jsdom cannot test Sandbox because neither of them implements the sandbox and srcdoc attributes of iframe.
+// Currently only sandbox is being implemented: https://github.com/capricorn86/happy-dom/pull/1375
+// After the srcdoc attribute is implemented, complete tests will be added
+
+describe('esm sandbox', () => {
+ describe('rewriteExports', () => {
+ beforeAll(async () => {
+ await lexer.init;
+ });
+ it('should assign the local name to the constant', async () => {
+ const iife = rewriteExports('m1', 'export default 1;', 'sandbox');
+ expect(await getModule(iife)).toMatchInlineSnapshot(`
+ "const m1 = 1;
+ window.___modules___['m1'] = { 'default': m1 };
+ parent.postMessage({ type: 'sandbox.esm.done' }, '*');"
+ `);
+ });
+
+ it('should remove the local name in the export statement', async () => {
+ const iife = rewriteExports(
+ 'm2',
+ 'export { xxx as default };',
+ 'sandbox',
+ );
+ expect(await getModule(iife)).toMatchInlineSnapshot(`
+ "export { };
+ window.___modules___['m2'] = { 'default': xxx };
+ parent.postMessage({ type: 'sandbox.esm.done' }, '*');"
+ `);
+ });
+ });
+});
diff --git a/src/client/theme-api/sandbox.ts b/src/client/theme-api/sandbox.ts
new file mode 100644
index 0000000000..ad89f7ff27
--- /dev/null
+++ b/src/client/theme-api/sandbox.ts
@@ -0,0 +1,353 @@
+import * as lexer from 'es-module-lexer';
+import hashId from 'hash-sum';
+
+const srcdoc = `
+
+
+
+
+
+
+
+
+
+`;
+interface ImportMap {
+ imports?: Record;
+ scopes?: Record;
+}
+
+export interface ExtendedImportMap extends ImportMap {
+ builtins?: Record;
+}
+
+const MODULE_STORE = '___modules___';
+
+type CodeSandboxWindow = Window & {
+ ___modules___: Record;
+};
+
+function createBlob(source: string) {
+ return URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
+}
+
+const DEFAULT_RESTRICTIONS = [
+ 'allow-forms',
+ 'allow-modals',
+ 'allow-pointer-lock',
+ 'allow-popups',
+ 'allow-same-origin',
+ 'allow-scripts',
+ 'allow-top-navigation-by-user-activation',
+];
+
+export function rewriteExports(
+ moduleId: string,
+ esm: string,
+ eventPrefix: string,
+) {
+ const [, exports] = lexer.parse(esm);
+ for (const e of exports) {
+ if (e.n === 'default') {
+ let rewriteEsm = '';
+ let defaultSymbol = '';
+ // export default or export { s as default }
+ if (e.ln) {
+ rewriteEsm = esm.substring(0, e.ls) + esm.substring(e.e);
+ defaultSymbol = e.ln;
+ } else {
+ const exportStatementStart = esm.lastIndexOf('export', e.s);
+ if (exportStatementStart >= 0) {
+ rewriteEsm =
+ esm.substring(0, exportStatementStart) +
+ `const ${moduleId} = ` +
+ esm.substring(e.e);
+ defaultSymbol = moduleId;
+ }
+ }
+ if (rewriteEsm && defaultSymbol) {
+ const url = createBlob(
+ rewriteEsm +
+ `\nwindow.${MODULE_STORE}['${moduleId}'] = { 'default': ${defaultSymbol} };` +
+ `\nparent.postMessage({ type: '${eventPrefix}.esm.done' }, '*');`,
+ );
+ return `(async function() {
+ try {
+ await import('${url}');
+ } catch(e) {
+ parent.postMessage({ type: '${eventPrefix}.esm.error', error: e });
+ }
+ })()`;
+ }
+ }
+ }
+ return esm;
+}
+
+class IFrameContainer {
+ private iframe!: HTMLIFrameElement;
+ private iframeWindow!: CodeSandboxWindow;
+ constructor(
+ private restrictions: string[],
+ private moduleCache: Record,
+ private eventPrefix: string,
+ ) {}
+ async create(srcdoc: string) {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement('iframe');
+ iframe.sandbox.add(...this.restrictions);
+ const iframeLoaded = () => {
+ this.iframeWindow = iframe.contentWindow as CodeSandboxWindow;
+ this.iframeWindow[MODULE_STORE] = this.moduleCache;
+ resolve(this);
+ };
+ iframe.addEventListener(
+ 'load',
+ () => {
+ if (iframe.contentWindow) {
+ iframeLoaded();
+ } else {
+ reject('missing contentWindow');
+ }
+ },
+ false,
+ );
+
+ iframe.addEventListener(
+ 'error',
+ (e) => {
+ reject(e.error);
+ },
+ false,
+ );
+
+ iframe.style.width = '0';
+ iframe.style.height = '0';
+ iframe.style.border = 'none';
+ iframe.style.visibility = 'hidden';
+ iframe.srcdoc = srcdoc;
+ (document.body ?? document.head).appendChild(iframe);
+ this.iframe = iframe;
+ if (iframe.contentWindow) {
+ (iframe.contentWindow as any)[MODULE_STORE] = this.moduleCache;
+ }
+ });
+ }
+
+ exec(moduleId: string, esm: string) {
+ return new Promise((execResolve, execReject) => {
+ const handleMessage = (e: MessageEvent) => {
+ const { data } = e;
+ if (data.type?.startsWith(`${this.eventPrefix}.esm.done`)) {
+ window.removeEventListener('message', handleMessage);
+ execResolve(this.moduleCache[moduleId]);
+ } else if (data.type?.startsWith(`${this.eventPrefix}.esm.error`)) {
+ execReject(data.error);
+ }
+ };
+ window.addEventListener('message', handleMessage, false);
+ this.iframeWindow.postMessage({ esm }, '*');
+ });
+ }
+
+ destroy() {
+ this.moduleCache = {};
+ this.iframe.remove();
+ }
+}
+
+export interface SandboxParams {
+ importMap?: ImportMap;
+ restrictions?: string[];
+ eventPrefix?: string;
+}
+
+/**
+ * A sandbox for executing ES modules
+ *
+ * Supports importmap, and can even inject module instances into the sandbox
+ * (requires "allow-same-origin" permission)
+ *
+ * @example
+ * ```ts
+ * const sandbox = await Sandbox.create({
+ * importMap: {
+ * builtins: { 'vue': Module },
+ * imports: {
+ * "lodash": "https://esm.sh/lodash-es",
+ * },
+ * scopes: {},
+ * }
+ * });
+ * await sandbox.exec('import vue from "vue";\nimport lodash from "lodash"');
+ * await sandbox.updateImportMap({});
+ * ```
+ */
+export class Sandbox {
+ private codeSandbox!: Promise;
+ private srcdoc: string = srcdoc;
+
+ private moduleCache: Record = {};
+ private restrictions: string[] = DEFAULT_RESTRICTIONS;
+ private eventPrefix: string = 'sandbox';
+
+ static create(params?: SandboxParams) {
+ return new Sandbox().init(params || {});
+ }
+
+ private contentLoadedListener?: () => void;
+
+ private async init({ importMap, restrictions, eventPrefix }: SandboxParams) {
+ if (importMap) {
+ this.updateSrcDoc(importMap);
+ }
+ if (restrictions) {
+ this.restrictions = restrictions;
+ }
+ if (eventPrefix) {
+ this.eventPrefix = eventPrefix;
+ }
+ this.codeSandbox = (async () => {
+ await lexer.init;
+ return new Promise((resolve, reject) => {
+ const initCodeSandbox = () => {
+ const container = new IFrameContainer(
+ this.restrictions,
+ this.moduleCache,
+ this.eventPrefix,
+ );
+ container.create(this.srcdoc).then(resolve).catch(reject);
+ };
+ if (document.readyState === 'loading') {
+ this.contentLoadedListener = initCodeSandbox;
+ document.addEventListener(
+ 'DOMContentLoaded',
+ this.contentLoadedListener,
+ false,
+ );
+ } else {
+ initCodeSandbox();
+ }
+ });
+ })();
+
+ await this.codeSandbox;
+ return this;
+ }
+
+ async destory() {
+ if (this.contentLoadedListener) {
+ document.removeEventListener(
+ 'DOMContentLoaded',
+ this.contentLoadedListener,
+ false,
+ );
+ }
+ return this.codeSandbox.then((codeSandbox) => {
+ return codeSandbox.destroy();
+ });
+ }
+
+ private updateSrcDoc({ imports, builtins, scopes }: ExtendedImportMap) {
+ const realImportMap: ImportMap = {};
+ if (imports) {
+ realImportMap.imports = {
+ ...imports,
+ };
+ }
+ if (scopes) {
+ realImportMap.scopes = {
+ ...scopes,
+ };
+ }
+ if (builtins) {
+ realImportMap.imports = Object.entries(builtins).reduce(
+ (acc, [identifier, module]) => {
+ let source =
+ `const modules = window.${MODULE_STORE} || {};` +
+ `\nconst m = modules['${identifier}'] || {}`;
+ let hasDefault = false;
+ Object.keys(module).forEach((member) => {
+ if (member === 'default') {
+ hasDefault = true;
+ }
+ source +=
+ member === 'default'
+ ? `\nexport default m['${member}']`
+ : `\nexport const ${member} = m['${member}'];`;
+ });
+ if (!hasDefault) {
+ source += `\nexport default m;`;
+ }
+ const blob = createBlob(source);
+ acc[identifier] = blob;
+ this.moduleCache[identifier] = module;
+ return acc;
+ },
+ realImportMap.imports ?? {},
+ );
+ }
+
+ const replacements: Record = {
+ importmap: JSON.stringify(realImportMap),
+ eventPrefix: this.eventPrefix,
+ };
+ this.srcdoc = srcdoc.replace(
+ /\{(\w+)\}/gi,
+ (_, key) => replacements[key] || '',
+ );
+ }
+
+ async updateImportMap(importMap: ExtendedImportMap) {
+ this.codeSandbox = this.codeSandbox
+ .then((codeSandbox) => {
+ codeSandbox.destroy();
+ this.updateSrcDoc(importMap);
+ })
+ .then(() => {
+ const container = new IFrameContainer(
+ this.restrictions,
+ this.moduleCache,
+ this.eventPrefix,
+ );
+ return container.create(this.srcdoc);
+ });
+ await this.codeSandbox;
+ }
+
+ async exec(esm: string) {
+ const moduleId = `module_${hashId(esm)}`;
+ const result = this.moduleCache[moduleId];
+ if (result) return result;
+ const codeSandbox = await this.codeSandbox;
+ return codeSandbox.exec(
+ moduleId,
+ rewriteExports(moduleId, esm, this.eventPrefix),
+ );
+ }
+}
diff --git a/src/client/theme-api/types.ts b/src/client/theme-api/types.ts
index f637f7165d..58f2471520 100644
--- a/src/client/theme-api/types.ts
+++ b/src/client/theme-api/types.ts
@@ -247,9 +247,11 @@ export type AgnosticComponentType =
| Promise
| AgnosticComponentModule;
+export type ModuleType = 'esm' | 'cjs';
+
export type IDemoCompileFn = (
code: string,
- opts: { filename: string },
+ opts: { filename: string; modules?: ModuleType },
) => Promise;
export type IDemoCancelableFn = (
diff --git a/src/client/theme-api/useImportMap.ts b/src/client/theme-api/useImportMap.ts
new file mode 100644
index 0000000000..8ac473a6a7
--- /dev/null
+++ b/src/client/theme-api/useImportMap.ts
@@ -0,0 +1,57 @@
+import { useMemo, useState } from 'react';
+import { ExtendedImportMap } from './sandbox';
+import { IDemoData } from './types';
+import { SUPPORTED_MODULE } from './utils';
+
+export const useImportMap = (demo: IDemoData) => {
+ const { context, asset } = demo;
+
+ const [internalImportMap, setImportMap] = useState({
+ builtins: context || {},
+ });
+
+ const importMap = useMemo(() => {
+ if (SUPPORTED_MODULE === 'cjs') return null;
+ return Object.assign(
+ {
+ imports: {},
+ scopes: {},
+ },
+ internalImportMap,
+ {
+ builtins: Object.keys(internalImportMap.builtins).reduce(
+ (builtins, dep) => {
+ builtins[dep] = asset.dependencies[dep]?.value;
+ return builtins;
+ },
+ {} as NonNullable,
+ ),
+ },
+ ) as ExtendedImportMap;
+ }, [internalImportMap]);
+
+ async function updateImportMap(modifiedImportMap: ExtendedImportMap) {
+ const map: ExtendedImportMap = {};
+ if (modifiedImportMap.imports) {
+ map.imports = modifiedImportMap.imports;
+ }
+ if (modifiedImportMap.scopes) {
+ map.scopes = modifiedImportMap.scopes;
+ }
+ setImportMap(() => ({
+ ...internalImportMap,
+ ...map,
+ }));
+ }
+ return {
+ /**
+ * Provided to sandbox
+ */
+ internalImportMap,
+ /**
+ * provided to editor
+ */
+ importMap,
+ updateImportMap,
+ };
+};
diff --git a/src/client/theme-api/useLiveDemo.ts b/src/client/theme-api/useLiveDemo.ts
index b3f6e1b38c..cdd95a4fbc 100644
--- a/src/client/theme-api/useLiveDemo.ts
+++ b/src/client/theme-api/useLiveDemo.ts
@@ -11,25 +11,13 @@ import {
} from 'react';
import DemoErrorBoundary from './DumiDemo/DemoErrorBoundary';
import type { AgnosticComponentType } from './types';
+import { useImportMap } from './useImportMap';
import { useRenderer } from './useRenderer';
+import { useSandbox } from './useSandbox';
+import { SUPPORTED_MODULE } from './utils';
const THROTTLE_WAIT = 500;
-type CommonJSContext = {
- module: any;
- exports: {
- default?: any;
- };
- require: any;
-};
-
-function evalCommonJS(
- js: string,
- { module, exports, require }: CommonJSContext,
-) {
- new Function('module', 'exports', 'require', js)(module, exports, require);
-}
-
export const useLiveDemo = (
id: string,
opts?: { containerRef?: RefObject; iframe?: boolean },
@@ -54,6 +42,11 @@ export const useLiveDemo = (
const [demoNode, setDemoNode] = useState();
const [error, setError] = useState(null);
+
+ const { importMap, internalImportMap, updateImportMap } = useImportMap(demo);
+
+ const sandbox = useSandbox(internalImportMap, SUPPORTED_MODULE);
+
const setSource = useCallback(
throttle(
async (source: Record) => {
@@ -82,7 +75,7 @@ export const useLiveDemo = (
value: { err: null | Error };
}>,
) => {
- if (ev.data.type.startsWith('dumi.liveDemo.compileDone')) {
+ if (ev.data.type?.startsWith('dumi.liveDemo.compileDone')) {
iframeWindow.removeEventListener('message', handler);
setError(ev.data.value.err);
resolve();
@@ -99,18 +92,17 @@ export const useLiveDemo = (
const entryFileName = Object.keys(asset.dependencies).find(
(k) => asset.dependencies[k].type === 'FILE',
)!;
- const require = (v: string) => {
- if (v in context!) return context![v];
- throw new Error(`Cannot find module: ${v}`);
- };
const token = (taskToken.current = Math.random());
let entryFileCode = source[entryFileName];
+ sandbox.init();
+
if (renderOpts?.compile) {
try {
entryFileCode = await renderOpts.compile(entryFileCode, {
filename: entryFileName,
+ modules: SUPPORTED_MODULE,
});
} catch (error: any) {
setError(error);
@@ -121,13 +113,9 @@ export const useLiveDemo = (
if (renderOpts?.renderer && renderOpts?.compile) {
try {
- const exports: AgnosticComponentType = {};
- const module = { exports };
- evalCommonJS(entryFileCode, {
- exports,
- module,
- require,
- });
+ const exports: AgnosticComponentType = await sandbox.exec(
+ entryFileCode,
+ );
setComponent(exports);
setDemoNode(createElement('div', { ref }));
setError(null);
@@ -146,17 +134,9 @@ export const useLiveDemo = (
// skip current task if another task is running
if (token !== taskToken.current) return;
-
- const exports: { default?: ComponentType } = {};
- const module = { exports };
-
- // initial component with fake runtime
- evalCommonJS(entryFileCode, {
- exports,
- module,
- require,
- });
-
+ const exports: { default?: ComponentType } = await sandbox.exec(
+ entryFileCode,
+ );
const newDemoNode = createElement(
DemoErrorBoundary,
null,
@@ -188,5 +168,12 @@ export const useLiveDemo = (
[context, asset, renderOpts],
);
- return { node: demoNode, loading, error, setSource };
+ return {
+ node: demoNode,
+ loading,
+ error,
+ setSource,
+ updateImportMap,
+ importMap,
+ };
};
diff --git a/src/client/theme-api/useSandbox.ts b/src/client/theme-api/useSandbox.ts
new file mode 100644
index 0000000000..2d24ad6152
--- /dev/null
+++ b/src/client/theme-api/useSandbox.ts
@@ -0,0 +1,80 @@
+import { useEffect, useRef } from 'react';
+import type { ExtendedImportMap, Sandbox } from './sandbox';
+import type { ModuleType } from './types';
+
+type CommonJSContext = {
+ module: any;
+ exports: {
+ default?: any;
+ };
+ require: any;
+};
+
+function evalCommonJS(
+ cjs: string,
+ { module, exports, require }: CommonJSContext,
+) {
+ new Function('module', 'exports', 'require', cjs)(module, exports, require);
+}
+
+async function createSandbox(
+ importMap: ExtendedImportMap,
+ modules: ModuleType,
+) {
+ if (modules === 'esm') {
+ return import('./sandbox').then(({ Sandbox }) =>
+ Sandbox.create({ importMap }),
+ );
+ }
+ const require = (v: string) => {
+ if (v in importMap.builtins!) return importMap.builtins![v];
+ throw new Error(`Cannot find module: ${v}`);
+ };
+ return Promise.resolve({
+ destory() {},
+ exec(cjs: string) {
+ const exports = {};
+ evalCommonJS(cjs, {
+ exports,
+ module: { exports },
+ require,
+ });
+ return exports;
+ },
+ } as Sandbox);
+}
+
+export function useSandbox(
+ importMap: ExtendedImportMap,
+ modules: ModuleType = 'esm',
+) {
+ const sandbox = useRef>();
+ const lastImportMap = useRef(importMap);
+ function destorySandbox() {
+ sandbox.current?.then((s) => s.destory());
+ }
+ if (lastImportMap.current !== importMap) {
+ lastImportMap.current = importMap;
+ sandbox.current = sandbox.current
+ ? sandbox.current
+ .then((s) => s.destory())
+ .then(() => createSandbox(importMap, modules))
+ : createSandbox(importMap, modules);
+ }
+
+ useEffect(() => () => destorySandbox(), []);
+
+ return {
+ init() {
+ if (sandbox.current) return;
+ sandbox.current = createSandbox(importMap, modules);
+ },
+ async exec(esm: string) {
+ if (!sandbox.current) {
+ throw new Error('Please execute init first');
+ }
+ const box = await sandbox.current;
+ return box.exec(esm);
+ },
+ };
+}
diff --git a/src/client/theme-api/utils.ts b/src/client/theme-api/utils.ts
index 76dec4dab9..2d6782ad84 100644
--- a/src/client/theme-api/utils.ts
+++ b/src/client/theme-api/utils.ts
@@ -7,6 +7,7 @@ import type {
IRouteMeta,
IRoutesById,
IUserNavValue,
+ ModuleType,
} from './types';
import { useLocale } from './useLocale';
@@ -146,3 +147,15 @@ export const pickRouteSortMeta = (
export function getLocaleNav(nav: IUserNavValue | INav, locale: ILocale) {
return Array.isArray(nav) ? nav : nav[locale.id];
}
+
+export const supports =
+ HTMLScriptElement.supports ||
+ function (type: string) {
+ if (type === 'module') {
+ return 'noModule' in HTMLScriptElement.prototype;
+ }
+ return false;
+ };
+
+export const SUPPORTED_MODULE: ModuleType =
+ supports('importmap') && supports('module') ? 'esm' : 'cjs';
diff --git a/src/client/theme-default/builtins/Previewer/index.tsx b/src/client/theme-default/builtins/Previewer/index.tsx
index 6ce407a40b..516f80e2b1 100644
--- a/src/client/theme-default/builtins/Previewer/index.tsx
+++ b/src/client/theme-default/builtins/Previewer/index.tsx
@@ -15,6 +15,8 @@ const Previewer: FC = (props) => {
error: liveDemoError,
loading: liveDemoLoading,
setSource: setLiveDemoSource,
+ importMap,
+ updateImportMap,
} = useLiveDemo(props.asset.id, {
iframe: Boolean(props.iframe || props._live_in_iframe),
containerRef: demoContainer,
@@ -76,6 +78,8 @@ const Previewer: FC = (props) => {
)}
svg {
width: 16px;
@@ -91,6 +93,18 @@
fill: lighten(@c-border-dark, 20%);
}
}
+ .@{prefix}-tabs-tabpane:not(:hover) & {
+ opacity: 0;
+ visibility: hidden;
+ }
+ }
+
+ &-editor-tip-btn {
+ position: absolute;
+ top: 9px;
+ right: 42px;
+ padding: 8px 12px;
+ cursor: help;
&[data-readonly] {
> span {
@@ -104,15 +118,50 @@
transform: rotate(45deg) translate(-50%, 120%);
}
}
+ }
- .@{prefix}-tabs-tabpane:not(:hover) & {
- opacity: 0;
- visibility: hidden;
+ &-importmap-btn {
+ position: absolute;
+ top: 9px;
+ right: 72px;
+ padding: 8px 12px;
+ line-height: 16px;
+ cursor: pointer;
+ }
+
+ &-json-ops {
+ position: absolute;
+ top: 9px;
+ right: 46px;
+ z-index: 3;
+
+ > button {
+ padding: 8px;
+ cursor: pointer;
+ }
+ }
+
+ &-json-editor {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 3;
+ width: 100%;
+ height: 100%;
+
+ > div {
+ height: 100%;
+
+ > div {
+ height: 100%;
+ }
}
}
}
.@{prefix}-tabs {
+ position: relative;
+
&-top {
flex-direction: column;
@@ -274,6 +323,7 @@
min-width: 30px;
margin-bottom: 8px;
box-sizing: border-box;
+ z-index: 5;
&-hidden {
display: none;
diff --git a/src/client/theme-default/slots/PreviewerActions/index.tsx b/src/client/theme-default/slots/PreviewerActions/index.tsx
index d6c32cd867..374ce80817 100644
--- a/src/client/theme-default/slots/PreviewerActions/index.tsx
+++ b/src/client/theme-default/slots/PreviewerActions/index.tsx
@@ -1,6 +1,9 @@
+import type { ExtendedImportMap } from '@/client/theme-api/sandbox';
import { ReactComponent as IconCheck } from '@ant-design/icons-svg/inline-svg/outlined/check.svg';
+import { ReactComponent as IconClose } from '@ant-design/icons-svg/inline-svg/outlined/close-circle.svg';
import { ReactComponent as IconCodeSandbox } from '@ant-design/icons-svg/inline-svg/outlined/code-sandbox.svg';
import { ReactComponent as IconEdit } from '@ant-design/icons-svg/inline-svg/outlined/edit.svg';
+import { ReactComponent as IconSave } from '@ant-design/icons-svg/inline-svg/outlined/save.svg';
import { ReactComponent as IconSketch } from '@ant-design/icons-svg/inline-svg/outlined/sketch.svg';
import { ReactComponent as IconStackBlitz } from '@ant-design/icons-svg/inline-svg/outlined/thunderbolt.svg';
import classNames from 'classnames';
@@ -15,7 +18,9 @@ import {
} from 'dumi';
import SourceCode from 'dumi/theme/builtins/SourceCode';
import PreviewerActionsExtra from 'dumi/theme/slots/PreviewerActionsExtra';
-import SourceCodeEditor from 'dumi/theme/slots/SourceCodeEditor';
+import SourceCodeEditor, {
+ type SourceCodeEditorMethods,
+} from 'dumi/theme/slots/SourceCodeEditor';
import Tabs from 'rc-tabs';
import RcTooltip from 'rc-tooltip';
import type { TooltipProps as RcTooltipProps } from 'rc-tooltip/lib/Tooltip';
@@ -39,22 +44,134 @@ const Tooltip: FC = (props) => {
);
};
-export interface IPreviewerActionsProps extends IPreviewerProps {
- /**
- * disabled actions
- */
- disabledActions?: ('CSB' | 'STACKBLITZ' | 'EXTERNAL' | 'HTML2SKETCH')[];
- extra?: ReactNode;
- forceShowCode?: boolean;
- demoContainer: HTMLDivElement | HTMLIFrameElement;
- onSourceTranspile?: (
- args:
- | { err: Error; source?: null }
- | { err?: null; source: Record },
- ) => void;
- onSourceChange?: (source: Record) => void;
+function checkImportMap(
+ importmap: ExtendedImportMap,
+ originImportMap: ExtendedImportMap,
+) {
+ if (
+ JSON.stringify(originImportMap?.builtins) !==
+ JSON.stringify(importmap.builtins)
+ ) {
+ throw new Error('Builtin dependencies cannot be changed online!');
+ }
+ if (importmap.imports && importmap.builtins) {
+ for (const i of Object.keys(importmap.imports)) {
+ if (importmap.builtins[i]) {
+ throw new Error(
+ 'Dependencies already in `builtins` cannot be introduced in `imports`',
+ );
+ }
+ }
+ }
}
+export interface UseImportMapEditorProps {
+ importMap?: ExtendedImportMap | null;
+ onImportMapChange?: (importMap: ExtendedImportMap) => void;
+}
+
+function useImportMapEditor(props: UseImportMapEditorProps) {
+ const [visible, setVisible] = useState(false);
+ const originImportMap = useRef(props.importMap);
+ const [source, setSource] = useState(
+ props.importMap && JSON.stringify(props.importMap, null, 2),
+ );
+
+ function close() {
+ setSource(JSON.stringify(originImportMap.current, null, 2));
+ setVisible(false);
+ }
+
+ function toggle() {
+ setVisible(!visible);
+ }
+
+ function saveImportMap() {
+ if (!source) return;
+ try {
+ const result = JSON.parse(source) as ExtendedImportMap;
+ checkImportMap(result, originImportMap.current!);
+ props.onImportMapChange?.(result);
+ originImportMap.current = result;
+ close();
+ } catch (error: any) {
+ alert(error.toString());
+ }
+ }
+
+ return {
+ visible,
+ toggle,
+ close,
+ saveImportMap,
+ source,
+ setSource,
+ };
+}
+
+const ImportMapEditor = (props: ReturnType) => {
+ const intl = useIntl();
+ if (!props.source) return null;
+ return (
+
+
{
+ props.setSource(code);
+ }}
+ lang="json"
+ extra={
+
+
+
+
+
+
+
+
+ }
+ />
+
+ );
+};
+
+export type IPreviewerActionsProps = IPreviewerProps &
+ UseImportMapEditorProps & {
+ /**
+ * disabled actions
+ */
+ disabledActions?: ('CSB' | 'STACKBLITZ' | 'EXTERNAL' | 'HTML2SKETCH')[];
+ extra?: ReactNode;
+ forceShowCode?: boolean;
+ demoContainer: HTMLDivElement | HTMLIFrameElement;
+ onSourceTranspile?: (
+ args:
+ | { err: Error; source?: null }
+ | { err?: null; source: Record },
+ ) => void;
+ onSourceChange?: (source: Record) => void;
+ };
+
const IconCode: FC = () => (