Skip to content

Commit f0967e4

Browse files
committed
feature #609 Outputting Typescript types (weaverryan)
This PR was merged into the 2.x branch. Discussion ---------- Outputting Typescript types | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Tickets | Helps with #601 | License | MIT Hi! This outputs proper TypeScript type files. As a bonus, I found the bug in our build system that was making `yarn build` WAY too slow. The GH action that checks for out-of-date build files is now reactivated ⭐ I decided to output all `.d.ts` files, even for modules that are considered "internal". I thought about only exporting types from the "main" file... but libraries like Stimulus seem to export everything. Mostly, I'd like to follow whatever is "most normal". Cheers! Commits ------- 715c289 Fixing TypeScript build system and making it output types
2 parents eeecf18 + 715c289 commit f0967e4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+669
-51
lines changed

.github/workflows/test.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ jobs:
3333
- run: yarn check-format
3434

3535
js-dist-current:
36-
# always skip check for now - building is too inefficient and large for CI
37-
if: false
3836
runs-on: ubuntu-latest
3937
steps:
4038
- uses: actions/checkout@master

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"workspaces": [
4-
"src/**/assets"
4+
"src/*/assets"
55
],
66
"scripts": {
77
"build": "yarn rollup -c",
@@ -16,9 +16,9 @@
1616
"@babel/preset-env": "^7.15.8",
1717
"@babel/preset-react": "^7.15.8",
1818
"@babel/preset-typescript": "^7.15.8",
19-
"@rollup/plugin-commonjs": "^22.0.0",
20-
"@rollup/plugin-node-resolve": "^13.0.6",
21-
"@rollup/plugin-typescript": "^8.3.0",
19+
"@rollup/plugin-commonjs": "^23.0.0",
20+
"@rollup/plugin-node-resolve": "^15.0.0",
21+
"@rollup/plugin-typescript": "^10.0.0",
2222
"@symfony/stimulus-testing": "^2.0.1",
2323
"@typescript-eslint/eslint-plugin": "^5.2.0",
2424
"@typescript-eslint/parser": "^5.2.0",
@@ -27,9 +27,8 @@
2727
"eslint-config-prettier": "^8.0.0",
2828
"eslint-plugin-jest": "^25.2.2",
2929
"jest": "^27.3.1",
30-
"pkg-up": "^3.1.0",
3130
"prettier": "^2.2.1",
32-
"rollup": "^2.68.0",
31+
"rollup": "^3.7.0",
3332
"tslib": "^2.3.1",
3433
"typescript": "^4.4.4"
3534
},

rollup.config.js

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import resolve from '@rollup/plugin-node-resolve';
2-
import commonjs from '@rollup/plugin-commonjs';
3-
import typescript from '@rollup/plugin-typescript';
4-
import glob from 'glob';
5-
import path from 'path';
6-
import pkgUp from 'pkg-up';
1+
const resolve = require('@rollup/plugin-node-resolve');
2+
const commonjs = require('@rollup/plugin-commonjs');
3+
const typescript = require('@rollup/plugin-typescript');
4+
const fs = require('fs');
5+
const glob = require('glob');
6+
const path = require('path');
77

