UNPKG

@sveltejs/package

Version:

The fastest way to build Svelte packages

204 lines (182 loc) 6.33 kB
import colors from 'kleur'; import { load_pkg_json } from './config.js'; /** * @param {import("./types.js").Options} options */ export function create_validator(options) { const { analyse_code, validate } = _create_validator(options); return { /** * Checks a file content for problematic imports and things like `import.meta` * @param {string} name * @param {string} content */ analyse_code(name, content) { analyse_code(name, content); }, validate() { /** @type {Record<string, any>} */ const pkg = load_pkg_json(options.cwd); const warnings = validate(pkg); if (Object.keys(pkg).length === 0) { warnings.push( 'No package.json found in the current directory. Please create one or run this command in a directory containing one.' ); } // Just warnings, not errors, because // - would be annoying in watch mode (would have to restart the server) // - maybe there's a custom post-build script that fixes some of these if (warnings.length) { console.log( colors .bold() .yellow('@sveltejs/package found the following issues while packaging your library:') ); for (const warning of warnings) { console.log(colors.yellow(`${warning}\n`)); } } } }; } /** * @param {import("./types.js").Options} options */ export function _create_validator(options) { /** @type {Set<string>} */ const imports = new Set(); let uses_import_meta = false; let has_svelte_files = false; /** * Checks a file content for problematic imports and things like `import.meta` * @param {string} name * @param {string} content */ function analyse_code(name, content) { has_svelte_files = has_svelte_files || (options.config.extensions ?? ['.svelte']).some((ext) => name.endsWith(ext)); uses_import_meta = uses_import_meta || content.includes('import.meta.env'); const file_imports = [ ...content.matchAll(/from\s+('|")([^"';,]+?)\1/g), ...content.matchAll(/import\s*\(\s*('|")([^"';,]+?)\1\s*\)/g) ]; for (const [, , import_path] of file_imports) { if (import_path.startsWith('$app/')) { imports.add(import_path); } } } /** * @param {Record<string, any>} pkg */ function validate(pkg) { /** @type {string[]} */ const warnings = []; if ( imports.has('$app/environment') && [...imports].filter((i) => i.startsWith('$app/')).length === 1 ) { warnings.push( 'Avoid usage of `$app/environment` in your code, if you want the library to work for people not using SvelteKit (only regular Svelte, for example). ' + 'Consider using packages like `esm-env` instead which provide cross-bundler-compatible environment variables.' ); } if (uses_import_meta) { warnings.push( 'Avoid usage of `import.meta.env` in your code. It only works in apps bundled with Vite. ' + 'Consider using packages like `esm-env` instead which works with all bundlers or without bundling.' ); } if ( !(pkg.dependencies?.['@sveltejs/kit'] || pkg.peerDependencies?.['@sveltejs/kit']) && ([...imports].some((i) => i.startsWith('$app/')) || imports.has('@sveltejs/kit')) ) { warnings.push( 'You are using SvelteKit-specific imports in your code, but you have not declared a dependency on `@sveltejs/kit` in your `package.json`. ' + 'Add it to your `dependencies` or `peerDependencies`.' ); } if ( !(pkg.dependencies?.svelte || pkg.peerDependencies?.svelte) && (has_svelte_files || [...imports].some((i) => i.startsWith('svelte/') || imports.has('svelte'))) ) { warnings.push( 'You are using Svelte components or Svelte-specific imports in your code, but you have not declared a dependency on `svelte` in your `package.json`. ' + 'Add it to your `dependencies` or `peerDependencies`.' ); } if (pkg.exports) { const { conditions } = traverse_exports(pkg.exports); if (has_svelte_files && !pkg.svelte && !conditions.has('svelte')) { warnings.push( 'You are using Svelte files, but did not declare a `svelte` condition in one of your `exports` in your `package.json`. ' + 'Add a `svelte` condition to your `exports` to ensure that your package is recognized as Svelte package by tooling. ' + 'See https://svelte.dev/docs/kit/packaging#anatomy-of-a-package-json-exports for more info' ); } if (pkg.svelte) { const root_export = pkg.exports['.']; if (!root_export) { warnings.push( 'You have a `svelte` field in your `package.json`, but no root export in your `exports`. Please align them so that bundlers will resolve consistently to the same file.' ); } else { const { exports } = traverse_exports({ '.': root_export }); if (![...exports].map(export_to_regex).some((_export) => _export.test(pkg.svelte))) { warnings.push( 'The `svelte` field in your `package.json` does not match any export in your root `exports`. Please align them so that bundlers will resolve consistently to the same file.' ); Object.keys(pkg.exports).map(export_to_regex); } } } } else { warnings.push( 'No `exports` field found in `package.json`, please provide one. ' + 'See https://svelte.dev/docs/kit/packaging#anatomy-of-a-package-json-exports for more info' ); } return warnings; } return { analyse_code, validate }; } /** * @param {Record<string, any>} exports_map * @returns {{ exports: Set<string>; conditions: Set<string> }} */ function traverse_exports(exports_map) { /** @type {Set<string>} */ const exports = new Set(); /** @type {Set<string>} */ const conditions = new Set(); /** * @param {Record<string, any>} exports_map * @param {boolean} is_first_level */ function traverse(exports_map, is_first_level) { for (const key of Object.keys(exports_map ?? {})) { if (!is_first_level) { conditions.add(key); } const _export = exports_map[key]; if (typeof _export === 'string') { exports.add(_export); } else { traverse(_export, false); } } } traverse(exports_map, true); return { exports, conditions }; } /** @param {string} _export */ function export_to_regex(_export) { // $& means the whole matched string const regex_str = _export.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'); return new RegExp(`^${regex_str}$`); }