diff --git a/.changeset/publish-types-layout.md b/.changeset/publish-types-layout.md new file mode 100644 index 000000000..139e4348f --- /dev/null +++ b/.changeset/publish-types-layout.md @@ -0,0 +1,31 @@ +--- +"@tko/bind": patch +"@tko/binding.component": patch +"@tko/binding.core": patch +"@tko/binding.foreach": patch +"@tko/binding.if": patch +"@tko/binding.template": patch +"@tko/builder": patch +"@tko/build.knockout": patch +"@tko/build.reference": patch +"@tko/computed": patch +"@tko/filter.punches": patch +"@tko/lifecycle": patch +"@tko/observable": patch +"@tko/provider": patch +"@tko/provider.attr": patch +"@tko/provider.bindingstring": patch +"@tko/provider.component": patch +"@tko/provider.databind": patch +"@tko/provider.multi": patch +"@tko/provider.mustache": patch +"@tko/provider.native": patch +"@tko/provider.virtual": patch +"@tko/utils": patch +"@tko/utils.component": patch +"@tko/utils.functionrewrite": patch +"@tko/utils.jsx": patch +"@tko/utils.parser": patch +--- + +Publish generated `types/` folders for all public packages and both bundled builds, and advertise those declarations through package `types` metadata and `exports` conditions so consumer imports resolve the shipped type surface instead of repo-only sources. diff --git a/.github/workflows/lint-and-typecheck.yml b/.github/workflows/lint-and-typecheck.yml index b3fd0b3eb..798782a11 100644 --- a/.github/workflows/lint-and-typecheck.yml +++ b/.github/workflows/lint-and-typecheck.yml @@ -32,3 +32,6 @@ jobs: - name: Typecheck run: bunx tsc + + - name: Verify types (smoke test .d.ts) + run: bun run verify:types diff --git a/.gitignore b/.gitignore index bd410d818..8a547d8ac 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ COVERAGE.md builds/**/meta .nyc_output **/dist/* +packages/*/types/* +builds/*/types/* !packages/tko/dist/* *-debug.log diff --git a/builds/knockout/package.json b/builds/knockout/package.json index eaaff8984..1d45478e1 100644 --- a/builds/knockout/package.json +++ b/builds/knockout/package.json @@ -26,7 +26,8 @@ ] }, "files": [ - "dist/" + "dist/", + "types/" ], "licenses": [ { @@ -53,6 +54,7 @@ }, "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.mjs" } @@ -71,5 +73,6 @@ "mvvm", "data-binding", "compatible" - ] + ], + "types": "./types/index.d.ts" } diff --git a/builds/knockout/src/index.ts b/builds/knockout/src/index.ts index 81ebf0c67..05b3e3edf 100644 --- a/builds/knockout/src/index.ts +++ b/builds/knockout/src/index.ts @@ -31,6 +31,13 @@ const expressionRewriting = { preProcessBindings: s => dataBindProvider.preProcessBindings(s) } +type KnockoutBuildExtensions = { + version: string + components: typeof components + Component: typeof components.ComponentABC + expressionRewriting: typeof expressionRewriting +} + const provider = new MultiProvider({ providers: [new ComponentProvider(), dataBindProvider, new VirtualProvider(), new AttributeProvider()] }) @@ -47,9 +54,11 @@ const builder = new Builder({ }) const version = BUILD_VERSION -export default builder.create({ +const knockoutBuild: KnockoutBuildExtensions = { version, components, Component: components.ComponentABC, expressionRewriting -}) +} + +export default builder.create(knockoutBuild) diff --git a/builds/reference/package.json b/builds/reference/package.json index a696d4083..33ad19a37 100644 --- a/builds/reference/package.json +++ b/builds/reference/package.json @@ -28,7 +28,8 @@ "@tko/utils": "^4.1.0" }, "files": [ - "dist/" + "dist/", + "types/" ], "homepage": "https://tko.io", "licenses": [ @@ -39,6 +40,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.mjs" } @@ -59,5 +61,6 @@ "mvvm", "tsx", "modern" - ] + ], + "types": "./types/index.d.ts" } diff --git a/builds/reference/src/index.ts b/builds/reference/src/index.ts index e79cb2001..d6b303b44 100644 --- a/builds/reference/src/index.ts +++ b/builds/reference/src/index.ts @@ -17,8 +17,8 @@ import { bindings as componentBindings } from '@tko/binding.component' import { filters } from '@tko/filter.punches' import components from '@tko/utils.component' -import { createElement, Fragment } from '@tko/utils.jsx' -import { JsxObserver } from '@tko/utils.jsx' +import { createElement, Fragment, render } from '@tko/utils.jsx' +import type { JsxRenderResult } from '@tko/utils.jsx' import { options } from '@tko/utils' @@ -27,6 +27,17 @@ declare const BUILD_VERSION: string /** Use === and !== instead of == and != in binding expressions */ options.strictEquality = true +type ReferenceBuildExtensions = { + jsx: { + createElement: typeof createElement + Fragment: typeof Fragment + render(jsx: any): JsxRenderResult + } + components: typeof components + version: string + Component: typeof components.ComponentABC +} + const builder = new Builder({ filters, extenders: {}, @@ -54,23 +65,15 @@ const builder = new Builder({ const version = BUILD_VERSION -export default builder.create({ +const referenceBuild: ReferenceBuildExtensions = { jsx: { createElement, Fragment, - /** Public render function that converts JSX to DOM nodes */ - render(jsx: any) { - const fragment = document.createDocumentFragment() - const observer = new JsxObserver(jsx, fragment) - // Return the first child if single node, or the fragment if multiple - const node = fragment.childNodes.length === 1 ? fragment.firstChild : fragment - return { - node, - dispose: () => observer.dispose() - } - } + render }, components, version, Component: components.ComponentABC -}) +} + +export default builder.create(referenceBuild) diff --git a/package.json b/package.json index f4cdeb0a3..1593b1d18 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,13 @@ "url": "https://github.com/knockout/tko.git" }, "scripts": { - "build": "bun run --filter './packages/*' build && bun run --filter './builds/*' build", + "build": "bun run dts && bun run --filter './packages/*' build && bun run --filter './builds/*' build", "test": "bunx @biomejs/biome ci . && bun run build && bunx vitest run", "test:ff": "bunx @biomejs/biome ci . && bun run build && VITEST_BROWSERS=firefox bunx vitest run", "test:coverage": "bun run build && bunx vitest run --coverage --project browser", "tsc": "bunx tsc", - "dts": "bunx tsc --build tsconfig.dts.json", + "dts": "bun tools/build-dts.ts", + "verify:types": "bunx tsc -p tools/types-smoke/tsconfig.json", "format": "bunx @biomejs/biome format .", "format:fix": "bunx @biomejs/biome format --write .", "lint": "bunx @biomejs/biome lint .", @@ -20,8 +21,8 @@ "check": "bunx @biomejs/biome check .", "knip": "knip", "verify:esm": "bun tools/verify-esm-extensions.ts", - "verify": "bunx @biomejs/biome check . && bunx tsc && bun run build && bun run verify:esm && bunx vitest run", - "clean": "rm -rf packages/*/dist builds/*/dist coverage" + "verify": "bunx @biomejs/biome check . && bunx tsc && bun run build && bun run verify:types && bun run verify:esm && bunx vitest run", + "clean": "rm -rf packages/*/dist packages/*/types builds/*/dist builds/*/types coverage .dts-tmp" }, "bugs": "https://github.com/knockout/tko/issues", "licenses": [ diff --git a/packages/bind/package.json b/packages/bind/package.json index 435decacb..80b3044cf 100644 --- a/packages/bind/package.json +++ b/packages/bind/package.json @@ -12,7 +12,8 @@ "@tko/builder": "^4.1.0" }, "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -33,6 +34,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -47,5 +49,6 @@ "dom", "observable", "data-binding" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/binding.component/package.json b/packages/binding.component/package.json index f94c23c30..610543898 100644 --- a/packages/binding.component/package.json +++ b/packages/binding.component/package.json @@ -14,7 +14,8 @@ "@tko/utils.jsx": "^4.1.0" }, "files": [ - "dist/" + "dist/", + "types/" ], "homepage": "https://tko.io", "licenses": [ @@ -25,6 +26,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -46,5 +48,6 @@ "binding", "web-components", "custom-elements" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/binding.core/package.json b/packages/binding.core/package.json index fef66bb15..ff5a731cf 100644 --- a/packages/binding.core/package.json +++ b/packages/binding.core/package.json @@ -4,7 +4,8 @@ "description": "TKO Core bindings", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -37,11 +38,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/binding.foreach/package.json b/packages/binding.foreach/package.json index ee50c181a..b50da1154 100644 --- a/packages/binding.foreach/package.json +++ b/packages/binding.foreach/package.json @@ -4,7 +4,8 @@ "description": "Knockout Foreach Binding", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -36,11 +37,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/binding.if/package.json b/packages/binding.if/package.json index c40bb1d0d..47ee3d989 100644 --- a/packages/binding.if/package.json +++ b/packages/binding.if/package.json @@ -4,7 +4,8 @@ "description": "TKO conditional (if/ifnot/unless/with/else) bindings", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -36,11 +37,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/binding.template/package.json b/packages/binding.template/package.json index 36e247054..2a35e04fb 100644 --- a/packages/binding.template/package.json +++ b/packages/binding.template/package.json @@ -4,7 +4,8 @@ "description": "TKO Template bindings", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -37,11 +38,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/builder/package.json b/packages/builder/package.json index 105de0015..14029b0e7 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -26,10 +26,12 @@ } ], "files": [ - "dist/" + "dist/", + "types/" ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -47,5 +49,6 @@ "builder", "framework", "customizable" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/builder/src/Builder.ts b/packages/builder/src/Builder.ts index a5e1da4be..ea5fa7e2d 100644 --- a/packages/builder/src/Builder.ts +++ b/packages/builder/src/Builder.ts @@ -312,10 +312,10 @@ export class Builder { } /** - * @return {KnockoutInstance} An instance of Knockout. + * @return {KnockoutInstance & T} An instance of Knockout with merged extension properties. */ - create(...additionalProperties): KnockoutInstance { - const instance: KnockoutInstance = Object.assign( + create(additionalProperties: T): KnockoutInstance & T { + const instance = Object.assign( { get getBindingHandler() { return options.getBindingHandler @@ -326,8 +326,8 @@ export class Builder { }, knockout, //never change the order of these this.providedProperties, - ...additionalProperties - ) + additionalProperties + ) as KnockoutInstance & T instance.options.knockoutInstance = instance diff --git a/packages/computed/package.json b/packages/computed/package.json index 8043196be..213d1177c 100644 --- a/packages/computed/package.json +++ b/packages/computed/package.json @@ -4,7 +4,8 @@ "description": "TKO Computed Observables", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "dependencies": { "@tko/observable": "^4.1.0", @@ -35,11 +36,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/filter.punches/package.json b/packages/filter.punches/package.json index 1247b1224..f4d55f1cf 100644 --- a/packages/filter.punches/package.json +++ b/packages/filter.punches/package.json @@ -4,7 +4,8 @@ "description": "TKO filters from knockout punches", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -34,11 +35,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/lifecycle/package.json b/packages/lifecycle/package.json index 2c7a583ee..bb41c09a6 100644 --- a/packages/lifecycle/package.json +++ b/packages/lifecycle/package.json @@ -7,7 +7,8 @@ "@tko/utils": "^4.1.0" }, "files": [ - "dist/" + "dist/", + "types/" ], "name": "@tko/lifecycle", "description": "Knockout LifeCycle for object instances", @@ -26,6 +27,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "lifecycle", "mixin", "observable" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/observable/package.json b/packages/observable/package.json index 6926ade7d..31fb1c5fb 100644 --- a/packages/observable/package.json +++ b/packages/observable/package.json @@ -8,12 +8,14 @@ }, "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "files": [ - "dist/" + "dist/", + "types/" ], "keywords": [ "knockout", @@ -40,5 +42,6 @@ "module": "dist/index.js", "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/provider.attr/package.json b/packages/provider.attr/package.json index afc7a813b..8933063d7 100644 --- a/packages/provider.attr/package.json +++ b/packages/provider.attr/package.json @@ -4,7 +4,8 @@ "description": "Link HTML attributes (e.g. ko-handler-name) to binding handlers", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -19,6 +20,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -40,5 +42,6 @@ "provider", "attributes", "ko-attr" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.bindingstring/package.json b/packages/provider.bindingstring/package.json index 24aa53651..4ab2ddbf9 100644 --- a/packages/provider.bindingstring/package.json +++ b/packages/provider.bindingstring/package.json @@ -4,7 +4,8 @@ "description": "Abstract Base Class for providers that parse a binding string", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -20,6 +21,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -41,5 +43,6 @@ "provider", "parser", "binding-string" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.component/package.json b/packages/provider.component/package.json index fe5ca559b..9385e950f 100644 --- a/packages/provider.component/package.json +++ b/packages/provider.component/package.json @@ -4,7 +4,8 @@ "description": "Bind custom web components e.g. ", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -24,6 +25,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -45,5 +47,6 @@ "provider", "web-components", "custom-elements" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.databind/package.json b/packages/provider.databind/package.json index 52b3b5377..cfb6d3d34 100644 --- a/packages/provider.databind/package.json +++ b/packages/provider.databind/package.json @@ -4,7 +4,8 @@ "description": "Link HTML attributes based on a `data-bind` HTML attribute", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -20,6 +21,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -41,5 +43,6 @@ "provider", "data-bind", "declarative" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.multi/package.json b/packages/provider.multi/package.json index 9f1066d3e..cef5d1f98 100644 --- a/packages/provider.multi/package.json +++ b/packages/provider.multi/package.json @@ -4,7 +4,8 @@ "description": "Combine multiple other providers into one", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -20,6 +21,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -41,5 +43,6 @@ "provider", "composition", "multi-provider" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.mustache/package.json b/packages/provider.mustache/package.json index c2198e39a..04d581dda 100644 --- a/packages/provider.mustache/package.json +++ b/packages/provider.mustache/package.json @@ -4,7 +4,8 @@ "description": "Interpolate text/node attributes {{ }}", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -36,11 +37,13 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/provider.native/package.json b/packages/provider.native/package.json index d204b0154..bdd0090d5 100644 --- a/packages/provider.native/package.json +++ b/packages/provider.native/package.json @@ -4,7 +4,8 @@ "description": "Link binding handlers whose value is already attached to the node", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -21,6 +22,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "provider", "native", "jsx" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider.virtual/package.json b/packages/provider.virtual/package.json index 8e1feef6a..f6df87786 100644 --- a/packages/provider.virtual/package.json +++ b/packages/provider.virtual/package.json @@ -4,7 +4,8 @@ "description": "Binding provider for virtual elements", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "author": "The Knockout Team", "license": "MIT", @@ -22,6 +23,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "provider", "virtual-elements", "comments" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/provider/package.json b/packages/provider/package.json index a02244c5a..0b52e5a5d 100644 --- a/packages/provider/package.json +++ b/packages/provider/package.json @@ -4,7 +4,8 @@ "description": "Abstract base class of tko Provider (HTML <-> Data Binding linker)", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "author": "The Knockout Team", "license": "MIT", @@ -22,6 +23,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "provider", "binding-provider", "abstract" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/utils.component/package.json b/packages/utils.component/package.json index ede1e5b55..13e5da727 100644 --- a/packages/utils.component/package.json +++ b/packages/utils.component/package.json @@ -4,7 +4,8 @@ "description": "Registry and loading utilities for web components", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -21,6 +22,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "components", "registry", "loader" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/utils.functionrewrite/package.json b/packages/utils.functionrewrite/package.json index 98c08cf92..39db95095 100644 --- a/packages/utils.functionrewrite/package.json +++ b/packages/utils.functionrewrite/package.json @@ -4,7 +4,8 @@ "description": "Rewrite `function {}` as lambdas (=>)", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": {}, @@ -17,6 +18,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -37,5 +39,6 @@ "tko", "function-rewrite", "arrow-functions" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/utils.jsx/package.json b/packages/utils.jsx/package.json index 16cf3898d..195cba82e 100644 --- a/packages/utils.jsx/package.json +++ b/packages/utils.jsx/package.json @@ -4,7 +4,8 @@ "description": "TKO JSX Rendering", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -36,6 +37,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -45,5 +47,6 @@ }, "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/packages/utils.jsx/src/index.ts b/packages/utils.jsx/src/index.ts index 4b3eefdcc..2ab1af172 100644 --- a/packages/utils.jsx/src/index.ts +++ b/packages/utils.jsx/src/index.ts @@ -1,3 +1,4 @@ -export { getOriginalJsxForNode, maybeJsx, createElement, Fragment } from './jsx' +export { getOriginalJsxForNode, maybeJsx, createElement, Fragment, render } from './jsx' +export type { JsxRenderResult } from './jsx' export { default as JsxObserver } from './JsxObserver' diff --git a/packages/utils.jsx/src/jsx.ts b/packages/utils.jsx/src/jsx.ts index 378bc771c..e580c99ed 100644 --- a/packages/utils.jsx/src/jsx.ts +++ b/packages/utils.jsx/src/jsx.ts @@ -1,6 +1,6 @@ import { isObservable, unwrap } from '@tko/observable' -import { ORIGINAL_JSX_SYM } from './JsxObserver' +import { JsxObserver, ORIGINAL_JSX_SYM } from './JsxObserver' /** * @@ -55,3 +55,20 @@ export function createElement(elementName, attributes, ...children) { } export const Fragment = Symbol('JSX Fragment') + +/** The shape returned by `render()`. */ +export type JsxRenderResult = { + node: ChildNode | DocumentFragment | null + dispose: () => void +} + +/** Converts JSX to DOM nodes, returning the node and a dispose callback. */ +export function render(jsx: any): JsxRenderResult { + const fragment = document.createDocumentFragment() + const observer = new JsxObserver(jsx, fragment) + const node = fragment.childNodes.length === 1 ? fragment.firstChild : fragment + return { + node, + dispose: () => observer.dispose() + } +} diff --git a/packages/utils.parser/package.json b/packages/utils.parser/package.json index 8e25f0275..1183f02b4 100644 --- a/packages/utils.parser/package.json +++ b/packages/utils.parser/package.json @@ -4,7 +4,8 @@ "description": "Parse the Javascript-like language used in data-bind and other HTML attributes (CSP-safe)", "module": "dist/index.js", "files": [ - "dist/" + "dist/", + "types/" ], "license": "MIT", "dependencies": { @@ -20,6 +21,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -41,5 +43,6 @@ "parser", "csp-safe", "expression" - ] + ], + "types": "./types/index.d.ts" } diff --git a/packages/utils/package.json b/packages/utils/package.json index b026750d9..073f6dc91 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -4,7 +4,8 @@ "description": "TKO Utilities", "type": "module", "files": [ - "dist/" + "dist/", + "types/" ], "repository": { "type": "git", @@ -35,6 +36,7 @@ ], "exports": { ".": { + "types": "./types/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.js" } @@ -42,5 +44,6 @@ "module": "dist/index.js", "scripts": { "build": "bun ../../tools/build.ts" - } + }, + "types": "./types/index.d.ts" } diff --git a/plans/2026-06-04-dts-types-layout.md b/plans/2026-06-04-dts-types-layout.md new file mode 100644 index 000000000..9b5e8b24a --- /dev/null +++ b/plans/2026-06-04-dts-types-layout.md @@ -0,0 +1,167 @@ +# Plan: Per-Package Types Output and Reference Build Declarations + +## Summary + +Replace the current repo-wide declaration emit with a published-layout model: + +- every publishable workspace gets a generated `types/` folder +- `@tko/build.reference` publishes a bundled declaration entry from its own + `types/` folder +- `@tko/build.knockout` publishes declarations for the KO-compatible default + import that consumers already use at runtime +- package manifests advertise the generated declaration files instead of + relying on source-only resolution + +This is a packaging and build-pipeline change, not a runtime API redesign. +The goal is to make the shipped type surface match the shipped JS surface. + +This plan addresses GitHub issue #384: the 4.x packages are TypeScript-authored +and ship source maps that point at `.ts` files, but the published packages do +not include `.d.ts` files or `types`/`typings` metadata. Consumers currently +hit missing-declaration errors for `@tko/build.knockout`, +`@tko/build.reference`, and modular `@tko/*` imports. + +## Current State + +- Root `package.json` runs `bunx tsc --build tsconfig.dts.json`. +- `tsconfig.dts.json` emits one + repo-level declaration bundle to `builds/dts/dist/tko.d.ts`. +- Published package manifests currently ship only `dist/`. +- Most package manifests expose JS through `exports` and `module`, but do not + point TypeScript at a published declaration artifact. +- Consumers importing `@tko/build.knockout`, `@tko/build.reference`, or an + individual `@tko/*` package get missing-declaration errors because no + package publishes `.d.ts` files. +- `builds/reference/types` + exists in the tree today but is empty, so there is no established generated + output contract yet. + +## Desired State + +- Each published package under `packages/*` generates `types/**/*.d.ts`. +- `@tko/build.reference` generates and publishes `types/index.d.ts` as its + public declaration entrypoint. +- `@tko/build.knockout` generates and publishes `types/index.d.ts` for the + backwards-compatible default import surface. +- Package consumers resolve declarations from published artifacts, not from + repo-only source paths. +- The repo still supports fast local typechecking against source via + `tsconfig.json` `paths`, but declaration generation uses a packaging-aware + pipeline. +- Clean/verify scripts remove and rebuild `types/` outputs just like `dist/`. + +## Constraints + +- Do not weaken the current `moduleResolution: "bundler"` setup or the source + resolution that keeps local `tsc` fast and accurate. +- Preserve the public JS entrypoints for all packages, especially + `@tko/build.knockout`. +- Avoid introducing runtime dependencies into framework packages. +- Treat manifest changes as public-package surface changes: the generated type + layout should be intentional and stable enough to publish. + +## Implementation Plan + +### 1. Replace the single-bundle d.ts strategy + +- Retire the current `outFile`-based `tsconfig.dts.json` flow. +- Introduce a declaration-generation script or tsconfig setup that emits + workspace-local declarations into `types/`. +- Add the types-folder to .gitignore +- Scope the generator to the 27 publishable workspaces (`packages/*`, + `builds/reference`, `builds/knockout`) so the output layout mirrors publish + boundaries. +- Emit declarations for `@tko/build.knockout` and `@tko/build.reference` + explicitly, because issue #384 reports both build packages as currently + untyped for consumers. + +### 2. Emit per-package declarations + +- For each package in `packages/*`, emit declarations rooted at + `types/index.d.ts` plus any sibling declaration files needed by imports. +- Ensure emitted paths stay self-contained inside each package's `types/` + folder and do not point back to repo-only source files. +- Verify that packages with re-export-heavy entrypoints still produce usable + published declarations. + +### 3. Bundle declarations for `@tko/build.reference` + +- Generate declarations for the reference build from its published entrypoint, + not from an ad hoc hand-maintained file. +- Bundle or flatten the reference build's public type surface into + `builds/reference/types/index.d.ts`. +- Make the bundled reference declaration the only public type entry for + `@tko/build.reference`, while its internal dependencies continue to publish + their own package-local `types/` folders. +- Confirm the reference declaration captures the builder-created default export + shape and its re-exported public symbols. + +### 4. Generate declarations for `@tko/build.knockout` + +- Generate declarations from the knockout build's published entrypoint. +- Preserve the KO-compatible default import surface so + `import ko from '@tko/build.knockout'` typechecks without depending on the old + `knockout` package or DefinitelyTyped. +- Verify the declaration output is compatible with the runtime export shape and + does not accidentally advertise source-only internals. + +### 5. Update package manifests + +- Update every published workspace `package.json` to ship `types/` alongside + `dist/`. +- Add top-level `"types"` entries that point at the generated declarations. +- Add `"types"` conditions inside `exports` for the main entrypoint so bundler + resolution and published consumption agree. +- Update `builds/reference/package.json` to publish its bundled declaration + entry from `types/index.d.ts`. +- Update `builds/knockout/package.json` to publish its declaration entry from + `types/index.d.ts`. +- Update the root `package.json` + scripts so `bun run dts` and `bun run clean` reflect the new output layout. + +### 6. Clean up ignored/generated artifacts + +- Extend ignore/cleanup rules so generated `types/` directories are treated the + same way as generated `dist/` output. +- Remove any obsolete references to `builds/dts/dist/tko.d.ts`. +- Decide whether the empty checked-in `builds/reference/types/` directory stays + as a generated path only or gains committed placeholders. Prefer generated + output only unless a tool requires the directory to exist. + +## Likely Files Touched During Implementation + +- `package.json` +- `tsconfig.dts.json` +- `tsconfig.json` +- `.gitignore` +- `builds/reference/package.json` +- `builds/knockout/package.json` +- `packages/*/package.json` +- one new shared declaration-build script or supporting tsconfig files under + `tools/` and/or repo root + +## Verification + +1. `bun run dts` creates `types/` folders for every publishable workspace. +2. `find packages builds -maxdepth 2 -name types -type d` shows the expected + generated directories and no stray `builds/dts` output. +3. Consumer-style TypeScript smoke tests pass for: + `import ko from '@tko/build.knockout'`, + `import ko from '@tko/build.reference'`, and at least one modular + `@tko/*` import. +4. `bun run build` still succeeds after the manifest changes. +5. `bun run tsc` still typechecks against source without published-artifact + regressions. +6. `bun run verify` passes, since this change touches shared packaging + infrastructure. +7. Spot-check packed contents for at least one leaf package, + `@tko/build.knockout`, and `@tko/build.reference` to confirm both `dist/` + and `types/` would publish. + +## Release Notes / Follow-up + +- Add a changeset when implementation lands, because this changes the + published package layout and TypeScript consumption contract. +- If declaration bundling for `@tko/build.reference` needs a new dev-only tool, + justify it explicitly in the implementation PR and keep it out of runtime + package dependencies. diff --git a/tools/build-dts.ts b/tools/build-dts.ts new file mode 100644 index 000000000..16ea9828a --- /dev/null +++ b/tools/build-dts.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env bun +import { $, Glob } from 'bun' + +const tempOutDir = '.dts-tmp/emit' +const buildWorkspaces = ['builds/knockout', 'builds/reference'] +const packageWorkspaces: string[] = [] + +for await (const manifest of new Glob('packages/*/package.json').scan('.')) { + packageWorkspaces.push(manifest.replace('/package.json', '')) +} + +const workspaces = [...packageWorkspaces.sort(), ...buildWorkspaces].sort() + +const WORKSPACES_EXPECTED_COUNT = 27 +if (workspaces.length !== WORKSPACES_EXPECTED_COUNT) { + throw new Error(`Expected ${WORKSPACES_EXPECTED_COUNT} publishable workspaces, found ${workspaces.length}`) +} + +await $`rm -rf ${tempOutDir}`.quiet() +await $`bunx tsc -p tsconfig.dts.json` + +for (const workspace of workspaces) { + const emittedTypesDir = `${tempOutDir}/${workspace}/src` + const packageTypesDir = `${workspace}/types` + const indexDeclaration = `${emittedTypesDir}/index.d.ts` + const result = await $`test -f ${indexDeclaration}`.nothrow().quiet() + + if (result.exitCode !== 0) { + throw new Error(`Missing declaration entry for ${workspace}: ${indexDeclaration}`) + } + + await $`rm -rf ${packageTypesDir}`.quiet() + await $`mkdir -p ${packageTypesDir}`.quiet() + await $`cp -R ${emittedTypesDir}/. ${packageTypesDir}/`.quiet() +} + +await $`rm -rf ${tempOutDir}`.quiet() diff --git a/tools/types-smoke/consumer.ts b/tools/types-smoke/consumer.ts new file mode 100644 index 000000000..3c69bfd1d --- /dev/null +++ b/tools/types-smoke/consumer.ts @@ -0,0 +1,42 @@ +import ko from '@tko/build.knockout' +import reference from '@tko/build.reference' +import { observable } from '@tko/observable' +import { computed } from '@tko/computed' +import type { Computed } from '@tko/computed' +import { bindings } from '@tko/binding.core' +import { DataBindProvider } from '@tko/provider.databind' +import type { Provider } from '@tko/provider' +import { parseJson } from '@tko/utils' + +const name = observable('TKO') +const knockoutVersion: string = ko.version +const referenceVersion: string = reference.version +const bindingString: string = ko.expressionRewriting.preProcessBindings('text: name') +const jsxTree = reference.jsx.createElement('div', null, name) +const rendered = reference.jsx.render(jsxTree) + +rendered.dispose() + +// @tko/computed — Computed is callable and its return type is T +const doubled: Computed = computed(() => 2 * 2) +const doubledValue: number = doubled() + +// @ts-expect-error — Computed must not be assignable to Computed +const _notComputedString: Computed = doubled + +// @tko/binding.core — built-in bindings map has known keys +const coreBindingCount: number = Object.keys(bindings).length +const _hasTextBinding: boolean = 'text' in bindings + +// @tko/provider.databind — DataBindProvider must be assignable to Provider +// (exercises 3-level inheritance chain through generated .d.ts files) +const dbProvider = new DataBindProvider() +const _asProvider: Provider = dbProvider + +// @tko/utils — parseJson returns T | null; null guard narrows to T +const parsed: number | null = parseJson('42') +if (parsed !== null) { + const _narrowed: number = parsed +} + +export { bindingString, knockoutVersion, referenceVersion, doubled, doubledValue, coreBindingCount, dbProvider, parsed } diff --git a/tools/types-smoke/tsconfig.json b/tools/types-smoke/tsconfig.json new file mode 100644 index 000000000..8cdb85948 --- /dev/null +++ b/tools/types-smoke/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "strict": true, + "noImplicitAny": false, + "skipLibCheck": true, + "noEmit": true, + "types": ["jquery"], + "lib": ["es2022", "dom"] + }, + "include": ["./consumer.ts"] +} diff --git a/tsconfig.dts.json b/tsconfig.dts.json index d8bdb939d..234366137 100644 --- a/tsconfig.dts.json +++ b/tsconfig.dts.json @@ -1,15 +1,24 @@ { "extends": "./tsconfig.json", - "compilerOptions": { + "compilerOptions": { "noEmit": false, "emitDeclarationOnly": true, - "outFile": "builds/dts/dist/tko.d.ts", + "rootDir": ".", + "outDir": "./.dts-tmp/emit" }, + "include": [ + "global.d.ts", + "packages/*/src/**/*.ts", + "builds/reference/src/**/*.ts", + "builds/knockout/src/**/*.ts" + ], "exclude": [ + "packages/*/src/**/*.d.ts", + "builds/reference/src/**/*.d.ts", + "builds/knockout/src/**/*.d.ts", + "**/spec/**", "node_modules", - "builds", - "docs", - "tools", - "**/spec/**" + "tko.io", + "tools" ] }