diff --git a/packages/cli/src/commands/bundle/bundlers.js b/packages/cli/src/commands/bundle/bundlers.js index 26bdf6b78ad0..9bc95fd39523 100644 --- a/packages/cli/src/commands/bundle/bundlers.js +++ b/packages/cli/src/commands/bundle/bundlers.js @@ -8,7 +8,11 @@ 'use strict'; const javascript = require('./javascript'); +const typescript = require('./typescript'); -const bundlers = new Map([['.js', javascript]]); +const bundlers = new Map([ + ['.js', javascript], + ['.ts', typescript], +]); module.exports = bundlers; diff --git a/packages/cli/src/commands/bundle/typescript.js b/packages/cli/src/commands/bundle/typescript.js new file mode 100644 index 000000000000..877bfed10d6a --- /dev/null +++ b/packages/cli/src/commands/bundle/typescript.js @@ -0,0 +1,155 @@ +/** + * Copyright IBM Corp. 2018, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const { babel } = require('@rollup/plugin-babel'); +const commonjs = require('@rollup/plugin-commonjs'); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const typescript = require('@rollup/plugin-typescript'); +const { pascalCase } = require('change-case'); +const fs = require('fs-extra'); +const path = require('path'); +const { rollup } = require('rollup'); + +async function bundle(entrypoint, options) { + const globals = options.globals ? formatGlobals(options.globals) : {}; + const { name } = options; + const packageFolder = await findPackageFolder(entrypoint); + + const outputFolders = [ + { + format: 'esm', + directory: path.join(packageFolder, 'es'), + }, + { + format: 'cjs', + directory: path.join(packageFolder, 'lib'), + }, + { + format: 'umd', + directory: path.join(packageFolder, 'umd'), + }, + ]; + + await Promise.all(outputFolders.map(({ directory }) => fs.remove(directory))); + + const jsEntryPoints = outputFolders.map(({ directory, format }) => ({ + outputDir: directory, + file: path.join(directory, 'index.js'), + format, + })); + + const packageJsonPath = path.join(packageFolder, 'package.json'); + const packageJson = await fs.readJson(packageJsonPath); + const { dependencies = {} } = packageJson; + + await Promise.all( + jsEntryPoints.map(async ({ outputDir, file, format }) => { + const bundle = await rollup({ + input: entrypoint, + external: Object.keys(dependencies), + plugins: [ + typescript({ + noEmitOnError: true, + noForceEmit: true, + outputToFilesystem: false, + compilerOptions: { + rootDir: 'src', + emitDeclarationOnly: true, + outDir: outputDir, + }, + }), + babel({ + exclude: 'node_modules/**', + babelrc: false, + presets: [ + [ + '@babel/preset-env', + { + modules: false, + targets: { + browsers: ['last 1 version', 'ie >= 11', 'Firefox ESR'], + }, + }, + ], + '@babel/preset-typescript', + ], + babelHelpers: 'bundled', + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }), + nodeResolve(), + commonjs({ + include: [/node_modules/], + extensions: ['.js'], + }), + ], + }); + const outputOptions = { + exports: 'auto', + file, + format, + }; + + if (format === 'umd') { + outputOptions.name = name; + outputOptions.globals = { + ...formatDependenciesIntoGlobals(dependencies), + ...globals, + }; + } + + return bundle.write(outputOptions); + }) + ); +} + +function formatGlobals(string) { + const mappings = string.split(',').map((mapping) => { + return mapping.split('='); + }); + return mappings.reduce( + (acc, [pkg, global]) => ({ + ...acc, + [pkg]: global, + }), + {} + ); +} + +function formatDependenciesIntoGlobals(dependencies) { + return Object.keys(dependencies).reduce((acc, key) => { + const parts = key.split('/').map((identifier, i) => { + if (i === 0) { + return identifier.replace(/@/, ''); + } + return identifier; + }); + + return { + ...acc, + [key]: pascalCase(parts.join(' ')), + }; + }, {}); +} + +async function findPackageFolder(entrypoint) { + let packageFolder = entrypoint; + + while (packageFolder !== '/' && path.dirname(packageFolder) !== '/') { + packageFolder = path.dirname(packageFolder); + const packageJsonPath = path.join(packageFolder, 'package.json'); + + if (await fs.pathExists(packageJsonPath)) { + break; + } + } + + return packageFolder; +} + +module.exports = bundle; diff --git a/packages/icon-build-helpers/src/builders/react/builder.js b/packages/icon-build-helpers/src/builders/react/builder.js index fcad7615efd7..a538adde2184 100644 --- a/packages/icon-build-helpers/src/builders/react/builder.js +++ b/packages/icon-build-helpers/src/builders/react/builder.js @@ -36,12 +36,14 @@ const babelConfig = { }, ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-transform-react-constant-elements', 'babel-plugin-dev-expression', ], babelHelpers: 'bundled', + extensions: ['.ts', '.tsx', '.js', '.jsx'], }; async function builder(metadata, { output }) { @@ -97,10 +99,11 @@ async function builder(metadata, { output }) { } const files = { - 'index.js': `${BANNER}\n\nexport { default as Icon } from './Icon.js';`, + 'index.ts': `${BANNER}\n\nexport { default as Icon } from './Icon.tsx';`, }; const input = { - 'index.js': 'index.js', + 'index.js': 'index.ts', + 'Icon.js': './Icon.tsx', }; for (const m of modules) { files[m.filepath] = m.entrypoint; @@ -115,23 +118,23 @@ async function builder(metadata, { output }) { files[filename] = `${BANNER} import React from 'react'; -import Icon from './Icon.js'; +import Icon from './Icon.tsx'; const didWarnAboutDeprecation = {};`; for (const m of bucket) { files[filename] += `export ${m.source}`; - files['index.js'] += `export { ${m.moduleName} } from '${filename}';`; + files['index.ts'] += `export { ${m.moduleName} } from '${filename}';`; } } const bundle = await rollup({ - input: input, + input, external, plugins: [ virtual({ - './Icon.js': await fs.readFile( - path.resolve(__dirname, './components/Icon.js'), + './Icon.tsx': await fs.readFile( + path.resolve(__dirname, './components/Icon.tsx'), 'utf8' ), ...files, @@ -164,12 +167,12 @@ const didWarnAboutDeprecation = {};`; } const umd = await rollup({ - input: 'index.js', + input: 'index.ts', external, plugins: [ virtual({ - './Icon.js': await fs.readFile( - path.resolve(__dirname, './components/Icon.js'), + './Icon.tsx': await fs.readFile( + path.resolve(__dirname, './components/Icon.tsx'), 'utf8' ), ...files, @@ -267,7 +270,7 @@ function createIconEntrypoint(moduleName, descriptor, isDeprecated = false) { const source = createIconSource(moduleName, descriptor, deprecatedBlock); return `${BANNER} import React from 'react'; -import Icon from './Icon.js'; +import Icon from './Icon.tsx'; ${deprecatedPreamble} ${source} export default ${moduleName}; diff --git a/packages/icon-build-helpers/src/builders/react/components/CarbonIcon.d.ts b/packages/icon-build-helpers/src/builders/react/components/CarbonIcon.d.ts new file mode 100644 index 000000000000..33af5a09c777 --- /dev/null +++ b/packages/icon-build-helpers/src/builders/react/components/CarbonIcon.d.ts @@ -0,0 +1,15 @@ +/** + * Copyright IBM Corp. 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ +import { IconProps } from './Icon'; + +export interface CarbonIconProps extends IconProps { + size?: number | string; +} + +export type CarbonIconType = React.ForwardRefExoticComponent< + CarbonIconProps & React.RefAttributes +>; diff --git a/packages/icon-build-helpers/src/builders/react/components/Icon.js b/packages/icon-build-helpers/src/builders/react/components/Icon.tsx similarity index 58% rename from packages/icon-build-helpers/src/builders/react/components/Icon.js rename to packages/icon-build-helpers/src/builders/react/components/Icon.tsx index 9f2e6320615c..2e339479ea00 100644 --- a/packages/icon-build-helpers/src/builders/react/components/Icon.js +++ b/packages/icon-build-helpers/src/builders/react/components/Icon.tsx @@ -8,21 +8,37 @@ import { getAttributes } from '@carbon/icon-helpers'; import PropTypes from 'prop-types'; import React from 'react'; +export interface IconProps + extends Omit, 'ref' | 'tabIndex'> { + /** + * @see React.SVGAttributes.tabIndex + * @todo remove support for string in v12 + */ + tabIndex?: string | number | undefined; + + title?: string | undefined; +} + const Icon = React.forwardRef(function Icon( - { className, children, tabIndex, ...rest }, - ref + { className, children, tabIndex, ...rest }: IconProps, + ref: React.ForwardedRef ) { - const { tabindex, ...props } = getAttributes({ + const { tabindex, ...attrs } = getAttributes({ ...rest, tabindex: tabIndex, }); + const props: React.SVGProps = attrs; if (className) { props.className = className; } if (tabindex !== undefined && tabindex !== null) { - props.tabIndex = tabindex; + if (typeof tabindex === 'number') { + props.tabIndex = tabindex; + } else { + props.tabIndex = Number(tabIndex); + } } if (ref) { @@ -34,14 +50,17 @@ const Icon = React.forwardRef(function Icon( Icon.displayName = 'Icon'; Icon.propTypes = { - 'aria-hidden': PropTypes.string, + 'aria-hidden': PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.oneOf<'true' | 'false'>(['true', 'false']), + ]), 'aria-label': PropTypes.string, 'aria-labelledby': PropTypes.string, children: PropTypes.node, className: PropTypes.string, height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), preserveAspectRatio: PropTypes.string, - tabIndex: PropTypes.string, + tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), viewBox: PropTypes.string, width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), xmlns: PropTypes.string, diff --git a/packages/icon-build-helpers/src/builders/react/next.js b/packages/icon-build-helpers/src/builders/react/next.js index 3454ab4a46c2..c976cd832e4b 100644 --- a/packages/icon-build-helpers/src/builders/react/next.js +++ b/packages/icon-build-helpers/src/builders/react/next.js @@ -14,10 +14,12 @@ const { babel } = require('@rollup/plugin-babel'); const fs = require('fs-extra'); const path = require('path'); const { rollup } = require('rollup'); +const ts = require('typescript'); const virtual = require('../plugins/virtual'); const { babelConfig } = require('./next/babel'); const { svgToJSX, jsToAST } = require('./next/convert'); const templates = require('./next/templates'); +const { writeTsDefinitions } = require('./next/typescript'); // This builder outputs a collection of CommonJS modules representing our icon // components. It does not generate an `index.js` entrypoint file due to the @@ -84,13 +86,14 @@ async function builder(metadata, { output }) { // `input` object to files that we're generating for each icon component in // the `files` object const files = { - 'index.js': template.ast(` - import Icon from './Icon.js'; + 'index.ts': template.ast(` + import Icon from './Icon.tsx'; export { Icon }; `), }; const input = { - 'index.js': 'index.js', + 'index.js': 'index.ts', + 'Icon.js': './Icon.tsx', }; const BUCKET_SIZE = 125; const buckets = [ @@ -126,7 +129,7 @@ async function builder(metadata, { output }) { input[filename] = filename; files[filename] = template.ast(` import React from 'react'; - import Icon from './Icon.js'; + import Icon from './Icon.tsx'; import { iconPropTypes } from './iconPropTypes.js'; const didWarnAboutDeprecation = {}; `); @@ -137,16 +140,16 @@ async function builder(metadata, { output }) { files[filename] = t.file(t.program(files[filename])); files[filename] = generate(files[filename]).code; - files['index.js'].push(template.ast(`export * from '${filename}';`)); + files['index.ts'].push(template.ast(`export * from '${filename}';`)); } - files['index.js'] = generate(t.file(t.program(files['index.js']))).code; + files['index.ts'] = generate(t.file(t.program(files['index.ts']))).code; const defaultVirtualOptions = { - // Each of our Icon modules use the "./Icon.js" path to import this base - // componnet - './Icon.js': await fs.readFile( - path.resolve(__dirname, './components/Icon.js'), + // Each Icon module uses the "./Icon.tsx" path to import this base component + // Babel transforms the .tsx extension to .js + './Icon.tsx': await fs.readFile( + path.resolve(__dirname, './components/Icon.tsx'), 'utf8' ), './iconPropTypes.js': ` @@ -179,10 +182,12 @@ async function builder(metadata, { output }) { { directory: path.join(output, 'es'), format: 'esm', + tsModuleKind: ts.ModuleKind.ESNext, }, { directory: path.join(output, 'lib'), format: 'commonjs', + tsModuleKind: ts.ModuleKind.CommonJS, }, ]; @@ -194,6 +199,9 @@ async function builder(metadata, { output }) { banner: templates.banner, exports: 'auto', }); + + // write TypeScript definition files + writeTsDefinitions(modules, buckets, target.tsModuleKind, target.directory); } } @@ -206,7 +214,7 @@ async function builder(metadata, { output }) { * * ```jsx * import React from 'react'; - * import Icon from './Icon.js'; + * import Icon from './Icon.tsx'; * * const ComponentName = React.forwardRef( * function ComponentName({ children, size = 32, ... rest}, ref) { @@ -230,7 +238,7 @@ function createIconEntrypoint(moduleName, source, isDeprecated = false) { const statements = [ // Import statements template.ast(`import React from 'react';`), - template.ast(`import Icon from './Icon.js';`), + template.ast(`import Icon from './Icon.tsx';`), template.ast(`import { iconPropTypes } from './iconPropTypes.js';`), ]; diff --git a/packages/icon-build-helpers/src/builders/react/next/babel.js b/packages/icon-build-helpers/src/builders/react/next/babel.js index 65ec0985f888..b2b0db7173ad 100644 --- a/packages/icon-build-helpers/src/builders/react/next/babel.js +++ b/packages/icon-build-helpers/src/builders/react/next/babel.js @@ -20,12 +20,14 @@ const babelConfig = { }, ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-transform-react-constant-elements', 'babel-plugin-dev-expression', ], babelHelpers: 'bundled', + extensions: ['.ts', '.tsx', '.js', '.jsx'], }; module.exports = { diff --git a/packages/icon-build-helpers/src/builders/react/next/typescript.js b/packages/icon-build-helpers/src/builders/react/next/typescript.js new file mode 100644 index 000000000000..80a74c355fde --- /dev/null +++ b/packages/icon-build-helpers/src/builders/react/next/typescript.js @@ -0,0 +1,105 @@ +/** + * Copyright IBM Corp. 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); +const templates = require('./templates'); +const ts = require('typescript'); + +/** + * TypeScript compiler options to use when producing definition files + * @type ts.CompilerOptions + */ +const tsConfig = { + target: ts.ScriptTarget.ES2015, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + allowJs: true, + declaration: true, + emitDeclarationOnly: true, + esModuleInterop: true, + strict: true, + skipLibCheck: true, +}; + +function emitIconComponent(compileOpts) { + const options = { ...tsConfig, ...compileOpts }; + const host = ts.createCompilerHost(options); + const iconComponentPath = path.resolve(__dirname, '../components/Icon.tsx'); + const program = ts.createProgram([iconComponentPath], options, host); + const emitResult = program.emit(); + ts.getPreEmitDiagnostics(program) + .concat(emitResult.diagnostics) + .forEach((diagnostic) => { + console.log( + ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') + ); + }); +} + +async function copyCarbonIconType(outDir) { + const srcPath = path.resolve(__dirname, '../components/CarbonIcon.d.ts'); + const destPath = path.resolve(outDir, 'CarbonIcon.d.ts'); + await fs.copyFile(srcPath, destPath); +} + +async function writeModuleTypes(modules, outDir) { + for (const m of modules) { + const content = + templates.banner + + '\n' + + "import type { CarbonIconType } from './CarbonIcon';\n" + + 'export const ' + + m.name + + ': CarbonIconType;\n'; + const filename = path.resolve(outDir, m.filepath.replace(/\.js$/, '.d.ts')); + await fs.writeFile(filename, content); + } +} + +async function writeBucketTypes(buckets, outDir) { + for (const bucket of buckets) { + const iconLines = []; + for (const m of bucket.modules) { + iconLines.push('export const ' + m.name + ': CarbonIconType;'); + } + const bucketModule = `generated/${bucket.id}`; + const filepath = path.resolve(outDir, `${bucketModule}.d.ts`); + const content = + templates.banner + + '\n' + + "import type { CarbonIconType } from '../CarbonIcon';\n" + + iconLines.join('\n') + + '\n'; + await fs.writeFile(filepath, content); + } +} + +async function writeIndex(buckets, outDir) { + const bucketModules = buckets.map((bucket) => `generated/${bucket.id}`); + const indexContent = + templates.banner + + '\n' + + "export { default as Icon } from './Icon';\n" + + bucketModules.map((path) => "export * from './" + path + "';").join('\n') + + '\n'; + await fs.writeFile(path.resolve(outDir, 'index.d.ts'), indexContent); +} + +async function writeTsDefinitions(modules, buckets, moduleKind, outDir) { + emitIconComponent({ module: moduleKind, outDir }); + copyCarbonIconType(outDir); + writeModuleTypes(modules, outDir); + writeBucketTypes(buckets, outDir); + writeIndex(buckets, outDir); +} + +module.exports = { + writeTsDefinitions, +}; diff --git a/packages/icon-helpers/package.json b/packages/icon-helpers/package.json index 44ac0dd98353..aeef566a44d0 100644 --- a/packages/icon-helpers/package.json +++ b/packages/icon-helpers/package.json @@ -30,7 +30,7 @@ "provenance": true }, "scripts": { - "build": "yarn clean && carbon-cli bundle src/index.js --name CarbonIconHelpers", + "build": "yarn clean && carbon-cli bundle src/index.ts --name CarbonIconHelpers", "clean": "rimraf es lib umd" }, "devDependencies": { diff --git a/packages/icon-helpers/src/getAttributes.js b/packages/icon-helpers/src/getAttributes.ts similarity index 80% rename from packages/icon-helpers/src/getAttributes.js rename to packages/icon-helpers/src/getAttributes.ts index 3bb9a389ae86..c35c2fb4fc10 100644 --- a/packages/icon-helpers/src/getAttributes.js +++ b/packages/icon-helpers/src/getAttributes.ts @@ -4,8 +4,16 @@ * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ +import React from 'react'; -export const defaultAttributes = { +interface IconAttributes + extends Omit, 'tabIndex'> { + tabindex?: string | number | undefined; + + title?: string | undefined; +} + +export const defaultAttributes: IconAttributes = { // Reference: // https://github.com/IBM/carbon-components-react/issues/1392 // https://github.com/PolymerElements/iron-iconset-svg/pull/47 @@ -23,9 +31,9 @@ export default function getAttributes({ height, viewBox = `0 0 ${width} ${height}`, ...attributes -} = {}) { +}: IconAttributes = {}): IconAttributes { const { tabindex, ...rest } = attributes; - const iconAttributes = { + const iconAttributes: IconAttributes = { ...defaultAttributes, ...rest, width, diff --git a/packages/icon-helpers/src/index.js b/packages/icon-helpers/src/index.ts similarity index 59% rename from packages/icon-helpers/src/index.js rename to packages/icon-helpers/src/index.ts index 8682d72328f4..afe2b73ed456 100644 --- a/packages/icon-helpers/src/index.js +++ b/packages/icon-helpers/src/index.ts @@ -6,7 +6,5 @@ */ import getAttributes, { defaultAttributes } from './getAttributes'; -import toString, { formatAttributes } from './toString'; -import toSVG from './toSVG'; -export { defaultAttributes, getAttributes, formatAttributes, toString, toSVG }; +export { defaultAttributes, getAttributes }; diff --git a/packages/icon-helpers/tsconfig.json b/packages/icon-helpers/tsconfig.json new file mode 100644 index 000000000000..50b186945f22 --- /dev/null +++ b/packages/icon-helpers/tsconfig.json @@ -0,0 +1,104 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "esnext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["src/**/*"] +} diff --git a/packages/pictograms-react/package.json b/packages/pictograms-react/package.json index 88e4e28b7935..5fefe074d7ac 100644 --- a/packages/pictograms-react/package.json +++ b/packages/pictograms-react/package.json @@ -44,7 +44,8 @@ }, "devDependencies": { "@carbon/icon-build-helpers": "^1.18.0", - "@carbon/pictograms": "^12.23.0" + "@carbon/pictograms": "^12.23.0", + "rimraf": "^5.0.0" }, "sideEffects": false } diff --git a/packages/react/icons/.gitignore b/packages/react/icons/.gitignore index 2acb30a937bf..8bb6d436611b 100644 --- a/packages/react/icons/.gitignore +++ b/packages/react/icons/.gitignore @@ -1,2 +1,3 @@ /index.esm.js /index.js +/index.d.ts diff --git a/packages/react/icons/src/index.js b/packages/react/icons/src/index.ts similarity index 100% rename from packages/react/icons/src/index.js rename to packages/react/icons/src/index.ts diff --git a/packages/react/package.json b/packages/react/package.json index 8fa1df9dbc0d..28a8918bf93b 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -34,7 +34,7 @@ }, "scripts": { "build": "yarn clean && node tasks/build-styles.js && node tasks/build.js", - "clean": "rimraf es lib icons/index.js icons/index.esm.js storybook-static", + "clean": "rimraf es lib icons/index.js icons/index.d.ts icons/index.esm.js storybook-static", "postinstall": "carbon-telemetry collect --install", "storybook": "storybook dev -p 3000", "storybook:build": "storybook build" diff --git a/packages/react/src/components/FileUploader/Filename.tsx b/packages/react/src/components/FileUploader/Filename.tsx index c9fd8d13c9ed..a9186e6cd2fe 100644 --- a/packages/react/src/components/FileUploader/Filename.tsx +++ b/packages/react/src/components/FileUploader/Filename.tsx @@ -11,11 +11,12 @@ import React from 'react'; import Loading from '../Loading'; import { usePrefix } from '../../internal/usePrefix'; import { ReactAttr } from '../../types/common'; - export type FilenameStatus = 'edit' | 'complete' | 'uploading'; +type SVGAttr = React.SVGAttributes; + export interface FilenameProps - extends Omit, 'tabIndex'> { + extends Omit { /** * Specify an id that describes the error to be read by screen readers when the filename is invalid */ @@ -85,8 +86,7 @@ function Filename({ + {...rest}> {iconDescription && {iconDescription}} ); diff --git a/packages/react/tasks/build.js b/packages/react/tasks/build.js index 4168b07a73cb..f156edbd1415 100644 --- a/packages/react/tasks/build.js +++ b/packages/react/tasks/build.js @@ -19,10 +19,12 @@ const packageJson = require('../package.json'); async function build() { const reactEntrypoint = { filepath: path.resolve(__dirname, '..', 'src', 'index.ts'), + rootDir: 'src', outputDirectory: path.resolve(__dirname, '..'), }; const iconsEntrypoint = { - filepath: path.resolve(__dirname, '..', 'icons', 'src', 'index.js'), + filepath: path.resolve(__dirname, '..', 'icons', 'src', 'index.ts'), + rootDir: path.join('icons', 'src'), outputDirectory: path.resolve(__dirname, '..', 'icons'), }; const formats = [ @@ -45,8 +47,8 @@ async function build() { const reactInputConfig = getRollupConfig( reactEntrypoint.filepath, - outputDirectory, - true + reactEntrypoint.rootDir, + outputDirectory ); const reactBundle = await rollup(reactInputConfig); @@ -58,20 +60,24 @@ async function build() { banner, exports: 'named', }); - } - const iconsInputConfig = getRollupConfig(iconsEntrypoint.filepath); - const iconsBundle = await rollup(iconsInputConfig); + const iconsInputConfig = getRollupConfig( + iconsEntrypoint.filepath, + iconsEntrypoint.rootDir, + outputDirectory + ); + const iconsBundle = await rollup(iconsInputConfig); - // Build @carbon/react icons - for (const format of formats) { - await iconsBundle.write({ - file: - format.type === 'commonjs' ? 'icons/index.js' : 'icons/index.esm.js', - format: format.type, - banner, - exports: 'named', - }); + // Build @carbon/react icons + for (const format of formats) { + await iconsBundle.write({ + file: + format.type === 'commonjs' ? 'icons/index.js' : 'icons/index.esm.js', + format: format.type, + banner, + exports: 'named', + }); + } } } @@ -98,6 +104,7 @@ const babelConfig = { }, ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ 'dev-expression', @@ -107,9 +114,10 @@ const babelConfig = { '@babel/plugin-transform-react-constant-elements', ], babelHelpers: 'bundled', + extensions: ['.ts', '.tsx', '.js', '.jsx'], }; -function getRollupConfig(input, outDir, useTS) { +function getRollupConfig(input, rootDir, outDir) { return { input, // Mark dependencies listed in `package.json` as external so that they are @@ -132,8 +140,17 @@ function getRollupConfig(input, outDir, useTS) { commonjs({ include: /node_modules/, }), - // Modify plugins for builds that require typescript - ...(useTS ? getTSPlugins(outDir) : getPlugins()), + typescript({ + noEmitOnError: true, + noForceEmit: true, + outputToFilesystem: false, + compilerOptions: { + emitDeclarationOnly: true, + rootDir, + outDir, + }, + }), + babel(babelConfig), stripBanner(), { transform(_code, id) { @@ -150,39 +167,6 @@ function getRollupConfig(input, outDir, useTS) { }; } -/** - * Rollup plugins to support typescript compilation/transpilation - * @param {*} outDir - * @returns - */ -function getTSPlugins(outDir) { - return [ - typescript({ - noEmitOnError: true, - noForceEmit: true, - outputToFilesystem: false, - compilerOptions: { - rootDir: 'src', - emitDeclarationOnly: true, - outDir, - }, - }), - babel({ - ...babelConfig, - presets: [...babelConfig.presets, '@babel/preset-typescript'], - extensions: ['.ts', '.tsx', '.js', '.jsx'], - }), - ]; -} - -/** - * Rollup plugins to support pure JS compilation - * @returns - */ -function getPlugins() { - return [babel(babelConfig)]; -} - build().catch((error) => { console.log(error); process.exit(1); diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 819211ef4edb..8052be5d0fec 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -21,6 +21,6 @@ // TODO: Turn back on once stricter typings for internal utitlies are complete "noImplicitAny": false }, - "include": ["src/**/*", "icons/**/*"], + "include": ["src/**/*", "icons/src/index.ts"], "exclude": ["node_modules", "build"] } diff --git a/yarn.lock b/yarn.lock index 70aec808aacc..7e404fe4d2f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2113,6 +2113,7 @@ __metadata: "@carbon/pictograms": ^12.23.0 "@carbon/telemetry": 0.1.0 prop-types: ^15.7.2 + rimraf: ^5.0.0 peerDependencies: react: ">=16" languageName: unknown