88
/**
99
* Guarantees that any files imported from a peer dependency are treated as an external.
@@ -38,10 +38,34 @@ const wildcardExternalsPlugin = (peerDependencies) => ({
3838
}
3939
});
4040

41+
/**
42+
* Moves the generated TypeScript declaration files to the correct location.
43+
*
44+
* This could probably be configured in the TypeScript plugin.
45+
*/
46+
const moveTypescriptDeclarationsPlugin = (packagePath) => ({
47+
name: 'move-ts-declarations',
48+
writeBundle: async () => {
49+
const files = glob.sync(path.join(packagePath, 'dist', '*', 'assets', 'src', '**/*.d.ts'));
50+
files.forEach((file) => {
51+
// a bit odd, but remove first 7 directories, which will leave
52+
// only the relative path to the file
53+
const relativePath = file.split('/').slice(7).join('/');
54+
55+
const targetFile = path.join(packagePath, 'dist', relativePath);
56+
if (!fs.existsSync(path.dirname(targetFile))) {
57+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
58+
}
59+
fs.renameSync(file, targetFile);
60+
});
61+
}
62+
});
63+
4164
const files = glob.sync('src/*/assets/src/*controller.ts');
42-
const packages = files.map((file) => {
43-
const absolutePath = path.join(__dirname, file);
44-
const packageData = require(pkgUp.sync({ cwd: absolutePath }));
65+
module.exports = files.map((file) => {
66+
const packageRoot = path.join(file, '..', '..');
67+
const packagePath = path.join(packageRoot, 'package.json');
68+
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
4569
const peerDependencies = [
4670
'@hotwired/stimulus',
4771
...(packageData.peerDependencies ? Object.keys(packageData.peerDependencies) : [])
@@ -50,21 +74,28 @@ const packages = files.map((file) => {
5074
return {
5175
input: file,
5276
output: {
53-
file: path.join(absolutePath, '..', '..', 'dist', path.basename(file, '.ts') + '.js'),
77+
file: path.join(packageRoot, 'dist', path.basename(file, '.ts') + '.js'),
5478
format: 'esm',
5579
},
5680
external: peerDependencies,
5781
plugins: [
5882
resolve(),
59-
typescript(),
83+
typescript({
84+
filterRoot: packageRoot,
85+
include: ['src/**/*.ts'],
86+
compilerOptions: {
87+
outDir: 'dist',
88+
declaration: true,
89+
emitDeclarationOnly: true,
90+
}
91+
}),
6092
commonjs({
6193
namedExports: {
6294
'react-dom/client': ['createRoot'],
6395
},
6496
}),
65-
wildcardExternalsPlugin(peerDependencies)
97+
wildcardExternalsPlugin(peerDependencies),
98+
moveTypescriptDeclarationsPlugin(packageRoot),
6699
],
67100
};
68101
});
69-
70-
export default packages;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import TomSelect from 'tom-select';
3+
export default class extends Controller {
4+
#private;
5+
static values: {
6+
url: StringConstructor;
7+
optionsAsHtml: BooleanConstructor;
8+
noResultsFoundText: StringConstructor;
9+
noMoreResultsText: StringConstructor;
10+
minCharacters: NumberConstructor;
11+
tomSelectOptions: ObjectConstructor;
12+
preload: StringConstructor;
13+
};
14+
readonly urlValue: string;
15+
readonly optionsAsHtmlValue: boolean;
16+
readonly noMoreResultsTextValue: string;
17+
readonly noResultsFoundTextValue: string;
18+
readonly minCharactersValue: number;
19+
readonly tomSelectOptionsValue: object;
20+
readonly hasPreloadValue: boolean;
21+
readonly preloadValue: string;
22+
tomSelect: TomSelect;
23+
initialize(): void;
24+
connect(): void;
25+
disconnect(): void;
26+
get selectElement(): HTMLSelectElement | null;
27+
get formElement(): HTMLInputElement | HTMLSelectElement;
28+
get preload(): string | boolean;
29+
}

src/Autocomplete/assets/dist/controller.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ function __classPrivateFieldGet(receiver, state, kind, f) {
2222
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
2323
}
2424

25-
var _instances, _getCommonConfig, _createAutocomplete, _createAutocompleteWithHtmlContents, _createAutocompleteWithRemoteData, _stripTags, _mergeObjects, _createTomSelect, _dispatchEvent;
25+
var _default_1_instances, _default_1_getCommonConfig, _default_1_createAutocomplete, _default_1_createAutocompleteWithHtmlContents, _default_1_createAutocompleteWithRemoteData, _default_1_stripTags, _default_1_mergeObjects, _default_1_createTomSelect, _default_1_dispatchEvent;
2626
class default_1 extends Controller {
2727
constructor() {
2828
super(...arguments);
29-
_instances.add(this);
29+
_default_1_instances.add(this);
3030
}
3131
initialize() {
3232
this.element.setAttribute('data-live-ignore', '');
@@ -39,14 +39,14 @@ class default_1 extends Controller {
3939
}
4040
connect() {
4141
if (this.urlValue) {
42-
this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocompleteWithRemoteData).call(this, this.urlValue, this.minCharactersValue);
42+
this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithRemoteData).call(this, this.urlValue, this.minCharactersValue);
4343
return;
4444
}
4545
if (this.optionsAsHtmlValue) {
46-
this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocompleteWithHtmlContents).call(this);
46+
this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithHtmlContents).call(this);
4747
return;
4848
}
49-
this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocomplete).call(this);
49+
this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocomplete).call(this);
5050
}
5151
disconnect() {
5252
this.tomSelect.revertSettings.innerHTML = this.element.innerHTML;
@@ -77,7 +77,7 @@ class default_1 extends Controller {
7777
return this.preloadValue;
7878
}
7979
}
80-
_instances = new WeakSet(), _getCommonConfig = function _getCommonConfig() {
80+
_default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _default_1_getCommonConfig() {
8181
const plugins = {};
8282
const isMultiple = !this.selectElement || this.selectElement.multiple;
8383
if (!this.formElement.disabled && !isMultiple) {
@@ -109,19 +109,19 @@ _instances = new WeakSet(), _getCommonConfig = function _getCommonConfig() {
109109
if (!this.selectElement && !this.urlValue) {
110110
config.shouldLoad = () => false;
111111
}
112-
return __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, config, this.tomSelectOptionsValue);
113-
}, _createAutocomplete = function _createAutocomplete() {
114-
const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), {
112+
return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, config, this.tomSelectOptionsValue);
113+
}, _default_1_createAutocomplete = function _default_1_createAutocomplete() {
114+
const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), {
115115
maxOptions: this.selectElement ? this.selectElement.options.length : 50,
116116
});
117-
return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config);
118-
}, _createAutocompleteWithHtmlContents = function _createAutocompleteWithHtmlContents() {
119-
const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), {
117+
return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config);
118+
}, _default_1_createAutocompleteWithHtmlContents = function _default_1_createAutocompleteWithHtmlContents() {
119+
const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), {
120120
maxOptions: this.selectElement ? this.selectElement.options.length : 50,
121121
score: (search) => {
122122
const scoringFunction = this.tomSelect.getScoreFunction(search);
123123
return (item) => {
124-
return scoringFunction(Object.assign(Object.assign({}, item), { text: __classPrivateFieldGet(this, _instances, "m", _stripTags).call(this, item.text) }));
124+
return scoringFunction(Object.assign(Object.assign({}, item), { text: __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_stripTags).call(this, item.text) }));
125125
};
126126
},
127127
render: {
@@ -133,9 +133,9 @@ _instances = new WeakSet(), _getCommonConfig = function _getCommonConfig() {
133133
},
134134
},
135135
});
136-
return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config);
137-
}, _createAutocompleteWithRemoteData = function _createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) {
138-
const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), {
136+
return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config);
137+
}, _default_1_createAutocompleteWithRemoteData = function _default_1_createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) {
138+
const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), {
139139
firstUrl: (query) => {
140140
const separator = autocompleteEndpointUrl.includes('?') ? '&' : '?';
141141
return `${autocompleteEndpointUrl}${separator}query=${encodeURIComponent(query)}`;
@@ -175,17 +175,17 @@ _instances = new WeakSet(), _getCommonConfig = function _getCommonConfig() {
175175
},
176176
preload: this.preload,
177177
});
178-
return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config);
179-
}, _stripTags = function _stripTags(string) {
178+
return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config);
179+
}, _default_1_stripTags = function _default_1_stripTags(string) {
180180
return string.replace(/(<([^>]+)>)/gi, '');
181-
}, _mergeObjects = function _mergeObjects(object1, object2) {
181+
}, _default_1_mergeObjects = function _default_1_mergeObjects(object1, object2) {
182182
return Object.assign(Object.assign({}, object1), object2);
183-
}, _createTomSelect = function _createTomSelect(options) {
184-
__classPrivateFieldGet(this, _instances, "m", _dispatchEvent).call(this, 'autocomplete:pre-connect', { options });
183+
}, _default_1_createTomSelect = function _default_1_createTomSelect(options) {
184+
__classPrivateFieldGet(this, _default_1_instances, "m", _default_1_dispatchEvent).call(this, 'autocomplete:pre-connect', { options });
185185
const tomSelect = new TomSelect(this.formElement, options);
186-
__classPrivateFieldGet(this, _instances, "m", _dispatchEvent).call(this, 'autocomplete:connect', { tomSelect, options });
186+
__classPrivateFieldGet(this, _default_1_instances, "m", _default_1_dispatchEvent).call(this, 'autocomplete:connect', { tomSelect, options });
187187
return tomSelect;
188-
}, _dispatchEvent = function _dispatchEvent(name, payload) {
188+
}, _default_1_dispatchEvent = function _default_1_dispatchEvent(name, payload) {
189189
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
190190
};
191191
default_1.values = {

src/Autocomplete/assets/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@symfony/ux-autocomplete",
33
"description": "JavaScript-powered autocompletion functionality for forms.",
44
"main": "dist/controller.js",
5-
"module": "dist/controller.js",
5+
"types": "dist/controller.d.ts",
66
"version": "1.0.0",
77
"license": "MIT",
88
"symfony": {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
export default class extends Controller {
3+
readonly viewValue: any;
4+
static values: {
5+
view: ObjectConstructor;
6+
};
7+
connect(): void;
8+
_dispatchEvent(name: string, payload: any): void;
9+
}

src/Chartjs/assets/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"description": "Chart.js integration for Symfony",
44
"license": "MIT",
55
"version": "1.1.0",
6+
"main": "dist/controller.js",
7+
"types": "dist/controller.d.ts",
68
"symfony": {
79
"controllers": {
810
"chart": {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
export default class CropperController extends Controller {
3+
readonly publicUrlValue: string;
4+
readonly optionsValue: object;
5+
static values: {
6+
publicUrl: StringConstructor;
7+
options: ObjectConstructor;
8+
};
9+
connect(): void;
10+
_dispatchEvent(name: string, payload: any): void;
11+
}

src/Cropperjs/assets/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"description": "Cropper.js integration for Symfony",
44
"license": "MIT",
55
"version": "1.1.0",
6+
"main": "dist/controller.js",
7+
"types": "dist/controller.d.ts",
68
"symfony": {
79
"controllers": {
810
"cropper": {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
export default class extends Controller {
3+
readonly inputTarget: HTMLInputElement;
4+
readonly placeholderTarget: HTMLDivElement;
5+
readonly previewTarget: HTMLDivElement;
6+
readonly previewClearButtonTarget: HTMLButtonElement;
7+
readonly previewFilenameTarget: HTMLDivElement;
8+
readonly previewImageTarget: HTMLDivElement;
9+
static targets: string[];
10+
connect(): void;
11+
clear(): void;
12+
onInputChange(event: any): void;
13+
_populateImagePreview(file: Blob): void;
14+
_dispatchEvent(name: string, payload?: any): void;
15+
}

src/Dropzone/assets/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"description": "File input dropzones for Symfony Forms",
44
"license": "MIT",
55
"version": "1.1.0",
6+
"main": "dist/controller.js",
7+
"types": "dist/controller.d.ts",
68
"symfony": {
79
"controllers": {
810
"dropzone": {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
export default class extends Controller {
3+
readonly srcValue: string;
4+
readonly srcsetValue: any;
5+
readonly hasSrcsetValue: boolean;
6+
static values: {
7+
src: StringConstructor;
8+
srcset: ObjectConstructor;
9+
};
10+
connect(): void;
11+
_calculateSrcsetString(): string;
12+
_dispatchEvent(name: string, payload: any): void;
13+
}

src/LazyImage/assets/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"description": "Lazy image loader and utilities for Symfony",
44
"license": "MIT",
55
"version": "1.1.0",
6+
"main": "dist/controller.js",
7+
"types": "dist/controller.d.ts",
68
"symfony": {
79
"controllers": {
810
"lazy-image": {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import BackendRequest from './BackendRequest';
2+
export interface BackendInterface {
3+
makeRequest(data: any, actions: BackendAction[], updatedModels: string[], childrenFingerprints: any): BackendRequest;
4+
}
5+
export interface BackendAction {
6+
name: string;
7+
args: Record<string, string>;
8+
}
9+
export default class implements BackendInterface {
10+
private url;
11+
private readonly csrfToken;
12+
constructor(url: string, csrfToken?: string | null);
13+
makeRequest(data: any, actions: BackendAction[], updatedModels: string[], childrenFingerprints: any): BackendRequest;
14+
private willDataFitInUrl;
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default class {
2+
promise: Promise<Response>;
3+
actions: string[];
4+
updatedModels: string[];
5+
isResolved: boolean;
6+
constructor(promise: Promise<Response>, actions: string[], updateModels: string[]);
7+
containsOneOfActions(targetedActions: string[]): boolean;
8+
areAnyModelsUpdated(targetedModels: string[]): boolean;
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default class {
2+
response: Response;
3+
private body;
4+
constructor(response: Response);
5+
getBody(): Promise<string>;
6+
}

0 commit comments

Comments
 (0)