diff --git a/.prettierignore b/.prettierignore index 045e6342a..e0d8f6813 100644 --- a/.prettierignore +++ b/.prettierignore @@ -21,3 +21,4 @@ pnpm-lock.yaml /**/*.svg /**/*.png /**/*.md +/**/*.gen.d.ts diff --git a/packages/ui5-tooling-transpile/README.md b/packages/ui5-tooling-transpile/README.md index dd0ec75bc..8582a5351 100644 --- a/packages/ui5-tooling-transpile/README.md +++ b/packages/ui5-tooling-transpile/README.md @@ -41,6 +41,9 @@ npm install ui5-tooling-transpile --save-dev - transformTypeScript: `boolean` (old alias: transpileTypeScript) if enabled, the tooling extension transforms TypeScript sources; the default value is derived from the existence of a `tsconfig.json` in the root folder of the project - if the file exists the configuration option is `true` otherwise `false`; setting this configuration option overrules the automatic determination +- generateTsInterfaces: `boolean|undefined` (*experimental feature*) + option requires a dependency to the `@ui5/ts-interface-generator` when either the value of the option is `true` or `undefined` and the project is a TypeScript-based project or the `transformTypeScript` option is set to `true` - can be forced to be inactive by setting the option to `false` (only relevant for the middleware) + - generateDts: `boolean` if enabled, the tooling extension will generate type definitions (`.d.ts`) files; by default for projects of type `library` this option is considered as `true` and for other projects such as `application` this option is considered as `false` by default (is only relevant in case of transformTypeScript is `true`) diff --git a/packages/ui5-tooling-transpile/lib/middleware.js b/packages/ui5-tooling-transpile/lib/middleware.js index 8bfc6d21b..aa7526be2 100644 --- a/packages/ui5-tooling-transpile/lib/middleware.js +++ b/packages/ui5-tooling-transpile/lib/middleware.js @@ -1,4 +1,5 @@ /* eslint-disable jsdoc/check-param-names */ +const path = require("path"); const parseurl = require("parseurl"); /** @@ -17,7 +18,7 @@ const parseurl = require("parseurl"); * [MiddlewareUtil]{@link module:@ui5/server.middleware.MiddlewareUtil} instance * @param {object} parameters.options Options * @param {string} [parameters.options.configuration] Custom server middleware configuration if given in ui5.yaml - * @returns {function} Middleware function to use + * @returns {Function} Middleware function to use */ module.exports = async function ({ log, resources, options, middlewareUtil }) { const { @@ -26,7 +27,8 @@ module.exports = async function ({ log, resources, options, middlewareUtil }) { normalizeLineFeeds, determineResourceFSPath, transformAsync, - shouldHandlePath + shouldHandlePath, + resolveNodeModule } = require("./util")(log); const cwd = middlewareUtil.getProject().getRootPath() || process.cwd(); @@ -86,6 +88,32 @@ module.exports = async function ({ log, resources, options, middlewareUtil }) { ); } + // if the TypeScript interfaces should be created, launch the ts-interface-generator in watch mode + if (config.generateTsInterfaces) { + const generateTSInterfacesAPI = resolveNodeModule( + "@ui5/ts-interface-generator/dist/generateTSInterfacesAPI", + cwd + ); + if (generateTSInterfacesAPI) { + const { main } = require(generateTSInterfacesAPI); + try { + config.debug && log.info(`Starting "@ui5/ts-interface-generator" in watch mode...`); + main({ + watch: true, + logLevel: config.debug ? log.constructor.getLevel() : "error", + config: path.join(cwd, "tsconfig.json") + }); + } catch (e) { + log.error(e); + } + } else { + config.debug && + log.warn( + `Missing dependency "@ui5/ts-interface-generator"! TypeScript interfaces will not be generated until dependency has been added...` + ); + } + } + return async (req, res, next) => { const pathname = parseurl(req)?.pathname; if (pathname.endsWith(".js") && shouldHandlePath(pathname, config.excludes, config.includes)) { diff --git a/packages/ui5-tooling-transpile/lib/task.js b/packages/ui5-tooling-transpile/lib/task.js index fe0caada0..ce6cc35ed 100644 --- a/packages/ui5-tooling-transpile/lib/task.js +++ b/packages/ui5-tooling-transpile/lib/task.js @@ -290,7 +290,7 @@ module.exports = async function ({ log, workspace /*, dependencies*/, taskUtil, config.debug && log.info(` + [.d.ts] index.d.ts`); const pckgJsonFile = path.join(cwd, "package.json"); if (fs.existsSync(pckgJsonFile)) { - const pckgJson = require(pckgJsonFile); + const pckgJson = JSON.parse(fs.readFileSync(pckgJsonFile, { encoding: "utf8" })); if (!pckgJson.types) { log.warn( ` /!\\ package.json has no "types" property! Add it and point to "index.d.ts" in build destination!` diff --git a/packages/ui5-tooling-transpile/lib/util.js b/packages/ui5-tooling-transpile/lib/util.js index 4f9e8e7b1..9462573b8 100644 --- a/packages/ui5-tooling-transpile/lib/util.js +++ b/packages/ui5-tooling-transpile/lib/util.js @@ -198,8 +198,8 @@ module.exports = function (log) { const tscJsonPath = path.join(cwd, "tsconfig.json"); const isTypeScriptProject = fs.existsSync(tscJsonPath); - // read package.json and tsconfig.json to determine whether to transpile dependencies or not - if (isTypeScriptProject && !config.transpileDependencies) { + // read tsconfig.json to determine whether to transpile dependencies or not + if (isTypeScriptProject && !config.transpileDependencies && fs.existsSync(tscJsonPath)) { const tscJson = JSONC.parse(fs.readFileSync(tscJsonPath, { encoding: "utf8" })); const tsDeps = tscJson?.compilerOptions?.types?.filter((typePkgName) => { try { @@ -221,6 +221,21 @@ module.exports = function (log) { // derive whether TypeScript should be transformed or not const transformTypeScript = config.transformTypeScript ?? config.transpileTypeScript ?? isTypeScriptProject; + // load the pkgJson to determine the existence of the @ui5/ts-interface-generator + // to automatically set the config option generateTsInterfaces (if this is a ts project) + let generateTsInterfaces = config.generateTsInterfaces; + const pkgJsonPath = path.join(cwd, "package.json"); + if (transformTypeScript && generateTsInterfaces === undefined && fs.existsSync(pkgJsonPath)) { + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, { encoding: "utf8" })); + const deps = [ + ...Object.keys(pkgJson?.dependencies || {}), + ...Object.keys(pkgJson?.devDependencies || {}) + ]; + if (deps.indexOf("@ui5/ts-interface-generator") !== -1) { + generateTsInterfaces = true; + } + } + // derive the includes/excludes from the configuration const includes = config.includes || config.includePatterns || []; const defaultExcludes = [".png", ".jpeg", ".jpg"]; // still needed? @@ -244,6 +259,7 @@ module.exports = function (log) { excludes, filePattern, omitTSFromBuildResult: config.omitTSFromBuildResult, + generateTsInterfaces, generateDts: config.generateDts, transpileDependencies: config.transpileDependencies, transformAtStartup: config.transformAtStartup, @@ -448,7 +464,7 @@ module.exports = function (log) { * @param {string} pathname the path name * @param {Array} excludes exclude paths * @param {Array} includes include paths - * @returns true, if the path should be handled + * @returns {boolean} true, if the path should be handled */ shouldHandlePath: function shouldHandlePath(pathname, excludes = [], includes = []) { return ( diff --git a/packages/ui5-tooling-transpile/package.json b/packages/ui5-tooling-transpile/package.json index b4ea0515c..71c24733b 100644 --- a/packages/ui5-tooling-transpile/package.json +++ b/packages/ui5-tooling-transpile/package.json @@ -23,6 +23,14 @@ "devDependencies": { "ava": "^5.3.1" }, + "peerDependencies": { + "@ui5/ts-interface-generator": ">=0.8.0" + }, + "peerDependenciesMeta": { + "@ui5/ts-interface-generator": { + "optional": true + } + }, "scripts": { "lint": "eslint lib", "test": "ava --no-worker-threads" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d39a97086..98cf759f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,6 +429,9 @@ importers: '@babel/preset-typescript': specifier: ^7.22.5 version: 7.22.5(@babel/core@7.22.10) + '@ui5/ts-interface-generator': + specifier: '>=0.8.0' + version: 0.8.0(typescript@5.1.6) babel-plugin-transform-async-to-promises: specifier: ^0.8.18 version: 0.8.18 @@ -757,8 +760,11 @@ importers: '@ui5/cli': specifier: ^3.4.0 version: 3.4.0 + '@ui5/ts-interface-generator': + specifier: ^0.8.0 + version: 0.8.0(typescript@5.1.6) eslint: - specifier: ^8.47.0 + specifier: ^8.46.0 version: 8.47.0 rimraf: specifier: ^5.0.1 @@ -791,8 +797,8 @@ importers: specifier: ^3.4.0 version: 3.4.0 '@ui5/ts-interface-generator': - specifier: ^0.7.0 - version: 0.7.0 + specifier: ^0.8.0 + version: 0.8.0(typescript@5.1.6) eslint: specifier: ^8.47.0 version: 8.47.0 @@ -3197,7 +3203,7 @@ packages: resolution: {integrity: sha512-YXmxzxXTP3u9HQpSXvK8qqoAm7VWQIFria3FVMQKkOSkWkph1TNnvt3Q1JvKT7/Jgd1HfTc3QrK09a2FND9+8A==} engines: {node: ^14.17.0 || >=16.0.0} dependencies: - chalk: 4.1.0 + chalk: 4.1.2 execa: 5.0.0 strong-log-transformer: 2.1.0 dev: true @@ -5097,14 +5103,16 @@ packages: transitivePeerDependencies: - supports-color - /@ui5/ts-interface-generator@0.7.0: - resolution: {integrity: sha512-3dCDGr05UnnjMCFm89Mj7m5q2rBtI+zDZ3p3aSbWD463XkC7G3xwXcJ/ynFa8cdWH64H7M+66qQmStuYQ/zHXg==} + /@ui5/ts-interface-generator@0.8.0(typescript@5.1.6): + resolution: {integrity: sha512-V6djTW+IZ3ScOoXQvAIqwSb09nfPZniAkjTlqAFSAD7JsTLK4RwjMD4u/MpUKYewBC5RIElSqz2jScNH+ttLiw==} hasBin: true + peerDependencies: + typescript: '>=4.4.3' dependencies: hjson: 3.2.2 loglevel: 1.8.1 + typescript: 5.1.6 yargs: 17.7.2 - dev: true /@wdio/cli@7.32.0(typescript@5.1.6): resolution: {integrity: sha512-JT6h5Tk7ZjaMWVFNCZFz+Y4vQRVmtUAlZjx6ECqDQuumItu9Fqf9NEmoFHOoyFJqz6mJ/85x/IQb9nfQf7HH+w==} @@ -9841,7 +9849,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.5 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -9852,7 +9860,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.8 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -10177,7 +10185,6 @@ packages: /hjson@3.2.2: resolution: {integrity: sha512-MkUeB0cTIlppeSsndgESkfFD21T2nXPRaBStLtf3cAYA2bVEFdXlodZB0TukwZiobPD1Ksax5DK4RTZeaXCI3Q==} hasBin: true - dev: true /homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} @@ -11138,7 +11145,7 @@ packages: resolution: {integrity: sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - chalk: 4.1.0 + chalk: 4.1.2 diff-sequences: 29.4.3 jest-get-type: 29.4.3 pretty-format: 29.6.2 @@ -12190,7 +12197,6 @@ packages: /loglevel@1.8.1: resolution: {integrity: sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==} engines: {node: '>= 0.6.0'} - dev: true /long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} @@ -13097,7 +13103,7 @@ packages: array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 - minimatch: 3.0.5 + minimatch: 3.1.2 dev: true /mustache@2.2.1: @@ -13596,7 +13602,7 @@ packages: '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.6 axios: 1.4.0 - chalk: 4.1.0 + chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 7.0.4 @@ -16924,7 +16930,6 @@ packages: resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} engines: {node: '>=14.17'} hasBin: true - dev: true /ua-parser-js@0.7.35: resolution: {integrity: sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==} diff --git a/showcases/ui5-tsapp-simple/package.json b/showcases/ui5-tsapp-simple/package.json index c5baf3c19..6fe77984e 100644 --- a/showcases/ui5-tsapp-simple/package.json +++ b/showcases/ui5-tsapp-simple/package.json @@ -24,8 +24,9 @@ "@types/openui5": "1.117.0", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", + "@ui5/ts-interface-generator": "^0.8.0", "@ui5/cli": "^3.4.0", - "eslint": "^8.47.0", + "eslint": "^8.46.0", "rimraf": "^5.0.1", "typescript": "^5.1.6", "ui5-middleware-livereload": "workspace:^", diff --git a/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.gen.d.ts b/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.gen.d.ts new file mode 100644 index 000000000..a055b5322 --- /dev/null +++ b/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.gen.d.ts @@ -0,0 +1,19 @@ +import { PropertyBindingInfo } from "sap/ui/base/ManagedObject"; +import { $ControlSettings } from "sap/ui/core/Control"; + +declare module "./SimpleControl" { + + /** + * Interface defining the settings object used in constructor calls + */ + interface $SimpleControlSettings extends $ControlSettings { + text?: string | PropertyBindingInfo; + } + + export default interface SimpleControl { + + // property: text + getText(): string; + setText(text: string): this; + } +} diff --git a/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.ts b/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.ts new file mode 100644 index 000000000..edf6c7674 --- /dev/null +++ b/showcases/ui5-tsapp-simple/webapp/control/SimpleControl.ts @@ -0,0 +1,36 @@ +import Control from "sap/ui/core/Control"; +import RenderManager from "sap/ui/core/RenderManager"; +import type { MetadataOptions } from "sap/ui/core/Element"; + +/** + * @namespace ui5.ecosystem.demo.simpletsapp.control + */ +export default class SimpleControl extends Control { + static readonly metadata: MetadataOptions = { + properties: { + text: "string", + }, + }; + + // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures + constructor(idOrSettings?: string | $SimpleControlSettings); + constructor(id?: string, settings?: $SimpleControlSettings); + constructor(id?: string, settings?: $SimpleControlSettings) { + super(id, settings); + } + + renderer = { + apiVersion: 2, + render: (rm: RenderManager, control: SimpleControl) => { + rm.openStart("div", control); + rm.style("font-size", "2rem"); + rm.style("width", "2rem"); + rm.style("height", "2rem"); + rm.style("display", "inline-block"); + rm.style("color", "blue"); + rm.openEnd(); + rm.text(control.getText()); + rm.close("div"); + }, + }; +} diff --git a/showcases/ui5-tslib/package.json b/showcases/ui5-tslib/package.json index 0e419b1f5..57af72707 100644 --- a/showcases/ui5-tslib/package.json +++ b/showcases/ui5-tslib/package.json @@ -32,7 +32,7 @@ "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "@ui5/cli": "^3.4.0", - "@ui5/ts-interface-generator": "^0.7.0", + "@ui5/ts-interface-generator": "^0.8.0", "eslint": "^8.47.0", "karma": "^6.4.2", "karma-chrome-launcher": "^3.2.0",