UNPKG

@typed/content-hash

Version:

Content hash a directory of HTML/JS/CSS files and other static assets

205 lines 8.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createHtmlPlugin = void 0; const FxEnv_1 = require("@typed/fp/FxEnv"); const Option_1 = require("fp-ts/Option"); const ReadonlyArray_1 = require("fp-ts/ReadonlyArray"); const Tree_1 = require("fp-ts/Tree"); const path_1 = require("path"); const typed_colors_1 = require("typed-colors"); const logging_1 = require("../../application/services/logging"); const ensureRelative_1 = require("../ensureRelative"); const fsReadFile_1 = require("../fsReadFile"); const defaults_1 = require("./defaults"); const getFileExtension_1 = require("./getFileExtension"); const isExternalUrl_1 = require("./isExternalUrl"); const parseSrcSets_1 = require("./parseSrcSets"); const resolvePackage_1 = require("./resolvePackage"); // eslint-disable-next-line @typescript-eslint/no-var-requires const { parse, parseDefaults } = require('himalaya'); const supportedFileExtension = ['.html']; const foldDependencies = (0, Tree_1.foldMap)((0, ReadonlyArray_1.getMonoid)()); const searchMap = { a: ['href', 'ping'], applet: ['archive', 'code', 'codebase', 'object', 'src'], area: ['href', 'ping'], audio: ['src'], base: ['href'], blockquote: ['cite'], body: ['background'], button: ['formaction'], del: ['cite'], embed: ['src'], form: ['action'], frame: ['longdesc', 'src'], head: ['profile'], html: ['manifest'], iframe: ['longdesc', 'src'], img: ['longdesc', 'src', 'srcset'], input: ['formaction', 'src'], ins: ['cite'], link: ['href'], menuitem: ['icon'], object: ['codebase', 'data'], q: ['cite'], script: ['src'], source: ['src', 'srcset'], table: ['background'], tbody: ['background'], td: ['background'], tfoot: ['background'], th: ['background'], thead: ['background'], tr: ['background'], track: ['src'], video: ['poster', 'src'], }; function createHtmlPlugin({ buildDirectory, mainFields = defaults_1.MAIN_FIELDS }) { const html = { readFilePath: (filePath) => (0, FxEnv_1.Do)(function* (_) { const ext = (0, getFileExtension_1.getFileExtension)(filePath); if (!supportedFileExtension.includes(ext)) { yield* _((0, logging_1.debug)(`${(0, typed_colors_1.red)(`[HTML]`)} Unsupported file extension ${filePath}`)); return Option_1.none; } yield* _((0, logging_1.debug)(`${(0, typed_colors_1.yellow)(`[HTML]`)} Reading ${filePath}...`)); const initial = yield* _((0, fsReadFile_1.fsReadFile)(filePath, { supportsSourceMaps: false, isBase64Encoded: false })); yield* _((0, logging_1.debug)(`${(0, typed_colors_1.yellow)(`[HTML]`)} Finding Dependencies ${filePath}...`)); const document = findDependencies(initial, buildDirectory, mainFields); return (0, Option_1.some)({ ...document, contentHash: Option_1.none, sourceMap: Option_1.none }); }), }; return html; } exports.createHtmlPlugin = createHtmlPlugin; function findDependencies(document, buildDirectory, mainFields) { const directory = path_1.posix.dirname(document.filePath); const ast = parse(document.contents, { ...parseDefaults, includePositions: true }); const dependencies = ast .map(astToTree) .flatMap(foldDependencies(isValidDependency(buildDirectory, directory, mainFields, document.contents))); return { ...document, dependencies }; } function astToTree(ast) { return { value: ast, forest: (ast.children || []).map(astToTree), }; } function isValidDependency(buildDirectory, directory, mainFields, contents) { return (ast) => { var _a; if (ast.type !== 'element') { return []; } const tagName = ast.tagName.toLowerCase(); if (tagName === 'template' && !!((_a = ast.children) === null || _a === void 0 ? void 0 : _a[0].content)) { const child = ast.children[0]; const start = child.position.start.index; const content = child.content; const childAst = parse(content, { ...parseDefaults, includePositions: true }); return childAst .map(astToTree) .flatMap(foldDependencies(isValidDependency(buildDirectory, directory, mainFields, content))) .map((d) => ({ ...d, position: { start: d.position.start + start, end: d.position.end + start } })); } if (!(tagName in searchMap)) { return []; } const attributesToSearch = searchMap[tagName]; return ast.attributes .filter(({ key }) => attributesToSearch.includes(key)) .flatMap(getDependencies(buildDirectory, directory, mainFields, contents, ast)); }; } function getDependencies(buildDirectory, directory, mainFields, contents, ast) { const { position } = ast; const astStart = position.start.index; const astEnd = position.end.index; const sourceString = contents.slice(astStart, astEnd); const tagName = ast.tagName.toLowerCase(); return (attr) => { const attrStart = astStart + findSourceIndex(sourceString, attr); const attrEnd = attrStart + attr.value.length; const isImgSrcSet = tagName === 'img' && attr.key === 'srcset'; const resolved = isImgSrcSet ? (0, parseSrcSets_1.parseSrcSets)(attr.value, attrStart).map((s) => resolveSpecifier(buildDirectory, directory, s.url, mainFields, s.position)) : [ resolveSpecifier(buildDirectory, directory, attr.value, mainFields, { start: attrStart, end: attrEnd, }), ]; return resolved.filter(Option_1.isSome).map((o) => o.value); }; } function findSourceIndex(source, attr) { const woQuotesIndex = source.indexOf(formatNoQuotes(attr)); if (woQuotesIndex > -1) { return woQuotesIndex + attr.key.length + 1; // + the equals } const singleQuotesIndex = source.indexOf(formatSingleQuotes(attr)); if (singleQuotesIndex > -1) { return singleQuotesIndex + attr.key.length + 2; // + the equals and first quote } const doubleQuotesIndex = source.indexOf(formatDoubleQuotes(attr)); if (doubleQuotesIndex > -1) { return doubleQuotesIndex + attr.key.length + 2; } throw new Error(`Unable to find HTML attribute ${formatDoubleQuotes(attr)} in ${source}`); } function formatNoQuotes(attr) { return `${attr.key}=${attr.value}`; } function formatSingleQuotes(attr) { return `${attr.key}='${attr.value}'`; } function formatDoubleQuotes(attr) { return `${attr.key}="${attr.value}"`; } function ensureRelativeSpecifier(specifier, buildDirectory, directory) { if (specifier.startsWith('/')) { return (0, ensureRelative_1.ensureRelative)(path_1.posix.relative(directory, path_1.posix.resolve(buildDirectory, specifier.slice(1)))); } return specifier; } function resolveSpecifier(buildDirectory, directory, specifier, mainFields, position) { const relativeSpecifier = ensureRelativeSpecifier(specifier, buildDirectory, directory); const hasFileExtension = isFileExtension(path_1.posix.extname(relativeSpecifier)); if ((0, isExternalUrl_1.isExternalUrl)(relativeSpecifier)) { return Option_1.none; } try { const filePath = (0, resolvePackage_1.resolvePackage)({ moduleSpecifier: relativeSpecifier, directory, extensions: ['.js'], mainFields, }); const dep = { specifier, filePath, fileExtension: (0, getFileExtension_1.getFileExtension)(filePath), position, }; return (0, Option_1.some)(dep); } catch (error) { // If we're really sure it is supposed to be a file, throw the error if (hasFileExtension) { throw error; } return Option_1.none; } } function isFileExtension(ext) { if (!ext.trim()) { return false; } const n = Number.parseFloat(ext); if (!Number.isNaN(n)) { return false; } return !ext.includes(' '); } //# sourceMappingURL=html.js.map