UNPKG

@stencil/core

Version:

A Compiler for Web Components and Progressive Web Apps

1,431 lines (1,419 loc) 52.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /** * Default style mode id */ const DEFAULT_STYLE_MODE = '$'; /** * Reusable empty obj/array * Don't add values to these!! */ const EMPTY_OBJ = {}; /** * Namespaces */ const SVG_NS = 'http://www.w3.org/2000/svg'; const XLINK_NS = 'http://www.w3.org/1999/xlink'; const XML_NS = 'http://www.w3.org/XML/1998/namespace'; /** * File names and value */ const BANNER = `Built with http://stenciljs.com`; const COLLECTION_MANIFEST_FILE_NAME = 'collection-manifest.json'; function normalizePath(str) { // Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar // https://github.com/sindresorhus/slash MIT // By Sindre Sorhus if (typeof str !== 'string') { throw new Error(`invalid path to normalize`); } str = str.trim(); if (EXTENDED_PATH_REGEX.test(str) || NON_ASCII_REGEX.test(str)) { return str; } str = str.replace(SLASH_REGEX, '/'); // always remove the trailing / // this makes our file cache look ups consistent if (str.charAt(str.length - 1) === '/') { const colonIndex = str.indexOf(':'); if (colonIndex > -1) { if (colonIndex < str.length - 2) { str = str.substring(0, str.length - 1); } } else if (str.length > 1) { str = str.substring(0, str.length - 1); } } return str; } const EXTENDED_PATH_REGEX = /^\\\\\?\\/; const NON_ASCII_REGEX = /[^\x00-\x80]+/; const SLASH_REGEX = /\\/g; const isDef = (v) => v != null; const toLowerCase = (str) => str.toLowerCase(); const toDashCase = (str) => toLowerCase(str.replace(/([A-Z0-9])/g, g => ' ' + g[0]).trim().replace(/ /g, '-')); const dashToPascalCase = (str) => toLowerCase(str).split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join(''); const toTitleCase = (str) => str.charAt(0).toUpperCase() + str.substr(1); const captializeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1); const noop = () => { }; const isComplexType = (o) => ['object', 'function'].includes(typeof o); function sortBy(array, prop) { return array.slice().sort((a, b) => { const nameA = prop(a); const nameB = prop(b); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; }); } function flatOne(array) { if (array.flat) { return array.flat(1); } return array.reduce((result, item) => { result.push(...item); return result; }, []); } function unique(array, predicate = (i) => i) { const set = new Set(); return array.filter(item => { const key = predicate(item); if (key == null) { return true; } if (set.has(key)) { return false; } set.add(key); return true; }); } function fromEntries(entries) { const object = {}; for (const [key, value] of entries) { object[key] = value; } return object; } function relativeImport(config, pathFrom, pathTo, ext, addPrefix = true) { let relativePath = config.sys.path.relative(config.sys.path.dirname(pathFrom), config.sys.path.dirname(pathTo)); if (addPrefix) { if (relativePath === '') { relativePath = '.'; } else if (relativePath[0] !== '.') { relativePath = './' + relativePath; } } return normalizePath(`${relativePath}/${config.sys.path.basename(pathTo, ext)}`); } const pluck = (obj, keys) => { return keys.reduce((final, key) => { if (obj[key]) { final[key] = obj[key]; } return final; }, {}); }; const isObject = (val) => { return val != null && typeof val === 'object' && Array.isArray(val) === false; }; class InMemoryFileSystem { constructor(disk, path) { this.disk = disk; this.path = path; this.items = new Map(); } async accessData(filePath) { const item = this.getItem(filePath); if (typeof item.exists === 'boolean') { return { exists: item.exists, isDirectory: item.isDirectory, isFile: item.isFile }; } const data = { exists: false, isDirectory: false, isFile: false }; try { const s = await this.stat(filePath); item.exists = true; item.isDirectory = s.isDirectory; item.isFile = s.isFile; data.exists = item.exists; data.isDirectory = item.isDirectory; data.isFile = item.isFile; } catch (e) { item.exists = false; } return data; } async access(filePath) { const data = await this.accessData(filePath); return data.exists; } /** * Synchronous!!! Do not use!!! * (Only typescript transpiling is allowed to use) * @param filePath */ accessSync(filePath) { const item = this.getItem(filePath); if (typeof item.exists === 'boolean') { return item.exists; } let hasAccess = false; try { const s = this.statSync(filePath); item.exists = true; item.isDirectory = s.isDirectory; item.isFile = s.isFile; hasAccess = true; } catch (e) { item.exists = false; } return hasAccess; } async copyFile(src, dest) { const item = this.getItem(src); item.queueCopyFileToDest = dest; } async emptyDir(dirPath) { const item = this.getItem(dirPath); await this.removeDir(dirPath); item.isFile = false; item.isDirectory = true; item.queueWriteToDisk = true; item.queueDeleteFromDisk = false; } async readdir(dirPath, opts = {}) { dirPath = normalizePath(dirPath); const collectedPaths = []; if (opts.inMemoryOnly === true) { let inMemoryDir = dirPath; if (!inMemoryDir.endsWith('/')) { inMemoryDir += '/'; } const inMemoryDirs = dirPath.split('/'); this.items.forEach((d, filePath) => { if (!filePath.startsWith(dirPath)) { return; } const parts = filePath.split('/'); if (parts.length === inMemoryDirs.length + 1 || (opts.recursive && parts.length > inMemoryDirs.length)) { if (d.exists) { const item = { absPath: filePath, relPath: parts[inMemoryDirs.length], isDirectory: d.isDirectory, isFile: d.isFile }; collectedPaths.push(item); } } }); } else { // always a disk read await this.readDirectory(dirPath, dirPath, opts, collectedPaths); } return collectedPaths.sort((a, b) => { if (a.absPath < b.absPath) return -1; if (a.absPath > b.absPath) return 1; return 0; }); } async readDirectory(initPath, dirPath, opts, collectedPaths) { // used internally only so we could easily recursively drill down // loop through this directory and sub directories // always a disk read!! const dirItems = await this.disk.readdir(dirPath); // cache some facts about this path const item = this.getItem(dirPath); item.exists = true; item.isFile = false; item.isDirectory = true; await Promise.all(dirItems.map(async (dirItem) => { // let's loop through each of the files we've found so far // create an absolute path of the item inside of this directory const absPath = normalizePath(this.path.join(dirPath, dirItem)); const relPath = normalizePath(this.path.relative(initPath, absPath)); // get the fs stats for the item, could be either a file or directory const stats = await this.stat(absPath); // cache some stats about this path const subItem = this.getItem(absPath); subItem.exists = true; subItem.isDirectory = stats.isDirectory; subItem.isFile = stats.isFile; collectedPaths.push({ absPath: absPath, relPath: relPath, isDirectory: stats.isDirectory, isFile: stats.isFile }); if (opts.recursive === true && stats.isDirectory === true) { // looks like it's yet another directory // let's keep drilling down await this.readDirectory(initPath, absPath, opts, collectedPaths); } })); } async readFile(filePath, opts) { if (opts == null || (opts.useCache === true || opts.useCache === undefined)) { const item = this.getItem(filePath); if (item.exists && typeof item.fileText === 'string') { return item.fileText; } } const fileContent = await this.disk.readFile(filePath); const item = this.getItem(filePath); if (fileContent.length < MAX_TEXT_CACHE) { item.exists = true; item.isFile = true; item.isDirectory = false; item.fileText = fileContent; } return fileContent; } /** * Synchronous!!! Do not use!!! * (Only typescript transpiling is allowed to use) * @param filePath */ readFileSync(filePath, opts) { if (opts == null || (opts.useCache === true || opts.useCache === undefined)) { const item = this.getItem(filePath); if (item.exists && typeof item.fileText === 'string') { return item.fileText; } } const fileContent = this.disk.readFileSync(filePath); const item = this.getItem(filePath); if (fileContent.length < MAX_TEXT_CACHE) { item.exists = true; item.isFile = true; item.isDirectory = false; item.fileText = fileContent; } return fileContent; } async remove(itemPath) { const stats = await this.stat(itemPath); if (stats.isDirectory === true) { await this.removeDir(itemPath); } else if (stats.isFile === true) { await this.removeItem(itemPath); } } async removeDir(dirPath) { const item = this.getItem(dirPath); item.isFile = false; item.isDirectory = true; if (!item.queueWriteToDisk) { item.queueDeleteFromDisk = true; } try { const dirItems = await this.readdir(dirPath, { recursive: true }); await Promise.all(dirItems.map(item => this.removeItem(item.absPath))); } catch (e) { // do not throw error if the directory never existed } } async removeItem(filePath) { const item = this.getItem(filePath); if (!item.queueWriteToDisk) { item.queueDeleteFromDisk = true; } } async stat(itemPath) { const item = this.getItem(itemPath); if (typeof item.isDirectory !== 'boolean' || typeof item.isFile !== 'boolean') { const s = await this.disk.stat(itemPath); item.exists = true; item.isDirectory = s.isDirectory(); item.isFile = s.isFile(); item.size = s.size; } return { exists: !!item.exists, isFile: !!item.isFile, isDirectory: !!item.isDirectory, size: typeof item.size === 'number' ? item.size : 0 }; } /** * Synchronous!!! Do not use!!! * (Only typescript transpiling is allowed to use) * @param itemPath */ statSync(itemPath) { const item = this.getItem(itemPath); if (typeof item.isDirectory !== 'boolean' || typeof item.isFile !== 'boolean') { const s = this.disk.statSync(itemPath); item.exists = true; item.isDirectory = s.isDirectory(); item.isFile = s.isFile(); } return { isFile: item.isFile, isDirectory: item.isDirectory }; } async writeFile(filePath, content, opts) { if (typeof filePath !== 'string') { throw new Error(`writeFile, invalid filePath: ${filePath}`); } if (typeof content !== 'string') { throw new Error(`writeFile, invalid content: ${filePath}`); } const results = { ignored: false, changedContent: false, queuedWrite: false }; if (shouldIgnore(filePath) === true) { results.ignored = true; return results; } const item = this.getItem(filePath); item.exists = true; item.isFile = true; item.isDirectory = false; item.queueDeleteFromDisk = false; results.changedContent = (item.fileText !== content); results.queuedWrite = false; item.fileText = content; if (opts != null && opts.useCache === false) { item.useCache = false; } if (opts != null && opts.inMemoryOnly === true) { // we don't want to actually write this to disk // just keep it in memory if (item.queueWriteToDisk) { // we already queued this file to write to disk // in that case we still need to do it results.queuedWrite = true; } else { // we only want this in memory and // it wasn't already queued to be written item.queueWriteToDisk = false; } } else if (opts != null && opts.immediateWrite === true) { // If this is an immediate write then write the file // and do not add it to the queue await this.ensureDir(filePath); await this.disk.writeFile(filePath, item.fileText); } else { // we want to write this to disk (eventually) // but only if the content is different // from our existing cached content if (!item.queueWriteToDisk && results.changedContent === true) { // not already queued to be written // and the content is different item.queueWriteToDisk = true; results.queuedWrite = true; } } return results; } writeFiles(files, opts) { return Promise.all(Object.keys(files).map(filePath => { return this.writeFile(filePath, files[filePath], opts); })); } async commit() { const instructions = getCommitInstructions(this.path, this.items); // ensure directories we need exist const dirsAdded = await this.commitEnsureDirs(instructions.dirsToEnsure); // write all queued the files const filesWritten = await this.commitWriteFiles(instructions.filesToWrite); // write all queued the files to copy const filesCopied = await this.commitCopyFiles(instructions.filesToCopy); // remove all the queued files to be deleted const filesDeleted = await this.commitDeleteFiles(instructions.filesToDelete); // remove all the queued dirs to be deleted const dirsDeleted = await this.commitDeleteDirs(instructions.dirsToDelete); instructions.filesToDelete.forEach(fileToDelete => { this.clearFileCache(fileToDelete); }); instructions.dirsToDelete.forEach(dirToDelete => { this.clearDirCache(dirToDelete); }); // return only the files that were return { filesCopied, filesWritten, filesDeleted, dirsDeleted, dirsAdded }; } async ensureDir(p) { const allDirs = []; while (true) { p = this.path.dirname(p); if (typeof p === 'string' && p.length > 0 && p !== '/' && p.endsWith(':/') === false && p.endsWith(':\\') === false) { allDirs.push(p); } else { break; } } allDirs.reverse(); await this.commitEnsureDirs(allDirs); } async commitEnsureDirs(dirsToEnsure) { const dirsAdded = []; for (const dirPath of dirsToEnsure) { const item = this.getItem(dirPath); if (item.exists === true && item.isDirectory === true) { // already cached that this path is indeed an existing directory continue; } try { // cache that we know this is a directory on disk item.exists = true; item.isDirectory = true; item.isFile = false; await this.disk.mkdir(dirPath); dirsAdded.push(dirPath); } catch (e) { } } return dirsAdded; } commitCopyFiles(filesToCopy) { const copiedFiles = Promise.all(filesToCopy.map(async (data) => { const src = data[0]; const dest = data[1]; await this.disk.copyFile(src, dest); return [src, dest]; })); return copiedFiles; } commitWriteFiles(filesToWrite) { const writtenFiles = Promise.all(filesToWrite.map(async (filePath) => { if (typeof filePath !== 'string') { throw new Error(`unable to writeFile without filePath`); } return this.commitWriteFile(filePath); })); return writtenFiles; } async commitWriteFile(filePath) { const item = this.getItem(filePath); if (item.fileText == null) { throw new Error(`unable to find item fileText to write: ${filePath}`); } await this.disk.writeFile(filePath, item.fileText); if (item.useCache === false) { this.clearFileCache(filePath); } return filePath; } async commitDeleteFiles(filesToDelete) { const deletedFiles = await Promise.all(filesToDelete.map(async (filePath) => { if (typeof filePath !== 'string') { throw new Error(`unable to unlink without filePath`); } await this.disk.unlink(filePath); return filePath; })); return deletedFiles; } async commitDeleteDirs(dirsToDelete) { const dirsDeleted = []; for (const dirPath of dirsToDelete) { try { await this.disk.rmdir(dirPath); } catch (e) { } dirsDeleted.push(dirPath); } return dirsDeleted; } clearDirCache(dirPath) { dirPath = normalizePath(dirPath); this.items.forEach((_, f) => { const filePath = this.path.relative(dirPath, f).split('/')[0]; if (!filePath.startsWith('.') && !filePath.startsWith('/')) { this.clearFileCache(f); } }); } clearFileCache(filePath) { filePath = normalizePath(filePath); const item = this.items.get(filePath); if (item != null && !item.queueWriteToDisk) { this.items.delete(filePath); } } cancelDeleteFilesFromDisk(filePaths) { filePaths.forEach(filePath => { const item = this.getItem(filePath); if (item.isFile === true && item.queueDeleteFromDisk === true) { item.queueDeleteFromDisk = false; } }); } cancelDeleteDirectoriesFromDisk(dirPaths) { dirPaths.forEach(dirPath => { const item = this.getItem(dirPath); if (item.queueDeleteFromDisk === true) { item.queueDeleteFromDisk = false; } }); } getItem(itemPath) { itemPath = normalizePath(itemPath); let item = this.items.get(itemPath); if (item != null) { return item; } this.items.set(itemPath, item = { exists: null, fileText: null, size: null, mtimeMs: null, isDirectory: null, isFile: null, queueCopyFileToDest: null, queueDeleteFromDisk: null, queueWriteToDisk: null, useCache: null }); return item; } clearCache() { this.items.clear(); } get keys() { return Array.from(this.items.keys()).sort(); } getMemoryStats() { return `data length: ${this.items.size}`; } } function getCommitInstructions(path, d) { const instructions = { filesToDelete: [], filesToWrite: [], filesToCopy: [], dirsToDelete: [], dirsToEnsure: [] }; d.forEach((item, itemPath) => { if (item.queueWriteToDisk === true) { if (item.isFile === true) { instructions.filesToWrite.push(itemPath); const dir = normalizePath(path.dirname(itemPath)); if (!instructions.dirsToEnsure.includes(dir)) { instructions.dirsToEnsure.push(dir); } const dirDeleteIndex = instructions.dirsToDelete.indexOf(dir); if (dirDeleteIndex > -1) { instructions.dirsToDelete.splice(dirDeleteIndex, 1); } const fileDeleteIndex = instructions.filesToDelete.indexOf(itemPath); if (fileDeleteIndex > -1) { instructions.filesToDelete.splice(fileDeleteIndex, 1); } } else if (item.isDirectory === true) { if (!instructions.dirsToEnsure.includes(itemPath)) { instructions.dirsToEnsure.push(itemPath); } const dirDeleteIndex = instructions.dirsToDelete.indexOf(itemPath); if (dirDeleteIndex > -1) { instructions.dirsToDelete.splice(dirDeleteIndex, 1); } } } else if (item.queueDeleteFromDisk === true) { if (item.isDirectory && !instructions.dirsToEnsure.includes(itemPath)) { instructions.dirsToDelete.push(itemPath); } else if (item.isFile && !instructions.filesToWrite.includes(itemPath)) { instructions.filesToDelete.push(itemPath); } } else if (typeof item.queueCopyFileToDest === 'string') { const src = itemPath; const dest = item.queueCopyFileToDest; instructions.filesToCopy.push([src, dest]); const dir = normalizePath(path.dirname(dest)); if (!instructions.dirsToEnsure.includes(dir)) { instructions.dirsToEnsure.push(dir); } const dirDeleteIndex = instructions.dirsToDelete.indexOf(dir); if (dirDeleteIndex > -1) { instructions.dirsToDelete.splice(dirDeleteIndex, 1); } const fileDeleteIndex = instructions.filesToDelete.indexOf(dest); if (fileDeleteIndex > -1) { instructions.filesToDelete.splice(fileDeleteIndex, 1); } } item.queueDeleteFromDisk = false; item.queueWriteToDisk = false; }); // add all the ancestor directories for each directory too for (let i = 0, ilen = instructions.dirsToEnsure.length; i < ilen; i++) { const segments = instructions.dirsToEnsure[i].split('/'); for (let j = 2; j < segments.length; j++) { const dir = segments.slice(0, j).join('/'); if (instructions.dirsToEnsure.includes(dir) === false) { instructions.dirsToEnsure.push(dir); } } } // sort directories so shortest paths are ensured first instructions.dirsToEnsure.sort((a, b) => { const segmentsA = a.split('/').length; const segmentsB = b.split('/').length; if (segmentsA < segmentsB) return -1; if (segmentsA > segmentsB) return 1; if (a.length < b.length) return -1; if (a.length > b.length) return 1; return 0; }); // sort directories so longest paths are removed first instructions.dirsToDelete.sort((a, b) => { const segmentsA = a.split('/').length; const segmentsB = b.split('/').length; if (segmentsA < segmentsB) return 1; if (segmentsA > segmentsB) return -1; if (a.length < b.length) return 1; if (a.length > b.length) return -1; return 0; }); instructions.dirsToEnsure.forEach(dirToEnsure => { const i = instructions.dirsToDelete.indexOf(dirToEnsure); if (i > -1) { instructions.dirsToDelete.splice(i, 1); } }); instructions.dirsToDelete = instructions.dirsToDelete.filter(dir => { if (dir === '/' || dir.endsWith(':/') === true) { return false; } return true; }); instructions.dirsToEnsure = instructions.dirsToEnsure.filter(dir => { const item = d.get(dir); if (item != null && item.exists === true && item.isDirectory === true) { return false; } if (dir === '/' || dir.endsWith(':/')) { return false; } return true; }); return instructions; } function shouldIgnore(filePath) { filePath = filePath.trim().toLowerCase(); return IGNORE.some(ignoreFile => filePath.endsWith(ignoreFile)); } function isTextFile(filePath) { filePath = filePath.toLowerCase().trim(); return TXT_EXT.some(ext => filePath.endsWith(ext)); } const TXT_EXT = [ '.ts', '.tsx', '.js', '.jsx', '.svg', '.html', '.txt', '.md', '.markdown', '.json', '.css', '.scss', '.sass', '.less', '.styl' ]; const IGNORE = [ '.ds_store', '.gitignore', 'desktop.ini', 'thumbs.db' ]; // only cache if it's less than 5MB-ish (using .length as a rough guess) // why 5MB? idk, seems like a good number for source text // it's pretty darn large to cover almost ALL legitimate source files // and anything larger is probably a REALLY large file and a rare case // which we don't need to eat up memory for const MAX_TEXT_CACHE = 5242880; function normalizeDiagnostics(compilerCtx, diagnostics) { const normalizedErrors = []; const normalizedOthers = []; const dups = new Set(); for (let i = 0; i < diagnostics.length; i++) { const d = normalizeDiagnostic(compilerCtx, diagnostics[i]); const key = d.absFilePath + d.code + d.messageText + d.type; if (dups.has(key)) { continue; } dups.add(key); const total = normalizedErrors.length + normalizedOthers.length; if (d.level === 'error') { normalizedErrors.push(d); } else if (total < MAX_ERRORS) { normalizedOthers.push(d); } } return [ ...normalizedErrors, ...normalizedOthers ]; } function normalizeDiagnostic(compilerCtx, diagnostic) { if (diagnostic.messageText) { if (typeof diagnostic.messageText.message === 'string') { diagnostic.messageText = diagnostic.messageText.message; } else if (typeof diagnostic.messageText === 'string' && diagnostic.messageText.indexOf('Error: ') === 0) { diagnostic.messageText = diagnostic.messageText.substr(7); } } if (diagnostic.messageText) { if (diagnostic.messageText.includes(`Cannot find name 'h'`)) { diagnostic.header = `Missing "h" import for JSX types`; diagnostic.messageText = `In order to load accurate JSX types for components, the "h" function must be imported from "@stencil/core" by each component using JSX. For example: import { Component, h } from '@stencil/core';`; try { const sourceText = compilerCtx.fs.readFileSync(diagnostic.absFilePath); const srcLines = splitLineBreaks(sourceText); for (let i = 0; i < srcLines.length; i++) { const srcLine = srcLines[i]; if (srcLine.includes('@stencil/core')) { const msgLines = []; const beforeLineIndex = i - 1; if (beforeLineIndex > -1) { const beforeLine = { lineIndex: beforeLineIndex, lineNumber: beforeLineIndex + 1, text: srcLines[beforeLineIndex], errorCharStart: -1, errorLength: -1 }; msgLines.push(beforeLine); } const errorLine = { lineIndex: i, lineNumber: i + 1, text: srcLine, errorCharStart: 0, errorLength: -1 }; msgLines.push(errorLine); diagnostic.lineNumber = errorLine.lineNumber; diagnostic.columnNumber = srcLine.indexOf('}'); const afterLineIndex = i + 1; if (afterLineIndex < srcLines.length) { const afterLine = { lineIndex: afterLineIndex, lineNumber: afterLineIndex + 1, text: srcLines[afterLineIndex], errorCharStart: -1, errorLength: -1 }; msgLines.push(afterLine); } diagnostic.lines = msgLines; break; } } } catch (e) { } } } return diagnostic; } function splitLineBreaks(sourceText) { if (typeof sourceText !== 'string') return []; sourceText = sourceText.replace(/\\r/g, '\n'); return sourceText.split('\n'); } function escapeHtml(unsafe) { if (unsafe === undefined) return 'undefined'; if (unsafe === null) return 'null'; if (typeof unsafe !== 'string') { unsafe = unsafe.toString(); } return unsafe .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#039;'); } const MAX_ERRORS = 25; function loadMinifyJsDiagnostics(sourceText, result, diagnostics) { if (!result || !result.error) { return; } const d = { level: 'error', type: 'build', language: 'javascript', header: 'Minify JS', code: '', messageText: result.error.message, absFilePath: null, relFilePath: null, lines: [] }; if (typeof result.error.line === 'number' && result.error.line > -1) { const srcLines = splitLineBreaks(sourceText); const errorLine = { lineIndex: result.error.line - 1, lineNumber: result.error.line, text: srcLines[result.error.line - 1], errorCharStart: result.error.col, errorLength: 0 }; d.lineNumber = errorLine.lineNumber; d.columnNumber = errorLine.errorCharStart; const highlightLine = errorLine.text.substr(d.columnNumber); for (let i = 0; i < highlightLine.length; i++) { if (CHAR_BREAK.includes(highlightLine.charAt(i))) { break; } errorLine.errorLength++; } d.lines.push(errorLine); if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) { errorLine.errorLength = 1; errorLine.errorCharStart--; } if (errorLine.lineIndex > 0) { const previousLine = { lineIndex: errorLine.lineIndex - 1, lineNumber: errorLine.lineNumber - 1, text: srcLines[errorLine.lineIndex - 1], errorCharStart: -1, errorLength: -1 }; d.lines.unshift(previousLine); } if (errorLine.lineIndex + 1 < srcLines.length) { const nextLine = { lineIndex: errorLine.lineIndex + 1, lineNumber: errorLine.lineNumber + 1, text: srcLines[errorLine.lineIndex + 1], errorCharStart: -1, errorLength: -1 }; d.lines.push(nextLine); } } diagnostics.push(d); } const CHAR_BREAK = [' ', '=', '.', ',', '?', ':', ';', '(', ')', '{', '}', '[', ']', '|', `'`, `"`, '`']; function buildError(diagnostics) { const diagnostic = { level: 'error', type: 'build', header: 'Build Error', messageText: 'build error', relFilePath: null, absFilePath: null, lines: [] }; diagnostics.push(diagnostic); return diagnostic; } function buildWarn(diagnostics) { const diagnostic = { level: 'warn', type: 'build', header: 'Build Warn', messageText: 'build warn', relFilePath: null, absFilePath: null, lines: [] }; diagnostics.push(diagnostic); return diagnostic; } function buildJsonFileError(compilerCtx, diagnostics, jsonFilePath, msg, pkgKey) { const err = buildError(diagnostics); err.messageText = msg; err.absFilePath = jsonFilePath; if (typeof pkgKey === 'string') { try { const jsonStr = compilerCtx.fs.readFileSync(jsonFilePath); const lines = jsonStr.replace(/\r/g, '\n').split('\n'); for (let i = 0; i < lines.length; i++) { const txtLine = lines[i]; const txtIndex = txtLine.indexOf(pkgKey); if (txtIndex > -1) { const warnLine = { lineIndex: i, lineNumber: i + 1, text: txtLine, errorCharStart: txtIndex, errorLength: pkgKey.length }; err.lineNumber = warnLine.lineNumber; err.columnNumber = txtIndex + 1; err.lines.push(warnLine); if (i >= 0) { const beforeWarnLine = { lineIndex: warnLine.lineIndex - 1, lineNumber: warnLine.lineNumber - 1, text: lines[i - 1], errorCharStart: -1, errorLength: -1 }; err.lines.unshift(beforeWarnLine); } if (i < lines.length) { const afterWarnLine = { lineIndex: warnLine.lineIndex + 1, lineNumber: warnLine.lineNumber + 1, text: lines[i + 1], errorCharStart: -1, errorLength: -1 }; err.lines.push(afterWarnLine); } break; } } } catch (e) { } } return err; } function catchError(diagnostics, err, msg) { const diagnostic = { level: 'error', type: 'build', header: 'Build Error', messageText: 'build error', relFilePath: null, absFilePath: null, lines: [] }; if (typeof msg === 'string') { diagnostic.messageText = msg; } else if (err != null) { if (err.stack != null) { diagnostic.messageText = err.stack.toString(); } else { if (err.message != null) { diagnostic.messageText = err.message.toString(); } else { diagnostic.messageText = err.toString(); } } } if (diagnostics != null && !shouldIgnoreError(diagnostic.messageText)) { diagnostics.push(diagnostic); } return diagnostic; } function hasError(diagnostics) { if (diagnostics == null || diagnostics.length === 0) { return false; } return diagnostics.some(d => d.level === 'error' && d.type !== 'runtime'); } function hasWarning(diagnostics) { if (diagnostics == null || diagnostics.length === 0) { return false; } return diagnostics.some(d => d.level === 'warn'); } function shouldIgnoreError(msg) { return (msg === TASK_CANCELED_MSG); } const TASK_CANCELED_MSG = `task canceled`; function loadRollupDiagnostics(buildCtx, rollupError) { const diagnostic = { level: 'error', type: 'bundling', language: 'javascript', code: rollupError.code, header: `Rollup: ${formatErrorCode(rollupError.code)}`, messageText: rollupError.message || '', relFilePath: null, absFilePath: null, lines: [] }; if (rollupError.plugin) { diagnostic.messageText += ` (plugin: ${rollupError.plugin}${rollupError.hook ? `, ${rollupError.hook}` : ''})`; } if (rollupError.loc) { diagnostic.absFilePath = rollupError.loc.file; diagnostic.lineNumber = rollupError.loc.line; diagnostic.columnNumber = rollupError.loc.column; } if (typeof rollupError.frame === 'string') { const sourceText = rollupError.frame.replace(/\r/g, '\n'); diagnostic.lines = sourceText.split('\n') .map(line => { const errorLine = { lineIndex: 0, lineNumber: 0, text: line, errorCharStart: -1, errorLength: -1 }; return errorLine; }); } buildCtx.diagnostics.push(diagnostic); } function createOnWarnFn(diagnostics, bundleModulesFiles) { const previousWarns = new Set(); return function onWarningMessage(warning) { if (warning == null || ignoreWarnCodes.has(warning.code) || previousWarns.has(warning.message)) { return; } previousWarns.add(warning.message); let label = ''; if (bundleModulesFiles) { label = bundleModulesFiles.reduce((cmps, m) => { cmps.push(...m.cmps); return cmps; }, []).join(', ').trim(); if (label.length) { label += ': '; } } const diagnostic = buildWarn(diagnostics); diagnostic.header = `Bundling Warning ${warning.code}`; diagnostic.messageText = label + (warning.message || warning); }; } const ignoreWarnCodes = new Set([ 'THIS_IS_UNDEFINED', 'NON_EXISTENT_EXPORT', 'CIRCULAR_DEPENDENCY', 'EMPTY_BUNDLE', 'UNUSED_EXTERNAL_IMPORT' ]); function formatErrorCode(errorCode) { if (typeof errorCode === 'string') { return errorCode.split('_').map(c => { return toTitleCase(c.toLowerCase()); }).join(' '); } return errorCode || ''; } function augmentDiagnosticWithNode(config, d, node) { if (!node) { return d; } const sourceFile = node.getSourceFile(); if (!sourceFile) { return d; } d.absFilePath = normalizePath(sourceFile.fileName); d.relFilePath = normalizePath(config.sys.path.relative(config.rootDir, sourceFile.fileName)); const sourceText = sourceFile.text; const srcLines = splitLineBreaks(sourceText); const start = node.getStart(); const end = node.getEnd(); const posStart = sourceFile.getLineAndCharacterOfPosition(start); const errorLine = { lineIndex: posStart.line, lineNumber: posStart.line + 1, text: srcLines[posStart.line], errorCharStart: posStart.character, errorLength: Math.max(end - start, 1) }; d.lineNumber = errorLine.lineNumber; d.columnNumber = errorLine.errorCharStart + 1; d.lines.push(errorLine); if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) { errorLine.errorLength = 1; errorLine.errorCharStart--; } if (errorLine.lineIndex > 0) { const previousLine = { lineIndex: errorLine.lineIndex - 1, lineNumber: errorLine.lineNumber - 1, text: srcLines[errorLine.lineIndex - 1], errorCharStart: -1, errorLength: -1 }; d.lines.unshift(previousLine); } if (errorLine.lineIndex + 1 < srcLines.length) { const nextLine = { lineIndex: errorLine.lineIndex + 1, lineNumber: errorLine.lineNumber + 1, text: srcLines[errorLine.lineIndex + 1], errorCharStart: -1, errorLength: -1 }; d.lines.push(nextLine); } return d; } /** * Ok, so formatting overkill, we know. But whatever, it makes for great * error reporting within a terminal. So, yeah, let's code it up, shall we? */ function loadTypeScriptDiagnostics(resultsDiagnostics, tsDiagnostics) { const maxErrors = Math.min(tsDiagnostics.length, 50); for (let i = 0; i < maxErrors; i++) { resultsDiagnostics.push(loadTypeScriptDiagnostic(tsDiagnostics[i])); } } function loadTypeScriptDiagnostic(tsDiagnostic) { const d = { level: 'warn', type: 'typescript', language: 'typescript', header: 'TypeScript', code: tsDiagnostic.code.toString(), messageText: formatMessageText(tsDiagnostic), relFilePath: null, absFilePath: null, lines: [] }; if (tsDiagnostic.category === 1) { d.level = 'error'; } if (tsDiagnostic.file) { d.absFilePath = tsDiagnostic.file.fileName; const sourceText = tsDiagnostic.file.text; const srcLines = splitLineBreaks(sourceText); const posData = tsDiagnostic.file.getLineAndCharacterOfPosition(tsDiagnostic.start); const errorLine = { lineIndex: posData.line, lineNumber: posData.line + 1, text: srcLines[posData.line], errorCharStart: posData.character, errorLength: Math.max(tsDiagnostic.length, 1) }; d.lineNumber = errorLine.lineNumber; d.columnNumber = errorLine.errorCharStart + 1; d.lines.push(errorLine); if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) { errorLine.errorLength = 1; errorLine.errorCharStart--; } if (errorLine.lineIndex > 0) { const previousLine = { lineIndex: errorLine.lineIndex - 1, lineNumber: errorLine.lineNumber - 1, text: srcLines[errorLine.lineIndex - 1], errorCharStart: -1, errorLength: -1 }; d.lines.unshift(previousLine); } if (errorLine.lineIndex + 1 < srcLines.length) { const nextLine = { lineIndex: errorLine.lineIndex + 1, lineNumber: errorLine.lineNumber + 1, text: srcLines[errorLine.lineIndex + 1], errorCharStart: -1, errorLength: -1 }; d.lines.push(nextLine); } } return d; } function formatMessageText(tsDiagnostic) { let diagnosticChain = tsDiagnostic.messageText; if (typeof diagnosticChain === 'string') { return diagnosticChain; } const ignoreCodes = []; const isStencilConfig = tsDiagnostic.file.fileName.includes('stencil.config'); if (isStencilConfig) { ignoreCodes.push(2322); } let result = ''; while (diagnosticChain) { if (!ignoreCodes.includes(diagnosticChain.code)) { result += diagnosticChain.messageText + ' '; } diagnosticChain = diagnosticChain.next; } if (isStencilConfig) { result = result.replace(`type 'StencilConfig'`, `Stencil Config`); result = result.replace(`Object literal may only specify known properties, but `, ``); result = result.replace(`Object literal may only specify known properties, and `, ``); } return result.trim(); } /** * Test if a file is a typescript source file, such as .ts or .tsx. * However, d.ts files and spec.ts files return false. * @param filePath */ function isTsFile(filePath) { const parts = filePath.toLowerCase().split('.'); if (parts.length > 1) { if (parts[parts.length - 1] === 'ts' || parts[parts.length - 1] === 'tsx') { if (parts.length > 2 && (parts[parts.length - 2] === 'd' || parts[parts.length - 2] === 'spec')) { return false; } return true; } } return false; } function isDtsFile(filePath) { const parts = filePath.toLowerCase().split('.'); if (parts.length > 2) { return (parts[parts.length - 2] === 'd' && parts[parts.length - 1] === 'ts'); } return false; } function isJsFile(filePath) { const parts = filePath.toLowerCase().split('.'); if (parts.length > 1) { if (parts[parts.length - 1] === 'js') { if (parts.length > 2 && parts[parts.length - 2] === 'spec') { return false; } return true; } } return false; } function hasFileExtension(filePath, extensions) { filePath = filePath.toLowerCase(); return extensions.some(ext => filePath.endsWith('.' + ext)); } function isCssFile(filePath) { return hasFileExtension(filePath, ['css']); } function isHtmlFile(filePath) { return hasFileExtension(filePath, ['html', 'htm']); } /** * Only web development text files, like ts, tsx, * js, html, css, scss, etc. * @param filePath */ function isWebDevFile(filePath) { return (hasFileExtension(filePath, WEB_DEV_EXT) || isTsFile(filePath)); } const WEB_DEV_EXT = ['js', 'jsx', 'html', 'htm', 'css', 'scss', 'sass', 'less', 'styl', 'pcss']; function generatePreamble(config, opts = {}) { let preamble = []; if (config.preamble) { preamble = config.preamble.split('\n'); } if (typeof opts.prefix === 'string') { opts.prefix.split('\n').forEach(c => { preamble.push(c); }); } if (opts.defaultBanner === true) { preamble.push(BANNER); } if (typeof opts.suffix === 'string') { opts.suffix.split('\n').forEach(c => { preamble.push(c); }); } if (preamble.length > 1) { preamble = preamble.map(l => ` * ${l}`); preamble.unshift(`/*!`); preamble.push(` */`); return preamble.join('\n'); } if (opts.defaultBanner === true) { return `/*! ${BANNER} */`; } return ''; } function isDocsPublic(jsDocs) { return !(jsDocs && jsDocs.tags.some((s) => s.name === 'internal')); } function getDependencies(buildCtx) { if (buildCtx.packageJson != null && buildCtx.packageJson.dependencies != null) { return Object.keys(buildCtx.packageJson.dependencies) .filter(pkgName => !SKIP_DEPS.includes(pkgName)); } return []; } function hasDependency(buildCtx, depName) { return getDependencies(buildCtx).includes(depName); } function getDynamicImportFunction(namespace) { return `__sc_import_${namespace.replace(/\s|-/g, '_')}`; } async function readPackageJson(config, compilerCtx, buildCtx) { const pkgJsonPath = config.sys.path.join(config.rootDir, 'package.json'); let pkgJson; try { pkgJson = await compilerCtx.fs.readFile(pkgJsonPath); } catch (e) { if (!config.outputTargets.some(o => o.type.includes('dist'))) { const diagnostic = buildError(buildCtx.diagnostics); diagnostic.header = `Missing "package.json"`; diagnostic.messageText = `Valid "package.json" file is required for distribution: ${pkgJsonPath}`; } return null; } let pkgData; try { pkgData = JSON.parse(pkgJson); } catch (e) { const diagnostic = buildError(buildCtx.diagnostics); diagnostic.header = `Error parsing "package.json"`; diagnostic.messageText = `${pkgJsonPath}, ${e}`; diagnostic.absFilePath = pkgJsonPath; return null; } buildCtx.packageJsonFilePath = pkgJsonPath; return pkgData; } const SKIP_DEPS = ['@stencil/core']; function validateComponentTag(tag) { if (tag !== tag.trim()) { return `Tag can not contain white spaces`; } if (tag !== tag.toLowerCase()) { return `Tag can not contain upper case characters`; } if (typeof tag !== 'string') { return `Tag "${tag}" must be a string type`; } if (tag.length === 0) { return `Received empty tag value`; } if (tag.indexOf(' ') > -1) { return `"${tag}" tag cannot contain a space`; } if (tag.indexOf(',') > -1) { return `"${tag}" tag cannot be used for multiple tags`; } const invalidChars = tag.replace(/\w|-/g, '')