first commit
This commit is contained in:
+14616
File diff suppressed because it is too large
Load Diff
+50
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@babel/parser",
|
||||
"version": "7.29.9",
|
||||
"description": "A JavaScript parser",
|
||||
"author": "The Babel Team (https://babel.dev/team)",
|
||||
"homepage": "https://babel.dev/docs/en/next/babel-parser",
|
||||
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"babel",
|
||||
"javascript",
|
||||
"parser",
|
||||
"tc39",
|
||||
"ecmascript",
|
||||
"@babel/parser"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/babel/babel.git",
|
||||
"directory": "packages/babel-parser"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"types": "./typings/babel-parser.d.ts",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"typings/babel-parser.d.ts",
|
||||
"index.cjs"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"# dependencies": "This package doesn't actually have runtime dependencies. @babel/types is only needed for type definitions.",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/helper-check-duplicate-nodes": "^7.29.7",
|
||||
"@babel/helper-fixtures": "^7.29.7",
|
||||
"@babel/helper-string-parser": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"charcodes": "^0.2.0"
|
||||
},
|
||||
"bin": "./bin/babel-parser.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
Generated
Vendored
+6801
File diff suppressed because it is too large
Load Diff
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
"types": "dist/compiler-core.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/compiler-core.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/compiler-core.cjs.prod.js",
|
||||
"development": "./dist/compiler-core.cjs.js",
|
||||
"default": "./dist/compiler-core.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/compiler-core.esm-bundler.js",
|
||||
"import": "./dist/compiler-core.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"buildOptions": {
|
||||
"name": "VueCompilerCore",
|
||||
"compat": true,
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/compiler-core"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.8",
|
||||
"@vue/shared": "3.5.43",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.29.8"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+692
@@ -0,0 +1,692 @@
|
||||
/**
|
||||
* @vue/compiler-dom v3.5.43
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
* @license MIT
|
||||
**/
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var compilerCore = require('@vue/compiler-core');
|
||||
var shared = require('@vue/shared');
|
||||
|
||||
const V_MODEL_RADIO = /* @__PURE__ */ Symbol(``);
|
||||
const V_MODEL_CHECKBOX = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
const V_MODEL_TEXT = /* @__PURE__ */ Symbol(``);
|
||||
const V_MODEL_SELECT = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
const V_MODEL_DYNAMIC = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
const V_ON_WITH_MODIFIERS = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
const V_ON_WITH_KEYS = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
const V_SHOW = /* @__PURE__ */ Symbol(``);
|
||||
const TRANSITION = /* @__PURE__ */ Symbol(``);
|
||||
const TRANSITION_GROUP = /* @__PURE__ */ Symbol(
|
||||
``
|
||||
);
|
||||
compilerCore.registerRuntimeHelpers({
|
||||
[V_MODEL_RADIO]: `vModelRadio`,
|
||||
[V_MODEL_CHECKBOX]: `vModelCheckbox`,
|
||||
[V_MODEL_TEXT]: `vModelText`,
|
||||
[V_MODEL_SELECT]: `vModelSelect`,
|
||||
[V_MODEL_DYNAMIC]: `vModelDynamic`,
|
||||
[V_ON_WITH_MODIFIERS]: `withModifiers`,
|
||||
[V_ON_WITH_KEYS]: `withKeys`,
|
||||
[V_SHOW]: `vShow`,
|
||||
[TRANSITION]: `Transition`,
|
||||
[TRANSITION_GROUP]: `TransitionGroup`
|
||||
});
|
||||
|
||||
const parserOptions = {
|
||||
parseMode: "html",
|
||||
isVoidTag: shared.isVoidTag,
|
||||
isNativeTag: (tag) => shared.isHTMLTag(tag) || shared.isSVGTag(tag) || shared.isMathMLTag(tag),
|
||||
isPreTag: (tag) => tag === "pre",
|
||||
isIgnoreNewlineTag: (tag) => tag === "pre" || tag === "textarea",
|
||||
decodeEntities: void 0,
|
||||
isBuiltInComponent: (tag) => {
|
||||
if (tag === "Transition" || tag === "transition") {
|
||||
return TRANSITION;
|
||||
} else if (tag === "TransitionGroup" || tag === "transition-group") {
|
||||
return TRANSITION_GROUP;
|
||||
}
|
||||
},
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
||||
getNamespace(tag, parent, rootNamespace) {
|
||||
let ns = parent ? parent.ns : rootNamespace;
|
||||
if (parent && ns === 2) {
|
||||
if (parent.tag === "annotation-xml") {
|
||||
if (tag === "svg") {
|
||||
return 1;
|
||||
}
|
||||
if (parent.props.some(
|
||||
(a) => a.type === 6 && a.name === "encoding" && a.value != null && (a.value.content === "text/html" || a.value.content === "application/xhtml+xml")
|
||||
)) {
|
||||
ns = 0;
|
||||
}
|
||||
} else if (/^m(?:[ions]|text)$/.test(parent.tag) && tag !== "mglyph" && tag !== "malignmark") {
|
||||
ns = 0;
|
||||
}
|
||||
} else if (parent && ns === 1) {
|
||||
if (parent.tag === "foreignObject" || parent.tag === "desc" || parent.tag === "title") {
|
||||
ns = 0;
|
||||
}
|
||||
}
|
||||
if (ns === 0) {
|
||||
if (tag === "svg") {
|
||||
return 1;
|
||||
}
|
||||
if (tag === "math") {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
};
|
||||
|
||||
const transformStyle = (node) => {
|
||||
if (node.type === 1) {
|
||||
node.props.forEach((p, i) => {
|
||||
if (p.type === 6 && p.name === "style" && p.value) {
|
||||
node.props[i] = {
|
||||
type: 7,
|
||||
name: `bind`,
|
||||
arg: compilerCore.createSimpleExpression(`style`, true, p.loc),
|
||||
exp: parseInlineCSS(p.value.content, p.loc),
|
||||
modifiers: [],
|
||||
loc: p.loc
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const parseInlineCSS = (cssText, loc) => {
|
||||
const normalized = shared.parseStringStyle(cssText);
|
||||
return compilerCore.createSimpleExpression(
|
||||
JSON.stringify(normalized),
|
||||
false,
|
||||
loc,
|
||||
3
|
||||
);
|
||||
};
|
||||
|
||||
function createDOMCompilerError(code, loc) {
|
||||
return compilerCore.createCompilerError(
|
||||
code,
|
||||
loc,
|
||||
DOMErrorMessages
|
||||
);
|
||||
}
|
||||
const DOMErrorCodes = {
|
||||
"X_V_HTML_NO_EXPRESSION": 54,
|
||||
"54": "X_V_HTML_NO_EXPRESSION",
|
||||
"X_V_HTML_WITH_CHILDREN": 55,
|
||||
"55": "X_V_HTML_WITH_CHILDREN",
|
||||
"X_V_TEXT_NO_EXPRESSION": 56,
|
||||
"56": "X_V_TEXT_NO_EXPRESSION",
|
||||
"X_V_TEXT_WITH_CHILDREN": 57,
|
||||
"57": "X_V_TEXT_WITH_CHILDREN",
|
||||
"X_V_MODEL_ON_INVALID_ELEMENT": 58,
|
||||
"58": "X_V_MODEL_ON_INVALID_ELEMENT",
|
||||
"X_V_MODEL_ARG_ON_ELEMENT": 59,
|
||||
"59": "X_V_MODEL_ARG_ON_ELEMENT",
|
||||
"X_V_MODEL_ON_FILE_INPUT_ELEMENT": 60,
|
||||
"60": "X_V_MODEL_ON_FILE_INPUT_ELEMENT",
|
||||
"X_V_MODEL_UNNECESSARY_VALUE": 61,
|
||||
"61": "X_V_MODEL_UNNECESSARY_VALUE",
|
||||
"X_V_SHOW_NO_EXPRESSION": 62,
|
||||
"62": "X_V_SHOW_NO_EXPRESSION",
|
||||
"X_TRANSITION_INVALID_CHILDREN": 63,
|
||||
"63": "X_TRANSITION_INVALID_CHILDREN",
|
||||
"X_IGNORED_SIDE_EFFECT_TAG": 64,
|
||||
"64": "X_IGNORED_SIDE_EFFECT_TAG",
|
||||
"__EXTEND_POINT__": 65,
|
||||
"65": "__EXTEND_POINT__"
|
||||
};
|
||||
const DOMErrorMessages = {
|
||||
[54]: `v-html is missing expression.`,
|
||||
[55]: `v-html will override element children.`,
|
||||
[56]: `v-text is missing expression.`,
|
||||
[57]: `v-text will override element children.`,
|
||||
[58]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
|
||||
[59]: `v-model argument is not supported on plain elements.`,
|
||||
[60]: `v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.`,
|
||||
[61]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
|
||||
[62]: `v-show is missing expression.`,
|
||||
[63]: `<Transition> expects exactly one child element or component.`,
|
||||
[64]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`
|
||||
};
|
||||
|
||||
const transformVHtml = (dir, node, context) => {
|
||||
const { exp, loc } = dir;
|
||||
if (!exp) {
|
||||
context.onError(
|
||||
createDOMCompilerError(54, loc)
|
||||
);
|
||||
}
|
||||
if (node.children.length) {
|
||||
context.onError(
|
||||
createDOMCompilerError(55, loc)
|
||||
);
|
||||
node.children.length = 0;
|
||||
}
|
||||
return {
|
||||
props: [
|
||||
compilerCore.createObjectProperty(
|
||||
compilerCore.createSimpleExpression(`innerHTML`, true, loc),
|
||||
exp || compilerCore.createSimpleExpression("", true)
|
||||
)
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
const transformVText = (dir, node, context) => {
|
||||
const { exp, loc } = dir;
|
||||
if (!exp) {
|
||||
context.onError(
|
||||
createDOMCompilerError(56, loc)
|
||||
);
|
||||
}
|
||||
if (node.children.length) {
|
||||
context.onError(
|
||||
createDOMCompilerError(57, loc)
|
||||
);
|
||||
node.children.length = 0;
|
||||
}
|
||||
return {
|
||||
props: [
|
||||
compilerCore.createObjectProperty(
|
||||
compilerCore.createSimpleExpression(`textContent`, true),
|
||||
exp ? compilerCore.getConstantType(exp, context) > 0 ? exp : compilerCore.createCallExpression(
|
||||
context.helperString(compilerCore.TO_DISPLAY_STRING),
|
||||
[exp],
|
||||
loc
|
||||
) : compilerCore.createSimpleExpression("", true)
|
||||
)
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
const transformModel = (dir, node, context) => {
|
||||
const baseResult = compilerCore.transformModel(dir, node, context);
|
||||
if (!baseResult.props.length || node.tagType === 1) {
|
||||
return baseResult;
|
||||
}
|
||||
if (dir.arg) {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
59,
|
||||
dir.arg.loc
|
||||
)
|
||||
);
|
||||
}
|
||||
const { tag } = node;
|
||||
const isCustomElement = context.isCustomElement(tag);
|
||||
if (tag === "input" || tag === "textarea" || tag === "select" || isCustomElement) {
|
||||
let directiveToUse = V_MODEL_TEXT;
|
||||
let isInvalidType = false;
|
||||
if (tag === "input" || isCustomElement) {
|
||||
const type = compilerCore.findProp(node, `type`);
|
||||
if (type) {
|
||||
if (type.type === 7) {
|
||||
directiveToUse = V_MODEL_DYNAMIC;
|
||||
} else if (type.value) {
|
||||
switch (type.value.content) {
|
||||
case "radio":
|
||||
directiveToUse = V_MODEL_RADIO;
|
||||
break;
|
||||
case "checkbox":
|
||||
directiveToUse = V_MODEL_CHECKBOX;
|
||||
break;
|
||||
case "file":
|
||||
isInvalidType = true;
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
60,
|
||||
dir.loc
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (compilerCore.hasDynamicKeyVBind(node)) {
|
||||
directiveToUse = V_MODEL_DYNAMIC;
|
||||
} else ;
|
||||
} else if (tag === "select") {
|
||||
directiveToUse = V_MODEL_SELECT;
|
||||
} else ;
|
||||
if (!isInvalidType) {
|
||||
baseResult.needRuntime = context.helper(directiveToUse);
|
||||
}
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
58,
|
||||
dir.loc
|
||||
)
|
||||
);
|
||||
}
|
||||
baseResult.props = baseResult.props.filter(
|
||||
(p) => !(p.key.type === 4 && p.key.content === "modelValue")
|
||||
);
|
||||
return baseResult;
|
||||
};
|
||||
|
||||
const isEventOptionModifier = /* @__PURE__ */ shared.makeMap(`passive,once,capture`);
|
||||
const isNonKeyModifier = /* @__PURE__ */ shared.makeMap(
|
||||
// event propagation management
|
||||
`stop,prevent,self,ctrl,shift,alt,meta,exact,middle`
|
||||
);
|
||||
const maybeKeyModifier = /* @__PURE__ */ shared.makeMap("left,right");
|
||||
const isKeyboardEvent = /* @__PURE__ */ shared.makeMap(`onkeyup,onkeydown,onkeypress`);
|
||||
const resolveModifiers = (key, modifiers, context, loc) => {
|
||||
const keyModifiers = [];
|
||||
const nonKeyModifiers = [];
|
||||
const eventOptionModifiers = [];
|
||||
for (let i = 0; i < modifiers.length; i++) {
|
||||
const modifier = modifiers[i].content;
|
||||
if (modifier === "native" && compilerCore.checkCompatEnabled(
|
||||
"COMPILER_V_ON_NATIVE",
|
||||
context,
|
||||
loc
|
||||
)) {
|
||||
eventOptionModifiers.push(modifier);
|
||||
} else if (isEventOptionModifier(modifier)) {
|
||||
eventOptionModifiers.push(modifier);
|
||||
} else {
|
||||
if (maybeKeyModifier(modifier)) {
|
||||
if (compilerCore.isStaticExp(key)) {
|
||||
if (isKeyboardEvent(key.content.toLowerCase())) {
|
||||
keyModifiers.push(modifier);
|
||||
} else {
|
||||
nonKeyModifiers.push(modifier);
|
||||
}
|
||||
} else {
|
||||
keyModifiers.push(modifier);
|
||||
nonKeyModifiers.push(modifier);
|
||||
}
|
||||
} else {
|
||||
if (isNonKeyModifier(modifier)) {
|
||||
nonKeyModifiers.push(modifier);
|
||||
} else {
|
||||
keyModifiers.push(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
keyModifiers,
|
||||
nonKeyModifiers,
|
||||
eventOptionModifiers
|
||||
};
|
||||
};
|
||||
const transformClick = (key, event) => {
|
||||
const isStaticClick = compilerCore.isStaticExp(key) && key.content.toLowerCase() === "onclick";
|
||||
return isStaticClick ? compilerCore.createSimpleExpression(event, true) : key.type !== 4 ? compilerCore.createCompoundExpression([
|
||||
`(`,
|
||||
key,
|
||||
`) === "onClick" ? "${event}" : (`,
|
||||
key,
|
||||
`)`
|
||||
]) : key;
|
||||
};
|
||||
const transformOn = (dir, node, context) => {
|
||||
return compilerCore.transformOn(dir, node, context, (baseResult) => {
|
||||
const { modifiers } = dir;
|
||||
if (!modifiers.length) return baseResult;
|
||||
let { key, value: handlerExp } = baseResult.props[0];
|
||||
const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(key, modifiers, context, dir.loc);
|
||||
if (nonKeyModifiers.includes("right")) {
|
||||
key = transformClick(key, `onContextmenu`);
|
||||
}
|
||||
if (nonKeyModifiers.includes("middle")) {
|
||||
key = transformClick(key, `onMouseup`);
|
||||
}
|
||||
if (nonKeyModifiers.length) {
|
||||
handlerExp = compilerCore.createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
||||
handlerExp,
|
||||
JSON.stringify(nonKeyModifiers)
|
||||
]);
|
||||
}
|
||||
if (keyModifiers.length && // if event name is dynamic, always wrap with keys guard
|
||||
(!compilerCore.isStaticExp(key) || isKeyboardEvent(key.content.toLowerCase()))) {
|
||||
handlerExp = compilerCore.createCallExpression(context.helper(V_ON_WITH_KEYS), [
|
||||
handlerExp,
|
||||
JSON.stringify(keyModifiers)
|
||||
]);
|
||||
}
|
||||
if (eventOptionModifiers.length) {
|
||||
const modifierPostfix = eventOptionModifiers.map(shared.capitalize).join("");
|
||||
key = compilerCore.isStaticExp(key) ? compilerCore.createSimpleExpression(`${key.content}${modifierPostfix}`, true) : compilerCore.createCompoundExpression([`(`, key, `) + "${modifierPostfix}"`]);
|
||||
}
|
||||
return {
|
||||
props: [compilerCore.createObjectProperty(key, handlerExp)]
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const transformShow = (dir, node, context) => {
|
||||
const { exp, loc } = dir;
|
||||
if (!exp) {
|
||||
context.onError(
|
||||
createDOMCompilerError(62, loc)
|
||||
);
|
||||
}
|
||||
return {
|
||||
props: [],
|
||||
needRuntime: context.helper(V_SHOW)
|
||||
};
|
||||
};
|
||||
|
||||
const expReplaceRE = /__VUE_EXP_START__(.*?)__VUE_EXP_END__/g;
|
||||
const stringifyStatic = (children, context, parent) => {
|
||||
if (context.scopes.vSlot > 0) {
|
||||
return;
|
||||
}
|
||||
const isParentCached = parent.type === 1 && parent.codegenNode && parent.codegenNode.type === 13 && parent.codegenNode.children && !shared.isArray(parent.codegenNode.children) && parent.codegenNode.children.type === 20;
|
||||
let nc = 0;
|
||||
let ec = 0;
|
||||
const currentChunk = [];
|
||||
const stringifyCurrentChunk = (currentIndex) => {
|
||||
if (nc >= 20 || ec >= 5) {
|
||||
const staticCall = compilerCore.createCallExpression(context.helper(compilerCore.CREATE_STATIC), [
|
||||
JSON.stringify(
|
||||
currentChunk.map((node) => stringifyNode(node, context)).join("")
|
||||
).replace(expReplaceRE, `" + $1 + "`),
|
||||
// the 2nd argument indicates the number of DOM nodes this static vnode
|
||||
// will insert / hydrate
|
||||
String(currentChunk.length)
|
||||
]);
|
||||
const deleteCount = currentChunk.length - 1;
|
||||
if (isParentCached) {
|
||||
children.splice(
|
||||
currentIndex - currentChunk.length,
|
||||
currentChunk.length,
|
||||
// @ts-expect-error
|
||||
staticCall
|
||||
);
|
||||
} else {
|
||||
currentChunk[0].codegenNode.value = staticCall;
|
||||
if (currentChunk.length > 1) {
|
||||
children.splice(currentIndex - currentChunk.length + 1, deleteCount);
|
||||
const cacheIndex = context.cached.indexOf(
|
||||
currentChunk[currentChunk.length - 1].codegenNode
|
||||
);
|
||||
if (cacheIndex > -1) {
|
||||
for (let i2 = cacheIndex; i2 < context.cached.length; i2++) {
|
||||
const c = context.cached[i2];
|
||||
if (c) c.index -= deleteCount;
|
||||
}
|
||||
context.cached.splice(cacheIndex - deleteCount + 1, deleteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
return deleteCount;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
let i = 0;
|
||||
for (; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const isCached = isParentCached || getCachedNode(child);
|
||||
if (isCached) {
|
||||
const result = analyzeNode(child);
|
||||
if (result) {
|
||||
nc += result[0];
|
||||
ec += result[1];
|
||||
currentChunk.push(child);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i -= stringifyCurrentChunk(i);
|
||||
nc = 0;
|
||||
ec = 0;
|
||||
currentChunk.length = 0;
|
||||
}
|
||||
stringifyCurrentChunk(i);
|
||||
};
|
||||
const getCachedNode = (node) => {
|
||||
if ((node.type === 1 && node.tagType === 0 || node.type === 12) && node.codegenNode && node.codegenNode.type === 20) {
|
||||
return node.codegenNode;
|
||||
}
|
||||
};
|
||||
const dataAriaRE = /^(?:data|aria)-/;
|
||||
const isStringifiableAttr = (name, ns) => {
|
||||
return (ns === 0 ? shared.isKnownHtmlAttr(name) : ns === 1 ? shared.isKnownSvgAttr(name) : ns === 2 ? shared.isKnownMathMLAttr(name) : false) || dataAriaRE.test(name);
|
||||
};
|
||||
const isNonStringifiable = /* @__PURE__ */ shared.makeMap(
|
||||
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`
|
||||
);
|
||||
function analyzeNode(node) {
|
||||
if (node.type === 1 && isNonStringifiable(node.tag)) {
|
||||
return false;
|
||||
}
|
||||
if (node.type === 1 && compilerCore.findDir(node, "once", true)) {
|
||||
return false;
|
||||
}
|
||||
if (node.type === 12) {
|
||||
return [1, 0];
|
||||
}
|
||||
let nc = 1;
|
||||
let ec = node.props.length > 0 ? 1 : 0;
|
||||
let bailed = false;
|
||||
const bail = () => {
|
||||
bailed = true;
|
||||
return false;
|
||||
};
|
||||
function walk(node2) {
|
||||
const isOptionTag = node2.tag === "option" && node2.ns === 0;
|
||||
for (let i = 0; i < node2.props.length; i++) {
|
||||
const p = node2.props[i];
|
||||
if (p.type === 6 && !isStringifiableAttr(p.name, node2.ns)) {
|
||||
return bail();
|
||||
}
|
||||
if (p.type === 7 && p.name === "bind") {
|
||||
if (p.arg && (p.arg.type === 8 || p.arg.isStatic && !isStringifiableAttr(p.arg.content, node2.ns))) {
|
||||
return bail();
|
||||
}
|
||||
if (p.exp && (p.exp.type === 8 || p.exp.constType < 3)) {
|
||||
return bail();
|
||||
}
|
||||
if (isOptionTag && compilerCore.isStaticArgOf(p.arg, "value") && p.exp && !p.exp.isStatic) {
|
||||
return bail();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < node2.children.length; i++) {
|
||||
nc++;
|
||||
const child = node2.children[i];
|
||||
if (child.type === 1) {
|
||||
if (child.props.length > 0) {
|
||||
ec++;
|
||||
}
|
||||
walk(child);
|
||||
if (bailed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return walk(node) ? [nc, ec] : false;
|
||||
}
|
||||
function stringifyNode(node, context) {
|
||||
if (shared.isString(node)) {
|
||||
return node;
|
||||
}
|
||||
if (shared.isSymbol(node)) {
|
||||
return ``;
|
||||
}
|
||||
switch (node.type) {
|
||||
case 1:
|
||||
return stringifyElement(node, context);
|
||||
case 2:
|
||||
return shared.escapeHtml(node.content);
|
||||
case 3:
|
||||
return `<!--${shared.escapeHtml(node.content)}-->`;
|
||||
case 5:
|
||||
return shared.escapeHtml(shared.toDisplayString(evaluateConstant(node.content)));
|
||||
case 8:
|
||||
return shared.escapeHtml(evaluateConstant(node));
|
||||
case 12:
|
||||
return stringifyNode(node.content, context);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function stringifyElement(node, context) {
|
||||
let res = `<${node.tag}`;
|
||||
let innerHTML = "";
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i];
|
||||
if (p.type === 6) {
|
||||
res += ` ${p.name}`;
|
||||
if (p.value) {
|
||||
res += `="${shared.escapeHtml(p.value.content)}"`;
|
||||
}
|
||||
} else if (p.type === 7) {
|
||||
if (p.name === "bind") {
|
||||
const exp = p.exp;
|
||||
if (exp.content[0] === "_") {
|
||||
res += ` ${p.arg.content}="__VUE_EXP_START__${exp.content}__VUE_EXP_END__"`;
|
||||
continue;
|
||||
}
|
||||
if ((shared.isBooleanAttr(p.arg.content) || p.arg.content === "hidden") && exp.content === "false") {
|
||||
continue;
|
||||
}
|
||||
let evaluated = evaluateConstant(exp);
|
||||
if (evaluated != null) {
|
||||
const arg = p.arg && p.arg.content;
|
||||
if (arg === "hidden" && typeof evaluated === "number" && !shared.includeBooleanAttr(evaluated)) {
|
||||
continue;
|
||||
}
|
||||
if (arg === "class") {
|
||||
evaluated = shared.normalizeClass(evaluated);
|
||||
} else if (arg === "style") {
|
||||
evaluated = shared.stringifyStyle(shared.normalizeStyle(evaluated));
|
||||
}
|
||||
res += ` ${p.arg.content}="${shared.escapeHtml(
|
||||
evaluated
|
||||
)}"`;
|
||||
}
|
||||
} else if (p.name === "html") {
|
||||
innerHTML = evaluateConstant(p.exp);
|
||||
} else if (p.name === "text") {
|
||||
innerHTML = shared.escapeHtml(
|
||||
shared.toDisplayString(evaluateConstant(p.exp))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (context.scopeId) {
|
||||
res += ` ${context.scopeId}`;
|
||||
}
|
||||
res += `>`;
|
||||
if (innerHTML) {
|
||||
res += innerHTML;
|
||||
} else {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
res += stringifyNode(node.children[i], context);
|
||||
}
|
||||
}
|
||||
if (!shared.isVoidTag(node.tag)) {
|
||||
res += `</${node.tag}>`;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
function evaluateConstant(exp) {
|
||||
if (exp.type === 4) {
|
||||
return new Function(`return (${exp.content})`)();
|
||||
} else {
|
||||
let res = ``;
|
||||
exp.children.forEach((c) => {
|
||||
if (shared.isString(c) || shared.isSymbol(c)) {
|
||||
return;
|
||||
}
|
||||
if (c.type === 2) {
|
||||
res += c.content;
|
||||
} else if (c.type === 5) {
|
||||
res += shared.toDisplayString(evaluateConstant(c.content));
|
||||
} else {
|
||||
res += evaluateConstant(c);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
const ignoreSideEffectTags = (node, context) => {
|
||||
if (node.type === 1 && node.tagType === 0 && (node.tag === "script" || node.tag === "style")) {
|
||||
context.removeNode();
|
||||
}
|
||||
};
|
||||
|
||||
const DOMNodeTransforms = [
|
||||
transformStyle,
|
||||
...[]
|
||||
];
|
||||
const DOMDirectiveTransforms = {
|
||||
cloak: compilerCore.noopDirectiveTransform,
|
||||
html: transformVHtml,
|
||||
text: transformVText,
|
||||
model: transformModel,
|
||||
// override compiler-core
|
||||
on: transformOn,
|
||||
// override compiler-core
|
||||
show: transformShow
|
||||
};
|
||||
function compile(src, options = {}) {
|
||||
return compilerCore.baseCompile(
|
||||
src,
|
||||
shared.extend({}, parserOptions, options, {
|
||||
nodeTransforms: [
|
||||
// ignore <script> and <tag>
|
||||
// this is not put inside DOMNodeTransforms because that list is used
|
||||
// by compiler-ssr to generate vnode fallback branches
|
||||
ignoreSideEffectTags,
|
||||
...DOMNodeTransforms,
|
||||
...options.nodeTransforms || []
|
||||
],
|
||||
directiveTransforms: shared.extend(
|
||||
{},
|
||||
DOMDirectiveTransforms,
|
||||
options.directiveTransforms || {}
|
||||
),
|
||||
transformHoist: stringifyStatic
|
||||
})
|
||||
);
|
||||
}
|
||||
function parse(template, options = {}) {
|
||||
return compilerCore.baseParse(template, shared.extend({}, parserOptions, options));
|
||||
}
|
||||
|
||||
exports.DOMDirectiveTransforms = DOMDirectiveTransforms;
|
||||
exports.DOMErrorCodes = DOMErrorCodes;
|
||||
exports.DOMErrorMessages = DOMErrorMessages;
|
||||
exports.DOMNodeTransforms = DOMNodeTransforms;
|
||||
exports.TRANSITION = TRANSITION;
|
||||
exports.TRANSITION_GROUP = TRANSITION_GROUP;
|
||||
exports.V_MODEL_CHECKBOX = V_MODEL_CHECKBOX;
|
||||
exports.V_MODEL_DYNAMIC = V_MODEL_DYNAMIC;
|
||||
exports.V_MODEL_RADIO = V_MODEL_RADIO;
|
||||
exports.V_MODEL_SELECT = V_MODEL_SELECT;
|
||||
exports.V_MODEL_TEXT = V_MODEL_TEXT;
|
||||
exports.V_ON_WITH_KEYS = V_ON_WITH_KEYS;
|
||||
exports.V_ON_WITH_MODIFIERS = V_ON_WITH_MODIFIERS;
|
||||
exports.V_SHOW = V_SHOW;
|
||||
exports.compile = compile;
|
||||
exports.createDOMCompilerError = createDOMCompilerError;
|
||||
exports.parse = parse;
|
||||
exports.parserOptions = parserOptions;
|
||||
exports.transformStyle = transformStyle;
|
||||
Object.keys(compilerCore).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = compilerCore[k];
|
||||
});
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
"types": "dist/compiler-dom.d.ts",
|
||||
"unpkg": "dist/compiler-dom.global.js",
|
||||
"jsdelivr": "dist/compiler-dom.global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/compiler-dom.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/compiler-dom.cjs.prod.js",
|
||||
"development": "./dist/compiler-dom.cjs.js",
|
||||
"default": "./dist/compiler-dom.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/compiler-dom.esm-bundler.js",
|
||||
"import": "./dist/compiler-dom.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"name": "VueCompilerDOM",
|
||||
"compat": true,
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"esm-browser",
|
||||
"cjs",
|
||||
"global"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/compiler-dom"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/compiler-core": "3.5.43"
|
||||
}
|
||||
}
|
||||
+1420
File diff suppressed because it is too large
Load Diff
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"buildOptions": {
|
||||
"prod": false,
|
||||
"formats": [
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/compiler-ssr"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/compiler-dom": "3.5.43"
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//#region src/index-node.ts
|
||||
function addCustomCommand() {}
|
||||
function addCustomTab() {}
|
||||
function onDevToolsClientConnected(_fn) {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
function onDevToolsConnected(_fn) {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
function removeCustomCommand() {}
|
||||
function setupDevToolsPlugin(_pluginDescriptor, _setupFn) {}
|
||||
//#endregion
|
||||
export { addCustomCommand, addCustomTab, onDevToolsClientConnected, onDevToolsConnected, removeCustomCommand, setupDevToolsPlugin, setupDevToolsPlugin as setupDevtoolsPlugin };
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@vue/devtools-api",
|
||||
"type": "module",
|
||||
"version": "8.2.1",
|
||||
"author": "webfansplz",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"directory": "packages/devtools-api",
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/devtools.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"node": {
|
||||
"import": "./dist/index-node.js",
|
||||
"require": "./dist/index-node.cjs"
|
||||
},
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"unpkg": "dist/vue-devtools-api.global.js",
|
||||
"jsdelivr": "dist/vue-devtools-api.global.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@vue/devtools-kit": "^8.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown --clean",
|
||||
"prepare:type": "tsdown --dts-only",
|
||||
"stub": "tsdown --watch --onSuccess 'tsdown --dts-only'"
|
||||
}
|
||||
}
|
||||
+1905
File diff suppressed because it is too large
Load Diff
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
"types": "dist/reactivity.d.ts",
|
||||
"unpkg": "dist/reactivity.global.js",
|
||||
"jsdelivr": "dist/reactivity.global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/reactivity.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/reactivity.cjs.prod.js",
|
||||
"development": "./dist/reactivity.cjs.js",
|
||||
"default": "./dist/reactivity.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/reactivity.esm-bundler.js",
|
||||
"import": "./dist/reactivity.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/reactivity"
|
||||
},
|
||||
"buildOptions": {
|
||||
"name": "VueReactivity",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"esm-browser",
|
||||
"cjs",
|
||||
"global"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+7006
File diff suppressed because it is too large
Load Diff
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
"types": "dist/runtime-core.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/runtime-core.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/runtime-core.cjs.prod.js",
|
||||
"development": "./dist/runtime-core.cjs.js",
|
||||
"default": "./dist/runtime-core.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/runtime-core.esm-bundler.js",
|
||||
"import": "./dist/runtime-core.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"buildOptions": {
|
||||
"name": "VueRuntimeCore",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/runtime-core"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/reactivity": "3.5.43"
|
||||
}
|
||||
}
|
||||
+1867
File diff suppressed because it is too large
Load Diff
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
"types": "dist/runtime-dom.d.ts",
|
||||
"unpkg": "dist/runtime-dom.global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/runtime-dom.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/runtime-dom.cjs.prod.js",
|
||||
"development": "./dist/runtime-dom.cjs.js",
|
||||
"default": "./dist/runtime-dom.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/runtime-dom.esm-bundler.js",
|
||||
"import": "./dist/runtime-dom.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"name": "VueRuntimeDOM",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"esm-browser",
|
||||
"cjs",
|
||||
"global"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/runtime-dom"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/runtime-core": "3.5.43",
|
||||
"@vue/reactivity": "3.5.43",
|
||||
"csstype": "^3.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+904
@@ -0,0 +1,904 @@
|
||||
/**
|
||||
* @vue/server-renderer v3.5.43
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
* @license MIT
|
||||
**/
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var Vue = require('@vue/runtime-dom');
|
||||
var shared = require('@vue/shared');
|
||||
var compilerSsr = require('@vue/compiler-ssr');
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
var n = Object.create(null);
|
||||
if (e) {
|
||||
for (var k in e) {
|
||||
n[k] = e[k];
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
var Vue__namespace = /*#__PURE__*/_interopNamespaceDefault(Vue);
|
||||
|
||||
const shouldIgnoreProp = /* @__PURE__ */ shared.makeMap(
|
||||
`,key,ref,innerHTML,textContent,ref_key,ref_for`
|
||||
);
|
||||
function ssrRenderAttrs(props, tag) {
|
||||
let ret = "";
|
||||
for (let key in props) {
|
||||
if (shouldIgnoreProp(key) || shared.isOn(key) || tag === "textarea" && key === "value" || // force as property (not rendered in SSR)
|
||||
key.startsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
const value = props[key];
|
||||
if (key.startsWith("^")) key = key.slice(1);
|
||||
if (key === "class") {
|
||||
ret += ` class="${ssrRenderClass(value)}"`;
|
||||
} else if (key === "style") {
|
||||
ret += ` style="${ssrRenderStyle(value)}"`;
|
||||
} else if (key === "className") {
|
||||
if (value != null) {
|
||||
ret += ` class="${shared.escapeHtml(String(value))}"`;
|
||||
}
|
||||
} else {
|
||||
ret += ssrRenderDynamicAttr(key, value, tag);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function ssrRenderDynamicAttr(key, value, tag) {
|
||||
if (!shared.isRenderableAttrValue(value)) {
|
||||
return ``;
|
||||
}
|
||||
const attrKey = tag && (tag.indexOf("-") > 0 || shared.isSVGTag(tag)) ? key : shared.propsToAttrMap[key] || key.toLowerCase();
|
||||
if (shared.isBooleanAttr(attrKey) || attrKey === "hidden" && (typeof value === "boolean" || typeof value === "number")) {
|
||||
return shared.includeBooleanAttr(value) ? ` ${attrKey}` : ``;
|
||||
} else if (shared.isSSRSafeAttrName(attrKey)) {
|
||||
return value === "" ? ` ${attrKey}` : ` ${attrKey}="${shared.escapeHtml(value)}"`;
|
||||
} else {
|
||||
console.warn(
|
||||
`[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`
|
||||
);
|
||||
return ``;
|
||||
}
|
||||
}
|
||||
function ssrRenderAttr(key, value) {
|
||||
if (!shared.isRenderableAttrValue(value)) {
|
||||
return ``;
|
||||
}
|
||||
return ` ${key}="${shared.escapeHtml(value)}"`;
|
||||
}
|
||||
function ssrRenderClass(raw) {
|
||||
return shared.escapeHtml(shared.normalizeClass(raw));
|
||||
}
|
||||
function ssrRenderStyle(raw) {
|
||||
if (!raw) {
|
||||
return "";
|
||||
}
|
||||
if (shared.isString(raw)) {
|
||||
return shared.escapeHtml(raw);
|
||||
}
|
||||
const styles = shared.normalizeStyle(ssrResetCssVars(raw));
|
||||
return shared.escapeHtml(shared.stringifyStyle(styles));
|
||||
}
|
||||
function ssrResetCssVars(raw) {
|
||||
if (!shared.isArray(raw) && shared.isObject(raw)) {
|
||||
const res = {};
|
||||
for (const key in raw) {
|
||||
if (key.startsWith(":--")) {
|
||||
res[key.slice(1)] = shared.normalizeCssVarValue(raw[key]);
|
||||
} else {
|
||||
res[key] = raw[key];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
function ssrRenderComponent(comp, props = null, children = null, parentComponent = null, slotScopeId) {
|
||||
return renderComponentVNode(
|
||||
Vue.createVNode(comp, props, children),
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
}
|
||||
|
||||
const { ensureValidVNode } = Vue.ssrUtils;
|
||||
function ssrRenderSlot(slots, slotName, slotProps, fallbackRenderFn, push, parentComponent, slotScopeId) {
|
||||
push(`<!--[-->`);
|
||||
ssrRenderSlotInner(
|
||||
slots,
|
||||
slotName,
|
||||
slotProps,
|
||||
fallbackRenderFn,
|
||||
push,
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
push(`<!--]-->`);
|
||||
}
|
||||
function ssrRenderSlotInner(slots, slotName, slotProps, fallbackRenderFn, push, parentComponent, slotScopeId, transition) {
|
||||
const slotFn = slots[slotName];
|
||||
if (slotFn) {
|
||||
const slotBuffer = [];
|
||||
const bufferedPush = (item) => {
|
||||
slotBuffer.push(item);
|
||||
};
|
||||
const ret = slotFn(
|
||||
// keep the slot function's contract in sync with `renderSlot`, which also
|
||||
// normalizes nullish props before invoking the slot
|
||||
slotProps || {},
|
||||
bufferedPush,
|
||||
parentComponent,
|
||||
slotScopeId ? " " + slotScopeId : ""
|
||||
);
|
||||
if (shared.isArray(ret)) {
|
||||
const validSlotContent = ensureValidVNode(ret);
|
||||
if (validSlotContent) {
|
||||
renderVNodeChildren(
|
||||
push,
|
||||
validSlotContent,
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
} else if (fallbackRenderFn) {
|
||||
fallbackRenderFn();
|
||||
} else if (transition) {
|
||||
push(`<!---->`);
|
||||
}
|
||||
} else {
|
||||
let isEmptySlot = true;
|
||||
if (transition) {
|
||||
isEmptySlot = false;
|
||||
} else {
|
||||
for (let i = 0; i < slotBuffer.length; i++) {
|
||||
if (!isComment(slotBuffer[i])) {
|
||||
isEmptySlot = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isEmptySlot) {
|
||||
if (fallbackRenderFn) {
|
||||
fallbackRenderFn();
|
||||
}
|
||||
} else {
|
||||
let start = 0;
|
||||
let end = slotBuffer.length;
|
||||
if (transition && slotBuffer[0] === "<!--[-->" && slotBuffer[end - 1] === "<!--]-->") {
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
if (start < end) {
|
||||
for (let i = start; i < end; i++) {
|
||||
push(slotBuffer[i]);
|
||||
}
|
||||
} else if (transition) {
|
||||
push(`<!---->`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (fallbackRenderFn) {
|
||||
fallbackRenderFn();
|
||||
} else if (transition) {
|
||||
push(`<!---->`);
|
||||
}
|
||||
}
|
||||
const commentTestRE = /^<!--[\s\S]*-->$/;
|
||||
const commentRE = /<!--[^]*?-->/gm;
|
||||
function isComment(item) {
|
||||
if (typeof item !== "string" || !commentTestRE.test(item)) return false;
|
||||
if (item.length <= 8) return true;
|
||||
return !item.replace(commentRE, "").trim();
|
||||
}
|
||||
|
||||
function ssrRenderTeleport(parentPush, contentRenderFn, target, disabled, parentComponent) {
|
||||
parentPush("<!--teleport start-->");
|
||||
const context = parentComponent.appContext.provides[Vue.ssrContextKey];
|
||||
const teleportBuffers = context.__teleportBuffers || (context.__teleportBuffers = {});
|
||||
const targetBuffer = teleportBuffers[target] || (teleportBuffers[target] = []);
|
||||
const bufferIndex = targetBuffer.length;
|
||||
let teleportContent;
|
||||
if (disabled) {
|
||||
contentRenderFn(parentPush);
|
||||
teleportContent = `<!--teleport start anchor--><!--teleport anchor-->`;
|
||||
} else {
|
||||
const { getBuffer, push } = createBuffer();
|
||||
push(`<!--teleport start anchor-->`);
|
||||
contentRenderFn(push);
|
||||
push(`<!--teleport anchor-->`);
|
||||
teleportContent = getBuffer();
|
||||
}
|
||||
targetBuffer.splice(bufferIndex, 0, teleportContent);
|
||||
if (shared.isPromise(teleportContent) || shared.isArray(teleportContent) && teleportContent.hasAsync) {
|
||||
targetBuffer.hasAsync = true;
|
||||
}
|
||||
parentPush("<!--teleport end-->");
|
||||
}
|
||||
|
||||
function ssrInterpolate(value) {
|
||||
return shared.escapeHtml(shared.toDisplayString(value));
|
||||
}
|
||||
|
||||
function ssrRenderList(source, renderItem) {
|
||||
if (shared.isArray(source) || shared.isString(source)) {
|
||||
for (let i = 0, l = source.length; i < l; i++) {
|
||||
renderItem(source[i], i);
|
||||
}
|
||||
} else if (typeof source === "number") {
|
||||
for (let i = 0; i < source; i++) {
|
||||
renderItem(i + 1, i);
|
||||
}
|
||||
} else if (shared.isObject(source)) {
|
||||
if (source[Symbol.iterator]) {
|
||||
let i = 0;
|
||||
for (const item of source) {
|
||||
renderItem(item, i++);
|
||||
}
|
||||
} else {
|
||||
const keys = Object.keys(source);
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
const key = keys[i];
|
||||
renderItem(source[key], key, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ssrRenderSuspense(push, { default: renderContent }) {
|
||||
if (renderContent) {
|
||||
renderContent();
|
||||
} else {
|
||||
push(`<!---->`);
|
||||
}
|
||||
}
|
||||
|
||||
function ssrGetDirectiveProps(instance, dir, value, arg, modifiers = {}) {
|
||||
if (typeof dir !== "function" && dir.getSSRProps) {
|
||||
return dir.getSSRProps(
|
||||
{
|
||||
dir,
|
||||
instance: Vue.ssrUtils.getComponentPublicInstance(instance.$),
|
||||
value,
|
||||
oldValue: void 0,
|
||||
arg,
|
||||
modifiers
|
||||
},
|
||||
null
|
||||
) || {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const ssrLooseEqual = shared.looseEqual;
|
||||
function ssrLooseContain(arr, value) {
|
||||
return shared.looseIndexOf(arr, value) > -1;
|
||||
}
|
||||
function ssrRenderDynamicModel(type, model, value) {
|
||||
switch (type) {
|
||||
case "radio":
|
||||
return shared.looseEqual(model, value) ? " checked" : "";
|
||||
case "checkbox":
|
||||
return (shared.isArray(model) ? ssrLooseContain(model, value) : model) ? " checked" : "";
|
||||
default:
|
||||
return ssrRenderAttr("value", model);
|
||||
}
|
||||
}
|
||||
function ssrGetDynamicModelProps(existingProps = {}, model) {
|
||||
const { type, value } = existingProps;
|
||||
switch (type) {
|
||||
case "radio":
|
||||
return shared.looseEqual(model, value) ? { checked: true } : null;
|
||||
case "checkbox":
|
||||
return (shared.isArray(model) ? ssrLooseContain(model, value) : model) ? { checked: true } : null;
|
||||
default:
|
||||
return { value: model };
|
||||
}
|
||||
}
|
||||
|
||||
var helpers = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
ssrGetDirectiveProps: ssrGetDirectiveProps,
|
||||
ssrGetDynamicModelProps: ssrGetDynamicModelProps,
|
||||
ssrIncludeBooleanAttr: shared.includeBooleanAttr,
|
||||
ssrInterpolate: ssrInterpolate,
|
||||
ssrLooseContain: ssrLooseContain,
|
||||
ssrLooseEqual: ssrLooseEqual,
|
||||
ssrRenderAttr: ssrRenderAttr,
|
||||
ssrRenderAttrs: ssrRenderAttrs,
|
||||
ssrRenderClass: ssrRenderClass,
|
||||
ssrRenderComponent: ssrRenderComponent,
|
||||
ssrRenderDynamicAttr: ssrRenderDynamicAttr,
|
||||
ssrRenderDynamicModel: ssrRenderDynamicModel,
|
||||
ssrRenderList: ssrRenderList,
|
||||
ssrRenderSlot: ssrRenderSlot,
|
||||
ssrRenderSlotInner: ssrRenderSlotInner,
|
||||
ssrRenderStyle: ssrRenderStyle,
|
||||
ssrRenderSuspense: ssrRenderSuspense,
|
||||
ssrRenderTeleport: ssrRenderTeleport,
|
||||
ssrRenderVNode: renderVNode
|
||||
});
|
||||
|
||||
const compileCache = /* @__PURE__ */ Object.create(null);
|
||||
function ssrCompile(template, instance) {
|
||||
const Component = instance.type;
|
||||
const { isCustomElement, compilerOptions } = instance.appContext.config;
|
||||
const { delimiters, compilerOptions: componentCompilerOptions } = Component;
|
||||
const finalCompilerOptions = shared.extend(
|
||||
shared.extend(
|
||||
{
|
||||
isCustomElement,
|
||||
delimiters
|
||||
},
|
||||
compilerOptions
|
||||
),
|
||||
componentCompilerOptions
|
||||
);
|
||||
finalCompilerOptions.isCustomElement = finalCompilerOptions.isCustomElement || shared.NO;
|
||||
finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || shared.NO;
|
||||
const cacheKey = JSON.stringify(
|
||||
{
|
||||
template,
|
||||
compilerOptions: finalCompilerOptions
|
||||
},
|
||||
(key, value) => {
|
||||
return shared.isFunction(value) ? value.toString() : value;
|
||||
}
|
||||
);
|
||||
const cached = compileCache[cacheKey];
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
finalCompilerOptions.onError = (err) => {
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
const { code } = compilerSsr.compile(template, finalCompilerOptions);
|
||||
const requireMap = {
|
||||
vue: Vue__namespace,
|
||||
"vue/server-renderer": helpers
|
||||
};
|
||||
const fakeRequire = (id) => requireMap[id];
|
||||
return compileCache[cacheKey] = Function("require", code)(fakeRequire);
|
||||
}
|
||||
|
||||
const {
|
||||
createComponentInstance,
|
||||
setCurrentRenderingInstance,
|
||||
setupComponent,
|
||||
renderComponentRoot,
|
||||
normalizeVNode,
|
||||
pushWarningContext,
|
||||
popWarningContext
|
||||
} = Vue.ssrUtils;
|
||||
function createBuffer() {
|
||||
let appendable = false;
|
||||
const buffer = [];
|
||||
return {
|
||||
getBuffer() {
|
||||
return buffer;
|
||||
},
|
||||
push(item) {
|
||||
const isStringItem = shared.isString(item);
|
||||
if (appendable && isStringItem) {
|
||||
buffer[buffer.length - 1] += item;
|
||||
return;
|
||||
}
|
||||
buffer.push(item);
|
||||
appendable = isStringItem;
|
||||
if (shared.isPromise(item) || shared.isArray(item) && item.hasAsync) {
|
||||
buffer.hasAsync = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function renderComponentVNode(vnode, parentComponent = null, slotScopeId) {
|
||||
const instance = vnode.component = createComponentInstance(
|
||||
vnode,
|
||||
parentComponent,
|
||||
null
|
||||
);
|
||||
const res = setupComponent(
|
||||
instance,
|
||||
true
|
||||
/* isSSR */
|
||||
);
|
||||
const hasAsyncSetup = shared.isPromise(res);
|
||||
let prefetches = instance.sp;
|
||||
if (hasAsyncSetup || prefetches) {
|
||||
const p = Promise.resolve(res).then(() => {
|
||||
if (hasAsyncSetup) prefetches = instance.sp;
|
||||
if (prefetches) {
|
||||
return Promise.all(
|
||||
prefetches.map((prefetch) => prefetch.call(instance.proxy))
|
||||
);
|
||||
}
|
||||
}).catch(shared.NOOP);
|
||||
return p.then(() => renderComponentSubTree(instance, slotScopeId));
|
||||
} else {
|
||||
try {
|
||||
return renderComponentSubTree(instance, slotScopeId);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
function renderComponentSubTree(instance, slotScopeId) {
|
||||
const comp = instance.type;
|
||||
const { getBuffer, push } = createBuffer();
|
||||
if (shared.isFunction(comp)) {
|
||||
let root = renderComponentRoot(instance);
|
||||
if (!comp.props) {
|
||||
for (const key in instance.attrs) {
|
||||
if (key.startsWith(`data-v-`)) {
|
||||
(root.props || (root.props = {}))[key] = ``;
|
||||
}
|
||||
}
|
||||
}
|
||||
renderVNode(push, instance.subTree = root, instance, slotScopeId);
|
||||
} else {
|
||||
if ((!instance.render || instance.render === shared.NOOP) && !instance.ssrRender && !comp.ssrRender && shared.isString(comp.template)) {
|
||||
comp.ssrRender = ssrCompile(comp.template, instance);
|
||||
}
|
||||
const ssrRender = instance.ssrRender || comp.ssrRender;
|
||||
if (ssrRender) {
|
||||
let attrs = instance.inheritAttrs !== false ? instance.attrs : void 0;
|
||||
let hasCloned = false;
|
||||
let cur = instance;
|
||||
while (true) {
|
||||
const scopeId = cur.vnode.scopeId;
|
||||
if (scopeId) {
|
||||
if (!hasCloned) {
|
||||
attrs = { ...attrs };
|
||||
hasCloned = true;
|
||||
}
|
||||
attrs[scopeId] = "";
|
||||
}
|
||||
const parent = cur.parent;
|
||||
if (parent && parent.subTree && parent.subTree === cur.vnode) {
|
||||
cur = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slotScopeId) {
|
||||
if (!hasCloned) attrs = { ...attrs };
|
||||
const slotScopeIdList = slotScopeId.trim().split(" ");
|
||||
for (let i = 0; i < slotScopeIdList.length; i++) {
|
||||
attrs[slotScopeIdList[i]] = "";
|
||||
}
|
||||
}
|
||||
const prev = setCurrentRenderingInstance(instance);
|
||||
try {
|
||||
ssrRender(
|
||||
instance.proxy,
|
||||
push,
|
||||
instance,
|
||||
attrs,
|
||||
// compiler-optimized bindings
|
||||
instance.props,
|
||||
instance.setupState,
|
||||
instance.data,
|
||||
instance.ctx
|
||||
);
|
||||
} catch (err) {
|
||||
Vue.handleError(err, instance, 1);
|
||||
} finally {
|
||||
setCurrentRenderingInstance(prev);
|
||||
}
|
||||
} else if (instance.render && instance.render !== shared.NOOP) {
|
||||
renderVNode(
|
||||
push,
|
||||
instance.subTree = renderComponentRoot(instance),
|
||||
instance,
|
||||
slotScopeId
|
||||
);
|
||||
} else {
|
||||
const componentName = comp.name || comp.__file || `<Anonymous>`;
|
||||
Vue.warn(`Component ${componentName} is missing template or render function.`);
|
||||
push(`<!---->`);
|
||||
}
|
||||
}
|
||||
return getBuffer();
|
||||
}
|
||||
function renderVNode(push, vnode, parentComponent, slotScopeId) {
|
||||
const { type, shapeFlag, children, dirs, props } = vnode;
|
||||
if (dirs) {
|
||||
vnode.props = applySSRDirectives(vnode, props, dirs);
|
||||
}
|
||||
switch (type) {
|
||||
case Vue.Text:
|
||||
push(shared.escapeHtml(children));
|
||||
break;
|
||||
case Vue.Comment:
|
||||
push(
|
||||
children ? `<!--${shared.escapeHtmlComment(children)}-->` : `<!---->`
|
||||
);
|
||||
break;
|
||||
case Vue.Static:
|
||||
push(children);
|
||||
break;
|
||||
case Vue.Fragment:
|
||||
if (vnode.slotScopeIds) {
|
||||
slotScopeId = (slotScopeId ? slotScopeId + " " : "") + vnode.slotScopeIds.join(" ");
|
||||
}
|
||||
push(`<!--[-->`);
|
||||
renderVNodeChildren(
|
||||
push,
|
||||
children,
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
push(`<!--]-->`);
|
||||
break;
|
||||
default:
|
||||
if (shapeFlag & 1) {
|
||||
renderElementVNode(push, vnode, parentComponent, slotScopeId);
|
||||
} else if (shapeFlag & 6) {
|
||||
push(renderComponentVNode(vnode, parentComponent, slotScopeId));
|
||||
} else if (shapeFlag & 64) {
|
||||
renderTeleportVNode(push, vnode, parentComponent, slotScopeId);
|
||||
} else if (shapeFlag & 128) {
|
||||
renderVNode(push, vnode.ssContent, parentComponent, slotScopeId);
|
||||
} else {
|
||||
Vue.warn(
|
||||
"[@vue/server-renderer] Invalid VNode type:",
|
||||
type,
|
||||
`(${typeof type})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
function renderVNodeChildren(push, children, parentComponent, slotScopeId) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId);
|
||||
}
|
||||
}
|
||||
function renderElementVNode(push, vnode, parentComponent, slotScopeId) {
|
||||
const tag = vnode.type;
|
||||
let { props, children, shapeFlag, scopeId } = vnode;
|
||||
let openTag = `<${tag}`;
|
||||
if (props) {
|
||||
openTag += ssrRenderAttrs(props, tag);
|
||||
}
|
||||
const renderedScopeIds = [];
|
||||
const appendScopeId = (id) => {
|
||||
if (id && (!props || !shared.hasOwn(props, id)) && !renderedScopeIds.includes(id)) {
|
||||
openTag += ` ${id}`;
|
||||
renderedScopeIds.push(id);
|
||||
}
|
||||
};
|
||||
if (scopeId) {
|
||||
appendScopeId(scopeId);
|
||||
}
|
||||
let curParent = parentComponent;
|
||||
let curVnode = vnode;
|
||||
while (curParent && curVnode === curParent.subTree) {
|
||||
curVnode = curParent.vnode;
|
||||
if (curVnode.scopeId) {
|
||||
appendScopeId(curVnode.scopeId);
|
||||
}
|
||||
curParent = curParent.parent;
|
||||
}
|
||||
if (slotScopeId) {
|
||||
const slotScopeIdList = slotScopeId.trim().split(" ");
|
||||
for (let i = 0; i < slotScopeIdList.length; i++) {
|
||||
appendScopeId(slotScopeIdList[i]);
|
||||
}
|
||||
}
|
||||
push(openTag + `>`);
|
||||
if (!shared.isVoidTag(tag)) {
|
||||
let hasChildrenOverride = false;
|
||||
if (props) {
|
||||
if (props.innerHTML) {
|
||||
hasChildrenOverride = true;
|
||||
push(props.innerHTML);
|
||||
} else if (props.textContent) {
|
||||
hasChildrenOverride = true;
|
||||
push(shared.escapeHtml(props.textContent));
|
||||
} else if (tag === "textarea" && props.value) {
|
||||
hasChildrenOverride = true;
|
||||
push(shared.escapeHtml(props.value));
|
||||
}
|
||||
}
|
||||
if (!hasChildrenOverride) {
|
||||
if (shapeFlag & 8) {
|
||||
push(shared.escapeHtml(children));
|
||||
} else if (shapeFlag & 16) {
|
||||
renderVNodeChildren(
|
||||
push,
|
||||
children,
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
}
|
||||
}
|
||||
push(`</${tag}>`);
|
||||
}
|
||||
}
|
||||
function applySSRDirectives(vnode, rawProps, dirs) {
|
||||
const toMerge = [];
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
const binding = dirs[i];
|
||||
const {
|
||||
dir: { getSSRProps }
|
||||
} = binding;
|
||||
if (getSSRProps) {
|
||||
const props = getSSRProps(binding, vnode);
|
||||
if (props) toMerge.push(props);
|
||||
}
|
||||
}
|
||||
return Vue.mergeProps(rawProps || {}, ...toMerge);
|
||||
}
|
||||
function renderTeleportVNode(push, vnode, parentComponent, slotScopeId) {
|
||||
const target = vnode.props && vnode.props.to;
|
||||
const disabled = vnode.props && vnode.props.disabled;
|
||||
if (!target) {
|
||||
if (!disabled) {
|
||||
Vue.warn(`[@vue/server-renderer] Teleport is missing target prop.`);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (!shared.isString(target)) {
|
||||
Vue.warn(
|
||||
`[@vue/server-renderer] Teleport target must be a query selector string.`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
ssrRenderTeleport(
|
||||
push,
|
||||
(push2) => {
|
||||
renderVNodeChildren(
|
||||
push2,
|
||||
vnode.children,
|
||||
parentComponent,
|
||||
slotScopeId
|
||||
);
|
||||
},
|
||||
target,
|
||||
disabled || disabled === "",
|
||||
parentComponent
|
||||
);
|
||||
}
|
||||
|
||||
const { isVNode: isVNode$1 } = Vue.ssrUtils;
|
||||
function nestedUnrollBuffer(buffer, parentRet, startIndex) {
|
||||
if (!buffer.hasAsync) {
|
||||
return parentRet + unrollBufferSync$1(buffer);
|
||||
}
|
||||
let ret = parentRet;
|
||||
for (let i = startIndex; i < buffer.length; i += 1) {
|
||||
const item = buffer[i];
|
||||
if (shared.isString(item)) {
|
||||
ret += item;
|
||||
continue;
|
||||
}
|
||||
if (shared.isPromise(item)) {
|
||||
return item.then((nestedItem) => {
|
||||
buffer[i] = nestedItem;
|
||||
return nestedUnrollBuffer(buffer, ret, i);
|
||||
});
|
||||
}
|
||||
const result = nestedUnrollBuffer(item, ret, 0);
|
||||
if (shared.isPromise(result)) {
|
||||
return result.then((nestedItem) => {
|
||||
buffer[i] = nestedItem;
|
||||
return nestedUnrollBuffer(buffer, "", i);
|
||||
});
|
||||
}
|
||||
ret = result;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function unrollBuffer$1(buffer) {
|
||||
return nestedUnrollBuffer(buffer, "", 0);
|
||||
}
|
||||
function unrollBufferSync$1(buffer) {
|
||||
let ret = "";
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let item = buffer[i];
|
||||
if (shared.isString(item)) {
|
||||
ret += item;
|
||||
} else {
|
||||
ret += unrollBufferSync$1(item);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
async function renderToString(input, context = {}) {
|
||||
if (isVNode$1(input)) {
|
||||
return renderToString(Vue.createApp({ render: () => input }), context);
|
||||
}
|
||||
const vnode = Vue.createVNode(input._component, input._props);
|
||||
vnode.appContext = input._context;
|
||||
input.provide(Vue.ssrContextKey, context);
|
||||
const buffer = await renderComponentVNode(vnode);
|
||||
const result = await unrollBuffer$1(buffer);
|
||||
await resolveTeleports(context);
|
||||
if (context.__watcherHandles) {
|
||||
for (const unwatch of context.__watcherHandles) {
|
||||
unwatch();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async function resolveTeleports(context) {
|
||||
if (context.__teleportBuffers) {
|
||||
context.teleports = context.teleports || {};
|
||||
for (const key in context.__teleportBuffers) {
|
||||
context.teleports[key] = await unrollBuffer$1(
|
||||
context.__teleportBuffers[key]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { isVNode } = Vue.ssrUtils;
|
||||
async function unrollBuffer(buffer, stream) {
|
||||
if (buffer.hasAsync) {
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let item = buffer[i];
|
||||
if (shared.isPromise(item)) {
|
||||
item = await item;
|
||||
}
|
||||
if (shared.isString(item)) {
|
||||
stream.push(item);
|
||||
} else {
|
||||
await unrollBuffer(item, stream);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unrollBufferSync(buffer, stream);
|
||||
}
|
||||
}
|
||||
function unrollBufferSync(buffer, stream) {
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let item = buffer[i];
|
||||
if (shared.isString(item)) {
|
||||
stream.push(item);
|
||||
} else {
|
||||
unrollBufferSync(item, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
function renderToSimpleStream(input, context, stream) {
|
||||
if (isVNode(input)) {
|
||||
return renderToSimpleStream(
|
||||
Vue.createApp({ render: () => input }),
|
||||
context,
|
||||
stream
|
||||
);
|
||||
}
|
||||
const vnode = Vue.createVNode(input._component, input._props);
|
||||
vnode.appContext = input._context;
|
||||
input.provide(Vue.ssrContextKey, context);
|
||||
Promise.resolve(renderComponentVNode(vnode)).then((buffer) => unrollBuffer(buffer, stream)).then(() => resolveTeleports(context)).then(() => {
|
||||
if (context.__watcherHandles) {
|
||||
for (const unwatch of context.__watcherHandles) {
|
||||
unwatch();
|
||||
}
|
||||
}
|
||||
}).then(() => stream.push(null)).catch((error) => {
|
||||
stream.destroy(error);
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
function renderToStream(input, context = {}) {
|
||||
console.warn(
|
||||
`[@vue/server-renderer] renderToStream is deprecated - use renderToNodeStream instead.`
|
||||
);
|
||||
return renderToNodeStream(input, context);
|
||||
}
|
||||
function renderToNodeStream(input, context = {}) {
|
||||
const stream = new (require("node:stream")).Readable({ read() {
|
||||
} }) ;
|
||||
if (!stream) {
|
||||
throw new Error(
|
||||
`ESM build of renderToStream() does not support renderToNodeStream(). Use pipeToNodeWritable() with an existing Node.js Writable stream instance instead.`
|
||||
);
|
||||
}
|
||||
return renderToSimpleStream(input, context, stream);
|
||||
}
|
||||
function pipeToNodeWritable(input, context = {}, writable) {
|
||||
renderToSimpleStream(input, context, {
|
||||
push(content) {
|
||||
if (content != null) {
|
||||
writable.write(content);
|
||||
} else {
|
||||
writable.end();
|
||||
}
|
||||
},
|
||||
destroy(err) {
|
||||
writable.destroy(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
function renderToWebStream(input, context = {}) {
|
||||
if (typeof ReadableStream !== "function") {
|
||||
throw new Error(
|
||||
`ReadableStream constructor is not available in the global scope. If the target environment does support web streams, consider using pipeToWebWritable() with an existing WritableStream instance instead.`
|
||||
);
|
||||
}
|
||||
const encoder = new TextEncoder();
|
||||
let cancelled = false;
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
renderToSimpleStream(input, context, {
|
||||
push(content) {
|
||||
if (cancelled) return;
|
||||
if (content != null) {
|
||||
controller.enqueue(encoder.encode(content));
|
||||
} else {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
destroy(err) {
|
||||
controller.error(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
cancelled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function pipeToWebWritable(input, context = {}, writable) {
|
||||
const writer = writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
let hasReady = false;
|
||||
try {
|
||||
hasReady = shared.isPromise(writer.ready);
|
||||
} catch (e) {
|
||||
}
|
||||
renderToSimpleStream(input, context, {
|
||||
async push(content) {
|
||||
if (hasReady) {
|
||||
await writer.ready;
|
||||
}
|
||||
if (content != null) {
|
||||
return writer.write(encoder.encode(content));
|
||||
} else {
|
||||
return writer.close();
|
||||
}
|
||||
},
|
||||
destroy(err) {
|
||||
console.log(err);
|
||||
writer.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Vue.initDirectivesForSSR();
|
||||
|
||||
exports.ssrIncludeBooleanAttr = shared.includeBooleanAttr;
|
||||
exports.pipeToNodeWritable = pipeToNodeWritable;
|
||||
exports.pipeToWebWritable = pipeToWebWritable;
|
||||
exports.renderToNodeStream = renderToNodeStream;
|
||||
exports.renderToSimpleStream = renderToSimpleStream;
|
||||
exports.renderToStream = renderToStream;
|
||||
exports.renderToString = renderToString;
|
||||
exports.renderToWebStream = renderToWebStream;
|
||||
exports.ssrGetDirectiveProps = ssrGetDirectiveProps;
|
||||
exports.ssrGetDynamicModelProps = ssrGetDynamicModelProps;
|
||||
exports.ssrInterpolate = ssrInterpolate;
|
||||
exports.ssrLooseContain = ssrLooseContain;
|
||||
exports.ssrLooseEqual = ssrLooseEqual;
|
||||
exports.ssrRenderAttr = ssrRenderAttr;
|
||||
exports.ssrRenderAttrs = ssrRenderAttrs;
|
||||
exports.ssrRenderClass = ssrRenderClass;
|
||||
exports.ssrRenderComponent = ssrRenderComponent;
|
||||
exports.ssrRenderDynamicAttr = ssrRenderDynamicAttr;
|
||||
exports.ssrRenderDynamicModel = ssrRenderDynamicModel;
|
||||
exports.ssrRenderList = ssrRenderList;
|
||||
exports.ssrRenderSlot = ssrRenderSlot;
|
||||
exports.ssrRenderSlotInner = ssrRenderSlotInner;
|
||||
exports.ssrRenderStyle = ssrRenderStyle;
|
||||
exports.ssrRenderSuspense = ssrRenderSuspense;
|
||||
exports.ssrRenderTeleport = ssrRenderTeleport;
|
||||
exports.ssrRenderVNode = renderVNode;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.43",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
"types": "dist/server-renderer.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/server-renderer.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/server-renderer.cjs.prod.js",
|
||||
"development": "./dist/server-renderer.cjs.js",
|
||||
"default": "./dist/server-renderer.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/server-renderer.esm-bundler.js",
|
||||
"import": "./dist/server-renderer.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"buildOptions": {
|
||||
"name": "VueServerRenderer",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"esm-browser",
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/server-renderer"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/compiler-ssr": "3.5.43",
|
||||
"@vue/runtime-dom": "3.5.43"
|
||||
}
|
||||
}
|
||||
+655
@@ -0,0 +1,655 @@
|
||||
/**
|
||||
* @vue/shared v3.5.43
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
* @license MIT
|
||||
**/
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function makeMap(str) {
|
||||
const map = /* @__PURE__ */ Object.create(null);
|
||||
for (const key of str.split(",")) map[key] = 1;
|
||||
return (val) => val in map;
|
||||
}
|
||||
|
||||
const EMPTY_OBJ = {};
|
||||
const EMPTY_ARR = [];
|
||||
const NOOP = () => {
|
||||
};
|
||||
const NO = () => false;
|
||||
const isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter
|
||||
(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);
|
||||
const isModelListener = (key) => key.startsWith("onUpdate:");
|
||||
const extend = Object.assign;
|
||||
const remove = (arr, el) => {
|
||||
const i = arr.indexOf(el);
|
||||
if (i > -1) {
|
||||
arr.splice(i, 1);
|
||||
}
|
||||
};
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
|
||||
const isArray = Array.isArray;
|
||||
const isMap = (val) => toTypeString(val) === "[object Map]";
|
||||
const isSet = (val) => toTypeString(val) === "[object Set]";
|
||||
const isDate = (val) => toTypeString(val) === "[object Date]";
|
||||
const isRegExp = (val) => toTypeString(val) === "[object RegExp]";
|
||||
const isFunction = (val) => typeof val === "function";
|
||||
const isString = (val) => typeof val === "string";
|
||||
const isSymbol = (val) => typeof val === "symbol";
|
||||
const isObject = (val) => val !== null && typeof val === "object";
|
||||
const isPromise = (val) => {
|
||||
return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch);
|
||||
};
|
||||
const objectToString = Object.prototype.toString;
|
||||
const toTypeString = (value) => objectToString.call(value);
|
||||
const toRawType = (value) => {
|
||||
return toTypeString(value).slice(8, -1);
|
||||
};
|
||||
const isPlainObject = (val) => toTypeString(val) === "[object Object]";
|
||||
const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key;
|
||||
const isReservedProp = /* @__PURE__ */ makeMap(
|
||||
// the leading comma is intentional so empty string "" is also included
|
||||
",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"
|
||||
);
|
||||
const isBuiltInDirective = /* @__PURE__ */ makeMap(
|
||||
"bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"
|
||||
);
|
||||
const cacheStringFunction = (fn) => {
|
||||
const cache = /* @__PURE__ */ Object.create(null);
|
||||
return ((str) => {
|
||||
const hit = cache[str];
|
||||
return hit || (cache[str] = fn(str));
|
||||
});
|
||||
};
|
||||
const camelizeRE = /-\w/g;
|
||||
const camelize = cacheStringFunction(
|
||||
(str) => {
|
||||
return str.replace(camelizeRE, (c) => c.slice(1).toUpperCase());
|
||||
}
|
||||
);
|
||||
const hyphenateRE = /\B([A-Z])/g;
|
||||
const hyphenate = cacheStringFunction(
|
||||
(str) => str.replace(hyphenateRE, "-$1").toLowerCase()
|
||||
);
|
||||
const capitalize = cacheStringFunction((str) => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
});
|
||||
const toHandlerKey = cacheStringFunction(
|
||||
(str) => {
|
||||
const s = str ? `on${capitalize(str)}` : ``;
|
||||
return s;
|
||||
}
|
||||
);
|
||||
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
|
||||
const invokeArrayFns = (fns, ...arg) => {
|
||||
for (let i = 0; i < fns.length; i++) {
|
||||
fns[i](...arg);
|
||||
}
|
||||
};
|
||||
const def = (obj, key, value, writable = false) => {
|
||||
Object.defineProperty(obj, key, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable,
|
||||
value
|
||||
});
|
||||
};
|
||||
const looseToNumber = (val) => {
|
||||
const n = parseFloat(val);
|
||||
return isNaN(n) ? val : n;
|
||||
};
|
||||
const toNumber = (val) => {
|
||||
const n = isString(val) ? Number(val) : NaN;
|
||||
return isNaN(n) ? val : n;
|
||||
};
|
||||
let _globalThis;
|
||||
const getGlobalThis = () => {
|
||||
return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {});
|
||||
};
|
||||
const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;
|
||||
function genPropsAccessExp(name) {
|
||||
return identRE.test(name) ? `__props.${name}` : `__props[${JSON.stringify(name)}]`;
|
||||
}
|
||||
function genCacheKey(source, options) {
|
||||
return source + JSON.stringify(
|
||||
options,
|
||||
(_, val) => typeof val === "function" ? val.toString() : val
|
||||
);
|
||||
}
|
||||
|
||||
const PatchFlags = {
|
||||
"TEXT": 1,
|
||||
"1": "TEXT",
|
||||
"CLASS": 2,
|
||||
"2": "CLASS",
|
||||
"STYLE": 4,
|
||||
"4": "STYLE",
|
||||
"PROPS": 8,
|
||||
"8": "PROPS",
|
||||
"FULL_PROPS": 16,
|
||||
"16": "FULL_PROPS",
|
||||
"NEED_HYDRATION": 32,
|
||||
"32": "NEED_HYDRATION",
|
||||
"STABLE_FRAGMENT": 64,
|
||||
"64": "STABLE_FRAGMENT",
|
||||
"KEYED_FRAGMENT": 128,
|
||||
"128": "KEYED_FRAGMENT",
|
||||
"UNKEYED_FRAGMENT": 256,
|
||||
"256": "UNKEYED_FRAGMENT",
|
||||
"NEED_PATCH": 512,
|
||||
"512": "NEED_PATCH",
|
||||
"DYNAMIC_SLOTS": 1024,
|
||||
"1024": "DYNAMIC_SLOTS",
|
||||
"DEV_ROOT_FRAGMENT": 2048,
|
||||
"2048": "DEV_ROOT_FRAGMENT",
|
||||
"CACHED": -1,
|
||||
"-1": "CACHED",
|
||||
"BAIL": -2,
|
||||
"-2": "BAIL"
|
||||
};
|
||||
const PatchFlagNames = {
|
||||
[1]: `TEXT`,
|
||||
[2]: `CLASS`,
|
||||
[4]: `STYLE`,
|
||||
[8]: `PROPS`,
|
||||
[16]: `FULL_PROPS`,
|
||||
[32]: `NEED_HYDRATION`,
|
||||
[64]: `STABLE_FRAGMENT`,
|
||||
[128]: `KEYED_FRAGMENT`,
|
||||
[256]: `UNKEYED_FRAGMENT`,
|
||||
[512]: `NEED_PATCH`,
|
||||
[1024]: `DYNAMIC_SLOTS`,
|
||||
[2048]: `DEV_ROOT_FRAGMENT`,
|
||||
[-1]: `CACHED`,
|
||||
[-2]: `BAIL`
|
||||
};
|
||||
|
||||
const ShapeFlags = {
|
||||
"ELEMENT": 1,
|
||||
"1": "ELEMENT",
|
||||
"FUNCTIONAL_COMPONENT": 2,
|
||||
"2": "FUNCTIONAL_COMPONENT",
|
||||
"STATEFUL_COMPONENT": 4,
|
||||
"4": "STATEFUL_COMPONENT",
|
||||
"TEXT_CHILDREN": 8,
|
||||
"8": "TEXT_CHILDREN",
|
||||
"ARRAY_CHILDREN": 16,
|
||||
"16": "ARRAY_CHILDREN",
|
||||
"SLOTS_CHILDREN": 32,
|
||||
"32": "SLOTS_CHILDREN",
|
||||
"TELEPORT": 64,
|
||||
"64": "TELEPORT",
|
||||
"SUSPENSE": 128,
|
||||
"128": "SUSPENSE",
|
||||
"COMPONENT_SHOULD_KEEP_ALIVE": 256,
|
||||
"256": "COMPONENT_SHOULD_KEEP_ALIVE",
|
||||
"COMPONENT_KEPT_ALIVE": 512,
|
||||
"512": "COMPONENT_KEPT_ALIVE",
|
||||
"COMPONENT": 6,
|
||||
"6": "COMPONENT"
|
||||
};
|
||||
|
||||
const SlotFlags = {
|
||||
"STABLE": 1,
|
||||
"1": "STABLE",
|
||||
"DYNAMIC": 2,
|
||||
"2": "DYNAMIC",
|
||||
"FORWARDED": 3,
|
||||
"3": "FORWARDED"
|
||||
};
|
||||
const slotFlagsText = {
|
||||
[1]: "STABLE",
|
||||
[2]: "DYNAMIC",
|
||||
[3]: "FORWARDED"
|
||||
};
|
||||
|
||||
const GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol";
|
||||
const isGloballyAllowed = /* @__PURE__ */ makeMap(GLOBALS_ALLOWED);
|
||||
const isGloballyWhitelisted = isGloballyAllowed;
|
||||
|
||||
const range = 2;
|
||||
function generateCodeFrame(source, start = 0, end = source.length) {
|
||||
start = Math.max(0, Math.min(start, source.length));
|
||||
end = Math.max(0, Math.min(end, source.length));
|
||||
if (start > end) return "";
|
||||
let lines = source.split(/(\r?\n)/);
|
||||
const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);
|
||||
lines = lines.filter((_, idx) => idx % 2 === 0);
|
||||
let count = 0;
|
||||
const res = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0);
|
||||
if (count >= start) {
|
||||
for (let j = i - range; j <= i + range || end > count; j++) {
|
||||
if (j < 0 || j >= lines.length) continue;
|
||||
const line = j + 1;
|
||||
res.push(
|
||||
`${line}${" ".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`
|
||||
);
|
||||
const lineLength = lines[j].length;
|
||||
const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0;
|
||||
if (j === i) {
|
||||
const pad = start - (count - (lineLength + newLineSeqLength));
|
||||
const length = Math.max(
|
||||
1,
|
||||
end > count ? lineLength - pad : end - start
|
||||
);
|
||||
res.push(` | ` + " ".repeat(pad) + "^".repeat(length));
|
||||
} else if (j > i) {
|
||||
if (end > count) {
|
||||
const length = Math.max(Math.min(end - count, lineLength), 1);
|
||||
res.push(` | ` + "^".repeat(length));
|
||||
}
|
||||
count += lineLength + newLineSeqLength;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res.join("\n");
|
||||
}
|
||||
|
||||
function normalizeStyle(value) {
|
||||
if (isArray(value)) {
|
||||
const res = {};
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const item = value[i];
|
||||
const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item);
|
||||
if (normalized) {
|
||||
for (const key in normalized) {
|
||||
res[key] = normalized[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} else if (isString(value) || isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
const listDelimiterRE = /;(?![^(]*\))/g;
|
||||
const propertyDelimiterRE = /:([^]+)/;
|
||||
const styleCommentRE = /"(?:[^"\\]|\\[^])*"|'(?:[^'\\]|\\[^])*'|\\[^]|\/\*[^]*?\*\//g;
|
||||
function parseStringStyle(cssText) {
|
||||
const ret = {};
|
||||
cssText.replace(styleCommentRE, (match) => match.startsWith("/*") ? "" : match).split(listDelimiterRE).forEach((item) => {
|
||||
if (item) {
|
||||
const tmp = item.split(propertyDelimiterRE);
|
||||
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
function stringifyStyle(styles) {
|
||||
if (!styles) return "";
|
||||
if (isString(styles)) return styles;
|
||||
let ret = "";
|
||||
for (const key in styles) {
|
||||
const value = styles[key];
|
||||
if (isString(value) || typeof value === "number") {
|
||||
const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key);
|
||||
ret += `${normalizedKey}:${value};`;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function normalizeClass(value) {
|
||||
let res = "";
|
||||
if (isString(value)) {
|
||||
res = value;
|
||||
} else if (isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const normalized = normalizeClass(value[i]);
|
||||
if (normalized) {
|
||||
res += normalized + " ";
|
||||
}
|
||||
}
|
||||
} else if (isObject(value)) {
|
||||
for (const name in value) {
|
||||
if (value[name]) {
|
||||
res += name + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
return res.trim();
|
||||
}
|
||||
function normalizeProps(props) {
|
||||
if (!props) return null;
|
||||
let { class: klass, style } = props;
|
||||
if (klass && !isString(klass)) {
|
||||
props.class = normalizeClass(klass);
|
||||
}
|
||||
if (style) {
|
||||
props.style = normalizeStyle(style);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
const HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot";
|
||||
const SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view";
|
||||
const MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics";
|
||||
const VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr";
|
||||
const isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS);
|
||||
const isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS);
|
||||
const isMathMLTag = /* @__PURE__ */ makeMap(MATH_TAGS);
|
||||
const isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS);
|
||||
|
||||
const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;
|
||||
const isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);
|
||||
const isBooleanAttr = /* @__PURE__ */ makeMap(
|
||||
specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`
|
||||
);
|
||||
function includeBooleanAttr(value) {
|
||||
return !!value || value === "";
|
||||
}
|
||||
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u000d\u0020]/;
|
||||
const attrValidationCache = {};
|
||||
function isSSRSafeAttrName(name) {
|
||||
if (attrValidationCache.hasOwnProperty(name)) {
|
||||
return attrValidationCache[name];
|
||||
}
|
||||
const isUnsafe = unsafeAttrCharRE.test(name);
|
||||
if (isUnsafe) {
|
||||
console.error(`unsafe attribute name: ${name}`);
|
||||
}
|
||||
return attrValidationCache[name] = !isUnsafe;
|
||||
}
|
||||
const propsToAttrMap = {
|
||||
acceptCharset: "accept-charset",
|
||||
className: "class",
|
||||
htmlFor: "for",
|
||||
httpEquiv: "http-equiv"
|
||||
};
|
||||
const isKnownHtmlAttr = /* @__PURE__ */ makeMap(
|
||||
`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`
|
||||
);
|
||||
const isKnownSvgAttr = /* @__PURE__ */ makeMap(
|
||||
`xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
|
||||
);
|
||||
const isKnownMathMLAttr = /* @__PURE__ */ makeMap(
|
||||
`accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns`
|
||||
);
|
||||
function isRenderableAttrValue(value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
const type = typeof value;
|
||||
return type === "string" || type === "number" || type === "boolean";
|
||||
}
|
||||
|
||||
const escapeRE = /["'&<>]/;
|
||||
function escapeHtml(string) {
|
||||
const str = "" + string;
|
||||
const match = escapeRE.exec(str);
|
||||
if (!match) {
|
||||
return str;
|
||||
}
|
||||
let html = "";
|
||||
let escaped;
|
||||
let index;
|
||||
let lastIndex = 0;
|
||||
for (index = match.index; index < str.length; index++) {
|
||||
switch (str.charCodeAt(index)) {
|
||||
case 34:
|
||||
escaped = """;
|
||||
break;
|
||||
case 38:
|
||||
escaped = "&";
|
||||
break;
|
||||
case 39:
|
||||
escaped = "'";
|
||||
break;
|
||||
case 60:
|
||||
escaped = "<";
|
||||
break;
|
||||
case 62:
|
||||
escaped = ">";
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (lastIndex !== index) {
|
||||
html += str.slice(lastIndex, index);
|
||||
}
|
||||
lastIndex = index + 1;
|
||||
html += escaped;
|
||||
}
|
||||
return lastIndex !== index ? html + str.slice(lastIndex, index) : html;
|
||||
}
|
||||
const commentStripRE = /^(?:-?>)+|<!--|-->|--!>|<!-$/g;
|
||||
function escapeHtmlComment(src) {
|
||||
let prev;
|
||||
do {
|
||||
prev = src;
|
||||
src = src.replace(commentStripRE, "");
|
||||
} while (src !== prev);
|
||||
return src;
|
||||
}
|
||||
const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g;
|
||||
function getEscapedCssVarName(key, doubleEscape) {
|
||||
return key.replace(
|
||||
cssVarNameEscapeSymbolsRE,
|
||||
(s) => doubleEscape ? s === '"' ? '\\\\\\"' : `\\\\${s}` : `\\${s}`
|
||||
);
|
||||
}
|
||||
|
||||
function looseCompareArrays(a, b, seen) {
|
||||
if (a.length !== b.length) return false;
|
||||
let equal = true;
|
||||
for (let i = 0; equal && i < a.length; i++) {
|
||||
equal = looseEqual(a[i], b[i], seen);
|
||||
}
|
||||
return equal;
|
||||
}
|
||||
function looseCompareCollections(a, b, seen) {
|
||||
if (a.size !== b.size) return false;
|
||||
const candidates = Array.from(b);
|
||||
const matched = new Uint8Array(candidates.length);
|
||||
for (const item of a) {
|
||||
let index = -1;
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
if (!matched[i] && looseEqual(item, candidates[i], seen)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index < 0) return false;
|
||||
matched[index] = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function looseCompareObjects(a, b, seen) {
|
||||
let aValidType = isMap(a);
|
||||
let bValidType = isMap(b);
|
||||
if (aValidType || bValidType) {
|
||||
return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
|
||||
}
|
||||
aValidType = isSet(a);
|
||||
bValidType = isSet(b);
|
||||
if (aValidType || bValidType) {
|
||||
return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
|
||||
}
|
||||
const aKeysCount = Object.keys(a).length;
|
||||
const bKeysCount = Object.keys(b).length;
|
||||
if (aKeysCount !== bKeysCount) {
|
||||
return false;
|
||||
}
|
||||
for (const key in a) {
|
||||
const aHasKey = a.hasOwnProperty(key);
|
||||
const bHasKey = b.hasOwnProperty(key);
|
||||
if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key], seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return String(a) === String(b);
|
||||
}
|
||||
function looseCompareNested(a, b, seen, compare) {
|
||||
if (!seen) {
|
||||
seen = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
|
||||
}
|
||||
const [seenA, seenB] = seen;
|
||||
if (seenA.has(a) || seenB.has(b)) {
|
||||
return seenA.get(a) === b && seenB.get(b) === a;
|
||||
}
|
||||
seenA.set(a, b);
|
||||
seenB.set(b, a);
|
||||
const equal = compare(a, b, seen);
|
||||
seenA.delete(a);
|
||||
seenB.delete(b);
|
||||
return equal;
|
||||
}
|
||||
function looseEqual(a, b, seen) {
|
||||
if (a === b) return true;
|
||||
let aValidType = isDate(a);
|
||||
let bValidType = isDate(b);
|
||||
if (aValidType || bValidType) {
|
||||
return aValidType && bValidType ? a.getTime() === b.getTime() : false;
|
||||
}
|
||||
aValidType = isSymbol(a);
|
||||
bValidType = isSymbol(b);
|
||||
if (aValidType || bValidType) {
|
||||
return a === b;
|
||||
}
|
||||
aValidType = isArray(a);
|
||||
bValidType = isArray(b);
|
||||
if (aValidType || bValidType) {
|
||||
return aValidType && bValidType ? looseCompareNested(a, b, seen, looseCompareArrays) : false;
|
||||
}
|
||||
aValidType = isObject(a);
|
||||
bValidType = isObject(b);
|
||||
if (aValidType || bValidType) {
|
||||
if (!aValidType || !bValidType) {
|
||||
return false;
|
||||
}
|
||||
return looseCompareNested(a, b, seen, looseCompareObjects);
|
||||
}
|
||||
return String(a) === String(b);
|
||||
}
|
||||
function looseIndexOf(arr, val) {
|
||||
return arr.findIndex((item) => looseEqual(item, val));
|
||||
}
|
||||
|
||||
const isRef = (val) => {
|
||||
return !!(val && val["__v_isRef"] === true);
|
||||
};
|
||||
const toDisplayString = (val) => {
|
||||
return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val);
|
||||
};
|
||||
const replacer = (_key, val) => {
|
||||
if (isRef(val)) {
|
||||
return replacer(_key, val.value);
|
||||
} else if (isMap(val)) {
|
||||
return {
|
||||
[`Map(${val.size})`]: [...val.entries()].reduce(
|
||||
(entries, [key, val2], i) => {
|
||||
entries[stringifySymbol(key, i) + " =>"] = val2;
|
||||
return entries;
|
||||
},
|
||||
{}
|
||||
)
|
||||
};
|
||||
} else if (isSet(val)) {
|
||||
return {
|
||||
[`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v))
|
||||
};
|
||||
} else if (isSymbol(val)) {
|
||||
return stringifySymbol(val);
|
||||
} else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
|
||||
return String(val);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
const stringifySymbol = (v, i = "") => {
|
||||
var _a;
|
||||
return (
|
||||
// Symbol.description in es2019+ so we need to cast here to pass
|
||||
// the lib: es2016 check
|
||||
isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v
|
||||
);
|
||||
};
|
||||
|
||||
function normalizeCssVarValue(value) {
|
||||
if (value == null) {
|
||||
return "initial";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value === "" ? " " : value;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
exports.EMPTY_ARR = EMPTY_ARR;
|
||||
exports.EMPTY_OBJ = EMPTY_OBJ;
|
||||
exports.NO = NO;
|
||||
exports.NOOP = NOOP;
|
||||
exports.PatchFlagNames = PatchFlagNames;
|
||||
exports.PatchFlags = PatchFlags;
|
||||
exports.ShapeFlags = ShapeFlags;
|
||||
exports.SlotFlags = SlotFlags;
|
||||
exports.camelize = camelize;
|
||||
exports.capitalize = capitalize;
|
||||
exports.cssVarNameEscapeSymbolsRE = cssVarNameEscapeSymbolsRE;
|
||||
exports.def = def;
|
||||
exports.escapeHtml = escapeHtml;
|
||||
exports.escapeHtmlComment = escapeHtmlComment;
|
||||
exports.extend = extend;
|
||||
exports.genCacheKey = genCacheKey;
|
||||
exports.genPropsAccessExp = genPropsAccessExp;
|
||||
exports.generateCodeFrame = generateCodeFrame;
|
||||
exports.getEscapedCssVarName = getEscapedCssVarName;
|
||||
exports.getGlobalThis = getGlobalThis;
|
||||
exports.hasChanged = hasChanged;
|
||||
exports.hasOwn = hasOwn;
|
||||
exports.hyphenate = hyphenate;
|
||||
exports.includeBooleanAttr = includeBooleanAttr;
|
||||
exports.invokeArrayFns = invokeArrayFns;
|
||||
exports.isArray = isArray;
|
||||
exports.isBooleanAttr = isBooleanAttr;
|
||||
exports.isBuiltInDirective = isBuiltInDirective;
|
||||
exports.isDate = isDate;
|
||||
exports.isFunction = isFunction;
|
||||
exports.isGloballyAllowed = isGloballyAllowed;
|
||||
exports.isGloballyWhitelisted = isGloballyWhitelisted;
|
||||
exports.isHTMLTag = isHTMLTag;
|
||||
exports.isIntegerKey = isIntegerKey;
|
||||
exports.isKnownHtmlAttr = isKnownHtmlAttr;
|
||||
exports.isKnownMathMLAttr = isKnownMathMLAttr;
|
||||
exports.isKnownSvgAttr = isKnownSvgAttr;
|
||||
exports.isMap = isMap;
|
||||
exports.isMathMLTag = isMathMLTag;
|
||||
exports.isModelListener = isModelListener;
|
||||
exports.isObject = isObject;
|
||||
exports.isOn = isOn;
|
||||
exports.isPlainObject = isPlainObject;
|
||||
exports.isPromise = isPromise;
|
||||
exports.isRegExp = isRegExp;
|
||||
exports.isRenderableAttrValue = isRenderableAttrValue;
|
||||
exports.isReservedProp = isReservedProp;
|
||||
exports.isSSRSafeAttrName = isSSRSafeAttrName;
|
||||
exports.isSVGTag = isSVGTag;
|
||||
exports.isSet = isSet;
|
||||
exports.isSpecialBooleanAttr = isSpecialBooleanAttr;
|
||||
exports.isString = isString;
|
||||
exports.isSymbol = isSymbol;
|
||||
exports.isVoidTag = isVoidTag;
|
||||
exports.looseEqual = looseEqual;
|
||||
exports.looseIndexOf = looseIndexOf;
|
||||
exports.looseToNumber = looseToNumber;
|
||||
exports.makeMap = makeMap;
|
||||
exports.normalizeClass = normalizeClass;
|
||||
exports.normalizeCssVarValue = normalizeCssVarValue;
|
||||
exports.normalizeProps = normalizeProps;
|
||||
exports.normalizeStyle = normalizeStyle;
|
||||
exports.objectToString = objectToString;
|
||||
exports.parseStringStyle = parseStringStyle;
|
||||
exports.propsToAttrMap = propsToAttrMap;
|
||||
exports.remove = remove;
|
||||
exports.slotFlagsText = slotFlagsText;
|
||||
exports.stringifyStyle = stringifyStyle;
|
||||
exports.toDisplayString = toDisplayString;
|
||||
exports.toHandlerKey = toHandlerKey;
|
||||
exports.toNumber = toNumber;
|
||||
exports.toRawType = toRawType;
|
||||
exports.toTypeString = toTypeString;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.5.43",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
"types": "dist/shared.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/shared.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/shared.cjs.prod.js",
|
||||
"development": "./dist/shared.cjs.js",
|
||||
"default": "./dist/shared.cjs.prod.js"
|
||||
},
|
||||
"module": "./dist/shared.esm-bundler.js",
|
||||
"import": "./dist/shared.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git",
|
||||
"directory": "packages/shared"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/shared#readme"
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "devalue",
|
||||
"description": "Gets the job done when JSON.stringify can't",
|
||||
"version": "6.0.2",
|
||||
"repository": "sveltejs/devalue",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/types.d.ts",
|
||||
"import": "./src/index.js",
|
||||
"default": "./src/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"types": "./types/types.d.ts",
|
||||
"engines": {
|
||||
"node": ">=22.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.29.6",
|
||||
"@js-temporal/polyfill": "^0.5.1",
|
||||
"@types/node": "^24.12.0",
|
||||
"prettier": "^3.2.4",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "8.3.0",
|
||||
"vitest": "5.0.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"changeset:version": "changeset version",
|
||||
"changeset:publish": "changeset publish",
|
||||
"build": "tsc",
|
||||
"test": "vitest run --project unit",
|
||||
"test:unit": "vitest run --project unit",
|
||||
"test:watch": "vitest --project unit",
|
||||
"test:types": "tsc -p . --noEmit",
|
||||
"check:format": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"bench": "node --allow-natives-syntax ./benchmarking/run.js",
|
||||
"bench:compare": "node --allow-natives-syntax ./benchmarking/compare/index.js"
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/* Baseline 2025 runtimes */
|
||||
|
||||
/** @type {(array_buffer: ArrayBuffer) => string} */
|
||||
export function encode_native(array_buffer) {
|
||||
return new Uint8Array(array_buffer).toBase64();
|
||||
}
|
||||
|
||||
/** @type {(base64: string) => ArrayBuffer} */
|
||||
export function decode_native(base64) {
|
||||
return Uint8Array.fromBase64(base64).buffer;
|
||||
}
|
||||
|
||||
/* Node-compatible runtimes */
|
||||
|
||||
/** @type {(array_buffer: ArrayBuffer) => string} */
|
||||
export function encode_buffer(array_buffer) {
|
||||
return Buffer.from(array_buffer).toString('base64');
|
||||
}
|
||||
|
||||
/** @type {(base64: string) => ArrayBuffer} */
|
||||
export function decode_buffer(base64) {
|
||||
return Uint8Array.from(Buffer.from(base64, 'base64')).buffer;
|
||||
}
|
||||
|
||||
/* Legacy runtimes */
|
||||
|
||||
/** @type {(array_buffer: ArrayBuffer) => string} */
|
||||
export function encode_legacy(array_buffer) {
|
||||
const array = new Uint8Array(array_buffer);
|
||||
let binary = '';
|
||||
|
||||
// the maximum number of arguments to String.fromCharCode.apply
|
||||
// should be around 0xFFFF in modern engines
|
||||
const chunk_size = 0x8000;
|
||||
for (let i = 0; i < array.length; i += chunk_size) {
|
||||
const chunk = array.subarray(i, i + chunk_size);
|
||||
// `apply` accepts any array-like at runtime; lib types only admit `number[]`.
|
||||
binary += String.fromCharCode.apply(
|
||||
null,
|
||||
/** @type {number[]} */ (/** @type {unknown} */ (chunk))
|
||||
);
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
/** @type {(base64: string) => ArrayBuffer} */
|
||||
export function decode_legacy(base64) {
|
||||
const binary_string = atob(base64);
|
||||
const len = binary_string.length;
|
||||
const array = new Uint8Array(len);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
array[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
|
||||
return array.buffer;
|
||||
}
|
||||
|
||||
const native = typeof Uint8Array.fromBase64 === 'function';
|
||||
const buffer = typeof process === 'object' && process.versions?.node !== undefined;
|
||||
|
||||
export const encode64 = native ? encode_native : buffer ? encode_buffer : encode_legacy;
|
||||
export const decode64 = native ? decode_native : buffer ? decode_buffer : decode_legacy;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export const UNDEFINED = -1;
|
||||
export const HOLE = -2;
|
||||
export const NAN = -3;
|
||||
export const POSITIVE_INFINITY = -4;
|
||||
export const NEGATIVE_INFINITY = -5;
|
||||
export const NEGATIVE_ZERO = -6;
|
||||
export const SPARSE = -7;
|
||||
|
||||
// The largest valid value for a JavaScript array's `length` property,
|
||||
// and the largest valid array index (one less than the max length).
|
||||
export const MAX_ARRAY_LEN = 2 ** 32 - 1;
|
||||
export const MAX_ARRAY_INDEX = MAX_ARRAY_LEN - 1;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export { uneval } from './uneval.js';
|
||||
export { parse, unflatten } from './parse.js';
|
||||
export { stringify, stringifyAsync } from './stringify.js';
|
||||
export {
|
||||
default_stringify_operations as defaultStringifyOperations,
|
||||
default_parse_operations as defaultParseOperations
|
||||
} from './operations.js';
|
||||
export { DevalueError, filter_array_indices as filterArrayIndices } from './utils.js';
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
const SOURCE = Symbol('JavaScriptSource');
|
||||
|
||||
export class JavaScriptSource {
|
||||
/**
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {unknown[]} values
|
||||
*/
|
||||
constructor(strings, values) {
|
||||
this.strings = strings;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: unknown) => string} fn
|
||||
*/
|
||||
render(fn) {
|
||||
const { strings, values } = this;
|
||||
let result = strings[0];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const value = values[i];
|
||||
result += JavaScriptSource.is_fragment(value)
|
||||
? JavaScriptSource.from(value).render(fn)
|
||||
: fn(value);
|
||||
result += strings[i + 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(value: unknown) => void} fn
|
||||
* @param {Set<string>} reserved
|
||||
* @param {Set<readonly string[]>} templates
|
||||
*/
|
||||
visit(fn, reserved, templates) {
|
||||
const { strings, values } = this;
|
||||
// Each template site reuses its string array, but its interpolations can change.
|
||||
if (!templates.has(strings)) {
|
||||
templates.add(strings);
|
||||
for (const string of strings) {
|
||||
// Generated names are ASCII, but literal identifiers can use Unicode escapes.
|
||||
const decoded = string.replace(
|
||||
/\\u(?:([\da-f]{4})|\{([\da-f]+)\})/gi,
|
||||
(escape, hex, code_point) => {
|
||||
const code = parseInt(hex || code_point, 16);
|
||||
return code < 128 ? String.fromCharCode(code) : escape;
|
||||
}
|
||||
);
|
||||
// Conservatively reserve words even in property names, strings and comments.
|
||||
for (const name of decoded.match(/[a-zA-Z_$][\w$]*/g) || []) reserved.add(name);
|
||||
}
|
||||
}
|
||||
for (const value of values) {
|
||||
if (JavaScriptSource.is_fragment(value)) {
|
||||
JavaScriptSource.from(value).visit(fn, reserved, templates);
|
||||
} else {
|
||||
fn(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} value
|
||||
* @returns {value is JavaScriptFragment}
|
||||
*/
|
||||
static is_fragment(value) {
|
||||
return typeof value === 'object' && value !== null && SOURCE in value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JavaScriptFragment} fragment
|
||||
* @returns {JavaScriptSource}
|
||||
*/
|
||||
static from(fragment) {
|
||||
if (!JavaScriptSource.is_fragment(fragment)) {
|
||||
throw new TypeError('Invalid JavaScript fragment');
|
||||
}
|
||||
return fragment[SOURCE];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {...unknown} values
|
||||
* @returns {JavaScriptFragment}
|
||||
*/
|
||||
export function js(strings, ...values) {
|
||||
if (!Array.isArray(strings) || !Array.isArray(strings.raw)) {
|
||||
throw new TypeError(
|
||||
'`js` must be used as a tagged template, but was called as a regular function'
|
||||
);
|
||||
}
|
||||
return { [SOURCE]: new JavaScriptSource(strings, values) };
|
||||
}
|
||||
|
||||
/**
|
||||
* The opaque result of the `js` tag passed to an `uneval` replacer.
|
||||
* @typedef {{ readonly [SOURCE]: JavaScriptSource }} JavaScriptFragment
|
||||
*/
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
import { MAX_ARRAY_INDEX } from './constants.js';
|
||||
import {
|
||||
enumerable_symbols,
|
||||
get_type,
|
||||
is_buffer,
|
||||
is_plain_object,
|
||||
valid_array_indices
|
||||
} from './utils.js';
|
||||
|
||||
/**
|
||||
* Merges caller-provided operation overrides over the defaults. Iterating the
|
||||
* default keys (rather than the override's own keys) means nullish members
|
||||
* fall back to the default, and inherited members — e.g. from a class
|
||||
* instance — are picked up.
|
||||
*
|
||||
* @template {Record<string, any>} T
|
||||
* @param {T} defaults
|
||||
* @param {Partial<T> | undefined} overrides
|
||||
* @returns {T}
|
||||
*/
|
||||
export function merge_operations(defaults, overrides) {
|
||||
if (!overrides) return defaults;
|
||||
|
||||
const merged = /** @type {T} */ ({});
|
||||
|
||||
for (const key of /** @type {(keyof T)[]} */ (Object.keys(defaults))) {
|
||||
merged[key] = overrides[key] ?? defaults[key];
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/** @type {{ kind: 'not-plain' }} */
|
||||
const NOT_PLAIN = /* @__PURE__ */ Object.freeze({ kind: 'not-plain' });
|
||||
|
||||
/** @type {{ kind: 'symbol-keys' }} */
|
||||
const SYMBOL_KEYS = /* @__PURE__ */ Object.freeze({ kind: 'symbol-keys' });
|
||||
|
||||
/**
|
||||
* The default implementations of every introspection/extraction operation
|
||||
* `stringify` performs on the value being serialized. Each one uses native
|
||||
* JavaScript semantics (property access, iteration, prototype methods, etc).
|
||||
*
|
||||
* Pass overrides via the `operations` option of `stringify`/`stringifyAsync`
|
||||
* to customize how values are inspected — e.g. to serialize values without
|
||||
* triggering getters, proxy traps, or patched prototype methods, or to
|
||||
* serialize values that live in a different JavaScript runtime (a `node:vm`
|
||||
* context, a WASM-hosted engine, a remote process) through handle objects.
|
||||
*
|
||||
* The object is frozen — it is shared by every `stringify` call that does
|
||||
* not override a given operation.
|
||||
*
|
||||
*/
|
||||
/** @type {import('./types.js').DefaultStringifyOperations} */
|
||||
const stringify_operations = {
|
||||
identify: (value) => value,
|
||||
|
||||
typeOf: (value) => (value === null ? 'null' : typeof value),
|
||||
|
||||
toPrimitive: (value) => value,
|
||||
|
||||
tagOf: (value) => get_type(value),
|
||||
|
||||
isThenable: (value) => typeof value.then === 'function',
|
||||
|
||||
toPromise: (thenable) => Promise.resolve(thenable),
|
||||
|
||||
unbox: (boxed) => boxed.valueOf(),
|
||||
|
||||
toISOString: (date) => (isNaN(date.getDate()) ? '' : date.toISOString()),
|
||||
|
||||
toStringValue: (value) => value.toString(),
|
||||
|
||||
regExpInfo: (regexp) => ({ source: regexp.source, flags: regexp.flags }),
|
||||
|
||||
valuesOf: (set) => set,
|
||||
|
||||
entriesOf: (map) => map,
|
||||
|
||||
viewInfo: (view) => {
|
||||
// Node Buffers may share a pool containing unrelated, sensitive data.
|
||||
// Copy only the visible bytes, without using Buffer's pooling or slice.
|
||||
if (is_buffer(view)) view = new Uint8Array(view);
|
||||
|
||||
return {
|
||||
buffer: view.buffer,
|
||||
byteOffset: view.byteOffset,
|
||||
byteLength: view.byteLength,
|
||||
length: view.length,
|
||||
bufferByteLength: view.buffer.byteLength
|
||||
};
|
||||
},
|
||||
|
||||
toArrayBuffer: (buffer) => buffer,
|
||||
|
||||
lengthOf: (array) => array.length,
|
||||
|
||||
hasOwn: (value, key) => Object.hasOwn(value, key),
|
||||
|
||||
indicesOf: (array) => valid_array_indices(array),
|
||||
|
||||
shapeOf: (value) => {
|
||||
if (!is_plain_object(value)) return NOT_PLAIN;
|
||||
if (enumerable_symbols(value).length > 0) return SYMBOL_KEYS;
|
||||
|
||||
return {
|
||||
kind: Object.getPrototypeOf(value) === null ? 'null-proto' : 'plain',
|
||||
keys: Object.keys(value)
|
||||
};
|
||||
},
|
||||
|
||||
get: (value, key) => value[key]
|
||||
};
|
||||
|
||||
export const default_stringify_operations = /* @__PURE__ */ Object.freeze(stringify_operations);
|
||||
|
||||
const array_buffer_byte_length = /** @type {() => number} */ (
|
||||
Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength')?.get
|
||||
);
|
||||
|
||||
const shared_array_buffer_byte_length =
|
||||
typeof SharedArrayBuffer === 'undefined'
|
||||
? undefined
|
||||
: Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'byteLength')?.get;
|
||||
|
||||
/**
|
||||
* The default implementations of every construction operation `parse` and
|
||||
* `unflatten` perform while reviving a value. Each one uses native
|
||||
* JavaScript semantics (built-in constructors, property assignment, etc).
|
||||
*
|
||||
* Pass overrides via the `operations` option of `parse`/`unflatten` to
|
||||
* customize how values are built — e.g. to construct them from the
|
||||
* intrinsics of a different realm (a `node:vm` context), or to build up
|
||||
* values inside another JavaScript runtime (a WASM-hosted engine, a remote
|
||||
* process) through handle objects.
|
||||
*
|
||||
* The object is frozen — it is shared by every `parse` call that does not
|
||||
* override a given operation.
|
||||
*
|
||||
*/
|
||||
/** @type {import('./types.js').DefaultParseOperations} */
|
||||
const parse_operations = {
|
||||
fromPrimitive: (primitive) => primitive,
|
||||
|
||||
fromISOString: (iso) => new Date(iso),
|
||||
|
||||
fromStringValue: (tag, text) => {
|
||||
if (tag === 'URL') return new URL(text);
|
||||
if (tag === 'URLSearchParams') return new URLSearchParams(text);
|
||||
// 'Temporal.Instant', 'Temporal.PlainDate', ...
|
||||
// @ts-expect-error TS doesn't know about Temporal yet
|
||||
return Temporal[tag.slice(9)].from(text);
|
||||
},
|
||||
|
||||
fromArrayBuffer: (buffer) => buffer,
|
||||
|
||||
fromRegExpInfo: (source, flags) => new RegExp(source, flags),
|
||||
|
||||
fromViewInfo: (tag, buffer, byteOffset, length) => {
|
||||
// A reviver can replace an ArrayBuffer with a length or array-like value,
|
||||
// which a typed array constructor would use to allocate a new buffer.
|
||||
// The native getters check internal slots, so they work across realms
|
||||
// and cannot be fooled by a forged prototype or Symbol.toStringTag.
|
||||
try {
|
||||
array_buffer_byte_length.call(buffer);
|
||||
} catch (error) {
|
||||
if (!shared_array_buffer_byte_length) throw error;
|
||||
shared_array_buffer_byte_length.call(buffer);
|
||||
}
|
||||
|
||||
const Constructor = /** @type {any} */ (globalThis)[tag];
|
||||
return byteOffset !== undefined
|
||||
? new Constructor(buffer, byteOffset, length)
|
||||
: new Constructor(buffer);
|
||||
},
|
||||
|
||||
box: (value) => Object(value),
|
||||
|
||||
createArray: (length) => new Array(length),
|
||||
|
||||
createSparseArray: (length) => {
|
||||
/** @type {any[]} */
|
||||
const array = [];
|
||||
|
||||
// Setting `array.length = length` (or equivalently calling
|
||||
// `new Array(length)`) on an untrusted length is a DoS vector: V8
|
||||
// eagerly allocates a contiguous backing store for array lengths below
|
||||
// ~10^8, so a small payload with a huge declared length can force
|
||||
// arbitrary memory allocation. Touching the largest-possible index
|
||||
// first forces V8 into dictionary-elements mode, where `length` is
|
||||
// just a number and no contiguous allocation occurs.
|
||||
array[MAX_ARRAY_INDEX] = undefined;
|
||||
delete array[MAX_ARRAY_INDEX];
|
||||
array.length = length;
|
||||
|
||||
return array;
|
||||
},
|
||||
|
||||
createObject: () => ({}),
|
||||
|
||||
createNullPrototypeObject: () => Object.create(null),
|
||||
|
||||
createSet: () => new Set(),
|
||||
|
||||
createMap: () => new Map(),
|
||||
|
||||
set: (target, key, value) => {
|
||||
target[key] = value;
|
||||
},
|
||||
|
||||
addValue: (set, value) => {
|
||||
set.add(value);
|
||||
},
|
||||
|
||||
addEntry: (map, key, value) => {
|
||||
map.set(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
export const default_parse_operations = /* @__PURE__ */ Object.freeze(parse_operations);
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
import { decode64 } from './base64.js';
|
||||
import {
|
||||
HOLE,
|
||||
NAN,
|
||||
NEGATIVE_INFINITY,
|
||||
NEGATIVE_ZERO,
|
||||
POSITIVE_INFINITY,
|
||||
SPARSE,
|
||||
UNDEFINED
|
||||
} from './constants.js';
|
||||
import { default_parse_operations, merge_operations } from './operations.js';
|
||||
import { is_valid_array_index, is_valid_array_len } from './utils.js';
|
||||
|
||||
function invalid() {
|
||||
throw new Error('Invalid input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Revive a value serialized with `devalue.stringify`
|
||||
* @param {string} serialized
|
||||
* @param {Record<string, (value: any) => any>} [revivers]
|
||||
* @param {import('./types.js').ParseOptions} [options]
|
||||
*/
|
||||
export function parse(serialized, revivers, options) {
|
||||
return unflatten(JSON.parse(serialized), revivers, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revive a value flattened with `devalue.stringify`
|
||||
* @param {number | any[]} parsed
|
||||
* @param {Record<string, (value: any) => any>} [revivers]
|
||||
* @param {import('./types.js').ParseOptions} [options]
|
||||
*/
|
||||
export function unflatten(parsed, revivers, options) {
|
||||
/** @type {import('./types.js').ParseOperations} */
|
||||
const ops = merge_operations(default_parse_operations, options?.operations);
|
||||
|
||||
if (typeof parsed === 'number') return hydrate(parsed, true);
|
||||
|
||||
if (!Array.isArray(parsed) || parsed.length === 0) {
|
||||
invalid();
|
||||
}
|
||||
|
||||
const values = /** @type {any[]} */ (parsed);
|
||||
|
||||
const hydrated = Array(values.length);
|
||||
|
||||
/**
|
||||
* A set of values currently being hydrated with custom revivers,
|
||||
* used to detect invalid cyclical dependencies
|
||||
* @type {Set<number> | null}
|
||||
*/
|
||||
let hydrating = null;
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {any}
|
||||
*/
|
||||
function get_raw(index) {
|
||||
if (!is_valid_array_index(index) || index >= values.length) {
|
||||
invalid();
|
||||
}
|
||||
|
||||
return values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {any}
|
||||
*/
|
||||
function hydrate(index, standalone = false) {
|
||||
if (index === UNDEFINED) return ops.fromPrimitive(undefined);
|
||||
if (index === NAN) return ops.fromPrimitive(NaN);
|
||||
if (index === POSITIVE_INFINITY) return ops.fromPrimitive(Infinity);
|
||||
if (index === NEGATIVE_INFINITY) return ops.fromPrimitive(-Infinity);
|
||||
if (index === NEGATIVE_ZERO) return ops.fromPrimitive(-0);
|
||||
|
||||
if (standalone || typeof index !== 'number') {
|
||||
invalid();
|
||||
}
|
||||
|
||||
if (index in hydrated) return hydrated[index];
|
||||
|
||||
if (index >= values.length) {
|
||||
invalid();
|
||||
}
|
||||
|
||||
const value = values[index];
|
||||
|
||||
if (!value || typeof value !== 'object') {
|
||||
hydrated[index] = ops.fromPrimitive(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
if (typeof value[0] === 'string') {
|
||||
const type = value[0];
|
||||
|
||||
const reviver = revivers && Object.hasOwn(revivers, type) ? revivers[type] : undefined;
|
||||
|
||||
if (reviver) {
|
||||
let i = value[1];
|
||||
if (typeof i !== 'number') {
|
||||
// if it's not a number, it was serialized by a builtin reviver
|
||||
// so we need to munge it into the format expected by a custom reviver
|
||||
i = values.push(value[1]) - 1;
|
||||
}
|
||||
|
||||
// If the payload is already hydrated, its recursion has already
|
||||
// terminated (e.g. a self-referential object cached itself before
|
||||
// following its own back-reference), so revive it directly. Falling
|
||||
// through to the `hydrating` guard here would wrongly reject a valid
|
||||
// cycle. An actually infinite payload (e.g. `[["Custom", 0]]`) is never
|
||||
// cached, so it still hits the guard below.
|
||||
if (Object.hasOwn(hydrated, i)) {
|
||||
return (hydrated[index] = reviver(hydrated[i]));
|
||||
}
|
||||
|
||||
hydrating ??= new Set();
|
||||
|
||||
if (hydrating.has(i)) {
|
||||
throw new Error('Invalid circular reference');
|
||||
}
|
||||
|
||||
hydrating.add(i);
|
||||
hydrated[index] = reviver(hydrate(i));
|
||||
hydrating.delete(i);
|
||||
|
||||
return hydrated[index];
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'Date':
|
||||
hydrated[index] = ops.fromISOString(value[1]);
|
||||
break;
|
||||
|
||||
case 'Set':
|
||||
const set = ops.createSet();
|
||||
hydrated[index] = set;
|
||||
for (let i = 1; i < value.length; i += 1) {
|
||||
ops.addValue(set, hydrate(value[i]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Map':
|
||||
const map = ops.createMap();
|
||||
hydrated[index] = map;
|
||||
for (let i = 1; i < value.length; i += 2) {
|
||||
ops.addEntry(map, hydrate(value[i]), hydrate(value[i + 1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'RegExp':
|
||||
hydrated[index] = ops.fromRegExpInfo(value[1], value[2]);
|
||||
break;
|
||||
|
||||
case 'Object': {
|
||||
const wrapped_index = value[1];
|
||||
|
||||
// Only a primitive can be boxed. The negative sentinels below are the
|
||||
// ones `stringify` emits for boxed numbers; every other sentinel
|
||||
// (`UNDEFINED`, `HOLE`, `SPARSE`) would box `undefined` into a plain
|
||||
// `{}`, which is not a value `stringify` can produce.
|
||||
const is_boxable_sentinel =
|
||||
wrapped_index === NAN ||
|
||||
wrapped_index === POSITIVE_INFINITY ||
|
||||
wrapped_index === NEGATIVE_INFINITY ||
|
||||
wrapped_index === NEGATIVE_ZERO;
|
||||
|
||||
if (!is_boxable_sentinel) {
|
||||
const wrapped = get_raw(wrapped_index);
|
||||
|
||||
const is_bigint = Array.isArray(wrapped) && wrapped[0] === 'BigInt';
|
||||
|
||||
if ((wrapped === undefined || typeof wrapped === 'object') && !is_bigint) {
|
||||
// avoid infinite recusion in case of malformed input
|
||||
invalid();
|
||||
}
|
||||
}
|
||||
|
||||
hydrated[index] = ops.box(hydrate(wrapped_index));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'BigInt':
|
||||
hydrated[index] = ops.fromPrimitive(BigInt(value[1]));
|
||||
break;
|
||||
|
||||
case 'null':
|
||||
const obj = ops.createNullPrototypeObject();
|
||||
hydrated[index] = obj;
|
||||
for (let i = 1; i < value.length; i += 2) {
|
||||
const key = value[i];
|
||||
if (typeof key !== 'string') {
|
||||
throw new Error('Cannot parse an object with a non-string key');
|
||||
}
|
||||
|
||||
if (key === '__proto__') {
|
||||
throw new Error('Cannot parse an object with a `__proto__` property');
|
||||
}
|
||||
|
||||
ops.set(obj, key, hydrate(value[i + 1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Int8Array':
|
||||
case 'Uint8Array':
|
||||
case 'Uint8ClampedArray':
|
||||
case 'Int16Array':
|
||||
case 'Uint16Array':
|
||||
case 'Float16Array':
|
||||
case 'Int32Array':
|
||||
case 'Uint32Array':
|
||||
case 'Float32Array':
|
||||
case 'Float64Array':
|
||||
case 'BigInt64Array':
|
||||
case 'BigUint64Array':
|
||||
case 'DataView': {
|
||||
const buffer_index = value[1];
|
||||
|
||||
const raw = get_raw(buffer_index);
|
||||
|
||||
if (!Array.isArray(raw) || raw[0] !== 'ArrayBuffer') {
|
||||
// without this, if we receive malformed input we could
|
||||
// end up trying to hydrate in a circle or allocate
|
||||
// huge amounts of memory when we call `new TypedArrayConstructor(buffer)`
|
||||
invalid();
|
||||
}
|
||||
|
||||
const buffer = hydrate(buffer_index);
|
||||
|
||||
hydrated[index] = ops.fromViewInfo(type, buffer, value[2], value[3]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrayBuffer': {
|
||||
const base64 = value[1];
|
||||
if (typeof base64 !== 'string') {
|
||||
throw new Error('Invalid ArrayBuffer encoding');
|
||||
}
|
||||
hydrated[index] = ops.fromArrayBuffer(decode64(base64));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'URL':
|
||||
case 'URLSearchParams':
|
||||
case 'Temporal.Duration':
|
||||
case 'Temporal.Instant':
|
||||
case 'Temporal.PlainDate':
|
||||
case 'Temporal.PlainTime':
|
||||
case 'Temporal.PlainDateTime':
|
||||
case 'Temporal.PlainMonthDay':
|
||||
case 'Temporal.PlainYearMonth':
|
||||
case 'Temporal.ZonedDateTime': {
|
||||
// the same tags `toStringValue` serializes on the stringify side
|
||||
hydrated[index] = ops.fromStringValue(type, value[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown type ${type}`);
|
||||
}
|
||||
} else if (value[0] === SPARSE) {
|
||||
// Sparse array encoding: [SPARSE, length, idx, val, idx, val, ...]
|
||||
const len = value[1];
|
||||
|
||||
if (!is_valid_array_len(len)) {
|
||||
invalid();
|
||||
}
|
||||
|
||||
// `len` comes from the input rather than being bounded by it, so
|
||||
// `createSparseArray` is responsible for not allocating storage
|
||||
// proportional to it.
|
||||
const array = ops.createSparseArray(len);
|
||||
hydrated[index] = array;
|
||||
|
||||
for (let i = 2; i < value.length; i += 2) {
|
||||
const idx = value[i];
|
||||
|
||||
if (!is_valid_array_index(idx) || idx >= len) {
|
||||
invalid();
|
||||
}
|
||||
|
||||
ops.set(array, idx, hydrate(value[i + 1]));
|
||||
}
|
||||
} else {
|
||||
const array = ops.createArray(value.length);
|
||||
hydrated[index] = array;
|
||||
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
const n = value[i];
|
||||
if (n === HOLE) continue;
|
||||
|
||||
ops.set(array, i, hydrate(n));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const object = ops.createObject();
|
||||
hydrated[index] = object;
|
||||
|
||||
for (const key of Object.keys(value)) {
|
||||
if (key === '__proto__') {
|
||||
throw new Error('Cannot parse an object with a `__proto__` property');
|
||||
}
|
||||
|
||||
ops.set(object, key, hydrate(value[key]));
|
||||
}
|
||||
}
|
||||
|
||||
return hydrated[index];
|
||||
}
|
||||
|
||||
return hydrate(0);
|
||||
}
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
import { DevalueError, MAP_KEY, format_path, quote_key, stringify_string } from './utils.js';
|
||||
import {
|
||||
HOLE,
|
||||
NAN,
|
||||
NEGATIVE_INFINITY,
|
||||
NEGATIVE_ZERO,
|
||||
POSITIVE_INFINITY,
|
||||
SPARSE,
|
||||
UNDEFINED
|
||||
} from './constants.js';
|
||||
import { encode64 } from './base64.js';
|
||||
import { default_stringify_operations, merge_operations } from './operations.js';
|
||||
|
||||
/**
|
||||
* Turn a value into a JSON string that can be parsed with `devalue.parse`
|
||||
* @param {any} value
|
||||
* @param {Record<string, (value: any) => any>} [reducers]
|
||||
* @param {import('./types.js').StringifyOptions} [options]
|
||||
*/
|
||||
export function stringify(value, reducers, options) {
|
||||
const stringified = run(false, value, reducers, options);
|
||||
return typeof stringified === 'string' ? stringified : `[${stringified.join(',')}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a value into a JSON string that can be parsed with `devalue.parse`
|
||||
* @param {any} value
|
||||
* @param {Record<string, (value: any) => any>} [reducers]
|
||||
* @param {import('./types.js').StringifyOptions} [options]
|
||||
*/
|
||||
export async function stringifyAsync(value, reducers, options) {
|
||||
const stringified = run(true, value, reducers, options);
|
||||
|
||||
if (typeof stringified === 'string') {
|
||||
return stringified;
|
||||
}
|
||||
|
||||
let out = '[';
|
||||
|
||||
for (let i = 0; i < stringified.length; i += 1) {
|
||||
let value = stringified[i];
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
await value;
|
||||
value = stringified[i];
|
||||
|
||||
if (i === 0 && value < 0) {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
out += value;
|
||||
|
||||
if (i < stringified.length - 1) {
|
||||
out += ',';
|
||||
}
|
||||
}
|
||||
|
||||
out += ']';
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} async
|
||||
* @param {any} value
|
||||
* @param {Record<string, (value: any) => any>} [reducers]
|
||||
* @param {import('./types.js').StringifyOptions} [options]
|
||||
*/
|
||||
function run(async, value, reducers, options) {
|
||||
const ops = merge_operations(default_stringify_operations, options?.operations);
|
||||
|
||||
/** @type {any[]} */
|
||||
const stringified = [];
|
||||
|
||||
/** @type {Map<any, number>} */
|
||||
const indexes = new Map();
|
||||
|
||||
/** @type {Array<{ key: string, fn: (value: any) => any }>} */
|
||||
const custom = [];
|
||||
if (reducers) {
|
||||
for (const key of Object.getOwnPropertyNames(reducers)) {
|
||||
custom.push({ key, fn: reducers[key] });
|
||||
}
|
||||
}
|
||||
|
||||
// the path to the value being serialized, recorded as-is and only
|
||||
// formatted by `error` — see `format_path`
|
||||
/** @type {any[]} */
|
||||
let keys = [];
|
||||
|
||||
let p = 0;
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {any} thing
|
||||
* @returns {never}
|
||||
*/
|
||||
function error(message, thing) {
|
||||
const path = format_path(keys, (key) => {
|
||||
const key_type = ops.typeOf(key);
|
||||
const key_is_primitive =
|
||||
key_type !== 'object' && key_type !== 'function' && key_type !== 'symbol';
|
||||
return key_is_primitive ? stringify_primitive(ops.toPrimitive(key)) : '...';
|
||||
});
|
||||
|
||||
throw new DevalueError(message, path, thing, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} thing
|
||||
* @param {number} [index]
|
||||
*/
|
||||
function flatten(thing, index) {
|
||||
const type = ops.typeOf(thing);
|
||||
|
||||
if (type === 'undefined') return UNDEFINED;
|
||||
|
||||
/** @type {number | undefined} */
|
||||
let number;
|
||||
|
||||
// `ops.toPrimitive` is the boundary between the value being serialized and
|
||||
// plain host JavaScript: everything below operates on the extracted host
|
||||
// primitive, so native comparisons and arithmetic are correct there.
|
||||
if (type === 'number') {
|
||||
number = /** @type {number} */ (ops.toPrimitive(thing));
|
||||
if (Number.isNaN(number)) return NAN;
|
||||
if (number === Infinity) return POSITIVE_INFINITY;
|
||||
if (number === -Infinity) return NEGATIVE_INFINITY;
|
||||
if (number === 0 && 1 / number < 0) return NEGATIVE_ZERO;
|
||||
}
|
||||
|
||||
const id = ops.identify(thing);
|
||||
|
||||
if (indexes.has(id)) return /** @type {number} */ (indexes.get(id));
|
||||
|
||||
index ??= p++;
|
||||
indexes.set(id, index);
|
||||
|
||||
for (const { key, fn } of custom) {
|
||||
const value = fn(thing);
|
||||
if (value) {
|
||||
stringified[index] = `["${key}",${flatten(value)}]`;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'function') {
|
||||
error(`Cannot stringify a function`, thing);
|
||||
} else if (type === 'symbol') {
|
||||
error(`Cannot stringify a Symbol primitive`, thing);
|
||||
}
|
||||
|
||||
/** @type {string | Promise<any>} */
|
||||
let str = '';
|
||||
|
||||
if (type !== 'object') {
|
||||
str = stringify_primitive(type === 'number' ? number : ops.toPrimitive(thing));
|
||||
} else if (ops.isThenable(thing)) {
|
||||
if (!async) {
|
||||
error(`Cannot stringify a Promise or thenable — use stringifyAsync instead`, thing);
|
||||
}
|
||||
|
||||
// the callback runs after the synchronous walk has unwound `keys`,
|
||||
// so the path to the thenable has to be captured and reinstated
|
||||
const prev_keys = keys.slice();
|
||||
|
||||
str = ops.toPromise(thing).then((value) => {
|
||||
keys = prev_keys;
|
||||
|
||||
const i = flatten(value, index);
|
||||
if (i < 0) stringified[index] = i;
|
||||
});
|
||||
|
||||
// Promises are awaited sequentially, and traversal may throw before
|
||||
// reaching them. Handle rejections immediately, but keep the original
|
||||
// promise so awaiting it still propagates the error to the caller.
|
||||
str.catch(() => {});
|
||||
} else {
|
||||
const tag = ops.tagOf(thing);
|
||||
|
||||
switch (tag) {
|
||||
case 'Number':
|
||||
case 'String':
|
||||
case 'Boolean':
|
||||
case 'BigInt':
|
||||
str = `["Object",${flatten(ops.unbox(thing))}]`;
|
||||
break;
|
||||
|
||||
case 'Date':
|
||||
str = `["Date","${ops.toISOString(thing)}"]`;
|
||||
break;
|
||||
|
||||
case 'URL':
|
||||
str = `["URL",${stringify_string(ops.toStringValue(thing))}]`;
|
||||
break;
|
||||
|
||||
case 'URLSearchParams':
|
||||
str = `["URLSearchParams",${stringify_string(ops.toStringValue(thing))}]`;
|
||||
break;
|
||||
|
||||
case 'RegExp':
|
||||
const { source, flags } = ops.regExpInfo(thing);
|
||||
str = flags
|
||||
? `["RegExp",${stringify_string(source)},"${flags}"]`
|
||||
: `["RegExp",${stringify_string(source)}]`;
|
||||
break;
|
||||
|
||||
case 'Array': {
|
||||
// For dense arrays (no holes), we iterate normally.
|
||||
// When we encounter the first hole, we call Object.keys
|
||||
// to determine the sparseness, then decide between:
|
||||
// - HOLE encoding: [-2, val, -2, ...] (default)
|
||||
// - Sparse encoding: [-7, length, idx, val, ...] (for very sparse arrays)
|
||||
// Only the sparse path avoids iterating every slot, which
|
||||
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
|
||||
let mostly_dense = false;
|
||||
|
||||
const length = ops.lengthOf(thing);
|
||||
|
||||
str = '[';
|
||||
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
if (i > 0) str += ',';
|
||||
|
||||
if (ops.hasOwn(thing, i)) {
|
||||
keys.push(i);
|
||||
str += flatten(ops.get(thing, i));
|
||||
keys.pop();
|
||||
} else if (mostly_dense) {
|
||||
// Use dense encoding. The heuristic guarantees the
|
||||
// array is only mildly sparse, so iterating over every
|
||||
// slot is fine.
|
||||
str += HOLE;
|
||||
} else {
|
||||
// Decide between HOLE encoding and sparse encoding.
|
||||
//
|
||||
// HOLE encoding: each hole is serialized as the HOLE
|
||||
// sentinel (-2). For example, [, "a", ,] becomes
|
||||
// [-2, 0, -2]. Each hole costs 3 chars ("-2" + comma).
|
||||
//
|
||||
// Sparse encoding: lists only populated indices.
|
||||
// For example, [, "a", ,] becomes [-7, 3, 1, 0] — the
|
||||
// -7 sentinel, the array length (3), then index-value
|
||||
// pairs. This avoids paying per-hole, but each element
|
||||
// costs extra chars to write its index.
|
||||
//
|
||||
// The values are the same size either way, so the
|
||||
// choice comes down to structural overhead:
|
||||
//
|
||||
// HOLE overhead:
|
||||
// 3 chars per hole ("-2" + comma)
|
||||
// = (L - P) * 3
|
||||
//
|
||||
// Sparse overhead:
|
||||
// "-7," — 3 chars (sparse sentinel + comma)
|
||||
// + length + "," — (d + 1) chars (array length + comma)
|
||||
// + per element: index + "," — (d + 1) chars
|
||||
// = (4 + d) + P * (d + 1)
|
||||
//
|
||||
// where L is the array length, P is the number of
|
||||
// populated elements, and d is the number of digits
|
||||
// in L (an upper bound on the digits in any index).
|
||||
//
|
||||
// Sparse encoding is cheaper when:
|
||||
// (4 + d) + P * (d + 1) < (L - P) * 3
|
||||
const populated_keys = ops.indicesOf(thing);
|
||||
const population = populated_keys.length;
|
||||
const d = String(length).length;
|
||||
|
||||
const hole_cost = (length - population) * 3;
|
||||
const sparse_cost = 4 + d + population * (d + 1);
|
||||
|
||||
if (hole_cost > sparse_cost) {
|
||||
str = '[' + SPARSE + ',' + length;
|
||||
for (let j = 0; j < populated_keys.length; j++) {
|
||||
const key = populated_keys[j];
|
||||
keys.push(+key);
|
||||
str += ',' + key + ',' + flatten(ops.get(thing, key));
|
||||
keys.pop();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
mostly_dense = true;
|
||||
str += HOLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str += ']';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Set':
|
||||
str = '["Set"';
|
||||
|
||||
for (const value of ops.valuesOf(thing)) {
|
||||
str += `,${flatten(value)}`;
|
||||
}
|
||||
|
||||
str += ']';
|
||||
break;
|
||||
|
||||
case 'Map':
|
||||
str = '["Map"';
|
||||
|
||||
for (const [key, value] of ops.entriesOf(thing)) {
|
||||
keys.push(MAP_KEY, key);
|
||||
str += `,${flatten(key)},${flatten(value)}`;
|
||||
keys.pop();
|
||||
keys.pop();
|
||||
}
|
||||
|
||||
str += ']';
|
||||
break;
|
||||
|
||||
case 'Int8Array':
|
||||
case 'Uint8Array':
|
||||
case 'Uint8ClampedArray':
|
||||
case 'Int16Array':
|
||||
case 'Uint16Array':
|
||||
case 'Float16Array':
|
||||
case 'Int32Array':
|
||||
case 'Uint32Array':
|
||||
case 'Float32Array':
|
||||
case 'Float64Array':
|
||||
case 'BigInt64Array':
|
||||
case 'BigUint64Array': {
|
||||
const info = ops.viewInfo(thing);
|
||||
str = '["' + tag + '",' + flatten(info.buffer);
|
||||
|
||||
// handle subarrays
|
||||
if (info.byteLength !== info.bufferByteLength) {
|
||||
str += `,${info.byteOffset},${info.length}`;
|
||||
}
|
||||
|
||||
str += ']';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'DataView': {
|
||||
const info = ops.viewInfo(thing);
|
||||
str = '["' + tag + '",' + flatten(info.buffer);
|
||||
|
||||
if (info.byteLength !== info.bufferByteLength) {
|
||||
str += `,${info.byteOffset},${info.byteLength}`;
|
||||
}
|
||||
|
||||
str += ']';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrayBuffer': {
|
||||
const base64 = encode64(ops.toArrayBuffer(thing));
|
||||
|
||||
str = `["ArrayBuffer","${base64}"]`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Temporal.Duration':
|
||||
case 'Temporal.Instant':
|
||||
case 'Temporal.PlainDate':
|
||||
case 'Temporal.PlainTime':
|
||||
case 'Temporal.PlainDateTime':
|
||||
case 'Temporal.PlainMonthDay':
|
||||
case 'Temporal.PlainYearMonth':
|
||||
case 'Temporal.ZonedDateTime':
|
||||
str = `["${tag}",${stringify_string(ops.toStringValue(thing))}]`;
|
||||
break;
|
||||
|
||||
default: {
|
||||
const shape = ops.shapeOf(thing);
|
||||
|
||||
if (shape.kind === 'not-plain') {
|
||||
error(`Cannot stringify arbitrary non-POJOs`, thing);
|
||||
}
|
||||
|
||||
if (shape.kind === 'symbol-keys') {
|
||||
error(`Cannot stringify POJOs with symbolic keys`, thing);
|
||||
}
|
||||
|
||||
if (shape.kind === 'null-proto') {
|
||||
str = '["null"';
|
||||
for (const key of shape.keys) {
|
||||
if (key === '__proto__') {
|
||||
error(`Cannot stringify objects with __proto__ keys`, thing);
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
str += `,${quote_key(key)},${flatten(ops.get(thing, key))}`;
|
||||
keys.pop();
|
||||
}
|
||||
str += ']';
|
||||
} else {
|
||||
str = '{';
|
||||
let started = false;
|
||||
for (const key of shape.keys) {
|
||||
if (key === '__proto__') {
|
||||
error(`Cannot stringify objects with __proto__ keys`, thing);
|
||||
}
|
||||
|
||||
if (started) str += ',';
|
||||
started = true;
|
||||
keys.push(key);
|
||||
str += `${quote_key(key)}:${flatten(ops.get(thing, key))}`;
|
||||
keys.pop();
|
||||
}
|
||||
str += '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stringified[index] = str;
|
||||
return index;
|
||||
}
|
||||
|
||||
const index = flatten(value);
|
||||
|
||||
// special case — value is represented as a negative index
|
||||
if (index < 0) return `${index}`;
|
||||
|
||||
return stringified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} thing
|
||||
* @returns {string}
|
||||
*/
|
||||
function stringify_primitive(thing) {
|
||||
const type = typeof thing;
|
||||
if (type === 'string') return stringify_string(thing);
|
||||
if (thing === void 0) return UNDEFINED.toString();
|
||||
if (thing === 0 && 1 / thing < 0) return NEGATIVE_ZERO.toString();
|
||||
if (type === 'bigint') return `["BigInt","${thing}"]`;
|
||||
return String(thing);
|
||||
}
|
||||
+623
@@ -0,0 +1,623 @@
|
||||
/**
|
||||
* @import { UnevalReplacer } from './types';
|
||||
*/
|
||||
import { js, JavaScriptSource } from './javascript-source.js';
|
||||
import {
|
||||
DevalueError,
|
||||
MAP_KEY,
|
||||
enumerable_symbols,
|
||||
escaped,
|
||||
format_path,
|
||||
get_name,
|
||||
get_type,
|
||||
is_buffer,
|
||||
is_plain_object,
|
||||
is_primitive,
|
||||
stringify_key,
|
||||
stringify_sparse_array,
|
||||
stringify_string,
|
||||
valid_array_indices
|
||||
} from './utils.js';
|
||||
|
||||
const unsafe_chars = /[<\b\f\n\r\t\0\u2028\u2029]/g;
|
||||
// Short strings have bounded escaping/output costs per reference.
|
||||
const MIN_STRING_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* Turn a value into the JavaScript that creates an equivalent value
|
||||
* @param {any} value
|
||||
* @param {UnevalReplacer} [replacer]
|
||||
*/
|
||||
export function uneval(value, replacer) {
|
||||
/** @type {Map<any, string>} */
|
||||
const names = new Map();
|
||||
const reserved = new Set();
|
||||
const templates = new Set();
|
||||
const seen = new Set();
|
||||
|
||||
// the path to the value being walked, recorded as-is and only formatted
|
||||
// by `error` — see `format_path`
|
||||
/** @type {any[]} */
|
||||
const keys = [];
|
||||
|
||||
/** @type {Map<any, JavaScriptSource>} */
|
||||
const custom = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {any} thing
|
||||
* @returns {never}
|
||||
*/
|
||||
function error(message, thing) {
|
||||
const path = format_path(keys, (key) => (is_primitive(key) ? stringify_primitive(key) : '...'));
|
||||
throw new DevalueError(message, path, thing, value);
|
||||
}
|
||||
|
||||
/** @type {Map<string | bigint, number> | undefined} */
|
||||
let primitive_counts;
|
||||
/** @type {Map<string | bigint, string> | undefined} */
|
||||
let primitives;
|
||||
|
||||
/** @param {any} thing */
|
||||
function stringify_cached_primitive(thing) {
|
||||
if (
|
||||
(typeof thing === 'string' && thing.length >= MIN_STRING_LENGTH) ||
|
||||
typeof thing === 'bigint'
|
||||
) {
|
||||
primitives ??= new Map();
|
||||
let literal = primitives.get(thing);
|
||||
if (literal === undefined) {
|
||||
literal = stringify_primitive(thing);
|
||||
primitives.set(thing, literal);
|
||||
}
|
||||
return literal;
|
||||
}
|
||||
return stringify_primitive(thing);
|
||||
}
|
||||
|
||||
/** @param {any} thing */
|
||||
function walk(thing) {
|
||||
if (!is_primitive(thing)) {
|
||||
if (seen.has(thing)) {
|
||||
if (!names.has(thing)) names.set(thing, '');
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(thing);
|
||||
|
||||
if (replacer) {
|
||||
const fragment = replacer(thing, js);
|
||||
|
||||
if (fragment) {
|
||||
const source = JavaScriptSource.from(fragment);
|
||||
custom.set(thing, source);
|
||||
source.visit(walk, reserved, templates);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragment !== undefined && fragment !== null && fragment !== false) {
|
||||
throw new TypeError('Invalid uneval replacer result');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof thing === 'function') {
|
||||
error(`Cannot stringify a function`, thing);
|
||||
}
|
||||
|
||||
const type = get_type(thing);
|
||||
|
||||
switch (type) {
|
||||
case 'BigInt':
|
||||
case 'String':
|
||||
walk(thing.valueOf());
|
||||
return;
|
||||
|
||||
case 'Number':
|
||||
case 'Boolean':
|
||||
case 'Date':
|
||||
case 'RegExp':
|
||||
case 'URL':
|
||||
case 'URLSearchParams':
|
||||
return;
|
||||
|
||||
case 'Array':
|
||||
// Never scan the logical length of a dictionary-backed sparse array.
|
||||
for (const i of valid_array_indices(thing)) {
|
||||
keys.push(+i);
|
||||
walk(thing[i]);
|
||||
keys.pop();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Set':
|
||||
for (const value of thing) walk(value);
|
||||
break;
|
||||
|
||||
case 'Map':
|
||||
for (const [key, value] of thing) {
|
||||
keys.push(MAP_KEY, key);
|
||||
walk(key);
|
||||
walk(value);
|
||||
keys.pop();
|
||||
keys.pop();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Int8Array':
|
||||
case 'Uint8Array':
|
||||
case 'Uint8ClampedArray':
|
||||
case 'Int16Array':
|
||||
case 'Uint16Array':
|
||||
case 'Float16Array':
|
||||
case 'Int32Array':
|
||||
case 'Uint32Array':
|
||||
case 'Float32Array':
|
||||
case 'Float64Array':
|
||||
case 'BigInt64Array':
|
||||
case 'BigUint64Array':
|
||||
case 'DataView':
|
||||
// Buffer pools can contain unrelated, sensitive bytes.
|
||||
if (!is_buffer(thing)) walk(thing.buffer);
|
||||
return;
|
||||
|
||||
case 'ArrayBuffer':
|
||||
return;
|
||||
|
||||
case 'Temporal.Duration':
|
||||
case 'Temporal.Instant':
|
||||
case 'Temporal.PlainDate':
|
||||
case 'Temporal.PlainTime':
|
||||
case 'Temporal.PlainDateTime':
|
||||
case 'Temporal.PlainMonthDay':
|
||||
case 'Temporal.PlainYearMonth':
|
||||
case 'Temporal.ZonedDateTime':
|
||||
return;
|
||||
|
||||
default:
|
||||
if (!is_plain_object(thing)) {
|
||||
error(`Cannot stringify arbitrary non-POJOs`, thing);
|
||||
}
|
||||
|
||||
if (enumerable_symbols(thing).length > 0) {
|
||||
error(`Cannot stringify POJOs with symbolic keys`, thing);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(thing)) {
|
||||
if (key === '__proto__') {
|
||||
error(`Cannot stringify objects with __proto__ keys`, thing);
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
walk(thing[key]);
|
||||
keys.pop();
|
||||
}
|
||||
}
|
||||
} else if (typeof thing === 'symbol') {
|
||||
error(`Cannot stringify a Symbol primitive`, thing);
|
||||
} else if (
|
||||
(typeof thing === 'string' && thing.length >= MIN_STRING_LENGTH) ||
|
||||
typeof thing === 'bigint'
|
||||
) {
|
||||
primitive_counts ??= new Map();
|
||||
primitive_counts.set(thing, (primitive_counts.get(thing) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
walk(value);
|
||||
|
||||
let name_index = 0;
|
||||
function next_name() {
|
||||
let name;
|
||||
do {
|
||||
name = get_name(name_index++);
|
||||
} while (reserved.has(name));
|
||||
return name;
|
||||
}
|
||||
|
||||
// Wait until every custom source has been visited before assigning names.
|
||||
for (const thing of names.keys()) names.set(thing, next_name());
|
||||
if (primitive_counts) {
|
||||
for (const [thing, count] of primitive_counts) {
|
||||
if (count < 2) continue;
|
||||
const name = next_name();
|
||||
const length = stringify_cached_primitive(thing).length;
|
||||
// Include the declaration and IIFE overhead even if one already exists.
|
||||
if (length * count > length + (count + 1) * name.length + 40) names.set(thing, name);
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the traversal set to track declarations during serialization.
|
||||
seen.clear();
|
||||
const inlining = custom.size > 0 ? new Set() : null;
|
||||
|
||||
/** @type {Map<any, () => void>} */
|
||||
const initializers = new Map();
|
||||
|
||||
/** @type {string[]} */
|
||||
const statements = [];
|
||||
|
||||
/**
|
||||
* @param {any} thing
|
||||
* @returns {string}
|
||||
*/
|
||||
function stringify(thing) {
|
||||
const name = names.get(thing);
|
||||
|
||||
if (name) {
|
||||
if (!seen.has(thing)) {
|
||||
if (is_primitive(thing)) {
|
||||
seen.add(thing);
|
||||
statements.push(`let ${name}=${stringify_cached_primitive(thing)}`);
|
||||
return name;
|
||||
}
|
||||
const type = custom.has(thing) ? null : get_type(thing);
|
||||
|
||||
switch (type) {
|
||||
case 'Object':
|
||||
seen.add(thing);
|
||||
statements.push(
|
||||
`let ${name}=${Object.getPrototypeOf(thing) === null ? 'Object.create(null)' : '{}'}`
|
||||
);
|
||||
Object.keys(thing).forEach((key) => {
|
||||
statements.push(`${name}${safe_prop(key)}=${stringify(thing[key])}`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'Array': {
|
||||
seen.add(thing);
|
||||
const indices = valid_array_indices(thing);
|
||||
const array =
|
||||
thing.length > 32 + 2 * indices.length
|
||||
? stringify_sparse_array(thing.length)
|
||||
: `Array(${thing.length})`;
|
||||
statements.push(`let ${name}=${array}`);
|
||||
for (const i of indices) {
|
||||
statements.push(`${name}[${i}]=${stringify(thing[i])}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Set':
|
||||
case 'Map': {
|
||||
seen.add(thing);
|
||||
/** @type {string[]} */
|
||||
const entries = [];
|
||||
let initialized = false;
|
||||
let statement_index = -1;
|
||||
|
||||
// Collect a ready prefix until a reference needs the collection.
|
||||
// Emit subsequent entries eagerly, preserving insertion order.
|
||||
const initialize = () => {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
initializers.delete(thing);
|
||||
statements.push(
|
||||
`let ${name}=new ${type}${entries.length ? `([${entries.join(',')}])` : ''}`
|
||||
);
|
||||
};
|
||||
|
||||
initializers.set(thing, initialize);
|
||||
|
||||
for (const entry of thing) {
|
||||
const args =
|
||||
type === 'Map' ? `${stringify(entry[0])},${stringify(entry[1])}` : stringify(entry);
|
||||
|
||||
if (!initialized) {
|
||||
entries.push(type === 'Map' ? `[${args}]` : args);
|
||||
} else {
|
||||
const call = `.${type === 'Map' ? 'set' : 'add'}(${args})`;
|
||||
if (statement_index === statements.length - 1) {
|
||||
statements[statement_index] += call;
|
||||
} else {
|
||||
statement_index = statements.push(name + call) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
const str = actually_stringify(thing);
|
||||
if (!seen.has(thing)) statements.push(`let ${name}=${str}`);
|
||||
seen.add(thing);
|
||||
}
|
||||
} else {
|
||||
initializers.get(thing)?.();
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
if (is_primitive(thing)) {
|
||||
return stringify_cached_primitive(thing);
|
||||
}
|
||||
|
||||
if (inlining === null) return actually_stringify(thing);
|
||||
|
||||
// A singly referenced value can still be part of a custom constructor's
|
||||
// cycle. If inlining it re-enters itself, hoist it to break the cycle.
|
||||
if (inlining.has(thing)) {
|
||||
names.set(thing, next_name());
|
||||
return stringify(thing);
|
||||
}
|
||||
|
||||
inlining.add(thing);
|
||||
const str = actually_stringify(thing);
|
||||
inlining.delete(thing);
|
||||
return names.get(thing) || str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} thing
|
||||
* @returns {string}
|
||||
*/
|
||||
function actually_stringify(thing) {
|
||||
const source = custom.get(thing);
|
||||
|
||||
if (source) {
|
||||
return source.render(stringify);
|
||||
}
|
||||
|
||||
const type = get_type(thing);
|
||||
|
||||
switch (type) {
|
||||
case 'Number':
|
||||
case 'String':
|
||||
case 'Boolean':
|
||||
case 'BigInt':
|
||||
return `Object(${stringify(thing.valueOf())})`;
|
||||
|
||||
case 'RegExp':
|
||||
const { source, flags } = thing;
|
||||
return flags
|
||||
? `new RegExp(${stringify_string(source)},"${flags}")`
|
||||
: `new RegExp(${stringify_string(source)})`;
|
||||
|
||||
case 'Date':
|
||||
return `new Date(${thing.getTime()})`;
|
||||
|
||||
case 'URL':
|
||||
return `new URL(${stringify_string(thing.toString())})`;
|
||||
|
||||
case 'URLSearchParams':
|
||||
return `new URLSearchParams(${stringify_string(thing.toString())})`;
|
||||
|
||||
case 'Array': {
|
||||
// For dense arrays (no holes), we iterate normally.
|
||||
// When we encounter the first hole, we collect own indices
|
||||
// to determine the sparseness, then decide between:
|
||||
// - Array literal with holes: [,"a",,] (default)
|
||||
// - Object.assign with a sparse-safe allocator (for very sparse arrays)
|
||||
// Only the Object.assign path avoids iterating every slot, which
|
||||
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
|
||||
let has_holes = false;
|
||||
|
||||
let result = '[';
|
||||
|
||||
for (let i = 0; i < thing.length; i += 1) {
|
||||
if (i > 0) result += ',';
|
||||
|
||||
if (Object.hasOwn(thing, i)) {
|
||||
result += stringify(thing[i]);
|
||||
} else if (!has_holes) {
|
||||
// Decide between array literal and Object.assign.
|
||||
//
|
||||
// Array literal: holes are consecutive commas.
|
||||
// For example, [, "a", ,] is written as [,"a",,].
|
||||
// Each hole costs 1 char (a comma).
|
||||
//
|
||||
// Object.assign: populated indices are listed explicitly.
|
||||
// For example, [, "a", ,] would be written as
|
||||
// Object.assign(sparse(3),{1:"a"}), where sparse(n) stands
|
||||
// for the expression emitted by stringify_sparse_array(n).
|
||||
// This avoids paying per-hole, but has fixed overhead for
|
||||
// the allocator and wrapper, plus each index and colon.
|
||||
//
|
||||
// The serialized values are the same size either way, so
|
||||
// the choice comes down to the structural overhead:
|
||||
//
|
||||
// Array literal overhead:
|
||||
// 1 char per element or hole (comma separators)
|
||||
// + 2 chars for "[" and "]"
|
||||
// = L + 2
|
||||
//
|
||||
// Object.assign overhead:
|
||||
// "Object.assign(" — 14 chars
|
||||
// + allocator expression — A chars
|
||||
// + ",{" — 2 chars
|
||||
// + for each populated element:
|
||||
// index + ":" + "," — (d + 2) chars
|
||||
// + "})" — 2 chars
|
||||
// = (18 + A) + P * (d + 2)
|
||||
//
|
||||
// where L is the array length, P is the number of
|
||||
// populated elements, A is the allocator length, and d
|
||||
// is the number of digits in L (an upper bound per index).
|
||||
//
|
||||
// Object.assign is cheaper when:
|
||||
// (18 + A) + P * (d + 2) < L + 2
|
||||
const populated_keys = valid_array_indices(/** @type {any[]} */ (thing));
|
||||
const population = populated_keys.length;
|
||||
const d = String(thing.length).length;
|
||||
const array = stringify_sparse_array(thing.length);
|
||||
|
||||
const hole_cost = thing.length + 2;
|
||||
const sparse_cost = array.length + 18 + population * (d + 2);
|
||||
|
||||
if (hole_cost > sparse_cost) {
|
||||
const entries = populated_keys.map((k) => `${k}:${stringify(thing[k])}`).join(',');
|
||||
return `Object.assign(${array},{${entries}})`;
|
||||
}
|
||||
|
||||
has_holes = true;
|
||||
}
|
||||
// else: already decided on array literal, hole is just an empty slot
|
||||
// (the comma separator is all we need — no content for this position)
|
||||
}
|
||||
|
||||
const tail = thing.length === 0 || Object.hasOwn(thing, thing.length - 1) ? '' : ',';
|
||||
return result + tail + ']';
|
||||
}
|
||||
|
||||
case 'Set':
|
||||
return `new Set([${Array.from(thing, stringify).join(',')}])`;
|
||||
|
||||
case 'Map': {
|
||||
const entries = Array.from(thing, ([k, v]) => `[${stringify(k)},${stringify(v)}]`);
|
||||
return `new Map([${entries.join(',')}])`;
|
||||
}
|
||||
|
||||
case 'Int8Array':
|
||||
case 'Uint8Array':
|
||||
case 'Uint8ClampedArray':
|
||||
case 'Int16Array':
|
||||
case 'Uint16Array':
|
||||
case 'Float16Array':
|
||||
case 'Int32Array':
|
||||
case 'Uint32Array':
|
||||
case 'Float32Array':
|
||||
case 'Float64Array':
|
||||
case 'BigInt64Array':
|
||||
case 'BigUint64Array': {
|
||||
if (is_buffer(thing)) thing = new Uint8Array(thing);
|
||||
|
||||
if (thing.buffer.byteLength % thing.BYTES_PER_ELEMENT !== 0) {
|
||||
return `new ${type}(${stringify(thing.buffer)},${thing.byteOffset},${thing.length})`;
|
||||
}
|
||||
|
||||
let str = `new ${type}`;
|
||||
|
||||
if (!names.has(thing.buffer)) {
|
||||
str += `([${stringify_typed_array_elements(type, thing.buffer)}])`;
|
||||
} else {
|
||||
str += `(${stringify(thing.buffer)})`;
|
||||
}
|
||||
|
||||
// handle subarrays
|
||||
if (thing.byteLength !== thing.buffer.byteLength) {
|
||||
const start = thing.byteOffset / thing.BYTES_PER_ELEMENT;
|
||||
const end = start + thing.length;
|
||||
str += `.subarray(${start},${end})`;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
case 'DataView': {
|
||||
let str = `new DataView`;
|
||||
|
||||
if (!names.has(thing.buffer)) {
|
||||
str += `(new Uint8Array([${new Uint8Array(thing.buffer)}]).buffer`;
|
||||
} else {
|
||||
str += `(${stringify(thing.buffer)}`;
|
||||
}
|
||||
|
||||
// handle subviews
|
||||
if (thing.byteLength !== thing.buffer.byteLength) {
|
||||
str += `,${thing.byteOffset},${thing.byteLength}`;
|
||||
}
|
||||
|
||||
return str + ')';
|
||||
}
|
||||
|
||||
case 'ArrayBuffer': {
|
||||
const ui8 = new Uint8Array(thing);
|
||||
return `new Uint8Array([${ui8.toString()}]).buffer`;
|
||||
}
|
||||
|
||||
case 'Temporal.Duration':
|
||||
case 'Temporal.Instant':
|
||||
case 'Temporal.PlainDate':
|
||||
case 'Temporal.PlainTime':
|
||||
case 'Temporal.PlainDateTime':
|
||||
case 'Temporal.PlainMonthDay':
|
||||
case 'Temporal.PlainYearMonth':
|
||||
case 'Temporal.ZonedDateTime':
|
||||
return `${type}.from(${stringify_string(thing.toString())})`;
|
||||
|
||||
default:
|
||||
const keys = Object.keys(thing);
|
||||
const obj = keys.map((key) => `${safe_key(key)}:${stringify(thing[key])}`).join(',');
|
||||
const proto = Object.getPrototypeOf(thing);
|
||||
if (proto === null) {
|
||||
return keys.length > 0 ? `{${obj},__proto__:null}` : `{__proto__:null}`;
|
||||
}
|
||||
|
||||
return `{${obj}}`;
|
||||
}
|
||||
}
|
||||
|
||||
const str = stringify(value);
|
||||
|
||||
if (statements.length === 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return `(function(){${statements.join(';')};return ${str}}())`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the elements of `buffer`, read as `type`, as a comma-separated list.
|
||||
* The view is created from `type` rather than from the serialized value's own
|
||||
* constructor, which may be a subclass like Node's `Buffer` whose `toString`
|
||||
* decodes the bytes instead of listing them.
|
||||
* `BigInt64Array`/`BigUint64Array` elements are bigints and must be written
|
||||
* with an `n` suffix, otherwise the emitted `new BigInt64Array([...])` throws.
|
||||
* @param {string} type
|
||||
* @param {ArrayBufferLike} buffer
|
||||
*/
|
||||
function stringify_typed_array_elements(type, buffer) {
|
||||
const array = new /** @type {any} */ (globalThis)[type](buffer);
|
||||
|
||||
if (type === 'BigInt64Array' || type === 'BigUint64Array') {
|
||||
return Array.from(array, (element) => `${element}n`).join(',');
|
||||
}
|
||||
|
||||
// Float arrays can hold `-0`, which `toString()` collapses to `"0"`, silently
|
||||
// losing the sign on round-trip. Emit `-0` explicitly for those elements.
|
||||
if (
|
||||
array instanceof Float32Array ||
|
||||
array instanceof Float64Array ||
|
||||
(typeof Float16Array !== 'undefined' && array instanceof Float16Array)
|
||||
) {
|
||||
return Array.from(array, (element) => (Object.is(element, -0) ? '-0' : `${element}`)).join(',');
|
||||
}
|
||||
|
||||
return array.toString();
|
||||
}
|
||||
|
||||
/** @param {string} c */
|
||||
function escape_unsafe_char(c) {
|
||||
return escaped[c] || c;
|
||||
}
|
||||
|
||||
/** @param {string} str */
|
||||
function escape_unsafe_chars(str) {
|
||||
return str.replace(unsafe_chars, escape_unsafe_char);
|
||||
}
|
||||
|
||||
/** @param {string} key */
|
||||
function safe_key(key) {
|
||||
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escape_unsafe_chars(JSON.stringify(key));
|
||||
}
|
||||
|
||||
/** @param {string} key */
|
||||
function safe_prop(key) {
|
||||
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key)
|
||||
? `.${key}`
|
||||
: `[${escape_unsafe_chars(JSON.stringify(key))}]`;
|
||||
}
|
||||
|
||||
/** @param {any} thing */
|
||||
function stringify_primitive(thing) {
|
||||
const type = typeof thing;
|
||||
if (type === 'string') return stringify_string(thing);
|
||||
if (thing === void 0) return 'void 0';
|
||||
if (thing === 0 && 1 / thing < 0) return '-0';
|
||||
const str = String(thing);
|
||||
if (type === 'number') return str.replace(/^(-)?0\./, '$1.');
|
||||
if (type === 'bigint') return thing + 'n';
|
||||
return str;
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
import { MAX_ARRAY_INDEX, MAX_ARRAY_LEN } from './constants.js';
|
||||
|
||||
const name_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
|
||||
const reserved_names =
|
||||
/^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
export const escaped = {
|
||||
'<': '\\u003C',
|
||||
'\\': '\\\\',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
'\u2028': '\\u2028',
|
||||
'\u2029': '\\u2029'
|
||||
};
|
||||
|
||||
export class DevalueError extends Error {
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {string[]} keys
|
||||
* @param {any} [value] - The value that failed to be serialized
|
||||
* @param {any} [root] - The root value being serialized
|
||||
*/
|
||||
constructor(message, keys, value, root) {
|
||||
super(message);
|
||||
this.name = 'DevalueError';
|
||||
this.path = keys.join('');
|
||||
this.value = value;
|
||||
this.root = root;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a Map key on the path being serialized. The key itself is recorded
|
||||
* right after the marker, so nothing is formatted or allocated per entry.
|
||||
*/
|
||||
export const MAP_KEY = Symbol();
|
||||
|
||||
/**
|
||||
* Formats the path recorded on the way to the value being serialized — a
|
||||
* property key, an array index, or `MAP_KEY` followed by a Map key — into
|
||||
* the segments a `DevalueError` joins. Only runs when an error is raised.
|
||||
* @param {any[]} keys
|
||||
* @param {(key: any) => string} format_map_key
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function format_path(keys, format_map_key) {
|
||||
/** @type {string[]} */
|
||||
const path = [];
|
||||
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const segment = keys[i];
|
||||
|
||||
if (segment === MAP_KEY) {
|
||||
path.push(`.get(${format_map_key(keys[++i])})`);
|
||||
} else if (typeof segment === 'number') {
|
||||
path.push(`[${segment}]`);
|
||||
} else {
|
||||
path.push(stringify_key(segment));
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/** @param {any} thing */
|
||||
export function is_primitive(thing) {
|
||||
return thing === null || (typeof thing !== 'object' && typeof thing !== 'function');
|
||||
}
|
||||
|
||||
const object_proto_names = /* @__PURE__ */ Object.getOwnPropertyNames(Object.prototype)
|
||||
.sort()
|
||||
.join('\0');
|
||||
|
||||
/** @param {any} thing */
|
||||
export function is_plain_object(thing) {
|
||||
const proto = Object.getPrototypeOf(thing);
|
||||
|
||||
return (
|
||||
proto === Object.prototype ||
|
||||
proto === null ||
|
||||
Object.getPrototypeOf(proto) === null ||
|
||||
Object.getOwnPropertyNames(proto).sort().join('\0') === object_proto_names
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {any} thing */
|
||||
export function get_type(thing) {
|
||||
return Object.prototype.toString.call(thing).slice(8, -1);
|
||||
}
|
||||
|
||||
/** @param {any} thing */
|
||||
export function is_buffer(thing) {
|
||||
return typeof Buffer !== 'undefined' && Buffer.isBuffer(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an array whose storage is not proportional to its declared length.
|
||||
* Touching and deleting the largest valid index forces dictionary elements
|
||||
* before setting the length; assigning .length on [] can still eagerly allocate.
|
||||
* @param {number} length
|
||||
*/
|
||||
export function stringify_sparse_array(length) {
|
||||
return `(function(a){a[${MAX_ARRAY_INDEX}]=0;delete a[${MAX_ARRAY_INDEX}];a.length=${length};return a}([]))`;
|
||||
}
|
||||
|
||||
/** Returns the compact JavaScript identifier at `index`. @param {number} index */
|
||||
export function get_name(index) {
|
||||
let name = '';
|
||||
|
||||
do {
|
||||
name = name_chars[index % name_chars.length] + name;
|
||||
index = ~~(index / name_chars.length) - 1;
|
||||
} while (index >= 0);
|
||||
|
||||
return reserved_names.test(name) ? `${name}0` : name;
|
||||
}
|
||||
|
||||
/** @param {string} char */
|
||||
function get_escaped_char(char) {
|
||||
switch (char) {
|
||||
case '"':
|
||||
return '\\"';
|
||||
case '<':
|
||||
return '\\u003C';
|
||||
case '\\':
|
||||
return '\\\\';
|
||||
case '\n':
|
||||
return '\\n';
|
||||
case '\r':
|
||||
return '\\r';
|
||||
case '\t':
|
||||
return '\\t';
|
||||
case '\b':
|
||||
return '\\b';
|
||||
case '\f':
|
||||
return '\\f';
|
||||
case '\u2028':
|
||||
return '\\u2028';
|
||||
case '\u2029':
|
||||
return '\\u2029';
|
||||
default:
|
||||
return char < ' ' ? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}` : '';
|
||||
}
|
||||
}
|
||||
|
||||
// characters `stringify_string` always escapes: quote, backslash, `<`, control
|
||||
// characters and the line/paragraph separators. Surrogates are only escaped
|
||||
// when unpaired, which `isWellFormed` detects natively, so well-formed pairs
|
||||
// such as emoji stay on the fast path
|
||||
const needs_escape = /["<\\\u0000-\u001f\u2028\u2029]/;
|
||||
|
||||
/** @param {string} str */
|
||||
export function stringify_string(str) {
|
||||
// the common case: nothing to escape, no per-character work
|
||||
if (!needs_escape.test(str) && str.isWellFormed()) return `"${str}"`;
|
||||
|
||||
let result = '';
|
||||
let last_pos = 0;
|
||||
const len = str.length;
|
||||
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
const code = str.charCodeAt(i);
|
||||
if (code >= 0xd800 && code <= 0xdfff) {
|
||||
// A well-formed surrogate pair passes through untouched; an unpaired
|
||||
// surrogate cannot survive UTF-8 transport, so it is escaped.
|
||||
if (code <= 0xdbff && i + 1 < len && (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
result += str.slice(last_pos, i) + `\\u${code.toString(16)}`;
|
||||
last_pos = i + 1;
|
||||
continue;
|
||||
}
|
||||
const replacement = get_escaped_char(str[i]);
|
||||
if (replacement) {
|
||||
result += str.slice(last_pos, i) + replacement;
|
||||
last_pos = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return `"${last_pos === 0 ? str : result + str.slice(last_pos)}"`;
|
||||
}
|
||||
|
||||
// Property names repeat across the values of one call and across calls, so
|
||||
// their quoted form is cached. Names come from the data being serialized, so
|
||||
// the cache is bounded in both directions: only short names are kept, and the
|
||||
// cache is emptied once it holds 1024 of them. Longer names are quoted on
|
||||
// every visit, like every other string.
|
||||
const MAX_CACHED_KEY_LENGTH = 64;
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const quoted_keys = new Map();
|
||||
|
||||
/** @param {string} key */
|
||||
export function quote_key(key) {
|
||||
if (key.length > MAX_CACHED_KEY_LENGTH) return stringify_string(key);
|
||||
|
||||
let quoted = quoted_keys.get(key);
|
||||
|
||||
if (quoted === undefined) {
|
||||
if (quoted_keys.size >= 1024) quoted_keys.clear();
|
||||
quoted = stringify_string(key);
|
||||
quoted_keys.set(key, quoted);
|
||||
}
|
||||
|
||||
return quoted;
|
||||
}
|
||||
|
||||
/** @param {Record<string | symbol, any>} object */
|
||||
export function enumerable_symbols(object) {
|
||||
const symbols = Object.getOwnPropertySymbols(object);
|
||||
// Own symbols always have a descriptor; the optional chain only satisfies the lib's
|
||||
// `PropertyDescriptor | undefined` return type.
|
||||
return symbols.length === 0
|
||||
? symbols
|
||||
: symbols.filter((symbol) => Object.getOwnPropertyDescriptor(object, symbol)?.enumerable);
|
||||
}
|
||||
|
||||
const is_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
|
||||
|
||||
/** @param {string} key */
|
||||
export function stringify_key(key) {
|
||||
return is_identifier.test(key) ? '.' + key : '[' + JSON.stringify(key) + ']';
|
||||
}
|
||||
|
||||
/** @param {number} n */
|
||||
export function is_valid_array_index(n) {
|
||||
if (!Number.isInteger(n)) return false;
|
||||
if (n < 0) return false;
|
||||
if (n > MAX_ARRAY_INDEX) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @param {number} n */
|
||||
export function is_valid_array_len(n) {
|
||||
if (!Number.isInteger(n)) return false;
|
||||
if (n < 0) return false;
|
||||
if (n > MAX_ARRAY_LEN) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @param {string} s */
|
||||
function is_valid_array_index_string(s) {
|
||||
if (s.length === 0) return false;
|
||||
if (s.length > 1 && s.charCodeAt(0) === 48) return false; // leading zero
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
const c = s.charCodeAt(i);
|
||||
if (c < 48 || c > 57) return false;
|
||||
}
|
||||
// by this point we know it's a string of digits, but it has to be within
|
||||
// the range of valid array indices
|
||||
return is_valid_array_index(+s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the leading run of valid array indices in `keys`.
|
||||
* @param {readonly string[]} keys
|
||||
*/
|
||||
function array_index_cut(keys) {
|
||||
for (var i = keys.length - 1; i >= 0; i--) {
|
||||
if (is_valid_array_index_string(keys[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the populated indices of an array.
|
||||
* @param {unknown[]} array
|
||||
*/
|
||||
export function valid_array_indices(array) {
|
||||
const keys = Object.keys(array);
|
||||
keys.length = array_index_cut(keys);
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the own enumerable string keys of an array-like value, in property
|
||||
* order, returns the leading run of them that are valid array indices.
|
||||
*
|
||||
* This is the filtering half of the `indicesOf` stringify operation,
|
||||
* exposed so that custom operations — which typically already have the keys
|
||||
* in hand, e.g. from a foreign runtime — don't have to reimplement it.
|
||||
*
|
||||
* Does not modify `keys`.
|
||||
*
|
||||
* @param {readonly string[]} keys
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function filter_array_indices(keys) {
|
||||
return keys.slice(0, array_index_cut(keys));
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
// Adapted from https://github.com/mathiasbynens/he/blob/36afe179392226cf1b6ccdb16ebbb7a5a844d93a/src/he.js#L106-L134
|
||||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fromCodePoint = void 0;
|
||||
exports.replaceCodePoint = replaceCodePoint;
|
||||
exports.decodeCodePoint = decodeCodePoint;
|
||||
const decodeMap = new Map([
|
||||
[0, 65533],
|
||||
// C1 Unicode control character reference replacements
|
||||
[128, 8364],
|
||||
[130, 8218],
|
||||
[131, 402],
|
||||
[132, 8222],
|
||||
[133, 8230],
|
||||
[134, 8224],
|
||||
[135, 8225],
|
||||
[136, 710],
|
||||
[137, 8240],
|
||||
[138, 352],
|
||||
[139, 8249],
|
||||
[140, 338],
|
||||
[142, 381],
|
||||
[145, 8216],
|
||||
[146, 8217],
|
||||
[147, 8220],
|
||||
[148, 8221],
|
||||
[149, 8226],
|
||||
[150, 8211],
|
||||
[151, 8212],
|
||||
[152, 732],
|
||||
[153, 8482],
|
||||
[154, 353],
|
||||
[155, 8250],
|
||||
[156, 339],
|
||||
[158, 382],
|
||||
[159, 376],
|
||||
]);
|
||||
/**
|
||||
* Polyfill for `String.fromCodePoint`. It is used to create a string from a Unicode code point.
|
||||
*/
|
||||
exports.fromCodePoint =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, n/no-unsupported-features/es-builtins
|
||||
(_a = String.fromCodePoint) !== null && _a !== void 0 ? _a : ((codePoint) => {
|
||||
let output = "";
|
||||
if (codePoint > 65535) {
|
||||
codePoint -= 65536;
|
||||
output += String.fromCharCode(((codePoint >>> 10) & 1023) | 55296);
|
||||
codePoint = 56320 | (codePoint & 1023);
|
||||
}
|
||||
output += String.fromCharCode(codePoint);
|
||||
return output;
|
||||
});
|
||||
/**
|
||||
* Replace the given code point with a replacement character if it is a
|
||||
* surrogate or is outside the valid range. Otherwise return the code
|
||||
* point unchanged.
|
||||
*/
|
||||
function replaceCodePoint(codePoint) {
|
||||
var _a;
|
||||
if ((codePoint >= 55296 && codePoint <= 57343) ||
|
||||
codePoint > 1114111) {
|
||||
return 65533;
|
||||
}
|
||||
return (_a = decodeMap.get(codePoint)) !== null && _a !== void 0 ? _a : codePoint;
|
||||
}
|
||||
/**
|
||||
* Replace the code point if relevant, then convert it to a string.
|
||||
*
|
||||
* @deprecated Use `fromCodePoint(replaceCodePoint(codePoint))` instead.
|
||||
* @param codePoint The code point to decode.
|
||||
* @returns The decoded code point.
|
||||
*/
|
||||
function decodeCodePoint(codePoint) {
|
||||
return (0, exports.fromCodePoint)(replaceCodePoint(codePoint));
|
||||
}
|
||||
//# sourceMappingURL=decode-codepoint.js.map
|
||||
+568
@@ -0,0 +1,568 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.xmlDecodeTree = exports.htmlDecodeTree = exports.replaceCodePoint = exports.fromCodePoint = exports.decodeCodePoint = exports.EntityDecoder = exports.DecodingMode = void 0;
|
||||
exports.determineBranch = determineBranch;
|
||||
exports.decodeHTML = decodeHTML;
|
||||
exports.decodeHTMLAttribute = decodeHTMLAttribute;
|
||||
exports.decodeHTMLStrict = decodeHTMLStrict;
|
||||
exports.decodeXML = decodeXML;
|
||||
const decode_codepoint_js_1 = require("./decode-codepoint.js");
|
||||
const decode_data_html_js_1 = require("./generated/decode-data-html.js");
|
||||
const decode_data_xml_js_1 = require("./generated/decode-data-xml.js");
|
||||
const bin_trie_flags_js_1 = require("./internal/bin-trie-flags.js");
|
||||
var CharCodes;
|
||||
(function (CharCodes) {
|
||||
CharCodes[CharCodes["NUM"] = 35] = "NUM";
|
||||
CharCodes[CharCodes["SEMI"] = 59] = "SEMI";
|
||||
CharCodes[CharCodes["EQUALS"] = 61] = "EQUALS";
|
||||
CharCodes[CharCodes["ZERO"] = 48] = "ZERO";
|
||||
CharCodes[CharCodes["NINE"] = 57] = "NINE";
|
||||
CharCodes[CharCodes["LOWER_A"] = 97] = "LOWER_A";
|
||||
CharCodes[CharCodes["LOWER_F"] = 102] = "LOWER_F";
|
||||
CharCodes[CharCodes["LOWER_X"] = 120] = "LOWER_X";
|
||||
CharCodes[CharCodes["LOWER_Z"] = 122] = "LOWER_Z";
|
||||
CharCodes[CharCodes["UPPER_A"] = 65] = "UPPER_A";
|
||||
CharCodes[CharCodes["UPPER_F"] = 70] = "UPPER_F";
|
||||
CharCodes[CharCodes["UPPER_Z"] = 90] = "UPPER_Z";
|
||||
})(CharCodes || (CharCodes = {}));
|
||||
/** Bit that needs to be set to convert an upper case ASCII character to lower case */
|
||||
const TO_LOWER_BIT = 32;
|
||||
function isNumber(code) {
|
||||
return code >= CharCodes.ZERO && code <= CharCodes.NINE;
|
||||
}
|
||||
function isHexadecimalCharacter(code) {
|
||||
return ((code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_F) ||
|
||||
(code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_F));
|
||||
}
|
||||
function isAsciiAlphaNumeric(code) {
|
||||
return ((code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_Z) ||
|
||||
(code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_Z) ||
|
||||
isNumber(code));
|
||||
}
|
||||
/**
|
||||
* Checks if the given character is a valid end character for an entity in an attribute.
|
||||
*
|
||||
* Attribute values that aren't terminated properly aren't parsed, and shouldn't lead to a parser error.
|
||||
* See the example in https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
|
||||
*/
|
||||
function isEntityInAttributeInvalidEnd(code) {
|
||||
return code === CharCodes.EQUALS || isAsciiAlphaNumeric(code);
|
||||
}
|
||||
var EntityDecoderState;
|
||||
(function (EntityDecoderState) {
|
||||
EntityDecoderState[EntityDecoderState["EntityStart"] = 0] = "EntityStart";
|
||||
EntityDecoderState[EntityDecoderState["NumericStart"] = 1] = "NumericStart";
|
||||
EntityDecoderState[EntityDecoderState["NumericDecimal"] = 2] = "NumericDecimal";
|
||||
EntityDecoderState[EntityDecoderState["NumericHex"] = 3] = "NumericHex";
|
||||
EntityDecoderState[EntityDecoderState["NamedEntity"] = 4] = "NamedEntity";
|
||||
})(EntityDecoderState || (EntityDecoderState = {}));
|
||||
var DecodingMode;
|
||||
(function (DecodingMode) {
|
||||
/** Entities in text nodes that can end with any character. */
|
||||
DecodingMode[DecodingMode["Legacy"] = 0] = "Legacy";
|
||||
/** Only allow entities terminated with a semicolon. */
|
||||
DecodingMode[DecodingMode["Strict"] = 1] = "Strict";
|
||||
/** Entities in attributes have limitations on ending characters. */
|
||||
DecodingMode[DecodingMode["Attribute"] = 2] = "Attribute";
|
||||
})(DecodingMode || (exports.DecodingMode = DecodingMode = {}));
|
||||
/**
|
||||
* Token decoder with support of writing partial entities.
|
||||
*/
|
||||
class EntityDecoder {
|
||||
constructor(
|
||||
/** The tree used to decode entities. */
|
||||
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: False positive
|
||||
decodeTree,
|
||||
/**
|
||||
* The function that is called when a codepoint is decoded.
|
||||
*
|
||||
* For multi-byte named entities, this will be called multiple times,
|
||||
* with the second codepoint, and the same `consumed` value.
|
||||
*
|
||||
* @param codepoint The decoded codepoint.
|
||||
* @param consumed The number of bytes consumed by the decoder.
|
||||
*/
|
||||
emitCodePoint,
|
||||
/** An object that is used to produce errors. */
|
||||
errors) {
|
||||
this.decodeTree = decodeTree;
|
||||
this.emitCodePoint = emitCodePoint;
|
||||
this.errors = errors;
|
||||
/** The current state of the decoder. */
|
||||
this.state = EntityDecoderState.EntityStart;
|
||||
/** Characters that were consumed while parsing an entity. */
|
||||
this.consumed = 1;
|
||||
/**
|
||||
* The result of the entity.
|
||||
*
|
||||
* Either the result index of a numeric entity, or the codepoint of a
|
||||
* numeric entity.
|
||||
*/
|
||||
this.result = 0;
|
||||
/** The current index in the decode tree. */
|
||||
this.treeIndex = 0;
|
||||
/** The number of characters that were consumed in excess. */
|
||||
this.excess = 1;
|
||||
/** The mode in which the decoder is operating. */
|
||||
this.decodeMode = DecodingMode.Strict;
|
||||
/** The number of characters that have been consumed in the current run. */
|
||||
this.runConsumed = 0;
|
||||
}
|
||||
/** Resets the instance to make it reusable. */
|
||||
startEntity(decodeMode) {
|
||||
this.decodeMode = decodeMode;
|
||||
this.state = EntityDecoderState.EntityStart;
|
||||
this.result = 0;
|
||||
this.treeIndex = 0;
|
||||
this.excess = 1;
|
||||
this.consumed = 1;
|
||||
this.runConsumed = 0;
|
||||
}
|
||||
/**
|
||||
* Write an entity to the decoder. This can be called multiple times with partial entities.
|
||||
* If the entity is incomplete, the decoder will return -1.
|
||||
*
|
||||
* Mirrors the implementation of `getDecoder`, but with the ability to stop decoding if the
|
||||
* entity is incomplete, and resume when the next string is written.
|
||||
*
|
||||
* @param input The string containing the entity (or a continuation of the entity).
|
||||
* @param offset The offset at which the entity begins. Should be 0 if this is not the first call.
|
||||
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
||||
*/
|
||||
write(input, offset) {
|
||||
switch (this.state) {
|
||||
case EntityDecoderState.EntityStart: {
|
||||
if (input.charCodeAt(offset) === CharCodes.NUM) {
|
||||
this.state = EntityDecoderState.NumericStart;
|
||||
this.consumed += 1;
|
||||
return this.stateNumericStart(input, offset + 1);
|
||||
}
|
||||
this.state = EntityDecoderState.NamedEntity;
|
||||
return this.stateNamedEntity(input, offset);
|
||||
}
|
||||
case EntityDecoderState.NumericStart: {
|
||||
return this.stateNumericStart(input, offset);
|
||||
}
|
||||
case EntityDecoderState.NumericDecimal: {
|
||||
return this.stateNumericDecimal(input, offset);
|
||||
}
|
||||
case EntityDecoderState.NumericHex: {
|
||||
return this.stateNumericHex(input, offset);
|
||||
}
|
||||
case EntityDecoderState.NamedEntity: {
|
||||
return this.stateNamedEntity(input, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Switches between the numeric decimal and hexadecimal states.
|
||||
*
|
||||
* Equivalent to the `Numeric character reference state` in the HTML spec.
|
||||
*
|
||||
* @param input The string containing the entity (or a continuation of the entity).
|
||||
* @param offset The current offset.
|
||||
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
||||
*/
|
||||
stateNumericStart(input, offset) {
|
||||
if (offset >= input.length) {
|
||||
return -1;
|
||||
}
|
||||
if ((input.charCodeAt(offset) | TO_LOWER_BIT) === CharCodes.LOWER_X) {
|
||||
this.state = EntityDecoderState.NumericHex;
|
||||
this.consumed += 1;
|
||||
return this.stateNumericHex(input, offset + 1);
|
||||
}
|
||||
this.state = EntityDecoderState.NumericDecimal;
|
||||
return this.stateNumericDecimal(input, offset);
|
||||
}
|
||||
/**
|
||||
* Parses a hexadecimal numeric entity.
|
||||
*
|
||||
* Equivalent to the `Hexademical character reference state` in the HTML spec.
|
||||
*
|
||||
* @param input The string containing the entity (or a continuation of the entity).
|
||||
* @param offset The current offset.
|
||||
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
||||
*/
|
||||
stateNumericHex(input, offset) {
|
||||
while (offset < input.length) {
|
||||
const char = input.charCodeAt(offset);
|
||||
if (isNumber(char) || isHexadecimalCharacter(char)) {
|
||||
// Convert hex digit to value (0-15); 'a'/'A' -> 10.
|
||||
const digit = char <= CharCodes.NINE
|
||||
? char - CharCodes.ZERO
|
||||
: (char | TO_LOWER_BIT) - CharCodes.LOWER_A + 10;
|
||||
this.result = this.result * 16 + digit;
|
||||
this.consumed++;
|
||||
offset++;
|
||||
}
|
||||
else {
|
||||
return this.emitNumericEntity(char, 3);
|
||||
}
|
||||
}
|
||||
return -1; // Incomplete entity
|
||||
}
|
||||
/**
|
||||
* Parses a decimal numeric entity.
|
||||
*
|
||||
* Equivalent to the `Decimal character reference state` in the HTML spec.
|
||||
*
|
||||
* @param input The string containing the entity (or a continuation of the entity).
|
||||
* @param offset The current offset.
|
||||
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
||||
*/
|
||||
stateNumericDecimal(input, offset) {
|
||||
while (offset < input.length) {
|
||||
const char = input.charCodeAt(offset);
|
||||
if (isNumber(char)) {
|
||||
this.result = this.result * 10 + (char - CharCodes.ZERO);
|
||||
this.consumed++;
|
||||
offset++;
|
||||
}
|
||||
else {
|
||||
return this.emitNumericEntity(char, 2);
|
||||
}
|
||||
}
|
||||
return -1; // Incomplete entity
|
||||
}
|
||||
/**
|
||||
* Validate and emit a numeric entity.
|
||||
*
|
||||
* Implements the logic from the `Hexademical character reference start
|
||||
* state` and `Numeric character reference end state` in the HTML spec.
|
||||
*
|
||||
* @param lastCp The last code point of the entity. Used to see if the
|
||||
* entity was terminated with a semicolon.
|
||||
* @param expectedLength The minimum number of characters that should be
|
||||
* consumed. Used to validate that at least one digit
|
||||
* was consumed.
|
||||
* @returns The number of characters that were consumed.
|
||||
*/
|
||||
emitNumericEntity(lastCp, expectedLength) {
|
||||
var _a;
|
||||
// Ensure we consumed at least one digit.
|
||||
if (this.consumed <= expectedLength) {
|
||||
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
||||
return 0;
|
||||
}
|
||||
// Figure out if this is a legit end of the entity
|
||||
if (lastCp === CharCodes.SEMI) {
|
||||
this.consumed += 1;
|
||||
}
|
||||
else if (this.decodeMode === DecodingMode.Strict) {
|
||||
return 0;
|
||||
}
|
||||
this.emitCodePoint((0, decode_codepoint_js_1.replaceCodePoint)(this.result), this.consumed);
|
||||
if (this.errors) {
|
||||
if (lastCp !== CharCodes.SEMI) {
|
||||
this.errors.missingSemicolonAfterCharacterReference();
|
||||
}
|
||||
this.errors.validateNumericCharacterReference(this.result);
|
||||
}
|
||||
return this.consumed;
|
||||
}
|
||||
/**
|
||||
* Parses a named entity.
|
||||
*
|
||||
* Equivalent to the `Named character reference state` in the HTML spec.
|
||||
*
|
||||
* @param input The string containing the entity (or a continuation of the entity).
|
||||
* @param offset The current offset.
|
||||
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
||||
*/
|
||||
stateNamedEntity(input, offset) {
|
||||
const { decodeTree } = this;
|
||||
let current = decodeTree[this.treeIndex];
|
||||
// The length is the number of bytes of the value, including the current byte.
|
||||
let valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
|
||||
while (offset < input.length) {
|
||||
// Handle compact runs (possibly inline): valueLength == 0 and SEMI_REQUIRED bit set.
|
||||
if (valueLength === 0 && (current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) !== 0) {
|
||||
const runLength = (current & bin_trie_flags_js_1.BinTrieFlags.BRANCH_LENGTH) >> 7; /* 2..63 */
|
||||
// If we are starting a run, check the first char.
|
||||
if (this.runConsumed === 0) {
|
||||
const firstChar = current & bin_trie_flags_js_1.BinTrieFlags.JUMP_TABLE;
|
||||
if (input.charCodeAt(offset) !== firstChar) {
|
||||
return this.result === 0
|
||||
? 0
|
||||
: this.emitNotTerminatedNamedEntity();
|
||||
}
|
||||
offset++;
|
||||
this.excess++;
|
||||
this.runConsumed++;
|
||||
}
|
||||
// Check remaining characters in the run.
|
||||
while (this.runConsumed < runLength) {
|
||||
if (offset >= input.length) {
|
||||
return -1;
|
||||
}
|
||||
const charIndexInPacked = this.runConsumed - 1;
|
||||
const packedWord = decodeTree[this.treeIndex + 1 + (charIndexInPacked >> 1)];
|
||||
const expectedChar = charIndexInPacked % 2 === 0
|
||||
? packedWord & 0xff
|
||||
: (packedWord >> 8) & 0xff;
|
||||
if (input.charCodeAt(offset) !== expectedChar) {
|
||||
this.runConsumed = 0;
|
||||
return this.result === 0
|
||||
? 0
|
||||
: this.emitNotTerminatedNamedEntity();
|
||||
}
|
||||
offset++;
|
||||
this.excess++;
|
||||
this.runConsumed++;
|
||||
}
|
||||
this.runConsumed = 0;
|
||||
this.treeIndex += 1 + (runLength >> 1);
|
||||
current = decodeTree[this.treeIndex];
|
||||
valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
|
||||
}
|
||||
if (offset >= input.length)
|
||||
break;
|
||||
const char = input.charCodeAt(offset);
|
||||
/*
|
||||
* Implicit semicolon handling for nodes that require a semicolon but
|
||||
* don't have an explicit ';' branch stored in the trie. If we have
|
||||
* a value on the current node, it requires a semicolon, and the
|
||||
* current input character is a semicolon, emit the entity using the
|
||||
* current node (without descending further).
|
||||
*/
|
||||
if (char === CharCodes.SEMI &&
|
||||
valueLength !== 0 &&
|
||||
(current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) !== 0) {
|
||||
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
|
||||
}
|
||||
this.treeIndex = determineBranch(decodeTree, current, this.treeIndex + Math.max(1, valueLength), char);
|
||||
if (this.treeIndex < 0) {
|
||||
return this.result === 0 ||
|
||||
// If we are parsing an attribute
|
||||
(this.decodeMode === DecodingMode.Attribute &&
|
||||
// We shouldn't have consumed any characters after the entity,
|
||||
(valueLength === 0 ||
|
||||
// And there should be no invalid characters.
|
||||
isEntityInAttributeInvalidEnd(char)))
|
||||
? 0
|
||||
: this.emitNotTerminatedNamedEntity();
|
||||
}
|
||||
current = decodeTree[this.treeIndex];
|
||||
valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
|
||||
// If the branch is a value, store it and continue
|
||||
if (valueLength !== 0) {
|
||||
// If the entity is terminated by a semicolon, we are done.
|
||||
if (char === CharCodes.SEMI) {
|
||||
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
|
||||
}
|
||||
// If we encounter a non-terminated (legacy) entity while parsing strictly, then ignore it.
|
||||
if (this.decodeMode !== DecodingMode.Strict &&
|
||||
(current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) === 0) {
|
||||
this.result = this.treeIndex;
|
||||
this.consumed += this.excess;
|
||||
this.excess = 0;
|
||||
}
|
||||
}
|
||||
// Increment offset & excess for next iteration
|
||||
offset++;
|
||||
this.excess++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/**
|
||||
* Emit a named entity that was not terminated with a semicolon.
|
||||
*
|
||||
* @returns The number of characters consumed.
|
||||
*/
|
||||
emitNotTerminatedNamedEntity() {
|
||||
var _a;
|
||||
const { result, decodeTree } = this;
|
||||
const valueLength = (decodeTree[result] & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
|
||||
this.emitNamedEntityData(result, valueLength, this.consumed);
|
||||
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.missingSemicolonAfterCharacterReference();
|
||||
return this.consumed;
|
||||
}
|
||||
/**
|
||||
* Emit a named entity.
|
||||
*
|
||||
* @param result The index of the entity in the decode tree.
|
||||
* @param valueLength The number of bytes in the entity.
|
||||
* @param consumed The number of characters consumed.
|
||||
*
|
||||
* @returns The number of characters consumed.
|
||||
*/
|
||||
emitNamedEntityData(result, valueLength, consumed) {
|
||||
const { decodeTree } = this;
|
||||
this.emitCodePoint(valueLength === 1
|
||||
? decodeTree[result] &
|
||||
~(bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH | bin_trie_flags_js_1.BinTrieFlags.FLAG13)
|
||||
: decodeTree[result + 1], consumed);
|
||||
if (valueLength === 3) {
|
||||
// For multi-byte values, we need to emit the second byte.
|
||||
this.emitCodePoint(decodeTree[result + 2], consumed);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
/**
|
||||
* Signal to the parser that the end of the input was reached.
|
||||
*
|
||||
* Remaining data will be emitted and relevant errors will be produced.
|
||||
*
|
||||
* @returns The number of characters consumed.
|
||||
*/
|
||||
end() {
|
||||
var _a;
|
||||
switch (this.state) {
|
||||
case EntityDecoderState.NamedEntity: {
|
||||
// Emit a named entity if we have one.
|
||||
return this.result !== 0 &&
|
||||
(this.decodeMode !== DecodingMode.Attribute ||
|
||||
this.result === this.treeIndex)
|
||||
? this.emitNotTerminatedNamedEntity()
|
||||
: 0;
|
||||
}
|
||||
// Otherwise, emit a numeric entity if we have one.
|
||||
case EntityDecoderState.NumericDecimal: {
|
||||
return this.emitNumericEntity(0, 2);
|
||||
}
|
||||
case EntityDecoderState.NumericHex: {
|
||||
return this.emitNumericEntity(0, 3);
|
||||
}
|
||||
case EntityDecoderState.NumericStart: {
|
||||
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
||||
return 0;
|
||||
}
|
||||
case EntityDecoderState.EntityStart: {
|
||||
// Return 0 if we have no entity.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.EntityDecoder = EntityDecoder;
|
||||
/**
|
||||
* Creates a function that decodes entities in a string.
|
||||
*
|
||||
* @param decodeTree The decode tree.
|
||||
* @returns A function that decodes entities in a string.
|
||||
*/
|
||||
function getDecoder(decodeTree) {
|
||||
let returnValue = "";
|
||||
const decoder = new EntityDecoder(decodeTree, (data) => (returnValue += (0, decode_codepoint_js_1.fromCodePoint)(data)));
|
||||
return function decodeWithTrie(input, decodeMode) {
|
||||
let lastIndex = 0;
|
||||
let offset = 0;
|
||||
while ((offset = input.indexOf("&", offset)) >= 0) {
|
||||
returnValue += input.slice(lastIndex, offset);
|
||||
decoder.startEntity(decodeMode);
|
||||
const length = decoder.write(input,
|
||||
// Skip the "&"
|
||||
offset + 1);
|
||||
if (length < 0) {
|
||||
lastIndex = offset + decoder.end();
|
||||
break;
|
||||
}
|
||||
lastIndex = offset + length;
|
||||
// If `length` is 0, skip the current `&` and continue.
|
||||
offset = length === 0 ? lastIndex + 1 : lastIndex;
|
||||
}
|
||||
const result = returnValue + input.slice(lastIndex);
|
||||
// Make sure we don't keep a reference to the final string.
|
||||
returnValue = "";
|
||||
return result;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Determines the branch of the current node that is taken given the current
|
||||
* character. This function is used to traverse the trie.
|
||||
*
|
||||
* @param decodeTree The trie.
|
||||
* @param current The current node.
|
||||
* @param nodeIdx The index right after the current node and its value.
|
||||
* @param char The current character.
|
||||
* @returns The index of the next node, or -1 if no branch is taken.
|
||||
*/
|
||||
function determineBranch(decodeTree, current, nodeIndex, char) {
|
||||
const branchCount = (current & bin_trie_flags_js_1.BinTrieFlags.BRANCH_LENGTH) >> 7;
|
||||
const jumpOffset = current & bin_trie_flags_js_1.BinTrieFlags.JUMP_TABLE;
|
||||
// Case 1: Single branch encoded in jump offset
|
||||
if (branchCount === 0) {
|
||||
return jumpOffset !== 0 && char === jumpOffset ? nodeIndex : -1;
|
||||
}
|
||||
// Case 2: Multiple branches encoded in jump table
|
||||
if (jumpOffset) {
|
||||
const value = char - jumpOffset;
|
||||
return value < 0 || value >= branchCount
|
||||
? -1
|
||||
: decodeTree[nodeIndex + value] - 1;
|
||||
}
|
||||
// Case 3: Multiple branches encoded in packed dictionary (two keys per uint16)
|
||||
const packedKeySlots = (branchCount + 1) >> 1;
|
||||
/*
|
||||
* Treat packed keys as a virtual sorted array of length `branchCount`.
|
||||
* Key(i) = low byte for even i, high byte for odd i in slot i>>1.
|
||||
*/
|
||||
let lo = 0;
|
||||
let hi = branchCount - 1;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >>> 1;
|
||||
const slot = mid >> 1;
|
||||
const packed = decodeTree[nodeIndex + slot];
|
||||
const midKey = (packed >> ((mid & 1) * 8)) & 0xff;
|
||||
if (midKey < char) {
|
||||
lo = mid + 1;
|
||||
}
|
||||
else if (midKey > char) {
|
||||
hi = mid - 1;
|
||||
}
|
||||
else {
|
||||
return decodeTree[nodeIndex + packedKeySlots + mid];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
const htmlDecoder = /* #__PURE__ */ getDecoder(decode_data_html_js_1.htmlDecodeTree);
|
||||
const xmlDecoder = /* #__PURE__ */ getDecoder(decode_data_xml_js_1.xmlDecodeTree);
|
||||
/**
|
||||
* Decodes an HTML string.
|
||||
*
|
||||
* @param htmlString The string to decode.
|
||||
* @param mode The decoding mode.
|
||||
* @returns The decoded string.
|
||||
*/
|
||||
function decodeHTML(htmlString, mode = DecodingMode.Legacy) {
|
||||
return htmlDecoder(htmlString, mode);
|
||||
}
|
||||
/**
|
||||
* Decodes an HTML string in an attribute.
|
||||
*
|
||||
* @param htmlAttribute The string to decode.
|
||||
* @returns The decoded string.
|
||||
*/
|
||||
function decodeHTMLAttribute(htmlAttribute) {
|
||||
return htmlDecoder(htmlAttribute, DecodingMode.Attribute);
|
||||
}
|
||||
/**
|
||||
* Decodes an HTML string, requiring all entities to be terminated by a semicolon.
|
||||
*
|
||||
* @param htmlString The string to decode.
|
||||
* @returns The decoded string.
|
||||
*/
|
||||
function decodeHTMLStrict(htmlString) {
|
||||
return htmlDecoder(htmlString, DecodingMode.Strict);
|
||||
}
|
||||
/**
|
||||
* Decodes an XML string, requiring all entities to be terminated by a semicolon.
|
||||
*
|
||||
* @param xmlString The string to decode.
|
||||
* @returns The decoded string.
|
||||
*/
|
||||
function decodeXML(xmlString) {
|
||||
return xmlDecoder(xmlString, DecodingMode.Strict);
|
||||
}
|
||||
var decode_codepoint_js_2 = require("./decode-codepoint.js");
|
||||
Object.defineProperty(exports, "decodeCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.decodeCodePoint; } });
|
||||
Object.defineProperty(exports, "fromCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.fromCodePoint; } });
|
||||
Object.defineProperty(exports, "replaceCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.replaceCodePoint; } });
|
||||
// Re-export for use by eg. htmlparser2
|
||||
var decode_data_html_js_2 = require("./generated/decode-data-html.js");
|
||||
Object.defineProperty(exports, "htmlDecodeTree", { enumerable: true, get: function () { return decode_data_html_js_2.htmlDecodeTree; } });
|
||||
var decode_data_xml_js_2 = require("./generated/decode-data-xml.js");
|
||||
Object.defineProperty(exports, "xmlDecodeTree", { enumerable: true, get: function () { return decode_data_xml_js_2.xmlDecodeTree; } });
|
||||
//# sourceMappingURL=decode.js.map
|
||||
Generated
Vendored
+7
File diff suppressed because one or more lines are too long
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
// Generated using scripts/write-decode-map.ts
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.xmlDecodeTree = void 0;
|
||||
const decode_shared_js_1 = require("../internal/decode-shared.js");
|
||||
exports.xmlDecodeTree = (0, decode_shared_js_1.decodeBase64)("AAJhZ2xxBwARABMAFQBtAg0AAAAAAA8AcAAmYG8AcwAnYHQAPmB0ADxg9SFvdCJg");
|
||||
//# sourceMappingURL=decode-data-xml.js.map
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinTrieFlags = void 0;
|
||||
/**
|
||||
* Bit flags & masks for the binary trie encoding used for entity decoding.
|
||||
*
|
||||
* Bit layout (16 bits total):
|
||||
* 15..14 VALUE_LENGTH (+1 encoding; 0 => no value)
|
||||
* 13 FLAG13. If valueLength>0: semicolon required flag (implicit ';').
|
||||
* If valueLength==0: compact run flag.
|
||||
* 12..7 BRANCH_LENGTH Branch length (0 => single branch in 6..0 if jumpOffset==char) OR run length (when compact run)
|
||||
* 6..0 JUMP_TABLE Jump offset (jump table) OR single-branch char code OR first run char
|
||||
*/
|
||||
var BinTrieFlags;
|
||||
(function (BinTrieFlags) {
|
||||
BinTrieFlags[BinTrieFlags["VALUE_LENGTH"] = 49152] = "VALUE_LENGTH";
|
||||
BinTrieFlags[BinTrieFlags["FLAG13"] = 8192] = "FLAG13";
|
||||
BinTrieFlags[BinTrieFlags["BRANCH_LENGTH"] = 8064] = "BRANCH_LENGTH";
|
||||
BinTrieFlags[BinTrieFlags["JUMP_TABLE"] = 127] = "JUMP_TABLE";
|
||||
})(BinTrieFlags || (exports.BinTrieFlags = BinTrieFlags = {}));
|
||||
//# sourceMappingURL=bin-trie-flags.js.map
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.decodeBase64 = decodeBase64;
|
||||
/*
|
||||
* Shared base64 decode helper for generated decode data.
|
||||
* Assumes global atob is available.
|
||||
*/
|
||||
function decodeBase64(input) {
|
||||
const binary =
|
||||
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
||||
typeof atob === "function"
|
||||
? // Browser (and Node >=16)
|
||||
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
||||
atob(input)
|
||||
: // Older Node versions (<16)
|
||||
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
||||
typeof Buffer.from === "function"
|
||||
? // eslint-disable-next-line n/no-unsupported-features/node-builtins
|
||||
Buffer.from(input, "base64").toString("binary")
|
||||
: // eslint-disable-next-line unicorn/no-new-buffer, n/no-deprecated-api
|
||||
new Buffer(input, "base64").toString("binary");
|
||||
const evenLength = binary.length & ~1; // Round down to even length
|
||||
const out = new Uint16Array(evenLength / 2);
|
||||
for (let index = 0, outIndex = 0; index < evenLength; index += 2) {
|
||||
const lo = binary.charCodeAt(index);
|
||||
const hi = binary.charCodeAt(index + 1);
|
||||
out[outIndex++] = lo | (hi << 8);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
//# sourceMappingURL=decode-shared.js.map
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"name": "entities",
|
||||
"version": "7.0.1",
|
||||
"description": "Encode & decode XML and HTML entities with ease & speed",
|
||||
"keywords": [
|
||||
"html entities",
|
||||
"entity decoder",
|
||||
"entity encoding",
|
||||
"html decoding",
|
||||
"html encoding",
|
||||
"xml decoding",
|
||||
"xml encoding"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fb55/entities.git"
|
||||
},
|
||||
"funding": "https://github.com/fb55/entities?sponsor=1",
|
||||
"license": "BSD-2-Clause",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
},
|
||||
"./decode": {
|
||||
"import": {
|
||||
"types": "./dist/esm/decode.d.ts",
|
||||
"default": "./dist/esm/decode.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/decode.d.ts",
|
||||
"default": "./dist/commonjs/decode.js"
|
||||
}
|
||||
},
|
||||
"./escape": {
|
||||
"import": {
|
||||
"types": "./dist/esm/escape.d.ts",
|
||||
"default": "./dist/esm/escape.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/escape.d.ts",
|
||||
"default": "./dist/commonjs/escape.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"files": [
|
||||
"decode.js",
|
||||
"decode.d.ts",
|
||||
"escape.js",
|
||||
"escape.d.ts",
|
||||
"dist",
|
||||
"src",
|
||||
"!**/*.spec.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build:docs": "typedoc --hideGenerator src/index.ts",
|
||||
"build:encode-trie": "node --import=tsx scripts/write-encode-map.ts",
|
||||
"build:trie": "node --import=tsx scripts/write-decode-map.ts",
|
||||
"benchmark": "node --import=tsx scripts/benchmark.ts",
|
||||
"format": "npm run format:es && npm run format:biome",
|
||||
"format:es": "npm run lint:es -- --fix",
|
||||
"format:biome": "biome check --fix .",
|
||||
"lint": "npm run lint:es && npm run lint:ts && npm run lint:biome",
|
||||
"lint:es": "eslint . --ignore-path .gitignore",
|
||||
"lint:biome": "biome check .",
|
||||
"lint:ts": "tsc --noEmit",
|
||||
"prepublishOnly": "tshy",
|
||||
"test": "npm run test:vi && npm run lint",
|
||||
"test:vi": "vitest run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.11",
|
||||
"@types/node": "^25.0.9",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@types/he": "^1.2.3",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-biome": "^2.1.3",
|
||||
"eslint-plugin-n": "^17.23.2",
|
||||
"eslint-plugin-unicorn": "^56.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-entities": "^2.6.0",
|
||||
"parse-entities": "^4.0.2",
|
||||
"tinybench": "^5.1.0",
|
||||
"tshy": "^3.1.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typedoc": "^0.28.16",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"tshy": {
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/__fixtures__/*",
|
||||
"**/__tests__/*",
|
||||
"**/__snapshots__/*"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./decode": "./src/decode.ts",
|
||||
"./escape": "./src/escape.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = global || self, factory(global.estreeWalker = {}));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
// @ts-check
|
||||
/** @typedef { import('estree').BaseNode} BaseNode */
|
||||
|
||||
/** @typedef {{
|
||||
skip: () => void;
|
||||
remove: () => void;
|
||||
replace: (node: BaseNode) => void;
|
||||
}} WalkerContext */
|
||||
|
||||
class WalkerBase {
|
||||
constructor() {
|
||||
/** @type {boolean} */
|
||||
this.should_skip = false;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.should_remove = false;
|
||||
|
||||
/** @type {BaseNode | null} */
|
||||
this.replacement = null;
|
||||
|
||||
/** @type {WalkerContext} */
|
||||
this.context = {
|
||||
skip: () => (this.should_skip = true),
|
||||
remove: () => (this.should_remove = true),
|
||||
replace: (node) => (this.replacement = node)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} parent
|
||||
* @param {string} prop
|
||||
* @param {number} index
|
||||
* @param {BaseNode} node
|
||||
*/
|
||||
replace(parent, prop, index, node) {
|
||||
if (parent) {
|
||||
if (index !== null) {
|
||||
parent[prop][index] = node;
|
||||
} else {
|
||||
parent[prop] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} parent
|
||||
* @param {string} prop
|
||||
* @param {number} index
|
||||
*/
|
||||
remove(parent, prop, index) {
|
||||
if (parent) {
|
||||
if (index !== null) {
|
||||
parent[prop].splice(index, 1);
|
||||
} else {
|
||||
delete parent[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @typedef { import('estree').BaseNode} BaseNode */
|
||||
/** @typedef { import('./walker.js').WalkerContext} WalkerContext */
|
||||
|
||||
/** @typedef {(
|
||||
* this: WalkerContext,
|
||||
* node: BaseNode,
|
||||
* parent: BaseNode,
|
||||
* key: string,
|
||||
* index: number
|
||||
* ) => void} SyncHandler */
|
||||
|
||||
class SyncWalker extends WalkerBase {
|
||||
/**
|
||||
*
|
||||
* @param {SyncHandler} enter
|
||||
* @param {SyncHandler} leave
|
||||
*/
|
||||
constructor(enter, leave) {
|
||||
super();
|
||||
|
||||
/** @type {SyncHandler} */
|
||||
this.enter = enter;
|
||||
|
||||
/** @type {SyncHandler} */
|
||||
this.leave = leave;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseNode} node
|
||||
* @param {BaseNode} parent
|
||||
* @param {string} [prop]
|
||||
* @param {number} [index]
|
||||
* @returns {BaseNode}
|
||||
*/
|
||||
visit(node, parent, prop, index) {
|
||||
if (node) {
|
||||
if (this.enter) {
|
||||
const _should_skip = this.should_skip;
|
||||
const _should_remove = this.should_remove;
|
||||
const _replacement = this.replacement;
|
||||
this.should_skip = false;
|
||||
this.should_remove = false;
|
||||
this.replacement = null;
|
||||
|
||||
this.enter.call(this.context, node, parent, prop, index);
|
||||
|
||||
if (this.replacement) {
|
||||
node = this.replacement;
|
||||
this.replace(parent, prop, index, node);
|
||||
}
|
||||
|
||||
if (this.should_remove) {
|
||||
this.remove(parent, prop, index);
|
||||
}
|
||||
|
||||
const skipped = this.should_skip;
|
||||
const removed = this.should_remove;
|
||||
|
||||
this.should_skip = _should_skip;
|
||||
this.should_remove = _should_remove;
|
||||
this.replacement = _replacement;
|
||||
|
||||
if (skipped) return node;
|
||||
if (removed) return null;
|
||||
}
|
||||
|
||||
for (const key in node) {
|
||||
const value = node[key];
|
||||
|
||||
if (typeof value !== "object") {
|
||||
continue;
|
||||
} else if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
if (value[i] !== null && typeof value[i].type === 'string') {
|
||||
if (!this.visit(value[i], node, key, i)) {
|
||||
// removed
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value !== null && typeof value.type === "string") {
|
||||
this.visit(value, node, key, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.leave) {
|
||||
const _replacement = this.replacement;
|
||||
const _should_remove = this.should_remove;
|
||||
this.replacement = null;
|
||||
this.should_remove = false;
|
||||
|
||||
this.leave.call(this.context, node, parent, prop, index);
|
||||
|
||||
if (this.replacement) {
|
||||
node = this.replacement;
|
||||
this.replace(parent, prop, index, node);
|
||||
}
|
||||
|
||||
if (this.should_remove) {
|
||||
this.remove(parent, prop, index);
|
||||
}
|
||||
|
||||
const removed = this.should_remove;
|
||||
|
||||
this.replacement = _replacement;
|
||||
this.should_remove = _should_remove;
|
||||
|
||||
if (removed) return null;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @typedef { import('estree').BaseNode} BaseNode */
|
||||
/** @typedef { import('./walker').WalkerContext} WalkerContext */
|
||||
|
||||
/** @typedef {(
|
||||
* this: WalkerContext,
|
||||
* node: BaseNode,
|
||||
* parent: BaseNode,
|
||||
* key: string,
|
||||
* index: number
|
||||
* ) => Promise<void>} AsyncHandler */
|
||||
|
||||
class AsyncWalker extends WalkerBase {
|
||||
/**
|
||||
*
|
||||
* @param {AsyncHandler} enter
|
||||
* @param {AsyncHandler} leave
|
||||
*/
|
||||
constructor(enter, leave) {
|
||||
super();
|
||||
|
||||
/** @type {AsyncHandler} */
|
||||
this.enter = enter;
|
||||
|
||||
/** @type {AsyncHandler} */
|
||||
this.leave = leave;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseNode} node
|
||||
* @param {BaseNode} parent
|
||||
* @param {string} [prop]
|
||||
* @param {number} [index]
|
||||
* @returns {Promise<BaseNode>}
|
||||
*/
|
||||
async visit(node, parent, prop, index) {
|
||||
if (node) {
|
||||
if (this.enter) {
|
||||
const _should_skip = this.should_skip;
|
||||
const _should_remove = this.should_remove;
|
||||
const _replacement = this.replacement;
|
||||
this.should_skip = false;
|
||||
this.should_remove = false;
|
||||
this.replacement = null;
|
||||
|
||||
await this.enter.call(this.context, node, parent, prop, index);
|
||||
|
||||
if (this.replacement) {
|
||||
node = this.replacement;
|
||||
this.replace(parent, prop, index, node);
|
||||
}
|
||||
|
||||
if (this.should_remove) {
|
||||
this.remove(parent, prop, index);
|
||||
}
|
||||
|
||||
const skipped = this.should_skip;
|
||||
const removed = this.should_remove;
|
||||
|
||||
this.should_skip = _should_skip;
|
||||
this.should_remove = _should_remove;
|
||||
this.replacement = _replacement;
|
||||
|
||||
if (skipped) return node;
|
||||
if (removed) return null;
|
||||
}
|
||||
|
||||
for (const key in node) {
|
||||
const value = node[key];
|
||||
|
||||
if (typeof value !== "object") {
|
||||
continue;
|
||||
} else if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
if (value[i] !== null && typeof value[i].type === 'string') {
|
||||
if (!(await this.visit(value[i], node, key, i))) {
|
||||
// removed
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value !== null && typeof value.type === "string") {
|
||||
await this.visit(value, node, key, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.leave) {
|
||||
const _replacement = this.replacement;
|
||||
const _should_remove = this.should_remove;
|
||||
this.replacement = null;
|
||||
this.should_remove = false;
|
||||
|
||||
await this.leave.call(this.context, node, parent, prop, index);
|
||||
|
||||
if (this.replacement) {
|
||||
node = this.replacement;
|
||||
this.replace(parent, prop, index, node);
|
||||
}
|
||||
|
||||
if (this.should_remove) {
|
||||
this.remove(parent, prop, index);
|
||||
}
|
||||
|
||||
const removed = this.should_remove;
|
||||
|
||||
this.replacement = _replacement;
|
||||
this.should_remove = _should_remove;
|
||||
|
||||
if (removed) return null;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @typedef { import('estree').BaseNode} BaseNode */
|
||||
/** @typedef { import('./sync.js').SyncHandler} SyncHandler */
|
||||
/** @typedef { import('./async.js').AsyncHandler} AsyncHandler */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseNode} ast
|
||||
* @param {{
|
||||
* enter?: SyncHandler
|
||||
* leave?: SyncHandler
|
||||
* }} walker
|
||||
* @returns {BaseNode}
|
||||
*/
|
||||
function walk(ast, { enter, leave }) {
|
||||
const instance = new SyncWalker(enter, leave);
|
||||
return instance.visit(ast, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BaseNode} ast
|
||||
* @param {{
|
||||
* enter?: AsyncHandler
|
||||
* leave?: AsyncHandler
|
||||
* }} walker
|
||||
* @returns {Promise<BaseNode>}
|
||||
*/
|
||||
async function asyncWalk(ast, { enter, leave }) {
|
||||
const instance = new AsyncWalker(enter, leave);
|
||||
return await instance.visit(ast, null);
|
||||
}
|
||||
|
||||
exports.asyncWalk = asyncWalk;
|
||||
exports.walk = walk;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
})));
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "estree-walker",
|
||||
"description": "Traverse an ESTree-compliant AST",
|
||||
"version": "2.0.2",
|
||||
"private": false,
|
||||
"author": "Rich Harris",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Rich-Harris/estree-walker"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"main": "./dist/umd/estree-walker.js",
|
||||
"module": "./dist/esm/estree-walker.js",
|
||||
"exports": {
|
||||
"require": "./dist/umd/estree-walker.js",
|
||||
"import": "./dist/esm/estree-walker.js"
|
||||
},
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build && npm test",
|
||||
"build": "tsc && rollup -c",
|
||||
"test": "uvu test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "0.0.42",
|
||||
"rollup": "^2.10.9",
|
||||
"typescript": "^3.7.5",
|
||||
"uvu": "^0.5.1"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist",
|
||||
"types",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
function flatHooks(configHooks, hooks = {}, parentName) {
|
||||
for (const key in configHooks) {
|
||||
const subHook = configHooks[key];
|
||||
const name = parentName ? `${parentName}:${key}` : key;
|
||||
if (typeof subHook === "object" && subHook !== null) flatHooks(subHook, hooks, name);
|
||||
else if (typeof subHook === "function") hooks[name] = subHook;
|
||||
}
|
||||
return hooks;
|
||||
}
|
||||
function mergeHooks(...hooks) {
|
||||
const finalHooks = {};
|
||||
for (const hook of hooks) {
|
||||
const flatenHook = flatHooks(hook);
|
||||
for (const key in flatenHook) if (finalHooks[key]) finalHooks[key].push(flatenHook[key]);
|
||||
else finalHooks[key] = [flatenHook[key]];
|
||||
}
|
||||
for (const key in finalHooks) if (finalHooks[key].length > 1) {
|
||||
const array = finalHooks[key];
|
||||
finalHooks[key] = (...arguments_) => serial(array, (function_) => function_(...arguments_));
|
||||
} else finalHooks[key] = finalHooks[key][0];
|
||||
return finalHooks;
|
||||
}
|
||||
function serial(tasks, function_) {
|
||||
return tasks.reduce((promise, task) => promise.then(() => function_(task)), Promise.resolve());
|
||||
}
|
||||
const createTask = /* @__PURE__ */ (() => {
|
||||
if (console.createTask) return console.createTask;
|
||||
const defaultTask = { run: (fn) => fn() };
|
||||
return () => defaultTask;
|
||||
})();
|
||||
function callHooks(hooks, args, startIndex, task) {
|
||||
for (let i = startIndex; i < hooks.length; i += 1) try {
|
||||
const result = task ? task.run(() => hooks[i](...args)) : hooks[i](...args);
|
||||
if (result && typeof result.then === "function") return Promise.resolve(result).then(() => callHooks(hooks, args, i + 1, task));
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
function serialTaskCaller(hooks, args, name) {
|
||||
if (hooks.length > 0) return callHooks(hooks, args, 0, createTask(name));
|
||||
}
|
||||
function parallelTaskCaller(hooks, args, name) {
|
||||
if (hooks.length > 0) {
|
||||
const task = createTask(name);
|
||||
return Promise.all(hooks.map((hook) => task.run(() => hook(...args))));
|
||||
}
|
||||
}
|
||||
function serialCaller(hooks, arguments_) {
|
||||
return hooks.reduce((promise, hookFunction) => promise.then(() => hookFunction(...arguments_ || [])), Promise.resolve());
|
||||
}
|
||||
function parallelCaller(hooks, args) {
|
||||
return Promise.all(hooks.map((hook) => hook(...args || [])));
|
||||
}
|
||||
function callEachWith(callbacks, arg0) {
|
||||
for (const callback of [...callbacks]) callback(arg0);
|
||||
}
|
||||
var Hookable = class {
|
||||
_hooks;
|
||||
_before;
|
||||
_after;
|
||||
_deprecatedHooks;
|
||||
_deprecatedMessages;
|
||||
constructor() {
|
||||
this._hooks = {};
|
||||
this._before = void 0;
|
||||
this._after = void 0;
|
||||
this._deprecatedMessages = void 0;
|
||||
this._deprecatedHooks = {};
|
||||
this.hook = this.hook.bind(this);
|
||||
this.callHook = this.callHook.bind(this);
|
||||
this.callHookWith = this.callHookWith.bind(this);
|
||||
}
|
||||
hook(name, function_, options = {}) {
|
||||
if (!name || typeof function_ !== "function") return () => {};
|
||||
const originalName = name;
|
||||
let dep;
|
||||
while (this._deprecatedHooks[name]) {
|
||||
dep = this._deprecatedHooks[name];
|
||||
name = dep.to;
|
||||
}
|
||||
if (dep && !options.allowDeprecated) {
|
||||
let message = dep.message;
|
||||
if (!message) message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
|
||||
if (!this._deprecatedMessages) this._deprecatedMessages = /* @__PURE__ */ new Set();
|
||||
if (!this._deprecatedMessages.has(message)) {
|
||||
console.warn(message);
|
||||
this._deprecatedMessages.add(message);
|
||||
}
|
||||
}
|
||||
if (!function_.name) try {
|
||||
Object.defineProperty(function_, "name", {
|
||||
get: () => "_" + name.replace(/\W+/g, "_") + "_hook_cb",
|
||||
configurable: true
|
||||
});
|
||||
} catch {}
|
||||
this._hooks[name] = this._hooks[name] || [];
|
||||
this._hooks[name].push(function_);
|
||||
return () => {
|
||||
if (function_) {
|
||||
this.removeHook(name, function_);
|
||||
function_ = void 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
hookOnce(name, function_) {
|
||||
let _unreg;
|
||||
let _function = (...arguments_) => {
|
||||
if (typeof _unreg === "function") _unreg();
|
||||
_unreg = void 0;
|
||||
_function = void 0;
|
||||
return function_(...arguments_);
|
||||
};
|
||||
_unreg = this.hook(name, _function);
|
||||
return _unreg;
|
||||
}
|
||||
removeHook(name, function_) {
|
||||
const hooks = this._hooks[name];
|
||||
if (hooks) {
|
||||
const index = hooks.indexOf(function_);
|
||||
if (index !== -1) hooks.splice(index, 1);
|
||||
if (hooks.length === 0) this._hooks[name] = void 0;
|
||||
}
|
||||
}
|
||||
clearHook(name) {
|
||||
this._hooks[name] = void 0;
|
||||
}
|
||||
deprecateHook(name, deprecated) {
|
||||
this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
|
||||
const _hooks = this._hooks[name] || [];
|
||||
this._hooks[name] = void 0;
|
||||
for (const hook of _hooks) this.hook(name, hook);
|
||||
}
|
||||
deprecateHooks(deprecatedHooks) {
|
||||
for (const name in deprecatedHooks) this.deprecateHook(name, deprecatedHooks[name]);
|
||||
}
|
||||
addHooks(configHooks) {
|
||||
const hooks = flatHooks(configHooks);
|
||||
const removeFns = Object.keys(hooks).map((key) => this.hook(key, hooks[key]));
|
||||
return () => {
|
||||
for (const unreg of removeFns) unreg();
|
||||
removeFns.length = 0;
|
||||
};
|
||||
}
|
||||
removeHooks(configHooks) {
|
||||
const hooks = flatHooks(configHooks);
|
||||
for (const key in hooks) this.removeHook(key, hooks[key]);
|
||||
}
|
||||
removeAllHooks() {
|
||||
this._hooks = {};
|
||||
}
|
||||
callHook(name, ...args) {
|
||||
return this.callHookWith(serialTaskCaller, name, args);
|
||||
}
|
||||
callHookParallel(name, ...args) {
|
||||
return this.callHookWith(parallelTaskCaller, name, args);
|
||||
}
|
||||
callHookWith(caller, name, args) {
|
||||
const event = this._before || this._after ? {
|
||||
name,
|
||||
args,
|
||||
context: {}
|
||||
} : void 0;
|
||||
if (this._before) callEachWith(this._before, event);
|
||||
const result = caller(this._hooks[name] ? [...this._hooks[name]] : [], args, name);
|
||||
if (result instanceof Promise) return result.finally(() => {
|
||||
if (this._after && event) callEachWith(this._after, event);
|
||||
});
|
||||
if (this._after && event) callEachWith(this._after, event);
|
||||
return result;
|
||||
}
|
||||
beforeEach(function_) {
|
||||
this._before = this._before || [];
|
||||
this._before.push(function_);
|
||||
return () => {
|
||||
if (this._before !== void 0) {
|
||||
const index = this._before.indexOf(function_);
|
||||
if (index !== -1) this._before.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
afterEach(function_) {
|
||||
this._after = this._after || [];
|
||||
this._after.push(function_);
|
||||
return () => {
|
||||
if (this._after !== void 0) {
|
||||
const index = this._after.indexOf(function_);
|
||||
if (index !== -1) this._after.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
function createHooks() {
|
||||
return new Hookable();
|
||||
}
|
||||
var HookableCore = class {
|
||||
_hooks;
|
||||
constructor() {
|
||||
this._hooks = {};
|
||||
}
|
||||
hook(name, fn) {
|
||||
if (!name || typeof fn !== "function") return () => {};
|
||||
this._hooks[name] = this._hooks[name] || [];
|
||||
this._hooks[name].push(fn);
|
||||
return () => {
|
||||
if (fn) {
|
||||
this.removeHook(name, fn);
|
||||
fn = void 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
removeHook(name, function_) {
|
||||
const hooks = this._hooks[name];
|
||||
if (hooks) {
|
||||
const index = hooks.indexOf(function_);
|
||||
if (index !== -1) hooks.splice(index, 1);
|
||||
if (hooks.length === 0) this._hooks[name] = void 0;
|
||||
}
|
||||
}
|
||||
callHook(name, ...args) {
|
||||
const hooks = this._hooks[name];
|
||||
if (!hooks || hooks.length === 0) return;
|
||||
return callHooks(hooks.slice(), args, 0);
|
||||
}
|
||||
};
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
function createDebugger(hooks, _options = {}) {
|
||||
const options = {
|
||||
inspect: isBrowser,
|
||||
group: isBrowser,
|
||||
filter: () => true,
|
||||
..._options
|
||||
};
|
||||
const _filter = options.filter;
|
||||
const filter = typeof _filter === "string" ? (name) => name.startsWith(_filter) : _filter;
|
||||
const _tag = options.tag ? `[${options.tag}] ` : "";
|
||||
const logPrefix = (event) => _tag + event.name + "".padEnd(event._id, "\0");
|
||||
const _idCtr = {};
|
||||
const unsubscribeBefore = hooks.beforeEach((event) => {
|
||||
if (filter !== void 0 && !filter(event.name)) return;
|
||||
_idCtr[event.name] = _idCtr[event.name] || 0;
|
||||
event._id = _idCtr[event.name]++;
|
||||
console.time(logPrefix(event));
|
||||
});
|
||||
const unsubscribeAfter = hooks.afterEach((event) => {
|
||||
if (filter !== void 0 && !filter(event.name)) return;
|
||||
if (options.group) console.groupCollapsed(event.name);
|
||||
if (options.inspect) console.timeLog(logPrefix(event), event.args);
|
||||
else console.timeEnd(logPrefix(event));
|
||||
if (options.group) console.groupEnd();
|
||||
_idCtr[event.name]--;
|
||||
});
|
||||
return { close: () => {
|
||||
unsubscribeBefore();
|
||||
unsubscribeAfter();
|
||||
} };
|
||||
}
|
||||
export { Hookable, HookableCore, createDebugger, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "hookable",
|
||||
"version": "6.1.2",
|
||||
"description": "Awaitable hook system",
|
||||
"keywords": [
|
||||
"hook",
|
||||
"hookable",
|
||||
"plugin",
|
||||
"tapable",
|
||||
"tappable"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "unjs/hookable"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"exports": {
|
||||
".": "./dist/index.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.6",
|
||||
"@vitest/coverage-v8": "^5",
|
||||
"changelogen": "^0.6.2",
|
||||
"esbuild": "^0.28.2",
|
||||
"eslint-config-unjs": "^0.6.2",
|
||||
"expect-type": "^1.4.0",
|
||||
"hookable-prev": "npm:hookable@^6.1.1",
|
||||
"mitata": "^1.0.34",
|
||||
"obuild": "^0.4.40",
|
||||
"oxfmt": ">=0.67.0",
|
||||
"oxlint": "^1.82.0",
|
||||
"typescript": "^6",
|
||||
"vite": "^8.3.0",
|
||||
"vitest": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node --expose-gc --allow-natives-syntax test/bench.ts",
|
||||
"build": "obuild src/index.ts",
|
||||
"dev": "vitest",
|
||||
"lint": "oxlint . && oxfmt --check src test",
|
||||
"lint:fix": "oxlint . --fix && oxfmt src test",
|
||||
"prepublish": "pnpm build",
|
||||
"release": "pnpm test && pnpm build && changelogen --release --publish --push",
|
||||
"test": "pnpm lint && vitest run --coverage",
|
||||
"test:types": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//#region src/formatters/ansi.ts
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
function ansiFormatter(colors) {
|
||||
return (d) => {
|
||||
const header = `${colors.bold(colors.red(`[${d.name}]`))} ${d.message}`;
|
||||
const details = [];
|
||||
if (d.fix) details.push(`${colors.dim("fix:")} ${d.fix}`);
|
||||
if (d.sources?.length) details.push(`${colors.dim("sources:")} ${d.sources.join(", ")}`);
|
||||
if (d.docs) details.push(`${colors.dim("see:")} ${colors.cyan(d.docs)}`);
|
||||
if (details.length === 0) return header;
|
||||
return [header, ...details.map((detail, i) => {
|
||||
return `${colors.dim(i < details.length - 1 ? "├▶" : "╰▶")} ${detail}`;
|
||||
})].join("\n");
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { ansiFormatter };
|
||||
|
||||
//# sourceMappingURL=ansi.mjs.map
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
//#region src/formatters/plain.ts
|
||||
/**
|
||||
* Renders a diagnostic into a multi-line, unicode-decorated string suitable
|
||||
* for terminal output. The first line is `[<name>] <message>`; optional
|
||||
* details (`fix`, `sources`, `docs`) follow with `├▶`/`╰▶` connectors.
|
||||
*/
|
||||
function formatDiagnostic(diagnostic) {
|
||||
const header = `[${diagnostic.name}] ${diagnostic.message}`;
|
||||
const details = [];
|
||||
if (diagnostic.fix) details.push(`fix: ${diagnostic.fix}`);
|
||||
if (diagnostic.sources?.length) details.push(`sources: ${diagnostic.sources.join(", ")}`);
|
||||
if (diagnostic.docs) details.push(`see: ${diagnostic.docs}`);
|
||||
if (details.length === 0) return header;
|
||||
return [header, ...details.map((detail, i) => {
|
||||
return `${i < details.length - 1 ? "├▶" : "╰▶"} ${detail}`;
|
||||
})].join("\n");
|
||||
}
|
||||
//#endregion
|
||||
//#region src/utils.ts
|
||||
/**
|
||||
* Transforms a value or a function that returns a value to a value.
|
||||
*
|
||||
* @param valFn either a value or a function that returns a value
|
||||
* @param args arguments to pass to the function if `valFn` is a function
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function toValueWithArgs(valFn, ...args) {
|
||||
return typeof valFn === "function" ? valFn(...args) : valFn;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/diagnostic.ts
|
||||
/**
|
||||
* Creates a console reporter that renders each diagnostic with `formatter` and
|
||||
* prints the result via `console[method]`. Both default sensibly (`'warn'` and
|
||||
* {@link formatDiagnostic}); `method` can also be overridden per call through
|
||||
* the reporter options.
|
||||
*/
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
function createConsoleReporter({ method: defaultMethod = "warn", formatter = formatDiagnostic } = {}) {
|
||||
return (diagnostic, { method = defaultMethod } = {}) => {
|
||||
console[method](formatter(diagnostic));
|
||||
};
|
||||
}
|
||||
const captureStackTrace = Error.captureStackTrace;
|
||||
var Diagnostic = class Diagnostic extends Error {
|
||||
name;
|
||||
/**
|
||||
* The diagnostic code, e.g. `MATH_E001`.
|
||||
* Also appears as the `name` property.
|
||||
*/
|
||||
code;
|
||||
/**
|
||||
* URL to extended documentation for this diagnostic code.
|
||||
* Auto-generated from {@link DefineDiagnosticsOptions.docsBase}.
|
||||
*/
|
||||
docs;
|
||||
/**
|
||||
* Optional actionable instructions on how to resolve the problem.
|
||||
*/
|
||||
fix;
|
||||
/**
|
||||
* Locations in user code that contributed to this diagnostic, in
|
||||
* `file:line:column` format. Relevant when the stack trace doesn't reflect
|
||||
* the user's source (e.g. compilers, bundlers), otherwise redundant with the
|
||||
* stack and should be omitted.
|
||||
*/
|
||||
sources;
|
||||
/**
|
||||
* Structured information associated with this diagnostic code.
|
||||
*/
|
||||
data;
|
||||
/**
|
||||
* Alias for {@link Error.message}: the reason this diagnostic was raised.
|
||||
*/
|
||||
get why() {
|
||||
return this.message;
|
||||
}
|
||||
/**
|
||||
* @param init structured initializer; `why` is required
|
||||
* @param captureFrom V8 stack-cutoff frame. Defaults to {@link Diagnostic}
|
||||
* so the top of the trace is the `new Diagnostic(...)` call site.
|
||||
* `defineDiagnostics` passes its action method to strip its own frames too.
|
||||
* Ignored on engines without `Error.captureStackTrace`.
|
||||
*/
|
||||
constructor(init, captureFrom = Diagnostic) {
|
||||
super(init.why, { cause: init.cause });
|
||||
this.code = this.name = init.code;
|
||||
this.fix = init.fix;
|
||||
this.docs = init.docs;
|
||||
this.sources = init.sources;
|
||||
this.data = init.data;
|
||||
captureStackTrace?.(this, captureFrom);
|
||||
}
|
||||
/**
|
||||
* Converts the diagnostic into a serializable structured object.
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
why: this.why,
|
||||
fix: this.fix,
|
||||
docs: this.docs,
|
||||
sources: this.sources,
|
||||
cause: this.cause,
|
||||
data: this.data,
|
||||
stack: this.stack
|
||||
};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Resolves the docs URL for a code from a `docsBase` (string template or
|
||||
* resolver function). Shared by {@link defineDiagnostics} and
|
||||
* {@link defineProdDiagnostics}. Per-code `docs` overrides are handled by the
|
||||
* caller; this only covers the `docsBase`-derived case.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function deriveDocs(docsBase, code) {
|
||||
return typeof docsBase === "string" ? `${docsBase}/${code.toLowerCase()}` : docsBase?.(code);
|
||||
}
|
||||
/**
|
||||
* Creates a typed diagnostics object from a set of code definitions. Each
|
||||
* code becomes a callable {@link DiagnosticHandle}: invoke to report, or
|
||||
* `throw` the result to raise. No `new` required, no proxy.
|
||||
*/
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
function defineDiagnostics(options) {
|
||||
const reporters = options.reporters ?? [];
|
||||
const result = {};
|
||||
const { docsBase } = options;
|
||||
for (const code of Object.keys(options.codes)) {
|
||||
const def = options.codes[code];
|
||||
const docs = def.docs === false ? void 0 : def.docs || deriveDocs(docsBase, code);
|
||||
const handle = (params = {}, reporterOptions = {}) => {
|
||||
const diagnostic = new Diagnostic({
|
||||
code,
|
||||
why: toValueWithArgs(def.why, params),
|
||||
fix: toValueWithArgs(def.fix, params),
|
||||
docs,
|
||||
cause: params.cause,
|
||||
sources: params.sources,
|
||||
data: toValueWithArgs(def.data, params)
|
||||
}, handle);
|
||||
for (const reporter of reporters) reporter(diagnostic, reporterOptions);
|
||||
return diagnostic;
|
||||
};
|
||||
result[code] = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/prod-diagnostics.ts
|
||||
/**
|
||||
* Production counterpart to {@link defineDiagnostics}. Returns a `Proxy` that
|
||||
* builds a minimal {@link Diagnostic} for any accessed code: the code becomes
|
||||
* the instance `name`, `docs` is derived from `docsBase`, and `why` points to
|
||||
* the docs URL when one exists (empty otherwise, so the thrown header is just
|
||||
* the code). It carries no catalog text or data, so it stays tiny in a bundle.
|
||||
*
|
||||
* The strip plugin (`@nostics/unplugin`) can rewrite a `defineDiagnostics()`
|
||||
* call into a `process.env.NODE_ENV === 'production'` ternary that selects this
|
||||
* factory in production, dropping every `why`/`fix` string from the bundle.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const diagnostics = defineProdDiagnostics({ docsBase: 'https://docs.example.com' })
|
||||
* throw diagnostics.NUXT_B2011() // NUXT_B2011: https://docs.example.com/nuxt_b2011
|
||||
* ```
|
||||
*/
|
||||
/* @__NO_SIDE_EFFECTS__ */
|
||||
function defineProdDiagnostics(options = {}) {
|
||||
const { docsBase, reporters = [] } = options;
|
||||
return new Proxy({}, { get(_target, code) {
|
||||
if (typeof code !== "string") return void 0;
|
||||
const handle = (params = {}, reporterOptions = {}) => {
|
||||
const docs = deriveDocs(docsBase, code);
|
||||
const diagnostic = new Diagnostic({
|
||||
code,
|
||||
why: docs ?? "",
|
||||
docs,
|
||||
cause: params.cause,
|
||||
sources: params.sources
|
||||
}, handle);
|
||||
for (const reporter of reporters) reporter(diagnostic, reporterOptions);
|
||||
return diagnostic;
|
||||
};
|
||||
return handle;
|
||||
} });
|
||||
}
|
||||
//#endregion
|
||||
export { Diagnostic, createConsoleReporter, defineDiagnostics, defineProdDiagnostics, formatDiagnostic };
|
||||
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"name": "nostics",
|
||||
"type": "module",
|
||||
"version": "1.3.0",
|
||||
"description": "Structured diagnostic code library",
|
||||
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
||||
"contributors": [
|
||||
"Eduardo San Martin Morote <posva13@gmail.com>",
|
||||
"Daniel Roe <daniel@roe.dev>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/vercel-labs/nostics#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vercel-labs/nostics.git"
|
||||
},
|
||||
"bugs": "https://github.com/vercel-labs/nostics/issues",
|
||||
"keywords": [],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": "./dist/index.mjs",
|
||||
"./formatters/ansi": "./dist/formatters/ansi.mjs",
|
||||
"./formatters/json": "./dist/formatters/json.mjs",
|
||||
"./reporters/dev": "./dist/reporters/dev.mjs",
|
||||
"./reporters/fetch": "./dist/reporters/fetch.mjs",
|
||||
"./reporters/node": "./dist/reporters/node.mjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist",
|
||||
"skills"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@posva/prompts": "^2.4.4",
|
||||
"@size-limit/esbuild-why": "^14.0.0",
|
||||
"@size-limit/preset-small-lib": "^14.0.0",
|
||||
"@types/node": "^26.6.2",
|
||||
"@types/semver": "^7.8.0",
|
||||
"@vitest/coverage-v8": "5.0.1",
|
||||
"conventional-changelog": "^8.1.3",
|
||||
"conventional-changelog-angular": "^9.4.0",
|
||||
"lint-staged": "^17.5.1",
|
||||
"oxfmt": "^0.68.0",
|
||||
"oxlint": "^1.80.0",
|
||||
"oxlint-plugin-posva": "^0.0.2",
|
||||
"publint": "^0.3.24",
|
||||
"semver": "^7.8.5",
|
||||
"simple-git-hooks": "^2.14.0",
|
||||
"size-limit": "^14.0.0",
|
||||
"tsdown": "^0.23.0",
|
||||
"tsnapi": "^1.5.0",
|
||||
"typescript": "^6.0.0",
|
||||
"vite": "^8.3.0",
|
||||
"vitest": "^5.0.1"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
"oxfmt --no-error-on-unmatched-pattern",
|
||||
"oxlint --no-error-on-unmatched-pattern --fix"
|
||||
]
|
||||
},
|
||||
"size-limit": [
|
||||
{
|
||||
"name": "defineDiagnostics from source",
|
||||
"path": "src/index.ts",
|
||||
"import": "{ defineDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineDiagnostics",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineProdDiagnostics",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineProdDiagnostics }"
|
||||
},
|
||||
{
|
||||
"name": "defineDiagnostics + createConsoleReporter",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineDiagnostics, createConsoleReporter }"
|
||||
},
|
||||
{
|
||||
"name": "defineProdDiagnostics + createConsoleReporter",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ defineProdDiagnostics, createConsoleReporter }"
|
||||
},
|
||||
{
|
||||
"name": "formatDiagnostic",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "{ formatDiagnostic }"
|
||||
},
|
||||
{
|
||||
"name": "full root",
|
||||
"path": "dist/index.mjs",
|
||||
"import": "*"
|
||||
},
|
||||
{
|
||||
"name": "ansi formatter",
|
||||
"path": "dist/formatters/ansi.mjs",
|
||||
"import": "*"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsdown && pnpm --filter \"./packages/*\" run build",
|
||||
"dev": "vitest",
|
||||
"docs": "pnpm -C docs run dev",
|
||||
"docs:build": "pnpm -C docs run build",
|
||||
"play": "pnpm -C playground run dev",
|
||||
"lint": "oxlint ${CI:+--deny-warnings} . && oxfmt --check .",
|
||||
"lint:fix": "oxfmt && oxlint --fix .",
|
||||
"fmt": "oxfmt",
|
||||
"release": "node scripts/release.ts",
|
||||
"size": "size-limit",
|
||||
"test": "pnpm run build && pnpm run \"/^test:/\"",
|
||||
"test:unit": "vitest run --coverage",
|
||||
"test:types": "pnpm run typecheck",
|
||||
"test:lint": "pnpm run lint",
|
||||
"test:demo": "pnpm -C demo-lib run build && pnpm -C demo-lib run test && pnpm -C playground run build",
|
||||
"typecheck": "tsc && pnpm --filter \"./packages/*\" run typecheck"
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var util = require('./util');
|
||||
var has = Object.prototype.hasOwnProperty;
|
||||
var hasNativeMap = typeof Map !== "undefined";
|
||||
|
||||
/**
|
||||
* A data structure which is a combination of an array and a set. Adding a new
|
||||
* member is O(1), testing for membership is O(1), and finding the index of an
|
||||
* element is O(1). Removing elements from the set is not supported. Only
|
||||
* strings are supported for membership.
|
||||
*/
|
||||
function ArraySet() {
|
||||
this._array = [];
|
||||
this._set = hasNativeMap ? new Map() : Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method for creating ArraySet instances from an existing array.
|
||||
*/
|
||||
ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
|
||||
var set = new ArraySet();
|
||||
for (var i = 0, len = aArray.length; i < len; i++) {
|
||||
set.add(aArray[i], aAllowDuplicates);
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return how many unique items are in this ArraySet. If duplicates have been
|
||||
* added, than those do not count towards the size.
|
||||
*
|
||||
* @returns Number
|
||||
*/
|
||||
ArraySet.prototype.size = function ArraySet_size() {
|
||||
return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the given string to this set.
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
|
||||
var sStr = hasNativeMap ? aStr : util.toSetString(aStr);
|
||||
var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr);
|
||||
var idx = this._array.length;
|
||||
if (!isDuplicate || aAllowDuplicates) {
|
||||
this._array.push(aStr);
|
||||
}
|
||||
if (!isDuplicate) {
|
||||
if (hasNativeMap) {
|
||||
this._set.set(aStr, idx);
|
||||
} else {
|
||||
this._set[sStr] = idx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a member of this set?
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.has = function ArraySet_has(aStr) {
|
||||
if (hasNativeMap) {
|
||||
return this._set.has(aStr);
|
||||
} else {
|
||||
var sStr = util.toSetString(aStr);
|
||||
return has.call(this._set, sStr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* What is the index of the given string in the array?
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
|
||||
if (hasNativeMap) {
|
||||
var idx = this._set.get(aStr);
|
||||
if (idx >= 0) {
|
||||
return idx;
|
||||
}
|
||||
} else {
|
||||
var sStr = util.toSetString(aStr);
|
||||
if (has.call(this._set, sStr)) {
|
||||
return this._set[sStr];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('"' + aStr + '" is not in the set.');
|
||||
};
|
||||
|
||||
/**
|
||||
* What is the element at the given index?
|
||||
*
|
||||
* @param Number aIdx
|
||||
*/
|
||||
ArraySet.prototype.at = function ArraySet_at(aIdx) {
|
||||
if (aIdx >= 0 && aIdx < this._array.length) {
|
||||
return this._array[aIdx];
|
||||
}
|
||||
throw new Error('No element indexed by ' + aIdx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the array representation of this set (which has the proper indices
|
||||
* indicated by indexOf). Note that this is a copy of the internal array used
|
||||
* for storing the members so that no one can mess with internal state.
|
||||
*/
|
||||
ArraySet.prototype.toArray = function ArraySet_toArray() {
|
||||
return this._array.slice();
|
||||
};
|
||||
|
||||
exports.ArraySet = ArraySet;
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Based on the Base 64 VLQ implementation in Closure Compiler:
|
||||
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
|
||||
*
|
||||
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
var base64 = require('./base64');
|
||||
|
||||
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
|
||||
// length quantities we use in the source map spec, the first bit is the sign,
|
||||
// the next four bits are the actual value, and the 6th bit is the
|
||||
// continuation bit. The continuation bit tells us whether there are more
|
||||
// digits in this value following this digit.
|
||||
//
|
||||
// Continuation
|
||||
// | Sign
|
||||
// | |
|
||||
// V V
|
||||
// 101011
|
||||
|
||||
var VLQ_BASE_SHIFT = 5;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
||||
|
||||
// binary: 011111
|
||||
var VLQ_BASE_MASK = VLQ_BASE - 1;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_CONTINUATION_BIT = VLQ_BASE;
|
||||
|
||||
/**
|
||||
* Converts from a two-complement value to a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
*/
|
||||
function toVLQSigned(aValue) {
|
||||
return aValue < 0
|
||||
? ((-aValue) << 1) + 1
|
||||
: (aValue << 1) + 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a two-complement value from a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
*/
|
||||
function fromVLQSigned(aValue) {
|
||||
var isNegative = (aValue & 1) === 1;
|
||||
var shifted = aValue >> 1;
|
||||
return isNegative
|
||||
? -shifted
|
||||
: shifted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base 64 VLQ encoded value.
|
||||
*/
|
||||
exports.encode = function base64VLQ_encode(aValue) {
|
||||
var encoded = "";
|
||||
var digit;
|
||||
|
||||
var vlq = toVLQSigned(aValue);
|
||||
|
||||
do {
|
||||
digit = vlq & VLQ_BASE_MASK;
|
||||
vlq >>>= VLQ_BASE_SHIFT;
|
||||
if (vlq > 0) {
|
||||
// There are still more digits in this value, so we must make sure the
|
||||
// continuation bit is marked.
|
||||
digit |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
encoded += base64.encode(digit);
|
||||
} while (vlq > 0);
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes the next base 64 VLQ value from the given string and returns the
|
||||
* value and the rest of the string via the out parameter.
|
||||
*/
|
||||
exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
|
||||
var strLen = aStr.length;
|
||||
var result = 0;
|
||||
var shift = 0;
|
||||
var continuation, digit;
|
||||
|
||||
do {
|
||||
if (aIndex >= strLen) {
|
||||
throw new Error("Expected more digits in base 64 VLQ value.");
|
||||
}
|
||||
|
||||
digit = base64.decode(aStr.charCodeAt(aIndex++));
|
||||
if (digit === -1) {
|
||||
throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
|
||||
}
|
||||
|
||||
continuation = !!(digit & VLQ_CONTINUATION_BIT);
|
||||
digit &= VLQ_BASE_MASK;
|
||||
result = result + (digit << shift);
|
||||
shift += VLQ_BASE_SHIFT;
|
||||
} while (continuation);
|
||||
|
||||
aOutParam.value = fromVLQSigned(result);
|
||||
aOutParam.rest = aIndex;
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
||||
|
||||
/**
|
||||
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
|
||||
*/
|
||||
exports.encode = function (number) {
|
||||
if (0 <= number && number < intToCharMap.length) {
|
||||
return intToCharMap[number];
|
||||
}
|
||||
throw new TypeError("Must be between 0 and 63: " + number);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a single base 64 character code digit to an integer. Returns -1 on
|
||||
* failure.
|
||||
*/
|
||||
exports.decode = function (charCode) {
|
||||
var bigA = 65; // 'A'
|
||||
var bigZ = 90; // 'Z'
|
||||
|
||||
var littleA = 97; // 'a'
|
||||
var littleZ = 122; // 'z'
|
||||
|
||||
var zero = 48; // '0'
|
||||
var nine = 57; // '9'
|
||||
|
||||
var plus = 43; // '+'
|
||||
var slash = 47; // '/'
|
||||
|
||||
var littleOffset = 26;
|
||||
var numberOffset = 52;
|
||||
|
||||
// 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
if (bigA <= charCode && charCode <= bigZ) {
|
||||
return (charCode - bigA);
|
||||
}
|
||||
|
||||
// 26 - 51: abcdefghijklmnopqrstuvwxyz
|
||||
if (littleA <= charCode && charCode <= littleZ) {
|
||||
return (charCode - littleA + littleOffset);
|
||||
}
|
||||
|
||||
// 52 - 61: 0123456789
|
||||
if (zero <= charCode && charCode <= nine) {
|
||||
return (charCode - zero + numberOffset);
|
||||
}
|
||||
|
||||
// 62: +
|
||||
if (charCode == plus) {
|
||||
return 62;
|
||||
}
|
||||
|
||||
// 63: /
|
||||
if (charCode == slash) {
|
||||
return 63;
|
||||
}
|
||||
|
||||
// Invalid base64 digit.
|
||||
return -1;
|
||||
};
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
exports.GREATEST_LOWER_BOUND = 1;
|
||||
exports.LEAST_UPPER_BOUND = 2;
|
||||
|
||||
/**
|
||||
* Recursive implementation of binary search.
|
||||
*
|
||||
* @param aLow Indices here and lower do not contain the needle.
|
||||
* @param aHigh Indices here and higher do not contain the needle.
|
||||
* @param aNeedle The element being searched for.
|
||||
* @param aHaystack The non-empty array being searched.
|
||||
* @param aCompare Function which takes two elements and returns -1, 0, or 1.
|
||||
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
|
||||
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
|
||||
* closest element that is smaller than or greater than the one we are
|
||||
* searching for, respectively, if the exact element cannot be found.
|
||||
*/
|
||||
function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
|
||||
// This function terminates when one of the following is true:
|
||||
//
|
||||
// 1. We find the exact element we are looking for.
|
||||
//
|
||||
// 2. We did not find the exact element, but we can return the index of
|
||||
// the next-closest element.
|
||||
//
|
||||
// 3. We did not find the exact element, and there is no next-closest
|
||||
// element than the one we are searching for, so we return -1.
|
||||
var mid = Math.floor((aHigh - aLow) / 2) + aLow;
|
||||
var cmp = aCompare(aNeedle, aHaystack[mid], true);
|
||||
if (cmp === 0) {
|
||||
// Found the element we are looking for.
|
||||
return mid;
|
||||
}
|
||||
else if (cmp > 0) {
|
||||
// Our needle is greater than aHaystack[mid].
|
||||
if (aHigh - mid > 1) {
|
||||
// The element is in the upper half.
|
||||
return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
|
||||
}
|
||||
|
||||
// The exact needle element was not found in this haystack. Determine if
|
||||
// we are in termination case (3) or (2) and return the appropriate thing.
|
||||
if (aBias == exports.LEAST_UPPER_BOUND) {
|
||||
return aHigh < aHaystack.length ? aHigh : -1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Our needle is less than aHaystack[mid].
|
||||
if (mid - aLow > 1) {
|
||||
// The element is in the lower half.
|
||||
return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
|
||||
}
|
||||
|
||||
// we are in termination case (3) or (2) and return the appropriate thing.
|
||||
if (aBias == exports.LEAST_UPPER_BOUND) {
|
||||
return mid;
|
||||
} else {
|
||||
return aLow < 0 ? -1 : aLow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an implementation of binary search which will always try and return
|
||||
* the index of the closest element if there is no exact hit. This is because
|
||||
* mappings between original and generated line/col pairs are single points,
|
||||
* and there is an implicit region between each of them, so a miss just means
|
||||
* that you aren't on the very start of a region.
|
||||
*
|
||||
* @param aNeedle The element you are looking for.
|
||||
* @param aHaystack The array that is being searched.
|
||||
* @param aCompare A function which takes the needle and an element in the
|
||||
* array and returns -1, 0, or 1 depending on whether the needle is less
|
||||
* than, equal to, or greater than the element, respectively.
|
||||
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
|
||||
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
|
||||
* closest element that is smaller than or greater than the one we are
|
||||
* searching for, respectively, if the exact element cannot be found.
|
||||
* Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
|
||||
*/
|
||||
exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
|
||||
if (aHaystack.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
|
||||
aCompare, aBias || exports.GREATEST_LOWER_BOUND);
|
||||
if (index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We have found either the exact element, or the next-closest element than
|
||||
// the one we are searching for. However, there may be more than one such
|
||||
// element. Make sure we always return the smallest of these.
|
||||
while (index - 1 >= 0) {
|
||||
if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
|
||||
break;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2014 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var util = require('./util');
|
||||
|
||||
/**
|
||||
* Determine whether mappingB is after mappingA with respect to generated
|
||||
* position.
|
||||
*/
|
||||
function generatedPositionAfter(mappingA, mappingB) {
|
||||
// Optimized for most common case
|
||||
var lineA = mappingA.generatedLine;
|
||||
var lineB = mappingB.generatedLine;
|
||||
var columnA = mappingA.generatedColumn;
|
||||
var columnB = mappingB.generatedColumn;
|
||||
return lineB > lineA || lineB == lineA && columnB >= columnA ||
|
||||
util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data structure to provide a sorted view of accumulated mappings in a
|
||||
* performance conscious manner. It trades a neglibable overhead in general
|
||||
* case for a large speedup in case of mappings being added in order.
|
||||
*/
|
||||
function MappingList() {
|
||||
this._array = [];
|
||||
this._sorted = true;
|
||||
// Serves as infimum
|
||||
this._last = {generatedLine: -1, generatedColumn: 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through internal items. This method takes the same arguments that
|
||||
* `Array.prototype.forEach` takes.
|
||||
*
|
||||
* NOTE: The order of the mappings is NOT guaranteed.
|
||||
*/
|
||||
MappingList.prototype.unsortedForEach =
|
||||
function MappingList_forEach(aCallback, aThisArg) {
|
||||
this._array.forEach(aCallback, aThisArg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the given source mapping.
|
||||
*
|
||||
* @param Object aMapping
|
||||
*/
|
||||
MappingList.prototype.add = function MappingList_add(aMapping) {
|
||||
if (generatedPositionAfter(this._last, aMapping)) {
|
||||
this._last = aMapping;
|
||||
this._array.push(aMapping);
|
||||
} else {
|
||||
this._sorted = false;
|
||||
this._array.push(aMapping);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the flat, sorted array of mappings. The mappings are sorted by
|
||||
* generated position.
|
||||
*
|
||||
* WARNING: This method returns internal data without copying, for
|
||||
* performance. The return value must NOT be mutated, and should be treated as
|
||||
* an immutable borrow. If you want to take ownership, you must make your own
|
||||
* copy.
|
||||
*/
|
||||
MappingList.prototype.toArray = function MappingList_toArray() {
|
||||
if (!this._sorted) {
|
||||
this._array.sort(util.compareByGeneratedPositionsInflated);
|
||||
this._sorted = true;
|
||||
}
|
||||
return this._array;
|
||||
};
|
||||
|
||||
exports.MappingList = MappingList;
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
// It turns out that some (most?) JavaScript engines don't self-host
|
||||
// `Array.prototype.sort`. This makes sense because C++ will likely remain
|
||||
// faster than JS when doing raw CPU-intensive sorting. However, when using a
|
||||
// custom comparator function, calling back and forth between the VM's C++ and
|
||||
// JIT'd JS is rather slow *and* loses JIT type information, resulting in
|
||||
// worse generated code for the comparator function than would be optimal. In
|
||||
// fact, when sorting with a comparator, these costs outweigh the benefits of
|
||||
// sorting in C++. By using our own JS-implemented Quick Sort (below), we get
|
||||
// a ~3500ms mean speed-up in `bench/bench.html`.
|
||||
|
||||
function SortTemplate(comparator) {
|
||||
|
||||
/**
|
||||
* Swap the elements indexed by `x` and `y` in the array `ary`.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* The array.
|
||||
* @param {Number} x
|
||||
* The index of the first item.
|
||||
* @param {Number} y
|
||||
* The index of the second item.
|
||||
*/
|
||||
function swap(ary, x, y) {
|
||||
var temp = ary[x];
|
||||
ary[x] = ary[y];
|
||||
ary[y] = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random integer within the range `low .. high` inclusive.
|
||||
*
|
||||
* @param {Number} low
|
||||
* The lower bound on the range.
|
||||
* @param {Number} high
|
||||
* The upper bound on the range.
|
||||
*/
|
||||
function randomIntInRange(low, high) {
|
||||
return Math.round(low + (Math.random() * (high - low)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Quick Sort algorithm.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* An array to sort.
|
||||
* @param {function} comparator
|
||||
* Function to use to compare two items.
|
||||
* @param {Number} p
|
||||
* Start index of the array
|
||||
* @param {Number} r
|
||||
* End index of the array
|
||||
*/
|
||||
function doQuickSort(ary, comparator, p, r) {
|
||||
// If our lower bound is less than our upper bound, we (1) partition the
|
||||
// array into two pieces and (2) recurse on each half. If it is not, this is
|
||||
// the empty array and our base case.
|
||||
|
||||
if (p < r) {
|
||||
// (1) Partitioning.
|
||||
//
|
||||
// The partitioning chooses a pivot between `p` and `r` and moves all
|
||||
// elements that are less than or equal to the pivot to the before it, and
|
||||
// all the elements that are greater than it after it. The effect is that
|
||||
// once partition is done, the pivot is in the exact place it will be when
|
||||
// the array is put in sorted order, and it will not need to be moved
|
||||
// again. This runs in O(n) time.
|
||||
|
||||
// Always choose a random pivot so that an input array which is reverse
|
||||
// sorted does not cause O(n^2) running time.
|
||||
var pivotIndex = randomIntInRange(p, r);
|
||||
var i = p - 1;
|
||||
|
||||
swap(ary, pivotIndex, r);
|
||||
var pivot = ary[r];
|
||||
|
||||
// Immediately after `j` is incremented in this loop, the following hold
|
||||
// true:
|
||||
//
|
||||
// * Every element in `ary[p .. i]` is less than or equal to the pivot.
|
||||
//
|
||||
// * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
|
||||
for (var j = p; j < r; j++) {
|
||||
if (comparator(ary[j], pivot, false) <= 0) {
|
||||
i += 1;
|
||||
swap(ary, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
swap(ary, i + 1, j);
|
||||
var q = i + 1;
|
||||
|
||||
// (2) Recurse on each half.
|
||||
|
||||
doQuickSort(ary, comparator, p, q - 1);
|
||||
doQuickSort(ary, comparator, q + 1, r);
|
||||
}
|
||||
}
|
||||
|
||||
return doQuickSort;
|
||||
}
|
||||
|
||||
function cloneSort(comparator) {
|
||||
let template = SortTemplate.toString();
|
||||
let templateFn = new Function(`return ${template}`)();
|
||||
return templateFn(comparator);
|
||||
}
|
||||
|
||||
let isEvalAllowed = (function () {
|
||||
try {
|
||||
new Function('return 0')();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Sort the given array in-place with the given comparator function.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* An array to sort.
|
||||
* @param {function} comparator
|
||||
* Function to use to compare two items.
|
||||
*/
|
||||
|
||||
let sortCache = new WeakMap();
|
||||
exports.quickSort = function (ary, comparator, start = 0) {
|
||||
let doQuickSort = isEvalAllowed ? sortCache.get(comparator) : SortTemplate(comparator);
|
||||
if (doQuickSort === void 0) {
|
||||
doQuickSort = cloneSort(comparator);
|
||||
sortCache.set(comparator, doQuickSort);
|
||||
}
|
||||
doQuickSort(ary, comparator, start, ary.length - 1);
|
||||
};
|
||||
+1236
File diff suppressed because it is too large
Load Diff
+446
@@ -0,0 +1,446 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var base64VLQ = require('./base64-vlq');
|
||||
var util = require('./util');
|
||||
var ArraySet = require('./array-set').ArraySet;
|
||||
var MappingList = require('./mapping-list').MappingList;
|
||||
|
||||
/**
|
||||
* An instance of the SourceMapGenerator represents a source map which is
|
||||
* being built incrementally. You may pass an object with the following
|
||||
* properties:
|
||||
*
|
||||
* - file: The filename of the generated source.
|
||||
* - sourceRoot: A root for all relative URLs in this source map.
|
||||
*/
|
||||
function SourceMapGenerator(aArgs) {
|
||||
if (!aArgs) {
|
||||
aArgs = {};
|
||||
}
|
||||
this._file = util.getArg(aArgs, 'file', null);
|
||||
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
|
||||
this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
|
||||
this._ignoreInvalidMapping = util.getArg(aArgs, 'ignoreInvalidMapping', false);
|
||||
this._sources = new ArraySet();
|
||||
this._names = new ArraySet();
|
||||
this._mappings = new MappingList();
|
||||
this._sourcesContents = null;
|
||||
}
|
||||
|
||||
SourceMapGenerator.prototype._version = 3;
|
||||
|
||||
/**
|
||||
* Creates a new SourceMapGenerator based on a SourceMapConsumer
|
||||
*
|
||||
* @param aSourceMapConsumer The SourceMap.
|
||||
*/
|
||||
SourceMapGenerator.fromSourceMap =
|
||||
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer, generatorOps) {
|
||||
var sourceRoot = aSourceMapConsumer.sourceRoot;
|
||||
var generator = new SourceMapGenerator(Object.assign(generatorOps || {}, {
|
||||
file: aSourceMapConsumer.file,
|
||||
sourceRoot: sourceRoot
|
||||
}));
|
||||
aSourceMapConsumer.eachMapping(function (mapping) {
|
||||
var newMapping = {
|
||||
generated: {
|
||||
line: mapping.generatedLine,
|
||||
column: mapping.generatedColumn
|
||||
}
|
||||
};
|
||||
|
||||
if (mapping.source != null) {
|
||||
newMapping.source = mapping.source;
|
||||
if (sourceRoot != null) {
|
||||
newMapping.source = util.relative(sourceRoot, newMapping.source);
|
||||
}
|
||||
|
||||
newMapping.original = {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn
|
||||
};
|
||||
|
||||
if (mapping.name != null) {
|
||||
newMapping.name = mapping.name;
|
||||
}
|
||||
}
|
||||
|
||||
generator.addMapping(newMapping);
|
||||
});
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var sourceRelative = sourceFile;
|
||||
if (sourceRoot !== null) {
|
||||
sourceRelative = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
|
||||
if (!generator._sources.has(sourceRelative)) {
|
||||
generator._sources.add(sourceRelative);
|
||||
}
|
||||
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
generator.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
return generator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a single mapping from original source line and column to the generated
|
||||
* source's line and column for this source map being created. The mapping
|
||||
* object should have the following properties:
|
||||
*
|
||||
* - generated: An object with the generated line and column positions.
|
||||
* - original: An object with the original line and column positions.
|
||||
* - source: The original source file (relative to the sourceRoot).
|
||||
* - name: An optional original token name for this mapping.
|
||||
*/
|
||||
SourceMapGenerator.prototype.addMapping =
|
||||
function SourceMapGenerator_addMapping(aArgs) {
|
||||
var generated = util.getArg(aArgs, 'generated');
|
||||
var original = util.getArg(aArgs, 'original', null);
|
||||
var source = util.getArg(aArgs, 'source', null);
|
||||
var name = util.getArg(aArgs, 'name', null);
|
||||
|
||||
if (!this._skipValidation) {
|
||||
if (this._validateMapping(generated, original, source, name) === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (source != null) {
|
||||
source = String(source);
|
||||
if (!this._sources.has(source)) {
|
||||
this._sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
name = String(name);
|
||||
if (!this._names.has(name)) {
|
||||
this._names.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
this._mappings.add({
|
||||
generatedLine: generated.line,
|
||||
generatedColumn: generated.column,
|
||||
originalLine: original != null && original.line,
|
||||
originalColumn: original != null && original.column,
|
||||
source: source,
|
||||
name: name
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the source content for a source file.
|
||||
*/
|
||||
SourceMapGenerator.prototype.setSourceContent =
|
||||
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
|
||||
var source = aSourceFile;
|
||||
if (this._sourceRoot != null) {
|
||||
source = util.relative(this._sourceRoot, source);
|
||||
}
|
||||
|
||||
if (aSourceContent != null) {
|
||||
// Add the source content to the _sourcesContents map.
|
||||
// Create a new _sourcesContents map if the property is null.
|
||||
if (!this._sourcesContents) {
|
||||
this._sourcesContents = Object.create(null);
|
||||
}
|
||||
this._sourcesContents[util.toSetString(source)] = aSourceContent;
|
||||
} else if (this._sourcesContents) {
|
||||
// Remove the source file from the _sourcesContents map.
|
||||
// If the _sourcesContents map is empty, set the property to null.
|
||||
delete this._sourcesContents[util.toSetString(source)];
|
||||
if (Object.keys(this._sourcesContents).length === 0) {
|
||||
this._sourcesContents = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies the mappings of a sub-source-map for a specific source file to the
|
||||
* source map being generated. Each mapping to the supplied source file is
|
||||
* rewritten using the supplied source map. Note: The resolution for the
|
||||
* resulting mappings is the minimium of this map and the supplied map.
|
||||
*
|
||||
* @param aSourceMapConsumer The source map to be applied.
|
||||
* @param aSourceFile Optional. The filename of the source file.
|
||||
* If omitted, SourceMapConsumer's file property will be used.
|
||||
* @param aSourceMapPath Optional. The dirname of the path to the source map
|
||||
* to be applied. If relative, it is relative to the SourceMapConsumer.
|
||||
* This parameter is needed when the two source maps aren't in the same
|
||||
* directory, and the source map to be applied contains relative source
|
||||
* paths. If so, those relative source paths need to be rewritten
|
||||
* relative to the SourceMapGenerator.
|
||||
*/
|
||||
SourceMapGenerator.prototype.applySourceMap =
|
||||
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
|
||||
var sourceFile = aSourceFile;
|
||||
// If aSourceFile is omitted, we will use the file property of the SourceMap
|
||||
if (aSourceFile == null) {
|
||||
if (aSourceMapConsumer.file == null) {
|
||||
throw new Error(
|
||||
'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
|
||||
'or the source map\'s "file" property. Both were omitted.'
|
||||
);
|
||||
}
|
||||
sourceFile = aSourceMapConsumer.file;
|
||||
}
|
||||
var sourceRoot = this._sourceRoot;
|
||||
// Make "sourceFile" relative if an absolute Url is passed.
|
||||
if (sourceRoot != null) {
|
||||
sourceFile = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
// Applying the SourceMap can add and remove items from the sources and
|
||||
// the names array.
|
||||
var newSources = new ArraySet();
|
||||
var newNames = new ArraySet();
|
||||
|
||||
// Find mappings for the "sourceFile"
|
||||
this._mappings.unsortedForEach(function (mapping) {
|
||||
if (mapping.source === sourceFile && mapping.originalLine != null) {
|
||||
// Check if it can be mapped by the source map, then update the mapping.
|
||||
var original = aSourceMapConsumer.originalPositionFor({
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn
|
||||
});
|
||||
if (original.source != null) {
|
||||
// Copy mapping
|
||||
mapping.source = original.source;
|
||||
if (aSourceMapPath != null) {
|
||||
mapping.source = util.join(aSourceMapPath, mapping.source)
|
||||
}
|
||||
if (sourceRoot != null) {
|
||||
mapping.source = util.relative(sourceRoot, mapping.source);
|
||||
}
|
||||
mapping.originalLine = original.line;
|
||||
mapping.originalColumn = original.column;
|
||||
if (original.name != null) {
|
||||
mapping.name = original.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var source = mapping.source;
|
||||
if (source != null && !newSources.has(source)) {
|
||||
newSources.add(source);
|
||||
}
|
||||
|
||||
var name = mapping.name;
|
||||
if (name != null && !newNames.has(name)) {
|
||||
newNames.add(name);
|
||||
}
|
||||
|
||||
}, this);
|
||||
this._sources = newSources;
|
||||
this._names = newNames;
|
||||
|
||||
// Copy sourcesContents of applied map.
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
if (aSourceMapPath != null) {
|
||||
sourceFile = util.join(aSourceMapPath, sourceFile);
|
||||
}
|
||||
if (sourceRoot != null) {
|
||||
sourceFile = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
this.setSourceContent(sourceFile, content);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A mapping can have one of the three levels of data:
|
||||
*
|
||||
* 1. Just the generated position.
|
||||
* 2. The Generated position, original position, and original source.
|
||||
* 3. Generated and original position, original source, as well as a name
|
||||
* token.
|
||||
*
|
||||
* To maintain consistency, we validate that any new mapping being added falls
|
||||
* in to one of these categories.
|
||||
*/
|
||||
SourceMapGenerator.prototype._validateMapping =
|
||||
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
|
||||
aName) {
|
||||
// When aOriginal is truthy but has empty values for .line and .column,
|
||||
// it is most likely a programmer error. In this case we throw a very
|
||||
// specific error message to try to guide them the right way.
|
||||
// For example: https://github.com/Polymer/polymer-bundler/pull/519
|
||||
if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') {
|
||||
var message = 'original.line and original.column are not numbers -- you probably meant to omit ' +
|
||||
'the original mapping entirely and only map the generated position. If so, pass ' +
|
||||
'null for the original mapping instead of an object with empty or null values.'
|
||||
|
||||
if (this._ignoreInvalidMapping) {
|
||||
if (typeof console !== 'undefined' && console.warn) {
|
||||
console.warn(message);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
|
||||
&& aGenerated.line > 0 && aGenerated.column >= 0
|
||||
&& !aOriginal && !aSource && !aName) {
|
||||
// Case 1.
|
||||
return;
|
||||
}
|
||||
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
|
||||
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal
|
||||
&& aGenerated.line > 0 && aGenerated.column >= 0
|
||||
&& aOriginal.line > 0 && aOriginal.column >= 0
|
||||
&& aSource) {
|
||||
// Cases 2 and 3.
|
||||
return;
|
||||
}
|
||||
else {
|
||||
var message = 'Invalid mapping: ' + JSON.stringify({
|
||||
generated: aGenerated,
|
||||
source: aSource,
|
||||
original: aOriginal,
|
||||
name: aName
|
||||
});
|
||||
|
||||
if (this._ignoreInvalidMapping) {
|
||||
if (typeof console !== 'undefined' && console.warn) {
|
||||
console.warn(message);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the accumulated mappings in to the stream of base 64 VLQs
|
||||
* specified by the source map format.
|
||||
*/
|
||||
SourceMapGenerator.prototype._serializeMappings =
|
||||
function SourceMapGenerator_serializeMappings() {
|
||||
var previousGeneratedColumn = 0;
|
||||
var previousGeneratedLine = 1;
|
||||
var previousOriginalColumn = 0;
|
||||
var previousOriginalLine = 0;
|
||||
var previousName = 0;
|
||||
var previousSource = 0;
|
||||
var result = '';
|
||||
var next;
|
||||
var mapping;
|
||||
var nameIdx;
|
||||
var sourceIdx;
|
||||
|
||||
var mappings = this._mappings.toArray();
|
||||
for (var i = 0, len = mappings.length; i < len; i++) {
|
||||
mapping = mappings[i];
|
||||
next = ''
|
||||
|
||||
if (mapping.generatedLine !== previousGeneratedLine) {
|
||||
previousGeneratedColumn = 0;
|
||||
// One ';' per skipped line. Build large gaps with a single flat
|
||||
// string: appending ';' one by one creates a rope node per line, which
|
||||
// can exhaust the heap for a big gap (CVE-2026-93749).
|
||||
var lineDelta = mapping.generatedLine - previousGeneratedLine;
|
||||
next += lineDelta === 1 ? ';' : ';'.repeat(lineDelta);
|
||||
previousGeneratedLine = mapping.generatedLine;
|
||||
}
|
||||
else {
|
||||
if (i > 0) {
|
||||
if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
|
||||
continue;
|
||||
}
|
||||
next += ',';
|
||||
}
|
||||
}
|
||||
|
||||
next += base64VLQ.encode(mapping.generatedColumn
|
||||
- previousGeneratedColumn);
|
||||
previousGeneratedColumn = mapping.generatedColumn;
|
||||
|
||||
if (mapping.source != null) {
|
||||
sourceIdx = this._sources.indexOf(mapping.source);
|
||||
next += base64VLQ.encode(sourceIdx - previousSource);
|
||||
previousSource = sourceIdx;
|
||||
|
||||
// lines are stored 0-based in SourceMap spec version 3
|
||||
next += base64VLQ.encode(mapping.originalLine - 1
|
||||
- previousOriginalLine);
|
||||
previousOriginalLine = mapping.originalLine - 1;
|
||||
|
||||
next += base64VLQ.encode(mapping.originalColumn
|
||||
- previousOriginalColumn);
|
||||
previousOriginalColumn = mapping.originalColumn;
|
||||
|
||||
if (mapping.name != null) {
|
||||
nameIdx = this._names.indexOf(mapping.name);
|
||||
next += base64VLQ.encode(nameIdx - previousName);
|
||||
previousName = nameIdx;
|
||||
}
|
||||
}
|
||||
|
||||
result += next;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
SourceMapGenerator.prototype._generateSourcesContent =
|
||||
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
|
||||
return aSources.map(function (source) {
|
||||
if (!this._sourcesContents) {
|
||||
return null;
|
||||
}
|
||||
if (aSourceRoot != null) {
|
||||
source = util.relative(aSourceRoot, source);
|
||||
}
|
||||
var key = util.toSetString(source);
|
||||
return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)
|
||||
? this._sourcesContents[key]
|
||||
: null;
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Externalize the source map.
|
||||
*/
|
||||
SourceMapGenerator.prototype.toJSON =
|
||||
function SourceMapGenerator_toJSON() {
|
||||
var map = {
|
||||
version: this._version,
|
||||
sources: this._sources.toArray(),
|
||||
names: this._names.toArray(),
|
||||
mappings: this._serializeMappings()
|
||||
};
|
||||
if (this._file != null) {
|
||||
map.file = this._file;
|
||||
}
|
||||
if (this._sourceRoot != null) {
|
||||
map.sourceRoot = this._sourceRoot;
|
||||
}
|
||||
if (this._sourcesContents) {
|
||||
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the source map being generated to a string.
|
||||
*/
|
||||
SourceMapGenerator.prototype.toString =
|
||||
function SourceMapGenerator_toString() {
|
||||
return JSON.stringify(this.toJSON());
|
||||
};
|
||||
|
||||
exports.SourceMapGenerator = SourceMapGenerator;
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator;
|
||||
var util = require('./util');
|
||||
|
||||
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
|
||||
// operating systems these days (capturing the result).
|
||||
var REGEX_NEWLINE = /(\r?\n)/;
|
||||
|
||||
// Newline character code for charCodeAt() comparisons
|
||||
var NEWLINE_CODE = 10;
|
||||
|
||||
// Private symbol for identifying `SourceNode`s when multiple versions of
|
||||
// the source-map library are loaded. This MUST NOT CHANGE across
|
||||
// versions!
|
||||
var isSourceNode = "$$$isSourceNode$$$";
|
||||
|
||||
/**
|
||||
* SourceNodes provide a way to abstract over interpolating/concatenating
|
||||
* snippets of generated JavaScript source code while maintaining the line and
|
||||
* column information associated with the original source code.
|
||||
*
|
||||
* @param aLine The original line number.
|
||||
* @param aColumn The original column number.
|
||||
* @param aSource The original source's filename.
|
||||
* @param aChunks Optional. An array of strings which are snippets of
|
||||
* generated JS, or other SourceNodes.
|
||||
* @param aName The original identifier.
|
||||
*/
|
||||
function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
|
||||
this.children = [];
|
||||
this.sourceContents = {};
|
||||
this.line = aLine == null ? null : aLine;
|
||||
this.column = aColumn == null ? null : aColumn;
|
||||
this.source = aSource == null ? null : aSource;
|
||||
this.name = aName == null ? null : aName;
|
||||
this[isSourceNode] = true;
|
||||
if (aChunks != null) this.add(aChunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SourceNode from generated code and a SourceMapConsumer.
|
||||
*
|
||||
* @param aGeneratedCode The generated code
|
||||
* @param aSourceMapConsumer The SourceMap for the generated code
|
||||
* @param aRelativePath Optional. The path that relative sources in the
|
||||
* SourceMapConsumer should be relative to.
|
||||
*/
|
||||
SourceNode.fromStringWithSourceMap =
|
||||
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
|
||||
// The SourceNode we want to fill with the generated code
|
||||
// and the SourceMap
|
||||
var node = new SourceNode();
|
||||
|
||||
// All even indices of this array are one line of the generated code,
|
||||
// while all odd indices are the newlines between two adjacent lines
|
||||
// (since `REGEX_NEWLINE` captures its match).
|
||||
// Processed fragments are accessed by calling `shiftNextLine`.
|
||||
var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
|
||||
var remainingLinesIndex = 0;
|
||||
var shiftNextLine = function() {
|
||||
var lineContents = getNextLine();
|
||||
// The last line of a file might not have a newline.
|
||||
var newLine = getNextLine() || "";
|
||||
return lineContents + newLine;
|
||||
|
||||
function getNextLine() {
|
||||
return remainingLinesIndex < remainingLines.length ?
|
||||
remainingLines[remainingLinesIndex++] : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// We need to remember the position of "remainingLines"
|
||||
var lastGeneratedLine = 1, lastGeneratedColumn = 0;
|
||||
|
||||
// The generate SourceNodes we need a code range.
|
||||
// To extract it current and last mapping is used.
|
||||
// Here we store the last mapping.
|
||||
var lastMapping = null;
|
||||
|
||||
aSourceMapConsumer.eachMapping(function (mapping) {
|
||||
if (lastMapping !== null) {
|
||||
// We add the code from "lastMapping" to "mapping":
|
||||
// First check if there is a new line in between.
|
||||
if (lastGeneratedLine < mapping.generatedLine) {
|
||||
// Associate first line with "lastMapping"
|
||||
addMappingWithCode(lastMapping, shiftNextLine());
|
||||
lastGeneratedLine++;
|
||||
lastGeneratedColumn = 0;
|
||||
// The remaining code is added without mapping
|
||||
} else {
|
||||
// There is no new line in between.
|
||||
// Associate the code between "lastGeneratedColumn" and
|
||||
// "mapping.generatedColumn" with "lastMapping"
|
||||
var nextLine = remainingLines[remainingLinesIndex] || '';
|
||||
var code = nextLine.substr(0, mapping.generatedColumn -
|
||||
lastGeneratedColumn);
|
||||
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn -
|
||||
lastGeneratedColumn);
|
||||
lastGeneratedColumn = mapping.generatedColumn;
|
||||
addMappingWithCode(lastMapping, code);
|
||||
// No more remaining code, continue
|
||||
lastMapping = mapping;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We add the generated code until the first mapping
|
||||
// to the SourceNode without any mapping.
|
||||
// Each line is added as separate string.
|
||||
while (lastGeneratedLine < mapping.generatedLine) {
|
||||
if (remainingLinesIndex >= remainingLines.length) {
|
||||
// The generated code is exhausted. Skip the rest instead of adding
|
||||
// empty lines one by one: a mapping may point far past the end of
|
||||
// the code (e.g. an indexed map section with a huge offset.line).
|
||||
lastGeneratedLine = mapping.generatedLine;
|
||||
break;
|
||||
}
|
||||
node.add(shiftNextLine());
|
||||
lastGeneratedLine++;
|
||||
}
|
||||
if (lastGeneratedColumn < mapping.generatedColumn) {
|
||||
var nextLine = remainingLines[remainingLinesIndex] || '';
|
||||
node.add(nextLine.substr(0, mapping.generatedColumn));
|
||||
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn);
|
||||
lastGeneratedColumn = mapping.generatedColumn;
|
||||
}
|
||||
lastMapping = mapping;
|
||||
}, this);
|
||||
// We have processed all mappings.
|
||||
if (remainingLinesIndex < remainingLines.length) {
|
||||
if (lastMapping) {
|
||||
// Associate the remaining code in the current line with "lastMapping"
|
||||
addMappingWithCode(lastMapping, shiftNextLine());
|
||||
}
|
||||
// and add the remaining lines without any mapping
|
||||
node.add(remainingLines.splice(remainingLinesIndex).join(""));
|
||||
}
|
||||
|
||||
// Copy sourcesContent into SourceNode
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
if (aRelativePath != null) {
|
||||
sourceFile = util.join(aRelativePath, sourceFile);
|
||||
}
|
||||
node.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
|
||||
return node;
|
||||
|
||||
function addMappingWithCode(mapping, code) {
|
||||
if (mapping === null || mapping.source === undefined) {
|
||||
node.add(code);
|
||||
} else {
|
||||
var source = aRelativePath
|
||||
? util.join(aRelativePath, mapping.source)
|
||||
: mapping.source;
|
||||
node.add(new SourceNode(mapping.originalLine,
|
||||
mapping.originalColumn,
|
||||
source,
|
||||
code,
|
||||
mapping.name));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a chunk of generated JS to this source node.
|
||||
*
|
||||
* @param aChunk A string snippet of generated JS code, another instance of
|
||||
* SourceNode, or an array where each member is one of those things.
|
||||
*/
|
||||
SourceNode.prototype.add = function SourceNode_add(aChunk) {
|
||||
if (Array.isArray(aChunk)) {
|
||||
aChunk.forEach(function (chunk) {
|
||||
this.add(chunk);
|
||||
}, this);
|
||||
}
|
||||
else if (aChunk[isSourceNode] || typeof aChunk === "string") {
|
||||
if (aChunk) {
|
||||
this.children.push(aChunk);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new TypeError(
|
||||
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
|
||||
);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a chunk of generated JS to the beginning of this source node.
|
||||
*
|
||||
* @param aChunk A string snippet of generated JS code, another instance of
|
||||
* SourceNode, or an array where each member is one of those things.
|
||||
*/
|
||||
SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
|
||||
if (Array.isArray(aChunk)) {
|
||||
for (var i = aChunk.length-1; i >= 0; i--) {
|
||||
this.prepend(aChunk[i]);
|
||||
}
|
||||
}
|
||||
else if (aChunk[isSourceNode] || typeof aChunk === "string") {
|
||||
this.children.unshift(aChunk);
|
||||
}
|
||||
else {
|
||||
throw new TypeError(
|
||||
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
|
||||
);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk over the tree of JS snippets in this node and its children. The
|
||||
* walking function is called once for each snippet of JS and is passed that
|
||||
* snippet and the its original associated source's line/column location.
|
||||
*
|
||||
* @param aFn The traversal function.
|
||||
*/
|
||||
SourceNode.prototype.walk = function SourceNode_walk(aFn) {
|
||||
var chunk;
|
||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||
chunk = this.children[i];
|
||||
if (chunk[isSourceNode]) {
|
||||
chunk.walk(aFn);
|
||||
}
|
||||
else {
|
||||
if (chunk !== '') {
|
||||
aFn(chunk, { source: this.source,
|
||||
line: this.line,
|
||||
column: this.column,
|
||||
name: this.name });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
|
||||
* each of `this.children`.
|
||||
*
|
||||
* @param aSep The separator.
|
||||
*/
|
||||
SourceNode.prototype.join = function SourceNode_join(aSep) {
|
||||
var newChildren;
|
||||
var i;
|
||||
var len = this.children.length;
|
||||
if (len > 0) {
|
||||
newChildren = [];
|
||||
for (i = 0; i < len-1; i++) {
|
||||
newChildren.push(this.children[i]);
|
||||
newChildren.push(aSep);
|
||||
}
|
||||
newChildren.push(this.children[i]);
|
||||
this.children = newChildren;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Call String.prototype.replace on the very right-most source snippet. Useful
|
||||
* for trimming whitespace from the end of a source node, etc.
|
||||
*
|
||||
* @param aPattern The pattern to replace.
|
||||
* @param aReplacement The thing to replace the pattern with.
|
||||
*/
|
||||
SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
|
||||
var lastChild = this.children[this.children.length - 1];
|
||||
if (lastChild[isSourceNode]) {
|
||||
lastChild.replaceRight(aPattern, aReplacement);
|
||||
}
|
||||
else if (typeof lastChild === 'string') {
|
||||
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
|
||||
}
|
||||
else {
|
||||
this.children.push(''.replace(aPattern, aReplacement));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the source content for a source file. This will be added to the SourceMapGenerator
|
||||
* in the sourcesContent field.
|
||||
*
|
||||
* @param aSourceFile The filename of the source file
|
||||
* @param aSourceContent The content of the source file
|
||||
*/
|
||||
SourceNode.prototype.setSourceContent =
|
||||
function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
|
||||
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk over the tree of SourceNodes. The walking function is called for each
|
||||
* source file content and is passed the filename and source content.
|
||||
*
|
||||
* @param aFn The traversal function.
|
||||
*/
|
||||
SourceNode.prototype.walkSourceContents =
|
||||
function SourceNode_walkSourceContents(aFn) {
|
||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||
if (this.children[i][isSourceNode]) {
|
||||
this.children[i].walkSourceContents(aFn);
|
||||
}
|
||||
}
|
||||
|
||||
var sources = Object.keys(this.sourceContents);
|
||||
for (var i = 0, len = sources.length; i < len; i++) {
|
||||
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the string representation of this source node. Walks over the tree
|
||||
* and concatenates all the various snippets together to one string.
|
||||
*/
|
||||
SourceNode.prototype.toString = function SourceNode_toString() {
|
||||
var str = "";
|
||||
this.walk(function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the string representation of this source node along with a source
|
||||
* map.
|
||||
*/
|
||||
SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
|
||||
var generated = {
|
||||
code: "",
|
||||
line: 1,
|
||||
column: 0
|
||||
};
|
||||
var map = new SourceMapGenerator(aArgs);
|
||||
var sourceMappingActive = false;
|
||||
var lastOriginalSource = null;
|
||||
var lastOriginalLine = null;
|
||||
var lastOriginalColumn = null;
|
||||
var lastOriginalName = null;
|
||||
this.walk(function (chunk, original) {
|
||||
generated.code += chunk;
|
||||
if (original.source !== null
|
||||
&& original.line !== null
|
||||
&& original.column !== null) {
|
||||
if(lastOriginalSource !== original.source
|
||||
|| lastOriginalLine !== original.line
|
||||
|| lastOriginalColumn !== original.column
|
||||
|| lastOriginalName !== original.name) {
|
||||
map.addMapping({
|
||||
source: original.source,
|
||||
original: {
|
||||
line: original.line,
|
||||
column: original.column
|
||||
},
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
},
|
||||
name: original.name
|
||||
});
|
||||
}
|
||||
lastOriginalSource = original.source;
|
||||
lastOriginalLine = original.line;
|
||||
lastOriginalColumn = original.column;
|
||||
lastOriginalName = original.name;
|
||||
sourceMappingActive = true;
|
||||
} else if (sourceMappingActive) {
|
||||
map.addMapping({
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
}
|
||||
});
|
||||
lastOriginalSource = null;
|
||||
sourceMappingActive = false;
|
||||
}
|
||||
for (var idx = 0, length = chunk.length; idx < length; idx++) {
|
||||
if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
|
||||
generated.line++;
|
||||
generated.column = 0;
|
||||
// Mappings end at eol
|
||||
if (idx + 1 === length) {
|
||||
lastOriginalSource = null;
|
||||
sourceMappingActive = false;
|
||||
} else if (sourceMappingActive) {
|
||||
map.addMapping({
|
||||
source: original.source,
|
||||
original: {
|
||||
line: original.line,
|
||||
column: original.column
|
||||
},
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
},
|
||||
name: original.name
|
||||
});
|
||||
}
|
||||
} else {
|
||||
generated.column++;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.walkSourceContents(function (sourceFile, sourceContent) {
|
||||
map.setSourceContent(sourceFile, sourceContent);
|
||||
});
|
||||
|
||||
return { code: generated.code, map: map };
|
||||
};
|
||||
|
||||
exports.SourceNode = SourceNode;
|
||||
+594
@@ -0,0 +1,594 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a helper function for getting values from parameter/options
|
||||
* objects.
|
||||
*
|
||||
* @param args The object we are extracting values from
|
||||
* @param name The name of the property we are getting.
|
||||
* @param defaultValue An optional value to return if the property is missing
|
||||
* from the object. If this is not specified and the property is missing, an
|
||||
* error will be thrown.
|
||||
*/
|
||||
function getArg(aArgs, aName, aDefaultValue) {
|
||||
if (aName in aArgs) {
|
||||
return aArgs[aName];
|
||||
} else if (arguments.length === 3) {
|
||||
return aDefaultValue;
|
||||
} else {
|
||||
throw new Error('"' + aName + '" is a required argument.');
|
||||
}
|
||||
}
|
||||
exports.getArg = getArg;
|
||||
|
||||
var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
|
||||
var dataUrlRegexp = /^data:.+\,.+$/;
|
||||
|
||||
function urlParse(aUrl) {
|
||||
var match = aUrl.match(urlRegexp);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
scheme: match[1],
|
||||
auth: match[2],
|
||||
host: match[3],
|
||||
port: match[4],
|
||||
path: match[5]
|
||||
};
|
||||
}
|
||||
exports.urlParse = urlParse;
|
||||
|
||||
function urlGenerate(aParsedUrl) {
|
||||
var url = '';
|
||||
if (aParsedUrl.scheme) {
|
||||
url += aParsedUrl.scheme + ':';
|
||||
}
|
||||
url += '//';
|
||||
if (aParsedUrl.auth) {
|
||||
url += aParsedUrl.auth + '@';
|
||||
}
|
||||
if (aParsedUrl.host) {
|
||||
url += aParsedUrl.host;
|
||||
}
|
||||
if (aParsedUrl.port) {
|
||||
url += ":" + aParsedUrl.port
|
||||
}
|
||||
if (aParsedUrl.path) {
|
||||
url += aParsedUrl.path;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
exports.urlGenerate = urlGenerate;
|
||||
|
||||
var MAX_CACHED_INPUTS = 32;
|
||||
|
||||
/**
|
||||
* Takes some function `f(input) -> result` and returns a memoized version of
|
||||
* `f`.
|
||||
*
|
||||
* We keep at most `MAX_CACHED_INPUTS` memoized results of `f` alive. The
|
||||
* memoization is a dumb-simple, linear least-recently-used cache.
|
||||
*/
|
||||
function lruMemoize(f) {
|
||||
var cache = [];
|
||||
|
||||
return function(input) {
|
||||
for (var i = 0; i < cache.length; i++) {
|
||||
if (cache[i].input === input) {
|
||||
var temp = cache[0];
|
||||
cache[0] = cache[i];
|
||||
cache[i] = temp;
|
||||
return cache[0].result;
|
||||
}
|
||||
}
|
||||
|
||||
var result = f(input);
|
||||
|
||||
cache.unshift({
|
||||
input,
|
||||
result,
|
||||
});
|
||||
|
||||
if (cache.length > MAX_CACHED_INPUTS) {
|
||||
cache.pop();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a path, or the path portion of a URL:
|
||||
*
|
||||
* - Replaces consecutive slashes with one slash.
|
||||
* - Removes unnecessary '.' parts.
|
||||
* - Removes unnecessary '<dir>/..' parts.
|
||||
*
|
||||
* Based on code in the Node.js 'path' core module.
|
||||
*
|
||||
* @param aPath The path or url to normalize.
|
||||
*/
|
||||
var normalize = lruMemoize(function normalize(aPath) {
|
||||
var path = aPath;
|
||||
var url = urlParse(aPath);
|
||||
if (url) {
|
||||
if (!url.path) {
|
||||
return aPath;
|
||||
}
|
||||
path = url.path;
|
||||
}
|
||||
var isAbsolute = exports.isAbsolute(path);
|
||||
// Split the path into parts between `/` characters. This is much faster than
|
||||
// using `.split(/\/+/g)`.
|
||||
var parts = [];
|
||||
var start = 0;
|
||||
var i = 0;
|
||||
while (true) {
|
||||
start = i;
|
||||
i = path.indexOf("/", start);
|
||||
if (i === -1) {
|
||||
parts.push(path.slice(start));
|
||||
break;
|
||||
} else {
|
||||
parts.push(path.slice(start, i));
|
||||
while (i < path.length && path[i] === "/") {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
||||
part = parts[i];
|
||||
if (part === '.') {
|
||||
parts.splice(i, 1);
|
||||
} else if (part === '..') {
|
||||
up++;
|
||||
} else if (up > 0) {
|
||||
if (part === '') {
|
||||
// The first part is blank if the path is absolute. Trying to go
|
||||
// above the root is a no-op. Therefore we can remove all '..' parts
|
||||
// directly after the root.
|
||||
parts.splice(i + 1, up);
|
||||
up = 0;
|
||||
} else {
|
||||
parts.splice(i, 2);
|
||||
up--;
|
||||
}
|
||||
}
|
||||
}
|
||||
path = parts.join('/');
|
||||
|
||||
if (path === '') {
|
||||
path = isAbsolute ? '/' : '.';
|
||||
}
|
||||
|
||||
if (url) {
|
||||
url.path = path;
|
||||
return urlGenerate(url);
|
||||
}
|
||||
return path;
|
||||
});
|
||||
exports.normalize = normalize;
|
||||
|
||||
/**
|
||||
* Joins two paths/URLs.
|
||||
*
|
||||
* @param aRoot The root path or URL.
|
||||
* @param aPath The path or URL to be joined with the root.
|
||||
*
|
||||
* - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
|
||||
* scheme-relative URL: Then the scheme of aRoot, if any, is prepended
|
||||
* first.
|
||||
* - Otherwise aPath is a path. If aRoot is a URL, then its path portion
|
||||
* is updated with the result and aRoot is returned. Otherwise the result
|
||||
* is returned.
|
||||
* - If aPath is absolute, the result is aPath.
|
||||
* - Otherwise the two paths are joined with a slash.
|
||||
* - Joining for example 'http://' and 'www.example.com' is also supported.
|
||||
*/
|
||||
function join(aRoot, aPath) {
|
||||
if (aRoot === "") {
|
||||
aRoot = ".";
|
||||
}
|
||||
if (aPath === "") {
|
||||
aPath = ".";
|
||||
}
|
||||
var aPathUrl = urlParse(aPath);
|
||||
var aRootUrl = urlParse(aRoot);
|
||||
if (aRootUrl) {
|
||||
aRoot = aRootUrl.path || '/';
|
||||
}
|
||||
|
||||
// `join(foo, '//www.example.org')`
|
||||
if (aPathUrl && !aPathUrl.scheme) {
|
||||
if (aRootUrl) {
|
||||
aPathUrl.scheme = aRootUrl.scheme;
|
||||
}
|
||||
return urlGenerate(aPathUrl);
|
||||
}
|
||||
|
||||
if (aPathUrl || aPath.match(dataUrlRegexp)) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
// `join('http://', 'www.example.com')`
|
||||
if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
|
||||
aRootUrl.host = aPath;
|
||||
return urlGenerate(aRootUrl);
|
||||
}
|
||||
|
||||
var joined = aPath.charAt(0) === '/'
|
||||
? aPath
|
||||
: normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
|
||||
|
||||
if (aRootUrl) {
|
||||
aRootUrl.path = joined;
|
||||
return urlGenerate(aRootUrl);
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
exports.join = join;
|
||||
|
||||
exports.isAbsolute = function (aPath) {
|
||||
return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a path relative to a URL or another path.
|
||||
*
|
||||
* @param aRoot The root path or URL.
|
||||
* @param aPath The path or URL to be made relative to aRoot.
|
||||
*/
|
||||
function relative(aRoot, aPath) {
|
||||
if (aRoot === "") {
|
||||
aRoot = ".";
|
||||
}
|
||||
|
||||
aRoot = aRoot.replace(/\/$/, '');
|
||||
|
||||
// It is possible for the path to be above the root. In this case, simply
|
||||
// checking whether the root is a prefix of the path won't work. Instead, we
|
||||
// need to remove components from the root one by one, until either we find
|
||||
// a prefix that fits, or we run out of components to remove.
|
||||
var level = 0;
|
||||
while (aPath.indexOf(aRoot + '/') !== 0) {
|
||||
var index = aRoot.lastIndexOf("/");
|
||||
if (index < 0) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
// If the only part of the root that is left is the scheme (i.e. http://,
|
||||
// file:///, etc.), one or more slashes (/), or simply nothing at all, we
|
||||
// have exhausted all components, so the path is not relative to the root.
|
||||
aRoot = aRoot.slice(0, index);
|
||||
if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
++level;
|
||||
}
|
||||
|
||||
// Make sure we add a "../" for each component we removed from the root.
|
||||
return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
|
||||
}
|
||||
exports.relative = relative;
|
||||
|
||||
var supportsNullProto = (function () {
|
||||
var obj = Object.create(null);
|
||||
return !('__proto__' in obj);
|
||||
}());
|
||||
|
||||
function identity (s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because behavior goes wacky when you set `__proto__` on objects, we
|
||||
* have to prefix all the strings in our set with an arbitrary character.
|
||||
*
|
||||
* See https://github.com/mozilla/source-map/pull/31 and
|
||||
* https://github.com/mozilla/source-map/issues/30
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
function toSetString(aStr) {
|
||||
if (isProtoString(aStr)) {
|
||||
return '$' + aStr;
|
||||
}
|
||||
|
||||
return aStr;
|
||||
}
|
||||
exports.toSetString = supportsNullProto ? identity : toSetString;
|
||||
|
||||
function fromSetString(aStr) {
|
||||
if (isProtoString(aStr)) {
|
||||
return aStr.slice(1);
|
||||
}
|
||||
|
||||
return aStr;
|
||||
}
|
||||
exports.fromSetString = supportsNullProto ? identity : fromSetString;
|
||||
|
||||
function isProtoString(s) {
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = s.length;
|
||||
|
||||
if (length < 9 /* "__proto__".length */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 2) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
|
||||
s.charCodeAt(length - 4) !== 116 /* 't' */ ||
|
||||
s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
|
||||
s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
|
||||
s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
|
||||
s.charCodeAt(length - 8) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 9) !== 95 /* '_' */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = length - 10; i >= 0; i--) {
|
||||
if (s.charCodeAt(i) !== 36 /* '$' */) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator between two mappings where the original positions are compared.
|
||||
*
|
||||
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
|
||||
* mappings with the same original source/line/column, but different generated
|
||||
* line and column the same. Useful when searching for a mapping with a
|
||||
* stubbed out mapping.
|
||||
*/
|
||||
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
|
||||
var cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0 || onlyCompareOriginal) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByOriginalPositions = compareByOriginalPositions;
|
||||
|
||||
function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) {
|
||||
var cmp
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0 || onlyCompareOriginal) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByOriginalPositionsNoSource = compareByOriginalPositionsNoSource;
|
||||
|
||||
/**
|
||||
* Comparator between two mappings with deflated source and name indices where
|
||||
* the generated positions are compared.
|
||||
*
|
||||
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
|
||||
* mappings with the same generated line and column, but different
|
||||
* source/name/original line and column the same. Useful when searching for a
|
||||
* mapping with a stubbed out mapping.
|
||||
*/
|
||||
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0 || onlyCompareGenerated) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
|
||||
|
||||
function compareByGeneratedPositionsDeflatedNoLine(mappingA, mappingB, onlyCompareGenerated) {
|
||||
var cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0 || onlyCompareGenerated) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByGeneratedPositionsDeflatedNoLine = compareByGeneratedPositionsDeflatedNoLine;
|
||||
|
||||
function strcmp(aStr1, aStr2) {
|
||||
if (aStr1 === aStr2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (aStr1 === null) {
|
||||
return 1; // aStr2 !== null
|
||||
}
|
||||
|
||||
if (aStr2 === null) {
|
||||
return -1; // aStr1 !== null
|
||||
}
|
||||
|
||||
if (aStr1 > aStr2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator between two mappings with inflated source and name strings where
|
||||
* the generated positions are compared.
|
||||
*/
|
||||
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
|
||||
|
||||
/**
|
||||
* Strip any JSON XSSI avoidance prefix from the string (as documented
|
||||
* in the source maps specification), and then parse the string as
|
||||
* JSON.
|
||||
*/
|
||||
function parseSourceMapInput(str) {
|
||||
return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
|
||||
}
|
||||
exports.parseSourceMapInput = parseSourceMapInput;
|
||||
|
||||
/**
|
||||
* Compute the URL of a source given the the source root, the source's
|
||||
* URL, and the source map's URL.
|
||||
*/
|
||||
function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
|
||||
sourceURL = sourceURL || '';
|
||||
|
||||
if (sourceRoot) {
|
||||
// This follows what Chrome does.
|
||||
if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
|
||||
sourceRoot += '/';
|
||||
}
|
||||
// The spec says:
|
||||
// Line 4: An optional source root, useful for relocating source
|
||||
// files on a server or removing repeated values in the
|
||||
// “sources” entry. This value is prepended to the individual
|
||||
// entries in the “source” field.
|
||||
sourceURL = sourceRoot + sourceURL;
|
||||
}
|
||||
|
||||
// Historically, SourceMapConsumer did not take the sourceMapURL as
|
||||
// a parameter. This mode is still somewhat supported, which is why
|
||||
// this code block is conditional. However, it's preferable to pass
|
||||
// the source map URL to SourceMapConsumer, so that this function
|
||||
// can implement the source URL resolution algorithm as outlined in
|
||||
// the spec. This block is basically the equivalent of:
|
||||
// new URL(sourceURL, sourceMapURL).toString()
|
||||
// ... except it avoids using URL, which wasn't available in the
|
||||
// older releases of node still supported by this library.
|
||||
//
|
||||
// The spec says:
|
||||
// If the sources are not absolute URLs after prepending of the
|
||||
// “sourceRoot”, the sources are resolved relative to the
|
||||
// SourceMap (like resolving script src in a html document).
|
||||
if (sourceMapURL) {
|
||||
var parsed = urlParse(sourceMapURL);
|
||||
if (!parsed) {
|
||||
throw new Error("sourceMapURL could not be parsed");
|
||||
}
|
||||
if (parsed.path) {
|
||||
// Strip the last path component, but keep the "/".
|
||||
var index = parsed.path.lastIndexOf('/');
|
||||
if (index >= 0) {
|
||||
parsed.path = parsed.path.substring(0, index + 1);
|
||||
}
|
||||
}
|
||||
sourceURL = join(urlGenerate(parsed), sourceURL);
|
||||
}
|
||||
|
||||
return normalize(sourceURL);
|
||||
}
|
||||
exports.computeSourceURL = computeSourceURL;
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "source-map-js",
|
||||
"description": "Generates and consumes source maps",
|
||||
"version": "1.2.2",
|
||||
"homepage": "https://github.com/7rulnik/source-map-js",
|
||||
"author": "Valentin 7rulnik Semirulnik <v7rulnik@gmail.com>",
|
||||
"contributors": [
|
||||
"Nick Fitzgerald <nfitzgerald@mozilla.com>",
|
||||
"Tobias Koppers <tobias.koppers@googlemail.com>",
|
||||
"Duncan Beevers <duncan@dweebd.com>",
|
||||
"Stephen Crane <scrane@mozilla.com>",
|
||||
"Ryan Seddon <seddon.ryan@gmail.com>",
|
||||
"Miles Elam <miles.elam@deem.com>",
|
||||
"Mihai Bazon <mihai.bazon@gmail.com>",
|
||||
"Michael Ficarra <github.public.email@michael.ficarra.me>",
|
||||
"Todd Wolfson <todd@twolfson.com>",
|
||||
"Alexander Solovyov <alexander@solovyov.net>",
|
||||
"Felix Gnass <fgnass@gmail.com>",
|
||||
"Conrad Irwin <conrad.irwin@gmail.com>",
|
||||
"usrbincc <usrbincc@yahoo.com>",
|
||||
"David Glasser <glasser@davidglasser.net>",
|
||||
"Chase Douglas <chase@newrelic.com>",
|
||||
"Evan Wallace <evan.exe@gmail.com>",
|
||||
"Heather Arthur <fayearthur@gmail.com>",
|
||||
"Hugh Kennedy <hughskennedy@gmail.com>",
|
||||
"David Glasser <glasser@davidglasser.net>",
|
||||
"Simon Lydell <simon.lydell@gmail.com>",
|
||||
"Jmeas Smith <jellyes2@gmail.com>",
|
||||
"Michael Z Goddard <mzgoddard@gmail.com>",
|
||||
"azu <azu@users.noreply.github.com>",
|
||||
"John Gozde <john@gozde.ca>",
|
||||
"Adam Kirkton <akirkton@truefitinnovation.com>",
|
||||
"Chris Montgomery <christopher.montgomery@dowjones.com>",
|
||||
"J. Ryan Stinnett <jryans@gmail.com>",
|
||||
"Jack Herrington <jherrington@walmartlabs.com>",
|
||||
"Chris Truter <jeffpalentine@gmail.com>",
|
||||
"Daniel Espeset <daniel@danielespeset.com>",
|
||||
"Jamie Wong <jamie.lf.wong@gmail.com>",
|
||||
"Eddy Bruël <ejpbruel@mozilla.com>",
|
||||
"Hawken Rives <hawkrives@gmail.com>",
|
||||
"Gilad Peleg <giladp007@gmail.com>",
|
||||
"djchie <djchie.dev@gmail.com>",
|
||||
"Gary Ye <garysye@gmail.com>",
|
||||
"Nicolas Lalevée <nicolas.lalevee@hibnet.org>"
|
||||
],
|
||||
"repository": "7rulnik/source-map-js",
|
||||
"main": "./source-map.js",
|
||||
"files": [
|
||||
"source-map.js",
|
||||
"source-map.d.ts",
|
||||
"lib/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"test": "npm run build && node test/run-tests.js",
|
||||
"build": "webpack --color",
|
||||
"toc": "doctoc --title '## Table of Contents' README.md && doctoc --title '## Table of Contents' CONTRIBUTING.md"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-publish": "^3.1.0",
|
||||
"doctoc": "^0.15.0",
|
||||
"webpack": "^1.12.0"
|
||||
},
|
||||
"clean-publish": {
|
||||
"cleanDocs": true
|
||||
},
|
||||
"typings": "source-map.d.ts"
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2009-2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE.txt or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
exports.SourceMapGenerator = require('./lib/source-map-generator').SourceMapGenerator;
|
||||
exports.SourceMapConsumer = require('./lib/source-map-consumer').SourceMapConsumer;
|
||||
exports.SourceNode = require('./lib/source-node').SourceNode;
|
||||
+645
@@ -0,0 +1,645 @@
|
||||
const n = /[^\0-\x7E]/;
|
||||
const t = /[\x2E\u3002\uFF0E\uFF61]/g;
|
||||
const o = {
|
||||
overflow: "Overflow Error",
|
||||
"not-basic": "Illegal Input",
|
||||
"invalid-input": "Invalid Input"
|
||||
};
|
||||
const e = Math.floor;
|
||||
const r = String.fromCharCode;
|
||||
function s(n2) {
|
||||
throw new RangeError(o[n2]);
|
||||
}
|
||||
const c = function(n2, t2) {
|
||||
return n2 + 22 + 75 * (n2 < 26) - ((t2 != 0) << 5);
|
||||
};
|
||||
const u = function(n2, t2, o2) {
|
||||
let r2 = 0;
|
||||
for (n2 = o2 ? e(n2 / 700) : n2 >> 1, n2 += e(n2 / t2); n2 > 455; r2 += 36) {
|
||||
n2 = e(n2 / 35);
|
||||
}
|
||||
return e(r2 + 36 * n2 / (n2 + 38));
|
||||
};
|
||||
function toASCII(o2) {
|
||||
return (function(n2, o3) {
|
||||
const e2 = n2.split("@");
|
||||
let r2 = "";
|
||||
e2.length > 1 && (r2 = e2[0] + "@", n2 = e2[1]);
|
||||
const s2 = (function(n3, t2) {
|
||||
const o4 = [];
|
||||
let e3 = n3.length;
|
||||
for (; e3--; ) {
|
||||
o4[e3] = t2(n3[e3]);
|
||||
}
|
||||
return o4;
|
||||
})((n2 = n2.replace(t, ".")).split("."), o3).join(".");
|
||||
return r2 + s2;
|
||||
})(o2, function(t2) {
|
||||
return n.test(t2) ? "xn--" + (function(n2) {
|
||||
const t3 = [];
|
||||
const o3 = (n2 = (function(n3) {
|
||||
const t4 = [];
|
||||
let o4 = 0;
|
||||
const e2 = n3.length;
|
||||
for (; o4 < e2; ) {
|
||||
const r2 = n3.charCodeAt(o4++);
|
||||
if (r2 >= 55296 && r2 <= 56319 && o4 < e2) {
|
||||
const e3 = n3.charCodeAt(o4++);
|
||||
(64512 & e3) == 56320 ? t4.push(((1023 & r2) << 10) + (1023 & e3) + 65536) : (t4.push(r2), o4--);
|
||||
} else {
|
||||
t4.push(r2);
|
||||
}
|
||||
}
|
||||
return t4;
|
||||
})(n2)).length;
|
||||
let f = 128;
|
||||
let i = 0;
|
||||
let l = 72;
|
||||
for (const o4 of n2) {
|
||||
o4 < 128 && t3.push(r(o4));
|
||||
}
|
||||
const h = t3.length;
|
||||
let p = h;
|
||||
for (h && t3.push("-"); p < o3; ) {
|
||||
let o4 = 2147483647;
|
||||
for (const t4 of n2) {
|
||||
t4 >= f && t4 < o4 && (o4 = t4);
|
||||
}
|
||||
const a = p + 1;
|
||||
o4 - f > e((2147483647 - i) / a) && s("overflow"), i += (o4 - f) * a, f = o4;
|
||||
for (const o5 of n2) {
|
||||
if (o5 < f && ++i > 2147483647 && s("overflow"), o5 == f) {
|
||||
let n3 = i;
|
||||
for (let o6 = 36; ; o6 += 36) {
|
||||
const s2 = o6 <= l ? 1 : o6 >= l + 26 ? 26 : o6 - l;
|
||||
if (n3 < s2) {
|
||||
break;
|
||||
}
|
||||
const u2 = n3 - s2;
|
||||
const f2 = 36 - s2;
|
||||
t3.push(r(c(s2 + u2 % f2, 0))), n3 = e(u2 / f2);
|
||||
}
|
||||
t3.push(r(c(n3, 0))), l = u(i, a, p == h), i = 0, ++p;
|
||||
}
|
||||
}
|
||||
++i, ++f;
|
||||
}
|
||||
return t3.join("");
|
||||
})(t2) : t2;
|
||||
});
|
||||
}
|
||||
|
||||
const HASH_RE = /#/g;
|
||||
const AMPERSAND_RE = /&/g;
|
||||
const SLASH_RE = /\//g;
|
||||
const EQUAL_RE = /=/g;
|
||||
const IM_RE = /\?/g;
|
||||
const PLUS_RE = /\+/g;
|
||||
const ENC_CARET_RE = /%5e/gi;
|
||||
const ENC_BACKTICK_RE = /%60/gi;
|
||||
const ENC_CURLY_OPEN_RE = /%7b/gi;
|
||||
const ENC_PIPE_RE = /%7c/gi;
|
||||
const ENC_CURLY_CLOSE_RE = /%7d/gi;
|
||||
const ENC_SPACE_RE = /%20/gi;
|
||||
const ENC_SLASH_RE = /%2f/gi;
|
||||
const ENC_ENC_SLASH_RE = /%252f/gi;
|
||||
function encode(text) {
|
||||
return encodeURI("" + text).replace(ENC_PIPE_RE, "|");
|
||||
}
|
||||
function encodeHash(text) {
|
||||
return encode(text).replace(ENC_CURLY_OPEN_RE, "{").replace(ENC_CURLY_CLOSE_RE, "}").replace(ENC_CARET_RE, "^");
|
||||
}
|
||||
function encodeQueryValue(input) {
|
||||
return encode(typeof input === "string" ? input : JSON.stringify(input)).replace(PLUS_RE, "%2B").replace(ENC_SPACE_RE, "+").replace(HASH_RE, "%23").replace(AMPERSAND_RE, "%26").replace(ENC_BACKTICK_RE, "`").replace(ENC_CARET_RE, "^").replace(SLASH_RE, "%2F");
|
||||
}
|
||||
function encodeQueryKey(text) {
|
||||
return encodeQueryValue(text).replace(EQUAL_RE, "%3D");
|
||||
}
|
||||
function encodePath(text) {
|
||||
return encode(text).replace(HASH_RE, "%23").replace(IM_RE, "%3F").replace(ENC_ENC_SLASH_RE, "%2F").replace(AMPERSAND_RE, "%26").replace(PLUS_RE, "%2B");
|
||||
}
|
||||
function encodeParam(text) {
|
||||
return encodePath(text).replace(SLASH_RE, "%2F");
|
||||
}
|
||||
function decode(text = "") {
|
||||
try {
|
||||
return decodeURIComponent("" + text);
|
||||
} catch {
|
||||
return "" + text;
|
||||
}
|
||||
}
|
||||
function decodePath(text) {
|
||||
return decode(text.replace(ENC_SLASH_RE, "%252F"));
|
||||
}
|
||||
function decodeQueryKey(text) {
|
||||
return decode(text.replace(PLUS_RE, " "));
|
||||
}
|
||||
function decodeQueryValue(text) {
|
||||
return decode(text.replace(PLUS_RE, " "));
|
||||
}
|
||||
function encodeHost(name = "") {
|
||||
return toASCII(name);
|
||||
}
|
||||
|
||||
function parseQuery(parametersString = "") {
|
||||
const object = /* @__PURE__ */ Object.create(null);
|
||||
if (parametersString[0] === "?") {
|
||||
parametersString = parametersString.slice(1);
|
||||
}
|
||||
for (const parameter of parametersString.split("&")) {
|
||||
const s = parameter.match(/([^=]+)=?(.*)/) || [];
|
||||
if (s.length < 2) {
|
||||
continue;
|
||||
}
|
||||
const key = decodeQueryKey(s[1]);
|
||||
if (key === "__proto__" || key === "constructor") {
|
||||
continue;
|
||||
}
|
||||
const value = decodeQueryValue(s[2] || "");
|
||||
if (object[key] === void 0) {
|
||||
object[key] = value;
|
||||
} else if (Array.isArray(object[key])) {
|
||||
object[key].push(value);
|
||||
} else {
|
||||
object[key] = [object[key], value];
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
function encodeQueryItem(key, value) {
|
||||
if (typeof value === "number" || typeof value === "boolean") {
|
||||
value = String(value);
|
||||
}
|
||||
if (!value) {
|
||||
return encodeQueryKey(key);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(
|
||||
(_value) => `${encodeQueryKey(key)}=${encodeQueryValue(_value)}`
|
||||
).join("&");
|
||||
}
|
||||
return `${encodeQueryKey(key)}=${encodeQueryValue(value)}`;
|
||||
}
|
||||
function stringifyQuery(query) {
|
||||
return Object.keys(query).filter((k) => query[k] !== void 0).map((k) => encodeQueryItem(k, query[k])).filter(Boolean).join("&");
|
||||
}
|
||||
|
||||
const PROTOCOL_STRICT_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{1,2})/;
|
||||
const PROTOCOL_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{2})?/;
|
||||
const PROTOCOL_RELATIVE_REGEX = /^([/\\]\s*){2,}[^/\\]/;
|
||||
const PROTOCOL_SCRIPT_RE = /^[\s\0]*(blob|data|javascript|vbscript):$/i;
|
||||
const TRAILING_SLASH_RE = /\/$|\/\?|\/#/;
|
||||
const JOIN_LEADING_SLASH_RE = /^\.?\//;
|
||||
function isRelative(inputString) {
|
||||
return ["./", "../"].some((string_) => inputString.startsWith(string_));
|
||||
}
|
||||
function hasProtocol(inputString, opts = {}) {
|
||||
if (typeof opts === "boolean") {
|
||||
opts = { acceptRelative: opts };
|
||||
}
|
||||
if (opts.strict) {
|
||||
return PROTOCOL_STRICT_REGEX.test(inputString);
|
||||
}
|
||||
return PROTOCOL_REGEX.test(inputString) || (opts.acceptRelative ? PROTOCOL_RELATIVE_REGEX.test(inputString) : false);
|
||||
}
|
||||
function isScriptProtocol(protocol) {
|
||||
return !!protocol && PROTOCOL_SCRIPT_RE.test(protocol);
|
||||
}
|
||||
function hasTrailingSlash(input = "", respectQueryAndFragment) {
|
||||
if (!respectQueryAndFragment) {
|
||||
return input.endsWith("/");
|
||||
}
|
||||
return TRAILING_SLASH_RE.test(input);
|
||||
}
|
||||
function withoutTrailingSlash(input = "", respectQueryAndFragment) {
|
||||
if (!respectQueryAndFragment) {
|
||||
return (hasTrailingSlash(input) ? input.slice(0, -1) : input) || "/";
|
||||
}
|
||||
if (!hasTrailingSlash(input, true)) {
|
||||
return input || "/";
|
||||
}
|
||||
let path = input;
|
||||
let fragment = "";
|
||||
const fragmentIndex = input.indexOf("#");
|
||||
if (fragmentIndex !== -1) {
|
||||
path = input.slice(0, fragmentIndex);
|
||||
fragment = input.slice(fragmentIndex);
|
||||
}
|
||||
const [s0, ...s] = path.split("?");
|
||||
const cleanPath = s0.endsWith("/") ? s0.slice(0, -1) : s0;
|
||||
return (cleanPath || "/") + (s.length > 0 ? `?${s.join("?")}` : "") + fragment;
|
||||
}
|
||||
function withTrailingSlash(input = "", respectQueryAndFragment) {
|
||||
if (!respectQueryAndFragment) {
|
||||
return input.endsWith("/") ? input : input + "/";
|
||||
}
|
||||
if (hasTrailingSlash(input, true)) {
|
||||
return input || "/";
|
||||
}
|
||||
let path = input;
|
||||
let fragment = "";
|
||||
const fragmentIndex = input.indexOf("#");
|
||||
if (fragmentIndex !== -1) {
|
||||
path = input.slice(0, fragmentIndex);
|
||||
fragment = input.slice(fragmentIndex);
|
||||
if (!path) {
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
const [s0, ...s] = path.split("?");
|
||||
return s0 + "/" + (s.length > 0 ? `?${s.join("?")}` : "") + fragment;
|
||||
}
|
||||
function hasLeadingSlash(input = "") {
|
||||
return input.startsWith("/");
|
||||
}
|
||||
function withoutLeadingSlash(input = "") {
|
||||
return (hasLeadingSlash(input) ? input.slice(1) : input) || "/";
|
||||
}
|
||||
function withLeadingSlash(input = "") {
|
||||
return hasLeadingSlash(input) ? input : "/" + input;
|
||||
}
|
||||
function cleanDoubleSlashes(input = "") {
|
||||
return input.split("://").map((string_) => string_.replace(/\/{2,}/g, "/")).join("://");
|
||||
}
|
||||
function withBase(input, base) {
|
||||
if (isEmptyURL(base) || hasProtocol(input)) {
|
||||
return input;
|
||||
}
|
||||
const _base = withoutTrailingSlash(base);
|
||||
if (input.startsWith(_base)) {
|
||||
const nextChar = input[_base.length];
|
||||
if (!nextChar || nextChar === "/" || nextChar === "?") {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
return joinURL(_base, input);
|
||||
}
|
||||
function withoutBase(input, base) {
|
||||
if (isEmptyURL(base)) {
|
||||
return input;
|
||||
}
|
||||
const _base = withoutTrailingSlash(base);
|
||||
if (!input.startsWith(_base)) {
|
||||
return input;
|
||||
}
|
||||
const nextChar = input[_base.length];
|
||||
if (nextChar && nextChar !== "/" && nextChar !== "?") {
|
||||
return input;
|
||||
}
|
||||
const trimmed = input.slice(_base.length).replace(/^\/+/, "");
|
||||
return "/" + trimmed;
|
||||
}
|
||||
function withQuery(input, query) {
|
||||
const parsed = parseURL(input);
|
||||
const mergedQuery = { ...parseQuery(parsed.search), ...query };
|
||||
parsed.search = stringifyQuery(mergedQuery);
|
||||
return stringifyParsedURL(parsed);
|
||||
}
|
||||
function filterQuery(input, predicate) {
|
||||
if (!input.includes("?")) {
|
||||
return input;
|
||||
}
|
||||
const parsed = parseURL(input);
|
||||
const query = parseQuery(parsed.search);
|
||||
const filteredQuery = Object.fromEntries(
|
||||
Object.entries(query).filter(([key, value]) => predicate(key, value))
|
||||
);
|
||||
parsed.search = stringifyQuery(filteredQuery);
|
||||
return stringifyParsedURL(parsed);
|
||||
}
|
||||
function getQuery(input) {
|
||||
return parseQuery(parseURL(input).search);
|
||||
}
|
||||
function isEmptyURL(url) {
|
||||
return !url || url === "/";
|
||||
}
|
||||
function isNonEmptyURL(url) {
|
||||
return url && url !== "/";
|
||||
}
|
||||
function joinURL(base, ...input) {
|
||||
let url = base || "";
|
||||
for (const segment of input.filter((url2) => isNonEmptyURL(url2))) {
|
||||
if (url) {
|
||||
const _segment = segment.replace(JOIN_LEADING_SLASH_RE, "");
|
||||
url = withTrailingSlash(url) + _segment;
|
||||
} else {
|
||||
url = segment;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
function joinRelativeURL(..._input) {
|
||||
const JOIN_SEGMENT_SPLIT_RE = /\/(?!\/)/;
|
||||
const input = _input.filter(Boolean);
|
||||
const segments = [];
|
||||
let segmentsDepth = 0;
|
||||
for (const i of input) {
|
||||
if (!i || i === "/") {
|
||||
continue;
|
||||
}
|
||||
for (const [sindex, s] of i.split(JOIN_SEGMENT_SPLIT_RE).entries()) {
|
||||
if (!s || s === ".") {
|
||||
continue;
|
||||
}
|
||||
if (s === "..") {
|
||||
if (segments.length === 1 && hasProtocol(segments[0])) {
|
||||
continue;
|
||||
}
|
||||
segments.pop();
|
||||
segmentsDepth--;
|
||||
continue;
|
||||
}
|
||||
if (sindex === 1 && segments[segments.length - 1]?.endsWith(":/")) {
|
||||
segments[segments.length - 1] += "/" + s;
|
||||
continue;
|
||||
}
|
||||
segments.push(s);
|
||||
segmentsDepth++;
|
||||
}
|
||||
}
|
||||
let url = segments.join("/");
|
||||
if (segmentsDepth >= 0) {
|
||||
if (input[0]?.startsWith("/") && !url.startsWith("/")) {
|
||||
url = "/" + url;
|
||||
} else if (input[0]?.startsWith("./") && !url.startsWith("./")) {
|
||||
url = "./" + url;
|
||||
}
|
||||
} else {
|
||||
url = "../".repeat(-1 * segmentsDepth) + url;
|
||||
}
|
||||
if (input[input.length - 1]?.endsWith("/") && !url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
function withHttp(input) {
|
||||
return withProtocol(input, "http://");
|
||||
}
|
||||
function withHttps(input) {
|
||||
return withProtocol(input, "https://");
|
||||
}
|
||||
function withoutProtocol(input) {
|
||||
return withProtocol(input, "");
|
||||
}
|
||||
function withProtocol(input, protocol) {
|
||||
let match = input.match(PROTOCOL_REGEX);
|
||||
if (!match) {
|
||||
match = input.match(/^\/{2,}/);
|
||||
}
|
||||
if (!match) {
|
||||
return protocol + input;
|
||||
}
|
||||
return protocol + input.slice(match[0].length);
|
||||
}
|
||||
function normalizeURL(input) {
|
||||
const parsed = parseURL(input);
|
||||
parsed.pathname = encodePath(decodePath(parsed.pathname));
|
||||
parsed.hash = encodeHash(decode(parsed.hash));
|
||||
parsed.host = encodeHost(decode(parsed.host));
|
||||
parsed.search = stringifyQuery(parseQuery(parsed.search));
|
||||
return stringifyParsedURL(parsed);
|
||||
}
|
||||
function resolveURL(base = "", ...inputs) {
|
||||
if (typeof base !== "string") {
|
||||
throw new TypeError(
|
||||
`URL input should be string received ${typeof base} (${base})`
|
||||
);
|
||||
}
|
||||
const filteredInputs = inputs.filter((input) => isNonEmptyURL(input));
|
||||
if (filteredInputs.length === 0) {
|
||||
return base;
|
||||
}
|
||||
const url = parseURL(base);
|
||||
for (const inputSegment of filteredInputs) {
|
||||
const urlSegment = parseURL(inputSegment);
|
||||
if (urlSegment.pathname) {
|
||||
url.pathname = withTrailingSlash(url.pathname) + withoutLeadingSlash(urlSegment.pathname);
|
||||
}
|
||||
if (urlSegment.hash && urlSegment.hash !== "#") {
|
||||
url.hash = urlSegment.hash;
|
||||
}
|
||||
if (urlSegment.search && urlSegment.search !== "?") {
|
||||
if (url.search && url.search !== "?") {
|
||||
const queryString = stringifyQuery({
|
||||
...parseQuery(url.search),
|
||||
...parseQuery(urlSegment.search)
|
||||
});
|
||||
url.search = queryString.length > 0 ? "?" + queryString : "";
|
||||
} else {
|
||||
url.search = urlSegment.search;
|
||||
}
|
||||
}
|
||||
}
|
||||
return stringifyParsedURL(url);
|
||||
}
|
||||
function isSamePath(p1, p2) {
|
||||
return decode(withoutTrailingSlash(p1)) === decode(withoutTrailingSlash(p2));
|
||||
}
|
||||
function isEqual(a, b, options = {}) {
|
||||
if (!options.trailingSlash) {
|
||||
a = withTrailingSlash(a);
|
||||
b = withTrailingSlash(b);
|
||||
}
|
||||
if (!options.leadingSlash) {
|
||||
a = withLeadingSlash(a);
|
||||
b = withLeadingSlash(b);
|
||||
}
|
||||
if (!options.encoding) {
|
||||
a = decode(a);
|
||||
b = decode(b);
|
||||
}
|
||||
return a === b;
|
||||
}
|
||||
function withFragment(input, hash) {
|
||||
if (!hash || hash === "#") {
|
||||
return input;
|
||||
}
|
||||
const parsed = parseURL(input);
|
||||
parsed.hash = hash === "" ? "" : "#" + encodeHash(hash);
|
||||
return stringifyParsedURL(parsed);
|
||||
}
|
||||
function withoutFragment(input) {
|
||||
return stringifyParsedURL({ ...parseURL(input), hash: "" });
|
||||
}
|
||||
function withoutHost(input) {
|
||||
const parsed = parseURL(input);
|
||||
return (parsed.pathname || "/") + parsed.search + parsed.hash;
|
||||
}
|
||||
|
||||
const protocolRelative = Symbol.for("ufo:protocolRelative");
|
||||
function parseURL(input = "", defaultProto) {
|
||||
const _specialProtoMatch = input.match(
|
||||
/^[\s\0]*(blob:|data:|javascript:|vbscript:)(.*)/i
|
||||
);
|
||||
if (_specialProtoMatch) {
|
||||
const [, _proto, _pathname = ""] = _specialProtoMatch;
|
||||
return {
|
||||
protocol: _proto.toLowerCase(),
|
||||
pathname: _pathname,
|
||||
href: _proto + _pathname,
|
||||
auth: "",
|
||||
host: "",
|
||||
search: "",
|
||||
hash: ""
|
||||
};
|
||||
}
|
||||
if (!hasProtocol(input, { acceptRelative: true })) {
|
||||
return defaultProto ? parseURL(defaultProto + input) : parsePath(input);
|
||||
}
|
||||
const [, protocol = "", auth, hostAndPath = ""] = input.replace(/\\/g, "/").match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/@]+@)?(.*)/) || [];
|
||||
let [, host = "", path = ""] = hostAndPath.match(/([^#/?]*)(.*)?/) || [];
|
||||
if (protocol === "file:") {
|
||||
path = path.replace(/\/(?=[A-Za-z]:)/, "");
|
||||
}
|
||||
const { pathname, search, hash } = parsePath(path);
|
||||
return {
|
||||
protocol: protocol.toLowerCase(),
|
||||
auth: auth ? auth.slice(0, Math.max(0, auth.length - 1)) : "",
|
||||
host,
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
[protocolRelative]: !protocol
|
||||
};
|
||||
}
|
||||
function parsePath(input = "") {
|
||||
const [pathname = "", search = "", hash = ""] = (input.match(/([^#?]*)(\?[^#]*)?(#.*)?/) || []).splice(1);
|
||||
return {
|
||||
pathname,
|
||||
search,
|
||||
hash
|
||||
};
|
||||
}
|
||||
function parseAuth(input = "") {
|
||||
const [username, password] = input.split(":");
|
||||
return {
|
||||
username: decode(username),
|
||||
password: decode(password)
|
||||
};
|
||||
}
|
||||
function parseHost(input = "") {
|
||||
const [hostname, port] = (input.match(/([^/:]*):?(\d+)?/) || []).splice(1);
|
||||
return {
|
||||
hostname: decode(hostname),
|
||||
port
|
||||
};
|
||||
}
|
||||
function stringifyParsedURL(parsed) {
|
||||
const pathname = parsed.pathname || "";
|
||||
const search = parsed.search ? (parsed.search.startsWith("?") ? "" : "?") + parsed.search : "";
|
||||
const hash = parsed.hash || "";
|
||||
const auth = parsed.auth ? parsed.auth + "@" : "";
|
||||
const host = parsed.host || "";
|
||||
const proto = parsed.protocol || parsed[protocolRelative] ? (parsed.protocol || "") + "//" : "";
|
||||
return proto + auth + host + pathname + search + hash;
|
||||
}
|
||||
const FILENAME_STRICT_REGEX = /\/([^/]+\.[^/]+)$/;
|
||||
const FILENAME_REGEX = /\/([^/]+)$/;
|
||||
function parseFilename(input = "", opts) {
|
||||
const { pathname } = parseURL(input);
|
||||
const matches = opts?.strict ? pathname.match(FILENAME_STRICT_REGEX) : pathname.match(FILENAME_REGEX);
|
||||
return matches ? matches[1] : void 0;
|
||||
}
|
||||
|
||||
class $URL {
|
||||
protocol;
|
||||
host;
|
||||
auth;
|
||||
pathname;
|
||||
query = {};
|
||||
hash;
|
||||
constructor(input = "") {
|
||||
if (typeof input !== "string") {
|
||||
throw new TypeError(
|
||||
`URL input should be string received ${typeof input} (${input})`
|
||||
);
|
||||
}
|
||||
const parsed = parseURL(input);
|
||||
this.protocol = decode(parsed.protocol);
|
||||
this.host = decode(parsed.host);
|
||||
this.auth = decode(parsed.auth);
|
||||
this.pathname = decodePath(parsed.pathname);
|
||||
this.query = parseQuery(parsed.search);
|
||||
this.hash = decode(parsed.hash);
|
||||
}
|
||||
get hostname() {
|
||||
return parseHost(this.host).hostname;
|
||||
}
|
||||
get port() {
|
||||
return parseHost(this.host).port || "";
|
||||
}
|
||||
get username() {
|
||||
return parseAuth(this.auth).username;
|
||||
}
|
||||
get password() {
|
||||
return parseAuth(this.auth).password || "";
|
||||
}
|
||||
get hasProtocol() {
|
||||
return this.protocol.length;
|
||||
}
|
||||
get isAbsolute() {
|
||||
return this.hasProtocol || this.pathname[0] === "/";
|
||||
}
|
||||
get search() {
|
||||
const q = stringifyQuery(this.query);
|
||||
return q.length > 0 ? "?" + q : "";
|
||||
}
|
||||
get searchParams() {
|
||||
const p = new URLSearchParams();
|
||||
for (const name in this.query) {
|
||||
const value = this.query[name];
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
p.append(name, v);
|
||||
}
|
||||
} else {
|
||||
p.append(
|
||||
name,
|
||||
typeof value === "string" ? value : JSON.stringify(value)
|
||||
);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
get origin() {
|
||||
return (this.protocol ? this.protocol + "//" : "") + encodeHost(this.host);
|
||||
}
|
||||
get fullpath() {
|
||||
return encodePath(this.pathname) + this.search + encodeHash(this.hash);
|
||||
}
|
||||
get encodedAuth() {
|
||||
if (!this.auth) {
|
||||
return "";
|
||||
}
|
||||
const { username, password } = parseAuth(this.auth);
|
||||
return encodeURIComponent(username) + (password ? ":" + encodeURIComponent(password) : "");
|
||||
}
|
||||
get href() {
|
||||
const auth = this.encodedAuth;
|
||||
const originWithAuth = (this.protocol ? this.protocol + "//" : "") + (auth ? auth + "@" : "") + encodeHost(this.host);
|
||||
return this.hasProtocol && this.isAbsolute ? originWithAuth + this.fullpath : this.fullpath;
|
||||
}
|
||||
append(url) {
|
||||
if (url.hasProtocol) {
|
||||
throw new Error("Cannot append a URL with protocol");
|
||||
}
|
||||
Object.assign(this.query, url.query);
|
||||
if (url.pathname) {
|
||||
this.pathname = withTrailingSlash(this.pathname) + withoutLeadingSlash(url.pathname);
|
||||
}
|
||||
if (url.hash) {
|
||||
this.hash = url.hash;
|
||||
}
|
||||
}
|
||||
toJSON() {
|
||||
return this.href;
|
||||
}
|
||||
toString() {
|
||||
return this.href;
|
||||
}
|
||||
}
|
||||
function createURL(input) {
|
||||
return new $URL(input);
|
||||
}
|
||||
|
||||
export { $URL, cleanDoubleSlashes, createURL, decode, decodePath, decodeQueryKey, decodeQueryValue, encode, encodeHash, encodeHost, encodeParam, encodePath, encodeQueryItem, encodeQueryKey, encodeQueryValue, filterQuery, getQuery, hasLeadingSlash, hasProtocol, hasTrailingSlash, isEmptyURL, isEqual, isNonEmptyURL, isRelative, isSamePath, isScriptProtocol, joinRelativeURL, joinURL, normalizeURL, parseAuth, parseFilename, parseHost, parsePath, parseQuery, parseURL, resolveURL, stringifyParsedURL, stringifyQuery, withBase, withFragment, withHttp, withHttps, withLeadingSlash, withProtocol, withQuery, withTrailingSlash, withoutBase, withoutFragment, withoutHost, withoutLeadingSlash, withoutProtocol, withoutTrailingSlash };
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "ufo",
|
||||
"version": "1.6.4",
|
||||
"description": "URL utils for humans",
|
||||
"repository": "unjs/ufo",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "automd && unbuild",
|
||||
"automd": "automd",
|
||||
"dev": "vitest",
|
||||
"lint": "eslint . && prettier -c src test",
|
||||
"lint:fix": "eslint --fix . && prettier -w src test",
|
||||
"prepack": "pnpm build",
|
||||
"release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
|
||||
"test": "pnpm lint && vitest run --typecheck"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0",
|
||||
"@vitest/coverage-v8": "^4.1.5",
|
||||
"automd": "^0.4.3",
|
||||
"changelogen": "^0.6.2",
|
||||
"eslint": "^10.2.1",
|
||||
"eslint-config-unjs": "^0.6.2",
|
||||
"jiti": "^2.6.1",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "^6.0.3",
|
||||
"unbuild": "^3.6.1",
|
||||
"untyped": "^2.0.0",
|
||||
"vitest": "^4.1.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.2"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
export { c as createHead } from './shared/unhead.oZ7pkTV1.mjs';
|
||||
export { c as createDomRenderer, r as renderDOMHead } from './shared/unhead.Bjbp1C8D.mjs';
|
||||
import './shared/unhead.D7HkBzZn.mjs';
|
||||
import './shared/unhead.CGPOfp5O.mjs';
|
||||
import './shared/unhead.Bm4Y6XQI.mjs';
|
||||
import 'hookable';
|
||||
import './shared/unhead.C5Bksi2B.mjs';
|
||||
import './shared/unhead.-hZVKou0.mjs';
|
||||
|
||||
function createDebouncedFn(callee, delayer) {
|
||||
let ctxId = 0;
|
||||
return () => {
|
||||
const delayFnCtxId = ++ctxId;
|
||||
delayer(() => {
|
||||
if (ctxId === delayFnCtxId) {
|
||||
callee();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { createDebouncedFn };
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin, A as AliasSortingPlugin } from './shared/unhead.tQnRaXfX.mjs';
|
||||
import { c as createUnhead } from './shared/unhead.D7HkBzZn.mjs';
|
||||
import { c as createHead$1 } from './shared/unhead.oZ7pkTV1.mjs';
|
||||
import { c as createHead$2 } from './shared/unhead.Dssd7L5I.mjs';
|
||||
import './shared/unhead.CHEy9ana.mjs';
|
||||
import './shared/unhead.BGFxPGPQ.mjs';
|
||||
import './shared/unhead.CGPOfp5O.mjs';
|
||||
import './shared/unhead.Bm4Y6XQI.mjs';
|
||||
import 'hookable';
|
||||
import './shared/unhead.C5Bksi2B.mjs';
|
||||
import './shared/unhead.Bjbp1C8D.mjs';
|
||||
import './shared/unhead.-hZVKou0.mjs';
|
||||
|
||||
const legacyPlugins = [DeprecationsPlugin, PromisesPlugin, TemplateParamsPlugin, AliasSortingPlugin];
|
||||
const activeHead = { value: null };
|
||||
function getActiveHead() {
|
||||
return activeHead.value;
|
||||
}
|
||||
function createHead(options = {}) {
|
||||
return activeHead.value = createHead$1({
|
||||
...options,
|
||||
plugins: [...legacyPlugins, ...options.plugins || []]
|
||||
});
|
||||
}
|
||||
function createServerHead(options = {}) {
|
||||
return activeHead.value = createHead$2({
|
||||
...options,
|
||||
plugins: [...legacyPlugins, ...options.plugins || []]
|
||||
});
|
||||
}
|
||||
const createHeadCore = createUnhead;
|
||||
|
||||
export { DeprecationsPlugin, activeHead, createHead, createHeadCore, createServerHead, getActiveHead, legacyPlugins };
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
function minifyCSS(code) {
|
||||
if (!/[\s/]|;\}|0\.\d/.test(code))
|
||||
return code;
|
||||
let result = "";
|
||||
let last = "";
|
||||
let i = 0;
|
||||
let parenDepth = 0;
|
||||
const len = code.length;
|
||||
const append = (value) => {
|
||||
result += value;
|
||||
last = value;
|
||||
};
|
||||
while (i < len) {
|
||||
const ch = code[i];
|
||||
if (ch === "'" || ch === '"') {
|
||||
const quote = ch;
|
||||
append(ch);
|
||||
i++;
|
||||
while (i < len && code[i] !== quote) {
|
||||
if (code[i] === "\\" && i + 1 < len)
|
||||
append(code[i++]);
|
||||
append(code[i++]);
|
||||
}
|
||||
if (i < len)
|
||||
append(code[i++]);
|
||||
} else if (ch === "/" && code[i + 1] === "*") {
|
||||
i += 2;
|
||||
while (i < len && !(code[i] === "*" && code[i + 1] === "/"))
|
||||
i++;
|
||||
i += 2;
|
||||
} else if (ch === "(") {
|
||||
parenDepth++;
|
||||
append(ch);
|
||||
i++;
|
||||
} else if (ch === ")") {
|
||||
parenDepth = Math.max(0, parenDepth - 1);
|
||||
append(ch);
|
||||
i++;
|
||||
} else if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
||||
while (i < len && (code[i] === " " || code[i] === " " || code[i] === "\n" || code[i] === "\r"))
|
||||
i++;
|
||||
const next = code[i];
|
||||
if (next === "!")
|
||||
continue;
|
||||
if (parenDepth > 0) {
|
||||
if (last && next && !isCSSCalcPunctuation(last) && !isCSSCalcPunctuation(next))
|
||||
append(" ");
|
||||
} else if (last && next && !isCSSPunctuation(last) && !isCSSPunctuation(next)) {
|
||||
append(" ");
|
||||
}
|
||||
} else if (ch === ";") {
|
||||
let j = i + 1;
|
||||
while (j < len && (code[j] === " " || code[j] === " " || code[j] === "\n" || code[j] === "\r"))
|
||||
j++;
|
||||
if (code[j] === "}") {
|
||||
i++;
|
||||
} else {
|
||||
append(ch);
|
||||
i++;
|
||||
}
|
||||
} else if (ch === "0" && code[i + 1] === "." && code[i + 2] >= "0" && code[i + 2] <= "9") {
|
||||
if (last && last >= "0" && last <= "9") {
|
||||
append(ch);
|
||||
i++;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
append(ch);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return result.trim();
|
||||
}
|
||||
function isCSSPunctuation(ch) {
|
||||
return ch === "{" || ch === "}" || ch === ";" || ch === ":" || ch === "," || ch === ">" || ch === "~" || ch === "+" || ch === "(" || ch === ")";
|
||||
}
|
||||
function isCSSCalcPunctuation(ch) {
|
||||
return ch === "*" || ch === "/" || ch === "(" || ch === ")" || ch === ",";
|
||||
}
|
||||
|
||||
function minifyJS(code) {
|
||||
if (!/[\s/]/.test(code))
|
||||
return code;
|
||||
let result = "";
|
||||
let last = "";
|
||||
let i = 0;
|
||||
const len = code.length;
|
||||
const append = (value) => {
|
||||
result += value;
|
||||
last = value;
|
||||
};
|
||||
while (i < len) {
|
||||
const ch = code[i];
|
||||
if (ch === "'" || ch === '"' || ch === "`") {
|
||||
const quote = ch;
|
||||
append(ch);
|
||||
i++;
|
||||
while (i < len && code[i] !== quote) {
|
||||
if (code[i] === "\\" && i + 1 < len) {
|
||||
append(code[i++]);
|
||||
}
|
||||
append(code[i++]);
|
||||
}
|
||||
if (i < len)
|
||||
append(code[i++]);
|
||||
} else if (ch === "/" && code[i + 1] === "/") {
|
||||
i += 2;
|
||||
while (i < len && code[i] !== "\n")
|
||||
i++;
|
||||
} else if (ch === "/" && code[i + 1] === "*") {
|
||||
i += 2;
|
||||
while (i < len && !(code[i] === "*" && code[i + 1] === "/"))
|
||||
i++;
|
||||
i += 2;
|
||||
} else if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
||||
let hasNewline = false;
|
||||
while (i < len && (code[i] === " " || code[i] === " " || code[i] === "\n" || code[i] === "\r")) {
|
||||
if (code[i] === "\n")
|
||||
hasNewline = true;
|
||||
i++;
|
||||
}
|
||||
const next = code[i];
|
||||
if (hasNewline && last && next && last !== "{" && last !== "}" && last !== ";" && next !== "}" && next !== ";")
|
||||
append("\n");
|
||||
else if (last && next && isIdentChar(last) && isIdentChar(next))
|
||||
append(" ");
|
||||
else if (last && next && (last === "+" && next === "+" || last === "-" && next === "-"))
|
||||
append(" ");
|
||||
} else {
|
||||
append(ch);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return result.trim();
|
||||
}
|
||||
function isIdentChar(ch) {
|
||||
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
||||
}
|
||||
|
||||
function isJsonWhitespace(char) {
|
||||
return char === " " || char === "\n" || char === "\r" || char === " ";
|
||||
}
|
||||
function minifyJSON(code) {
|
||||
let escaped = false;
|
||||
let inString = false;
|
||||
let segmentStart = 0;
|
||||
let chunks;
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
const char = code[i];
|
||||
if (inString) {
|
||||
if (escaped)
|
||||
escaped = false;
|
||||
else if (char === "\\")
|
||||
escaped = true;
|
||||
else if (char === '"')
|
||||
inString = false;
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
inString = true;
|
||||
continue;
|
||||
}
|
||||
if (isJsonWhitespace(char)) {
|
||||
chunks ||= [];
|
||||
if (segmentStart < i)
|
||||
chunks.push(code.slice(segmentStart, i));
|
||||
segmentStart = i + 1;
|
||||
}
|
||||
}
|
||||
if (!chunks)
|
||||
return code;
|
||||
try {
|
||||
JSON.parse(code);
|
||||
} catch {
|
||||
return code;
|
||||
}
|
||||
if (segmentStart < code.length)
|
||||
chunks.push(code.slice(segmentStart));
|
||||
return chunks.join("");
|
||||
}
|
||||
|
||||
export { minifyCSS, minifyJS, minifyJSON };
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
import { h as hasOwn } from './shared/unhead.Bf_fPVYA.mjs';
|
||||
|
||||
const TAG_HTML = 0;
|
||||
const TAG_HEAD = 1;
|
||||
const TAG_TITLE = 4;
|
||||
const TAG_META = 5;
|
||||
const TAG_BODY = 44;
|
||||
const TAG_SCRIPT = 52;
|
||||
const TAG_STYLE = 53;
|
||||
const TAG_LINK = 54;
|
||||
const TAG_BASE = 56;
|
||||
const LT_CHAR = 60;
|
||||
const GT_CHAR = 62;
|
||||
const SLASH_CHAR = 47;
|
||||
const EQUALS_CHAR = 61;
|
||||
const QUOTE_CHAR = 34;
|
||||
const APOS_CHAR = 39;
|
||||
const EXCLAMATION_CHAR = 33;
|
||||
const BACKSLASH_CHAR = 92;
|
||||
const DASH_CHAR = 45;
|
||||
const TagIdMap = {
|
||||
html: TAG_HTML,
|
||||
head: TAG_HEAD,
|
||||
title: TAG_TITLE,
|
||||
meta: TAG_META,
|
||||
body: TAG_BODY,
|
||||
script: TAG_SCRIPT,
|
||||
style: TAG_STYLE,
|
||||
link: TAG_LINK,
|
||||
base: TAG_BASE
|
||||
};
|
||||
const HEAD_ELEMENTS_TO_EXTRACT = /* @__PURE__ */ new Set([TAG_TITLE, TAG_META, TAG_LINK, TAG_SCRIPT, TAG_STYLE, TAG_BASE]);
|
||||
function prepareTemplate(html) {
|
||||
const template = parseHtmlForIndexes(html);
|
||||
Object.freeze(template.input);
|
||||
Object.freeze(template.indexes);
|
||||
return Object.freeze(template);
|
||||
}
|
||||
function isWhitespace(charCode) {
|
||||
return charCode === 32 || charCode === 9 || charCode === 10 || charCode === 13;
|
||||
}
|
||||
function processCommentOrDoctype(htmlChunk, position) {
|
||||
let i = position;
|
||||
const chunkLength = htmlChunk.length;
|
||||
if (i + 3 < chunkLength && htmlChunk.charCodeAt(i + 2) === DASH_CHAR && htmlChunk.charCodeAt(i + 3) === DASH_CHAR) {
|
||||
i += 4;
|
||||
while (i < chunkLength - 2) {
|
||||
if (htmlChunk.charCodeAt(i) === DASH_CHAR && htmlChunk.charCodeAt(i + 1) === DASH_CHAR && htmlChunk.charCodeAt(i + 2) === GT_CHAR) {
|
||||
i += 3;
|
||||
return {
|
||||
complete: true,
|
||||
newPosition: i,
|
||||
remainingText: ""
|
||||
};
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return {
|
||||
complete: false,
|
||||
newPosition: position,
|
||||
remainingText: htmlChunk.substring(position)
|
||||
};
|
||||
} else {
|
||||
i += 2;
|
||||
while (i < chunkLength) {
|
||||
if (htmlChunk.charCodeAt(i) === GT_CHAR) {
|
||||
i++;
|
||||
return {
|
||||
complete: true,
|
||||
newPosition: i,
|
||||
remainingText: ""
|
||||
};
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return {
|
||||
complete: false,
|
||||
newPosition: i,
|
||||
remainingText: htmlChunk.substring(position, i)
|
||||
};
|
||||
}
|
||||
}
|
||||
function parseAttributes(attrStr) {
|
||||
if (!attrStr)
|
||||
return {};
|
||||
const result = {};
|
||||
const setAttr = (attrName, value) => {
|
||||
if (!hasOwn(result, attrName))
|
||||
result[attrName] = value;
|
||||
};
|
||||
const len = attrStr.length;
|
||||
let i = 0;
|
||||
const WHITESPACE = 0;
|
||||
const NAME = 1;
|
||||
const AFTER_NAME = 2;
|
||||
const BEFORE_VALUE = 3;
|
||||
const QUOTED_VALUE = 4;
|
||||
const UNQUOTED_VALUE = 5;
|
||||
let state = WHITESPACE;
|
||||
let nameStart = 0;
|
||||
let nameEnd = 0;
|
||||
let valueStart = 0;
|
||||
let quoteChar = 0;
|
||||
let name = "";
|
||||
while (i < len) {
|
||||
const charCode = attrStr.charCodeAt(i);
|
||||
const isSpace = isWhitespace(charCode);
|
||||
switch (state) {
|
||||
case WHITESPACE:
|
||||
if (!isSpace) {
|
||||
state = NAME;
|
||||
nameStart = i;
|
||||
nameEnd = 0;
|
||||
}
|
||||
break;
|
||||
case NAME:
|
||||
if (charCode === EQUALS_CHAR || isSpace) {
|
||||
nameEnd = i;
|
||||
name = attrStr.substring(nameStart, nameEnd).toLowerCase();
|
||||
state = charCode === EQUALS_CHAR ? BEFORE_VALUE : AFTER_NAME;
|
||||
}
|
||||
break;
|
||||
case AFTER_NAME:
|
||||
if (charCode === EQUALS_CHAR) {
|
||||
state = BEFORE_VALUE;
|
||||
} else if (!isSpace) {
|
||||
setAttr(name, "");
|
||||
state = NAME;
|
||||
nameStart = i;
|
||||
nameEnd = 0;
|
||||
}
|
||||
break;
|
||||
case BEFORE_VALUE:
|
||||
if (charCode === QUOTE_CHAR || charCode === APOS_CHAR) {
|
||||
quoteChar = charCode;
|
||||
state = QUOTED_VALUE;
|
||||
valueStart = i + 1;
|
||||
} else if (!isSpace) {
|
||||
state = UNQUOTED_VALUE;
|
||||
valueStart = i;
|
||||
}
|
||||
break;
|
||||
case QUOTED_VALUE:
|
||||
if (charCode === quoteChar) {
|
||||
setAttr(name, attrStr.substring(valueStart, i));
|
||||
state = WHITESPACE;
|
||||
}
|
||||
break;
|
||||
case UNQUOTED_VALUE:
|
||||
if (isSpace || charCode === GT_CHAR) {
|
||||
setAttr(name, attrStr.substring(valueStart, i));
|
||||
state = WHITESPACE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (state === QUOTED_VALUE || state === UNQUOTED_VALUE) {
|
||||
if (name) {
|
||||
setAttr(name, attrStr.substring(valueStart, i));
|
||||
}
|
||||
} else if (state === NAME || state === AFTER_NAME || state === BEFORE_VALUE) {
|
||||
nameEnd = nameEnd || i;
|
||||
const currentName = attrStr.substring(nameStart, nameEnd).toLowerCase();
|
||||
if (currentName) {
|
||||
setAttr(currentName, "");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function parseHtmlForIndexes(html) {
|
||||
const indexes = {
|
||||
htmlTagStart: html.indexOf("<html"),
|
||||
htmlTagEnd: -1,
|
||||
headTagEnd: html.indexOf("</head>"),
|
||||
bodyTagStart: html.indexOf("<body"),
|
||||
bodyTagEnd: -1,
|
||||
bodyCloseTagStart: html.indexOf("</body>")
|
||||
};
|
||||
if (indexes.htmlTagStart >= 0) {
|
||||
const htmlTagEndPos = html.indexOf(">", indexes.htmlTagStart);
|
||||
if (htmlTagEndPos >= 0) {
|
||||
indexes.htmlTagEnd = htmlTagEndPos + 1;
|
||||
}
|
||||
}
|
||||
if (indexes.bodyTagStart >= 0) {
|
||||
const bodyTagEndPos = html.indexOf(">", indexes.bodyTagStart);
|
||||
if (bodyTagEndPos >= 0) {
|
||||
indexes.bodyTagEnd = bodyTagEndPos + 1;
|
||||
}
|
||||
}
|
||||
return { html, input: {}, indexes };
|
||||
}
|
||||
function parseHtmlForUnheadExtraction(html) {
|
||||
const input = {};
|
||||
const htmlParts = [];
|
||||
let position = 0;
|
||||
let inHead = false;
|
||||
let foundBodyStart = false;
|
||||
let lastCopyPosition = 0;
|
||||
let currentOutputLength = 0;
|
||||
const indexes = {
|
||||
htmlTagStart: -1,
|
||||
htmlTagEnd: -1,
|
||||
headTagEnd: -1,
|
||||
bodyTagStart: -1,
|
||||
bodyTagEnd: -1,
|
||||
bodyCloseTagStart: -1
|
||||
};
|
||||
function copyAccumulatedText() {
|
||||
if (lastCopyPosition < position) {
|
||||
const textToAdd = html.substring(lastCopyPosition, position);
|
||||
htmlParts.push(textToAdd);
|
||||
currentOutputLength += textToAdd.length;
|
||||
lastCopyPosition = position;
|
||||
}
|
||||
}
|
||||
function addText(text) {
|
||||
htmlParts.push(text);
|
||||
currentOutputLength += text.length;
|
||||
}
|
||||
while (position < html.length && !foundBodyStart) {
|
||||
const currentCharCode = html.charCodeAt(position);
|
||||
if (currentCharCode !== LT_CHAR) {
|
||||
position++;
|
||||
continue;
|
||||
}
|
||||
if (position + 1 >= html.length) {
|
||||
copyAccumulatedText();
|
||||
addText(html[position]);
|
||||
break;
|
||||
}
|
||||
const nextCharCode = html.charCodeAt(position + 1);
|
||||
if (nextCharCode === EXCLAMATION_CHAR) {
|
||||
const result = processCommentOrDoctype(html, position);
|
||||
if (result.complete) {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position, result.newPosition));
|
||||
position = result.newPosition;
|
||||
lastCopyPosition = position;
|
||||
} else {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (nextCharCode === SLASH_CHAR) {
|
||||
let tagEnd2 = position + 2;
|
||||
while (tagEnd2 < html.length && html.charCodeAt(tagEnd2) !== GT_CHAR) {
|
||||
tagEnd2++;
|
||||
}
|
||||
if (tagEnd2 >= html.length) {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position));
|
||||
break;
|
||||
}
|
||||
const tagName2 = html.substring(position + 2, tagEnd2).toLowerCase().trim();
|
||||
const tagId2 = TagIdMap[tagName2] ?? -1;
|
||||
if (tagId2 === TAG_HEAD) {
|
||||
inHead = false;
|
||||
copyAccumulatedText();
|
||||
const headCloseStart = currentOutputLength;
|
||||
addText(html.substring(position, tagEnd2 + 1));
|
||||
indexes.headTagEnd = headCloseStart;
|
||||
} else {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position, tagEnd2 + 1));
|
||||
}
|
||||
position = tagEnd2 + 1;
|
||||
lastCopyPosition = position;
|
||||
continue;
|
||||
}
|
||||
const tagStart = position + 1;
|
||||
let tagNameEnd = tagStart;
|
||||
while (tagNameEnd < html.length) {
|
||||
const c = html.charCodeAt(tagNameEnd);
|
||||
if (isWhitespace(c) || c === SLASH_CHAR || c === GT_CHAR) {
|
||||
break;
|
||||
}
|
||||
tagNameEnd++;
|
||||
}
|
||||
if (tagNameEnd >= html.length) {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position));
|
||||
break;
|
||||
}
|
||||
const tagName = html.substring(tagStart, tagNameEnd).toLowerCase();
|
||||
const tagId = TagIdMap[tagName] ?? -1;
|
||||
let tagEnd = tagNameEnd;
|
||||
let inQuote = false;
|
||||
let quoteChar = 0;
|
||||
let foundEnd = false;
|
||||
let isSelfClosing = false;
|
||||
while (tagEnd < html.length && !foundEnd) {
|
||||
const c = html.charCodeAt(tagEnd);
|
||||
if (inQuote) {
|
||||
if (c === quoteChar) {
|
||||
inQuote = false;
|
||||
}
|
||||
} else if (c === QUOTE_CHAR || c === APOS_CHAR) {
|
||||
inQuote = true;
|
||||
quoteChar = c;
|
||||
} else if (c === SLASH_CHAR && tagEnd + 1 < html.length && html.charCodeAt(tagEnd + 1) === GT_CHAR) {
|
||||
isSelfClosing = true;
|
||||
tagEnd += 2;
|
||||
foundEnd = true;
|
||||
continue;
|
||||
} else if (c === GT_CHAR) {
|
||||
tagEnd++;
|
||||
foundEnd = true;
|
||||
continue;
|
||||
}
|
||||
tagEnd++;
|
||||
}
|
||||
if (!foundEnd) {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position));
|
||||
break;
|
||||
}
|
||||
const attributesStr = html.substring(tagNameEnd, tagEnd - (isSelfClosing ? 2 : 1)).trim();
|
||||
const attributes = parseAttributes(attributesStr);
|
||||
if (tagId === TAG_HTML) {
|
||||
copyAccumulatedText();
|
||||
indexes.htmlTagStart = currentOutputLength;
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
input.htmlAttrs = attributes;
|
||||
addText(`<${tagName}>`);
|
||||
} else {
|
||||
addText(html.substring(position, tagEnd));
|
||||
}
|
||||
indexes.htmlTagEnd = currentOutputLength;
|
||||
} else if (tagId === TAG_BODY) {
|
||||
copyAccumulatedText();
|
||||
indexes.bodyTagStart = currentOutputLength;
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
input.bodyAttrs = attributes;
|
||||
addText(`<${tagName}>`);
|
||||
} else {
|
||||
addText(html.substring(position, tagEnd));
|
||||
}
|
||||
indexes.bodyTagEnd = currentOutputLength;
|
||||
foundBodyStart = true;
|
||||
position = tagEnd;
|
||||
lastCopyPosition = position;
|
||||
break;
|
||||
} else if (tagId === TAG_HEAD) {
|
||||
inHead = true;
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position, tagEnd));
|
||||
} else if (inHead && HEAD_ELEMENTS_TO_EXTRACT.has(tagId)) {
|
||||
if (tagId === TAG_TITLE) {
|
||||
if (!isSelfClosing) {
|
||||
const titleEnd = findClosingTag(html, tagEnd, tagName);
|
||||
if (titleEnd !== -1) {
|
||||
const titleContent = html.substring(tagEnd, titleEnd).trim();
|
||||
if (titleContent && !input.title) {
|
||||
input.title = titleContent;
|
||||
}
|
||||
position = findTagEnd(html, titleEnd, tagName);
|
||||
lastCopyPosition = position;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (tagId === TAG_SCRIPT) {
|
||||
const scriptAttrs = { ...attributes };
|
||||
if (!isSelfClosing) {
|
||||
const scriptEnd = findClosingTag(html, tagEnd, tagName);
|
||||
if (scriptEnd !== -1) {
|
||||
const scriptContent = html.substring(tagEnd, scriptEnd);
|
||||
scriptAttrs.innerHTML = scriptContent || "";
|
||||
position = findTagEnd(html, scriptEnd, tagName);
|
||||
} else {
|
||||
scriptAttrs.innerHTML = "";
|
||||
position = tagEnd;
|
||||
}
|
||||
} else {
|
||||
scriptAttrs.innerHTML = "";
|
||||
position = tagEnd;
|
||||
}
|
||||
lastCopyPosition = position;
|
||||
(input.script ||= []).push(scriptAttrs);
|
||||
continue;
|
||||
} else if (tagId === TAG_STYLE) {
|
||||
const styleAttrs = { ...attributes };
|
||||
if (!isSelfClosing) {
|
||||
const styleEnd = findClosingTag(html, tagEnd, tagName);
|
||||
if (styleEnd !== -1) {
|
||||
const styleContent = html.substring(tagEnd, styleEnd);
|
||||
styleAttrs.textContent = styleContent || "";
|
||||
position = findTagEnd(html, styleEnd, tagName);
|
||||
} else {
|
||||
styleAttrs.textContent = "";
|
||||
position = tagEnd;
|
||||
}
|
||||
} else {
|
||||
styleAttrs.textContent = "";
|
||||
position = tagEnd;
|
||||
}
|
||||
lastCopyPosition = position;
|
||||
(input.style ||= []).push(styleAttrs);
|
||||
continue;
|
||||
} else if (tagId === TAG_META) {
|
||||
(input.meta ||= []).push(attributes);
|
||||
position = tagEnd;
|
||||
lastCopyPosition = position;
|
||||
continue;
|
||||
} else if (tagId === TAG_LINK) {
|
||||
(input.link ||= []).push(attributes);
|
||||
position = tagEnd;
|
||||
lastCopyPosition = position;
|
||||
continue;
|
||||
} else if (tagId === TAG_BASE && !input.base) {
|
||||
input.base = attributes;
|
||||
position = tagEnd;
|
||||
lastCopyPosition = position;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
copyAccumulatedText();
|
||||
addText(html.substring(position, tagEnd));
|
||||
}
|
||||
position = tagEnd;
|
||||
lastCopyPosition = position;
|
||||
}
|
||||
const remainingHtml = html.substring(position);
|
||||
const bodyCloseIndex = remainingHtml.indexOf("</body>");
|
||||
if (bodyCloseIndex !== -1) {
|
||||
indexes.bodyCloseTagStart = currentOutputLength + bodyCloseIndex;
|
||||
}
|
||||
copyAccumulatedText();
|
||||
addText(remainingHtml);
|
||||
return { html: htmlParts.join(""), input, indexes };
|
||||
}
|
||||
function findClosingTag(html, startPos, tagName) {
|
||||
const tagId = TagIdMap[tagName];
|
||||
const isScriptOrStyle = tagId === TAG_SCRIPT || tagId === TAG_STYLE;
|
||||
if (!isScriptOrStyle) {
|
||||
const closingTag2 = `</${tagName}`;
|
||||
const index = html.indexOf(closingTag2, startPos);
|
||||
return index === -1 ? -1 : index;
|
||||
}
|
||||
const closingTag = `</${tagName}`;
|
||||
let pos = startPos;
|
||||
let inSingleQuote = false;
|
||||
let inDoubleQuote = false;
|
||||
let inBacktick = false;
|
||||
let lastCharWasBackslash = false;
|
||||
while (pos < html.length) {
|
||||
const currentCharCode = html.charCodeAt(pos);
|
||||
if (!lastCharWasBackslash) {
|
||||
if (currentCharCode === APOS_CHAR && !inDoubleQuote && !inBacktick) {
|
||||
inSingleQuote = !inSingleQuote;
|
||||
} else if (currentCharCode === QUOTE_CHAR && !inSingleQuote && !inBacktick) {
|
||||
inDoubleQuote = !inDoubleQuote;
|
||||
} else if (currentCharCode === 96 && !inSingleQuote && !inDoubleQuote) {
|
||||
inBacktick = !inBacktick;
|
||||
}
|
||||
}
|
||||
lastCharWasBackslash = currentCharCode === BACKSLASH_CHAR && !lastCharWasBackslash;
|
||||
const inQuotes = inSingleQuote || inDoubleQuote || inBacktick;
|
||||
if (!inQuotes && html.startsWith(closingTag, pos)) {
|
||||
const afterTagPos = pos + closingTag.length;
|
||||
if (afterTagPos >= html.length) {
|
||||
return pos;
|
||||
}
|
||||
const nextChar = html.charCodeAt(afterTagPos);
|
||||
if (nextChar === GT_CHAR || isWhitespace(nextChar)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function findTagEnd(html, closingTagStart, tagName) {
|
||||
let pos = closingTagStart + tagName.length + 2;
|
||||
while (pos < html.length && html.charCodeAt(pos) !== GT_CHAR) {
|
||||
pos++;
|
||||
}
|
||||
return pos < html.length ? pos + 1 : pos;
|
||||
}
|
||||
function applyHeadToHtml(template, headHtml) {
|
||||
const { html, indexes } = template;
|
||||
const parts = [];
|
||||
let lastIndex = 0;
|
||||
if (indexes.htmlTagStart >= 0) {
|
||||
parts.push(html.substring(lastIndex, indexes.htmlTagStart));
|
||||
parts.push(`<html${headHtml.htmlAttrs}>`);
|
||||
lastIndex = indexes.htmlTagEnd;
|
||||
}
|
||||
if (indexes.headTagEnd >= 0) {
|
||||
parts.push(html.substring(lastIndex, indexes.headTagEnd));
|
||||
parts.push(headHtml.headTags);
|
||||
parts.push("</head>");
|
||||
lastIndex = indexes.headTagEnd + 7;
|
||||
}
|
||||
if (indexes.bodyTagStart >= 0) {
|
||||
parts.push(html.substring(lastIndex, indexes.bodyTagStart));
|
||||
if (headHtml.bodyTagsOpen) {
|
||||
parts.push(`<body${headHtml.bodyAttrs}>
|
||||
${headHtml.bodyTagsOpen}`);
|
||||
} else {
|
||||
parts.push(`<body${headHtml.bodyAttrs}>`);
|
||||
}
|
||||
lastIndex = indexes.bodyTagEnd;
|
||||
}
|
||||
if (indexes.bodyCloseTagStart >= 0) {
|
||||
parts.push(html.substring(lastIndex, indexes.bodyCloseTagStart));
|
||||
parts.push(headHtml.bodyTags);
|
||||
parts.push(html.substring(indexes.bodyCloseTagStart));
|
||||
} else {
|
||||
parts.push(html.substring(lastIndex));
|
||||
}
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
export { TagIdMap, applyHeadToHtml, parseAttributes, parseHtmlForIndexes, parseHtmlForUnheadExtraction, prepareTemplate };
|
||||
+746
@@ -0,0 +1,746 @@
|
||||
export { A as AliasSortingPlugin, D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin } from './shared/unhead.tQnRaXfX.mjs';
|
||||
import { d as defineHeadPlugin } from './shared/unhead.CHEy9ana.mjs';
|
||||
export { F as FlatMetaPlugin, S as SafeInputPlugin } from './shared/unhead.jEy9BmRq.mjs';
|
||||
import { h as hasContent } from './shared/unhead.CGPOfp5O.mjs';
|
||||
import { minifyJS, minifyCSS, minifyJSON } from './minify.mjs';
|
||||
import { u as unpackMeta } from './shared/unhead.BQWSw36o.mjs';
|
||||
import { t as tagInputFromRuntime, u as tagPredicates, i as inputShapeFromRuntime, b as inputShapePredicates, U as URL_META_KEYS, v as titleInputFromRuntime, h as headInputPredicates } from './shared/unhead.BEmVMkeA.mjs';
|
||||
import './shared/unhead.BGFxPGPQ.mjs';
|
||||
|
||||
const HTTP_SCHEME_RE = /^https?:\/\//i;
|
||||
const META_TRANSFORMABLE_URL = [
|
||||
"og:url",
|
||||
"og:image",
|
||||
"og:image:url",
|
||||
"og:image:secure_url",
|
||||
"twitter:image",
|
||||
"twitter:image:src",
|
||||
"og:video",
|
||||
"og:video:url",
|
||||
"og:video:secure_url",
|
||||
"og:audio",
|
||||
"og:audio:url",
|
||||
"og:audio:secure_url",
|
||||
"twitter:player",
|
||||
"twitter:player:stream"
|
||||
];
|
||||
const LINK_REL_RESOLVABLE = /* @__PURE__ */ new Set([
|
||||
"canonical",
|
||||
"next",
|
||||
"prev",
|
||||
"alternate",
|
||||
"author",
|
||||
"license",
|
||||
"help",
|
||||
"search",
|
||||
"pingback"
|
||||
]);
|
||||
const META_CANONICAL_URL = /* @__PURE__ */ new Set([
|
||||
"og:url"
|
||||
]);
|
||||
function CanonicalPlugin(options) {
|
||||
return (head) => {
|
||||
let host = options.canonicalHost || (!head.ssr ? window.location.origin : "");
|
||||
if (!HTTP_SCHEME_RE.test(host) && !host.startsWith("//")) {
|
||||
host = `https://${host}`;
|
||||
}
|
||||
host = new URL(host).origin;
|
||||
const whitelist = options.queryWhitelist !== void 0 ? options.queryWhitelist : [];
|
||||
function normalizeCanonicalUrl(url) {
|
||||
try {
|
||||
const parsed = new URL(url, host);
|
||||
parsed.hash = "";
|
||||
if (whitelist !== false && parsed.search) {
|
||||
const filtered = new URLSearchParams();
|
||||
for (const key of whitelist) {
|
||||
if (parsed.searchParams.has(key)) {
|
||||
for (const value of parsed.searchParams.getAll(key)) {
|
||||
filtered.append(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
parsed.search = filtered.toString();
|
||||
}
|
||||
if (options.trailingSlash === true && !parsed.pathname.endsWith("/")) {
|
||||
parsed.pathname = `${parsed.pathname}/`;
|
||||
} else if (options.trailingSlash === false && parsed.pathname !== "/" && parsed.pathname.endsWith("/")) {
|
||||
parsed.pathname = parsed.pathname.slice(0, -1);
|
||||
}
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
function resolvePath(path) {
|
||||
if (options?.customResolver) {
|
||||
return options.customResolver(path);
|
||||
}
|
||||
if (HTTP_SCHEME_RE.test(path) || path.startsWith("//"))
|
||||
return path;
|
||||
try {
|
||||
return new URL(path, host).toString();
|
||||
} catch {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return {
|
||||
key: "canonical",
|
||||
hooks: {
|
||||
"tags:resolve": (ctx) => {
|
||||
for (const tag of ctx.tags) {
|
||||
const metaKey = tag.props?.property || tag.props?.name;
|
||||
if (tag.tag === "meta" && META_TRANSFORMABLE_URL.includes(metaKey)) {
|
||||
tag.props.content = resolvePath(tag.props.content);
|
||||
if (META_CANONICAL_URL.has(metaKey)) {
|
||||
tag.props.content = normalizeCanonicalUrl(tag.props.content);
|
||||
}
|
||||
} else if (tag.tag === "link" && LINK_REL_RESOLVABLE.has(tag.props.rel)) {
|
||||
const isCanonical = tag.props.rel === "canonical";
|
||||
tag.props.href = resolvePath(tag.props.href);
|
||||
if (isCanonical) {
|
||||
tag.props.href = normalizeCanonicalUrl(tag.props.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function InferSeoMetaPlugin(options = {}) {
|
||||
return defineHeadPlugin((head) => {
|
||||
if (options.twitterCard !== false) {
|
||||
head.push({
|
||||
meta: [
|
||||
{
|
||||
name: "twitter:card",
|
||||
content: options.twitterCard || "summary_large_image",
|
||||
tagPriority: "low"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
head.push({
|
||||
meta: [
|
||||
// content is intentionally omitted - inferred from title/description by the hook below
|
||||
{
|
||||
"property": "og:title",
|
||||
"tagPriority": "low",
|
||||
"data-infer": ""
|
||||
},
|
||||
{
|
||||
"property": "og:description",
|
||||
"tagPriority": "low",
|
||||
"data-infer": ""
|
||||
}
|
||||
]
|
||||
});
|
||||
return {
|
||||
key: "infer-seo-meta",
|
||||
hooks: {
|
||||
"tags:beforeResolve": ({ tagMap }) => {
|
||||
let title = head._titleTemplate || head._title;
|
||||
const ogTitle = tagMap.get("meta:og:title");
|
||||
if (typeof ogTitle?.props["data-infer"] !== "undefined") {
|
||||
if (typeof title === "function") {
|
||||
title = title(head._title);
|
||||
}
|
||||
title = hasContent(title) ? String(title) : void 0;
|
||||
ogTitle.props.content = options.ogTitle ? options.ogTitle(title) : title || "";
|
||||
ogTitle.processTemplateParams = true;
|
||||
}
|
||||
const descriptionValue = tagMap.get("meta:description")?.props?.content;
|
||||
const description = hasContent(descriptionValue) ? String(descriptionValue) : void 0;
|
||||
const ogDescription = tagMap.get("meta:og:description");
|
||||
if (typeof ogDescription?.props["data-infer"] !== "undefined") {
|
||||
ogDescription.props.content = options.ogDescription ? options.ogDescription(description) : description || "";
|
||||
ogDescription.processTemplateParams = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}, "infer-seo-meta");
|
||||
}
|
||||
|
||||
const JSON_TYPES = /* @__PURE__ */ new Set(["application/json", "application/ld+json", "speculationrules", "importmap"]);
|
||||
function MinifyPlugin(options) {
|
||||
const jsMinify = options?.js === false ? false : options?.js || minifyJS;
|
||||
const cssMinify = options?.css === false ? false : options?.css || minifyCSS;
|
||||
const jsonMinify = options?.json !== false;
|
||||
const omitLineBreaks = options?.omitLineBreaks === true;
|
||||
return {
|
||||
key: "minify",
|
||||
hooks: {
|
||||
"ssr:render": (ctx) => {
|
||||
if (omitLineBreaks)
|
||||
ctx.options.omitLineBreaks = true;
|
||||
for (const tag of ctx.tags) {
|
||||
const content = tag.innerHTML;
|
||||
if (!content || content.length < 20)
|
||||
continue;
|
||||
if (tag.tag === "script") {
|
||||
const type = tag.props?.type;
|
||||
if (type && JSON_TYPES.has(type)) {
|
||||
if (jsonMinify) {
|
||||
const min = minifyJSON(content);
|
||||
if (min.length < content.length)
|
||||
tag.innerHTML = min;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (jsMinify) {
|
||||
const min = jsMinify(content);
|
||||
if (min.length < content.length)
|
||||
tag.innerHTML = min;
|
||||
}
|
||||
} else if (tag.tag === "style" && cssMinify) {
|
||||
const min = cssMinify(content);
|
||||
if (min.length < content.length)
|
||||
tag.innerHTML = min;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const TEMPLATE_PARAM_RE = /%\w+(?:\.\w+)?%/;
|
||||
const AT_PREFIX_RE = /^at\s+/;
|
||||
const JAVASCRIPT_TYPES = /* @__PURE__ */ new Set([
|
||||
"application/ecmascript",
|
||||
"application/javascript",
|
||||
"application/x-ecmascript",
|
||||
"application/x-javascript",
|
||||
"text/ecmascript",
|
||||
"text/javascript",
|
||||
"text/javascript1.0",
|
||||
"text/javascript1.1",
|
||||
"text/javascript1.2",
|
||||
"text/javascript1.3",
|
||||
"text/javascript1.4",
|
||||
"text/javascript1.5",
|
||||
"text/jscript",
|
||||
"text/livescript",
|
||||
"text/x-ecmascript",
|
||||
"text/x-javascript"
|
||||
]);
|
||||
const SLACK_TWITTER_META_NAMES = /* @__PURE__ */ new Set([
|
||||
"twitter:data1",
|
||||
"twitter:data2",
|
||||
"twitter:label1",
|
||||
"twitter:label2"
|
||||
]);
|
||||
function isExecutableScript(type) {
|
||||
const value = typeof type === "boolean" ? "" : String(type ?? "");
|
||||
if (!value)
|
||||
return true;
|
||||
const normalized = value.replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "").toLowerCase();
|
||||
return normalized === "module" || JAVASCRIPT_TYPES.has(normalized);
|
||||
}
|
||||
const PREDICATE_SEVERITY = {
|
||||
"defer-on-module-script": "info",
|
||||
"deprecated-prop-body": "warn",
|
||||
"deprecated-prop-children": "warn",
|
||||
"deprecated-prop-hid-vmid": "warn",
|
||||
"empty-meta-content": "warn",
|
||||
"html-in-title": "warn",
|
||||
"invalid-input-shape": "warn",
|
||||
"non-absolute-canonical": "warn",
|
||||
"numeric-tag-priority": "info",
|
||||
"possible-typo": "warn",
|
||||
"preload-font-crossorigin": "warn",
|
||||
"preload-missing-as": "warn",
|
||||
"robots-conflict": "warn",
|
||||
"script-src-with-content": "warn",
|
||||
"streamed-tag-hidden-from-bots": "warn",
|
||||
"twitter-handle-missing-at": "warn",
|
||||
"viewport-user-scalable": "info"
|
||||
};
|
||||
function isAbsoluteUrl(url) {
|
||||
return url.startsWith("http://") || url.startsWith("https://");
|
||||
}
|
||||
function extractOrigin(url) {
|
||||
if (!isAbsoluteUrl(url))
|
||||
return void 0;
|
||||
const slash = url.indexOf("/", url.indexOf("//") + 2);
|
||||
return slash === -1 ? url : url.slice(0, slash);
|
||||
}
|
||||
function resolveSeverity(config, fallback) {
|
||||
if (config == null)
|
||||
return fallback;
|
||||
return Array.isArray(config) ? config[0] : config;
|
||||
}
|
||||
function resolveOptions(config, id, defaults) {
|
||||
const entry = config[id];
|
||||
if (Array.isArray(entry))
|
||||
return { ...defaults, ...entry[1] };
|
||||
return defaults;
|
||||
}
|
||||
function captureSource(root) {
|
||||
const stack = new Error("source").stack;
|
||||
if (!stack)
|
||||
return void 0;
|
||||
const lines = stack.split("\n");
|
||||
for (let i = 4; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line && !line.includes("node_modules") && !line.includes("unhead/src/")) {
|
||||
let source = line.replace(AT_PREFIX_RE, "");
|
||||
if (root) {
|
||||
const prefix = root.endsWith("/") ? root : `${root}/`;
|
||||
source = source.replace(prefix, "./");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
function createInputShapeObserver() {
|
||||
let remainingRootKeys;
|
||||
let resolvedValues;
|
||||
const resolver = (key, value) => {
|
||||
if (key === void 0) {
|
||||
if (value?.constructor === Object) {
|
||||
remainingRootKeys = new Set(Object.keys(value));
|
||||
resolvedValues = /* @__PURE__ */ new Map();
|
||||
} else {
|
||||
remainingRootKeys = void 0;
|
||||
resolvedValues = void 0;
|
||||
}
|
||||
} else if (remainingRootKeys?.delete(key)) {
|
||||
resolvedValues?.set(key, value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
resolver._static = true;
|
||||
return {
|
||||
resolver,
|
||||
take() {
|
||||
const input = resolvedValues ? Object.fromEntries(resolvedValues) : void 0;
|
||||
remainingRootKeys = void 0;
|
||||
resolvedValues = void 0;
|
||||
return input;
|
||||
}
|
||||
};
|
||||
}
|
||||
const BOT_HEAD_META_NAMES = /* @__PURE__ */ new Set(["description", "robots", "googlebot", "bingbot", "slurp", "keywords"]);
|
||||
const BOT_HEAD_META_EQUIVS = /* @__PURE__ */ new Set(["refresh", "content-language"]);
|
||||
const BOT_HEAD_LINK_RELS = /* @__PURE__ */ new Set(["canonical", "alternate", "amphtml", "prev", "next", "author", "license"]);
|
||||
const BOT_HEAD_META_PREFIX_RE = /^(?:og|twitter|article|book|profile|fb|al|music|video|place|product):/;
|
||||
const JSON_LD_TYPE_RE = /^[\t\n\f\r ]*application\/ld\+json[\t\n\f\r ]*(?:;|$)/i;
|
||||
const REL_SEPARATOR_RE = /[\t\n\f\r ]+/;
|
||||
const SAFE_BLOCKED_RELS = /* @__PURE__ */ new Set(["canonical", "modulepreload", "prerender", "preload", "prefetch", "dns-prefetch", "preconnect", "manifest", "pingback"]);
|
||||
function relTokens(value) {
|
||||
return String(value || "").toLowerCase().split(REL_SEPARATOR_RE);
|
||||
}
|
||||
function* expandPendingTag(tag) {
|
||||
if (tag._safe && tag.tag === "link" && relTokens(tag.props.rel).some((rel) => SAFE_BLOCKED_RELS.has(rel)))
|
||||
return;
|
||||
if (tag.props.body)
|
||||
tag.tagPosition = "bodyClose";
|
||||
if (tag.tag !== "_flatMeta") {
|
||||
yield tag;
|
||||
return;
|
||||
}
|
||||
for (const props of unpackMeta(tag.props))
|
||||
yield { ...tag, tag: "meta", props };
|
||||
}
|
||||
function isHiddenFromBots(tag, writesBodyTags) {
|
||||
const props = tag.props;
|
||||
if (tag.tag === "script")
|
||||
return !writesBodyTags && JSON_LD_TYPE_RE.test(String(props.type || ""));
|
||||
if (tag.tagPosition?.startsWith("body"))
|
||||
return false;
|
||||
switch (tag.tag) {
|
||||
case "title":
|
||||
case "titleTemplate":
|
||||
case "base":
|
||||
return true;
|
||||
case "meta": {
|
||||
if (BOT_HEAD_META_NAMES.has(String(props.name || "").toLowerCase()))
|
||||
return true;
|
||||
if (BOT_HEAD_META_EQUIVS.has(String(props["http-equiv"] || "").toLowerCase()))
|
||||
return true;
|
||||
return BOT_HEAD_META_PREFIX_RE.test(String(props.property || props.name || "").toLowerCase());
|
||||
}
|
||||
case "link":
|
||||
return relTokens(props.rel).some((rel) => BOT_HEAD_LINK_RELS.has(rel));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function describeTag(tag) {
|
||||
const props = tag.props;
|
||||
if (tag.tag === "meta")
|
||||
return `meta[${props.property ? `property="${props.property}"` : props["http-equiv"] ? `http-equiv="${props["http-equiv"]}"` : `name="${props.name}"`}]`;
|
||||
if (tag.tag === "link")
|
||||
return `link[rel="${props.rel}"]`;
|
||||
if (tag.tag === "script")
|
||||
return 'script[type="application/ld+json"]';
|
||||
return tag.tag;
|
||||
}
|
||||
function ValidatePlugin(options = {}) {
|
||||
const ruleConfig = options.rules || {};
|
||||
const root = options.root;
|
||||
const only = options.only && new Set(options.only);
|
||||
const pluginKey = options.key || "validate";
|
||||
function severityFor(id, fallback) {
|
||||
if (only && !only.has(id))
|
||||
return "off";
|
||||
return resolveSeverity(ruleConfig[id], fallback);
|
||||
}
|
||||
return defineHeadPlugin((head) => {
|
||||
const stacks = /* @__PURE__ */ new Map();
|
||||
const pendingInputDiagnostics = [];
|
||||
const inputShapeObserver = createInputShapeObserver();
|
||||
head.resolvedOptions.propResolvers = [
|
||||
...head.resolvedOptions.propResolvers || [],
|
||||
inputShapeObserver.resolver
|
||||
];
|
||||
const hooks = head.hooks;
|
||||
if (hooks) {
|
||||
const _callHook = hooks.callHook.bind(hooks);
|
||||
let warnedAsyncHook = false;
|
||||
hooks.callHook = (name, ...args) => {
|
||||
const result = _callHook(name, ...args);
|
||||
if (result?.then && !warnedAsyncHook && !only) {
|
||||
warnedAsyncHook = true;
|
||||
console.warn(`[unhead] promise ignored: ${name}`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
const _push = head.push.bind(head);
|
||||
head.push = (input, opts) => {
|
||||
if (opts?.mode && severityFor("deprecated-option-mode", "warn") !== "off") {
|
||||
console.warn(`[unhead] "mode: '${opts.mode}'" option was removed in v3. Use the appropriate createHead import (unhead/client or unhead/server) instead.`);
|
||||
}
|
||||
const source = captureSource(root);
|
||||
const active = _push(input, opts);
|
||||
if (source)
|
||||
stacks.set(active._i, source);
|
||||
const _dispose = active.dispose;
|
||||
active.dispose = () => {
|
||||
stacks.delete(active._i);
|
||||
_dispose();
|
||||
};
|
||||
return active;
|
||||
};
|
||||
function dispatch(rules, mode) {
|
||||
const head_ = head;
|
||||
head_._validationRules = mode === "replace" ? rules : [...head_._validationRules || [], ...rules];
|
||||
if (!rules.length)
|
||||
return;
|
||||
if (options.onReport) {
|
||||
options.onReport(rules);
|
||||
return;
|
||||
}
|
||||
for (const rule of rules) {
|
||||
const loc = rule.source ? ` (${rule.source})` : "";
|
||||
console.warn(`[unhead] ${rule.message}${loc}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
key: pluginKey,
|
||||
hooks: {
|
||||
"ssr:streamChunk": ({ tags }) => {
|
||||
const severity = severityFor("streamed-tag-hidden-from-bots", "warn");
|
||||
if (severity === "off")
|
||||
return;
|
||||
const rules = [];
|
||||
for (const pending of tags) {
|
||||
for (const tag of expandPendingTag(pending)) {
|
||||
if (!isHiddenFromBots(tag, !!head._stream?.writesBodyTags))
|
||||
continue;
|
||||
const entryIndex = tag._p != null ? tag._p >> 10 : void 0;
|
||||
rules.push({
|
||||
id: "streamed-tag-hidden-from-bots",
|
||||
message: `Bots will not see ${describeTag(tag)}. It arrived after the shell, so it ships as a client patch. Register it before the shell.`,
|
||||
severity,
|
||||
source: entryIndex != null ? stacks.get(entryIndex) : void 0,
|
||||
tag
|
||||
});
|
||||
}
|
||||
}
|
||||
dispatch(rules, "append");
|
||||
},
|
||||
"entries:normalize": ({ entry }) => {
|
||||
const input = inputShapeObserver.take();
|
||||
if (!input || input.constructor !== Object)
|
||||
return;
|
||||
const shape = inputShapeFromRuntime("head", input);
|
||||
for (const predicate of Object.values(inputShapePredicates)) {
|
||||
for (const diagnostic of predicate(shape))
|
||||
pendingInputDiagnostics.push({ diagnostic, entryIndex: entry._i });
|
||||
}
|
||||
},
|
||||
"tags:afterResolve": ({ tags }) => {
|
||||
const rules = [];
|
||||
function report(id, message, defaultSeverity, tag, inputEntryIndex) {
|
||||
const severity = severityFor(id, defaultSeverity);
|
||||
if (severity === "off")
|
||||
return;
|
||||
const entryIndex = inputEntryIndex ?? (tag?._p != null ? tag._p >> 10 : void 0);
|
||||
const source = entryIndex != null ? stacks.get(entryIndex) : void 0;
|
||||
rules.push({ id, message, severity, source, tag });
|
||||
}
|
||||
for (const { diagnostic, entryIndex } of pendingInputDiagnostics) {
|
||||
const severity = PREDICATE_SEVERITY[diagnostic.ruleId] ?? "warn";
|
||||
report(diagnostic.ruleId, diagnostic.message, severity, void 0, entryIndex);
|
||||
}
|
||||
pendingInputDiagnostics.length = 0;
|
||||
const metaByKey = /* @__PURE__ */ new Map();
|
||||
let canonicalHref;
|
||||
let hasOgTags = false;
|
||||
let hasTitle = false;
|
||||
let hasDescription = false;
|
||||
let isIndexable = true;
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "title")
|
||||
hasTitle = true;
|
||||
if (tag.tag === "meta") {
|
||||
const key = tag.props.property || (tag.props.name ? String(tag.props.name).toLowerCase() : void 0);
|
||||
if (key) {
|
||||
metaByKey.set(key, tag);
|
||||
if (key.startsWith("og:"))
|
||||
hasOgTags = true;
|
||||
if (key === "description")
|
||||
hasDescription = true;
|
||||
if (key === "robots" && String(tag.props.content ?? "").toLowerCase().includes("noindex"))
|
||||
isIndexable = false;
|
||||
}
|
||||
}
|
||||
if (tag.tag === "link" && tag.props.rel === "canonical")
|
||||
canonicalHref = tag.props.href;
|
||||
}
|
||||
function emitFromPredicates(diagnostics, tag) {
|
||||
for (const diag of diagnostics) {
|
||||
const sev = PREDICATE_SEVERITY[diag.ruleId] ?? "warn";
|
||||
report(diag.ruleId, diag.message, sev, tag);
|
||||
}
|
||||
}
|
||||
for (const tag of tags) {
|
||||
const { props } = tag;
|
||||
const metaKey = props.property || (props.name ? String(props.name).toLowerCase() : void 0);
|
||||
const tagInput = tagInputFromRuntime(tag);
|
||||
if (tagInput) {
|
||||
for (const predicate of Object.values(tagPredicates))
|
||||
emitFromPredicates(predicate(tagInput), tag);
|
||||
}
|
||||
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
||||
const inputShape = inputShapeFromRuntime(tag.tag, tag.props);
|
||||
for (const predicate of Object.values(inputShapePredicates))
|
||||
emitFromPredicates(predicate(inputShape), tag);
|
||||
}
|
||||
if (tag.tag === "meta" && typeof metaKey === "string") {
|
||||
const normalizedMetaKey = metaKey.toLowerCase();
|
||||
if (normalizedMetaKey.startsWith("twitter:") && !SLACK_TWITTER_META_NAMES.has(normalizedMetaKey))
|
||||
report("deprecated-twitter-meta", `${normalizedMetaKey} is deprecated. Use Open Graph metadata instead.`, "warn", tag);
|
||||
}
|
||||
if (tag.tag === "meta" && metaKey && URL_META_KEYS.has(metaKey)) {
|
||||
const content = String(props.content ?? "");
|
||||
if (content && !isAbsoluteUrl(content))
|
||||
report("non-absolute-og-url", `${metaKey} should be an absolute URL, received "${content}".`, "warn", tag);
|
||||
}
|
||||
if (tag.tag === "meta" && metaKey) {
|
||||
const content = String(props.content ?? "");
|
||||
if (content && TEMPLATE_PARAM_RE.test(content))
|
||||
report("unresolved-template-param", `Unresolved template param in ${metaKey}: "${content}".`, "warn", tag);
|
||||
}
|
||||
if (tag.tag === "title") {
|
||||
const titleInput = titleInputFromRuntime(tag);
|
||||
if (titleInput) {
|
||||
for (const diag of headInputPredicates["no-html-in-title"](titleInput))
|
||||
report(diag.ruleId, diag.message, "warn", tag);
|
||||
}
|
||||
const text = String(tag.textContent ?? "");
|
||||
if (TEMPLATE_PARAM_RE.test(text))
|
||||
report("unresolved-template-param", `Unresolved template param in title: "${text}".`, "warn", tag);
|
||||
if (!text.trim())
|
||||
report("empty-title", `Title tag is empty. If using titleTemplate, ensure it produces output.`, "warn", tag);
|
||||
}
|
||||
if (tag.tag === "script" && props.src && !props.async && !props.defer && props.type !== "module" && (!tag.tagPosition || tag.tagPosition === "head"))
|
||||
report("render-blocking-script", `Script "${props.src}" is render-blocking. Add "async", "defer", or use type="module" to avoid blocking the critical rendering path.`, "warn", tag);
|
||||
if (tag.tag === "link" && props.rel === "preload" && props.fetchpriority === "low" && props.as !== "script")
|
||||
report("preload-fetchpriority-conflict", `This preload uses fetchpriority="low". Check whether the resource needs a higher priority.`, "off", tag);
|
||||
if (tag.tag === "style" && (tag.innerHTML || tag.textContent)) {
|
||||
const content = tag.innerHTML || tag.textContent || "";
|
||||
const sizeKB = new TextEncoder().encode(content).byteLength / 1024;
|
||||
const { maxKB: styleMaxKB } = resolveOptions(ruleConfig, "inline-style-size", { maxKB: 14 });
|
||||
if (sizeKB > styleMaxKB)
|
||||
report("inline-style-size", `Inline <style> is ${sizeKB.toFixed(1)}KB \u2014 exceeds ${styleMaxKB}KB critical CSS budget. Consider moving to an external stylesheet for cacheability.`, "info", tag);
|
||||
}
|
||||
if (tag.tag === "script" && isExecutableScript(props.type) && !props.src && (tag.innerHTML || tag.textContent)) {
|
||||
const content = tag.innerHTML || tag.textContent || "";
|
||||
const sizeKB = new TextEncoder().encode(content).byteLength / 1024;
|
||||
const { maxKB: scriptMaxKB } = resolveOptions(ruleConfig, "inline-script-size", { maxKB: 2 });
|
||||
if (sizeKB > scriptMaxKB)
|
||||
report("inline-script-size", `Inline <script> is ${sizeKB.toFixed(1)}KB \u2014 consider moving to an external file for cacheability.`, "info", tag);
|
||||
}
|
||||
}
|
||||
const ogUrl = metaByKey.get("og:url");
|
||||
if (canonicalHref && ogUrl?.props.content && canonicalHref !== ogUrl.props.content)
|
||||
report("canonical-og-url-mismatch", `Canonical URL "${canonicalHref}" differs from og:url "${ogUrl.props.content}".`, "warn", ogUrl);
|
||||
if (metaByKey.has("og:image") && (!metaByKey.has("og:image:width") || !metaByKey.has("og:image:height")))
|
||||
report("og-image-missing-dimensions", `og:image is set but og:image:width and/or og:image:height are missing \u2014 social platforms may not display the image.`, "warn", metaByKey.get("og:image"));
|
||||
if (hasOgTags) {
|
||||
if (!metaByKey.has("og:title"))
|
||||
report("og-missing-title", `Open Graph tags are present but og:title is missing.`, "warn");
|
||||
if (!metaByKey.has("og:description"))
|
||||
report("og-missing-description", `Open Graph tags are present but og:description is missing.`, "warn");
|
||||
}
|
||||
if (!hasTitle)
|
||||
report("missing-title", `Page is missing a <title> tag.`, "warn");
|
||||
if (!hasDescription && isIndexable)
|
||||
report("missing-description", `Page is missing a meta description and is indexable by search engines.`, "warn");
|
||||
const { max: maxPreloads } = resolveOptions(ruleConfig, "too-many-preloads", { max: 6 });
|
||||
const preloadCount = tags.filter((t) => t.tag === "link" && t.props.rel === "preload").length;
|
||||
if (preloadCount > maxPreloads)
|
||||
report("too-many-preloads", `Found ${preloadCount} preload links \u2014 more than ${maxPreloads} preloads compete for bandwidth and can hurt performance.`, "warn");
|
||||
const { max: maxPrefetches } = resolveOptions(ruleConfig, "too-many-prefetches", { max: 50 });
|
||||
const prefetchCount = tags.filter((t) => t.tag === "link" && t.props.rel === "prefetch").length;
|
||||
if (prefetchCount > maxPrefetches)
|
||||
report("too-many-prefetches", `Found ${prefetchCount} prefetch links. The configured maximum of ${maxPrefetches} is an advisory guardrail, not a standards limit. Large speculative fetch sets can consume bandwidth and cache capacity.`, "info");
|
||||
const { max: maxPreconnects } = resolveOptions(ruleConfig, "too-many-preconnects", { max: 4 });
|
||||
const preconnectCount = tags.filter((t) => t.tag === "link" && t.props.rel === "preconnect").length;
|
||||
if (preconnectCount > maxPreconnects)
|
||||
report("too-many-preconnects", `Found ${preconnectCount} preconnect links \u2014 each initiates a TCP+TLS handshake, more than ${maxPreconnects} compete for limited connections.`, "warn");
|
||||
const preconnectOrigins = /* @__PURE__ */ new Set();
|
||||
const dnsPrefetchTags = [];
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.href) {
|
||||
if (tag.props.rel === "preconnect")
|
||||
preconnectOrigins.add(tag.props.href);
|
||||
else if (tag.props.rel === "dns-prefetch")
|
||||
dnsPrefetchTags.push(tag);
|
||||
}
|
||||
}
|
||||
for (const tag of dnsPrefetchTags) {
|
||||
if (preconnectOrigins.has(tag.props.href))
|
||||
report("redundant-dns-prefetch", `dns-prefetch for "${tag.props.href}" is redundant \u2014 preconnect already includes DNS resolution.`, "info", tag);
|
||||
}
|
||||
const preloadScriptHrefs = /* @__PURE__ */ new Map();
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.as === "script" && tag.props.href && tag.props.fetchpriority !== "low")
|
||||
preloadScriptHrefs.set(tag.props.href, tag);
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "script" && tag.props.src && (tag.props.async || tag.props.defer)) {
|
||||
const preloadTag = preloadScriptHrefs.get(tag.props.src);
|
||||
if (preloadTag) {
|
||||
const attr = tag.props.async ? "async" : "defer";
|
||||
report("preload-async-defer-conflict", `Script "${tag.props.src}" is preloaded with "${attr}". Check whether early fetching is intentional.`, "off", preloadTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
const preloadHrefs = /* @__PURE__ */ new Set();
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.href)
|
||||
preloadHrefs.add(tag.props.href);
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.rel === "prefetch" && tag.props.href && preloadHrefs.has(tag.props.href))
|
||||
report("prefetch-preload-conflict", `"${tag.props.href}" has both preload and prefetch \u2014 use preload for current page resources, prefetch for future navigation.`, "warn", tag);
|
||||
}
|
||||
if (!head.plugins.has("template-params")) {
|
||||
const tpTag = tags.find((t) => t.tag === "templateParams");
|
||||
if (tpTag)
|
||||
report("missing-template-params-plugin", `templateParams are set but TemplateParamsPlugin is not registered. In v3, this plugin is opt-in. Add it to createHead({ plugins: [TemplateParamsPlugin] }).`, "warn", tpTag);
|
||||
}
|
||||
if (!head.plugins.has("aliasSorting")) {
|
||||
for (const tag of tags) {
|
||||
const p = typeof tag.tagPriority === "string" ? tag.tagPriority : "";
|
||||
if (p.startsWith("before:") || p.startsWith("after:")) {
|
||||
report("missing-alias-sorting-plugin", `Tag priority alias "${p}" requires AliasSortingPlugin. In v3, this plugin is opt-in. Add it to createHead({ plugins: [AliasSortingPlugin] }).`, "warn", tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const { max: maxHighPriority } = resolveOptions(ruleConfig, "too-many-fetchpriority-high", { max: 2 });
|
||||
const highPriorityCount = tags.filter((t) => t.props.fetchpriority === "high").length;
|
||||
if (highPriorityCount > maxHighPriority)
|
||||
report("too-many-fetchpriority-high", `Found ${highPriorityCount} resources with fetchpriority="high". When everything is high priority, nothing is. Limit to ${maxHighPriority} for the signal to be effective.`, "warn");
|
||||
const resourceHintsSeen = /* @__PURE__ */ new Map();
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.href && (tag.props.rel === "preload" || tag.props.rel === "prefetch" || tag.props.rel === "preconnect")) {
|
||||
const crossoriginSuffix = tag.props.rel === "preconnect" && "crossorigin" in tag.props ? ":cors" : "";
|
||||
const key = `${tag.props.rel}:${tag.props.href}${crossoriginSuffix}`;
|
||||
if (resourceHintsSeen.has(key))
|
||||
report("duplicate-resource-hint", `Duplicate ${tag.props.rel} for "${tag.props.href}".`, "warn", tag);
|
||||
else
|
||||
resourceHintsSeen.set(key, tag);
|
||||
}
|
||||
}
|
||||
if (head.ssr) {
|
||||
const { maxPosition: charsetMaxPos } = resolveOptions(ruleConfig, "charset-not-early", { maxPosition: 3 });
|
||||
const headElementTags = /* @__PURE__ */ new Set(["title", "base", "meta", "link", "style", "script", "noscript"]);
|
||||
const sortedHeadTags = tags.filter((t) => headElementTags.has(t.tag) && (!t.tagPosition || t.tagPosition === "head")).sort((a, b) => (a._w ?? 100) === (b._w ?? 100) ? (a._p ?? 0) - (b._p ?? 0) : (a._w ?? 100) - (b._w ?? 100));
|
||||
let charsetTag;
|
||||
let charsetPosition = -1;
|
||||
for (let i = 0; i < sortedHeadTags.length; i++) {
|
||||
const tag = sortedHeadTags[i];
|
||||
if (tag.tag === "meta" && ("charset" in tag.props || tag.props["http-equiv"]?.toLowerCase() === "content-type")) {
|
||||
charsetTag = tag;
|
||||
charsetPosition = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (charsetTag && charsetPosition > charsetMaxPos)
|
||||
report("charset-not-early", `<meta charset> is at position ${charsetPosition} in <head>. It should be within the first ${charsetMaxPos} tags so the browser doesn't need to re-parse.`, "warn", charsetTag);
|
||||
}
|
||||
const moduleScriptSrcs = /* @__PURE__ */ new Set();
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "script" && tag.props.type === "module" && tag.props.src)
|
||||
moduleScriptSrcs.add(tag.props.src);
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.rel === "preload" && tag.props.as === "script" && tag.props.href && moduleScriptSrcs.has(tag.props.href))
|
||||
report("preload-not-modulepreload", `"${tag.props.href}" is a module script but uses rel="preload". Use rel="modulepreload" instead to also trigger module parsing.`, "warn", tag);
|
||||
}
|
||||
const corsOrigins = /* @__PURE__ */ new Set();
|
||||
const preconnectCorsOrigins = /* @__PURE__ */ new Set();
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.href && "crossorigin" in tag.props) {
|
||||
const origin = extractOrigin(tag.props.href);
|
||||
if (origin) {
|
||||
corsOrigins.add(origin);
|
||||
if (tag.props.rel === "preconnect")
|
||||
preconnectCorsOrigins.add(origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "link" && tag.props.rel === "preconnect" && tag.props.href && !("crossorigin" in tag.props)) {
|
||||
const origin = extractOrigin(tag.props.href);
|
||||
if (origin && corsOrigins.has(origin) && !preconnectCorsOrigins.has(origin))
|
||||
report("preconnect-missing-crossorigin", `Preconnect to "${tag.props.href}" is missing "crossorigin" but CORS resources are loaded from this origin. Without it, the browser opens a separate connection for CORS requests.`, "warn", tag);
|
||||
}
|
||||
}
|
||||
const { maxBytes: crawlerMaxBytes } = resolveOptions(ruleConfig, "meta-beyond-1mb", { maxBytes: 1048576 });
|
||||
let byteOffset = 0;
|
||||
for (const tag of tags) {
|
||||
if (tag.tagPosition && tag.tagPosition !== "head")
|
||||
continue;
|
||||
const props = Object.entries(tag.props).filter(([, v]) => v !== false && v != null).map(([k, v]) => v === true || v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
|
||||
const content = tag.innerHTML || tag.textContent || "";
|
||||
const tagSize = `<${tag.tag}${props}>${content}</${tag.tag}>
|
||||
`.length;
|
||||
byteOffset += tagSize;
|
||||
if (byteOffset > crawlerMaxBytes && tag.tag === "meta") {
|
||||
const key = tag.props.property || tag.props.name || "unknown";
|
||||
report(
|
||||
"meta-beyond-1mb",
|
||||
`Meta tag "${key}" is rendered ~${(byteOffset / 1024).toFixed(0)}KB into <head>, beyond the ${(crawlerMaxBytes / 1048576).toFixed(0)}MB crawler parsing limit. Social crawlers (Facebook, Twitter) may not see it. Use \`tagPriority\` to promote it, or configure a custom \`tagWeight\` to reorder tags for bot requests.`,
|
||||
"warn",
|
||||
tag
|
||||
);
|
||||
}
|
||||
}
|
||||
dispatch(rules, "replace");
|
||||
}
|
||||
}
|
||||
};
|
||||
}, pluginKey);
|
||||
}
|
||||
|
||||
export { CanonicalPlugin, InferSeoMetaPlugin, MinifyPlugin, ValidatePlugin, defineHeadPlugin };
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { parseHtmlForUnheadExtraction, applyHeadToHtml, parseHtmlForIndexes } from './parser.mjs';
|
||||
export { prepareTemplate } from './parser.mjs';
|
||||
import { a as capoTagWeight } from './shared/unhead.Dssd7L5I.mjs';
|
||||
export { c as createHead, b as createServerRenderer, e as escapeHtml, p as propsToString, r as renderSSRHead, s as ssrRenderTags, t as tagToString } from './shared/unhead.Dssd7L5I.mjs';
|
||||
import { n as normalizeEntryToTags, d as dedupeKey, h as hashTag } from './shared/unhead.-hZVKou0.mjs';
|
||||
import './shared/unhead.Bf_fPVYA.mjs';
|
||||
import './shared/unhead.D7HkBzZn.mjs';
|
||||
import './shared/unhead.Bm4Y6XQI.mjs';
|
||||
import 'hookable';
|
||||
import './shared/unhead.CGPOfp5O.mjs';
|
||||
|
||||
let extractedTemplates;
|
||||
function cloneExtractedInput(value) {
|
||||
if (Array.isArray(value))
|
||||
return value.map(cloneExtractedInput);
|
||||
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
||||
const clone = {};
|
||||
for (const key of Object.keys(value))
|
||||
clone[key] = cloneExtractedInput(value[key]);
|
||||
return clone;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function freezeExtractedInput(value) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value)
|
||||
freezeExtractedInput(item);
|
||||
} else if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
||||
for (const key of Object.keys(value))
|
||||
freezeExtractedInput(value[key]);
|
||||
}
|
||||
return value && typeof value === "object" ? Object.freeze(value) : value;
|
||||
}
|
||||
function hasHooks(head) {
|
||||
const hooks = head.hooks?._hooks || {};
|
||||
for (const key in hooks) {
|
||||
if (hooks[key]?.length)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function precomputeExtractedTags(input) {
|
||||
const tags = normalizeEntryToTags(input, []);
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const tag = tags[i];
|
||||
tag._w = capoTagWeight(tag);
|
||||
tag._p = i;
|
||||
tag._d = dedupeKey(tag);
|
||||
if (!tag._d)
|
||||
tag._h = hashTag(tag);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
function extractPreparedTemplate(prepared) {
|
||||
const cache = extractedTemplates ||= /* @__PURE__ */ new WeakMap();
|
||||
let extracted = cache.get(prepared);
|
||||
if (!extracted) {
|
||||
const template = parseHtmlForUnheadExtraction(prepared.html);
|
||||
freezeExtractedInput(template.input);
|
||||
Object.freeze(template.indexes);
|
||||
Object.freeze(template);
|
||||
const tags = precomputeExtractedTags(template.input);
|
||||
freezeExtractedInput(tags);
|
||||
extracted = Object.freeze({
|
||||
template,
|
||||
tags
|
||||
});
|
||||
cache.set(prepared, extracted);
|
||||
}
|
||||
return extracted;
|
||||
}
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function transformHtmlTemplate(head, html) {
|
||||
let cached;
|
||||
let template;
|
||||
if (typeof html === "string") {
|
||||
template = parseHtmlForUnheadExtraction(html);
|
||||
} else {
|
||||
cached = extractPreparedTemplate(html);
|
||||
template = cached.template;
|
||||
}
|
||||
const hooked = cached ? hasHooks(head) : false;
|
||||
const hasDynamicResolver = cached ? head.resolvedOptions.propResolvers?.some((resolver) => !resolver._static) || false : false;
|
||||
const input = cached && (hooked || hasDynamicResolver) ? cloneExtractedInput(template.input) : template.input;
|
||||
head.push(input, { _index: 0 });
|
||||
if (cached && !hooked && head.resolvedOptions._tagWeight === capoTagWeight && !hasDynamicResolver) {
|
||||
const entry = head.entries.get(0);
|
||||
if (entry)
|
||||
entry._precomputedTags = cached.tags;
|
||||
}
|
||||
return applyHeadToHtml(template, head.render());
|
||||
}
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function transformHtmlTemplateRaw(head, html) {
|
||||
const template = typeof html === "string" ? parseHtmlForIndexes(html) : html;
|
||||
return applyHeadToHtml(template, head.render());
|
||||
}
|
||||
|
||||
export { capoTagWeight, transformHtmlTemplate, transformHtmlTemplateRaw };
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
import { U as UniqueTags, c as TagsWithInnerContent, M as MetaTagsArrayable, i as isUnsafeKey, H as HasElementTags, T as TagConfigKeys, D as DupeableTags, d as UsesMergeStrategy, V as ValidHeadTags, h as hasContent } from './unhead.CGPOfp5O.mjs';
|
||||
import { a as callHook } from './unhead.Bm4Y6XQI.mjs';
|
||||
|
||||
const META_NOREWRITE_RE = /^(?:viewport|description|keywords|robots)$/;
|
||||
const META_KEY_ATTRS = ["name", "property", "http-equiv"];
|
||||
function isMetaArrayDupeKey(v) {
|
||||
const i = v.indexOf(":");
|
||||
if (i === -1)
|
||||
return false;
|
||||
const key = v.slice(i + 1);
|
||||
return MetaTagsArrayable.has(key) || key.startsWith("og:image:") || key.startsWith("og:video:") || key.startsWith("og:audio:") || key.startsWith("twitter:image:");
|
||||
}
|
||||
function dedupeKey(tag) {
|
||||
const { props, tag: t, key } = tag;
|
||||
if (UniqueTags.has(t))
|
||||
return t;
|
||||
if (t === "link") {
|
||||
if (props.rel === "canonical")
|
||||
return "canonical";
|
||||
if (props.rel === "alternate" && props.hreflang)
|
||||
return `alternate:${props.hreflang}`;
|
||||
}
|
||||
if (props.charset)
|
||||
return "charset";
|
||||
if (t === "meta") {
|
||||
for (const n of META_KEY_ATTRS) {
|
||||
const v = props[n];
|
||||
if (v !== void 0)
|
||||
return `meta:${v}${(typeof v !== "string" || !v.includes(":")) && !META_NOREWRITE_RE.test(v) && key ? `:key:${key}` : ""}`;
|
||||
}
|
||||
}
|
||||
if (key)
|
||||
return `${t}:key:${key}`;
|
||||
if (props.id)
|
||||
return `${t}:id:${props.id}`;
|
||||
if (t === "link" && props.rel && props.href)
|
||||
return `link:${props.rel}:${props.href}`;
|
||||
return TagsWithInnerContent.has(t) && (tag.textContent || tag.innerHTML) ? `${t}:content:${tag.textContent || tag.innerHTML}` : void 0;
|
||||
}
|
||||
function hashTag(tag) {
|
||||
const identity = tag._h || tag._d || tag.textContent || tag.innerHTML;
|
||||
if (identity)
|
||||
return identity;
|
||||
const keys = Object.keys(tag.props).sort();
|
||||
let hash = `${tag.tag}:`;
|
||||
let separator = "";
|
||||
for (const key of keys) {
|
||||
hash += `${separator}${key}:${String(tag.props[key])}`;
|
||||
separator = ",";
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function walkResolver(val, resolve, key) {
|
||||
if (key === "_resolver")
|
||||
return val;
|
||||
if (typeof val === "function" && (!key || key !== "titleTemplate" && !key.startsWith("on")))
|
||||
val = val();
|
||||
const v = resolve ? resolve(key, val) : val;
|
||||
if (Array.isArray(v)) {
|
||||
let out;
|
||||
for (let i = 0; i < v.length; i++) {
|
||||
const r = walkResolver(v[i], resolve);
|
||||
if (out) {
|
||||
out[i] = r;
|
||||
} else if (r !== v[i]) {
|
||||
out = v.slice(0, i);
|
||||
out[i] = r;
|
||||
}
|
||||
}
|
||||
return out || v;
|
||||
}
|
||||
if (v?.constructor === Object) {
|
||||
let next;
|
||||
for (const k in v) {
|
||||
const unsafe = isUnsafeKey(k);
|
||||
const r = unsafe ? void 0 : walkResolver(v[k], resolve, k);
|
||||
if (!next && (unsafe || r !== v[k])) {
|
||||
next = {};
|
||||
for (const pk in v) {
|
||||
if (pk === k)
|
||||
break;
|
||||
next[pk] = v[pk];
|
||||
}
|
||||
}
|
||||
if (next && !unsafe)
|
||||
next[k] = r;
|
||||
}
|
||||
return next || v;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const INVALID_ATTR_NAME_RE = /[\s"'<>/=\x00-\x1F\x7F]/;
|
||||
|
||||
function normalizeStyleClassProps(key, value) {
|
||||
const isStyle = key === "style";
|
||||
const store = isStyle ? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Set();
|
||||
const add = (v) => {
|
||||
if (!v)
|
||||
return;
|
||||
if (isStyle) {
|
||||
const i = v.indexOf(":");
|
||||
i > 0 && store.set(v.slice(0, i).trim(), v.slice(i + 1).trim());
|
||||
} else {
|
||||
v.split(" ").forEach((c) => c && store.add(c));
|
||||
}
|
||||
};
|
||||
if (typeof value === "string") {
|
||||
(isStyle ? value.split(";") : [value]).forEach(add);
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach(add);
|
||||
} else if (value && typeof value === "object") {
|
||||
for (const k in value) {
|
||||
const v = value[k];
|
||||
v && v !== "false" && (isStyle ? store.set(k.trim(), String(v)) : add(k));
|
||||
}
|
||||
}
|
||||
return store;
|
||||
}
|
||||
function normalizeProps(tag, input) {
|
||||
tag.props = tag.props || {};
|
||||
if (!input)
|
||||
return tag;
|
||||
if (tag.tag === "templateParams") {
|
||||
tag.props = input;
|
||||
return tag;
|
||||
}
|
||||
const isHtmlTag = HasElementTags.has(tag.tag) || tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs";
|
||||
for (const prop in input) {
|
||||
if (isUnsafeKey(prop))
|
||||
continue;
|
||||
const isData = prop.startsWith("data-");
|
||||
const isHtmlAttr = isHtmlTag && !TagConfigKeys.has(prop);
|
||||
const key = isHtmlAttr && !isData ? prop.toLowerCase() : prop;
|
||||
if (isHtmlAttr && (!key || INVALID_ATTR_NAME_RE.test(key)))
|
||||
continue;
|
||||
const value = input[prop];
|
||||
if (value === null) {
|
||||
tag.props[key] = null;
|
||||
} else if (prop === "class" || prop === "style") {
|
||||
tag.props[prop] = normalizeStyleClassProps(prop, value);
|
||||
} else if (TagConfigKeys.has(prop)) {
|
||||
if ((prop === "textContent" || prop === "innerHTML") && typeof value === "object") {
|
||||
const type = input.type || "application/json";
|
||||
if (type.endsWith("json") || type === "speculationrules" || type === "importmap") {
|
||||
tag.props.type = type;
|
||||
tag[prop] = JSON.stringify(value);
|
||||
}
|
||||
} else {
|
||||
tag[prop] = value;
|
||||
}
|
||||
} else if (value !== void 0) {
|
||||
const str = String(value);
|
||||
const isMeta = tag.tag === "meta" && key === "content";
|
||||
tag.props[key] = str === "true" || str === "" ? isData || isMeta ? str : true : !value && isData && str === "false" ? "false" : value;
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
function resolveHeadInput(input, propResolvers) {
|
||||
let resolve;
|
||||
if (propResolvers.length) {
|
||||
resolve = (key, val) => {
|
||||
for (let i = 0; i < propResolvers.length; i++)
|
||||
val = propResolvers[i](key, val);
|
||||
return val;
|
||||
};
|
||||
input = resolve(void 0, input);
|
||||
}
|
||||
return walkResolver(input, resolve);
|
||||
}
|
||||
function normalizeTag(tagName, _input) {
|
||||
const input = typeof _input === "object" && typeof _input !== "function" ? _input : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: _input };
|
||||
const tag = normalizeProps({ tag: tagName, props: {} }, input);
|
||||
if (tag.key && DupeableTags.has(tag.tag))
|
||||
tag.props["data-hid"] = tag._h = tag.key;
|
||||
if (tag.tag === "script" && typeof tag.innerHTML === "object") {
|
||||
tag.innerHTML = JSON.stringify(tag.innerHTML);
|
||||
tag.props.type = tag.props.type || "application/json";
|
||||
}
|
||||
if (Array.isArray(tag.props.content)) {
|
||||
const tags = [];
|
||||
for (const content of tag.props.content) {
|
||||
tags.push({ ...tag, props: { ...tag.props, content } });
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
function pushNormalizedTag(tags, tag) {
|
||||
if (Array.isArray(tag)) {
|
||||
for (const t of tag) tags.push(t);
|
||||
} else {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
function normalizeEntryToTags(input, propResolvers) {
|
||||
if (!input)
|
||||
return [];
|
||||
if (typeof input === "function")
|
||||
input = input();
|
||||
input = resolveHeadInput(input, propResolvers);
|
||||
const tags = [];
|
||||
for (const key in input) {
|
||||
const value = input[key];
|
||||
if (value !== void 0) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const v of value) pushNormalizedTag(tags, normalizeTag(key, v));
|
||||
} else {
|
||||
pushNormalizedTag(tags, normalizeTag(key, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
const LT_RE = /</g;
|
||||
const SCRIPT_END_RE = /<\/script/g;
|
||||
const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w;
|
||||
const DEFAULT_TAG_WEIGHT = () => 100;
|
||||
function isEmptyProps(props) {
|
||||
for (const _ in props)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
const TAG_MUTATING_HOOK_RE = /^tags:|:render/;
|
||||
function syncEntryHookCache(head, hooks) {
|
||||
const count = (hooks["entries:resolve"]?.length || 0) + (hooks["entries:normalize"]?.length || 0);
|
||||
if (head._h !== count) {
|
||||
head._h = count;
|
||||
for (const entry of head.entries.values())
|
||||
delete entry._tags;
|
||||
}
|
||||
}
|
||||
function cloneTagsInPlace(tags) {
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const t = tags[i];
|
||||
const props = { ...t.props };
|
||||
if (props.class instanceof Set)
|
||||
props.class = new Set(props.class);
|
||||
if (props.style instanceof Map)
|
||||
props.style = new Map(props.style);
|
||||
tags[i] = { ...t, props };
|
||||
}
|
||||
}
|
||||
function valuesToTags(ctx, sortFlatMeta) {
|
||||
const tags = ctx.tags;
|
||||
let w = 0;
|
||||
for (const value of ctx.tagMap.values()) {
|
||||
if (Array.isArray(value)) {
|
||||
for (const tag of value) tags[w++] = tag;
|
||||
} else {
|
||||
tags[w++] = value;
|
||||
}
|
||||
}
|
||||
tags.length = w;
|
||||
if (sortFlatMeta)
|
||||
tags.sort(sortTags);
|
||||
}
|
||||
function dedupeTags(ctx) {
|
||||
let hasFlatMeta = false;
|
||||
for (const next of ctx.tags.sort(sortTags)) {
|
||||
const k = next._d || hashTag(next);
|
||||
if (!k)
|
||||
continue;
|
||||
const prev = ctx.tagMap.get(k);
|
||||
if (!prev) {
|
||||
ctx.tagMap.set(k, next);
|
||||
continue;
|
||||
}
|
||||
const strategy = next.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? "merge" : null) || (next.key && next.key === prev.key ? "merge" : null);
|
||||
if (strategy === "merge") {
|
||||
const props = { ...prev.props };
|
||||
for (const p in next.props) {
|
||||
props[p] = p === "style" ? new Map([...prev.props.style || /* @__PURE__ */ new Map(), ...next.props[p]]) : p === "class" ? /* @__PURE__ */ new Set([...prev.props.class || [], ...next.props[p]]) : next.props[p];
|
||||
}
|
||||
ctx.tagMap.set(k, { ...next, props });
|
||||
} else if (next._p >> 10 === prev._p >> 10 && next.tag === "meta" && isMetaArrayDupeKey(k)) {
|
||||
ctx.tagMap.set(k, Object.assign([...Array.isArray(prev) ? prev : [prev], next], next));
|
||||
hasFlatMeta = true;
|
||||
} else if (next._w === prev._w ? next._p > prev._p : next._w < prev._w) {
|
||||
ctx.tagMap.set(k, next);
|
||||
}
|
||||
}
|
||||
return hasFlatMeta;
|
||||
}
|
||||
function resolveTitleTemplate(ctx, head) {
|
||||
const title = ctx.tagMap.get("title");
|
||||
const tpl = ctx.tagMap.get("titleTemplate");
|
||||
head._title = title?.textContent;
|
||||
if (!tpl)
|
||||
return;
|
||||
const fn = tpl.textContent;
|
||||
head._titleTemplate = fn;
|
||||
if (!fn)
|
||||
return;
|
||||
let v = typeof fn === "function" ? fn(title?.textContent) : fn;
|
||||
if (typeof v === "string" && !head.plugins.has("template-params"))
|
||||
v = v.replace("%s", title?.textContent || "");
|
||||
if (title) {
|
||||
v === null ? ctx.tagMap.delete("title") : ctx.tagMap.set("title", { ...title, textContent: v });
|
||||
} else {
|
||||
ctx.tagMap.set("titleTemplate", { ...tpl, tag: "title", textContent: v });
|
||||
}
|
||||
}
|
||||
function sanitizeTagsInPlace(tags) {
|
||||
let w = 0;
|
||||
for (let t of tags) {
|
||||
const { innerHTML, tag, props } = t;
|
||||
if (!ValidHeadTags.has(tag) || isEmptyProps(props) && !hasContent(innerHTML) && !hasContent(t.textContent))
|
||||
continue;
|
||||
if (tag === "meta") {
|
||||
if (!hasContent(props.content) && !props["http-equiv"] && !props.charset)
|
||||
continue;
|
||||
}
|
||||
if (tag === "script" && (innerHTML || t.textContent)) {
|
||||
const type = String(props.type);
|
||||
const isJsonLike = type.endsWith("json") || type === "importmap" || type === "speculationrules";
|
||||
const escape = (content) => isJsonLike ? (typeof content === "string" ? content : JSON.stringify(content)).replace(LT_RE, "\\u003C") : typeof content === "string" ? content.replace(SCRIPT_END_RE, "<\\/script") : content;
|
||||
t = { ...t };
|
||||
if (innerHTML)
|
||||
t.innerHTML = escape(innerHTML);
|
||||
if (t.textContent)
|
||||
t.textContent = escape(t.textContent);
|
||||
t._d = dedupeKey(t);
|
||||
}
|
||||
tags[w++] = t;
|
||||
}
|
||||
tags.length = w;
|
||||
return tags;
|
||||
}
|
||||
function sanitizeTags(tags) {
|
||||
return sanitizeTagsInPlace([...tags]);
|
||||
}
|
||||
function resolveTags(head, options) {
|
||||
const weightFn = options?.tagWeight ?? head.resolvedOptions._tagWeight ?? DEFAULT_TAG_WEIGHT;
|
||||
const ctx = { tagMap: /* @__PURE__ */ new Map(), tags: [] };
|
||||
const hooks = head.hooks?._hooks || {};
|
||||
syncEntryHookCache(head, hooks);
|
||||
for (const e of head.entries.values()) {
|
||||
if (e._pending !== void 0) {
|
||||
e.input = e._pending;
|
||||
delete e._pending;
|
||||
delete e._tags;
|
||||
delete e._precomputedTags;
|
||||
}
|
||||
}
|
||||
let entries;
|
||||
if (hooks["entries:resolve"]?.length || hooks["entries:normalize"]?.length) {
|
||||
entries = [...head.entries.values()];
|
||||
if (hooks["entries:resolve"]?.length)
|
||||
callHook(head, "entries:resolve", { entries, ...ctx });
|
||||
}
|
||||
syncEntryHookCache(head, hooks);
|
||||
for (const e of entries || head.entries.values()) {
|
||||
let tags = e._tags;
|
||||
if (!tags) {
|
||||
if (e._precomputedTags && weightFn === head.resolvedOptions._tagWeight && !hooks["entries:normalize"]?.length && !hooks["entries:resolve"]?.length && (!e.options || isEmptyProps(e.options))) {
|
||||
tags = e._precomputedTags;
|
||||
} else {
|
||||
tags = normalizeEntryToTags(e.input, head.resolvedOptions.propResolvers || []);
|
||||
if (e.options && !isEmptyProps(e.options)) {
|
||||
for (const t of tags)
|
||||
Object.assign(t, e.options);
|
||||
}
|
||||
if (hooks["entries:normalize"]?.length) {
|
||||
const normalizeCtx = { tags, entry: e };
|
||||
callHook(head, "entries:normalize", normalizeCtx);
|
||||
tags = normalizeCtx.tags;
|
||||
}
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const t = tags[i];
|
||||
t._w = weightFn(t);
|
||||
t._p = (e._i << 10) + i;
|
||||
t._d = dedupeKey(t);
|
||||
if (!t._d)
|
||||
t._h = hashTag(t);
|
||||
}
|
||||
e._tags = tags;
|
||||
}
|
||||
}
|
||||
ctx.tags.push(...tags);
|
||||
}
|
||||
for (const name in hooks) {
|
||||
if (hooks[name]?.length && TAG_MUTATING_HOOK_RE.test(name)) {
|
||||
cloneTagsInPlace(ctx.tags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const hasFlatMeta = dedupeTags(ctx);
|
||||
resolveTitleTemplate(ctx, head);
|
||||
valuesToTags(ctx, hasFlatMeta);
|
||||
callHook(head, "tags:beforeResolve", ctx);
|
||||
callHook(head, "tags:resolve", ctx);
|
||||
callHook(head, "tags:afterResolve", ctx);
|
||||
return sanitizeTagsInPlace(ctx.tags);
|
||||
}
|
||||
|
||||
export { INVALID_ATTR_NAME_RE as I, dedupeTags as a, normalizeProps as b, resolveTitleTemplate as c, dedupeKey as d, resolveHeadInput as e, hashTag as h, isMetaArrayDupeKey as i, normalizeEntryToTags as n, resolveTags as r, sanitizeTags as s, walkResolver as w };
|
||||
+673
@@ -0,0 +1,673 @@
|
||||
const URL_META_KEYS = /* @__PURE__ */ new Set([
|
||||
"og:url",
|
||||
"og:image",
|
||||
"og:image:url",
|
||||
"og:image:secure_url",
|
||||
"og:video",
|
||||
"og:video:url",
|
||||
"og:video:secure_url",
|
||||
"og:audio",
|
||||
"og:audio:url",
|
||||
"og:audio:secure_url",
|
||||
"twitter:image",
|
||||
"twitter:image:src",
|
||||
"twitter:player",
|
||||
"twitter:player:stream",
|
||||
"payment:success_url"
|
||||
]);
|
||||
const KNOWN_META_PROPERTIES = /* @__PURE__ */ new Set([
|
||||
"article:author",
|
||||
"article:expiration_time",
|
||||
"article:modified_time",
|
||||
"article:published_time",
|
||||
"article:section",
|
||||
"article:tag",
|
||||
"book:author",
|
||||
"book:isbn",
|
||||
"book:release_date",
|
||||
"book:tag",
|
||||
"fb:app_id",
|
||||
"og:audio",
|
||||
"og:audio:secure_url",
|
||||
"og:audio:type",
|
||||
"og:audio:url",
|
||||
"og:description",
|
||||
"og:determiner",
|
||||
"og:image",
|
||||
"og:image:alt",
|
||||
"og:image:height",
|
||||
"og:image:secure_url",
|
||||
"og:image:type",
|
||||
"og:image:url",
|
||||
"og:image:width",
|
||||
"og:locale",
|
||||
"og:locale:alternate",
|
||||
"og:site_name",
|
||||
"og:title",
|
||||
"og:type",
|
||||
"og:url",
|
||||
"og:video",
|
||||
"og:video:alt",
|
||||
"og:video:duration",
|
||||
"og:video:height",
|
||||
"og:video:secure_url",
|
||||
"og:video:type",
|
||||
"og:video:url",
|
||||
"og:video:width",
|
||||
"payment:amount",
|
||||
"payment:currency",
|
||||
"payment:description",
|
||||
"payment:expires_at",
|
||||
"payment:id",
|
||||
"payment:status",
|
||||
"payment:success_url",
|
||||
"profile:first_name",
|
||||
"profile:gender",
|
||||
"profile:last_name",
|
||||
"profile:username"
|
||||
]);
|
||||
const KNOWN_META_NAMES = /* @__PURE__ */ new Set([
|
||||
"apple-itunes-app",
|
||||
"apple-mobile-web-app-capable",
|
||||
"apple-mobile-web-app-status-bar-style",
|
||||
"apple-mobile-web-app-title",
|
||||
"application-name",
|
||||
"author",
|
||||
"color-scheme",
|
||||
"creator",
|
||||
"description",
|
||||
"fb:app_id",
|
||||
"fediverse:creator",
|
||||
"format-detection",
|
||||
"generator",
|
||||
"google-site-verification",
|
||||
"google",
|
||||
"googlebot",
|
||||
"keywords",
|
||||
"mobile-web-app-capable",
|
||||
"msapplication-config",
|
||||
"msapplication-tilecolor",
|
||||
"msapplication-tileimage",
|
||||
"publisher",
|
||||
"rating",
|
||||
"referrer",
|
||||
"robots",
|
||||
"theme-color",
|
||||
"viewport",
|
||||
"twitter:app:id:googleplay",
|
||||
"twitter:app:id:ipad",
|
||||
"twitter:app:id:iphone",
|
||||
"twitter:app:name:googleplay",
|
||||
"twitter:app:name:ipad",
|
||||
"twitter:app:name:iphone",
|
||||
"twitter:app:url:googleplay",
|
||||
"twitter:app:url:ipad",
|
||||
"twitter:app:url:iphone",
|
||||
"twitter:card",
|
||||
"twitter:creator",
|
||||
"twitter:creator:id",
|
||||
"twitter:data:1",
|
||||
"twitter:data:2",
|
||||
"twitter:description",
|
||||
"twitter:image",
|
||||
"twitter:image:alt",
|
||||
"twitter:label:1",
|
||||
"twitter:label:2",
|
||||
"twitter:player",
|
||||
"twitter:player:height",
|
||||
"twitter:player:stream",
|
||||
"twitter:player:width",
|
||||
"twitter:site",
|
||||
"twitter:site:id",
|
||||
"twitter:title"
|
||||
]);
|
||||
const TAG_PRIORITY_ALIASES = ["critical", "high", "low"];
|
||||
const DEPRECATED_PROPS = {
|
||||
children: { replacement: "innerHTML", ruleId: "deprecated-prop-children" },
|
||||
hid: { replacement: "key", ruleId: "deprecated-prop-hid-vmid" },
|
||||
vmid: { replacement: "key", ruleId: "deprecated-prop-hid-vmid" },
|
||||
body: { replacement: "tagPosition: 'bodyClose'", ruleId: "deprecated-prop-body" }
|
||||
};
|
||||
|
||||
function levenshtein(a, b) {
|
||||
const m = a.length;
|
||||
const n = b.length;
|
||||
const d = Array.from({ length: n + 1 }, (_, i) => i);
|
||||
for (let i = 1; i <= m; i++) {
|
||||
let prev = i - 1;
|
||||
d[0] = i;
|
||||
for (let j = 1; j <= n; j++) {
|
||||
const tmp = d[j];
|
||||
d[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, d[j], d[j - 1]);
|
||||
prev = tmp;
|
||||
}
|
||||
}
|
||||
return d[n];
|
||||
}
|
||||
function findClosestMatch(value, knownSet) {
|
||||
const threshold = value.length <= 8 ? 2 : 3;
|
||||
let best;
|
||||
let bestDist = threshold + 1;
|
||||
for (const known of knownSet) {
|
||||
if (Math.abs(known.length - value.length) > threshold)
|
||||
continue;
|
||||
const dist = levenshtein(value, known);
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
best = known;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
const emptyMetaContent = (tag) => {
|
||||
if (tag.tagType !== "meta")
|
||||
return [];
|
||||
if (!tag.keys.has("content"))
|
||||
return [];
|
||||
if (tag.props.content !== "")
|
||||
return [];
|
||||
const key = typeof tag.props.name === "string" && tag.props.name || typeof tag.props.property === "string" && tag.props.property || "meta";
|
||||
const diag = {
|
||||
ruleId: "empty-meta-content",
|
||||
message: `Meta tag "${key}" has empty content.`,
|
||||
at: { kind: "prop", key: "content" }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const RESOLVABLE_OBJECT = ["boolean", "function", "null", "object"];
|
||||
const RESOLVABLE_TAG_LIST = ["array", "boolean", "function", "null"];
|
||||
const RESOLVABLE_TITLE = ["boolean", "function", "null", "number", "object", "string"];
|
||||
const ATTRIBUTE_SCALAR = ["boolean", "function", "null", "number", "string"];
|
||||
const ATTRIBUTE_STRUCTURED = ["array", "boolean", "function", "null", "number", "object", "string"];
|
||||
const INPUT_SHAPE_FIELDS = {
|
||||
base: {
|
||||
canonical: "base",
|
||||
contexts: { head: RESOLVABLE_OBJECT }
|
||||
},
|
||||
bodyattrs: {
|
||||
canonical: "bodyAttrs",
|
||||
contexts: { head: RESOLVABLE_OBJECT }
|
||||
},
|
||||
class: {
|
||||
canonical: "class",
|
||||
contexts: {
|
||||
bodyAttrs: ATTRIBUTE_STRUCTURED,
|
||||
htmlAttrs: ATTRIBUTE_STRUCTURED
|
||||
}
|
||||
},
|
||||
htmlattrs: {
|
||||
canonical: "htmlAttrs",
|
||||
contexts: { head: RESOLVABLE_OBJECT }
|
||||
},
|
||||
link: {
|
||||
canonical: "link",
|
||||
contexts: { head: RESOLVABLE_TAG_LIST }
|
||||
},
|
||||
meta: {
|
||||
canonical: "meta",
|
||||
contexts: { head: RESOLVABLE_TAG_LIST }
|
||||
},
|
||||
noscript: {
|
||||
canonical: "noscript",
|
||||
contexts: { head: RESOLVABLE_TAG_LIST }
|
||||
},
|
||||
script: {
|
||||
canonical: "script",
|
||||
contexts: { head: RESOLVABLE_TAG_LIST }
|
||||
},
|
||||
style: {
|
||||
canonical: "style",
|
||||
contexts: {
|
||||
bodyAttrs: ATTRIBUTE_STRUCTURED,
|
||||
head: RESOLVABLE_TAG_LIST,
|
||||
htmlAttrs: ATTRIBUTE_STRUCTURED
|
||||
}
|
||||
},
|
||||
templateparams: {
|
||||
canonical: "templateParams",
|
||||
contexts: { head: ["object"] }
|
||||
},
|
||||
title: {
|
||||
canonical: "title",
|
||||
contexts: {
|
||||
bodyAttrs: ATTRIBUTE_SCALAR,
|
||||
head: RESOLVABLE_TITLE,
|
||||
htmlAttrs: ATTRIBUTE_SCALAR,
|
||||
seoMeta: RESOLVABLE_TITLE
|
||||
}
|
||||
},
|
||||
titletemplate: {
|
||||
canonical: "titleTemplate",
|
||||
contexts: {
|
||||
head: ["function", "null", "object", "string"],
|
||||
seoMeta: ["function", "null", "object", "string"]
|
||||
}
|
||||
}
|
||||
};
|
||||
function includes(kinds, kind) {
|
||||
return kinds?.includes(kind) === true;
|
||||
}
|
||||
function formatKinds(kinds) {
|
||||
return kinds.join(", ");
|
||||
}
|
||||
function invalidKnownHeadField(input, key, shape, kind) {
|
||||
const expected = shape.contexts[input.context];
|
||||
if (includes(expected, kind))
|
||||
return void 0;
|
||||
if ((input.context === "htmlAttrs" || input.context === "bodyAttrs") && kind === "null")
|
||||
return void 0;
|
||||
if (input.context === "head") {
|
||||
return {
|
||||
ruleId: "invalid-input-shape",
|
||||
message: `"${shape.canonical}" in a head input must be one of: ${formatKinds(expected || [])}. Received ${kind}.`,
|
||||
at: { kind: "prop-value", key }
|
||||
};
|
||||
}
|
||||
if (includes(shape.contexts.head, kind)) {
|
||||
return {
|
||||
ruleId: "invalid-input-shape",
|
||||
message: `"${shape.canonical}" has a head input shape but appears in ${input.context}. Move it to the top-level head input.`,
|
||||
at: { kind: "prop-value", key }
|
||||
};
|
||||
}
|
||||
}
|
||||
function invalidAttributeField(input, key, kind) {
|
||||
if (includes(ATTRIBUTE_SCALAR, kind))
|
||||
return void 0;
|
||||
return {
|
||||
ruleId: "invalid-input-shape",
|
||||
message: `"${key}" in ${input.context} must resolve to a scalar attribute value. Received ${kind}.`,
|
||||
at: { kind: "prop-value", key }
|
||||
};
|
||||
}
|
||||
function validateInputShape(input) {
|
||||
const diagnostics = [];
|
||||
const isAttributes = input.context === "htmlAttrs" || input.context === "bodyAttrs";
|
||||
for (const key of input.keys) {
|
||||
const kind = input.valueKinds.get(key) ?? "unknown";
|
||||
if (kind === "unknown")
|
||||
continue;
|
||||
const shape = INPUT_SHAPE_FIELDS[key.toLowerCase()];
|
||||
const knownDiagnostic = shape ? invalidKnownHeadField(input, key, shape, kind) : void 0;
|
||||
if (knownDiagnostic) {
|
||||
diagnostics.push(knownDiagnostic);
|
||||
continue;
|
||||
}
|
||||
if (isAttributes && !shape?.contexts[input.context]) {
|
||||
const attributeDiagnostic = invalidAttributeField(input, key, kind);
|
||||
if (attributeDiagnostic)
|
||||
diagnostics.push(attributeDiagnostic);
|
||||
}
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
const noDeprecatedProps = (tag) => {
|
||||
const out = [];
|
||||
for (const key of tag.keys) {
|
||||
if (!(key in DEPRECATED_PROPS))
|
||||
continue;
|
||||
const { replacement } = DEPRECATED_PROPS[key];
|
||||
if (key === "body") {
|
||||
if (tag.props.body !== true)
|
||||
continue;
|
||||
const fix2 = tag.keys.has("tagPosition") ? void 0 : { type: "replace-prop", key: "body", newSource: `tagPosition: 'bodyClose'` };
|
||||
out.push({
|
||||
ruleId: "deprecated-prop-body",
|
||||
message: `"body" was removed in v3 of unhead. Use "${replacement}" instead.`,
|
||||
at: { kind: "prop", key },
|
||||
fix: fix2
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const newKey = key === "children" ? "innerHTML" : "key";
|
||||
const fix = tag.keys.has(newKey) ? void 0 : { type: "rename-prop", key, newKey };
|
||||
out.push({
|
||||
ruleId: key === "children" ? "deprecated-prop-children" : "deprecated-prop-hid-vmid",
|
||||
message: `"${key}" was removed in v3 of unhead. Use "${replacement}" instead.`,
|
||||
at: { kind: "prop", key },
|
||||
fix
|
||||
});
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const HTML_CHARS_RE = /[<>]/;
|
||||
const noHtmlInTitle = (input) => {
|
||||
const title = input.props.title;
|
||||
if (typeof title !== "string" || !HTML_CHARS_RE.test(title))
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "html-in-title",
|
||||
message: `Title contains HTML characters which will be escaped, not rendered: "${title}".`,
|
||||
at: { kind: "prop-value", key: "title" }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const OG_PREFIX_RE = /^(?:og|article|book|profile|fb):/;
|
||||
const noUnknownMeta = (tag) => {
|
||||
if (tag.tagType !== "meta")
|
||||
return [];
|
||||
const out = [];
|
||||
const property = tag.props.property;
|
||||
if (typeof property === "string" && !KNOWN_META_PROPERTIES.has(property) && OG_PREFIX_RE.test(property)) {
|
||||
const suggestion = findClosestMatch(property, KNOWN_META_PROPERTIES);
|
||||
if (suggestion) {
|
||||
out.push({
|
||||
ruleId: "possible-typo",
|
||||
message: `Unknown meta property "${property}". Did you mean "${suggestion}"?`,
|
||||
at: { kind: "prop-value", key: "property" },
|
||||
fix: { type: "replace-prop-value", key: "property", newSource: `'${suggestion}'` }
|
||||
});
|
||||
}
|
||||
}
|
||||
const name = tag.props.name;
|
||||
if (typeof name === "string") {
|
||||
const lower = name.toLowerCase();
|
||||
if (!KNOWN_META_NAMES.has(lower) && (lower.startsWith("twitter:") || lower.startsWith("fediverse:") || !lower.includes(":"))) {
|
||||
const suggestion = findClosestMatch(lower, KNOWN_META_NAMES);
|
||||
if (suggestion) {
|
||||
out.push({
|
||||
ruleId: "possible-typo",
|
||||
message: `Unknown meta name "${name}". Did you mean "${suggestion}"?`,
|
||||
at: { kind: "prop-value", key: "name" },
|
||||
fix: { type: "replace-prop-value", key: "name", newSource: `'${suggestion}'` }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
function isAbsolute(url) {
|
||||
return url.startsWith("http://") || url.startsWith("https://");
|
||||
}
|
||||
const nonAbsoluteCanonical = (tag) => {
|
||||
if (tag.tagType !== "link")
|
||||
return [];
|
||||
if (tag.props.rel !== "canonical")
|
||||
return [];
|
||||
const href = tag.props.href;
|
||||
if (typeof href !== "string")
|
||||
return [];
|
||||
if (isAbsolute(href))
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "non-absolute-canonical",
|
||||
message: `Canonical URL should be absolute, received "${href}".`,
|
||||
at: { kind: "prop-value", key: "href" }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const ALIASES = ["critical", "high", "low"];
|
||||
const numericTagPriority = (tag) => {
|
||||
const value = tag.props.tagPriority;
|
||||
if (typeof value !== "number")
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "numeric-tag-priority",
|
||||
message: `Numeric tagPriority (${value}) is brittle. Prefer an alias ('critical' | 'high' | 'low') or 'before:<key>' / 'after:<key>'.`,
|
||||
at: { kind: "prop-value", key: "tagPriority" },
|
||||
suggestions: ALIASES.map((alias) => ({
|
||||
message: `Replace with '${alias}'`,
|
||||
fix: { type: "replace-prop-value", key: "tagPriority", newSource: `'${alias}'` }
|
||||
}))
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const TAG_TO_HELPER = {
|
||||
link: "defineLink",
|
||||
script: "defineScript"
|
||||
};
|
||||
const preferDefineHelpers = (tag, ctx) => {
|
||||
if (!tag.inArray)
|
||||
return [];
|
||||
const helper = TAG_TO_HELPER[tag.tagType];
|
||||
if (!helper)
|
||||
return [];
|
||||
const localName = ctx?.importedHelpers?.get(helper);
|
||||
const imported = localName !== void 0;
|
||||
const fix = { type: "wrap-tag", wrapWith: localName ?? helper };
|
||||
const diag = {
|
||||
ruleId: "prefer-define-helpers",
|
||||
message: `Wrap this ${tag.tagType} entry in \`${helper}()\` so unhead can narrow its type.`,
|
||||
fix: imported ? fix : void 0,
|
||||
suggestions: imported ? void 0 : [{ message: `Wrap in \`${helper}()\` (you may need to import it).`, fix: { type: "wrap-tag", wrapWith: helper } }]
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const preloadMissingAs = (tag) => {
|
||||
if (tag.tagType !== "link")
|
||||
return [];
|
||||
if (tag.props.rel !== "preload")
|
||||
return [];
|
||||
if (tag.keys.has("as"))
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "preload-missing-as",
|
||||
message: 'Preload link is missing the required "as" attribute.'
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
const preloadFontCrossorigin = (tag) => {
|
||||
if (tag.tagType !== "link")
|
||||
return [];
|
||||
if (tag.props.rel !== "preload")
|
||||
return [];
|
||||
if (tag.props.as !== "font")
|
||||
return [];
|
||||
if (tag.keys.has("crossorigin"))
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "preload-font-crossorigin",
|
||||
message: 'Font preload requires "crossorigin" \u2014 without it the font will be fetched twice.',
|
||||
fix: { type: "insert-after-prop", afterKey: "as", insert: `, crossorigin: 'anonymous'` }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const robotsConflict = (tag) => {
|
||||
if (tag.tagType !== "meta")
|
||||
return [];
|
||||
if (tag.props.name !== "robots")
|
||||
return [];
|
||||
const content = tag.props.content;
|
||||
if (typeof content !== "string")
|
||||
return [];
|
||||
const directives = content.toLowerCase().split(",").map((d) => d.trim());
|
||||
const out = [];
|
||||
if (directives.includes("index") && directives.includes("noindex")) {
|
||||
out.push({
|
||||
ruleId: "robots-conflict",
|
||||
message: 'Robots meta has conflicting "index" and "noindex" directives.'
|
||||
});
|
||||
}
|
||||
if (directives.includes("follow") && directives.includes("nofollow")) {
|
||||
out.push({
|
||||
ruleId: "robots-conflict",
|
||||
message: 'Robots meta has conflicting "follow" and "nofollow" directives.'
|
||||
});
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const deferOnModuleScript = (tag) => {
|
||||
if (tag.tagType !== "script")
|
||||
return [];
|
||||
if (tag.props.type !== "module")
|
||||
return [];
|
||||
if (tag.props.defer !== true)
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "defer-on-module-script",
|
||||
message: '"defer" is redundant on module scripts. Modules are deferred by default.',
|
||||
at: { kind: "prop", key: "defer" },
|
||||
fix: { type: "remove-prop", key: "defer" }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
const scriptSrcWithContent = (tag) => {
|
||||
if (tag.tagType !== "script")
|
||||
return [];
|
||||
if (typeof tag.props.src !== "string")
|
||||
return [];
|
||||
const hasInner = tag.keys.has("innerHTML") && tag.props.innerHTML !== "" || tag.keys.has("textContent") && tag.props.textContent !== "";
|
||||
if (!hasInner)
|
||||
return [];
|
||||
const diag = {
|
||||
ruleId: "script-src-with-content",
|
||||
message: 'Script has both "src" and inline content. The browser will ignore the inline content.'
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const NUMERIC_RE = /^\d+$/;
|
||||
const twitterHandleMissingAt = (tag) => {
|
||||
if (tag.tagType !== "meta")
|
||||
return [];
|
||||
const name = tag.props.name;
|
||||
if (name !== "twitter:site" && name !== "twitter:creator")
|
||||
return [];
|
||||
const content = tag.props.content;
|
||||
if (typeof content !== "string")
|
||||
return [];
|
||||
if (content.startsWith("@") || NUMERIC_RE.test(content))
|
||||
return [];
|
||||
const fixedSource = JSON.stringify(`@${content}`);
|
||||
const diag = {
|
||||
ruleId: "twitter-handle-missing-at",
|
||||
message: `${name} should start with "@", received "${content}".`,
|
||||
at: { kind: "prop-value", key: "content" },
|
||||
fix: { type: "replace-prop-value", key: "content", newSource: fixedSource }
|
||||
};
|
||||
return [diag];
|
||||
};
|
||||
|
||||
const USER_SCALABLE_NO_RE = /user-scalable\s*=\s*(?:no|0|false)(?:[\s,;]|$)/i;
|
||||
const MAX_SCALE_RE = /maximum-scale\s*=\s*1(?:\.\d+)?(?:[\s,;]|$)/i;
|
||||
const viewportUserScalable = (tag) => {
|
||||
if (tag.tagType !== "meta")
|
||||
return [];
|
||||
if (tag.props.name !== "viewport")
|
||||
return [];
|
||||
const content = tag.props.content;
|
||||
if (typeof content !== "string")
|
||||
return [];
|
||||
const out = [];
|
||||
if (USER_SCALABLE_NO_RE.test(content)) {
|
||||
out.push({
|
||||
ruleId: "viewport-user-scalable",
|
||||
message: 'viewport "user-scalable=no" prevents zooming and harms accessibility.'
|
||||
});
|
||||
}
|
||||
if (MAX_SCALE_RE.test(content)) {
|
||||
out.push({
|
||||
ruleId: "viewport-user-scalable",
|
||||
message: 'viewport "maximum-scale=1" limits zooming and may harm accessibility.'
|
||||
});
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const TAG_TYPES = /* @__PURE__ */ new Set(["meta", "link", "script", "noscript", "style"]);
|
||||
function valueKind(value) {
|
||||
if (value === null)
|
||||
return "null";
|
||||
if (Array.isArray(value))
|
||||
return "array";
|
||||
const kind = typeof value;
|
||||
if (kind === "boolean" || kind === "function" || kind === "number" || kind === "object" || kind === "string")
|
||||
return kind;
|
||||
return "unknown";
|
||||
}
|
||||
function inputShapeFromRuntime(context, input) {
|
||||
const keys = /* @__PURE__ */ new Set();
|
||||
const valueKinds = /* @__PURE__ */ new Map();
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
keys.add(key);
|
||||
valueKinds.set(key, valueKind(value));
|
||||
}
|
||||
return { context, keys, valueKinds };
|
||||
}
|
||||
function tagInputFromRuntime(tag) {
|
||||
if (!TAG_TYPES.has(tag.tag))
|
||||
return void 0;
|
||||
const props = {};
|
||||
const keys = /* @__PURE__ */ new Set();
|
||||
for (const [k, v] of Object.entries(tag.props)) {
|
||||
keys.add(k);
|
||||
if (v == null) {
|
||||
if (tag.tag === "meta" && k === "content")
|
||||
props[k] = "";
|
||||
continue;
|
||||
}
|
||||
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
||||
if (tag.tag === "meta" && k === "name" && typeof v === "string")
|
||||
props[k] = v.toLowerCase();
|
||||
else
|
||||
props[k] = v;
|
||||
} else {
|
||||
props[k] = String(v);
|
||||
}
|
||||
}
|
||||
if (tag.tag === "script" || tag.tag === "style" || tag.tag === "noscript") {
|
||||
if (tag.innerHTML != null && tag.innerHTML !== "") {
|
||||
keys.add("innerHTML");
|
||||
}
|
||||
if (tag.textContent != null && tag.textContent !== "") {
|
||||
keys.add("textContent");
|
||||
}
|
||||
}
|
||||
if (tag.tagPriority != null) {
|
||||
keys.add("tagPriority");
|
||||
if (typeof tag.tagPriority === "string" || typeof tag.tagPriority === "number")
|
||||
props.tagPriority = tag.tagPriority;
|
||||
}
|
||||
return {
|
||||
tagType: tag.tag,
|
||||
props,
|
||||
keys
|
||||
};
|
||||
}
|
||||
function titleInputFromRuntime(titleTag) {
|
||||
if (titleTag.tag !== "title")
|
||||
return void 0;
|
||||
const text = titleTag.textContent ?? titleTag.innerHTML ?? "";
|
||||
return {
|
||||
callee: "runtime",
|
||||
props: { title: text },
|
||||
keys: /* @__PURE__ */ new Set(["title"])
|
||||
};
|
||||
}
|
||||
|
||||
const tagPredicates = {
|
||||
"defer-on-module-script": deferOnModuleScript,
|
||||
"empty-meta-content": emptyMetaContent,
|
||||
"no-deprecated-props": noDeprecatedProps,
|
||||
"no-unknown-meta": noUnknownMeta,
|
||||
"non-absolute-canonical": nonAbsoluteCanonical,
|
||||
"numeric-tag-priority": numericTagPriority,
|
||||
"preload-font-crossorigin": preloadFontCrossorigin,
|
||||
"preload-missing-as": preloadMissingAs,
|
||||
"robots-conflict": robotsConflict,
|
||||
"script-src-with-content": scriptSrcWithContent,
|
||||
"twitter-handle-missing-at": twitterHandleMissingAt,
|
||||
"viewport-user-scalable": viewportUserScalable
|
||||
};
|
||||
const inputShapePredicates = {
|
||||
"invalid-input-shape": validateInputShape
|
||||
};
|
||||
const migrationTagPredicates = {
|
||||
"prefer-define-helpers": preferDefineHelpers
|
||||
};
|
||||
const headInputPredicates = {
|
||||
"no-html-in-title": noHtmlInTitle
|
||||
};
|
||||
|
||||
export { DEPRECATED_PROPS as D, INPUT_SHAPE_FIELDS as I, KNOWN_META_NAMES as K, TAG_PRIORITY_ALIASES as T, URL_META_KEYS as U, KNOWN_META_PROPERTIES as a, inputShapePredicates as b, noHtmlInTitle as c, deferOnModuleScript as d, emptyMetaContent as e, findClosestMatch as f, noUnknownMeta as g, headInputPredicates as h, inputShapeFromRuntime as i, nonAbsoluteCanonical as j, numericTagPriority as k, levenshtein as l, migrationTagPredicates as m, noDeprecatedProps as n, preloadFontCrossorigin as o, preferDefineHelpers as p, preloadMissingAs as q, robotsConflict as r, scriptSrcWithContent as s, tagInputFromRuntime as t, tagPredicates as u, titleInputFromRuntime as v, twitterHandleMissingAt as w, validateInputShape as x, viewportUserScalable as y };
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
const BACKSLASH_RE = /\\/g;
|
||||
const LT_RE = /</g;
|
||||
const DOUBLE_QUOTE_RE = /"/g;
|
||||
const TOKEN_RE = /%\w+(?:\.\w+)?/g;
|
||||
const SepSub = "%separator";
|
||||
function sub(p, token, isJson = false) {
|
||||
let val;
|
||||
if (token === "s" || token === "pageTitle") {
|
||||
val = p.pageTitle;
|
||||
} else if (token.includes(".")) {
|
||||
const dotIndex = token.indexOf(".");
|
||||
val = p[token.substring(0, dotIndex)]?.[token.substring(dotIndex + 1)];
|
||||
} else {
|
||||
val = p[token];
|
||||
}
|
||||
if (val !== void 0) {
|
||||
return isJson ? (val || "").replace(BACKSLASH_RE, "\\\\").replace(LT_RE, "\\u003C").replace(DOUBLE_QUOTE_RE, '\\"') : val || "";
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
function processTemplateParams(s, p, sep, isJson = false) {
|
||||
if (typeof s !== "string" || !s.includes("%"))
|
||||
return s;
|
||||
let decoded = s;
|
||||
try {
|
||||
decoded = decodeURI(s);
|
||||
} catch {
|
||||
}
|
||||
const tokens = decoded.match(TOKEN_RE);
|
||||
if (!tokens) {
|
||||
return s;
|
||||
}
|
||||
const hasSepSub = s.includes(SepSub);
|
||||
s = s.replace(TOKEN_RE, (token) => {
|
||||
if (token === SepSub || !tokens.includes(token)) {
|
||||
return token;
|
||||
}
|
||||
const re = sub(p, token.slice(1), isJson);
|
||||
return re !== void 0 ? re : token;
|
||||
}).trim();
|
||||
if (hasSepSub) {
|
||||
s = s.split(SepSub).map((part) => part.trim()).filter((part) => part !== "").join(sep ? ` ${sep} ` : " ");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export { processTemplateParams as p };
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
import { M as MetaTagsArrayable } from './unhead.CGPOfp5O.mjs';
|
||||
|
||||
const META_NAMESPACES = ["twitter", "fediverse"];
|
||||
const PROPERTY_NAMESPACES = ["og", "book", "article", "profile", "fb", "payment"];
|
||||
const MEDIA_KEYS = ["ogImage", "ogVideo", "ogAudio", "twitterImage"];
|
||||
const HTTP_EQUIV_KEYS = ["contentType", "defaultStyle", "xUaCompatible"];
|
||||
const META_ALIASES = {
|
||||
articleExpirationTime: "article:expiration_time",
|
||||
articleModifiedTime: "article:modified_time",
|
||||
articlePublishedTime: "article:published_time",
|
||||
bookReleaseDate: "book:release_date",
|
||||
fbAppId: "fb:app_id",
|
||||
ogAudioSecureUrl: "og:audio:secure_url",
|
||||
ogAudioUrl: "og:audio",
|
||||
ogImageSecureUrl: "og:image:secure_url",
|
||||
ogImageUrl: "og:image",
|
||||
ogSiteName: "og:site_name",
|
||||
ogVideoSecureUrl: "og:video:secure_url",
|
||||
ogVideoUrl: "og:video",
|
||||
paymentExpiresAt: "payment:expires_at",
|
||||
paymentSuccessUrl: "payment:success_url",
|
||||
profileFirstName: "profile:first_name",
|
||||
profileLastName: "profile:last_name",
|
||||
profileUsername: "profile:username",
|
||||
msapplicationConfig: "msapplication-Config",
|
||||
msapplicationTileColor: "msapplication-TileColor",
|
||||
msapplicationTileImage: "msapplication-TileImage"
|
||||
};
|
||||
const CAPS_RE = /([A-Z])/g;
|
||||
const OG_TWITTER_RE = /^(?:og|twitter)/;
|
||||
const MetaPackingSchema = {
|
||||
appleItunesApp: {
|
||||
unpack: {
|
||||
entrySeparator: ", ",
|
||||
resolve: ({ key, value }) => `${fixKeyCase(key)}=${value}`
|
||||
}
|
||||
},
|
||||
refresh: {
|
||||
metaKey: "http-equiv",
|
||||
unpack: {
|
||||
entrySeparator: ";",
|
||||
resolve: ({ key, value }) => key === "seconds" ? `${value}` : void 0
|
||||
}
|
||||
},
|
||||
robots: {
|
||||
unpack: {
|
||||
entrySeparator: ", ",
|
||||
resolve: ({ key, value }) => typeof value === "boolean" ? fixKeyCase(key) : `${fixKeyCase(key)}:${value}`
|
||||
}
|
||||
},
|
||||
contentSecurityPolicy: {
|
||||
metaKey: "http-equiv",
|
||||
unpack: {
|
||||
entrySeparator: "; ",
|
||||
resolve: ({ key, value }) => `${fixKeyCase(key)} ${value}`
|
||||
}
|
||||
},
|
||||
charset: {}
|
||||
};
|
||||
function fixKeyCase(key) {
|
||||
const updated = key.replace(CAPS_RE, "-$1").toLowerCase();
|
||||
const prefixIndex = updated.indexOf("-");
|
||||
return prefixIndex === -1 ? updated : META_NAMESPACES.includes(updated.slice(0, prefixIndex)) || PROPERTY_NAMESPACES.includes(updated.slice(0, prefixIndex)) ? key.replace(CAPS_RE, ":$1").toLowerCase() : updated;
|
||||
}
|
||||
function sanitizeObject(input) {
|
||||
return Object.fromEntries(Object.entries(input).filter(([k, v]) => String(v) !== "false" && k));
|
||||
}
|
||||
function transformObject(obj) {
|
||||
return Array.isArray(obj) ? obj.map(transformObject) : !obj || typeof obj !== "object" ? obj : Object.fromEntries(Object.entries(obj).map(([k, v]) => [fixKeyCase(k), transformObject(v)]));
|
||||
}
|
||||
function unpackToString(value, options = {}) {
|
||||
const { entrySeparator = "", keyValueSeparator = "", wrapValue, resolve } = options;
|
||||
return Object.entries(value).map(([key, val]) => {
|
||||
if (resolve) {
|
||||
const resolved = resolve({ key, value: val });
|
||||
if (resolved !== void 0)
|
||||
return resolved;
|
||||
}
|
||||
const processedVal = typeof val === "object" ? unpackToString(val, options) : typeof val === "number" ? val.toString() : typeof val === "string" && wrapValue ? `${wrapValue}${val.replace(new RegExp(wrapValue, "g"), `\\${wrapValue}`)}${wrapValue}` : val;
|
||||
return `${key}${keyValueSeparator}${processedVal}`;
|
||||
}).join(entrySeparator);
|
||||
}
|
||||
function handleObjectEntry(key, value) {
|
||||
const sanitizedValue = sanitizeObject(value);
|
||||
const fixedKey = fixKeyCase(key);
|
||||
const attr = resolveMetaKeyType(fixedKey);
|
||||
if (!MetaTagsArrayable.has(fixedKey)) {
|
||||
return [{ [attr]: fixedKey, ...sanitizedValue }];
|
||||
}
|
||||
const input = Object.fromEntries(
|
||||
Object.entries(sanitizedValue).map(([k, v]) => [`${key}${k === "url" ? "" : `${k[0].toUpperCase()}${k.slice(1)}`}`, v])
|
||||
);
|
||||
return unpackMeta(input || {}).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
|
||||
}
|
||||
function resolveMetaKeyType(key) {
|
||||
if (MetaPackingSchema[key]?.metaKey === "http-equiv" || HTTP_EQUIV_KEYS.includes(key)) {
|
||||
return "http-equiv";
|
||||
}
|
||||
const fixed = fixKeyCase(key);
|
||||
const colonIndex = fixed.indexOf(":");
|
||||
return colonIndex === -1 ? "name" : PROPERTY_NAMESPACES.includes(fixed.slice(0, colonIndex)) ? "property" : "name";
|
||||
}
|
||||
function resolveMetaKeyValue(key) {
|
||||
return META_ALIASES[key] || fixKeyCase(key);
|
||||
}
|
||||
function resolvePackedMetaObjectValue(value, key) {
|
||||
if (key === "refresh")
|
||||
return `${value.seconds};url=${value.url}`;
|
||||
return unpackToString(transformObject(value), {
|
||||
keyValueSeparator: "=",
|
||||
entrySeparator: ", ",
|
||||
resolve: ({ value: value2, key: key2 }) => value2 === null ? "" : typeof value2 === "boolean" ? key2 : void 0,
|
||||
...MetaPackingSchema[key]?.unpack
|
||||
});
|
||||
}
|
||||
function unpackMeta(input) {
|
||||
const extras = [];
|
||||
const primitives = {};
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
if (Array.isArray(value)) {
|
||||
if (key === "themeColor") {
|
||||
value.forEach((v) => {
|
||||
if (typeof v === "object" && v !== null) {
|
||||
extras.push({ name: "theme-color", ...v });
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
for (const v of value) {
|
||||
if (typeof v === "object" && v !== null) {
|
||||
const urlProps = [];
|
||||
const otherProps = [];
|
||||
for (const [propKey, propValue] of Object.entries(v)) {
|
||||
const metaKey = `${key}${propKey === "url" ? "" : `:${propKey}`}`;
|
||||
const meta2 = unpackMeta({ [metaKey]: propValue });
|
||||
(propKey === "url" ? urlProps : otherProps).push(...meta2);
|
||||
}
|
||||
extras.push(...urlProps, ...otherProps);
|
||||
} else {
|
||||
extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (typeof value === "object" && value) {
|
||||
if (MEDIA_KEYS.includes(key)) {
|
||||
const prefix = key.startsWith("twitter") ? "twitter" : "og";
|
||||
const type = key.replace(OG_TWITTER_RE, "").toLowerCase();
|
||||
const metaKey = prefix === "twitter" ? "name" : "property";
|
||||
if (value.url) {
|
||||
extras.push({
|
||||
[metaKey]: `${prefix}:${type}`,
|
||||
content: value.url
|
||||
});
|
||||
}
|
||||
if (value.secureUrl) {
|
||||
extras.push({
|
||||
[metaKey]: `${prefix}:${type}:secure_url`,
|
||||
content: value.secureUrl
|
||||
});
|
||||
}
|
||||
for (const [propKey, propValue] of Object.entries(value)) {
|
||||
if (propKey !== "url" && propKey !== "secureUrl") {
|
||||
extras.push({
|
||||
[metaKey]: `${prefix}:${type}:${propKey}`,
|
||||
content: propValue
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (MetaTagsArrayable.has(fixKeyCase(key))) {
|
||||
extras.push(...handleObjectEntry(key, value));
|
||||
} else {
|
||||
primitives[key] = sanitizeObject(value);
|
||||
}
|
||||
} else {
|
||||
primitives[key] = value;
|
||||
}
|
||||
}
|
||||
const meta = Object.entries(primitives).map(([key, value]) => {
|
||||
if (key === "charset")
|
||||
return { charset: value === null ? "_null" : value };
|
||||
const metaKey = resolveMetaKeyType(key);
|
||||
const keyValue = resolveMetaKeyValue(key);
|
||||
const processedValue = value === null ? "_null" : typeof value === "object" ? resolvePackedMetaObjectValue(value, key) : typeof value === "number" ? value.toString() : value;
|
||||
return metaKey === "http-equiv" ? { "http-equiv": keyValue, "content": processedValue } : { [metaKey]: keyValue, content: processedValue };
|
||||
});
|
||||
return [...extras, ...meta].map(
|
||||
(m) => !("content" in m) ? m : m.content === "_null" ? { ...m, content: null } : m
|
||||
);
|
||||
}
|
||||
|
||||
export { resolveMetaKeyValue as a, resolvePackedMetaObjectValue as b, resolveMetaKeyType as r, unpackMeta as u };
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
function hasOwn(object, key) {
|
||||
return hasOwnProperty.call(object, key);
|
||||
}
|
||||
|
||||
export { hasOwn as h };
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
import { H as HasElementTags } from './unhead.CGPOfp5O.mjs';
|
||||
import { r as resolveTags, i as isMetaArrayDupeKey, b as normalizeProps, d as dedupeKey, h as hashTag } from './unhead.-hZVKou0.mjs';
|
||||
import { a as callHook } from './unhead.Bm4Y6XQI.mjs';
|
||||
|
||||
const WHITESPACE_RE = /\s+/;
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function createDomRenderer(options = {}) {
|
||||
return (head) => _renderDOMHead(head, options);
|
||||
}
|
||||
function renderDOMHead(head, options = {}) {
|
||||
return _renderDOMHead(head, options);
|
||||
}
|
||||
function hasPendingEntries(head) {
|
||||
for (const entry of head.entries.values()) {
|
||||
if (entry._pending !== void 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function createDomState(head, dom) {
|
||||
const state = { _d: dom, _t: dom.title, _e: /* @__PURE__ */ new Map([["htmlAttrs", dom.documentElement], ["bodyAttrs", dom.body]]), _p: {}, _s: {}, _l: /* @__PURE__ */ new Map() };
|
||||
for (const entry of head.entries.values()) {
|
||||
if (entry._o !== void 0) {
|
||||
const orig = entry._o;
|
||||
for (const t of ["bodyAttrs", "htmlAttrs"]) {
|
||||
const cls = orig[t]?.class;
|
||||
if (typeof cls === "string") {
|
||||
const $el = state._e.get(t);
|
||||
for (const c of cls.split(WHITESPACE_RE)) {
|
||||
if (c)
|
||||
state._p[`${t}:attr:class:${c}`] = () => $el.classList.remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
function _renderDOMHead(head, options = {}) {
|
||||
const dom = options.document || head.resolvedOptions.document;
|
||||
const activeState = head._dom;
|
||||
if (!dom || head._du || activeState && activeState._d === dom && !head.dirty && !hasPendingEntries(head))
|
||||
return false;
|
||||
const defaultView = dom.defaultView;
|
||||
head._du = true;
|
||||
let didRender = false;
|
||||
try {
|
||||
let track = function(key, fn, fresh) {
|
||||
renderState._s[key] = !fresh && previous[key] || fn;
|
||||
delete previous[key];
|
||||
}, trackEvent = function(id, k, ev, source, $el, target) {
|
||||
const key = `${id}:event:${k}`;
|
||||
const prev = renderState._l.get(key);
|
||||
if (prev && prev[0] === target && prev[1] === ev && prev[2] === source) {
|
||||
track(key, prev[4]);
|
||||
return;
|
||||
}
|
||||
prev?.[4]();
|
||||
const dk = `data-${k}`;
|
||||
const handler = ((e) => source.call($el, e));
|
||||
const cleanup = () => {
|
||||
target.removeEventListener(ev, handler);
|
||||
if ($el.getAttribute(dk) === "")
|
||||
$el.removeAttribute(dk);
|
||||
if (renderState._l.get(key)?.[3] === handler)
|
||||
renderState._l.delete(key);
|
||||
};
|
||||
target.addEventListener(ev, handler);
|
||||
renderState._l.set(key, [target, ev, source, handler, cleanup]);
|
||||
$el.setAttribute(dk, "");
|
||||
track(key, cleanup, true);
|
||||
}, trackCtx = function({ id, $el, tag }) {
|
||||
renderState._e.set(id, $el);
|
||||
if (!tag.tag.endsWith("Attrs")) {
|
||||
const text = tag.textContent;
|
||||
if (text != null && text !== "") {
|
||||
if (text !== $el.textContent)
|
||||
$el.textContent = text;
|
||||
track(`${id}:text`, () => {
|
||||
if ($el.textContent === text)
|
||||
$el.textContent = "";
|
||||
}, true);
|
||||
}
|
||||
const html = tag.innerHTML;
|
||||
if (html != null && html !== "") {
|
||||
if (html !== $el.innerHTML)
|
||||
$el.innerHTML = html;
|
||||
track(`${id}:html`, () => {
|
||||
if ($el.innerHTML === html)
|
||||
$el.innerHTML = "";
|
||||
}, true);
|
||||
}
|
||||
const elKey = `${id}:el`;
|
||||
track(elKey, previous[elKey] || (() => {
|
||||
$el?.remove();
|
||||
renderState._e.delete(id);
|
||||
}));
|
||||
}
|
||||
for (const k in tag.props) {
|
||||
const v = tag.props[k];
|
||||
if (k[0] === "o" && k[1] === "n" && typeof v === "function") {
|
||||
const ev = k.slice(2);
|
||||
if ($el?.dataset?.[`${k}fired`])
|
||||
v.call($el, new (defaultView?.Event || Event)(ev));
|
||||
trackEvent(id, k, ev, v, $el, tag.tag === "bodyAttrs" && defaultView ? defaultView : $el);
|
||||
continue;
|
||||
}
|
||||
const ck = `${id}:attr:${k}`;
|
||||
if (k === "class" && v) {
|
||||
for (const c of v) {
|
||||
const key = `${ck}:${c}`;
|
||||
track(key, previous[key] || (() => $el.classList.remove(c)));
|
||||
if (!$el.classList.contains(c))
|
||||
$el.classList.add(c);
|
||||
}
|
||||
} else if (k === "style" && v) {
|
||||
for (const [sk, sv] of v) {
|
||||
const key = `${ck}:${sk}`;
|
||||
track(key, previous[key] || (() => $el.style.removeProperty(sk)));
|
||||
$el.style.setProperty(sk, sv);
|
||||
}
|
||||
} else if (v !== false && v !== null) {
|
||||
if ($el.getAttribute(k) !== v)
|
||||
$el.setAttribute(k, v === true ? "" : String(v));
|
||||
track(ck, previous[ck] || (() => $el.removeAttribute(k)));
|
||||
}
|
||||
}
|
||||
};
|
||||
const beforeRenderCtx = { shouldRender: true, tags: [] };
|
||||
callHook(head, "dom:beforeRender", beforeRenderCtx);
|
||||
if (!beforeRenderCtx.shouldRender)
|
||||
return false;
|
||||
let state = activeState;
|
||||
if (state?._d !== dom) {
|
||||
if (state) {
|
||||
for (const k in state._s) state._s[k]();
|
||||
for (const k in state._p) state._p[k]();
|
||||
state._s = state._p = {};
|
||||
state._e.clear();
|
||||
state._l.clear();
|
||||
}
|
||||
state = void 0;
|
||||
}
|
||||
if (!state) {
|
||||
state = createDomState(head, dom);
|
||||
} else {
|
||||
state._p = state._s;
|
||||
}
|
||||
state._s = {};
|
||||
const renderState = state;
|
||||
const previous = renderState._p;
|
||||
const pending = [];
|
||||
const frag = {};
|
||||
head.dirty = false;
|
||||
const rawTags = resolveTags(head, options.tagWeight ? { tagWeight: options.tagWeight } : void 0);
|
||||
const tags = [];
|
||||
const dupeKeyCounter = {};
|
||||
for (const tag of rawTags) {
|
||||
const count = dupeKeyCounter[tag._d] || 0;
|
||||
const id = (count ? `${tag._d}:${count}` : tag._d) || tag._h;
|
||||
const ctx = { tag, id, shouldRender: true };
|
||||
if (tag.tag === "meta" && tag._d && isMetaArrayDupeKey(tag._d))
|
||||
dupeKeyCounter[tag._d] = count + 1;
|
||||
tags.push(ctx);
|
||||
if (tag.tag === "title") {
|
||||
dom.title = tag.textContent;
|
||||
track("title:", () => dom.title = renderState._t);
|
||||
continue;
|
||||
}
|
||||
ctx.$el = renderState._e.get(id);
|
||||
if (ctx.$el)
|
||||
trackCtx(ctx);
|
||||
else if (HasElementTags.has(tag.tag))
|
||||
pending.push(ctx);
|
||||
}
|
||||
if (pending.length) {
|
||||
const tracked = new Set(renderState._e.values());
|
||||
for (const el of [...dom.body.children, ...dom.head.children]) {
|
||||
const elTag = el.tagName.toLowerCase();
|
||||
if (!HasElementTags.has(elTag) || tracked.has(el))
|
||||
continue;
|
||||
const props = { innerHTML: el.innerHTML };
|
||||
for (const n of el.getAttributeNames())
|
||||
props[n] = el.getAttribute(n);
|
||||
const next = normalizeProps({ tag: elTag, props: {} }, props);
|
||||
next.key = el.getAttribute("data-hid") || void 0;
|
||||
const dedupe = dedupeKey(next) || hashTag(next);
|
||||
let k = dedupe;
|
||||
let c = 1;
|
||||
while (renderState._e.has(k))
|
||||
k = `${dedupe}:${c++}`;
|
||||
renderState._e.set(k, el);
|
||||
}
|
||||
}
|
||||
for (const ctx of pending) {
|
||||
const found = renderState._e.get(ctx.id);
|
||||
ctx.$el = found || dom.createElement(ctx.tag.tag);
|
||||
trackCtx(ctx);
|
||||
if (!found)
|
||||
(frag[ctx.tag.tagPosition || "head"] ??= dom.createDocumentFragment()).appendChild(ctx.$el);
|
||||
}
|
||||
if (frag.head)
|
||||
dom.head.appendChild(frag.head);
|
||||
if (frag.bodyOpen)
|
||||
dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild);
|
||||
if (frag.bodyClose)
|
||||
dom.body.appendChild(frag.bodyClose);
|
||||
for (const k in previous)
|
||||
previous[k]();
|
||||
head._dom = renderState;
|
||||
didRender = true;
|
||||
callHook(head, "dom:rendered", { renders: tags });
|
||||
} catch (e) {
|
||||
head.dirty = true;
|
||||
throw e;
|
||||
} finally {
|
||||
head._du = false;
|
||||
}
|
||||
if (didRender && (head.dirty || hasPendingEntries(head)))
|
||||
_renderDOMHead(head, options);
|
||||
return didRender;
|
||||
}
|
||||
|
||||
export { createDomRenderer as c, renderDOMHead as r };
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { HookableCore } from 'hookable';
|
||||
|
||||
function createHooks(hooks) {
|
||||
const instance = new HookableCore();
|
||||
for (const key in hooks || {}) {
|
||||
instance.hook(key, hooks[key]);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
function callHook(head, hook, ctx) {
|
||||
const hooks = head.hooks?._hooks?.[hook];
|
||||
if (!hooks?.length)
|
||||
return;
|
||||
return head.hooks?.callHook(hook, ctx);
|
||||
}
|
||||
|
||||
export { callHook as a, createHooks as c };
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { r as registerPlugin } from './unhead.D7HkBzZn.mjs';
|
||||
|
||||
function createClientHeadAdapter(core, hooks, render) {
|
||||
const corePush = core.push;
|
||||
const head = core;
|
||||
head.ssr = false;
|
||||
head.hooks = hooks;
|
||||
head.dirty = !!head.dirty;
|
||||
head.use = (p) => registerPlugin(head, p);
|
||||
head.render = () => render(head);
|
||||
function notify() {
|
||||
hooks.callHook("entries:updated", head);
|
||||
if (!head._b)
|
||||
head.render();
|
||||
}
|
||||
head.invalidate = () => {
|
||||
for (const entry of head.entries.values())
|
||||
delete entry._tags;
|
||||
head.dirty = true;
|
||||
notify();
|
||||
};
|
||||
head.push = (input, entryOptions) => {
|
||||
const unhook = entryOptions?.onRendered ? hooks.hook("dom:rendered", entryOptions.onRendered) : void 0;
|
||||
const active = corePush(input, entryOptions);
|
||||
const entry = core.entries.get(active._i);
|
||||
if (entry)
|
||||
entry._o = input;
|
||||
head.dirty = true;
|
||||
notify();
|
||||
return {
|
||||
_i: active._i,
|
||||
patch(input2) {
|
||||
active.patch(input2);
|
||||
head.dirty = true;
|
||||
notify();
|
||||
},
|
||||
dispose() {
|
||||
unhook?.();
|
||||
if (core.entries.has(active._i)) {
|
||||
active.dispose();
|
||||
head.invalidate();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
return head;
|
||||
}
|
||||
|
||||
export { createClientHeadAdapter as c };
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
|
||||
const DupeableTags = /* @__PURE__ */ new Set(["link", "style", "script", "noscript"]);
|
||||
const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
|
||||
const HasElementTags = /* @__PURE__ */ new Set(["base", "meta", "link", "style", "script", "noscript"]);
|
||||
const ValidHeadTags = /* @__PURE__ */ new Set(["title", "base", "htmlAttrs", "bodyAttrs", "meta", "link", "style", "script", "noscript"]);
|
||||
const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
|
||||
const TagConfigKeys = /* @__PURE__ */ new Set(["key", "tagPosition", "tagPriority", "tagDuplicateStrategy", "innerHTML", "textContent", "processTemplateParams"]);
|
||||
const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
|
||||
const UsesMergeStrategy = /* @__PURE__ */ new Set(["templateParams", "htmlAttrs", "bodyAttrs"]);
|
||||
const MetaTagsArrayable = /* @__PURE__ */ new Set([
|
||||
"theme-color",
|
||||
"google-site-verification",
|
||||
"author",
|
||||
"og:locale:alternate",
|
||||
"og:image",
|
||||
"og:video",
|
||||
"og:audio",
|
||||
"article:author",
|
||||
"article:tag",
|
||||
"book:author",
|
||||
"book:tag",
|
||||
"twitter:image"
|
||||
]);
|
||||
const TagPriorityAliases = { critical: -8, high: -1, low: 2 };
|
||||
const hasContent = (value) => typeof value === "number" ? Number.isFinite(value) : value;
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function isUnsafeKey(key) {
|
||||
return key === "__proto__" || key === "constructor" || key === "prototype";
|
||||
}
|
||||
|
||||
export { DupeableTags as D, HasElementTags as H, MetaTagsArrayable as M, ScriptNetworkEvents as S, TagConfigKeys as T, UniqueTags as U, ValidHeadTags as V, SelfClosingTags as a, TagPriorityAliases as b, TagsWithInnerContent as c, UsesMergeStrategy as d, hasContent as h, isUnsafeKey as i };
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
function defineHeadPlugin(plugin, key) {
|
||||
if (key && typeof plugin === "function")
|
||||
plugin.key = key;
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export { defineHeadPlugin as d };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
const VALID_STREAM_KEY_RE = /^[$_a-z][$\w]*$/i;
|
||||
function parseStreamKey(streamKey) {
|
||||
if (typeof streamKey !== "string" || !VALID_STREAM_KEY_RE.test(streamKey)) {
|
||||
throw new Error(
|
||||
`[unhead] Invalid streamKey: must be a valid JavaScript identifier matching ${VALID_STREAM_KEY_RE}. Received: ${JSON.stringify(streamKey)}`
|
||||
);
|
||||
}
|
||||
return streamKey;
|
||||
}
|
||||
|
||||
export { parseStreamKey as p };
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
function registerPlugin(head, p) {
|
||||
if (typeof p === "function" && p.key && head.plugins.has(p.key))
|
||||
return;
|
||||
const plugin = typeof p === "function" ? p(head) : p;
|
||||
const key = plugin.key || String(head.plugins.size + 1);
|
||||
if (!head.plugins.get(key)) {
|
||||
head.plugins.set(key, plugin);
|
||||
for (const k in plugin.hooks || {})
|
||||
head.hooks?.hook(k, plugin.hooks[k]);
|
||||
}
|
||||
}
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function createUnhead(renderer, resolvedOptions = {}) {
|
||||
const ssr = !resolvedOptions.document;
|
||||
const entries = /* @__PURE__ */ new Map();
|
||||
const plugins = /* @__PURE__ */ new Map();
|
||||
const head = {
|
||||
_entryCount: 1,
|
||||
_h: 0,
|
||||
plugins,
|
||||
resolvedOptions,
|
||||
ssr,
|
||||
entries,
|
||||
// adapters decorate this same object; reserve the slot to keep its shape stable
|
||||
hooks: void 0,
|
||||
render: () => renderer(head),
|
||||
use: (p) => registerPlugin(head, p),
|
||||
push(input, _options) {
|
||||
const _i = _options?._index ?? head._entryCount++;
|
||||
const options = _options ? { ..._options } : {};
|
||||
delete options.head;
|
||||
delete options.onRendered;
|
||||
delete options._index;
|
||||
const entry = { _i, input, options };
|
||||
entries.set(_i, entry);
|
||||
const active = {
|
||||
_i,
|
||||
dispose() {
|
||||
entries.delete(_i);
|
||||
},
|
||||
patch(input2) {
|
||||
if (ssr) {
|
||||
entry.input = input2;
|
||||
delete entry._tags;
|
||||
} else {
|
||||
entry._pending = input2;
|
||||
}
|
||||
if (!entries.has(_i))
|
||||
entries.set(_i, entry);
|
||||
}
|
||||
};
|
||||
return active;
|
||||
}
|
||||
};
|
||||
resolvedOptions.init?.forEach((e) => e && head.push(e));
|
||||
return head;
|
||||
}
|
||||
|
||||
export { createUnhead as c, registerPlugin as r };
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import { c as createUnhead } from './unhead.D7HkBzZn.mjs';
|
||||
import { I as INVALID_ATTR_NAME_RE, r as resolveTags, n as normalizeEntryToTags, d as dedupeKey, h as hashTag } from './unhead.-hZVKou0.mjs';
|
||||
import { a as callHook, c as createHooks } from './unhead.Bm4Y6XQI.mjs';
|
||||
import { b as TagPriorityAliases, a as SelfClosingTags, c as TagsWithInnerContent } from './unhead.CGPOfp5O.mjs';
|
||||
|
||||
const isTruthy = (v) => v === "" || v === true;
|
||||
function capoTagWeight(tag) {
|
||||
if (typeof tag.tagPriority === "number")
|
||||
return tag.tagPriority;
|
||||
let weight = 100;
|
||||
const offset = TagPriorityAliases[tag.tagPriority] || 0;
|
||||
if (tag.tag === "base") {
|
||||
weight = -10;
|
||||
} else if (tag.tag === "title") {
|
||||
weight = 10;
|
||||
} else if (tag.tag === "meta") {
|
||||
weight = tag.props["http-equiv"] === "content-security-policy" ? -30 : tag.props.charset ? -20 : tag.props.name === "viewport" ? -15 : weight;
|
||||
} else if (tag.tag === "link" && tag.props.rel) {
|
||||
const rel = tag.props.rel;
|
||||
weight = rel === "preconnect" ? 20 : rel === "stylesheet" ? 60 : rel === "preload" || rel === "modulepreload" ? 70 : rel === "prefetch" || rel === "dns-prefetch" || rel === "prerender" ? 90 : weight;
|
||||
} else if (tag.tag === "script") {
|
||||
const type = typeof tag.props.type === "string" ? tag.props.type : "";
|
||||
const json = type.endsWith("json");
|
||||
if (type === "importmap")
|
||||
weight = 25;
|
||||
else if (type === "speculationrules")
|
||||
weight = 90;
|
||||
else if (isTruthy(tag.props.async))
|
||||
weight = 30;
|
||||
else if (tag.props.src && !isTruthy(tag.props.defer) && type !== "module" && !json || (tag.innerHTML || tag.textContent) && !json)
|
||||
weight = 50;
|
||||
else if (isTruthy(tag.props.defer) && tag.props.src || type === "module")
|
||||
weight = 80;
|
||||
} else if (tag.tag === "style") {
|
||||
weight = tag.innerHTML && /@import/.test(tag.innerHTML) ? 40 : 60;
|
||||
}
|
||||
return (weight || 100) + offset;
|
||||
}
|
||||
|
||||
const DOUBLE_QUOTE_RE = /"/g;
|
||||
function encodeAttribute(value) {
|
||||
const s = typeof value === "string" ? value : String(value);
|
||||
return s.includes('"') ? s.replace(DOUBLE_QUOTE_RE, """) : s;
|
||||
}
|
||||
function propsToString(props) {
|
||||
let attrs = "";
|
||||
for (const key in props) {
|
||||
if (!Object.hasOwn(props, key) || !key || INVALID_ATTR_NAME_RE.test(key))
|
||||
continue;
|
||||
let value = props[key];
|
||||
if (typeof value !== "string") {
|
||||
if (key === "class") {
|
||||
let out = "";
|
||||
for (const c of value) out += out ? ` ${c}` : c;
|
||||
value = out;
|
||||
} else if (key === "style") {
|
||||
let out = "";
|
||||
for (const [k, v] of value) out += out ? `;${k}:${v}` : `${k}:${v}`;
|
||||
value = out;
|
||||
}
|
||||
}
|
||||
if (value !== false && value !== null) {
|
||||
attrs += value === true ? ` ${key}` : ` ${key}="${encodeAttribute(value)}"`;
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
const ESCAPE_HTML_RE = /[&<>"'/]/g;
|
||||
const CLOSE_TAG_RE = {};
|
||||
const ESCAPE_HTML_MAP = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" };
|
||||
function escapeHtml(str) {
|
||||
return str.replace(ESCAPE_HTML_RE, (c) => ESCAPE_HTML_MAP[c]);
|
||||
}
|
||||
function tagToString(tag) {
|
||||
const attrs = propsToString(tag.props);
|
||||
const openTag = `<${tag.tag}${attrs}>`;
|
||||
if (SelfClosingTags.has(tag.tag))
|
||||
return openTag;
|
||||
if (!TagsWithInnerContent.has(tag.tag))
|
||||
return `${openTag}</${tag.tag}>`;
|
||||
let content = String(tag.textContent ?? tag.innerHTML ?? "");
|
||||
content = tag.tag === "title" ? escapeHtml(content) : content.replace(CLOSE_TAG_RE[tag.tag] ||= new RegExp(`</${tag.tag}`, "gi"), `<\\/${tag.tag}`);
|
||||
return `${openTag}${content}</${tag.tag}>`;
|
||||
}
|
||||
|
||||
function ssrRenderTags(tags, options) {
|
||||
const schema = { htmlAttrs: {}, bodyAttrs: {}, tags: { head: "", bodyClose: "", bodyOpen: "" } };
|
||||
const lineBreaks = !options?.omitLineBreaks ? "\n" : "";
|
||||
for (const tag of tags) {
|
||||
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
||||
Object.assign(schema[tag.tag], tag.props);
|
||||
continue;
|
||||
}
|
||||
const s = tagToString(tag);
|
||||
const tagPosition = tag.tagPosition || "head";
|
||||
schema.tags[tagPosition] += schema.tags[tagPosition] ? `${lineBreaks}${s}` : s;
|
||||
}
|
||||
return {
|
||||
headTags: schema.tags.head,
|
||||
bodyTags: schema.tags.bodyClose,
|
||||
bodyTagsOpen: schema.tags.bodyOpen,
|
||||
htmlAttrs: propsToString(schema.htmlAttrs),
|
||||
bodyAttrs: propsToString(schema.bodyAttrs)
|
||||
};
|
||||
}
|
||||
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function createServerRenderer(options = {}) {
|
||||
return (head) => {
|
||||
const beforeRenderCtx = { shouldRender: true };
|
||||
callHook(head, "ssr:beforeRender", beforeRenderCtx);
|
||||
if (!beforeRenderCtx.shouldRender)
|
||||
return ssrRenderTags([]);
|
||||
const ctx = {
|
||||
tags: options.resolvedTags || resolveTags(head, { tagWeight: options.tagWeight ?? capoTagWeight }),
|
||||
options: { ...options }
|
||||
};
|
||||
callHook(head, "ssr:render", ctx);
|
||||
const html = ssrRenderTags(ctx.tags, ctx.options);
|
||||
const renderCtx = { tags: ctx.tags, html };
|
||||
callHook(head, "ssr:rendered", renderCtx);
|
||||
return renderCtx.html;
|
||||
};
|
||||
}
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function renderSSRHead(head, options) {
|
||||
return (/* @__PURE__ */ createServerRenderer(options))(head);
|
||||
}
|
||||
|
||||
const DEFAULT_INIT = {
|
||||
htmlAttrs: {
|
||||
lang: "en"
|
||||
},
|
||||
meta: [
|
||||
{
|
||||
charset: "utf-8"
|
||||
},
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1"
|
||||
}
|
||||
]
|
||||
};
|
||||
const serverPropResolver = /* @__PURE__ */ Object.assign(
|
||||
(k, v) => {
|
||||
if (k && k.startsWith("on") && typeof v === "function") {
|
||||
return `this.dataset.${k}fired = true`;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
{ _static: true }
|
||||
);
|
||||
let defaultInitTags;
|
||||
function getDefaultInitTags() {
|
||||
if (!defaultInitTags) {
|
||||
defaultInitTags = normalizeEntryToTags(DEFAULT_INIT, []);
|
||||
for (let i = 0; i < defaultInitTags.length; i++) {
|
||||
const t = defaultInitTags[i];
|
||||
t._w = capoTagWeight(t);
|
||||
t._p = (1 << 10) + i;
|
||||
t._d = dedupeKey(t);
|
||||
if (!t._d)
|
||||
t._h = hashTag(t);
|
||||
}
|
||||
}
|
||||
return defaultInitTags;
|
||||
}
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function createHead(options = {}) {
|
||||
const tagWeight = options.tagWeight || capoTagWeight;
|
||||
const core = createUnhead(createServerRenderer({ tagWeight, omitLineBreaks: options.omitLineBreaks }), {
|
||||
_tagWeight: tagWeight,
|
||||
// @ts-expect-error untyped
|
||||
document: false,
|
||||
experimentalStreamKey: options.experimentalStreamKey,
|
||||
propResolvers: [
|
||||
...options.propResolvers || [],
|
||||
serverPropResolver
|
||||
],
|
||||
init: [
|
||||
options.disableDefaults ? void 0 : DEFAULT_INIT,
|
||||
...options.init || []
|
||||
]
|
||||
});
|
||||
if (!options.disableDefaults && !options.tagWeight && !options.propResolvers?.some((r) => !r._static)) {
|
||||
const defaultEntry = core.entries.get(1);
|
||||
if (defaultEntry)
|
||||
defaultEntry._precomputedTags = getDefaultInitTags();
|
||||
}
|
||||
core.hooks = createHooks(options.hooks);
|
||||
options.plugins?.forEach((p) => core.use(p));
|
||||
return core;
|
||||
}
|
||||
|
||||
export { capoTagWeight as a, createServerRenderer as b, createHead as c, escapeHtml as e, propsToString as p, renderSSRHead as r, ssrRenderTags as s, tagToString as t };
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
import { u as unpackMeta } from './unhead.BQWSw36o.mjs';
|
||||
import { d as defineHeadPlugin } from './unhead.CHEy9ana.mjs';
|
||||
import { h as hasContent, i as isUnsafeKey } from './unhead.CGPOfp5O.mjs';
|
||||
|
||||
const FlatMetaPlugin = /* @__PURE__ */ defineHeadPlugin({
|
||||
key: "flatMeta",
|
||||
hooks: {
|
||||
"entries:normalize": (ctx) => {
|
||||
let hasFlatMeta = false;
|
||||
const tags = [];
|
||||
const tagsToAdd = [];
|
||||
for (const t of ctx.tags) {
|
||||
if (t.tag !== "_flatMeta") {
|
||||
tags.push(t);
|
||||
continue;
|
||||
}
|
||||
hasFlatMeta = true;
|
||||
for (const props of unpackMeta(t.props))
|
||||
tagsToAdd.push({ ...t, tag: "meta", props });
|
||||
}
|
||||
if (!hasFlatMeta)
|
||||
return;
|
||||
for (const tag of tagsToAdd) tags.push(tag);
|
||||
ctx.tags = tags;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const WhitelistAttributes = {
|
||||
htmlAttrs: /* @__PURE__ */ new Set(["class", "style", "lang", "dir"]),
|
||||
bodyAttrs: /* @__PURE__ */ new Set(["class", "style"]),
|
||||
meta: /* @__PURE__ */ new Set(["name", "property", "charset", "content", "media"]),
|
||||
noscript: /* @__PURE__ */ new Set([]),
|
||||
style: /* @__PURE__ */ new Set(["media", "nonce", "title", "blocking"]),
|
||||
script: /* @__PURE__ */ new Set(["type", "textContent", "nonce", "blocking"]),
|
||||
link: /* @__PURE__ */ new Set(["color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"])
|
||||
};
|
||||
const BlockedLinkRels = /* @__PURE__ */ new Set(["canonical", "modulepreload", "prerender", "preload", "prefetch", "dns-prefetch", "preconnect", "manifest", "pingback"]);
|
||||
const SafeDataAttrName = /^[a-z][a-z0-9-]*[a-z0-9]$/i;
|
||||
const AsciiWhitespace = /[\t\n\f\r ]+/;
|
||||
const HtmlEntityHex = /&#x([0-9a-f]+);?/gi;
|
||||
const HtmlEntityDec = /&#(\d+);?/g;
|
||||
const HtmlEntityNamed = /&(tab|newline|colon|semi|lpar|rpar|sol|bsol|comma|period|excl|num|dollar|percnt|amp|apos|ast|plus|lt|gt|equals|quest|at|lsqb|rsqb|lcub|rcub|vert|hat|grave|tilde|nbsp);?/gi;
|
||||
const ControlChars = /[\x00-\x20]+/g;
|
||||
const NamedEntityMap = {
|
||||
tab: " ",
|
||||
newline: "\n",
|
||||
colon: ":",
|
||||
semi: ";",
|
||||
lpar: "(",
|
||||
rpar: ")",
|
||||
sol: "/",
|
||||
bsol: "\\",
|
||||
comma: ",",
|
||||
period: ".",
|
||||
excl: "!",
|
||||
num: "#",
|
||||
dollar: "$",
|
||||
percnt: "%",
|
||||
amp: "&",
|
||||
apos: "'",
|
||||
ast: "*",
|
||||
plus: "+",
|
||||
lt: "<",
|
||||
gt: ">",
|
||||
equals: "=",
|
||||
quest: "?",
|
||||
at: "@",
|
||||
lsqb: "[",
|
||||
rsqb: "]",
|
||||
lcub: "{",
|
||||
rcub: "}",
|
||||
vert: "|",
|
||||
hat: "^",
|
||||
grave: "`",
|
||||
tilde: "~",
|
||||
nbsp: "\xA0"
|
||||
};
|
||||
function safeFromCodePoint(codePoint) {
|
||||
if (codePoint > 1114111 || codePoint < 0 || Number.isNaN(codePoint))
|
||||
return "";
|
||||
return String.fromCodePoint(codePoint);
|
||||
}
|
||||
function decodeHtmlEntities(str) {
|
||||
return str.replace(HtmlEntityHex, (_, hex) => safeFromCodePoint(Number.parseInt(hex, 16))).replace(HtmlEntityDec, (_, dec) => safeFromCodePoint(Number(dec))).replace(HtmlEntityNamed, (_, name) => NamedEntityMap[name.toLowerCase()] || "");
|
||||
}
|
||||
function hasDangerousProtocol(url) {
|
||||
const entityDecoded = decodeHtmlEntities(url);
|
||||
const cleaned = entityDecoded.replace(ControlChars, "");
|
||||
let decoded;
|
||||
try {
|
||||
decoded = decodeURIComponent(cleaned);
|
||||
} catch {
|
||||
decoded = cleaned;
|
||||
}
|
||||
const sanitized = decoded.replace(ControlChars, "");
|
||||
const lower = sanitized.toLowerCase();
|
||||
return lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:");
|
||||
}
|
||||
function stripProtoKeys(obj) {
|
||||
if (Array.isArray(obj))
|
||||
return obj.map(stripProtoKeys);
|
||||
if (obj && typeof obj === "object") {
|
||||
const clean = {};
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (isUnsafeKey(key))
|
||||
continue;
|
||||
clean[key] = stripProtoKeys(obj[key]);
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
function acceptDataAttrs(value, allowId = true) {
|
||||
const next = {};
|
||||
if (!value)
|
||||
return next;
|
||||
const keys = Object.keys(value);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const isData = key.startsWith("data-");
|
||||
if ((isData || allowId && key === "id") && SafeDataAttrName.test(key))
|
||||
next[key] = value[key];
|
||||
}
|
||||
return next;
|
||||
}
|
||||
function hasBlockedRel(rel) {
|
||||
const tokens = rel.split(AsciiWhitespace);
|
||||
return !tokens.some(Boolean) || tokens.some((token) => BlockedLinkRels.has(token.toLowerCase()));
|
||||
}
|
||||
function makeTagSafe(tag) {
|
||||
let next = {};
|
||||
const { tag: type, props: prev } = tag;
|
||||
switch (type) {
|
||||
// title: textContent is escaped in rendering (tagToString), no props needed
|
||||
case "title":
|
||||
break;
|
||||
// virtual tags, not rendered to HTML — but sanitize to prevent injection if rendered
|
||||
case "titleTemplate":
|
||||
case "templateParams":
|
||||
next = prev;
|
||||
break;
|
||||
case "htmlAttrs":
|
||||
case "bodyAttrs":
|
||||
WhitelistAttributes[type].forEach((attr) => {
|
||||
if (prev[attr]) {
|
||||
next[attr] = prev[attr];
|
||||
}
|
||||
});
|
||||
delete tag.innerHTML;
|
||||
delete tag.textContent;
|
||||
tag.props = { ...acceptDataAttrs(prev, false), ...next };
|
||||
return !Object.keys(tag.props).length ? false : tag;
|
||||
case "style":
|
||||
next = acceptDataAttrs(prev);
|
||||
WhitelistAttributes.style.forEach((key) => {
|
||||
if (prev[key]) {
|
||||
next[key] = prev[key];
|
||||
}
|
||||
});
|
||||
break;
|
||||
// meta is safe, except for http-equiv
|
||||
case "meta":
|
||||
WhitelistAttributes.meta.forEach((key) => {
|
||||
if (hasContent(prev[key])) {
|
||||
next[key] = prev[key];
|
||||
}
|
||||
});
|
||||
break;
|
||||
// link tags we block preloading, prerendering, prefetching, dns-prefetch, preconnect, manifest, etc
|
||||
case "link":
|
||||
WhitelistAttributes.link.forEach((key) => {
|
||||
const val = prev[key];
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
if (key === "rel" && (typeof val !== "string" || hasBlockedRel(val))) {
|
||||
return;
|
||||
}
|
||||
if (key === "href" || key === "imagesrcset") {
|
||||
if (typeof val !== "string") {
|
||||
return;
|
||||
}
|
||||
const urls = key === "imagesrcset" ? val.split(",").map((s) => s.trim()) : [val];
|
||||
if (urls.some((u) => hasDangerousProtocol(u))) {
|
||||
return;
|
||||
}
|
||||
next[key] = val;
|
||||
} else if (val) {
|
||||
next[key] = val;
|
||||
}
|
||||
});
|
||||
if (!next.href && !next.imagesrcset || !next.rel) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "noscript":
|
||||
WhitelistAttributes.noscript.forEach((key) => {
|
||||
if (prev[key]) {
|
||||
next[key] = prev[key];
|
||||
}
|
||||
});
|
||||
break;
|
||||
// we only allow JSON in scripts
|
||||
case "script":
|
||||
if (!tag.textContent || typeof prev.type !== "string" || !prev.type.endsWith("json")) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const jsonVal = typeof tag.textContent === "string" ? JSON.parse(tag.textContent) : tag.textContent;
|
||||
tag.textContent = JSON.stringify(stripProtoKeys(jsonVal), null, 0);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
WhitelistAttributes.script.forEach((s) => {
|
||||
if (s !== "textContent" && prev[s]) {
|
||||
next[s] = prev[s];
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
delete tag.innerHTML;
|
||||
if (type !== "title" && type !== "script") {
|
||||
delete tag.textContent;
|
||||
}
|
||||
tag.props = { ...acceptDataAttrs(prev), ...next };
|
||||
if (!Object.keys(tag.props).length && !tag.tag.endsWith("Attrs") && !tag.textContent) {
|
||||
return false;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
const SafeInputPlugin = /* @__PURE__ */ defineHeadPlugin({
|
||||
key: "safe",
|
||||
hooks: {
|
||||
"entries:normalize": (ctx) => {
|
||||
if (ctx.entry.options?._safe) {
|
||||
ctx.tags = ctx.tags.reduce((acc, tag) => {
|
||||
const safeTag = makeTagSafe(tag);
|
||||
if (safeTag)
|
||||
acc.push(safeTag);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
},
|
||||
"tags:afterResolve": (ctx) => {
|
||||
ctx.tags = ctx.tags.reduce((acc, tag) => {
|
||||
if (!tag._safe) {
|
||||
acc.push(tag);
|
||||
return acc;
|
||||
}
|
||||
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
||||
acc.push(tag);
|
||||
return acc;
|
||||
}
|
||||
const safeTag = makeTagSafe(tag);
|
||||
if (safeTag)
|
||||
acc.push(safeTag);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export { FlatMetaPlugin as F, SafeInputPlugin as S };
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { c as createUnhead } from './unhead.D7HkBzZn.mjs';
|
||||
import { b as TagPriorityAliases } from './unhead.CGPOfp5O.mjs';
|
||||
import { c as createHooks } from './unhead.Bm4Y6XQI.mjs';
|
||||
import { c as createClientHeadAdapter } from './unhead.C5Bksi2B.mjs';
|
||||
import { c as createDomRenderer } from './unhead.Bjbp1C8D.mjs';
|
||||
|
||||
const tagWeight = (tag) => typeof tag.tagPriority === "number" ? tag.tagPriority : 100 + (TagPriorityAliases[tag.tagPriority] || 0);
|
||||
function createHead(options = {}) {
|
||||
options.document = options.document || (typeof window !== "undefined" ? document : void 0);
|
||||
const renderer = options.render || createDomRenderer({ document: options.document });
|
||||
const core = createUnhead(renderer, { document: options.document, propResolvers: options.propResolvers, _tagWeight: tagWeight, init: [] });
|
||||
const hooks = createHooks(options.hooks);
|
||||
const head = createClientHeadAdapter(core, hooks, renderer);
|
||||
options.plugins?.forEach((p) => head.use(p));
|
||||
options.init?.forEach((e) => e && head.push(e));
|
||||
return head;
|
||||
}
|
||||
|
||||
export { createHead as c };
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
import { d as defineHeadPlugin } from './unhead.CHEy9ana.mjs';
|
||||
import { p as processTemplateParams } from './unhead.BGFxPGPQ.mjs';
|
||||
|
||||
const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w;
|
||||
const formatKey = (k) => !k.includes(":key") ? k.split(":").join(":key:") : k;
|
||||
const AliasSortingPlugin = /* @__PURE__ */ defineHeadPlugin({
|
||||
key: "aliasSorting",
|
||||
hooks: {
|
||||
"tags:resolve": (ctx) => {
|
||||
let m = false;
|
||||
for (const t of ctx.tags) {
|
||||
const p = t.tagPriority;
|
||||
if (!p)
|
||||
continue;
|
||||
const s = String(p);
|
||||
if (s.startsWith("before:")) {
|
||||
const k = formatKey(s.slice(7));
|
||||
const l = ctx.tagMap.get(k);
|
||||
if (l) {
|
||||
if (typeof l.tagPriority === "number")
|
||||
t.tagPriority = l.tagPriority;
|
||||
t._p = l._p - 1;
|
||||
m = true;
|
||||
}
|
||||
} else if (s.startsWith("after:")) {
|
||||
const k = formatKey(s.slice(6));
|
||||
const l = ctx.tagMap.get(k);
|
||||
if (l) {
|
||||
if (typeof l.tagPriority === "number")
|
||||
t.tagPriority = l.tagPriority;
|
||||
t._p = l._p + 1;
|
||||
m = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m)
|
||||
ctx.tags = ctx.tags.sort(sortTags);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const DeprecationsPlugin = /* @__PURE__ */ defineHeadPlugin({
|
||||
key: "deprecations",
|
||||
hooks: {
|
||||
"entries:normalize": ({ tags }) => {
|
||||
for (const tag of tags) {
|
||||
if (tag.props.children) {
|
||||
tag.innerHTML = tag.props.children;
|
||||
delete tag.props.children;
|
||||
}
|
||||
if (tag.props.hid) {
|
||||
tag.key = tag.props.hid;
|
||||
delete tag.props.hid;
|
||||
}
|
||||
if (tag.props.vmid) {
|
||||
tag.key = tag.props.vmid;
|
||||
delete tag.props.vmid;
|
||||
}
|
||||
if ("body" in tag.props) {
|
||||
if (tag.props.body) {
|
||||
tag.tagPosition = "bodyClose";
|
||||
}
|
||||
delete tag.props.body;
|
||||
}
|
||||
if (tag.props.renderPriority != null) {
|
||||
tag.tagPriority = tag.props.renderPriority;
|
||||
delete tag.props.renderPriority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function isThenable(v) {
|
||||
return typeof v?.then === "function";
|
||||
}
|
||||
const maxSyncPrefix = 256;
|
||||
function walkArrayPromises(v, index) {
|
||||
if (index === v.length)
|
||||
return;
|
||||
if (index === maxSyncPrefix) {
|
||||
const values2 = new Array(v.length);
|
||||
let hasThenable = false;
|
||||
for (; index < v.length; index++) {
|
||||
const value2 = walkPromises(v[index]);
|
||||
values2[index] = value2;
|
||||
hasThenable ||= isThenable(value2);
|
||||
}
|
||||
return hasThenable ? values2 : void 0;
|
||||
}
|
||||
const value = walkPromises(v[index]);
|
||||
if (isThenable(value)) {
|
||||
const values2 = new Array(v.length);
|
||||
values2[index] = value;
|
||||
for (let rest = index + 1; rest < v.length; rest++)
|
||||
values2[rest] = walkPromises(v[rest]);
|
||||
return values2;
|
||||
}
|
||||
const values = walkArrayPromises(v, index + 1);
|
||||
if (values)
|
||||
values[index] = value;
|
||||
return values;
|
||||
}
|
||||
function walkObjectPromises(v, keys, index) {
|
||||
if (index === keys.length)
|
||||
return;
|
||||
if (index === maxSyncPrefix) {
|
||||
const values2 = new Array(keys.length);
|
||||
let hasThenable = false;
|
||||
for (; index < keys.length; index++) {
|
||||
const value2 = walkPromises(v[keys[index]]);
|
||||
values2[index] = value2;
|
||||
hasThenable ||= isThenable(value2);
|
||||
}
|
||||
return hasThenable ? values2 : void 0;
|
||||
}
|
||||
const value = walkPromises(v[keys[index]]);
|
||||
if (isThenable(value)) {
|
||||
const values2 = new Array(keys.length);
|
||||
values2[index] = value;
|
||||
for (let rest = index + 1; rest < keys.length; rest++)
|
||||
values2[rest] = walkPromises(v[keys[rest]]);
|
||||
return values2;
|
||||
}
|
||||
const values = walkObjectPromises(v, keys, index + 1);
|
||||
if (values)
|
||||
values[index] = value;
|
||||
return values;
|
||||
}
|
||||
function walkPromises(v) {
|
||||
if (typeof v === "function")
|
||||
return v;
|
||||
if (isThenable(v))
|
||||
return Promise.resolve(v).then(walkPromises);
|
||||
if (Array.isArray(v)) {
|
||||
const values = walkArrayPromises(v, 0);
|
||||
return values ? Promise.all(values) : v;
|
||||
}
|
||||
if (v?.constructor === Object) {
|
||||
const keys = Object.keys(v);
|
||||
const values = walkObjectPromises(v, keys, 0);
|
||||
if (values) {
|
||||
return Promise.all(values).then((resolved) => Object.fromEntries(
|
||||
keys.map((key, index) => [key, resolved[index]])
|
||||
));
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
const PromisesPlugin = /* @__PURE__ */ defineHeadPlugin((head) => {
|
||||
const pending = /* @__PURE__ */ new WeakMap();
|
||||
return {
|
||||
key: "promises",
|
||||
hooks: {
|
||||
"entries:resolve": (ctx) => {
|
||||
for (let index = ctx.entries.length - 1; index >= 0; index--) {
|
||||
const entry = ctx.entries[index];
|
||||
const input = entry.input;
|
||||
if (pending.get(entry) === input) {
|
||||
ctx.entries.splice(index, 1);
|
||||
continue;
|
||||
}
|
||||
const result = walkPromises(input);
|
||||
if (!isThenable(result)) {
|
||||
pending.delete(entry);
|
||||
continue;
|
||||
}
|
||||
pending.set(entry, input);
|
||||
ctx.entries.splice(index, 1);
|
||||
void Promise.resolve(result).then(
|
||||
(resolved) => {
|
||||
if (pending.get(entry) !== input)
|
||||
return;
|
||||
pending.delete(entry);
|
||||
entry.input = resolved;
|
||||
delete entry._tags;
|
||||
head.invalidate?.();
|
||||
},
|
||||
() => {
|
||||
if (pending.get(entry) === input)
|
||||
pending.delete(entry);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}, "promises");
|
||||
|
||||
const SupportedAttrs = {
|
||||
meta: "content",
|
||||
link: "href",
|
||||
htmlAttrs: "lang"
|
||||
};
|
||||
const contentAttrs = ["innerHTML", "textContent"];
|
||||
function processIfNeeded(value, params, separator, isJson = false) {
|
||||
return typeof value === "string" && value.includes("%") ? processTemplateParams(value, params, separator, isJson) : value;
|
||||
}
|
||||
const TemplateParamsPlugin = /* @__PURE__ */ defineHeadPlugin((head) => {
|
||||
return {
|
||||
key: "template-params",
|
||||
hooks: {
|
||||
"tags:resolve": ({ tagMap, tags }) => {
|
||||
const params = tagMap.get("templateParams")?.props || {};
|
||||
const sep = params.separator || "|";
|
||||
delete params.separator;
|
||||
params.pageTitle = processIfNeeded(
|
||||
// find templateParams
|
||||
params.pageTitle || head._title || "",
|
||||
params,
|
||||
sep
|
||||
);
|
||||
for (const tag of tags) {
|
||||
if (tag.processTemplateParams === false) {
|
||||
continue;
|
||||
}
|
||||
const v = SupportedAttrs[tag.tag];
|
||||
if (v && typeof tag.props[v] === "string") {
|
||||
tag.props[v] = processIfNeeded(tag.props[v], params, sep);
|
||||
} else if (tag.processTemplateParams || tag.tag === "titleTemplate" || tag.tag === "title") {
|
||||
for (const p of contentAttrs) {
|
||||
if (typeof tag[p] === "string")
|
||||
tag[p] = processIfNeeded(tag[p], params, sep, tag.tag === "script" && typeof tag.props.type === "string" && tag.props.type.endsWith("json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
head._templateParams = params;
|
||||
head._separator = sep;
|
||||
},
|
||||
"tags:afterResolve": ({ tagMap }) => {
|
||||
const title = tagMap.get("title");
|
||||
if (title?.textContent && title.processTemplateParams !== false) {
|
||||
title.textContent = processIfNeeded(title.textContent, head._templateParams, head._separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}, "template-params");
|
||||
|
||||
export { AliasSortingPlugin as A, DeprecationsPlugin as D, PromisesPlugin as P, TemplateParamsPlugin as T };
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { c as createClientHeadAdapter } from '../shared/unhead.C5Bksi2B.mjs';
|
||||
import { c as createHooks } from '../shared/unhead.Bm4Y6XQI.mjs';
|
||||
import '../shared/unhead.D7HkBzZn.mjs';
|
||||
import 'hookable';
|
||||
|
||||
const DEFAULT_STREAM_KEY = "__unhead__";
|
||||
function createStreamableHead(options = {}) {
|
||||
const { streamKey = DEFAULT_STREAM_KEY, ...rest } = options;
|
||||
const win = typeof window !== "undefined" ? window : void 0;
|
||||
const streamQueue = win?.[streamKey];
|
||||
const core = streamQueue?._head;
|
||||
if (!core)
|
||||
return void 0;
|
||||
if (core._wrapped) {
|
||||
const wrapped = core;
|
||||
for (const name in rest.hooks || {})
|
||||
wrapped.hooks?.hook(name, rest.hooks[name]);
|
||||
(rest.plugins || []).forEach((p) => wrapped.use(p));
|
||||
rest.init?.forEach((e) => e && wrapped.push(e));
|
||||
return wrapped;
|
||||
}
|
||||
const coreRender = core.render;
|
||||
const hooks = createHooks(rest.hooks);
|
||||
const head = createClientHeadAdapter(core, hooks, () => coreRender());
|
||||
head._wrapped = true;
|
||||
(rest.plugins || []).forEach((p) => head.use(p));
|
||||
rest.init?.forEach((e) => e && head.push(e));
|
||||
if (streamQueue)
|
||||
streamQueue._head = head;
|
||||
return head;
|
||||
}
|
||||
|
||||
export { DEFAULT_STREAM_KEY, createStreamableHead };
|
||||
+2
File diff suppressed because one or more lines are too long
+446
@@ -0,0 +1,446 @@
|
||||
import { parseHtmlForIndexes, applyHeadToHtml } from '../parser.mjs';
|
||||
export { prepareTemplate } from '../parser.mjs';
|
||||
import { c as createHead } from '../shared/unhead.Dssd7L5I.mjs';
|
||||
import { e as resolveHeadInput, h as hashTag, n as normalizeEntryToTags, b as normalizeProps, d as dedupeKey } from '../shared/unhead.-hZVKou0.mjs';
|
||||
import { a as callHook } from '../shared/unhead.Bm4Y6XQI.mjs';
|
||||
import { DEFAULT_STREAM_KEY } from './client.mjs';
|
||||
import { p as parseStreamKey } from '../shared/unhead.C_7zwq8E.mjs';
|
||||
import '../shared/unhead.Bf_fPVYA.mjs';
|
||||
import '../shared/unhead.D7HkBzZn.mjs';
|
||||
import '../shared/unhead.CGPOfp5O.mjs';
|
||||
import 'hookable';
|
||||
import '../shared/unhead.C5Bksi2B.mjs';
|
||||
|
||||
const LT_RE = /</g;
|
||||
const GT_RE = />/g;
|
||||
const AMP_RE = /&/g;
|
||||
const SSR_OUTLET_RE = /<!--\s*(?:app-html|ssr-outlet)\s*-->/;
|
||||
let encoder;
|
||||
let preparedStreamingLayouts;
|
||||
// @__NO_SIDE_EFFECTS__
|
||||
function createStreamableHead(options = {}) {
|
||||
const { streamKey, writesBodyTags, nonce, ...rest } = options;
|
||||
const parsedStreamKey = streamKey === void 0 ? void 0 : parseStreamKey(streamKey);
|
||||
const head = createHead({
|
||||
...rest,
|
||||
experimentalStreamKey: parsedStreamKey
|
||||
});
|
||||
if (writesBodyTags)
|
||||
streamState(head).writesBodyTags = true;
|
||||
if (nonce)
|
||||
streamState(head).nonce = nonce;
|
||||
let resolveShellReady;
|
||||
const shellReady = new Promise((resolve) => {
|
||||
resolveShellReady = resolve;
|
||||
});
|
||||
return {
|
||||
head,
|
||||
onShellReady: () => resolveShellReady(),
|
||||
shellReady
|
||||
};
|
||||
}
|
||||
function getStreamKey(head) {
|
||||
const key = head.resolvedOptions.experimentalStreamKey || DEFAULT_STREAM_KEY;
|
||||
return parseStreamKey(key);
|
||||
}
|
||||
function createBootstrapScript(streamKey = DEFAULT_STREAM_KEY, nonce) {
|
||||
const parsedStreamKey = parseStreamKey(streamKey);
|
||||
const nonceAttr = renderNonceAttribute(nonce);
|
||||
return `<script${nonceAttr}>window.${parsedStreamKey}||(window.${parsedStreamKey}={_q:[],push(e){this._q.push(e)}})<\/script>`;
|
||||
}
|
||||
function renderNonceAttribute(nonce) {
|
||||
return nonce ? ` nonce="${nonce.replace(AMP_RE, "&").replace(/"/g, """)}"` : "";
|
||||
}
|
||||
function renderShell(head) {
|
||||
const result = head.render();
|
||||
rememberShellBodyTags(head);
|
||||
head.entries.clear();
|
||||
streamState(head).shellRendered = true;
|
||||
return result;
|
||||
}
|
||||
function renderSSRHeadShell(head, template) {
|
||||
const parsed = typeof template === "string" ? parseHtmlForIndexes(template) : template;
|
||||
const result = applyShellToTemplate(head, head.render(), parsed);
|
||||
rememberShellBodyTags(head);
|
||||
head.entries.clear();
|
||||
streamState(head).shellRendered = true;
|
||||
return result;
|
||||
}
|
||||
function applyShellToTemplate(head, ssr, parsed) {
|
||||
return applyHeadToHtml(parsed, {
|
||||
htmlAttrs: ssr.htmlAttrs,
|
||||
headTags: createBootstrapScript(getStreamKey(head), head._stream?.nonce) + ssr.headTags,
|
||||
bodyAttrs: ssr.bodyAttrs,
|
||||
bodyTagsOpen: ssr.bodyTagsOpen,
|
||||
bodyTags: ssr.bodyTags
|
||||
});
|
||||
}
|
||||
function normalizePendingTags(head) {
|
||||
const propResolvers = head.resolvedOptions.propResolvers || [];
|
||||
const tags = [];
|
||||
const entries = /* @__PURE__ */ new Map();
|
||||
for (const entry of head.entries.values()) {
|
||||
const resolved = resolveHeadInput(unwrapEntryInput(entry.input), propResolvers);
|
||||
entries.set(entry._i, { input: entry.input, resolved });
|
||||
let index = 0;
|
||||
for (const tag of normalizeEntryToTags(resolved, [])) {
|
||||
if (entry.options)
|
||||
Object.assign(tag, entry.options);
|
||||
tag._p = (entry._i << 10) + index++;
|
||||
if (tag.tag === "templateParams")
|
||||
tag.props = { ...tag.props };
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
return { tags, entries };
|
||||
}
|
||||
const JSON_LD_TYPE_RE = /\bld\+json\b/i;
|
||||
function streamState(head) {
|
||||
return head._stream ||= {};
|
||||
}
|
||||
function isStreamedBodyTag(tagName, tag, entryPosition) {
|
||||
if (!tag || typeof tag !== "object")
|
||||
return false;
|
||||
if (tagName === "noscript")
|
||||
return true;
|
||||
const position = tag.tagPosition ?? entryPosition;
|
||||
if (position === "bodyClose" || position === "bodyOpen")
|
||||
return true;
|
||||
const type = tag.type ?? tag.props?.type;
|
||||
return tagName === "script" && typeof type === "string" && JSON_LD_TYPE_RE.test(type);
|
||||
}
|
||||
function streamedBodyTagIdentity(tagName, tag) {
|
||||
const normalized = normalizeProps({ tag: tagName, props: {} }, tag);
|
||||
const content = hashTag(normalized);
|
||||
return { slot: dedupeKey(normalized) || content, content };
|
||||
}
|
||||
function unwrapEntryInput(input) {
|
||||
return typeof input === "function" ? input() : input;
|
||||
}
|
||||
function rememberShellBodyTags(head) {
|
||||
const state = streamState(head);
|
||||
let seen = state.seen;
|
||||
for (const entry of head.entries.values()) {
|
||||
const entryPosition = entry.options?.tagPosition;
|
||||
for (const tag of entry._tags || entry._precomputedTags || []) {
|
||||
if (!isStreamedBodyTag(tag.tag, tag, entryPosition))
|
||||
continue;
|
||||
seen ||= state.seen = /* @__PURE__ */ new Set();
|
||||
if (tag.textContent || tag.innerHTML)
|
||||
seen.add(String(tag.textContent || tag.innerHTML));
|
||||
seen.add(tag._d || tag._h || hashTag(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
function hasStreamedBodyTags(input, entryPosition) {
|
||||
if (input && typeof input === "object") {
|
||||
for (const key in input) {
|
||||
const value = input[key];
|
||||
if (Array.isArray(value)) {
|
||||
for (const tag of value) {
|
||||
if (isStreamedBodyTag(key, tag, entryPosition))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function splitStreamedBodyTags(input, seen, entryPosition) {
|
||||
let patch;
|
||||
let bodyTags;
|
||||
for (const key in input) {
|
||||
const value = input[key];
|
||||
if (!Array.isArray(value) || !value.some((tag) => isStreamedBodyTag(key, tag, entryPosition)))
|
||||
continue;
|
||||
const rest = [];
|
||||
const carried = [];
|
||||
for (const tag of value) {
|
||||
if (!isStreamedBodyTag(key, tag, entryPosition)) {
|
||||
rest.push(tag);
|
||||
continue;
|
||||
}
|
||||
const id = streamedBodyTagIdentity(key, tag);
|
||||
if (seen.has(id.content))
|
||||
continue;
|
||||
if (seen.has(id.slot)) {
|
||||
rest.push(tag);
|
||||
continue;
|
||||
}
|
||||
seen.add(id.content);
|
||||
carried.push({ ...tag, tagPosition: "bodyClose" });
|
||||
}
|
||||
patch ||= { ...input };
|
||||
if (rest.length)
|
||||
patch[key] = rest;
|
||||
else
|
||||
delete patch[key];
|
||||
if (carried.length)
|
||||
(bodyTags ||= {})[key] = carried;
|
||||
}
|
||||
const hasPatch = Object.keys(patch).some((k) => patch[k] !== void 0);
|
||||
return { patch: hasPatch ? patch : void 0, bodyTags };
|
||||
}
|
||||
function renderStreamBodyTags(head) {
|
||||
const bodyTagInputs = streamState(head).bodyTags;
|
||||
if (!bodyTagInputs?.length) {
|
||||
return "";
|
||||
}
|
||||
const restore = new Map(head.entries);
|
||||
head.entries.clear();
|
||||
try {
|
||||
for (const input of bodyTagInputs)
|
||||
head.push(input);
|
||||
const bodyTags = head.render().bodyTags;
|
||||
streamState(head).bodyTags = void 0;
|
||||
return bodyTags;
|
||||
} finally {
|
||||
head.entries.clear();
|
||||
for (const [k, v] of restore)
|
||||
head.entries.set(k, v);
|
||||
}
|
||||
}
|
||||
function renderStreamEnd(head, parts) {
|
||||
const bodyTags = renderStreamBodyTags(head);
|
||||
if (!bodyTags)
|
||||
return parts.end;
|
||||
const at = parts.bodyTagsAt ?? parts.end.length;
|
||||
return parts.end.slice(0, at) + bodyTags + parts.end.slice(at);
|
||||
}
|
||||
function renderSSRHeadSuspenseChunk(head) {
|
||||
if (!head.entries.size)
|
||||
return "";
|
||||
const streamKey = getStreamKey(head);
|
||||
const propResolvers = head.resolvedOptions.propResolvers || [];
|
||||
let normalizedEntries;
|
||||
let serialized;
|
||||
let patchCount = 0;
|
||||
try {
|
||||
if (head.hooks?._hooks?.["ssr:streamChunk"]?.length) {
|
||||
const normalized = normalizePendingTags(head);
|
||||
normalizedEntries = normalized.entries;
|
||||
callHook(head, "ssr:streamChunk", { tags: normalized.tags });
|
||||
}
|
||||
const state = streamState(head);
|
||||
let nextSeen;
|
||||
const inputs = [];
|
||||
let bodyTags;
|
||||
for (const entry of head.entries.values()) {
|
||||
const normalized = normalizedEntries?.get(entry._i);
|
||||
const input = normalized && normalized.input === entry.input ? normalized.resolved : resolveHeadInput(unwrapEntryInput(entry.input), propResolvers);
|
||||
const entryPosition = entry.options?.tagPosition;
|
||||
if (!state.writesBodyTags || !hasStreamedBodyTags(input, entryPosition)) {
|
||||
inputs.push(input);
|
||||
continue;
|
||||
}
|
||||
const split = splitStreamedBodyTags(input, nextSeen ||= new Set(state.seen), entryPosition);
|
||||
if (split.patch)
|
||||
inputs.push(split.patch);
|
||||
if (split.bodyTags)
|
||||
(bodyTags ||= []).push(split.bodyTags);
|
||||
}
|
||||
serialized = safeJsonStringify(inputs);
|
||||
patchCount = inputs.length;
|
||||
if (nextSeen)
|
||||
state.seen = nextSeen;
|
||||
if (bodyTags)
|
||||
(state.bodyTags ||= []).push(...bodyTags);
|
||||
} catch (error) {
|
||||
for (const [key, entry] of head.entries) {
|
||||
try {
|
||||
safeJsonStringify(resolveHeadInput(unwrapEntryInput(entry.input), propResolvers));
|
||||
} catch {
|
||||
head.entries.delete(key);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
head.entries.clear();
|
||||
if (!patchCount)
|
||||
return "";
|
||||
return `window.${streamKey}.push(${serialized})`;
|
||||
}
|
||||
function safeJsonStringify(obj) {
|
||||
return JSON.stringify(obj).replace(LT_RE, "\\u003c").replace(GT_RE, "\\u003e").replace(AMP_RE, "\\u0026");
|
||||
}
|
||||
function wrapStream(head, stream, template, preRenderedState, options) {
|
||||
streamState(head).writesBodyTags = true;
|
||||
const flushChunk = options?.flushChunk ?? (() => {
|
||||
let chunk;
|
||||
try {
|
||||
chunk = renderSSRHeadSuspenseChunk(head);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
if (!chunk)
|
||||
return "";
|
||||
return `<script${renderNonceAttribute(head._stream?.nonce)}>window.${getStreamKey(head)}&&(${chunk});document.currentScript.remove()<\/script>`;
|
||||
});
|
||||
const enc = encoder ??= new TextEncoder();
|
||||
let reader;
|
||||
let parts;
|
||||
return new ReadableStream({
|
||||
// Async so a failure here rejects into an errored stream instead of
|
||||
// throwing synchronously out of the constructor. The reader is acquired
|
||||
// before rendering (and released if rendering fails) so a failure at
|
||||
// either step leaves `head.entries` intact and the upstream unlocked
|
||||
// for retry.
|
||||
async start(controller) {
|
||||
const activeReader = stream.getReader();
|
||||
let prepared;
|
||||
try {
|
||||
prepared = prepareStreamingTemplate(head, template, preRenderedState);
|
||||
} catch (error) {
|
||||
activeReader.releaseLock();
|
||||
throw error;
|
||||
}
|
||||
reader = activeReader;
|
||||
parts = prepared;
|
||||
controller.enqueue(enc.encode(prepared.shell));
|
||||
},
|
||||
// Read at most one upstream chunk per downstream request so backpressure
|
||||
// propagates instead of eagerly draining the app stream.
|
||||
async pull(controller) {
|
||||
const activeReader = reader;
|
||||
if (!activeReader)
|
||||
return;
|
||||
const result = await activeReader.read().then(
|
||||
(value) => ({ ok: true, value }),
|
||||
(error) => ({ ok: false, error })
|
||||
);
|
||||
if (activeReader !== reader)
|
||||
return;
|
||||
if (!result.ok) {
|
||||
reader = void 0;
|
||||
activeReader.releaseLock();
|
||||
controller.error(result.error);
|
||||
return;
|
||||
}
|
||||
if (result.value.done) {
|
||||
reader = void 0;
|
||||
activeReader.releaseLock();
|
||||
const extra2 = flushChunk?.();
|
||||
if (extra2)
|
||||
controller.enqueue(enc.encode(extra2));
|
||||
const closing = parts ? renderStreamEnd(head, parts) : "";
|
||||
if (closing)
|
||||
controller.enqueue(enc.encode(closing));
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
controller.enqueue(result.value.value);
|
||||
const extra = flushChunk?.();
|
||||
if (extra)
|
||||
controller.enqueue(enc.encode(extra));
|
||||
},
|
||||
async cancel(reason) {
|
||||
const activeReader = reader;
|
||||
reader = void 0;
|
||||
if (activeReader) {
|
||||
try {
|
||||
await activeReader.cancel(reason);
|
||||
} catch {
|
||||
}
|
||||
activeReader.releaseLock();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function createStreamingTemplateLayout(parsed) {
|
||||
const html = parsed.html;
|
||||
const bodyEnd = parsed.indexes.bodyTagEnd;
|
||||
const bodyCloseStart = parsed.indexes.bodyCloseTagStart;
|
||||
if (bodyEnd < 0 || bodyCloseStart < 0)
|
||||
return;
|
||||
const bodyInterior = html.substring(bodyEnd, bodyCloseStart);
|
||||
const markerMatch = bodyInterior.match(SSR_OUTLET_RE);
|
||||
let beforeStream;
|
||||
let afterStream;
|
||||
if (markerMatch) {
|
||||
beforeStream = bodyInterior.substring(0, markerMatch.index);
|
||||
afterStream = bodyInterior.substring(markerMatch.index + markerMatch[0].length);
|
||||
} else {
|
||||
beforeStream = "";
|
||||
afterStream = bodyInterior;
|
||||
}
|
||||
const shellPart = html.substring(0, bodyEnd) + beforeStream;
|
||||
const endPart = html.substring(bodyCloseStart);
|
||||
let shellTemplate;
|
||||
if (bodyCloseStart >= bodyEnd) {
|
||||
const shellLen = shellPart.length;
|
||||
const { htmlTagStart, headTagEnd, bodyTagStart } = parsed.indexes;
|
||||
const shellHtmlTagStart = htmlTagStart >= 0 && htmlTagStart + 5 <= shellLen ? htmlTagStart : -1;
|
||||
let shellHtmlTagEnd = -1;
|
||||
if (shellHtmlTagStart >= 0) {
|
||||
const gt = shellPart.indexOf(">", shellHtmlTagStart);
|
||||
shellHtmlTagEnd = gt >= 0 ? gt + 1 : shellLen + 7;
|
||||
}
|
||||
shellTemplate = {
|
||||
html: `${shellPart}</body></html>`,
|
||||
input: parsed.input,
|
||||
indexes: {
|
||||
htmlTagStart: shellHtmlTagStart,
|
||||
htmlTagEnd: shellHtmlTagEnd,
|
||||
headTagEnd: headTagEnd >= 0 && headTagEnd + 7 <= shellLen ? headTagEnd : -1,
|
||||
// <body> is always fully inside the prefix in this branch.
|
||||
bodyTagStart,
|
||||
bodyTagEnd: bodyEnd,
|
||||
bodyCloseTagStart: bodyCloseStart + 7 <= shellLen ? bodyCloseStart : shellLen
|
||||
}
|
||||
};
|
||||
} else {
|
||||
shellTemplate = parseHtmlForIndexes(`${shellPart}</body></html>`);
|
||||
}
|
||||
return {
|
||||
shellTemplate,
|
||||
endBeforeBodyTags: afterStream,
|
||||
endAfterBodyTags: endPart
|
||||
};
|
||||
}
|
||||
function getPreparedStreamingLayout(template) {
|
||||
const cache = preparedStreamingLayouts ||= /* @__PURE__ */ new WeakMap();
|
||||
let layout = cache.get(template);
|
||||
if (layout === void 0) {
|
||||
layout = createStreamingTemplateLayout(template) || null;
|
||||
if (layout) {
|
||||
Object.freeze(layout.shellTemplate.indexes);
|
||||
Object.freeze(layout.shellTemplate);
|
||||
Object.freeze(layout);
|
||||
}
|
||||
cache.set(template, layout);
|
||||
}
|
||||
return layout || void 0;
|
||||
}
|
||||
function prepareStreamingTemplate(head, template, preRenderedState) {
|
||||
const ssr = preRenderedState ?? head.render();
|
||||
const parsed = typeof template === "string" ? parseHtmlForIndexes(template) : template;
|
||||
const layout = typeof template === "string" ? createStreamingTemplateLayout(parsed) : getPreparedStreamingLayout(template);
|
||||
let parts;
|
||||
if (layout) {
|
||||
const shell = applyHeadToHtml(layout.shellTemplate, {
|
||||
htmlAttrs: ssr.htmlAttrs,
|
||||
headTags: createBootstrapScript(getStreamKey(head), head._stream?.nonce) + ssr.headTags,
|
||||
bodyAttrs: ssr.bodyAttrs,
|
||||
bodyTagsOpen: ssr.bodyTagsOpen,
|
||||
bodyTags: ""
|
||||
}).replace("</body></html>", "");
|
||||
parts = {
|
||||
shell,
|
||||
end: layout.endBeforeBodyTags + ssr.bodyTags + layout.endAfterBodyTags,
|
||||
bodyTagsAt: layout.endBeforeBodyTags.length
|
||||
};
|
||||
} else {
|
||||
parts = {
|
||||
shell: applyShellToTemplate(head, ssr, parsed),
|
||||
end: ""
|
||||
};
|
||||
}
|
||||
if (!preRenderedState) {
|
||||
rememberShellBodyTags(head);
|
||||
head.entries.clear();
|
||||
}
|
||||
streamState(head).shellRendered = true;
|
||||
return parts;
|
||||
}
|
||||
|
||||
export { createBootstrapScript, createStreamableHead, prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, renderShell, renderStreamBodyTags, renderStreamEnd, wrapStream };
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export { D as DupeableTags, H as HasElementTags, M as MetaTagsArrayable, S as ScriptNetworkEvents, a as SelfClosingTags, T as TagConfigKeys, b as TagPriorityAliases, c as TagsWithInnerContent, U as UniqueTags, d as UsesMergeStrategy, V as ValidHeadTags } from './shared/unhead.CGPOfp5O.mjs';
|
||||
export { d as dedupeKey, a as dedupeTags, h as hashTag, i as isMetaArrayDupeKey, n as normalizeEntryToTags, b as normalizeProps, r as resolveTags, c as resolveTitleTemplate, s as sanitizeTags, w as walkResolver } from './shared/unhead.-hZVKou0.mjs';
|
||||
export { h as hasOwn } from './shared/unhead.Bf_fPVYA.mjs';
|
||||
export { r as resolveMetaKeyType, a as resolveMetaKeyValue, b as resolvePackedMetaObjectValue, u as unpackMeta } from './shared/unhead.BQWSw36o.mjs';
|
||||
export { p as processTemplateParams } from './shared/unhead.BGFxPGPQ.mjs';
|
||||
import './shared/unhead.Bm4Y6XQI.mjs';
|
||||
import 'hookable';
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"name": "unhead",
|
||||
"type": "module",
|
||||
"version": "3.4.2",
|
||||
"description": "Full-stack <head> manager built for any framework.",
|
||||
"author": {
|
||||
"name": "Harlan Wilton",
|
||||
"email": "harlan@harlanzw.com",
|
||||
"url": "https://harlanzw.com/"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"tag": "next"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/harlan-zw",
|
||||
"homepage": "https://unhead.unjs.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/unjs/unhead.git",
|
||||
"directory": "packages/unhead"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/unjs/unhead/issues"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./plugins": {
|
||||
"types": "./dist/plugins.d.ts",
|
||||
"default": "./dist/plugins.mjs"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./dist/legacy.d.ts",
|
||||
"default": "./dist/legacy.mjs"
|
||||
},
|
||||
"./minify": {
|
||||
"types": "./dist/minify.d.ts",
|
||||
"default": "./dist/minify.mjs"
|
||||
},
|
||||
"./server": {
|
||||
"types": "./dist/server.d.ts",
|
||||
"default": "./dist/server.mjs"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./dist/client.d.ts",
|
||||
"default": "./dist/client.mjs"
|
||||
},
|
||||
"./stream/server": {
|
||||
"types": "./dist/stream/server.d.ts",
|
||||
"default": "./dist/stream/server.mjs"
|
||||
},
|
||||
"./stream/client": {
|
||||
"types": "./dist/stream/client.d.ts",
|
||||
"default": "./dist/stream/client.mjs"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./dist/utils.d.ts",
|
||||
"default": "./dist/utils.mjs"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types.d.ts",
|
||||
"default": "./dist/types.mjs"
|
||||
},
|
||||
"./scripts": {
|
||||
"types": "./dist/scripts.d.ts",
|
||||
"node": "./dist/scripts.node.mjs",
|
||||
"default": "./dist/scripts.mjs"
|
||||
},
|
||||
"./scripts/triggers": {
|
||||
"types": "./dist/scripts/triggers.d.ts",
|
||||
"default": "./dist/scripts/triggers.mjs"
|
||||
},
|
||||
"./parser": {
|
||||
"types": "./dist/parser.d.ts",
|
||||
"default": "./dist/parser.mjs"
|
||||
},
|
||||
"./validate": {
|
||||
"types": "./dist/validate.d.ts",
|
||||
"default": "./dist/validate.mjs"
|
||||
},
|
||||
"./stream/unplugin": {
|
||||
"types": "./dist/stream/unplugin.d.ts",
|
||||
"default": "./dist/stream/unplugin.mjs"
|
||||
},
|
||||
"./stream/vite": {
|
||||
"types": "./dist/stream/vite.d.ts",
|
||||
"default": "./dist/stream/vite.mjs"
|
||||
},
|
||||
"./stream/iife": {
|
||||
"types": "./dist/stream/iife.d.ts",
|
||||
"default": "./dist/stream/iife.mjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.mjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"plugins": [
|
||||
"dist/plugins.d.ts"
|
||||
],
|
||||
"legacy": [
|
||||
"dist/legacy.d.ts"
|
||||
],
|
||||
"minify": [
|
||||
"dist/minify.d.ts"
|
||||
],
|
||||
"server": [
|
||||
"dist/server.d.ts"
|
||||
],
|
||||
"client": [
|
||||
"dist/client.d.ts"
|
||||
],
|
||||
"stream/server": [
|
||||
"dist/stream/server.d.ts"
|
||||
],
|
||||
"stream/client": [
|
||||
"dist/stream/client.d.ts"
|
||||
],
|
||||
"utils": [
|
||||
"dist/utils.d.ts"
|
||||
],
|
||||
"types": [
|
||||
"dist/types.d.ts"
|
||||
],
|
||||
"scripts": [
|
||||
"dist/scripts.d.ts"
|
||||
],
|
||||
"scripts/triggers": [
|
||||
"dist/scripts/triggers.d.ts"
|
||||
],
|
||||
"parser": [
|
||||
"dist/parser.d.ts"
|
||||
],
|
||||
"validate": [
|
||||
"dist/validate.d.ts"
|
||||
],
|
||||
"stream/unplugin": [
|
||||
"dist/stream/unplugin.d.ts"
|
||||
],
|
||||
"stream/vite": [
|
||||
"dist/stream/vite.d.ts"
|
||||
],
|
||||
"stream/iife": [
|
||||
"dist/stream/iife.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"*.d.ts",
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"vite": ">=6.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"hookable": "^6.1.1",
|
||||
"unplugin": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"rollup": "^4.63.2",
|
||||
"vite": "^8.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub",
|
||||
"test:attw": "attw --pack"
|
||||
}
|
||||
}
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
import { withLeadingSlash } from "ufo";
|
||||
//#region src/runtime.ts
|
||||
const MAX_INITIAL_SLOTS = 8192;
|
||||
function createMergeSlots(capacity = 16) {
|
||||
return {
|
||||
slotOf: Object.create(null),
|
||||
count: 0,
|
||||
idOf: [],
|
||||
metaOf: [],
|
||||
scripts: new Uint8Array(capacity),
|
||||
styles: new Uint8Array(capacity),
|
||||
preload: new Uint8Array(capacity),
|
||||
prefetch: new Uint8Array(capacity),
|
||||
epoch: 0
|
||||
};
|
||||
}
|
||||
function slotFor(slots, id, meta) {
|
||||
let slot = slots.slotOf[id];
|
||||
if (slot === void 0) {
|
||||
slot = slots.count++;
|
||||
slots.slotOf[id] = slot;
|
||||
slots.idOf.push(id);
|
||||
slots.metaOf.push(meta);
|
||||
if (slot >= slots.scripts.length) {
|
||||
const capacity = slots.scripts.length * 2;
|
||||
for (const kind of [
|
||||
"scripts",
|
||||
"styles",
|
||||
"preload",
|
||||
"prefetch"
|
||||
]) {
|
||||
const grown = new Uint8Array(capacity);
|
||||
grown.set(slots[kind]);
|
||||
slots[kind] = grown;
|
||||
}
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
function createFragmentCaches() {
|
||||
return {
|
||||
style: [],
|
||||
script: [],
|
||||
preloadHint: [],
|
||||
prefetchHint: [],
|
||||
preloadHeader: [],
|
||||
prefetchHeader: [],
|
||||
href: []
|
||||
};
|
||||
}
|
||||
function createRendererContext({ manifest, precomputed, buildAssetsURL, dependencySetsCacheSize }) {
|
||||
if (!manifest && !precomputed) throw new Error("Either manifest or precomputed data must be provided");
|
||||
const cacheSize = typeof dependencySetsCacheSize === "number" && Number.isFinite(dependencySetsCacheSize) && dependencySetsCacheSize > 0 ? Math.floor(dependencySetsCacheSize) : dependencySetsCacheSize === void 0 ? 1e3 : 0;
|
||||
const entrypoints = [];
|
||||
let slotCapacity = 16;
|
||||
if (precomputed) slotCapacity = precomputed.resourceCount ?? 16;
|
||||
else if (manifest) for (const id in manifest) {
|
||||
slotCapacity++;
|
||||
if (manifest[id].isEntry) entrypoints.push(id);
|
||||
}
|
||||
const ctx = {
|
||||
buildAssetsURL: buildAssetsURL || withLeadingSlash,
|
||||
manifest,
|
||||
precomputed,
|
||||
updateManifest,
|
||||
_dependencies: {},
|
||||
_dependencySets: /* @__PURE__ */ new Map(),
|
||||
_dependencySetAliases: /* @__PURE__ */ new Map(),
|
||||
_aliasIdHashes: Object.create(null),
|
||||
_aliasIdHashCount: 0,
|
||||
_dependencySetsCacheSize: cacheSize,
|
||||
_entrypoints: [],
|
||||
_renderedCache: /* @__PURE__ */ new WeakMap(),
|
||||
_fragments: createFragmentCaches(),
|
||||
_flatDependencies: Object.create(null),
|
||||
_mergeSlots: createMergeSlots(Math.min(slotCapacity, MAX_INITIAL_SLOTS)),
|
||||
_idScratch: []
|
||||
};
|
||||
function updateManifest(manifest) {
|
||||
ctx.manifest = manifest;
|
||||
ctx._dependencies = {};
|
||||
ctx._dependencySets.clear();
|
||||
ctx._dependencySetAliases.clear();
|
||||
ctx._aliasIdHashes = Object.create(null);
|
||||
ctx._aliasIdHashCount = 0;
|
||||
ctx._renderedCache = /* @__PURE__ */ new WeakMap();
|
||||
ctx._fragments = createFragmentCaches();
|
||||
ctx._flatDependencies = Object.create(null);
|
||||
let capacity = 16;
|
||||
const entrypoints = [];
|
||||
for (const id in manifest) {
|
||||
capacity++;
|
||||
if (manifest[id].isEntry) entrypoints.push(id);
|
||||
}
|
||||
ctx._mergeSlots = createMergeSlots(Math.min(capacity, MAX_INITIAL_SLOTS));
|
||||
ctx._entrypoints = entrypoints;
|
||||
}
|
||||
if (precomputed) {
|
||||
ctx._dependencies = precomputed.dependencies;
|
||||
ctx._entrypoints = precomputed.entrypoints;
|
||||
} else if (manifest) ctx._entrypoints = entrypoints;
|
||||
return ctx;
|
||||
}
|
||||
function getModuleDependencies(id, rendererContext) {
|
||||
if (rendererContext._dependencies[id]) return rendererContext._dependencies[id];
|
||||
const dependencies = rendererContext._dependencies[id] = {
|
||||
scripts: {},
|
||||
styles: {},
|
||||
preload: {},
|
||||
prefetch: {}
|
||||
};
|
||||
if (!rendererContext.manifest) return dependencies;
|
||||
const meta = rendererContext.manifest[id];
|
||||
if (!meta) return dependencies;
|
||||
if (meta.file) {
|
||||
if (meta.preload) dependencies.preload[id] = meta;
|
||||
if (meta.isEntry || meta.sideEffects) dependencies.scripts[id] = meta;
|
||||
}
|
||||
for (const css of meta.css || []) {
|
||||
const cssResource = rendererContext.manifest[css];
|
||||
dependencies.styles[css] = dependencies.prefetch[css] = cssResource;
|
||||
if (cssResource.preload) dependencies.preload[css] = cssResource;
|
||||
}
|
||||
for (const asset of meta.assets || []) {
|
||||
const assetResource = rendererContext.manifest[asset];
|
||||
dependencies.prefetch[asset] = assetResource;
|
||||
if (assetResource.preload) dependencies.preload[asset] = assetResource;
|
||||
}
|
||||
if (meta.imports) for (const depId of meta.imports) {
|
||||
const depDeps = getModuleDependencies(depId, rendererContext);
|
||||
Object.assign(dependencies.styles, depDeps.styles);
|
||||
Object.assign(dependencies.preload, depDeps.preload);
|
||||
Object.assign(dependencies.prefetch, depDeps.prefetch);
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
/** Past this, the table is dropped wholesale; stale alias keys stop matching. */
|
||||
const MAX_ALIAS_ID_HASHES = 65536;
|
||||
function aliasHash(rendererContext, entrypoints, requestIds) {
|
||||
let hashes = rendererContext._aliasIdHashes;
|
||||
if (rendererContext._aliasIdHashCount > MAX_ALIAS_ID_HASHES) {
|
||||
hashes = rendererContext._aliasIdHashes = Object.create(null);
|
||||
rendererContext._aliasIdHashCount = 0;
|
||||
rendererContext._dependencySetAliases.clear();
|
||||
}
|
||||
let sum = 0;
|
||||
let mixed = 1;
|
||||
let count = 0;
|
||||
for (const id of entrypoints) {
|
||||
let hash = hashes[id];
|
||||
if (hash === void 0) {
|
||||
hash = rendererContext._aliasIdHashCount++ * 2654435761 | 0;
|
||||
hashes[id] = hash;
|
||||
}
|
||||
sum = sum + hash | 0;
|
||||
mixed = mixed ^ hash + count | 0;
|
||||
count++;
|
||||
}
|
||||
if (requestIds) for (const id of requestIds) {
|
||||
let hash = hashes[id];
|
||||
if (hash === void 0) {
|
||||
hash = rendererContext._aliasIdHashCount++ * 2654435761 | 0;
|
||||
hashes[id] = hash;
|
||||
}
|
||||
sum = sum + hash | 0;
|
||||
mixed = mixed ^ hash + count | 0;
|
||||
count++;
|
||||
}
|
||||
return Math.imul(sum, 2654435761) ^ Math.imul(mixed, 40503) ^ count | 0;
|
||||
}
|
||||
function readAlias(rendererContext, aliasKey, moduleIds, entrypoints, requestIds) {
|
||||
const entry = rendererContext._dependencySetAliases.get(aliasKey);
|
||||
if (entry === void 0) return;
|
||||
const { ids } = entry;
|
||||
let i = 0;
|
||||
if (moduleIds) {
|
||||
if (moduleIds.length !== ids.length) return;
|
||||
for (; i < moduleIds.length; i++) if (ids[i] !== moduleIds[i]) return;
|
||||
return entry.deps;
|
||||
}
|
||||
for (const id of entrypoints) if (ids[i++] !== id) return;
|
||||
if (requestIds) {
|
||||
for (const id of requestIds) if (ids[i++] !== id) return;
|
||||
}
|
||||
return i === ids.length ? entry.deps : void 0;
|
||||
}
|
||||
function setAlias(rendererContext, aliasKey, ids, deps, cacheSize) {
|
||||
const aliases = rendererContext._dependencySetAliases;
|
||||
aliases.set(aliasKey, {
|
||||
ids: [...ids],
|
||||
deps
|
||||
});
|
||||
if (aliases.size > cacheSize) {
|
||||
const oldest = aliases.keys().next().value;
|
||||
if (oldest !== void 0) aliases.delete(oldest);
|
||||
}
|
||||
}
|
||||
function collectInto(source, slots, mergeSlots) {
|
||||
for (const id in source) slots.push(slotFor(mergeSlots, id, source[id]));
|
||||
}
|
||||
/** A module's own styles are never preloaded; cross-module overlap is filtered per request. */
|
||||
function collectPreload(deps, slots, mergeSlots) {
|
||||
const { styles, preload } = deps;
|
||||
for (const id in preload) {
|
||||
if (id in styles) continue;
|
||||
slots.push(slotFor(mergeSlots, id, preload[id]));
|
||||
}
|
||||
}
|
||||
/** Opt-out and a module's own preload and styles are fixed; cross-module overlap is not. */
|
||||
function collectPrefetch(deps, source, slots, seen, mergeSlots) {
|
||||
const { styles, preload } = deps;
|
||||
for (const id in source) {
|
||||
const meta = source[id];
|
||||
if (!meta.prefetch || id in preload || id in styles) continue;
|
||||
const slot = slotFor(mergeSlots, id, meta);
|
||||
if (seen.has(slot)) continue;
|
||||
seen.add(slot);
|
||||
slots.push(slot);
|
||||
}
|
||||
}
|
||||
function getFlatDependencies(id, rendererContext) {
|
||||
const cached = rendererContext._flatDependencies[id];
|
||||
if (cached !== void 0) return cached;
|
||||
const deps = getModuleDependencies(id, rendererContext);
|
||||
const mergeSlots = rendererContext._mergeSlots;
|
||||
const flat = {
|
||||
scriptSlots: [],
|
||||
styleSlots: [],
|
||||
preloadSlots: [],
|
||||
prefetchSlots: []
|
||||
};
|
||||
collectInto(deps.scripts, flat.scriptSlots, mergeSlots);
|
||||
collectInto(deps.styles, flat.styleSlots, mergeSlots);
|
||||
collectPreload(deps, flat.preloadSlots, mergeSlots);
|
||||
const prefetchSeen = /* @__PURE__ */ new Set();
|
||||
collectPrefetch(deps, deps.prefetch, flat.prefetchSlots, prefetchSeen, mergeSlots);
|
||||
const dynamicImports = rendererContext.manifest?.[id]?.dynamicImports || rendererContext.precomputed?.modules[id]?.dynamicImports;
|
||||
if (dynamicImports) for (const dynamicDepId of dynamicImports) {
|
||||
const dynamicDeps = getModuleDependencies(dynamicDepId, rendererContext);
|
||||
collectPrefetch(deps, dynamicDeps.scripts, flat.prefetchSlots, prefetchSeen, mergeSlots);
|
||||
collectPrefetch(deps, dynamicDeps.styles, flat.prefetchSlots, prefetchSeen, mergeSlots);
|
||||
collectPrefetch(deps, dynamicDeps.preload, flat.prefetchSlots, prefetchSeen, mergeSlots);
|
||||
}
|
||||
rendererContext._flatDependencies[id] = flat;
|
||||
return flat;
|
||||
}
|
||||
function getAllDependencies(ids, rendererContext) {
|
||||
const cacheSize = rendererContext._dependencySetsCacheSize;
|
||||
const moduleIds = rendererContext._idScratch;
|
||||
moduleIds.length = 0;
|
||||
for (const id of ids) moduleIds.push(id);
|
||||
let aliasKey = 0;
|
||||
let hasAlias = false;
|
||||
if (cacheSize > 0 && ids.size > 1) {
|
||||
aliasKey = aliasHash(rendererContext, moduleIds, void 0);
|
||||
hasAlias = true;
|
||||
const aliased = readAlias(rendererContext, aliasKey, moduleIds, moduleIds, void 0);
|
||||
if (aliased !== void 0) return aliased;
|
||||
}
|
||||
return resolveDependencies(moduleIds, rendererContext, aliasKey, hasAlias);
|
||||
}
|
||||
/** The id list may contain duplicates: merging deduplicates at resource level. */
|
||||
function resolveDependencies(moduleIds, rendererContext, aliasKey, hasAlias) {
|
||||
const cacheSize = rendererContext._dependencySetsCacheSize;
|
||||
const useCache = cacheSize > 0;
|
||||
let cacheKey = "";
|
||||
if (useCache) {
|
||||
if (moduleIds.length <= 1) cacheKey = moduleIds[0] || "";
|
||||
else cacheKey = [...new Set(moduleIds)].sort().join(",");
|
||||
const cached = rendererContext._dependencySets.get(cacheKey);
|
||||
if (cached !== void 0) {
|
||||
if (rendererContext._dependencySets.size >= cacheSize) {
|
||||
rendererContext._dependencySets.delete(cacheKey);
|
||||
rendererContext._dependencySets.set(cacheKey, cached);
|
||||
}
|
||||
if (hasAlias) setAlias(rendererContext, aliasKey, moduleIds, cached, cacheSize);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
const styleSlots = [];
|
||||
const scriptSlots = [];
|
||||
const preloadSlots = [];
|
||||
const prefetchSlots = [];
|
||||
const mergeSlots = rendererContext._mergeSlots;
|
||||
let epoch = mergeSlots.epoch + 1;
|
||||
if (epoch > 255) {
|
||||
epoch = 1;
|
||||
mergeSlots.scripts.fill(0);
|
||||
mergeSlots.styles.fill(0);
|
||||
mergeSlots.preload.fill(0);
|
||||
mergeSlots.prefetch.fill(0);
|
||||
}
|
||||
mergeSlots.epoch = epoch;
|
||||
let scriptSeen = mergeSlots.scripts;
|
||||
let styleSeen = mergeSlots.styles;
|
||||
let preloadSeen = mergeSlots.preload;
|
||||
let prefetchSeen = mergeSlots.prefetch;
|
||||
for (let m = 0; m < moduleIds.length; m++) {
|
||||
const flat = getFlatDependencies(moduleIds[m], rendererContext);
|
||||
if (mergeSlots.scripts !== scriptSeen) {
|
||||
scriptSeen = mergeSlots.scripts;
|
||||
styleSeen = mergeSlots.styles;
|
||||
preloadSeen = mergeSlots.preload;
|
||||
prefetchSeen = mergeSlots.prefetch;
|
||||
}
|
||||
for (let i = 0; i < flat.scriptSlots.length; i++) {
|
||||
const slot = flat.scriptSlots[i];
|
||||
if (scriptSeen[slot] === epoch) continue;
|
||||
scriptSeen[slot] = epoch;
|
||||
scriptSlots.push(slot);
|
||||
}
|
||||
for (let i = 0; i < flat.styleSlots.length; i++) {
|
||||
const slot = flat.styleSlots[i];
|
||||
if (styleSeen[slot] === epoch) continue;
|
||||
styleSeen[slot] = epoch;
|
||||
styleSlots.push(slot);
|
||||
}
|
||||
for (let i = 0; i < flat.preloadSlots.length; i++) {
|
||||
const slot = flat.preloadSlots[i];
|
||||
if (preloadSeen[slot] === epoch) continue;
|
||||
preloadSeen[slot] = epoch;
|
||||
preloadSlots.push(slot);
|
||||
}
|
||||
for (let i = 0; i < flat.prefetchSlots.length; i++) {
|
||||
const slot = flat.prefetchSlots[i];
|
||||
if (prefetchSeen[slot] === epoch) continue;
|
||||
prefetchSeen[slot] = epoch;
|
||||
prefetchSlots.push(slot);
|
||||
}
|
||||
}
|
||||
let kept = 0;
|
||||
for (let i = 0; i < preloadSlots.length; i++) {
|
||||
const slot = preloadSlots[i];
|
||||
if (styleSeen[slot] !== epoch) {
|
||||
preloadSlots[kept] = slot;
|
||||
kept++;
|
||||
}
|
||||
}
|
||||
preloadSlots.length = kept;
|
||||
kept = 0;
|
||||
const metaOf = mergeSlots.metaOf;
|
||||
for (let i = 0; i < prefetchSlots.length; i++) {
|
||||
const slot = prefetchSlots[i];
|
||||
if (metaOf[slot].prefetch && preloadSeen[slot] !== epoch && styleSeen[slot] !== epoch) {
|
||||
prefetchSlots[kept] = slot;
|
||||
kept++;
|
||||
}
|
||||
}
|
||||
prefetchSlots.length = kept;
|
||||
const order = {
|
||||
styleSlots,
|
||||
scriptSlots,
|
||||
preloadSlots,
|
||||
prefetchSlots,
|
||||
mergeSlots
|
||||
};
|
||||
const allDeps = new Proxy({ [SLOT_SOURCE]: order }, LAZY_DEPENDENCIES);
|
||||
rendererContext._renderedCache.set(allDeps, { order });
|
||||
if (useCache) {
|
||||
rendererContext._dependencySets.set(cacheKey, allDeps);
|
||||
if (rendererContext._dependencySets.size > cacheSize) {
|
||||
const oldest = rendererContext._dependencySets.keys().next().value;
|
||||
if (oldest !== void 0) rendererContext._dependencySets.delete(oldest);
|
||||
}
|
||||
if (hasAlias) setAlias(rendererContext, aliasKey, moduleIds, allDeps, cacheSize);
|
||||
}
|
||||
return allDeps;
|
||||
}
|
||||
function getRequestDependencies(ssrContext, rendererContext, options) {
|
||||
const excluded = options?.exclude ? new Set(options.exclude) : void 0;
|
||||
const hasExcluded = excluded && excluded.size > 0;
|
||||
if (!hasExcluded && ssrContext._requestDependencies) return ssrContext._requestDependencies;
|
||||
const requestIds = ssrContext.modules || ssrContext._registeredComponents;
|
||||
let aliasKey = 0;
|
||||
let hasAlias = false;
|
||||
if (!hasExcluded && rendererContext._dependencySetsCacheSize > 0) {
|
||||
aliasKey = aliasHash(rendererContext, rendererContext._entrypoints, requestIds);
|
||||
hasAlias = true;
|
||||
const aliased = readAlias(rendererContext, aliasKey, void 0, rendererContext._entrypoints, requestIds);
|
||||
if (aliased !== void 0) {
|
||||
ssrContext._requestDependencies = aliased;
|
||||
return aliased;
|
||||
}
|
||||
}
|
||||
const moduleIds = rendererContext._idScratch;
|
||||
moduleIds.length = 0;
|
||||
if (hasExcluded) {
|
||||
for (const id of rendererContext._entrypoints) if (!excluded.has(id)) moduleIds.push(id);
|
||||
if (requestIds) {
|
||||
for (const id of requestIds) if (!excluded.has(id)) moduleIds.push(id);
|
||||
}
|
||||
} else {
|
||||
for (const id of rendererContext._entrypoints) moduleIds.push(id);
|
||||
if (requestIds) for (const id of requestIds) moduleIds.push(id);
|
||||
}
|
||||
const deps = resolveDependencies(moduleIds, rendererContext, aliasKey, hasAlias);
|
||||
if (!hasExcluded) ssrContext._requestDependencies = deps;
|
||||
return deps;
|
||||
}
|
||||
function getRenderedOutputs(rendererContext, deps) {
|
||||
let entry = rendererContext._renderedCache.get(deps);
|
||||
if (!entry) {
|
||||
entry = {};
|
||||
rendererContext._renderedCache.set(deps, entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
const SLOT_SOURCE = Symbol("slots");
|
||||
const SLOTS_FOR = {
|
||||
scripts: "scriptSlots",
|
||||
styles: "styleSlots",
|
||||
preload: "preloadSlots",
|
||||
prefetch: "prefetchSlots"
|
||||
};
|
||||
/** Records are built on first access; rendering reads the slot arrays directly. */
|
||||
const LAZY_DEPENDENCIES = {
|
||||
get(target, key) {
|
||||
const value = target[key];
|
||||
if (value !== void 0 || !isRecordKey(key)) return value;
|
||||
return materialiseRecord(target, key);
|
||||
},
|
||||
has(target, key) {
|
||||
return isRecordKey(key) || key in target;
|
||||
},
|
||||
ownKeys(target) {
|
||||
const extra = Reflect.ownKeys(target).filter((key) => key !== SLOT_SOURCE && !isRecordKey(key));
|
||||
return extra.length > 0 ? [...RECORD_KEYS, ...extra] : RECORD_KEYS.slice();
|
||||
},
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
if (!isRecordKey(key)) return key === SLOT_SOURCE ? void 0 : Reflect.getOwnPropertyDescriptor(target, key);
|
||||
return {
|
||||
value: target[key] ?? materialiseRecord(target, key),
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
};
|
||||
},
|
||||
set(target, key, value) {
|
||||
target[key] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
return Reflect.deleteProperty(target, key);
|
||||
}
|
||||
};
|
||||
const RECORD_KEYS = [
|
||||
"scripts",
|
||||
"styles",
|
||||
"preload",
|
||||
"prefetch"
|
||||
];
|
||||
function isRecordKey(key) {
|
||||
return key === "scripts" || key === "styles" || key === "preload" || key === "prefetch";
|
||||
}
|
||||
function materialiseRecord(target, key) {
|
||||
const order = target[SLOT_SOURCE];
|
||||
const record = buildRecord(order.mergeSlots, order[SLOTS_FOR[key]]);
|
||||
target[key] = record;
|
||||
return record;
|
||||
}
|
||||
function buildRecord(mergeSlots, slots) {
|
||||
const { idOf, metaOf } = mergeSlots;
|
||||
const record = {};
|
||||
for (let i = 0; i < slots.length; i++) {
|
||||
const slot = slots[i];
|
||||
record[idOf[slot]] = metaOf[slot];
|
||||
}
|
||||
return record;
|
||||
}
|
||||
function collectOrder(source, slots, mergeSlots) {
|
||||
for (const id in source) slots.push(slotFor(mergeSlots, id, source[id]));
|
||||
}
|
||||
/** Records supplied by a caller carry no order, so derive and memoize one. */
|
||||
function getOrder(rendererContext, deps, rendered) {
|
||||
if (rendered.order) return rendered.order;
|
||||
const mergeSlots = rendererContext._mergeSlots;
|
||||
const order = {
|
||||
styleSlots: [],
|
||||
scriptSlots: [],
|
||||
preloadSlots: [],
|
||||
prefetchSlots: [],
|
||||
mergeSlots
|
||||
};
|
||||
collectOrder(deps.styles, order.styleSlots, mergeSlots);
|
||||
collectOrder(deps.scripts, order.scriptSlots, mergeSlots);
|
||||
collectOrder(deps.preload, order.preloadSlots, mergeSlots);
|
||||
collectOrder(deps.prefetch, order.prefetchSlots, mergeSlots);
|
||||
rendered.order = order;
|
||||
return order;
|
||||
}
|
||||
function renderStyles(ssrContext, rendererContext) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext);
|
||||
const rendered = getRenderedOutputs(rendererContext, deps);
|
||||
if (rendered.styles !== void 0) return rendered.styles;
|
||||
const order = getOrder(rendererContext, deps, rendered);
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
let result = "";
|
||||
const cache = rendererContext._fragments.style;
|
||||
for (let i = 0; i < order.styleSlots.length; i++) {
|
||||
const slot = order.styleSlots[i];
|
||||
let fragment = cache[slot];
|
||||
if (fragment === void 0) fragment = cache[slot] = `<link rel="stylesheet" href="${rendererContext.buildAssetsURL(metaOf[slot].file)}" crossorigin>`;
|
||||
result += fragment;
|
||||
}
|
||||
rendered.styles = result;
|
||||
return result;
|
||||
}
|
||||
function getResources(ssrContext, rendererContext) {
|
||||
return [...getPreloadLinks(ssrContext, rendererContext), ...getPrefetchLinks(ssrContext, rendererContext)];
|
||||
}
|
||||
function isScriptResource(resource) {
|
||||
return !!resource.module || resource.resourceType === "script";
|
||||
}
|
||||
function renderResourceHints(ssrContext, rendererContext, options) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext, options);
|
||||
const rendered = getRenderedOutputs(rendererContext, deps);
|
||||
const withScripts = options?.scripts !== false;
|
||||
const cached = withScripts ? rendered.hints : rendered.hintsWithoutScripts;
|
||||
if (cached !== void 0) return cached;
|
||||
const order = getOrder(rendererContext, deps, rendered);
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
let result = "";
|
||||
const preloadCache = rendererContext._fragments.preloadHint;
|
||||
for (let i = 0; i < order.preloadSlots.length; i++) {
|
||||
const slot = order.preloadSlots[i];
|
||||
if (!withScripts && isScriptResource(metaOf[slot])) continue;
|
||||
let fragment = preloadCache[slot];
|
||||
if (fragment === void 0) {
|
||||
const resource = metaOf[slot];
|
||||
const href = rendererContext.buildAssetsURL(resource.file);
|
||||
const rel = resource.module ? "modulepreload" : "preload";
|
||||
const crossorigin = resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? " crossorigin" : "";
|
||||
fragment = resource.resourceType && resource.mimeType ? `<link rel="${rel}" as="${resource.resourceType}" type="${resource.mimeType}"${crossorigin} href="${href}">` : resource.resourceType ? `<link rel="${rel}" as="${resource.resourceType}"${crossorigin} href="${href}">` : `<link rel="${rel}"${crossorigin} href="${href}">`;
|
||||
preloadCache[slot] = fragment;
|
||||
}
|
||||
result += fragment;
|
||||
}
|
||||
const prefetchCache = rendererContext._fragments.prefetchHint;
|
||||
for (let i = 0; i < order.prefetchSlots.length; i++) {
|
||||
const slot = order.prefetchSlots[i];
|
||||
if (!withScripts && isScriptResource(metaOf[slot])) continue;
|
||||
let fragment = prefetchCache[slot];
|
||||
if (fragment === void 0) {
|
||||
const resource = metaOf[slot];
|
||||
const href = rendererContext.buildAssetsURL(resource.file);
|
||||
const crossorigin = resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? " crossorigin" : "";
|
||||
fragment = resource.resourceType && resource.mimeType ? `<link rel="prefetch" as="${resource.resourceType}" type="${resource.mimeType}"${crossorigin} href="${href}">` : resource.resourceType ? `<link rel="prefetch" as="${resource.resourceType}"${crossorigin} href="${href}">` : `<link rel="prefetch"${crossorigin} href="${href}">`;
|
||||
prefetchCache[slot] = fragment;
|
||||
}
|
||||
result += fragment;
|
||||
}
|
||||
if (withScripts) rendered.hints = result;
|
||||
else rendered.hintsWithoutScripts = result;
|
||||
return result;
|
||||
}
|
||||
const NON_ASCII_RE = /[^\0-\u007F]+/g;
|
||||
function renderResourceHeaders(ssrContext, rendererContext, options) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext, options);
|
||||
const rendered = getRenderedOutputs(rendererContext, deps);
|
||||
const withScripts = options?.scripts !== false;
|
||||
const cached = withScripts ? rendered.headerLink : rendered.headerLinkWithoutScripts;
|
||||
if (cached !== void 0) return { link: cached };
|
||||
const order = getOrder(rendererContext, deps, rendered);
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
let link = "";
|
||||
const preloadCache = rendererContext._fragments.preloadHeader;
|
||||
for (let i = 0; i < order.preloadSlots.length; i++) {
|
||||
const slot = order.preloadSlots[i];
|
||||
if (!withScripts && isScriptResource(metaOf[slot])) continue;
|
||||
let header = preloadCache[slot];
|
||||
if (header === void 0) {
|
||||
const resource = metaOf[slot];
|
||||
header = `<${rendererContext.buildAssetsURL(resource.file).replace(NON_ASCII_RE, encodeURIComponent)}>; rel="${resource.module ? "modulepreload" : "preload"}"`;
|
||||
if (resource.resourceType) header += `; as="${resource.resourceType}"`;
|
||||
if (resource.mimeType) header += `; type="${resource.mimeType}"`;
|
||||
if (resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module) header += "; crossorigin";
|
||||
preloadCache[slot] = header;
|
||||
}
|
||||
link = link ? `${link}, ${header}` : header;
|
||||
}
|
||||
const prefetchCache = rendererContext._fragments.prefetchHeader;
|
||||
for (let i = 0; i < order.prefetchSlots.length; i++) {
|
||||
const slot = order.prefetchSlots[i];
|
||||
if (!withScripts && isScriptResource(metaOf[slot])) continue;
|
||||
let header = prefetchCache[slot];
|
||||
if (header === void 0) {
|
||||
const resource = metaOf[slot];
|
||||
header = `<${rendererContext.buildAssetsURL(resource.file).replace(NON_ASCII_RE, encodeURIComponent)}>; rel="prefetch"`;
|
||||
if (resource.resourceType) header += `; as="${resource.resourceType}"`;
|
||||
if (resource.mimeType) header += `; type="${resource.mimeType}"`;
|
||||
if (resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module) header += "; crossorigin";
|
||||
prefetchCache[slot] = header;
|
||||
}
|
||||
link = link ? `${link}, ${header}` : header;
|
||||
}
|
||||
if (withScripts) rendered.headerLink = link;
|
||||
else rendered.headerLinkWithoutScripts = link;
|
||||
return { link };
|
||||
}
|
||||
/** `buildAssetsURL` is a pure function of the id, so each result is built once. */
|
||||
function hrefFor(rendererContext, slot, resource) {
|
||||
const cache = rendererContext._fragments.href;
|
||||
let href = cache[slot];
|
||||
if (href === void 0) href = cache[slot] = rendererContext.buildAssetsURL(resource.file);
|
||||
return href;
|
||||
}
|
||||
function getPreloadLinks(ssrContext, rendererContext, options) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext, options);
|
||||
const order = getOrder(rendererContext, deps, getRenderedOutputs(rendererContext, deps));
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
const withScripts = options?.scripts !== false;
|
||||
const result = [];
|
||||
for (let i = 0; i < order.preloadSlots.length; i++) {
|
||||
const slot = order.preloadSlots[i];
|
||||
const resource = metaOf[slot];
|
||||
if (!withScripts && isScriptResource(resource)) continue;
|
||||
result.push({
|
||||
rel: resource.module ? "modulepreload" : "preload",
|
||||
as: resource.resourceType,
|
||||
type: resource.mimeType ?? null,
|
||||
crossorigin: resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? "" : null,
|
||||
href: hrefFor(rendererContext, slot, resource)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function getPrefetchLinks(ssrContext, rendererContext, options) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext, options);
|
||||
const order = getOrder(rendererContext, deps, getRenderedOutputs(rendererContext, deps));
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
const withScripts = options?.scripts !== false;
|
||||
const result = [];
|
||||
for (let i = 0; i < order.prefetchSlots.length; i++) {
|
||||
const slot = order.prefetchSlots[i];
|
||||
const resource = metaOf[slot];
|
||||
if (!withScripts && isScriptResource(resource)) continue;
|
||||
result.push({
|
||||
rel: "prefetch",
|
||||
as: resource.resourceType,
|
||||
type: resource.mimeType ?? null,
|
||||
crossorigin: resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? "" : null,
|
||||
href: hrefFor(rendererContext, slot, resource)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function renderScripts(ssrContext, rendererContext) {
|
||||
const deps = getRequestDependencies(ssrContext, rendererContext);
|
||||
const rendered = getRenderedOutputs(rendererContext, deps);
|
||||
if (rendered.scripts !== void 0) return rendered.scripts;
|
||||
const order = getOrder(rendererContext, deps, rendered);
|
||||
const metaOf = rendererContext._mergeSlots.metaOf;
|
||||
let result = "";
|
||||
const cache = rendererContext._fragments.script;
|
||||
for (let i = 0; i < order.scriptSlots.length; i++) {
|
||||
const slot = order.scriptSlots[i];
|
||||
let fragment = cache[slot];
|
||||
if (fragment === void 0) {
|
||||
const resource = metaOf[order.scriptSlots[i]];
|
||||
fragment = cache[slot] = resource.module ? `<script type="module" src="${rendererContext.buildAssetsURL(resource.file)}" crossorigin><\/script>` : `<script src="${rendererContext.buildAssetsURL(resource.file)}" defer crossorigin><\/script>`;
|
||||
}
|
||||
result += fragment;
|
||||
}
|
||||
rendered.scripts = result;
|
||||
return result;
|
||||
}
|
||||
function createRenderer(createApp, renderOptions) {
|
||||
const rendererContext = createRendererContext(renderOptions);
|
||||
return {
|
||||
rendererContext,
|
||||
async renderToString(ssrContext) {
|
||||
ssrContext._registeredComponents = ssrContext._registeredComponents || /* @__PURE__ */ new Set();
|
||||
const app = await (await Promise.resolve(createApp).then((r) => "default" in r ? r.default : r))(ssrContext);
|
||||
const html = await renderOptions.renderToString(app, ssrContext);
|
||||
const wrap = (fn) => () => fn(ssrContext, rendererContext);
|
||||
return {
|
||||
html,
|
||||
renderResourceHeaders: wrap(renderResourceHeaders),
|
||||
renderResourceHints: wrap(renderResourceHints),
|
||||
renderStyles: wrap(renderStyles),
|
||||
renderScripts: wrap(renderScripts)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { createRenderer, createRendererContext, getAllDependencies, getModuleDependencies, getPrefetchLinks, getPreloadLinks, getRequestDependencies, getResources, renderResourceHeaders, renderResourceHints, renderScripts, renderStyles };
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "vue-bundle-renderer",
|
||||
"version": "2.4.0",
|
||||
"description": "Bundle Renderer for Vue 3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "nuxt-contrib/vue-bundle-renderer"
|
||||
},
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./runtime": {
|
||||
"types": "./dist/runtime.d.mts",
|
||||
"import": "./dist/runtime.mjs",
|
||||
"require": "./dist/runtime.cjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"dist",
|
||||
"runtime.d.ts"
|
||||
],
|
||||
"dependencies": {
|
||||
"ufo": "^1.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codspeed/vitest-plugin": "^5.7.1",
|
||||
"@nuxt/eslint-config": "^1.17.0",
|
||||
"@types/node": "^24.13.4",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
"eslint": "^10.10.0",
|
||||
"pkg-pr-new": "^0.0.88",
|
||||
"std-env": "^4.2.0",
|
||||
"tsdown": "0.23.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.3.0",
|
||||
"vitest": "4.1.11",
|
||||
"vue": "3.5.42"
|
||||
},
|
||||
"devEngines": {
|
||||
"runtime": {
|
||||
"name": "node",
|
||||
"version": "^24.0.0",
|
||||
"onFail": "download"
|
||||
},
|
||||
"packageManager": {
|
||||
"name": "pnpm",
|
||||
"version": "12.3.4",
|
||||
"onFail": "download"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "vitest",
|
||||
"lint": "eslint src",
|
||||
"test": "pnpm lint && pnpm vitest run --coverage && tsc --noEmit",
|
||||
"bench": "vitest bench"
|
||||
}
|
||||
}
|
||||
+1060
File diff suppressed because it is too large
Load Diff
+444
@@ -0,0 +1,444 @@
|
||||
/*!
|
||||
* vue-router v5.3.1
|
||||
* (c) 2026 Eduardo San Martin Morote
|
||||
* @license MIT
|
||||
*/
|
||||
import { createConsoleReporter, defineDiagnostics } from "nostics";
|
||||
import { inject } from "vue";
|
||||
//#region src/utils/index.ts
|
||||
/**
|
||||
* Identity function that returns the value as is.
|
||||
*
|
||||
* @param v - the value to return
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const identityFn = (v) => v;
|
||||
/**
|
||||
* Checks if a path is absolute, meaning it starts with a `/`.
|
||||
*
|
||||
* @param path - path to check
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const isAbsolutePath = (path) => path.startsWith("/");
|
||||
/**
|
||||
* Allows differentiating lazy components from functional components and vue-class-component
|
||||
* @internal
|
||||
*
|
||||
* @param component
|
||||
*/
|
||||
function isRouteComponent(component) {
|
||||
return typeof component === "object" || "displayName" in component || "props" in component || "__vccOpts" in component;
|
||||
}
|
||||
function isESModule(obj) {
|
||||
return obj.__esModule || obj[Symbol.toStringTag] === "Module" || obj.default && isRouteComponent(obj.default);
|
||||
}
|
||||
const assign = Object.assign;
|
||||
function applyToParams(fn, params) {
|
||||
const newParams = {};
|
||||
for (const key in params) {
|
||||
const value = params[key];
|
||||
newParams[key] = isArray(value) ? value.map(fn) : fn(value);
|
||||
}
|
||||
return newParams;
|
||||
}
|
||||
const noop = () => {};
|
||||
/**
|
||||
* Typesafe alternative to Array.isArray
|
||||
* https://github.com/microsoft/TypeScript/pull/48228
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const isArray = Array.isArray;
|
||||
function mergeOptions(defaults, partialOptions) {
|
||||
const options = {};
|
||||
for (const key in defaults) options[key] = key in partialOptions ? partialOptions[key] : defaults[key];
|
||||
return options;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/errors.ts
|
||||
const NavigationFailureSymbol = Symbol(process.env.NODE_ENV !== "production" ? "navigation failure" : "");
|
||||
/**
|
||||
* Enumeration with all possible types for navigation failures. Can be passed to
|
||||
* {@link isNavigationFailure} to check for specific failures.
|
||||
*/
|
||||
let NavigationFailureType = /* @__PURE__ */ function(NavigationFailureType) {
|
||||
/**
|
||||
* An aborted navigation is a navigation that failed because a navigation
|
||||
* guard returned `false` or called `next(false)`
|
||||
*/
|
||||
NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";
|
||||
/**
|
||||
* A cancelled navigation is a navigation that failed because a more recent
|
||||
* navigation finished started (not necessarily finished).
|
||||
*/
|
||||
NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";
|
||||
/**
|
||||
* A duplicated navigation is a navigation that failed because it was
|
||||
* initiated while already being at the exact same location.
|
||||
*/
|
||||
NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";
|
||||
return NavigationFailureType;
|
||||
}({});
|
||||
const ErrorTypeMessages = {
|
||||
[1]({ location, currentLocation }) {
|
||||
return `No match for\n ${JSON.stringify(location)}${currentLocation ? "\nwhile being at\n" + JSON.stringify(currentLocation) : ""}`;
|
||||
},
|
||||
[2]({ from, to }) {
|
||||
return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
|
||||
},
|
||||
[4]({ from, to }) {
|
||||
return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
|
||||
},
|
||||
[8]({ from, to }) {
|
||||
return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
|
||||
},
|
||||
[16]({ from, to: _to }) {
|
||||
return `Avoided redundant navigation to current location: "${from.fullPath}".`;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Creates a typed NavigationFailure object.
|
||||
* @internal
|
||||
* @param type - NavigationFailureType
|
||||
* @param params - { from, to }
|
||||
*/
|
||||
function createRouterError(type, params) {
|
||||
if (process.env.NODE_ENV !== "production" || false) return assign(new Error(ErrorTypeMessages[type](params)), {
|
||||
type,
|
||||
[NavigationFailureSymbol]: true
|
||||
}, params);
|
||||
else return assign(/* @__PURE__ */ new Error(), {
|
||||
type,
|
||||
[NavigationFailureSymbol]: true
|
||||
}, params);
|
||||
}
|
||||
function isNavigationFailure(error, type) {
|
||||
return error instanceof Error && NavigationFailureSymbol in error && (type == null || !!(error.type & type));
|
||||
}
|
||||
const propertiesToLog = [
|
||||
"params",
|
||||
"query",
|
||||
"hash"
|
||||
];
|
||||
/**
|
||||
* Stringifies a raw location for display in dev warnings.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function stringifyRoute(to) {
|
||||
if (!to || typeof to === "string") return to;
|
||||
if (to.path != null) return to.path;
|
||||
const location = {};
|
||||
for (const key of propertiesToLog) if (key in to) location[key] = to[key];
|
||||
return JSON.stringify(location, null, 2);
|
||||
}
|
||||
//#endregion
|
||||
//#region src/diagnostics.ts
|
||||
/**
|
||||
* Runtime diagnostics catalog for Vue Router.
|
||||
*
|
||||
* Every entry has a stable `VUE_ROUTER_R####` code, a `why` that states the problem
|
||||
* (the diagnosis only, never the remedy) and a `fix` that states the remedy
|
||||
* (only, never the diagnosis). They are complementary: the reporter prints
|
||||
* both, so neither repeats the other. The diagnosis substrings asserted by the
|
||||
* warning tests stay in `why`. All call sites stay behind the existing `__DEV__` (or
|
||||
* `process.env.NODE_ENV !== 'production'`) guards and remain bare expression
|
||||
* statements so they tree-shake out of production builds.
|
||||
*
|
||||
* Codes are permanent: never rename or reuse one.
|
||||
* - `VUE_ROUTER_R0###` core runtime warnings
|
||||
* - `VUE_ROUTER_R1###` experimental data-loaders
|
||||
*/
|
||||
const diagnostics = /*#__PURE__*/ defineDiagnostics({
|
||||
reporters: [/*#__PURE__*/ createConsoleReporter()],
|
||||
codes: {
|
||||
VUE_ROUTER_R0001: {
|
||||
why: (p) => `Parent route "${p.name}" not found when adding child route`,
|
||||
fix: "Add the parent route before its children, or check the parent name for typos.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/dynamic-routing.html#Adding-nested-routes"
|
||||
},
|
||||
VUE_ROUTER_R0002: {
|
||||
why: (p) => `Cannot remove non-existent route "${p.name}"`,
|
||||
fix: "Check the route name; it may already have been removed or was never added.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/dynamic-routing.html#Removing-routes"
|
||||
},
|
||||
VUE_ROUTER_R0003: {
|
||||
why: (p) => `Location "${stringifyRoute(p.location)}" resolved to "${p.href}". A resolved location cannot start with multiple slashes.`,
|
||||
fix: "Remove the leading slashes from the location or fix the route configuration."
|
||||
},
|
||||
VUE_ROUTER_R0004: {
|
||||
why: (p) => `No match found for location with path "${stringifyRoute(p.path)}"`,
|
||||
fix: "Add a route matching this path or check for typos in the location.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/dynamic-matching.html#Catch-all-404-Not-found-Route"
|
||||
},
|
||||
VUE_ROUTER_R0005: {
|
||||
why: (p) => `router.resolve() was passed an invalid location. This will fail in production.\nLocation: ${stringifyRoute(p.rawLocation)}`,
|
||||
fix: "Pass a valid route location: a string path or an object with `path` or `name`."
|
||||
},
|
||||
VUE_ROUTER_R0006: {
|
||||
why: (p) => `Path "${p.path}" was passed with params but they will be ignored because a "path" was passed.`,
|
||||
fix: "Use a named route `{ name, params }` instead of `{ path, params }`.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/navigation.html#Navigate-to-a-different-location"
|
||||
},
|
||||
VUE_ROUTER_R0007: {
|
||||
why: (p) => `A \`hash\` should always start with the character "#" but received "${p.hash}".`,
|
||||
fix: (p) => `Prepend "#" to the hash in your route location: use "#${p.hash}".`
|
||||
},
|
||||
VUE_ROUTER_R0008: {
|
||||
why: (p) => `Invalid redirect found:\n${p.target}\n when navigating to "${p.to}".\nThis will break in production.`,
|
||||
fix: "A redirect must resolve to a location with a `name` or `path`; return one of those (or a string path) from `redirect`.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/redirect-and-alias.html#Redirect"
|
||||
},
|
||||
VUE_ROUTER_R0009: {
|
||||
why: (p) => `Detected a possibly infinite redirection in a navigation guard when going from "${p.from}" to "${p.to}". Aborting to avoid a Stack Overflow. This might break in production if not fixed.`,
|
||||
fix: "A guard is returning a new location on every call; make that return conditional so it only redirects when actually needed.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Global-Before-Guards"
|
||||
},
|
||||
VUE_ROUTER_R0010: {
|
||||
why: "Uncaught error during route navigation",
|
||||
fix: "Register an error handler with `router.onError()` to handle navigation errors."
|
||||
},
|
||||
VUE_ROUTER_R0011: {
|
||||
why: "Unexpected error when starting the router:",
|
||||
fix: "Inspect the actual cause; a navigation guard or async component likely threw during the initial navigation."
|
||||
},
|
||||
VUE_ROUTER_R0020: {
|
||||
why: (p) => `No active route record was found when calling \`${p.fn}()\`. Maybe you called it inside of App.vue?`,
|
||||
fix: "Call it from a component rendered inside <router-view> (a page component or one of its children), not from App.vue.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/composition-api.html#Navigation-Guards"
|
||||
},
|
||||
VUE_ROUTER_R0021: {
|
||||
why: "No active route record was found when reactivating component with navigation guard. This is likely a bug in vue-router.",
|
||||
fix: "Report with a minimal reproduction at https://github.com/vuejs/router/issues/new/choose."
|
||||
},
|
||||
VUE_ROUTER_R0022: {
|
||||
why: (p) => `${p.fn}() was called outside of component setup but it must be called at the top of a setup function`,
|
||||
fix: "Call it synchronously at the top of `setup()`, before any `await`.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/composition-api.html#Navigation-Guards"
|
||||
},
|
||||
VUE_ROUTER_R0023: {
|
||||
why: (p) => `The "next" callback was never called inside of ${p.name ? `"${p.name}"` : ""}:\n${p.guard}`,
|
||||
fix: "Make sure `next()` runs on every branch, including early returns and async paths, or drop the `next` parameter and return the value instead.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
|
||||
},
|
||||
VUE_ROUTER_R0024: {
|
||||
why: (p) => `The "next" callback was called more than once in one navigation guard when going from "${p.from}" to "${p.to}". This will fail in production.`,
|
||||
fix: "Call `next()` exactly once per guard: remove the extra call, or migrate to returning the value you passed to `next()`.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
|
||||
},
|
||||
VUE_ROUTER_R0025: {
|
||||
why: "The `next()` callback in navigation guards is deprecated.",
|
||||
fix: "Return the value instead: `next()` becomes `return`, `next(false)` becomes `return false`, `next(\"/path\")` becomes `return \"/path\"`.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/navigation-guards.html#Optional-third-argument-next"
|
||||
},
|
||||
VUE_ROUTER_R0026: {
|
||||
why: (p) => `Record with path "${p.path}" is either missing a "component(s)" or "children" property.`,
|
||||
fix: "Add a `component`, `components`, or `children` to the route record.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html"
|
||||
},
|
||||
VUE_ROUTER_R0027: {
|
||||
why: (p) => `Component "${p.name}" in record with path "${p.path}" is not a valid component. Received "${p.received}".`,
|
||||
fix: "Pass a component or a function returning a Promise that resolves to one."
|
||||
},
|
||||
VUE_ROUTER_R0028: {
|
||||
why: (p) => `Component "${p.name}" in record with path "${p.path}" is a Promise instead of a function that returns a Promise. This will break in production if not fixed.`,
|
||||
fix: `Defer the import in an arrow function so it loads lazily: write "() => import('./MyPage.vue')", not "import('./MyPage.vue')".`,
|
||||
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html"
|
||||
},
|
||||
VUE_ROUTER_R0029: {
|
||||
why: (p) => `Component "${p.name}" in record with path "${p.path}" is defined using "defineAsyncComponent()".`,
|
||||
fix: `Drop the wrapper and pass "() => import('./MyPage.vue')" directly; the router handles lazy components itself.`,
|
||||
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html#Relationship-to-async-components"
|
||||
},
|
||||
VUE_ROUTER_R0030: {
|
||||
why: (p) => `Component "${p.name}" in record with path "${p.path}" is a function that does not return a Promise. This will break in production if not fixed.`,
|
||||
fix: "Return a dynamic import (`() => import(\"./MyPage.vue\")`) from the function, or add a `displayName` if it is a functional component.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/lazy-loading.html"
|
||||
},
|
||||
VUE_ROUTER_R0040: {
|
||||
why: (p) => `Because "${p.el}" starts with "#", scrollBehavior resolves it as an element id via document.getElementById("${p.el.slice(1)}"), not as a CSS selector. No element has that id, but "${p.el}" does match an element with document.querySelector().`,
|
||||
fix: (p) => `Resolve the element yourself and return the node: el: document.querySelector('${p.el}').`,
|
||||
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
|
||||
},
|
||||
VUE_ROUTER_R0041: {
|
||||
why: (p) => `The selector "${p.el}" is invalid. See https://mathiasbynens.be/notes/css-escapes or CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape) for the escaping rules.`,
|
||||
fix: "Build an id selector as `#${CSS.escape(id)}` so special characters in the id are escaped.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
|
||||
},
|
||||
VUE_ROUTER_R0042: {
|
||||
why: (p) => `Couldn't find element using selector "${p.el}" returned by scrollBehavior.`,
|
||||
fix: "Return a selector that matches an existing element, or guard against missing elements.",
|
||||
docs: "https://router.vuejs.org/guide/advanced/scroll-behavior.html"
|
||||
},
|
||||
VUE_ROUTER_R0050: {
|
||||
why: (p) => {
|
||||
let to;
|
||||
try {
|
||||
to = p.to === void 0 ? "undefined" : JSON.stringify(p.to);
|
||||
} catch {
|
||||
to = String(p.to);
|
||||
}
|
||||
return `Invalid value for prop "to" in useLink()\n- to: ${to}`;
|
||||
},
|
||||
fix: "Pass a valid route location (a string path or an object) to the \"to\" prop."
|
||||
},
|
||||
VUE_ROUTER_R0060: {
|
||||
why: (p) => `<router-view> can no longer be used directly inside <${p.comp}>.`,
|
||||
fix: (p) => `Wrap the slot's resolved component with <${p.comp}> instead of nesting <router-view> in it:\n\n<router-view v-slot="{ Component }">\n <${p.comp}>\n <component :is="Component" />\n </${p.comp}>\n</router-view>`,
|
||||
docs: "https://router.vuejs.org/guide/advanced/router-view-slot.html#KeepAlive-Transition"
|
||||
},
|
||||
VUE_ROUTER_R0070: {
|
||||
why: (p) => `Cannot resolve a relative location without an absolute path. Trying to resolve "${p.to}" from "${p.from}".`,
|
||||
fix: (p) => `Resolve from an absolute \`from\` path that starts with "/", e.g. "/${p.from}".`
|
||||
},
|
||||
VUE_ROUTER_R0080: {
|
||||
why: (p) => `Error decoding "${p.text}". Using original value`,
|
||||
fix: "Ensure the value is correctly percent-encoded."
|
||||
},
|
||||
VUE_ROUTER_R0090: {
|
||||
why: (p) => `Found duplicated params with name "${p.name}" for path "${p.path}". Only the last one will be available on "$route.params".`,
|
||||
fix: "Give each param a unique name within the path.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/route-matching-syntax.html"
|
||||
},
|
||||
VUE_ROUTER_R0100: {
|
||||
why: (p) => `Discarded invalid param(s) "${p.params}" when navigating.` + p.inherited + ` See https://github.com/vuejs/router/commit/e887570 for more details.`,
|
||||
fix: "Only pass params that exist on the target route."
|
||||
},
|
||||
VUE_ROUTER_R0101: {
|
||||
why: (p) => `The Matcher cannot resolve relative paths but received "${p.path}". Unless you directly called \`matcher.resolve("${p.path}")\`, this is probably a bug in vue-router. Please open an issue at https://github.com/vuejs/router/issues/new/choose.`,
|
||||
fix: "Pass an absolute path (starting with \"/\") to the matcher."
|
||||
},
|
||||
VUE_ROUTER_R0102: {
|
||||
why: (p) => `Alias "${p.alias}" and the original record: "${p.original}" must have the exact same param named "${p.name}"`,
|
||||
fix: "Use the same param names in the alias as in the original route.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/redirect-and-alias.html#Alias"
|
||||
},
|
||||
VUE_ROUTER_R0103: {
|
||||
why: (p) => `The route named "${p.name}" has a child without a name, an empty path, and no children. Using that name won't render the empty path child, so this is probably a mistake.`,
|
||||
fix: "Move the `name` onto the empty-path child; or, if intentional, give the child its own name to silence this.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html#Nested-Named-Routes"
|
||||
},
|
||||
VUE_ROUTER_R0104: {
|
||||
why: (p) => `Absolute path "${p.path}" must have the exact same param named "${p.name}" as its parent "${p.parent}".`,
|
||||
fix: "Include the parent route params in the absolute child path.",
|
||||
docs: "https://router.vuejs.org/guide/essentials/nested-routes.html"
|
||||
},
|
||||
VUE_ROUTER_R0105: {
|
||||
why: (p) => `Finding ancestor route "${p.ancestor}" failed for "${p.record}"`,
|
||||
fix: "Report a reproduction at https://github.com/vuejs/router/issues/new/choose."
|
||||
},
|
||||
VUE_ROUTER_R0110: {
|
||||
why: `A hash base must end with a "#"`,
|
||||
fix: (p) => `Append "#" to the "base" argument passed to "createWebHashHistory()": "${p.base}" should be "${p.suggestion}".`
|
||||
},
|
||||
VUE_ROUTER_R0120: {
|
||||
why: "Error with push/replace State",
|
||||
fix: "The browser rejected the history API call; check for cross-origin or rate-limit issues."
|
||||
},
|
||||
VUE_ROUTER_R0121: {
|
||||
why: "history.state seems to have been manually replaced without preserving the necessary values.\nYou can find more information at https://router.vuejs.org/guide/migration/#Usage-of-history-state",
|
||||
fix: "Merge the router's state into your own when calling it manually: `history.replaceState({ ...history.state, ...yourState }, '', url)`.",
|
||||
docs: "https://router.vuejs.org/guide/migration.html#Usage-of-history-state"
|
||||
},
|
||||
VUE_ROUTER_R1001: {
|
||||
why: (p) => `Data loader "${String(p.key)}" has a different parent than the current context. This shouldn't be happening.`,
|
||||
fix: "Report a bug with a minimal reproduction at https://github.com/vuejs/router/."
|
||||
},
|
||||
VUE_ROUTER_R1002: {
|
||||
why: "Returning a NavigationResult is deprecated.",
|
||||
fix: "Replace `return new NavigationResult(to)` with `reroute(to)`, which throws internally to reroute.",
|
||||
docs: "https://router.vuejs.org/data-loaders/navigation-aware.html#Controlling-the-navigation-with-reroute-"
|
||||
},
|
||||
VUE_ROUTER_R1003: {
|
||||
why: (p) => `Loader "${p.key}"'s "commit()" was called but there is no staged data.`,
|
||||
fix: "Ensure the loader resolved before calling `commit()`.",
|
||||
docs: "https://router.vuejs.org/data-loaders/defining-loaders.html#Delaying-data-updates-with-commit"
|
||||
},
|
||||
VUE_ROUTER_R1004: {
|
||||
why: (p) => "A loader returned a NavigationResult but is not registered on the route." + p.key,
|
||||
fix: "Export the loader from the page component so it gets registered, e.g. `export const useUserData = defineLoader(...)`.",
|
||||
docs: "https://router.vuejs.org/data-loaders/organization.html"
|
||||
},
|
||||
VUE_ROUTER_R1005: {
|
||||
why: (p) => `Data loader "${p.key}" has itself as parent. This shouldn't be happening.`,
|
||||
fix: "Report a bug with a minimal reproduction at https://github.com/vuejs/router/."
|
||||
},
|
||||
VUE_ROUTER_R1006: {
|
||||
why: (p) => `A query was defined with the same key as the loader "[${p.key}]".\nSee https://pinia-colada.esm.dev/#TODO`,
|
||||
fix: "If the key is meant to match, use the data loader directly; otherwise rename the `useQuery()` key so it no longer collides.",
|
||||
docs: "https://router.vuejs.org/data-loaders/colada.html"
|
||||
},
|
||||
VUE_ROUTER_R1007: {
|
||||
why: "Data Loader was setup twice.",
|
||||
fix: "Register `DataLoaderPlugin` a single time via `app.use()`.",
|
||||
docs: "https://router.vuejs.org/data-loaders.html#Installation"
|
||||
},
|
||||
VUE_ROUTER_R1008: {
|
||||
why: "Data Loader is experimental and subject to breaking changes in the future.",
|
||||
docs: "https://router.vuejs.org/data-loaders.html"
|
||||
},
|
||||
VUE_ROUTER_R1009: {
|
||||
why: "Returning a NavigationResult from a loader is deprecated.",
|
||||
fix: "Call `reroute(to)` inside the loader instead of returning `new NavigationResult(to)`; it throws internally to reroute.",
|
||||
docs: "https://router.vuejs.org/data-loaders/navigation-aware.html#Controlling-the-navigation-with-reroute-"
|
||||
}
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
//#region src/injectionSymbols.ts
|
||||
/**
|
||||
* RouteRecord being rendered by the closest ancestor Router View. Used for
|
||||
* `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
|
||||
* Location Matched
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const matchedRouteKey = Symbol(process.env.NODE_ENV !== "production" ? "router view location matched" : "");
|
||||
/**
|
||||
* Allows overriding the router view depth to control which component in
|
||||
* `matched` is rendered. rvd stands for Router View Depth
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const viewDepthKey = Symbol(process.env.NODE_ENV !== "production" ? "router view depth" : "");
|
||||
/**
|
||||
* Allows overriding the router instance returned by `useRouter` in tests. r
|
||||
* stands for router
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const routerKey = Symbol(process.env.NODE_ENV !== "production" ? "router" : "");
|
||||
/**
|
||||
* Allows overriding the current route returned by `useRoute` in tests. rl
|
||||
* stands for route location
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const routeLocationKey = Symbol(process.env.NODE_ENV !== "production" ? "route location" : "");
|
||||
/**
|
||||
* Allows overriding the current route used by router-view. Internally this is
|
||||
* used when the `route` prop is passed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const routerViewLocationKey = Symbol(process.env.NODE_ENV !== "production" ? "router view location" : "");
|
||||
//#endregion
|
||||
//#region src/useApi.ts
|
||||
/**
|
||||
* Returns the router instance. Equivalent to using `$router` inside
|
||||
* templates.
|
||||
*/
|
||||
function useRouter() {
|
||||
return inject(routerKey);
|
||||
}
|
||||
/**
|
||||
* Returns the current route location. Equivalent to using `$route` inside
|
||||
* templates.
|
||||
*/
|
||||
function useRoute(_name) {
|
||||
return inject(routeLocationKey);
|
||||
}
|
||||
//#endregion
|
||||
export { isESModule as _, routerKey as a, noop as b, diagnostics as c, isNavigationFailure as d, applyToParams as f, isArray as g, isAbsolutePath as h, routeLocationKey as i, NavigationFailureType as l, identityFn as m, useRouter as n, routerViewLocationKey as o, assign as p, matchedRouteKey as r, viewDepthKey as s, useRoute as t, createRouterError as u, isRouteComponent as v, mergeOptions as y };
|
||||
+1560
File diff suppressed because it is too large
Load Diff
+211
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"name": "vue-router",
|
||||
"version": "5.3.1",
|
||||
"homepage": "https://router.vuejs.org",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/router/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Eduardo San Martin Morote",
|
||||
"email": "posva13@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/router.git"
|
||||
},
|
||||
"funding": "https://github.com/sponsors/posva",
|
||||
"files": [
|
||||
"dist",
|
||||
"index.cjs",
|
||||
"README.md",
|
||||
"route.schema.json",
|
||||
"vue-router-auto-resolver.d.mts",
|
||||
"vue-router-auto-routes.d.mts",
|
||||
"vue-router-auto.d.ts",
|
||||
"vue-router.node.mjs",
|
||||
"vetur/attributes.json",
|
||||
"vetur/tags.json"
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"main": "index.cjs",
|
||||
"module": "dist/vue-router.js",
|
||||
"types": "dist/vue-router.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"vite": [
|
||||
"./dist/unplugin/vite.d.mts"
|
||||
],
|
||||
"unplugin": [
|
||||
"./dist/unplugin/index.d.mts"
|
||||
],
|
||||
"unplugin/*": [
|
||||
"./dist/unplugin/*.d.mts"
|
||||
],
|
||||
"volar/*": [
|
||||
"./dist/volar/*.d.cts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"unpkg": "dist/vue-router.global.js",
|
||||
"jsdelivr": "dist/vue-router.global.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/vue-router.d.ts",
|
||||
"node": {
|
||||
"import": {
|
||||
"production": "./vue-router.node.mjs",
|
||||
"development": "./vue-router.node.mjs",
|
||||
"default": "./vue-router.node.mjs"
|
||||
},
|
||||
"require": {
|
||||
"production": "./dist/vue-router.prod.cjs",
|
||||
"development": "./dist/vue-router.cjs",
|
||||
"default": "./dist/vue-router.prod.cjs"
|
||||
}
|
||||
},
|
||||
"import": "./dist/vue-router.js",
|
||||
"require": "./index.cjs"
|
||||
},
|
||||
"./vetur/*": "./vetur/*",
|
||||
"./package.json": "./package.json",
|
||||
"./experimental": {
|
||||
"types": "./dist/experimental/index.d.ts",
|
||||
"import": "./dist/experimental/index.js"
|
||||
},
|
||||
"./experimental/pinia-colada": {
|
||||
"types": "./dist/experimental/pinia-colada.d.ts",
|
||||
"import": "./dist/experimental/pinia-colada.js"
|
||||
},
|
||||
"./auto-resolver": {
|
||||
"types": "./vue-router-auto-resolver.d.mts"
|
||||
},
|
||||
"./auto-routes": {
|
||||
"types": "./vue-router-auto-routes.d.mts"
|
||||
},
|
||||
"./auto": {
|
||||
"types": "./vue-router-auto.d.ts",
|
||||
"node": {
|
||||
"import": {
|
||||
"production": "./vue-router.node.mjs",
|
||||
"development": "./vue-router.node.mjs",
|
||||
"default": "./vue-router.node.mjs"
|
||||
},
|
||||
"require": {
|
||||
"production": "./dist/vue-router.prod.cjs",
|
||||
"development": "./dist/vue-router.cjs",
|
||||
"default": "./dist/vue-router.prod.cjs"
|
||||
}
|
||||
},
|
||||
"import": "./dist/vue-router.js",
|
||||
"require": "./index.cjs"
|
||||
},
|
||||
"./vite": {
|
||||
"types": "./dist/unplugin/vite.d.mts",
|
||||
"import": "./dist/unplugin/vite.mjs",
|
||||
"require": "./dist/unplugin/vite.cjs"
|
||||
},
|
||||
"./unplugin": {
|
||||
"types": "./dist/unplugin/index.d.mts",
|
||||
"import": "./dist/unplugin/index.mjs",
|
||||
"require": "./dist/unplugin/index.cjs"
|
||||
},
|
||||
"./unplugin/*": "./dist/unplugin/*",
|
||||
"./volar/sfc-route-blocks": {
|
||||
"types": "./dist/volar/sfc-route-blocks.d.cts",
|
||||
"default": "./dist/volar/sfc-route-blocks.cjs"
|
||||
},
|
||||
"./volar/sfc-typed-router": {
|
||||
"types": "./dist/volar/sfc-typed-router.d.cts",
|
||||
"default": "./dist/volar/sfc-typed-router.cjs"
|
||||
},
|
||||
"./dist/*": "./dist/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue-macros/common": "^3.1.3",
|
||||
"@vue/devtools-api": "^8.1.5",
|
||||
"ast-walker-scope": "^0.9.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"confbox": "^0.2.4",
|
||||
"local-pkg": "^1.2.1",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.2",
|
||||
"muggle-string": "^0.4.1",
|
||||
"nostics": "^1.1.4",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.5",
|
||||
"scule": "^1.3.0",
|
||||
"tinyglobby": "^0.2.17",
|
||||
"unplugin": "^3.3.0",
|
||||
"unplugin-utils": "^0.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^8.0.0",
|
||||
"@pinia/colada": "^1.3.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@rollup/plugin-commonjs": "^29.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@rollup/plugin-terser": "^1.0.0",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/picomatch": "^4.0.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@vue/compiler-sfc": "^3.5.34",
|
||||
"@vue/language-core": "^3.3.7",
|
||||
"@vue/server-renderer": "^3.5.34",
|
||||
"@vue/test-utils": "^2.4.11",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"faked-promise": "^2.2.2",
|
||||
"happy-dom": "^20.10.6",
|
||||
"pinia": "^3.0.4",
|
||||
"rimraf": "^6.1.3",
|
||||
"rollup": "^4.62.2",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"tsdown": "^0.22.7",
|
||||
"tsup": "^8.5.1",
|
||||
"valibot": "^1.4.2",
|
||||
"vite": "^8.2.0",
|
||||
"vue": "^3.5.34",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pinia/colada": ">=0.21.2",
|
||||
"@vue/compiler-sfc": "^3.5.34 || ^4.0.0",
|
||||
"pinia": "^3.0.4 || ^4.0.2",
|
||||
"vite": "^7.3.0 || ^8.0.0",
|
||||
"vue": "^3.5.34 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/compiler-sfc": {
|
||||
"optional": true
|
||||
},
|
||||
"pinia": {
|
||||
"optional": true
|
||||
},
|
||||
"@pinia/colada": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"vetur": {
|
||||
"tags": "vetur/tags.json",
|
||||
"attributes": "vetur/attributes.json"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vitest --ui",
|
||||
"build": "tsdown",
|
||||
"build:old": "rimraf dist && rollup -c rollup.config.mjs",
|
||||
"build:playground": "vue-tsc --noEmit && vite build --config playground/vite.config.ts",
|
||||
"build:e2e": "vue-tsc --noEmit && vite build --config e2e/vite.config.mjs",
|
||||
"build:size": "pnpm run build && rollup -c size-checks/rollup.config.mjs",
|
||||
"dev:e2e": "vite --config e2e/vite.config.mjs",
|
||||
"test:types": "tsgo --build tsconfig.json ./test-dts/tsconfig.experimental.json",
|
||||
"test:unit": "vitest --coverage run",
|
||||
"test": "pnpm run build && pnpm run '/^(test:unit|test:types)$/' && pnpm run test:e2e",
|
||||
"test:e2e": "playwright test"
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
global.__VUE_PROD_DEVTOOLS__ = false
|
||||
export * from './dist/vue-router.js'
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* vue v3.5.43
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
* @license MIT
|
||||
**/
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var compilerDom = require('@vue/compiler-dom');
|
||||
var runtimeDom = require('@vue/runtime-dom');
|
||||
var shared = require('@vue/shared');
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
var n = Object.create(null);
|
||||
if (e) {
|
||||
for (var k in e) {
|
||||
n[k] = e[k];
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
var runtimeDom__namespace = /*#__PURE__*/_interopNamespaceDefault(runtimeDom);
|
||||
|
||||
const compileCache = /* @__PURE__ */ Object.create(null);
|
||||
function compileToFunction(template, options) {
|
||||
if (!shared.isString(template)) {
|
||||
if (template.nodeType) {
|
||||
template = template.innerHTML;
|
||||
} else {
|
||||
runtimeDom.warn(`invalid template option: `, template);
|
||||
return shared.NOOP;
|
||||
}
|
||||
}
|
||||
const key = shared.genCacheKey(template, options);
|
||||
const cached = compileCache[key];
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
if (template[0] === "#") {
|
||||
const el = document.querySelector(template);
|
||||
if (!el) {
|
||||
runtimeDom.warn(`Template element not found or is empty: ${template}`);
|
||||
}
|
||||
template = el ? el.innerHTML : ``;
|
||||
}
|
||||
const opts = shared.extend(
|
||||
{
|
||||
hoistStatic: true,
|
||||
onError: onError ,
|
||||
onWarn: (e) => onError(e, true)
|
||||
},
|
||||
options
|
||||
);
|
||||
if (!opts.isCustomElement && typeof customElements !== "undefined") {
|
||||
opts.isCustomElement = (tag) => !!customElements.get(tag);
|
||||
}
|
||||
const { code } = compilerDom.compile(template, opts);
|
||||
function onError(err, asWarning = false) {
|
||||
const message = asWarning ? err.message : `Template compilation error: ${err.message}`;
|
||||
const codeFrame = err.loc && shared.generateCodeFrame(
|
||||
template,
|
||||
err.loc.start.offset,
|
||||
err.loc.end.offset
|
||||
);
|
||||
runtimeDom.warn(codeFrame ? `${message}
|
||||
${codeFrame}` : message);
|
||||
}
|
||||
const render = new Function("Vue", code)(runtimeDom__namespace);
|
||||
render._rc = true;
|
||||
return compileCache[key] = render;
|
||||
}
|
||||
runtimeDom.registerRuntimeCompiler(compileToFunction);
|
||||
|
||||
exports.compile = compileToFunction;
|
||||
Object.keys(runtimeDom).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = runtimeDom[k];
|
||||
});
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* vue v3.5.43
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
* @license MIT
|
||||
**/
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var compilerDom = require('@vue/compiler-dom');
|
||||
var runtimeDom = require('@vue/runtime-dom');
|
||||
var shared = require('@vue/shared');
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
var n = Object.create(null);
|
||||
if (e) {
|
||||
for (var k in e) {
|
||||
n[k] = e[k];
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
var runtimeDom__namespace = /*#__PURE__*/_interopNamespaceDefault(runtimeDom);
|
||||
|
||||
const compileCache = /* @__PURE__ */ Object.create(null);
|
||||
function compileToFunction(template, options) {
|
||||
if (!shared.isString(template)) {
|
||||
if (template.nodeType) {
|
||||
template = template.innerHTML;
|
||||
} else {
|
||||
return shared.NOOP;
|
||||
}
|
||||
}
|
||||
const key = shared.genCacheKey(template, options);
|
||||
const cached = compileCache[key];
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
if (template[0] === "#") {
|
||||
const el = document.querySelector(template);
|
||||
template = el ? el.innerHTML : ``;
|
||||
}
|
||||
const opts = shared.extend(
|
||||
{
|
||||
hoistStatic: true,
|
||||
onError: void 0,
|
||||
onWarn: shared.NOOP
|
||||
},
|
||||
options
|
||||
);
|
||||
if (!opts.isCustomElement && typeof customElements !== "undefined") {
|
||||
opts.isCustomElement = (tag) => !!customElements.get(tag);
|
||||
}
|
||||
const { code } = compilerDom.compile(template, opts);
|
||||
const render = new Function("Vue", code)(runtimeDom__namespace);
|
||||
render._rc = true;
|
||||
return compileCache[key] = render;
|
||||
}
|
||||
runtimeDom.registerRuntimeCompiler(compileToFunction);
|
||||
|
||||
exports.compile = compileToFunction;
|
||||
Object.keys(runtimeDom).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = runtimeDom[k];
|
||||
});
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./dist/vue.cjs.prod.js')
|
||||
} else {
|
||||
module.exports = require('./dist/vue.cjs.js')
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './index.js'
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"name": "vue",
|
||||
"version": "3.5.43",
|
||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
"types": "dist/vue.d.ts",
|
||||
"unpkg": "dist/vue.global.js",
|
||||
"jsdelivr": "dist/vue.global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.mjs",
|
||||
"dist",
|
||||
"compiler-sfc",
|
||||
"server-renderer",
|
||||
"jsx-runtime",
|
||||
"jsx.d.ts"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/vue.d.mts",
|
||||
"node": "./index.mjs",
|
||||
"default": "./dist/vue.runtime.esm-bundler.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/vue.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/vue.cjs.prod.js",
|
||||
"development": "./dist/vue.cjs.js",
|
||||
"default": "./dist/vue.cjs.prod.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
}
|
||||
},
|
||||
"./server-renderer": {
|
||||
"import": {
|
||||
"types": "./server-renderer/index.d.mts",
|
||||
"default": "./server-renderer/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./server-renderer/index.d.ts",
|
||||
"default": "./server-renderer/index.js"
|
||||
}
|
||||
},
|
||||
"./compiler-sfc": {
|
||||
"import": {
|
||||
"types": "./compiler-sfc/index.d.mts",
|
||||
"browser": "./compiler-sfc/index.browser.mjs",
|
||||
"default": "./compiler-sfc/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./compiler-sfc/index.d.ts",
|
||||
"browser": "./compiler-sfc/index.browser.js",
|
||||
"default": "./compiler-sfc/index.js"
|
||||
}
|
||||
},
|
||||
"./jsx-runtime": {
|
||||
"types": "./jsx-runtime/index.d.ts",
|
||||
"import": "./jsx-runtime/index.mjs",
|
||||
"require": "./jsx-runtime/index.js"
|
||||
},
|
||||
"./jsx-dev-runtime": {
|
||||
"types": "./jsx-runtime/index.d.ts",
|
||||
"import": "./jsx-runtime/index.mjs",
|
||||
"require": "./jsx-runtime/index.js"
|
||||
},
|
||||
"./jsx": "./jsx.d.ts",
|
||||
"./dist/*": "./dist/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"buildOptions": {
|
||||
"name": "Vue",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"esm-bundler-runtime",
|
||||
"cjs",
|
||||
"global",
|
||||
"global-runtime",
|
||||
"esm-browser",
|
||||
"esm-browser-runtime"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/core.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/core/issues"
|
||||
},
|
||||
"homepage": "https://vuejs.org/",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.43",
|
||||
"@vue/compiler-dom": "3.5.43",
|
||||
"@vue/runtime-dom": "3.5.43",
|
||||
"@vue/compiler-sfc": "3.5.43",
|
||||
"@vue/server-renderer": "3.5.43"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from '@vue/server-renderer'
|
||||
Reference in New Issue
Block a user