UNPKG

svelte-language-server

Version:
265 lines 10.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.configLoader = exports.ConfigLoader = void 0; const logger_1 = require("../../logger"); const importPackage_1 = require("../../importPackage"); const fdir_1 = require("fdir"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const url_1 = require("url"); const fileCollection_1 = require("./fileCollection"); const typescript_1 = __importDefault(require("typescript")); const DEFAULT_OPTIONS = { dev: true }; const NO_GENERATE = { generate: false }; /** * This function encapsulates the import call in a way * that TypeScript does not transpile `import()`. * https://github.com/microsoft/TypeScript/issues/43329 */ const _dynamicImport = new Function('modulePath', 'return import(modulePath)'); const configRegex = /\/svelte\.config\.(js|cjs|mjs)$/; /** * Loads svelte.config.{js,cjs,mjs} files. Provides both a synchronous and asynchronous * interface to get a config file because snapshots need access to it synchronously. * This means that another instance (the ts service host on startup) should make * sure that all config files are loaded before snapshots are retrieved. * Asynchronousity is needed because we use the dynamic `import()` statement. */ class ConfigLoader { constructor(globSync, fs, path, dynamicImport) { this.globSync = globSync; this.fs = fs; this.path = path; this.dynamicImport = dynamicImport; this.configFiles = new fileCollection_1.FileMap(); this.configFilesAsync = new fileCollection_1.FileMap(); this.filePathToConfigPath = new fileCollection_1.FileMap(); this.disabled = false; } /** * Enable/disable loading of configs (for security reasons for example) */ setDisabled(disabled) { this.disabled = disabled; } /** * Tries to load all `svelte.config.js` files below given directory * and the first one found inside/above that directory. * * @param directory Directory where to load the configs from */ async loadConfigs(directory) { logger_1.Logger.log('Trying to load configs for', directory); try { const pathResults = new this.globSync({}) .withPathSeparator('/') .exclude((_, path) => { // no / at the start, path could start with node_modules return path.includes('node_modules/') || path.includes('/.') || path[0] === '.'; }) .filter((path, isDir) => { return !isDir && configRegex.test(path); }) .withRelativePaths() .crawl(directory) .sync(); const someConfigIsImmediateFileInDirectory = pathResults.length > 0 && pathResults.some((res) => !this.path.dirname(res)); if (!someConfigIsImmediateFileInDirectory) { const configPathUpwards = this.searchConfigPathUpwards(directory); if (configPathUpwards) { pathResults.push(this.path.relative(directory, configPathUpwards)); } } if (pathResults.length === 0) { this.addFallbackConfig(directory); return; } const promises = pathResults .map((pathResult) => this.path.join(directory, pathResult)) .filter((pathResult) => { const config = this.configFiles.get(pathResult); return !config || config.loadConfigError; }) .map(async (pathResult) => { await this.loadAndCacheConfig(pathResult, directory); }); await Promise.all(promises); } catch (e) { logger_1.Logger.error(e); } } addFallbackConfig(directory) { const fallback = this.useFallbackPreprocessor(directory, false); const path = this.path.join(directory, 'svelte.config.js'); this.configFilesAsync.set(path, Promise.resolve(fallback)); this.configFiles.set(path, fallback); } searchConfigPathUpwards(path) { let currentDir = path; let nextDir = this.path.dirname(path); while (currentDir !== nextDir) { const tryFindConfigPath = (ending) => { const path = this.path.join(currentDir, `svelte.config.${ending}`); return this.fs.existsSync(path) ? path : undefined; }; const configPath = tryFindConfigPath('js') || tryFindConfigPath('cjs') || tryFindConfigPath('mjs'); if (configPath) { return configPath; } currentDir = nextDir; nextDir = this.path.dirname(currentDir); } } async loadAndCacheConfig(configPath, directory) { const loadingConfig = this.configFilesAsync.get(configPath); if (loadingConfig) { await loadingConfig; } else { const newConfig = this.loadConfig(configPath, directory); this.configFilesAsync.set(configPath, newConfig); this.configFiles.set(configPath, await newConfig); } } async loadConfig(configPath, directory) { try { let config = this.disabled ? {} : (await this.dynamicImport((0, url_1.pathToFileURL)(configPath)))?.default; if (!config) { throw new Error('Missing exports in the config. Make sure to include "export default config" or "module.exports = config"'); } config = { ...config, compilerOptions: { ...DEFAULT_OPTIONS, ...config.compilerOptions, ...NO_GENERATE } }; logger_1.Logger.log('Loaded config at ', configPath); return config; } catch (err) { logger_1.Logger.error('Error while loading config at ', configPath); logger_1.Logger.error(err); const config = { ...this.useFallbackPreprocessor(directory, true), compilerOptions: { ...DEFAULT_OPTIONS, ...NO_GENERATE }, loadConfigError: err }; return config; } } /** * Returns config associated to file. If no config is found, the file * was called in a context where no config file search was done before, * which can happen * - if TS intellisense is turned off and the search did not run on tsconfig init * - if the file was opened not through the TS service crawl, but through the LSP * * @param file */ getConfig(file) { const cached = this.filePathToConfigPath.get(file); if (cached) { return this.configFiles.get(cached); } let currentDir = file; let nextDir = this.path.dirname(file); while (currentDir !== nextDir) { currentDir = nextDir; const config = this.tryGetConfig(file, currentDir, 'js') || this.tryGetConfig(file, currentDir, 'cjs') || this.tryGetConfig(file, currentDir, 'mjs'); if (config) { return config; } nextDir = this.path.dirname(currentDir); } } /** * Like `getConfig`, but will search for a config above if no config found. */ async awaitConfig(file) { const config = this.getConfig(file); if (config) { return config; } const fileDirectory = this.path.dirname(file); const configPath = this.searchConfigPathUpwards(fileDirectory); if (configPath) { await this.loadAndCacheConfig(configPath, fileDirectory); } else { this.addFallbackConfig(fileDirectory); } return this.getConfig(file); } tryGetConfig(file, fromDirectory, configFileEnding) { const path = this.path.join(fromDirectory, `svelte.config.${configFileEnding}`); const config = this.configFiles.get(path); if (config) { this.filePathToConfigPath.set(file, path); return config; } } useFallbackPreprocessor(path, foundConfig) { try { const sveltePreprocess = (0, importPackage_1.importSveltePreprocess)(path); logger_1.Logger.log((foundConfig ? 'Found svelte.config.js but there was an error loading it. ' : 'No svelte.config.js found. ') + 'Using https://github.com/sveltejs/svelte-preprocess as fallback'); return { preprocess: sveltePreprocess({ // 4.x does not have transpileOnly anymore, but if the user has version 3.x // in his repo, that one is loaded instead, for which we still need this. typescript: { transpileOnly: true, compilerOptions: { sourceMap: true, inlineSourceMap: false } } }), isFallbackConfig: true }; } catch (e) { // User doesn't have svelte-preprocess installed, provide a barebones TS preprocessor return { preprocess: { // @ts-ignore name property exists in Svelte 4 onwards name: 'svelte-language-tools-ts-fallback-preprocessor', script: ({ content, attributes, filename }) => { if (attributes.lang !== 'ts') return; const { outputText, sourceMapText } = typescript_1.default.transpileModule(content, { fileName: filename, compilerOptions: { module: typescript_1.default.ModuleKind.ESNext, target: typescript_1.default.ScriptTarget.ESNext, sourceMap: true, verbatimModuleSyntax: true } }); return { code: outputText, map: sourceMapText }; } }, isFallbackConfig: true }; } } } exports.ConfigLoader = ConfigLoader; exports.configLoader = new ConfigLoader(fdir_1.fdir, fs_1.default, path_1.default, _dynamicImport); //# sourceMappingURL=configLoader.js.map