diff --git a/.changeset/easy-dancers-smile.md b/.changeset/easy-dancers-smile.md new file mode 100644 index 000000000..d6fd3fec5 --- /dev/null +++ b/.changeset/easy-dancers-smile.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/vite-plugin-svelte': patch +--- + +refactor internal caching to reduce code, memory use and avoid perEnvironmentCache diff --git a/packages/vite-plugin-svelte/src/plugins/compile.js b/packages/vite-plugin-svelte/src/plugins/compile.js index a5bb21c66..d43270e22 100644 --- a/packages/vite-plugin-svelte/src/plugins/compile.js +++ b/packages/vite-plugin-svelte/src/plugins/compile.js @@ -1,6 +1,5 @@ import { toRollupError } from '../utils/error.js'; import { logCompilerWarnings } from '../utils/log.js'; -import { ensureWatchedFile } from '../utils/watch.js'; /** * @param {import('../types/plugin-api.d.ts').PluginAPI} api @@ -32,37 +31,30 @@ export function compile(api) { if (!svelteRequest || svelteRequest.raw) { return; } - const cache = api.getEnvironmentCache(this); let compileData; try { - const svelteMeta = this.getModuleInfo(id)?.meta?.svelte; - compileData = await compileSvelte(svelteRequest, code, options, svelteMeta?.preprocessed); + compileData = await compileSvelte( + svelteRequest, + code, + options, + this.getCombinedSourcemap() + ); } catch (e) { - cache.setError(svelteRequest, e); throw toRollupError(e, options); } if (compileData.compiled?.warnings) { logCompilerWarnings(svelteRequest, compileData.compiled.warnings, options); } - cache.update(compileData); - if (compileData.dependencies?.length) { - if (options.server) { - for (const dep of compileData.dependencies) { - ensureWatchedFile(options.server.watcher, dep, options.root); - } - } else if (options.isBuild && this.environment.config.build.watch) { - for (const dep of compileData.dependencies) { - this.addWatchFile(dep); - } - } - } return { ...compileData.compiled.js, moduleType: 'js', meta: { vite: { lang: compileData.lang + }, + svelte: { + css: compileData.compiled.css } } }; diff --git a/packages/vite-plugin-svelte/src/plugins/configure.js b/packages/vite-plugin-svelte/src/plugins/configure.js index 3797efd8c..d5d28a2cf 100644 --- a/packages/vite-plugin-svelte/src/plugins/configure.js +++ b/packages/vite-plugin-svelte/src/plugins/configure.js @@ -1,7 +1,6 @@ import process from 'node:process'; import { isDebugNamespaceEnabled, log } from '../utils/log.js'; import * as vite from 'vite'; -import { VitePluginSvelteCache } from '../utils/vite-plugin-svelte-cache.js'; import { VitePluginSvelteStats } from '../utils/vite-plugin-svelte-stats.js'; import { buildExtraViteConfig, @@ -15,7 +14,7 @@ import { buildIdFilter, buildIdParser } from '../utils/id.js'; import { createCompileSvelte } from '../utils/compile.js'; // @ts-expect-error rolldownVersion -const { version: viteVersion, rolldownVersion, perEnvironmentState } = vite; +const { version: viteVersion, rolldownVersion } = vite; /** * @param {Partial} [inlineOptions] @@ -71,8 +70,7 @@ export function configure(api, inlineOptions) { if (isDebugNamespaceEnabled('stats')) { api.options.stats = new VitePluginSvelteStats(); } - //@ts-expect-error perEnvironmentState uses a wider type for PluginContext - api.getEnvironmentCache = perEnvironmentState((_env) => new VitePluginSvelteCache()); + api.idFilter = buildIdFilter(options); api.idParser = buildIdParser(options); diff --git a/packages/vite-plugin-svelte/src/plugins/hot-update.js b/packages/vite-plugin-svelte/src/plugins/hot-update.js index 6342ae21c..b36f3fc1f 100644 --- a/packages/vite-plugin-svelte/src/plugins/hot-update.js +++ b/packages/vite-plugin-svelte/src/plugins/hot-update.js @@ -57,7 +57,7 @@ export function hotUpdate(api) { (e) => e.config.consumer === 'client' ); if (clientEnvironment) { - setupWatchers(options, api.getEnvironmentCache({ environment: clientEnvironment })); + setupWatchers(options); } else { log.warn( 'No client environment found, not adding watchers for svelte config and preprocessor dependencies' diff --git a/packages/vite-plugin-svelte/src/plugins/load-compiled-css.js b/packages/vite-plugin-svelte/src/plugins/load-compiled-css.js index fc7ec9d61..dc319e155 100644 --- a/packages/vite-plugin-svelte/src/plugins/load-compiled-css.js +++ b/packages/vite-plugin-svelte/src/plugins/load-compiled-css.js @@ -26,16 +26,14 @@ export function loadCompiledCss(api) { if (!svelteRequest) { return; } - const cache = api.getEnvironmentCache(this); - const cachedCss = cache.getCSS(svelteRequest); + const cachedCss = this.getModuleInfo(svelteRequest.filename)?.meta.svelte?.css; if (cachedCss) { const { hasGlobal, ...css } = cachedCss; if (hasGlobal === false) { // hasGlobal was added in svelte 5.26.0, so make sure it is boolean false css.meta ??= {}; css.meta.vite ??= {}; - // TODO is that slice the best way to get the filename without parsing the id? - css.meta.vite.cssScopeTo = [id.slice(0, id.lastIndexOf('?')), 'default']; + css.meta.vite.cssScopeTo = [svelteRequest.filename, 'default']; } css.moduleType = 'css'; return css; diff --git a/packages/vite-plugin-svelte/src/plugins/preprocess.js b/packages/vite-plugin-svelte/src/plugins/preprocess.js index ce05b6ce8..0bdd8f14b 100644 --- a/packages/vite-plugin-svelte/src/plugins/preprocess.js +++ b/packages/vite-plugin-svelte/src/plugins/preprocess.js @@ -3,6 +3,8 @@ import { mapToRelative } from '../utils/sourcemaps.js'; import * as svelte from 'svelte/compiler'; import { log } from '../utils/log.js'; import { arraify } from '../utils/options.js'; +import fs from 'node:fs'; +import path from 'node:path'; /** * @param {import('../types/plugin-api.d.ts').PluginAPI} api @@ -14,10 +16,16 @@ export function preprocess(api) { */ let options; + /** + * @type {DependenciesCache} + */ + let dependenciesCache; + /** * @type {import("../types/compile.d.ts").PreprocessSvelte} */ let preprocessSvelte; + /** @type {import('vite').Plugin} */ const plugin = { name: 'vite-plugin-svelte:preprocess', @@ -37,19 +45,39 @@ export function preprocess(api) { delete plugin.transform; } }, - + configureServer(server) { + dependenciesCache = new DependenciesCache(server); + }, + buildStart() { + dependenciesCache?.clear(); + }, transform: { async handler(code, id) { - const cache = api.getEnvironmentCache(this); const ssr = this.environment.config.consumer === 'server'; const svelteRequest = api.idParser(id, ssr); if (!svelteRequest) { return; } try { - return await preprocessSvelte(svelteRequest, code, options); + const preprocessed = await preprocessSvelte(svelteRequest, code, options); + dependenciesCache?.update(svelteRequest, preprocessed?.dependencies ?? []); + if (!preprocessed) { + return; + } + if (options.isBuild && this.environment.config.build.watch && preprocessed.dependencies) { + for (const dep of preprocessed.dependencies) { + this.addWatchFile(dep); + } + } + + /** @type {import('vite').Rollup.SourceDescription}*/ + const result = { code: preprocessed.code }; + if (preprocessed.map) { + // @ts-expect-error type differs but should work + result.map = preprocessed.map; + } + return result; } catch (e) { - cache.setError(svelteRequest, e); throw toRollupError(e, options); } } @@ -63,8 +91,6 @@ export function preprocess(api) { * @returns {import('../types/compile.d.ts').PreprocessSvelte} */ function createPreprocessSvelte(options, resolvedConfig) { - /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ - let stats; /** @type {Array} */ const preprocessors = arraify(options.preprocess); @@ -75,59 +101,105 @@ function createPreprocessSvelte(options, resolvedConfig) { } /** @type {import('../types/compile.d.ts').PreprocessSvelte} */ - return async function preprocessSvelte(svelteRequest, code, options) { - const { filename, ssr } = svelteRequest; - - if (options.stats) { - if (options.isBuild) { - if (!stats) { - // build is either completely ssr or csr, create stats collector on first compile - // it is then finished in the buildEnd hook. - stats = options.stats.startCollection(`${ssr ? 'ssr' : 'dom'} preprocess`, { - logInProgress: () => false - }); - } - } else { - // dev time ssr, it's a ssr request and there are no stats, assume new page load and start collecting - if (ssr && !stats) { - stats = options.stats.startCollection('ssr preprocess'); - } - // stats are being collected but this isn't an ssr request, assume page loaded and stop collecting - if (!ssr && stats) { - stats.finish(); - stats = undefined; - } - // TODO find a way to trace dom compile during dev - // problem: we need to call finish at some point but have no way to tell if page load finished - // also they for hmr updates too - } - } - + return async function preprocessSvelte(svelteRequest, code) { + const { filename } = svelteRequest; let preprocessed; - if (preprocessors && preprocessors.length > 0) { try { - const endStat = stats?.start(filename); preprocessed = await svelte.preprocess(code, preprocessors, { filename }); // full filename here so postcss works - endStat?.(); } catch (e) { e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`; throw e; } - if (typeof preprocessed?.map === 'object') { mapToRelative(preprocessed?.map, filename); } - return /** @type {import('../types/compile.d.ts').PreprocessTransformOutput} */ { - code: preprocessed.code, - // @ts-expect-error - map: preprocessed.map, - meta: { - svelte: { - preprocessed - } - } - }; + return preprocessed; } }; } + +/** + * @class + * + * caches dependencies of preprocessed files and emit change events on dependants + */ +class DependenciesCache { + /** @type {Map} */ + #dependencies = new Map(); + /** @type {Map>} */ + #dependants = new Map(); + + /** @type {import('vite').ViteDevServer} */ + #server; + /** + * + * @param {import('vite').ViteDevServer} server + */ + constructor(server) { + this.#server = server; + /** @type {(filename: string) => void} */ + const emitChangeEventOnDependants = (filename) => { + const dependants = this.#dependants.get(filename); + dependants?.forEach((dependant) => { + if (fs.existsSync(dependant)) { + log.debug( + `emitting virtual change event for "${dependant}" because dependency "${filename}" changed`, + undefined, + 'hmr' + ); + server.watcher.emit('change', dependant); + } + }); + }; + server.watcher.on('change', emitChangeEventOnDependants); + server.watcher.on('unlink', emitChangeEventOnDependants); + } + + /** + * @param {string} file + */ + #ensureWatchedFile(file) { + const root = this.#server.config.root; + if ( + file && + // only need to watch if out of root + !file.startsWith(root + '/') && + // some rollup plugins use null bytes for private resolved Ids + !file.includes('\0') && + fs.existsSync(file) + ) { + // resolve file to normalized system path + this.#server.watcher.add(path.resolve(file)); + } + } + + clear() { + this.#dependencies.clear(); + this.#dependants.clear(); + } + + /** + * + * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest + * @param {string[]} dependencies + */ + update(svelteRequest, dependencies) { + const id = svelteRequest.normalizedFilename; + const prevDependencies = this.#dependencies.get(id) || []; + + this.#dependencies.set(id, dependencies); + const removed = prevDependencies.filter((d) => !dependencies.includes(d)); + const added = dependencies.filter((d) => !prevDependencies.includes(d)); + added.forEach((d) => { + this.#ensureWatchedFile(d); + if (!this.#dependants.has(d)) { + this.#dependants.set(d, new Set()); + } + /** @type {Set} */ (this.#dependants.get(d)).add(svelteRequest.filename); + }); + removed.forEach((d) => { + /** @type {Set} */ (this.#dependants.get(d)).delete(svelteRequest.filename); + }); + } +} diff --git a/packages/vite-plugin-svelte/src/types/compile.d.ts b/packages/vite-plugin-svelte/src/types/compile.d.ts index 603446376..422a979b6 100644 --- a/packages/vite-plugin-svelte/src/types/compile.d.ts +++ b/packages/vite-plugin-svelte/src/types/compile.d.ts @@ -7,24 +7,14 @@ export type CompileSvelte = ( svelteRequest: SvelteRequest, code: string, options: Partial, - preprocessed?: Processed + sourcemap?: Rollup.SourceMap ) => Promise; export type PreprocessSvelte = ( svelteRequest: SvelteRequest, code: string, options: Partial -) => Promise; - -export interface PreprocessTransformOutput { - code: string; - map: Rollup.SourceMapInput; - meta: { - svelte: { - preprocessed: Processed; - }; - }; -} +) => Promise; export interface Code { code: string; @@ -44,6 +34,4 @@ export interface CompileData { lang: string; compiled: CompileResult; ssr: boolean | undefined; - dependencies: string[]; - preprocessed: Processed; } diff --git a/packages/vite-plugin-svelte/src/types/plugin-api.d.ts b/packages/vite-plugin-svelte/src/types/plugin-api.d.ts index bb89bc942..66e20105d 100644 --- a/packages/vite-plugin-svelte/src/types/plugin-api.d.ts +++ b/packages/vite-plugin-svelte/src/types/plugin-api.d.ts @@ -1,17 +1,9 @@ import type { ResolvedOptions } from './options.d.ts'; import type { IdFilter, IdParser } from './id.d.ts'; import type { CompileSvelte } from './compile.d.ts'; -import type { Environment } from 'vite'; -// eslint-disable-next-line n/no-missing-import -import { VitePluginSvelteCache } from '../utils/vite-plugin-svelte-cache.js'; - -interface EnvContext { - environment: Environment; -} export interface PluginAPI { options: ResolvedOptions; - getEnvironmentCache: (arg: EnvContext) => VitePluginSvelteCache; idFilter: IdFilter; idParser: IdParser; compileSvelte: CompileSvelte; diff --git a/packages/vite-plugin-svelte/src/utils/compile.js b/packages/vite-plugin-svelte/src/utils/compile.js index 43d55ec35..29c11d052 100644 --- a/packages/vite-plugin-svelte/src/utils/compile.js +++ b/packages/vite-plugin-svelte/src/utils/compile.js @@ -2,7 +2,6 @@ import * as svelte from 'svelte/compiler'; import { safeBase64Hash } from './hash.js'; import { log } from './log.js'; -import { checkPreprocessDependencies } from './preprocess.js'; import { mapToRelative } from './sourcemaps.js'; import { enhanceCompileError } from './error.js'; @@ -19,11 +18,9 @@ export function createCompileSvelte() { /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let stats; /** @type {import('../types/compile.d.ts').CompileSvelte} */ - return async function compileSvelte(svelteRequest, code, options, preprocessed) { + return async function compileSvelte(svelteRequest, code, options, sourcemap) { const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest; const { emitCss = true } = options; - /** @type {string[]} */ - const dependencies = []; /** @type {import('svelte/compiler').Warning[]} */ const warnings = []; @@ -59,20 +56,6 @@ export function createCompileSvelte() { generate: ssr ? 'server' : 'client' }; - if (preprocessed) { - if (preprocessed.dependencies?.length) { - const checked = checkPreprocessDependencies(filename, preprocessed.dependencies); - if (checked.warnings.length) { - warnings.push(...checked.warnings); - } - if (checked.dependencies.length) { - dependencies.push(...checked.dependencies); - } - } - - if (preprocessed.map) compileOptions.sourcemap = preprocessed.map; - } - let finalCode = code; if (compileOptions.hmr && options.emitCss) { const hash = `s-${safeBase64Hash(normalizedFilename)}`; @@ -103,6 +86,9 @@ export function createCompileSvelte() { ...dynamicCompileOptions } : compileOptions; + if (sourcemap) { + finalCompileOptions.sourcemap = sourcemap; + } const endStat = stats?.start(filename); /** @type {import('svelte/compiler').CompileResult} */ let compiled; @@ -160,9 +146,7 @@ export function createCompileSvelte() { cssId, lang, compiled, - ssr, - dependencies, - preprocessed: preprocessed ?? { code } + ssr }; }; } diff --git a/packages/vite-plugin-svelte/src/utils/preprocess.js b/packages/vite-plugin-svelte/src/utils/preprocess.js index 8ce174505..3832ec85a 100644 --- a/packages/vite-plugin-svelte/src/utils/preprocess.js +++ b/packages/vite-plugin-svelte/src/utils/preprocess.js @@ -1,5 +1,4 @@ import { log } from './log.js'; -import { normalizePath } from 'vite'; /** * @param {import('../types/options.d.ts').ResolvedOptions} options @@ -80,52 +79,3 @@ export function addExtraPreprocessors(options, config) { } } } - -/** - * - * @param filename {string} - * @param dependencies {string[]} - * @returns {({dependencies: string[], warnings:import('svelte/compiler').Warning[] })} - */ -export function checkPreprocessDependencies(filename, dependencies) { - /** @type {import('svelte/compiler').Warning[]} */ - const warnings = []; - - // to find self, we have to compare normalized filenames, but must keep the original values in `dependencies` - // because otherwise file watching on windows doesn't work - // so we track idx and filter by that in the end - /** @type {number[]} */ - const selfIdx = []; - const normalizedFullFilename = normalizePath(filename); - const normalizedDeps = dependencies.map(normalizePath); - for (let i = 0; i < normalizedDeps.length; i++) { - if (normalizedDeps[i] === normalizedFullFilename) { - selfIdx.push(i); - } - } - const hasSelfDependency = selfIdx.length > 0; - if (hasSelfDependency) { - warnings.push({ - code: 'vite-plugin-svelte-preprocess-depends-on-self', - message: - 'svelte.preprocess returned this file as a dependency of itself. This can be caused by an invalid configuration or importing generated code that depends on .svelte files (eg. tailwind base css)', - filename - }); - } - - if (dependencies.length > 10) { - warnings.push({ - code: 'vite-plugin-svelte-preprocess-many-dependencies', - message: `svelte.preprocess depends on more than 10 external files which can cause slow builds and poor DX, try to reduce them. Found: ${dependencies.join( - ', ' - )}`, - filename - }); - } - return { - dependencies: hasSelfDependency - ? dependencies.filter((_, i) => !selfIdx.includes(i)) // remove self dependency - : dependencies, - warnings - }; -} diff --git a/packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-cache.js b/packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-cache.js deleted file mode 100644 index 1a6580c31..000000000 --- a/packages/vite-plugin-svelte/src/utils/vite-plugin-svelte-cache.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @class - */ -export class VitePluginSvelteCache { - /** @type {Map} */ - #css = new Map(); - /** @type {Map} */ - #js = new Map(); - /** @type {Map} */ - #dependencies = new Map(); - /** @type {Map>} */ - #dependants = new Map(); - /** @type {Map} */ - #errors = new Map(); - - /** - * @param {import('../types/compile.d.ts').CompileData} compileData - */ - update(compileData) { - this.#errors.delete(compileData.normalizedFilename); - this.#updateCSS(compileData); - this.#updateJS(compileData); - this.#updateDependencies(compileData); - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @returns {boolean} - */ - has(svelteRequest) { - const id = svelteRequest.normalizedFilename; - return this.#errors.has(id) || this.#js.has(id) || this.#css.has(id); - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @param {any} error - */ - setError(svelteRequest, error) { - // keep dependency info, otherwise errors in dependants would not trigger an update after fixing - // because they are no longer watched - this.remove(svelteRequest, true); - this.#errors.set(svelteRequest.normalizedFilename, error); - } - - /** - * @param {import('../types/compile.d.ts').CompileData} compileData - */ - #updateCSS(compileData) { - this.#css.set(compileData.normalizedFilename, compileData.compiled.css); - } - - /** - * @param {import('../types/compile.d.ts').CompileData} compileData - */ - #updateJS(compileData) { - if (!compileData.ssr) { - // do not cache SSR js - this.#js.set(compileData.normalizedFilename, compileData.compiled.js); - } - } - - /** - * @param {import('../types/compile.d.ts').CompileData} compileData - */ - #updateDependencies(compileData) { - const id = compileData.normalizedFilename; - const prevDependencies = this.#dependencies.get(id) || []; - const dependencies = compileData.dependencies; - this.#dependencies.set(id, dependencies); - const removed = prevDependencies.filter((d) => !dependencies.includes(d)); - const added = dependencies.filter((d) => !prevDependencies.includes(d)); - added.forEach((d) => { - if (!this.#dependants.has(d)) { - this.#dependants.set(d, new Set()); - } - /** @type {Set} */ (this.#dependants.get(d)).add(compileData.filename); - }); - removed.forEach((d) => { - /** @type {Set} */ (this.#dependants.get(d)).delete(compileData.filename); - }); - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @param {boolean} [keepDependencies] - * @returns {boolean} - */ - remove(svelteRequest, keepDependencies = false) { - const id = svelteRequest.normalizedFilename; - let removed = false; - if (this.#errors.delete(id)) { - removed = true; - } - if (this.#js.delete(id)) { - removed = true; - } - if (this.#css.delete(id)) { - removed = true; - } - if (!keepDependencies) { - const dependencies = this.#dependencies.get(id); - if (dependencies) { - removed = true; - dependencies.forEach((d) => { - const dependants = this.#dependants.get(d); - if (dependants && dependants.has(svelteRequest.filename)) { - dependants.delete(svelteRequest.filename); - } - }); - this.#dependencies.delete(id); - } - } - - return removed; - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @returns {import('../types/compile.d.ts').Code | undefined | null} - */ - getCSS(svelteRequest) { - return this.#css.get(svelteRequest.normalizedFilename); - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @returns {import('../types/compile.d.ts').Code | undefined | null} - */ - getJS(svelteRequest) { - if (!svelteRequest.ssr) { - // SSR js isn't cached - return this.#js.get(svelteRequest.normalizedFilename); - } - } - - /** - * @param {import('../types/id.d.ts').SvelteRequest} svelteRequest - * @returns {any} - */ - getError(svelteRequest) { - return this.#errors.get(svelteRequest.normalizedFilename); - } - - /** - * @param {string} path - * @returns {string[]} - */ - getDependants(path) { - const dependants = this.#dependants.get(path); - return dependants ? [...dependants] : []; - } -} diff --git a/packages/vite-plugin-svelte/src/utils/watch.js b/packages/vite-plugin-svelte/src/utils/watch.js index 5ed90bc29..d67785960 100644 --- a/packages/vite-plugin-svelte/src/utils/watch.js +++ b/packages/vite-plugin-svelte/src/utils/watch.js @@ -5,30 +5,15 @@ import path from 'node:path'; /** * @param {import('../types/options.d.ts').ResolvedOptions} options - * @param {import('./vite-plugin-svelte-cache.js').VitePluginSvelteCache} cache * @returns {void} */ -export function setupWatchers(options, cache) { +export function setupWatchers(options) { const { server, configFile: svelteConfigFile } = options; if (!server) { return; } const { watcher, ws } = server; const { root, server: serverConfig } = server.config; - /** @type {(filename: string) => void} */ - const emitChangeEventOnDependants = (filename) => { - const dependants = cache.getDependants(filename); - dependants.forEach((dependant) => { - if (fs.existsSync(dependant)) { - log.debug( - `emitting virtual change event for "${dependant}" because dependency "${filename}" changed`, - undefined, - 'hmr' - ); - watcher.emit('change', dependant); - } - }); - }; /** @type {(filename: string) => void} */ const triggerViteRestart = (filename) => { @@ -52,8 +37,8 @@ export function setupWatchers(options, cache) { /** @type {Record} */ const listenerCollection = { add: [], - change: [emitChangeEventOnDependants], - unlink: [emitChangeEventOnDependants] + change: [], + unlink: [] }; if (svelteConfigFile !== false) {