|
|
|
|
|
/*
|
|
|
|
|
|
* proxy.js
|
|
|
|
|
|
* Copyright (C) 2024 veypi <i@veypi.com>
|
|
|
|
|
|
*
|
|
|
|
|
|
* Distributed under terms of the MIT license.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {([boolean, ()=>void])[]} */
|
|
|
|
|
|
const callbackList = []
|
|
|
|
|
|
/** @type {number[]} */
|
|
|
|
|
|
const cacheUpdateList = []
|
|
|
|
|
|
// 界面更新响应频率40hz
|
|
|
|
|
|
const sync = () => {
|
|
|
|
|
|
let list = new Set(cacheUpdateList.splice(0))
|
|
|
|
|
|
let c = 0
|
|
|
|
|
|
for (let l of list) {
|
|
|
|
|
|
if (callbackList[l]) {
|
|
|
|
|
|
callbackList[l]()
|
|
|
|
|
|
c++
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c > 0) {
|
|
|
|
|
|
console.log(`update ${c}`)
|
|
|
|
|
|
// sync()
|
|
|
|
|
|
}
|
|
|
|
|
|
return c
|
|
|
|
|
|
}
|
|
|
|
|
|
setInterval(sync, 25)
|
|
|
|
|
|
|
|
|
|
|
|
function GenUniqueID() {
|
|
|
|
|
|
const timestamp = performance.now().toString(36);
|
|
|
|
|
|
const random = Math.random().toString(36).substring(2, 5);
|
|
|
|
|
|
return `${timestamp}-${random}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ForceUpdate() {
|
|
|
|
|
|
for (let c of callbackList) {
|
|
|
|
|
|
if (c) {
|
|
|
|
|
|
c()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.$vupdate = (id) => {
|
|
|
|
|
|
console.log('update', id)
|
|
|
|
|
|
callbackList[id]()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function deepAccess(obj, seen = new Set()) {
|
|
|
|
|
|
if (obj && typeof obj === 'object' && !seen.has(obj)) {
|
|
|
|
|
|
seen.add(obj)
|
|
|
|
|
|
for (let key in obj) {
|
|
|
|
|
|
deepAccess(obj[key], seen)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return obj
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {number[]} */
|
|
|
|
|
|
var listen_tags = []
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param {()=>void} callback
|
|
|
|
|
|
* @returns number
|
|
|
|
|
|
*/
|
|
|
|
|
|
function Watch(target, callback, options) {
|
|
|
|
|
|
let idx = callbackList.length
|
|
|
|
|
|
listen_tags.push(idx)
|
|
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
|
|
callbackList.push(() => {
|
|
|
|
|
|
callback(target())
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
callbackList.push(target)
|
|
|
|
|
|
}
|
|
|
|
|
|
let res
|
|
|
|
|
|
try {
|
|
|
|
|
|
res = target()
|
|
|
|
|
|
if (options && options.deep) {
|
|
|
|
|
|
deepAccess(res)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('running \n%s\n failed:', target, e)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
listen_tags.pop()
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
|
|
callback(res)
|
|
|
|
|
|
}
|
|
|
|
|
|
return idx
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Cancel(idx) {
|
|
|
|
|
|
if (idx < 0 || idx >= callbackList.length) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
callbackList[idx] = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isProxy = Symbol("isProxy")
|
|
|
|
|
|
const DataID = Symbol("DataID")
|
|
|
|
|
|
const DataBind = Symbol("bind")
|
|
|
|
|
|
const rootObj = Symbol("root")
|
|
|
|
|
|
const rootArg = Symbol("root arg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function SetDataRoot(data, root) {
|
|
|
|
|
|
data[rootObj] = root
|
|
|
|
|
|
Object.keys(root).forEach(k => {
|
|
|
|
|
|
if (k in data) {
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data[k] = rootArg
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isProxyType(v) {
|
|
|
|
|
|
if (!v || typeof v !== 'object') {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (v instanceof Node || v instanceof Date || v instanceof RegExp || v instanceof Event) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (v.__noproxy) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (v.constructor !== Object && v.constructor !== Array) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// oldValue只集成值, 不继承事件
|
|
|
|
|
|
function copyBind(oldValue, newValue) {
|
|
|
|
|
|
if (!oldValue || !oldValue[isProxy] || !isProxyType(newValue)) {
|
|
|
|
|
|
return newValue
|
|
|
|
|
|
}
|
|
|
|
|
|
let binds = oldValue[DataBind]
|
|
|
|
|
|
if (newValue[isProxy]) {
|
|
|
|
|
|
// 新值也是代理对象,继承旧值的事件绑定, 使用新的代理对象
|
|
|
|
|
|
if (newValue[DataID] === oldValue[DataID]) {
|
|
|
|
|
|
return newValue
|
|
|
|
|
|
}
|
|
|
|
|
|
for (let k in binds) {
|
|
|
|
|
|
if (newValue[DataBind][k]?.indexOf) {
|
|
|
|
|
|
const currentBinds = newValue[DataBind][k]
|
|
|
|
|
|
const bindSet = new Set(currentBinds)
|
|
|
|
|
|
for (let i of binds[k]) {
|
|
|
|
|
|
if (!bindSet.has(i)) {
|
|
|
|
|
|
currentBinds.push(i)
|
|
|
|
|
|
bindSet.add(i)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newValue[DataBind][k] = binds[k]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 新值不是代理对象,继承值,使用旧的代理对象
|
|
|
|
|
|
if (Array.isArray(newValue) && Array.isArray(oldValue)) {
|
|
|
|
|
|
oldValue.length = 0
|
|
|
|
|
|
for (let i = 0; i < newValue.length; i++) {
|
|
|
|
|
|
oldValue.push(newValue[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
return oldValue
|
|
|
|
|
|
}
|
|
|
|
|
|
Object.keys(oldValue).forEach(k => {
|
|
|
|
|
|
if (!newValue.hasOwnProperty(k)) {
|
|
|
|
|
|
delete oldValue[k]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
Object.keys(newValue).forEach(k => {
|
|
|
|
|
|
if (oldValue[k]?.[isProxy]) {
|
|
|
|
|
|
oldValue[k] = copyBind(oldValue[k], newValue[k])
|
|
|
|
|
|
} else {
|
|
|
|
|
|
oldValue[k] = newValue[k]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return oldValue
|
|
|
|
|
|
}
|
|
|
|
|
|
for (let k in newValue) {
|
|
|
|
|
|
if (k in oldValue && oldValue[k]?.[isProxy]) {
|
|
|
|
|
|
newValue[k] = copyBind(oldValue[k], newValue[k])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return newValue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let stopChecking = false
|
|
|
|
|
|
function Wrap(data, root = undefined) {
|
|
|
|
|
|
const did = GenUniqueID()
|
|
|
|
|
|
let isArray = false
|
|
|
|
|
|
if (Object.prototype.toString.call(data) === '[object Array]') {
|
|
|
|
|
|
isArray = true
|
|
|
|
|
|
}
|
|
|
|
|
|
if (root) {
|
|
|
|
|
|
SetDataRoot(data, root)
|
|
|
|
|
|
}
|
|
|
|
|
|
// data[DataID] = did
|
|
|
|
|
|
const listeners = {}
|
|
|
|
|
|
const handler = {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param {Object} target
|
|
|
|
|
|
* @param {string|symbol} key
|
|
|
|
|
|
*
|
|
|
|
|
|
* */
|
|
|
|
|
|
get(target, key, receiver) {
|
|
|
|
|
|
if (key === DataID) {
|
|
|
|
|
|
return did
|
|
|
|
|
|
} else if (key === isProxy) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
} else if (key === DataBind) {
|
|
|
|
|
|
return listeners
|
|
|
|
|
|
}
|
|
|
|
|
|
const value = Reflect.get(target, key, receiver)
|
|
|
|
|
|
if (value === rootArg) {
|
|
|
|
|
|
return target[rootObj][key]
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof key === 'symbol' && stopChecking) {
|
|
|
|
|
|
return value
|
|
|
|
|
|
} else if (typeof value === 'function') {
|
|
|
|
|
|
return value
|
|
|
|
|
|
}
|
|
|
|
|
|
let idx = -1
|
|
|
|
|
|
if (listen_tags.length > 0) {
|
|
|
|
|
|
let lkey = key
|
|
|
|
|
|
idx = listen_tags[listen_tags.length - 1]
|
|
|
|
|
|
if (isArray) {
|
|
|
|
|
|
lkey = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!listeners.hasOwnProperty(lkey)) {
|
|
|
|
|
|
listeners[lkey] = [idx]
|
|
|
|
|
|
} else if (listeners[lkey].indexOf(idx) == -1) {
|
|
|
|
|
|
listeners[lkey].push(idx)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (window.vdev) {
|
|
|
|
|
|
console.log(`${did} get ${key.toString()}:|${value}| `)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isProxyType(value) && !value[isProxy]) {
|
|
|
|
|
|
let newValue = Wrap(value, undefined)
|
|
|
|
|
|
Reflect.set(target, key, newValue, receiver)
|
|
|
|
|
|
return newValue
|
|
|
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
|
|
|
},
|
|
|
|
|
|
set(target, key, newValue, receiver) {
|
|
|
|
|
|
const oldValue = Reflect.get(target, key, receiver)
|
|
|
|
|
|
if (oldValue === rootArg) {
|
|
|
|
|
|
target[rootObj][key] = newValue
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
if (oldValue === newValue) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
} else if (stopChecking) {
|
|
|
|
|
|
return Reflect.set(target, key, newValue, receiver)
|
|
|
|
|
|
}
|
|
|
|
|
|
let result = true
|
|
|
|
|
|
if (Array.isArray(newValue) && Array.isArray(oldValue)) {
|
|
|
|
|
|
stopChecking = true
|
|
|
|
|
|
oldValue.length = 0
|
|
|
|
|
|
for (let i = 0; i < newValue.length; i++) {
|
|
|
|
|
|
oldValue.push(newValue[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
stopChecking = false
|
|
|
|
|
|
} else if (oldValue && oldValue[isProxy] && isProxyType(newValue)) {
|
|
|
|
|
|
// 监听对象只赋值可迭代属性
|
|
|
|
|
|
newValue = copyBind(oldValue, newValue)
|
|
|
|
|
|
result = Reflect.set(target, key, newValue, receiver);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result = Reflect.set(target, key, newValue, receiver);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (result && listen_tags.length === 0) {
|
|
|
|
|
|
let lkey = key
|
|
|
|
|
|
if (isArray) {
|
|
|
|
|
|
lkey = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
if (listeners[lkey]) {
|
|
|
|
|
|
let i = 0
|
|
|
|
|
|
if (window.vdev) {
|
|
|
|
|
|
console.log(`before set ${key} listeners:`, listeners[lkey], target)
|
|
|
|
|
|
}
|
|
|
|
|
|
while (i < listeners[lkey].length) {
|
|
|
|
|
|
let cb = listeners[lkey][i]
|
|
|
|
|
|
if (!callbackList[cb]) {
|
|
|
|
|
|
listeners[lkey].splice(i, 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
i++
|
|
|
|
|
|
if (window.vdev) {
|
|
|
|
|
|
console.log(`${did} set ${key}:`, '\n', callbackList[cb], '\n', oldValue, newValue)
|
|
|
|
|
|
}
|
|
|
|
|
|
cacheUpdateList.push(cb)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
},
|
|
|
|
|
|
deleteProperty(target, key) {
|
|
|
|
|
|
if (window.vdev) {
|
|
|
|
|
|
console.log(`del ${key}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
const result = Reflect.deleteProperty(target, key);
|
|
|
|
|
|
if (result && listen_tags.length === 0) {
|
|
|
|
|
|
let lkey = key
|
|
|
|
|
|
if (isArray) {
|
|
|
|
|
|
lkey = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
if (listeners[lkey]) {
|
|
|
|
|
|
let i = 0
|
|
|
|
|
|
while (i < listeners[lkey].length) {
|
|
|
|
|
|
let cb = listeners[lkey][i]
|
|
|
|
|
|
if (!callbackList[cb]) {
|
|
|
|
|
|
listeners[lkey].splice(i, 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
i++
|
|
|
|
|
|
cacheUpdateList.push(cb)
|
|
|
|
|
|
if (window.vdev) {
|
|
|
|
|
|
console.log(`${did} del ${key}:`, '\n', callbackList[cb], '\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let res = new Proxy(data, handler);
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const expose = {
|
|
|
|
|
|
'console': console,
|
|
|
|
|
|
'window': window,
|
|
|
|
|
|
'prompt': prompt.bind(window),
|
|
|
|
|
|
'alert': alert.bind(window),
|
|
|
|
|
|
'confirm': confirm.bind(window),
|
|
|
|
|
|
'RegExp': RegExp,
|
|
|
|
|
|
'document': document,
|
|
|
|
|
|
'Array': Array,
|
|
|
|
|
|
'Object': Object,
|
|
|
|
|
|
'Math': Math,
|
|
|
|
|
|
'Date': Date,
|
|
|
|
|
|
'JSON': JSON,
|
|
|
|
|
|
'Symbol': Symbol,
|
|
|
|
|
|
'Number': Number,
|
|
|
|
|
|
'eval': eval,
|
|
|
|
|
|
'isNaN': isNaN,
|
|
|
|
|
|
'parseInt': parseInt,
|
|
|
|
|
|
'parseFloat': parseFloat,
|
|
|
|
|
|
'setTimeout': setTimeout.bind(window),
|
|
|
|
|
|
'setInterval': setInterval.bind(window),
|
|
|
|
|
|
'clearTimeout': clearTimeout.bind(window),
|
|
|
|
|
|
'clearInterval': clearInterval.bind(window),
|
|
|
|
|
|
'encodeURIComponent': encodeURIComponent,
|
|
|
|
|
|
'btoa': btoa.bind(window),
|
|
|
|
|
|
'fetch': fetch.bind(window),
|
|
|
|
|
|
'TextDecoder': TextDecoder,
|
|
|
|
|
|
'history': history,
|
|
|
|
|
|
'requestAnimationFrame': requestAnimationFrame.bind(window),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function newProxy(data, env, tmpenv) {
|
|
|
|
|
|
const proxy = new Proxy(data, {
|
|
|
|
|
|
// 拦截所有属性,防止到 Proxy 对象以外的作用域链查找。
|
|
|
|
|
|
has(target, key) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
},
|
|
|
|
|
|
get(target, key, receiver) {
|
|
|
|
|
|
let v
|
|
|
|
|
|
if (key === '$data') {
|
|
|
|
|
|
v = data
|
|
|
|
|
|
} else if (key === '$env') {
|
|
|
|
|
|
v = env
|
|
|
|
|
|
} else if (key in target) {
|
|
|
|
|
|
v = Reflect.get(target, key, receiver);
|
|
|
|
|
|
} else if (key in env) {
|
|
|
|
|
|
v = env[key]
|
|
|
|
|
|
} else if (tmpenv && key in tmpenv) {
|
|
|
|
|
|
v = tmpenv[key]
|
|
|
|
|
|
} else if (key in expose) {
|
|
|
|
|
|
v = expose[key]
|
|
|
|
|
|
} else if (key in window) {
|
|
|
|
|
|
v = window[key]
|
|
|
|
|
|
}
|
|
|
|
|
|
return v
|
|
|
|
|
|
},
|
|
|
|
|
|
set(target, key, newValue, receiver) {
|
|
|
|
|
|
// code global variable set will work on "data"
|
|
|
|
|
|
return Reflect.set(target, key, newValue, receiver);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return proxy
|
|
|
|
|
|
}
|
|
|
|
|
|
// 运行dom属性绑定等小代码语句
|
|
|
|
|
|
// for code snapshot
|
|
|
|
|
|
function Run(originCode, data, env, tmpenv) {
|
|
|
|
|
|
let code = originCode.trim()
|
|
|
|
|
|
const cleanCode = code.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '').trim()
|
|
|
|
|
|
const isStatement = /^(var|let|const|if|for|while|switch|try|throw|class|function|return|debugger)\b/.test(cleanCode)
|
|
|
|
|
|
if (!isStatement && (code.indexOf('\n') === -1 || !cleanCode.includes(';'))) {
|
|
|
|
|
|
code = 'return ' + code
|
|
|
|
|
|
}
|
|
|
|
|
|
code = `
|
|
|
|
|
|
with (sandbox) {
|
|
|
|
|
|
${code}
|
|
|
|
|
|
}`
|
|
|
|
|
|
let res
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fn = new Function('sandbox', code);
|
|
|
|
|
|
res = fn(newProxy(data, env, tmpenv))
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn(`Run error:`, originCode, '\n', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const AsyncFunction = Object.getPrototypeOf(async function() { }).constructor
|
|
|
|
|
|
|
|
|
|
|
|
// 运行大段代码库
|
|
|
|
|
|
async function AsyncRun(originCode, data, env, tmpenv) {
|
|
|
|
|
|
let code = originCode.trim()
|
|
|
|
|
|
if (code.indexOf('\n') === -1) {
|
|
|
|
|
|
code = 'return ' + code
|
|
|
|
|
|
}
|
|
|
|
|
|
code = `
|
|
|
|
|
|
with (sandbox) {
|
|
|
|
|
|
${code}
|
|
|
|
|
|
}`
|
|
|
|
|
|
// try {
|
|
|
|
|
|
const fn = new AsyncFunction('sandbox', code);
|
|
|
|
|
|
return await fn(newProxy(data, env, tmpenv))
|
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
|
// console.warn('AsyncRun error:', error, '\n', originCode)
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolvePath(relativePath, currentPath) {
|
|
|
|
|
|
// 如果相对路径已经是绝对路径,直接返回
|
|
|
|
|
|
if (relativePath.startsWith('/')) {
|
|
|
|
|
|
return relativePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前路径的目录部分(去掉文件名)
|
|
|
|
|
|
const currentDir = currentPath.substring(0, currentPath.lastIndexOf('/'));
|
|
|
|
|
|
|
|
|
|
|
|
// 分割路径段
|
|
|
|
|
|
const currentSegments = currentDir.split('/').filter(segment => segment !== '');
|
|
|
|
|
|
const relativeSegments = relativePath.split('/').filter(segment => segment !== '');
|
|
|
|
|
|
|
|
|
|
|
|
// 处理相对路径段
|
|
|
|
|
|
for (const segment of relativeSegments) {
|
|
|
|
|
|
if (segment === '..') {
|
|
|
|
|
|
// 返回上一级目录
|
|
|
|
|
|
if (currentSegments.length > 0) {
|
|
|
|
|
|
currentSegments.pop();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (segment === '.') {
|
|
|
|
|
|
// 当前目录,不做任何操作
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 普通目录或文件名
|
|
|
|
|
|
currentSegments.push(segment);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建绝对路径
|
|
|
|
|
|
return '/' + currentSegments.join('/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function ParseImport(code, data, env, src) {
|
|
|
|
|
|
data = data || {}
|
|
|
|
|
|
let scoped = env.scoped || ''
|
|
|
|
|
|
let codeCopy = code
|
|
|
|
|
|
let match;
|
|
|
|
|
|
src = src.startsWith('http') ? src : scoped + src
|
|
|
|
|
|
|
|
|
|
|
|
const awaitImportRegex = /await import\(['"]([^'"]+)['"]\)/gm;
|
|
|
|
|
|
while ((match = awaitImportRegex.exec(code)) !== null) {
|
|
|
|
|
|
let url = match[1]
|
|
|
|
|
|
if (!url.startsWith('http')) {
|
|
|
|
|
|
url = resolvePath(url, src)
|
|
|
|
|
|
url = window.location.origin + url
|
|
|
|
|
|
}
|
|
|
|
|
|
codeCopy = codeCopy.replace(match[0], `await import('${url}')`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const importRegex = /^[\s/]*import\s+([\w{},\s]+)\s+from\s+['"]([^'"]+)['"][;\s]*$/gm;
|
|
|
|
|
|
|
|
|
|
|
|
// 提取所有匹配的模块路径
|
|
|
|
|
|
while ((match = importRegex.exec(code)) !== null) {
|
|
|
|
|
|
codeCopy = codeCopy.replace(match[0], '')
|
|
|
|
|
|
if (match[0].trim().startsWith('//')) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
let url = match[2]
|
|
|
|
|
|
if (!url.startsWith('http') && !url.startsWith('@')) {
|
|
|
|
|
|
if (url.startsWith('/') && scoped) {
|
|
|
|
|
|
url = scoped + url
|
|
|
|
|
|
} else {
|
|
|
|
|
|
url = resolvePath(url, src)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (url.startsWith('@')) {
|
|
|
|
|
|
url = url.slice(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!url.endsWith('.js')) {
|
|
|
|
|
|
url += '.js'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!url.startsWith('http')) {
|
|
|
|
|
|
url = window.location.origin + url
|
|
|
|
|
|
}
|
|
|
|
|
|
let packname = match[1].trim()
|
|
|
|
|
|
try {
|
|
|
|
|
|
let packs = null
|
|
|
|
|
|
if (/^\w+$/.test(packname)) {
|
|
|
|
|
|
packs = packname
|
|
|
|
|
|
} else if (/^{[\w\s,]+}$/.test(packname)) {
|
|
|
|
|
|
packs = packname.slice(1, -1).split(',').map(p => p.trim())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('unsupported import: ' + match[0])
|
|
|
|
|
|
}
|
|
|
|
|
|
// window.$env = env
|
|
|
|
|
|
const module = await import(url)
|
|
|
|
|
|
if (typeof packs === 'string') {
|
|
|
|
|
|
if (module.default) {
|
|
|
|
|
|
data[packs] = module.default
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data[packs] = module
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
packs.forEach((p) => {
|
|
|
|
|
|
if (p in module) {
|
|
|
|
|
|
data[p] = module[p]
|
|
|
|
|
|
} else if (p in module.default) {
|
|
|
|
|
|
data[p] = module.default[p]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`模块加载失败 (${match[0]}):`, error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return codeCopy.trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
Wrap, Watch, Cancel, ForceUpdate, SetDataRoot,
|
|
|
|
|
|
DataID, GenUniqueID, Run, AsyncRun, ParseImport
|
|
|
|
|
|
}
|