diff --git a/.changeset/nice-garlics-juggle.md b/.changeset/nice-garlics-juggle.md new file mode 100644 index 0000000000..34e9089d09 --- /dev/null +++ b/.changeset/nice-garlics-juggle.md @@ -0,0 +1,5 @@ +--- +'@ryanatkn/gro': minor +--- + +extract `moss_helpers.ts` and lazy load the Moss plugin diff --git a/package-lock.json b/package-lock.json index f365692776..e4ef47f0fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,17 +29,17 @@ "@changesets/types": "^6.0.0", "@ryanatkn/eslint-config": "^0.5.6", "@ryanatkn/fuz": "^0.130.3", - "@ryanatkn/moss": "^0.19.0", + "@ryanatkn/moss": "^0.20.0", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.7.3", "@sveltejs/package": "^2.3.7", "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/fs-extra": "^11.0.4", - "@types/node": "^22.8.4", + "@types/node": "^22.8.5", "esbuild": "^0.21.5", "eslint": "^9.13.0", "eslint-plugin-svelte": "^2.46.0", - "svelte": "^5.1.5", + "svelte": "^5.1.6", "svelte-check": "^4.0.5", "typescript": "^5.6.3", "typescript-eslint": "^8.12.2", @@ -467,9 +467,9 @@ } }, "node_modules/@ryanatkn/moss": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@ryanatkn/moss/-/moss-0.19.0.tgz", - "integrity": "sha512-abpVJ4j25TlWKA7yYgMWj+4p6+4syja6/Yj13R0NK+4L7RSRLKaxPFX+Q08tVj7nA/1YUIfScpcllzrnHL82bg==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@ryanatkn/moss/-/moss-0.20.0.tgz", + "integrity": "sha512-qQzgIRXi6V8v0crwB5qvhbUGRKv4flcdneujckymY4jDpZLq4AVHGP5d5CDr3Pe3suXHpwI4EgC/b5f+E05Tdg==", "dev": true, "license": "MIT", "engines": { @@ -662,9 +662,9 @@ } }, "node_modules/@types/node": { - "version": "22.8.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz", - "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==", + "version": "22.8.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.5.tgz", + "integrity": "sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2537,9 +2537,9 @@ } }, "node_modules/svelte": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.5.tgz", - "integrity": "sha512-AyYondx6wS0g8mmBMfwJVnOYYBswjBv6L4bc99awfbET2KozWvVwxe8NSN7fhx7Pgr7pOfOXIv7K8+Impc0OoQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.6.tgz", + "integrity": "sha512-bYS/DpkqXk0j5UZgiNXrEjZYPRZ4Ncd87w4KUSbcZGyojA0+i/Ls9OGUjETHmdLe8RcQ0G8SX/T0PypPpAA/ew==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", diff --git a/package.json b/package.json index 9e4f675d8c..719030bd4e 100644 --- a/package.json +++ b/package.json @@ -70,17 +70,17 @@ "@changesets/types": "^6.0.0", "@ryanatkn/eslint-config": "^0.5.6", "@ryanatkn/fuz": "^0.130.3", - "@ryanatkn/moss": "^0.19.0", + "@ryanatkn/moss": "^0.20.0", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.7.3", "@sveltejs/package": "^2.3.7", "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/fs-extra": "^11.0.4", - "@types/node": "^22.8.4", + "@types/node": "^22.8.5", "esbuild": "^0.21.5", "eslint": "^9.13.0", "eslint-plugin-svelte": "^2.46.0", - "svelte": "^5.1.5", + "svelte": "^5.1.6", "svelte-check": "^4.0.5", "typescript": "^5.6.3", "typescript-eslint": "^8.12.2", diff --git a/src/lib/gro.config.default.ts b/src/lib/gro.config.default.ts index 1624cf4500..989d6a7045 100644 --- a/src/lib/gro.config.default.ts +++ b/src/lib/gro.config.default.ts @@ -4,8 +4,7 @@ import {has_server, gro_plugin_server} from './gro_plugin_server.js'; import {gro_plugin_sveltekit_app} from './gro_plugin_sveltekit_app.js'; import {has_sveltekit_app, has_sveltekit_library} from './sveltekit_helpers.js'; import {gro_plugin_gen} from './gro_plugin_gen.js'; -import {gro_plugin_moss, has_moss_dep} from './gro_plugin_moss.js'; -import {load_package_json} from './package_json.js'; +import {has_dep, load_package_json} from './package_json.js'; /** * This is the default config that's passed to `gro.config.ts` @@ -19,22 +18,18 @@ import {load_package_json} from './package_json.js'; const config: Create_Gro_Config = async (cfg) => { const package_json = load_package_json(); // TODO gets wastefully loaded by some plugins, maybe put in plugin/task context? how does that interact with `map_package_json`? - const [ - moss_plugin_result, - has_server_result, - has_sveltekit_library_result, - has_sveltekit_app_result, - ] = await Promise.all([ - has_moss_dep(package_json), - has_server(), - has_sveltekit_library(package_json), - has_sveltekit_app(), - ]); + const [has_moss_dep, has_server_result, has_sveltekit_library_result, has_sveltekit_app_result] = + await Promise.all([ + has_dep('@ryanatkn/moss', package_json), + has_server(), + has_sveltekit_library(package_json), + has_sveltekit_app(), + ]); - cfg.plugins = () => + cfg.plugins = async () => [ // put things that generate files before SvelteKit so it can see them - moss_plugin_result.ok ? gro_plugin_moss() : null, + has_moss_dep ? (await import('./gro_plugin_moss.js')).gro_plugin_moss() : null, // lazy load to avoid errors if it's not installed gro_plugin_gen(), has_server_result.ok ? gro_plugin_server() : null, has_sveltekit_library_result.ok ? gro_plugin_sveltekit_library() : null, diff --git a/src/lib/gro_plugin_moss.ts b/src/lib/gro_plugin_moss.ts index e367ef7118..63267010f1 100644 --- a/src/lib/gro_plugin_moss.ts +++ b/src/lib/gro_plugin_moss.ts @@ -2,53 +2,18 @@ import {EMPTY_OBJECT} from '@ryanatkn/belt/object.js'; import {throttle} from '@ryanatkn/belt/throttle.js'; import {Unreachable_Error} from '@ryanatkn/belt/error.js'; import {writeFileSync} from 'node:fs'; -import {collect_css_classes, Css_Classes} from '@ryanatkn/moss/css_class_helpers.js'; +import { + collect_css_classes, + Css_Classes, + generate_classes_css, +} from '@ryanatkn/moss/css_class_helpers.js'; import {css_classes_by_name} from '@ryanatkn/moss/css_classes.js'; -import type {Result} from '@ryanatkn/belt/result.js'; import type {Plugin} from './plugin.js'; import type {Args} from './args.js'; import type {Cleanup_Watch} from './filer.js'; import {format_file} from './format_file.js'; import type {File_Filter} from './path.js'; -import {has_dep, type Package_Json} from './package_json.js'; - -export const MOSS_PACKAGE_DEP_NAME = '@ryanatkn/moss'; - -export const has_moss_dep = ( - package_json: Package_Json, - dep_name = MOSS_PACKAGE_DEP_NAME, -): Result => { - if (!has_dep(dep_name, package_json)) { - return { - ok: false, - message: `no dependency found in package.json for ${dep_name}`, - }; - } - - return {ok: true}; -}; - -export const generate_classes_css = (classes: Iterable): string => { - let css = ''; - for (const c of classes) { - const v = css_classes_by_name[c]; - if (!v) { - // diagnostic - // if (!/^[a-z_0-9]+$/.test(c)) { - // console.error('invalid class detected, fix the regexps', c); - // } - continue; - } - if ('declaration' in v) { - css += `.${c} { ${v.declaration} }\n`; - } else { - css += v.ruleset + '\n'; - } - } - - return css; -}; const FLUSH_DEBOUNCE_DELAY = 500; @@ -56,7 +21,7 @@ export interface Task_Args extends Args { watch?: boolean; } -export interface Options { +export interface Gro_Plugin_Moss_Options { include_classes?: string[] | Set | null; outfile?: string; filter_file?: File_Filter | null; @@ -70,7 +35,7 @@ export const gro_plugin_moss = ({ filter_file = (p) => !p.includes('.test.') && !p.includes('/test/'), flush_debounce_delay = FLUSH_DEBOUNCE_DELAY, banner = 'generated by gro_plugin_moss', -}: Options = EMPTY_OBJECT): Plugin => { +}: Gro_Plugin_Moss_Options = EMPTY_OBJECT): Plugin => { const css_classes = new Css_Classes( Array.isArray(include_classes) ? new Set(include_classes) : include_classes, ); @@ -88,7 +53,7 @@ export const gro_plugin_moss = ({ }; const flush_gen_queue = throttle( async () => { - const css = generate_classes_css(css_classes.get_sorted_array()); + const css = generate_classes_css(css_classes.get_sorted_array(), css_classes_by_name); const contents = `/* ${banner} */\n\n${css}\n\n/* ${banner} */`; const output = await format_file(contents, {filepath: outfile}); // TODO think about using gen to implement this, would have some nice benefits like automatic change detection diff --git a/src/lib/package.ts b/src/lib/package.ts index d41e92e2cc..fa61a476c0 100644 --- a/src/lib/package.ts +++ b/src/lib/package.ts @@ -62,17 +62,17 @@ export const package_json = { '@changesets/types': '^6.0.0', '@ryanatkn/eslint-config': '^0.5.6', '@ryanatkn/fuz': '^0.130.3', - '@ryanatkn/moss': '^0.19.0', + '@ryanatkn/moss': '^0.20.0', '@sveltejs/adapter-static': '^3.0.6', '@sveltejs/kit': '^2.7.3', '@sveltejs/package': '^2.3.7', '@sveltejs/vite-plugin-svelte': '^4.0.0', '@types/fs-extra': '^11.0.4', - '@types/node': '^22.8.4', + '@types/node': '^22.8.5', esbuild: '^0.21.5', eslint: '^9.13.0', 'eslint-plugin-svelte': '^2.46.0', - svelte: '^5.1.5', + svelte: '^5.1.6', 'svelte-check': '^4.0.5', typescript: '^5.6.3', 'typescript-eslint': '^8.12.2', @@ -605,11 +605,8 @@ export const src_json = { './gro_plugin_moss.js': { path: 'gro_plugin_moss.ts', declarations: [ - {name: 'MOSS_PACKAGE_DEP_NAME', kind: 'variable'}, - {name: 'has_moss_dep', kind: 'function'}, - {name: 'generate_classes_css', kind: 'function'}, {name: 'Task_Args', kind: 'type'}, - {name: 'Options', kind: 'type'}, + {name: 'Gro_Plugin_Moss_Options', kind: 'type'}, {name: 'gro_plugin_moss', kind: 'function'}, ], }, diff --git a/src/lib/resolve_node_specifier.ts b/src/lib/resolve_node_specifier.ts index c12d5069f9..4be611cd30 100644 --- a/src/lib/resolve_node_specifier.ts +++ b/src/lib/resolve_node_specifier.ts @@ -1,17 +1,17 @@ import {join, extname} from 'node:path'; import {existsSync} from 'node:fs'; import {DEV} from 'esm-env'; +import {escape_regexp} from '@ryanatkn/belt/regexp.js'; import {Export_Value, Package_Json, load_package_json} from './package_json.js'; import {paths} from './paths.js'; import {NODE_MODULES_DIRNAME} from './constants.js'; import type {Resolved_Specifier} from './resolve_specifier.js'; -import {escape_regexp} from '@ryanatkn/belt/regexp.js'; /** - * This likely has differences from Node - they should be fixed on a case-by-case basis. * Ideally Gro would just use `import.meta.resolve`, but it can't be used in custom loaders, * which Gro relies on for TypeScript. + * This likely has differences from Node - they should be fixed on a case-by-case basis. */ export const resolve_node_specifier = ( specifier: string, @@ -24,23 +24,16 @@ export const resolve_node_specifier = ( const raw = specifier.endsWith('?raw'); const mapped_specifier = raw ? specifier.substring(0, specifier.length - 4) : specifier; - // Parse the specifier - let idx: number = -1; - if (mapped_specifier[0] === '@') { - let count = 0; - for (let i = 0; i < mapped_specifier.length; i++) { - if (mapped_specifier[i] === '/') count++; - if (count === 2) { - idx = i; - break; - } - } - } else { - idx = mapped_specifier.indexOf('/'); - } + const specifier_slash_path_index = get_specifier_slash_path_index(mapped_specifier); - const pkg_name = idx === -1 ? mapped_specifier : mapped_specifier.substring(0, idx); - const module_path = idx === -1 ? '' : mapped_specifier.substring(idx + 1); + const pkg_name = + specifier_slash_path_index === -1 + ? mapped_specifier + : mapped_specifier.substring(0, specifier_slash_path_index); + const module_path = + specifier_slash_path_index === -1 + ? '' + : mapped_specifier.substring(specifier_slash_path_index + 1); const subpath = module_path ? './' + module_path : '.'; const package_dir = join(dir, NODE_MODULES_DIRNAME, pkg_name); @@ -146,6 +139,7 @@ const resolve_subpath = (package_json: Package_Json, subpath: string): unknown = return exports[subpath]; } + // TODO some of this may be wrong, will just need to patch as we go // Sort patterns by specificity const patterns = Object.entries(exports) .filter(([pattern]) => pattern.includes('*')) @@ -257,8 +251,7 @@ const resolve_exported_value = ( const exported_obj = exported as Record; - // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents - let default_value: Export_Value | undefined; + let default_value: Export_Value | undefined; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents // For each key in exported_obj, in order for (const [condition, value] of Object.entries(exported_obj)) { @@ -356,3 +349,21 @@ const validate_export_target = (target: string, throw_on_missing_package: boolea } } }; + +const get_specifier_slash_path_index = (mapped_specifier: string): number => { + let index: number = -1; + if (mapped_specifier[0] === '@') { + let count = 0; + for (let i = 0; i < mapped_specifier.length; i++) { + if (mapped_specifier[i] === '/') count++; + if (count === 2) { + index = i; + break; + } + } + } else { + index = mapped_specifier.indexOf('/'); + } + + return index; +};