File size: 6,083 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
'use strict';
const fetch = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/fetch'));
const { $ } = require('basic-devtools');
const $xworker = (require('./worker/class.js'));
const workerURL = (require('./worker/url.js'));
const { getRuntime, getRuntimeID } = require('./loader.js');
const { registry } = require('./interpreters.js');
const { JSModules, all, dispatch, resolve, defineProperty, nodeInfo, registerJSModules } = require('./utils.js');
const getRoot = (script) => {
let parent = script;
while (parent.parentNode) parent = parent.parentNode;
return parent;
};
const queryTarget = (script, idOrSelector) => {
const root = getRoot(script);
return root.getElementById(idOrSelector) || $(idOrSelector, root);
};
exports.queryTarget = queryTarget;
const targets = new WeakMap();
const targetDescriptor = {
get() {
let target = targets.get(this);
if (!target) {
target = document.createElement(`${this.type}-script`);
targets.set(this, target);
handle(this);
}
return target;
},
set(target) {
if (typeof target === 'string')
targets.set(this, queryTarget(this, target));
else {
targets.set(this, target);
handle(this);
}
},
};
const handled = new WeakMap();
const interpreters = new Map();
exports.interpreters = interpreters;
const execute = async (currentScript, source, XWorker, isAsync) => {
const { type } = currentScript;
const module = registry.get(type);
/* c8 ignore start */
if (module.experimental)
console.warn(`The ${type} interpreter is experimental`);
const [interpreter, content] = await all([
handled.get(currentScript).interpreter,
source,
]);
try {
// temporarily override inherited document.currentScript in a non writable way
// but it deletes it right after to preserve native behavior (as it's sync: no trouble)
defineProperty(document, 'currentScript', {
configurable: true,
get: () => currentScript,
});
registerJSModules(type, module, interpreter, JSModules);
module.registerJSModule(interpreter, 'polyscript', {
XWorker,
currentScript,
js_modules: JSModules,
});
dispatch(currentScript, type, 'ready');
const result = module[isAsync ? 'runAsync' : 'run'](interpreter, content);
const done = dispatch.bind(null, currentScript, type, 'done');
if (isAsync) result.then(done);
else done();
return result;
} finally {
delete document.currentScript;
}
/* c8 ignore stop */
};
const getValue = (ref, prefix) => {
const value = ref?.value;
return value ? prefix + value : '';
};
const getDetails = (type, id, name, version, config, configURL, runtime = type) => {
if (!interpreters.has(id)) {
const details = {
interpreter: getRuntime(name, config, configURL),
queue: resolve(),
XWorker: $xworker(type, version),
};
interpreters.set(id, details);
// enable sane defaults when single interpreter *of kind* is used in the page
// this allows `xxx-*` attributes to refer to such interpreter without `env` around
/* c8 ignore start *//* this is tested very well in PyScript */
if (!interpreters.has(type)) interpreters.set(type, details);
if (!interpreters.has(runtime)) interpreters.set(runtime, details);
/* c8 ignore stopt */
}
return interpreters.get(id);
};
exports.getDetails = getDetails;
/**
* @param {HTMLScriptElement} script a special type of <script>
*/
const handle = async (script) => {
// known node, move its companion target after
// vDOM or other use cases where the script is a tracked element
if (handled.has(script)) {
const { target } = script;
if (target) {
// if the script is in the head just append target to the body
if (script.closest('head')) document.body.append(target);
// in any other case preserve the script position
else script.after(target);
}
}
// new script to handle ... allow newly created scripts to work
// just exactly like any other script would
else {
// allow a shared config among scripts, beside interpreter,
// and/or source code with different config or interpreter
const {
attributes: { async: isAsync, config, env, target, version },
src,
type,
} = script;
const versionValue = version?.value;
const name = getRuntimeID(type, versionValue);
let configValue = getValue(config, '|');
const id = getValue(env, '') || `${name}${configValue}`;
configValue = configValue.slice(1);
/* c8 ignore start */
const url = workerURL(script);
if (url) {
const XWorker = $xworker(type, versionValue);
const xworker = new XWorker(url, {
...nodeInfo(script, type),
async: !!isAsync,
config: configValue
});
handled.set(
defineProperty(script, 'xworker', { value: xworker }),
{ xworker }
);
return;
}
/* c8 ignore stop */
const targetValue = getValue(target, '');
const details = getDetails(type, id, name, versionValue, configValue);
handled.set(
defineProperty(script, 'target', targetDescriptor),
details,
);
if (targetValue) targets.set(script, queryTarget(script, targetValue));
// start fetching external resources ASAP
const source = src ? fetch(src).text() : script.textContent;
details.queue = details.queue.then(() =>
execute(script, source, details.XWorker, !!isAsync),
);
}
};
exports.handle = handle;
|