272 lines
9.3 KiB
Plaintext
272 lines
9.3 KiB
Plaintext
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.extractScriptTags = void 0;
|
||
|
const utils_1 = require("@astrojs/compiler/utils");
|
||
|
const language_core_1 = require("@volar/language-core");
|
||
|
function extractScriptTags(snapshot, htmlDocument, ast) {
|
||
|
const embeddedJSCodes = findModuleScripts(snapshot, htmlDocument.roots);
|
||
|
const javascriptContexts = [
|
||
|
...findClassicScripts(htmlDocument, snapshot),
|
||
|
...findEventAttributes(ast),
|
||
|
].sort((a, b) => a.startOffset - b.startOffset);
|
||
|
if (javascriptContexts.length > 0) {
|
||
|
// classic scripts share the same scope
|
||
|
// merging them brings about redeclaration errors
|
||
|
embeddedJSCodes.push(mergeJSContexts(javascriptContexts));
|
||
|
}
|
||
|
return embeddedJSCodes;
|
||
|
}
|
||
|
exports.extractScriptTags = extractScriptTags;
|
||
|
function getScriptType(scriptTag) {
|
||
|
// script tags without attributes are processed and converted into module scripts
|
||
|
if (!scriptTag.attributes || Object.entries(scriptTag.attributes).length === 0)
|
||
|
return 'processed module';
|
||
|
// even when it is not processed by vite, scripts with type=module remain modules
|
||
|
if (scriptTag.attributes['type']?.includes('module') === true)
|
||
|
return 'module';
|
||
|
// whenever there are attributes, is:inline is implied and in the absence of type=module, the script is classic
|
||
|
return 'classic';
|
||
|
}
|
||
|
/**
|
||
|
* Get all the isolated scripts in the HTML document
|
||
|
* Isolated scripts are scripts that are hoisted by Astro and as such, are isolated from the rest of the code because of the implicit `type="module"`
|
||
|
* All the isolated scripts are passed to the TypeScript language server as separate `.mts` files.
|
||
|
*/
|
||
|
function findModuleScripts(snapshot, roots) {
|
||
|
const embeddedScripts = [];
|
||
|
let scriptIndex = 0;
|
||
|
getEmbeddedScriptsInNodes(roots);
|
||
|
function getEmbeddedScriptsInNodes(nodes) {
|
||
|
for (const [_, node] of nodes.entries()) {
|
||
|
if (node.tag === 'script' &&
|
||
|
node.startTagEnd !== undefined &&
|
||
|
node.endTagStart !== undefined &&
|
||
|
getScriptType(node) !== 'classic') {
|
||
|
const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
|
||
|
const extension = getScriptType(node) === 'processed module' ? 'mts' : 'mjs';
|
||
|
const languageId = getScriptType(node) === 'processed module' ? 'typescript' : 'javascript';
|
||
|
embeddedScripts.push({
|
||
|
id: `${scriptIndex}.${extension}`,
|
||
|
languageId: languageId,
|
||
|
snapshot: {
|
||
|
getText: (start, end) => scriptText.substring(start, end),
|
||
|
getLength: () => scriptText.length,
|
||
|
getChangeRange: () => undefined,
|
||
|
},
|
||
|
mappings: [
|
||
|
{
|
||
|
sourceOffsets: [node.startTagEnd],
|
||
|
generatedOffsets: [0],
|
||
|
lengths: [scriptText.length],
|
||
|
data: {
|
||
|
verification: true,
|
||
|
completion: true,
|
||
|
semantic: true,
|
||
|
navigation: true,
|
||
|
structure: true,
|
||
|
format: false,
|
||
|
},
|
||
|
},
|
||
|
],
|
||
|
embeddedCodes: [],
|
||
|
});
|
||
|
scriptIndex++;
|
||
|
}
|
||
|
if (node.children)
|
||
|
getEmbeddedScriptsInNodes(node.children);
|
||
|
}
|
||
|
}
|
||
|
return embeddedScripts;
|
||
|
}
|
||
|
/**
|
||
|
* Get all the inline scripts in the HTML document
|
||
|
* Inline scripts are scripts that are not hoisted by Astro and as such, are not isolated from the rest of the code.
|
||
|
* All the inline scripts are concatenated into a single `.mjs` file and passed to the TypeScript language server.
|
||
|
*/
|
||
|
function findClassicScripts(htmlDocument, snapshot) {
|
||
|
const inlineScripts = [];
|
||
|
getInlineScriptsInNodes(htmlDocument.roots);
|
||
|
function getInlineScriptsInNodes(nodes) {
|
||
|
for (const [_, node] of nodes.entries()) {
|
||
|
if (node.tag === 'script' &&
|
||
|
node.startTagEnd !== undefined &&
|
||
|
node.endTagStart !== undefined &&
|
||
|
!isJSON(node.attributes?.type) &&
|
||
|
getScriptType(node) === 'classic') {
|
||
|
const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
|
||
|
inlineScripts.push({
|
||
|
startOffset: node.startTagEnd,
|
||
|
content: scriptText,
|
||
|
});
|
||
|
}
|
||
|
if (node.children)
|
||
|
getInlineScriptsInNodes(node.children);
|
||
|
}
|
||
|
}
|
||
|
return inlineScripts;
|
||
|
}
|
||
|
/**
|
||
|
* Include both MIME JSON types and `importmap` and `speculationrules` script types
|
||
|
* See MIME Types -> https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||
|
* See Script Types -> https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
|
||
|
*/
|
||
|
const JSON_TYPES = ['application/json', 'application/ld+json', 'importmap', 'speculationrules'];
|
||
|
/**
|
||
|
* Check if the script has a type, and if it's included in JSON_TYPES above.
|
||
|
* @param type Found in the `type` attribute of the script tag
|
||
|
*/
|
||
|
function isJSON(type) {
|
||
|
if (!type)
|
||
|
return false;
|
||
|
// HTML attributes are quoted, slice " and ' at the start and end of the string
|
||
|
return JSON_TYPES.includes(type.slice(1, -1));
|
||
|
}
|
||
|
function findEventAttributes(ast) {
|
||
|
const eventAttrs = [];
|
||
|
// `@astrojs/compiler`'s `walk` method is async, so we can't use it here. Arf
|
||
|
function walkDown(parent) {
|
||
|
if (!parent.children)
|
||
|
return;
|
||
|
parent.children.forEach((child) => {
|
||
|
if (utils_1.is.element(child)) {
|
||
|
const eventAttribute = child.attributes.find((attr) => htmlEventAttributes.includes(attr.name) && attr.kind === 'quoted');
|
||
|
if (eventAttribute && eventAttribute.position) {
|
||
|
eventAttrs.push({
|
||
|
// Add a semicolon to the end of the event attribute to attempt to prevent errors from spreading to the rest of the document
|
||
|
// This is not perfect, but it's better than nothing
|
||
|
// See: https://github.com/microsoft/vscode/blob/e8e04769ec817a3374c3eaa26a08d3ae491820d5/extensions/html-language-features/server/src/modes/embeddedSupport.ts#L192
|
||
|
content: eventAttribute.value + ';',
|
||
|
startOffset: eventAttribute.position.start.offset + `${eventAttribute.name}="`.length,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
if (utils_1.is.parent(child)) {
|
||
|
walkDown(child);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
walkDown(ast);
|
||
|
return eventAttrs;
|
||
|
}
|
||
|
/**
|
||
|
* Merge all the inline and non-hoisted scripts into a single `.mjs` file
|
||
|
*/
|
||
|
function mergeJSContexts(javascriptContexts) {
|
||
|
const codes = [];
|
||
|
for (const javascriptContext of javascriptContexts) {
|
||
|
codes.push([
|
||
|
javascriptContext.content,
|
||
|
undefined,
|
||
|
javascriptContext.startOffset,
|
||
|
{
|
||
|
verification: true,
|
||
|
completion: true,
|
||
|
semantic: true,
|
||
|
navigation: true,
|
||
|
structure: true,
|
||
|
format: false,
|
||
|
},
|
||
|
]);
|
||
|
}
|
||
|
const mappings = (0, language_core_1.buildMappings)(codes);
|
||
|
const text = (0, language_core_1.toString)(codes);
|
||
|
return {
|
||
|
id: 'inline.mjs',
|
||
|
languageId: 'javascript',
|
||
|
snapshot: {
|
||
|
getText: (start, end) => text.substring(start, end),
|
||
|
getLength: () => text.length,
|
||
|
getChangeRange: () => undefined,
|
||
|
},
|
||
|
embeddedCodes: [],
|
||
|
mappings,
|
||
|
};
|
||
|
}
|
||
|
const htmlEventAttributes = [
|
||
|
'onabort',
|
||
|
'onafterprint',
|
||
|
'onauxclick',
|
||
|
'onbeforematch',
|
||
|
'onbeforeprint',
|
||
|
'onbeforeunload',
|
||
|
'onblur',
|
||
|
'oncancel',
|
||
|
'oncanplay',
|
||
|
'oncanplaythrough',
|
||
|
'onchange',
|
||
|
'onclick',
|
||
|
'onclose',
|
||
|
'oncontextlost',
|
||
|
'oncontextmenu',
|
||
|
'oncontextrestored',
|
||
|
'oncopy',
|
||
|
'oncuechange',
|
||
|
'oncut',
|
||
|
'ondblclick',
|
||
|
'ondrag',
|
||
|
'ondragend',
|
||
|
'ondragenter',
|
||
|
'ondragleave',
|
||
|
'ondragover',
|
||
|
'ondragstart',
|
||
|
'ondrop',
|
||
|
'ondurationchange',
|
||
|
'onemptied',
|
||
|
'onended',
|
||
|
'onerror',
|
||
|
'onfocus',
|
||
|
'onformdata',
|
||
|
'onhashchange',
|
||
|
'oninput',
|
||
|
'oninvalid',
|
||
|
'onkeydown',
|
||
|
'onkeypress',
|
||
|
'onkeyup',
|
||
|
'onlanguagechange',
|
||
|
'onload',
|
||
|
'onloadeddata',
|
||
|
'onloadedmetadata',
|
||
|
'onloadstart',
|
||
|
'onmessage',
|
||
|
'onmessageerror',
|
||
|
'onmousedown',
|
||
|
'onmouseenter',
|
||
|
'onmouseleave',
|
||
|
'onmousemove',
|
||
|
'onmouseout',
|
||
|
'onmouseover',
|
||
|
'onmouseup',
|
||
|
'onoffline',
|
||
|
'ononline',
|
||
|
'onpagehide',
|
||
|
'onpageshow',
|
||
|
'onpaste',
|
||
|
'onpause',
|
||
|
'onplay',
|
||
|
'onplaying',
|
||
|
'onpopstate',
|
||
|
'onprogress',
|
||
|
'onratechange',
|
||
|
'onrejectionhandled',
|
||
|
'onreset',
|
||
|
'onresize',
|
||
|
'onscroll',
|
||
|
'onscrollend',
|
||
|
'onsecuritypolicyviolation',
|
||
|
'onseeked',
|
||
|
'onseeking',
|
||
|
'onselect',
|
||
|
'onslotchange',
|
||
|
'onstalled',
|
||
|
'onstorage',
|
||
|
'onsubmit',
|
||
|
'onsuspend',
|
||
|
'ontimeupdate',
|
||
|
'ontoggle',
|
||
|
'onunhandledrejection',
|
||
|
'onunload',
|
||
|
'onvolumechange',
|
||
|
'onwaiting',
|
||
|
'onwheel',
|
||
|
];
|
||
|
//# sourceMappingURL=parseJS.js.map
|