"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformer = exports.loadTsconfig = void 0; const path_1 = require("path"); const typescript_1 = __importDefault(require("typescript")); const compiler_1 = require("svelte/compiler"); const magic_string_1 = __importDefault(require("magic-string")); const sorcery_1 = __importDefault(require("sorcery")); const errors_1 = require("../modules/errors"); const markup_1 = require("../modules/markup"); const utils_1 = require("../modules/utils"); const package_json_1 = __importDefault(require("svelte/package.json")); const injectedCodeSeparator = 'const $$$$$$$$ = null;'; /** * Map of valid tsconfigs (no errors). Key is the path. */ const tsconfigMap = new Map(); function createFormatDiagnosticsHost(cwd) { return { getCanonicalFileName: (fileName) => fileName.replace('.injected.ts', ''), getCurrentDirectory: () => cwd, getNewLine: () => typescript_1.default.sys.newLine, }; } function formatDiagnostics(diagnostics, basePath) { if (Array.isArray(diagnostics)) { return typescript_1.default.formatDiagnosticsWithColorAndContext(diagnostics, createFormatDiagnosticsHost(basePath)); } return typescript_1.default.formatDiagnostic(diagnostics, createFormatDiagnosticsHost(basePath)); } const importTransformer = (context) => { const visit = (node) => { var _a; if (typescript_1.default.isImportDeclaration(node)) { if ((_a = node.importClause) === null || _a === void 0 ? void 0 : _a.isTypeOnly) { return typescript_1.default.createEmptyStatement(); } return typescript_1.default.createImportDeclaration(node.decorators, node.modifiers, node.importClause, node.moduleSpecifier); } return typescript_1.default.visitEachChild(node, (child) => visit(child), context); }; return (node) => typescript_1.default.visitNode(node, visit); }; function getScriptContent(markup, module) { const regex = (0, markup_1.createTagRegex)('script', 'gi'); let match; while ((match = regex.exec(markup)) !== null) { const { context } = (0, markup_1.parseAttributes)(match[1] || ''); if ((context !== 'module' && !module) || (context === 'module' && module)) { return match[2]; } } return ''; } function createSourceMapChain({ filename, content, compilerOptions, }) { if (compilerOptions.sourceMap) { return { content: { [filename]: content, }, sourcemaps: {}, }; } } function injectVarsToCode({ content, markup, filename, attributes, sourceMapChain, }) { if (!markup) return content; const { vars } = (0, compiler_1.compile)((0, markup_1.stripTags)(markup), { generate: false, varsReport: 'full', errorMode: 'warn', filename, }); const sep = `\n${injectedCodeSeparator}\n`; const varnames = vars.map((v) => v.name.startsWith('$') && !v.name.startsWith('$$') ? `${v.name},${v.name.slice(1)}` : v.name); const contentForCodestores = content + // Append instance script content because it's valid // to import a store in module script and autosubscribe to it in instance script ((attributes === null || attributes === void 0 ? void 0 : attributes.context) === 'module' ? getScriptContent(markup, false) : ''); // This regex extracts all possible store variables // TODO investigate if it's possible to achieve this with a // TS transformer (previous attemps have failed) const codestores = Array.from(contentForCodestores.match(/\$[^\s();:,[\]{}.?!+\-=*/\\~|&%<>^`"'°§#0-9][^\s();:,[\]{}.?!+\-=*/\\~|&%<>^`"'°§#]*/g) || [], (name) => name.slice(1)).filter((name) => !utils_1.JAVASCRIPT_RESERVED_KEYWORD_SET.has(name)); const varsString = [...codestores, ...varnames].join(','); const injectedVars = `const $$vars$$ = [${varsString}];`; // Append instance/markup script content because it's valid // to import things in one and reference it in the other. const injectedCode = (attributes === null || attributes === void 0 ? void 0 : attributes.context) === 'module' ? `${sep}${getScriptContent(markup, false)}\n${injectedVars}` : `${sep}${getScriptContent(markup, true)}\n${injectedVars}`; if (sourceMapChain) { const ms = new magic_string_1.default(content).append(injectedCode); const fname = `${filename}.injected.ts`; const code = ms.toString(); const map = ms.generateMap({ source: filename, file: fname, hires: true, }); sourceMapChain.content[fname] = code; sourceMapChain.sourcemaps[fname] = map; return code; } return `${content}${injectedCode}`; } function stripInjectedCode({ transpiledCode, markup, filename, sourceMapChain, }) { if (!markup) return transpiledCode; const injectedCodeStart = transpiledCode.indexOf(injectedCodeSeparator); if (sourceMapChain) { const ms = new magic_string_1.default(transpiledCode).snip(0, injectedCodeStart); const source = `${filename}.transpiled.js`; const file = `${filename}.js`; const code = ms.toString(); const map = ms.generateMap({ source, file, hires: true, }); sourceMapChain.content[file] = code; sourceMapChain.sourcemaps[file] = map; return code; } return transpiledCode.slice(0, injectedCodeStart); } async function concatSourceMaps({ filename, markup, sourceMapChain, }) { if (!sourceMapChain) return; if (!markup) { return sourceMapChain.sourcemaps[`${filename}.js`]; } const chain = await sorcery_1.default.load(`${filename}.js`, sourceMapChain); return chain.apply(); } function getCompilerOptions({ filename, options, basePath, }) { var _a; const inputOptions = (_a = options.compilerOptions) !== null && _a !== void 0 ? _a : {}; const { errors, options: convertedCompilerOptions } = options.tsconfigFile !== false || options.tsconfigDirectory ? loadTsconfig(inputOptions, filename, options) : typescript_1.default.convertCompilerOptionsFromJson(inputOptions, basePath); if (errors.length) { throw new Error(formatDiagnostics(errors, basePath)); } const compilerOptions = { target: typescript_1.default.ScriptTarget.ES2015, moduleResolution: typescript_1.default.ModuleResolutionKind.NodeJs, ...convertedCompilerOptions, importsNotUsedAsValues: typescript_1.default.ImportsNotUsedAsValues.Error, allowNonTsExtensions: true, // Clear outDir since it causes source map issues when the files aren't actually written to disk. outDir: undefined, }; if (compilerOptions.target === typescript_1.default.ScriptTarget.ES3 || compilerOptions.target === typescript_1.default.ScriptTarget.ES5) { throw new Error(`Svelte only supports es6+ syntax. Set your 'compilerOptions.target' to 'es6' or higher.`); } return compilerOptions; } function transpileTs({ code, fileName, basePath, options, compilerOptions, transformers, }) { const { outputText: transpiledCode, sourceMapText, diagnostics, } = typescript_1.default.transpileModule(code, { fileName, compilerOptions, reportDiagnostics: options.reportDiagnostics !== false, transformers, }); if (diagnostics && diagnostics.length > 0) { // could this be handled elsewhere? const hasError = diagnostics.some((d) => d.category === typescript_1.default.DiagnosticCategory.Error); const formattedDiagnostics = formatDiagnostics(diagnostics, basePath); console.log(formattedDiagnostics); if (hasError) { (0, errors_1.throwTypescriptError)(); } } return { transpiledCode, sourceMapText, diagnostics }; } function loadTsconfig(compilerOptionsJSON, filename, tsOptions) { if (typeof tsOptions.tsconfigFile === 'boolean') { return { errors: [], options: compilerOptionsJSON }; } let basePath = process.cwd(); const fileDirectory = (tsOptions.tsconfigDirectory || (0, path_1.dirname)(filename)); let tsconfigFile = tsOptions.tsconfigFile || typescript_1.default.findConfigFile(fileDirectory, typescript_1.default.sys.fileExists); if (!tsconfigFile) { return { errors: [], options: compilerOptionsJSON }; } tsconfigFile = (0, path_1.isAbsolute)(tsconfigFile) ? tsconfigFile : (0, path_1.join)(basePath, tsconfigFile); basePath = (0, path_1.dirname)(tsconfigFile); if (tsconfigMap.has(tsconfigFile)) { return { errors: [], options: tsconfigMap.get(tsconfigFile), }; } const { error, config } = typescript_1.default.readConfigFile(tsconfigFile, typescript_1.default.sys.readFile); if (error) { throw new Error(formatDiagnostics(error, basePath)); } // Do this so TS will not search for initial files which might take a while config.include = []; let { errors, options } = typescript_1.default.parseJsonConfigFileContent(config, typescript_1.default.sys, basePath, compilerOptionsJSON, tsconfigFile); // Filter out "no files found error" errors = errors.filter((d) => d.code !== 18003); if (errors.length === 0) { tsconfigMap.set(tsconfigFile, options); } return { errors, options }; } exports.loadTsconfig = loadTsconfig; async function mixedImportsTranspiler({ content, filename = 'source.svelte', markup, options = {}, attributes, compilerOptions, basePath, }) { const sourceMapChain = createSourceMapChain({ filename, content, compilerOptions, }); const injectedCode = injectVarsToCode({ content, markup, filename, attributes, sourceMapChain, }); const { transpiledCode, sourceMapText, diagnostics } = transpileTs({ code: injectedCode, fileName: `${filename}.injected.ts`, basePath, options, compilerOptions, }); if (sourceMapChain && sourceMapText) { const fname = `${filename}.transpiled.js`; sourceMapChain.content[fname] = transpiledCode; sourceMapChain.sourcemaps[fname] = JSON.parse(sourceMapText); } const code = stripInjectedCode({ transpiledCode, markup, filename, sourceMapChain, }); // Sorcery tries to load the code/map from disk if it's empty, // prevent that because it would try to load inexistent files // https://github.com/Rich-Harris/sorcery/issues/167 if (!code) { return { code, diagnostics }; } const map = await concatSourceMaps({ filename, markup, sourceMapChain, }); return { code, map, diagnostics, }; } async function simpleTranspiler({ content, filename = 'source.svelte', options = {}, compilerOptions, basePath, }) { const { transpiledCode, sourceMapText, diagnostics } = transpileTs({ code: content, // `preserveValueImports` essentially does the same as our import transformer, // keeping all imports that are not type imports transformers: compilerOptions.preserveValueImports ? undefined : { before: [importTransformer] }, fileName: filename, basePath, options, compilerOptions, }); return { code: transpiledCode, map: sourceMapText, diagnostics, }; } const transformer = async ({ content, filename, markup, options = {}, attributes, }) => { const basePath = process.cwd(); if (filename == null) return { code: content }; filename = (0, path_1.isAbsolute)(filename) ? filename : (0, path_1.resolve)(basePath, filename); const compilerOptions = getCompilerOptions({ filename, options, basePath }); const versionParts = package_json_1.default.version.split('.'); const canUseMixedImportsTranspiler = +versionParts[0] > 3 || (+versionParts[0] === 3 && +versionParts[1] >= 39); if (!canUseMixedImportsTranspiler && options.handleMixedImports) { throw new Error('You need at least Svelte 3.39 to use the handleMixedImports option'); } const handleMixedImports = !compilerOptions.preserveValueImports && (options.handleMixedImports === false ? false : options.handleMixedImports || canUseMixedImportsTranspiler); return handleMixedImports ? mixedImportsTranspiler({ content, filename, markup, options, attributes, compilerOptions, basePath, }) : simpleTranspiler({ content, filename, markup, options, attributes, compilerOptions, basePath, }); }; exports.transformer = transformer;