UNPKG

svelte-language-server

Version:
385 lines 16.8 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 load_config_1 = require("@sveltejs/load-config"); const utils_1 = require("../../utils"); const importPackage_1 = require("../../importPackage"); const fdir_1 = require("fdir"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const fileCollection_1 = require("./fileCollection"); const typescript_1 = __importDefault(require("typescript")); const DEFAULT_OPTIONS = { dev: true }; const NO_GENERATE = { generate: false }; const SVELTE_CONFIG_EXTENSIONS = ['js', 'cjs', 'mjs']; const SVELTE_CONFIG_TS_EXTENSIONS = ['ts', 'mts']; const VITE_CONFIG_EXTENSIONS = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts']; const configRegex = /\/(svelte\.config\.(js|ts|cjs|mjs|mts)|vite\.config\.(js|mjs|ts|cjs|mts|cts))$/; const configRegexWithoutTs = /\/(svelte\.config\.(js|cjs|mjs)|vite\.config\.(js|mjs|ts|cjs|mts|cts))$/; /** * Loads vite.config.* and svelte.config.{js,ts,cjs,mjs,mts} 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, processFeatures, loadFromDirectory) { this.globSync = globSync; this.fs = fs; this.path = path; this.loadFromDirectory = loadFromDirectory; this.configFiles = new fileCollection_1.FileMap(); this.configFilesAsync = new fileCollection_1.FileMap(); this.filePathToConfigPath = new fileCollection_1.FileMap(); this.disabled = false; this.loadSvelteConfigTs = processFeatures && 'typescript' in processFeatures && !!processFeatures.typescript; } /** * Enable/disable loading of configs (for security reasons for example) */ setDisabled(disabled) { this.disabled = disabled; } /** * Use a specific config file path instead of searching for standard config filenames. * Only applies within `rootDirectory` (the initial tsconfig/workspace being checked). */ setExplicitConfigScope(scope) { this.explicitConfigScope = scope; } isInExplicitConfigScope(fileOrDirPath) { if (!this.explicitConfigScope) { return false; } const normalized = (0, utils_1.normalizePath)(fileOrDirPath); const root = (0, utils_1.normalizePath)(this.explicitConfigScope.rootDirectory); return normalized === root || normalized.startsWith(root + '/'); } /** * 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) { // TODO at some point we gotta find a good way to not do this anymore. Nowadays most if not all projects have one svelte.config/vite.config file at the root of the project, // no need to traverse each time when there's a tsconfig.json. Something like "if Svelte 5 && tsconfig.json found then don't?" const targetRegex = this.loadSvelteConfigTs ? configRegex : configRegexWithoutTs; logger_1.Logger.log('Trying to load configs for', directory); try { if (this.explicitConfigScope && this.isInExplicitConfigScope(directory)) { await this.loadAndCacheConfig(this.explicitConfigScope.configPath, directory); return; } 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 && targetRegex.test(path); }) .withRelativePaths() .crawl(directory) .sync(); const someConfigIsImmediateFileInDirectory = pathResults.length > 0 && pathResults.some((res) => { const dirname = this.path.dirname(res); return !dirname || dirname === '.'; }); if (!someConfigIsImmediateFileInDirectory) { const configPathUpwards = this.searchConfigPathUpwards(directory); if (configPathUpwards) { pathResults.push(this.path.relative(directory, configPathUpwards)); } } if (pathResults.length === 0) { await 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); } } async addFallbackConfig(directory) { const configPath = this.searchConfigPathUpwards(directory); if (configPath) { const loadedConfigPath = await this.loadAndCacheConfig(configPath, directory); const config = loadedConfigPath && this.configFiles.get(loadedConfigPath); if (config && !config.loadConfigError && !config.isFallbackConfig) { return; } } const fallback = this.useFallbackPreprocessor(directory, false, configPath && isViteConfigPath(configPath) ? 'vite-error' : 'none'); const path = this.path.join(directory, 'svelte.config.js'); this.configFilesAsync.set(path, Promise.resolve(fallback)); this.configFiles.set(path, fallback); } searchConfigPathUpwards(path) { if (this.explicitConfigScope && this.isInExplicitConfigScope(path)) { return this.explicitConfigScope.configPath; } let currentDir = path; let nextDir = this.path.dirname(path); while (currentDir !== nextDir) { const configPath = findViteConfigInDirectory(this.fs, this.path, currentDir) ?? findSvelteConfigInDirectory(this.fs, this.path, currentDir, this.loadSvelteConfigTs); 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; return configPath; } else { const newConfig = this.loadConfig(configPath, directory); this.configFilesAsync.set(configPath, newConfig.then(({ config }) => config)); const { config, configFilePath } = await newConfig; this.configFiles.set(configFilePath, config); if (configFilePath !== configPath) { this.configFiles.set(configPath, config); } return configFilePath; } } async loadConfig(configPath, directory) { const configDirectory = this.path.dirname(configPath); if (this.disabled) { return { config: { ...this.useFallbackPreprocessor(directory, true, getConfigSource(configPath)), configSource: getConfigSource(configPath), compilerOptions: { ...DEFAULT_OPTIONS, ...NO_GENERATE }, loadConfigError: new Error('Config loading is disabled') }, configFilePath: configPath }; } const result = await this.loadFromDirectory(this.explicitConfigScope && configPath === this.explicitConfigScope.configPath && this.isInExplicitConfigScope(directory) ? configPath : configDirectory, { traverse: false }); if (result && 'config' in result) { const configSource = result.configSource; const config = { ...result.config, configSource, compilerOptions: { ...DEFAULT_OPTIONS, ...result.config.compilerOptions, ...NO_GENERATE } }; logger_1.Logger.log('Loaded config at ', result.configFilePath); return { config, configFilePath: result.configFilePath }; } const configSource = result?.configSource ?? getConfigSource(configPath); const error = result?.error ?? new Error(configSource === 'vite' ? 'No Svelte configuration found in vite config. Is @sveltejs/vite-plugin-svelte configured?' : 'No Svelte configuration found'); const errorConfigPath = result?.configFilePath ?? configPath; logger_1.Logger.error('Error while loading config at ', errorConfigPath); logger_1.Logger.error(error); return { config: { ...this.useFallbackPreprocessor(directory, true, configSource), configSource, compilerOptions: { ...DEFAULT_OPTIONS, ...NO_GENERATE }, loadConfigError: error }, configFilePath: errorConfigPath }; } /** * 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.tryGetConfigForDirectory(file, currentDir); 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 { await this.addFallbackConfig(fileDirectory); } return this.getConfig(file); } tryGetConfigForDirectory(file, fromDirectory) { if (this.explicitConfigScope && this.isInExplicitConfigScope(file)) { const config = this.configFiles.get(this.explicitConfigScope.configPath); if (config) { this.filePathToConfigPath.set(file, this.explicitConfigScope.configPath); return config; } } for (const ending of VITE_CONFIG_EXTENSIONS) { const configPath = this.path.join(fromDirectory, `vite.config.${ending}`); const config = this.configFiles.get(configPath); if (config) { this.filePathToConfigPath.set(file, configPath); return config; } } for (const ending of getSvelteConfigExtensions(this.loadSvelteConfigTs)) { const configPath = this.path.join(fromDirectory, `svelte.config.${ending}`); const config = this.configFiles.get(configPath); if (config) { this.filePathToConfigPath.set(file, configPath); return config; } } } useFallbackPreprocessor(path, foundConfig, configKind) { try { const sveltePreprocess = (0, importPackage_1.importSveltePreprocess)(path); logger_1.Logger.log(getFallbackLogMessage(foundConfig, configKind) + '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; function getSvelteConfigExtensions(loadSvelteConfigTs) { return loadSvelteConfigTs ? [...SVELTE_CONFIG_EXTENSIONS, ...SVELTE_CONFIG_TS_EXTENSIONS] : SVELTE_CONFIG_EXTENSIONS; } function findSvelteConfigInDirectory(fs, pathUtils, directory, loadSvelteConfigTs) { for (const ending of getSvelteConfigExtensions(loadSvelteConfigTs)) { const configPath = pathUtils.join(directory, `svelte.config.${ending}`); if (fs.existsSync(configPath)) { return configPath; } } } function findViteConfigInDirectory(fs, pathUtils, directory) { for (const ending of VITE_CONFIG_EXTENSIONS) { const configPath = pathUtils.join(directory, `vite.config.${ending}`); if (fs.existsSync(configPath)) { return configPath; } } } function isViteConfigPath(configPath) { return /[/\\]vite\.config\.(js|mjs|ts|cjs|mts|cts)$/.test(configPath); } function getConfigSource(configPath) { return isViteConfigPath(configPath) ? 'vite' : 'svelte'; } function getFallbackLogMessage(foundConfig, configKind) { if (foundConfig && configKind === 'svelte') { return 'Found svelte.config.js but there was an error loading it. '; } if (foundConfig && configKind === 'vite') { return 'Found vite.config but there was an error loading it. '; } if (configKind === 'vite-error') { return 'Found vite.config but there was an error loading Svelte options from it. '; } return 'No svelte.config.js or vite.config found. '; } exports.configLoader = new ConfigLoader(fdir_1.fdir, fs_1.default, path_1.default, process.features, load_config_1.loadConfig); //# sourceMappingURL=configLoader.js.map