diff --git a/package.json b/package.json index e253170..8461f5e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lint:fix": "eslint --cache --max-warnings 0 --fix", "ensure-linted": "eslint --max-warnings 0 .", "semantic-release": "semantic-release", - "build": "yarn run clean && rollup -c", + "build": "yarn run clean && rollup -c --configPlugin typescript", "clean": "rimraf dist", "prepare": "husky install" }, @@ -44,7 +44,9 @@ "@semantic-release/changelog": "^5.0.1", "@semantic-release/exec": "^5.0.0", "@semantic-release/git": "^9.0.0", + "@types/glob": "^7.1.4", "@types/jest": "^26.0.24", + "@types/rimraf": "^3.0.1", "@typescript-eslint/eslint-plugin": "^4.28.3", "@typescript-eslint/parser": "^4.28.3", "babel-jest": "^27.0.6", diff --git a/rollup.config.js b/rollup.config.ts similarity index 100% rename from rollup.config.js rename to rollup.config.ts diff --git a/script-modules/gen-pkg.js b/script-modules/gen-pkg.ts similarity index 54% rename from script-modules/gen-pkg.js rename to script-modules/gen-pkg.ts index 1bcaf87..6d98fca 100644 --- a/script-modules/gen-pkg.js +++ b/script-modules/gen-pkg.ts @@ -1,4 +1,4 @@ -function trimDist(p) { +function trimDist(p: string): string { if (!p.startsWith("dist/")) { throw new Error("Path should starts with 'dist/'."); } @@ -8,9 +8,11 @@ function trimDist(p) { /** * * @see https://github.com/vladshcherbin/rollup-plugin-generate-package-json#basecontents - * @param {object} pkg input package.json content + * @param pkg input package.json content */ -export function getPkgJsonBaseContents(pkg) { +export function getPkgJsonBaseContents( + pkg: Record, +): Record { const reserved = [ "name", "version", @@ -23,14 +25,24 @@ export function getPkgJsonBaseContents(pkg) { ]; const pkgEntries = ["main", "module", "types"]; - const contents = {}; + const contents: Record = {}; for (const e of reserved) { contents[e] = pkg[e]; } for (const e of pkgEntries) { - contents[e] = trimDist(pkg[e]); + const value = pkg[e]; + if (value === undefined) continue; + + if (typeof value !== "string") { + throw new Error( + `package.json "${e}" field must be a string if specified, but received ${String( + value, + )}`, + ); + } + contents[e] = trimDist(value); } contents.exports = { diff --git a/script-modules/rollup-bundle.js b/script-modules/rollup-bundle.ts similarity index 53% rename from script-modules/rollup-bundle.js rename to script-modules/rollup-bundle.ts index 8bd2079..107420c 100644 --- a/script-modules/rollup-bundle.js +++ b/script-modules/rollup-bundle.ts @@ -1,39 +1,37 @@ import { compilePlugins } from "./rollup-common"; import { terser } from "rollup-plugin-terser"; import { pascalCase } from "pascal-case"; -import pkg from "../package.json"; +import * as pkg from "../package.json"; import commonjs from "@rollup/plugin-commonjs"; import { nodeResolve } from "@rollup/plugin-node-resolve"; +import type { ModuleFormat, OutputOptions, RollupOptions } from "rollup"; const GLOBAL_NAMESPACE = pascalCase(pkg.name); -const FORMATS = [ +const FORMATS: ModuleFormat[] = [ // "es", "iife", "umd", ]; -/** - * - * @param { import("rollup").ModuleFormat } format - * @returns { import("rollup").OutputOptions } - */ -function genOutputs(format) { +function genOutputs(format: ModuleFormat): OutputOptions[] { // https://rollupjs.org/guide/en/#outputname const name = format === "es" ? undefined : GLOBAL_NAMESPACE; - return [true, false].map((min) => ({ - format, - sourcemap: true, - file: `dist/bundle/${format}${min ? ".min" : ""}.js`, - name, - plugins: min ? [terser()] : undefined, - exports: "auto", - })); + return [true, false].map( + (min): OutputOptions => ({ + format, + sourcemap: true, + file: `dist/bundle/${format}${min ? ".min" : ""}.js`, + name, + plugins: min ? [terser()] : undefined, + exports: "auto", + }), + ); } -export default { +const bundleConfig: RollupOptions = { input: "src/index.ts", output: FORMATS.map(genOutputs).flat(1), plugins: [ @@ -43,3 +41,5 @@ export default { commonjs(), ], }; + +export default bundleConfig; diff --git a/script-modules/rollup-common.js b/script-modules/rollup-common.js deleted file mode 100644 index 085aa97..0000000 --- a/script-modules/rollup-common.js +++ /dev/null @@ -1,21 +0,0 @@ -import typescript from "@rollup/plugin-typescript"; -import babel from "@rollup/plugin-babel"; - -export function compilePlugins(options) { - return [ - // https://github.com/rollup/plugins/tree/master/packages/typescript - typescript({ - tsconfig: "tsconfig.json", - sourceMap: true, - inlineSources: true, - ...(options || {}).typescript, - }), - // https://github.com/rollup/plugins/tree/master/packages/babel - babel({ - extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".ts", ".tsx"], - babelHelpers: "bundled", - exclude: /node_modules/, - ...(options || {}).babel, - }), - ]; -} diff --git a/script-modules/rollup-common.ts b/script-modules/rollup-common.ts new file mode 100644 index 0000000..85c3858 --- /dev/null +++ b/script-modules/rollup-common.ts @@ -0,0 +1,29 @@ +import typescript, { RollupTypescriptOptions } from "@rollup/plugin-typescript"; +import babel, { RollupBabelInputPluginOptions } from "@rollup/plugin-babel"; +import type { Plugin } from "rollup"; + +export interface CompilePluginsOptions { + typescript?: Partial; + babel?: Partial; +} + +export function compilePlugins(options: CompilePluginsOptions = {}): Plugin[] { + return [ + // https://github.com/rollup/plugins/tree/master/packages/typescript + typescript({ + tsconfig: "tsconfig.json", + sourceMap: true, + inlineSources: true, + include: "src/**/*.ts", + exclude: ["**/*.spec.ts", "**/*.test.ts", "**/__test__/**/*.spec.ts"], + ...options.typescript, + }), + // https://github.com/rollup/plugins/tree/master/packages/babel + babel({ + extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".ts", ".tsx"], + babelHelpers: "bundled", + exclude: /node_modules/, + ...options.babel, + }), + ]; +} diff --git a/script-modules/rollup-dts.js b/script-modules/rollup-dts.ts similarity index 51% rename from script-modules/rollup-dts.js rename to script-modules/rollup-dts.ts index cce9071..ce08968 100644 --- a/script-modules/rollup-dts.js +++ b/script-modules/rollup-dts.ts @@ -2,34 +2,47 @@ import dts from "rollup-plugin-dts"; import getEntryFiles from "./util/entry-files"; import { chunkFileNames, typescriptDeclarationDir } from "./util/common"; import rimraf from "rimraf"; +import type { PluginImpl, RollupOptions } from "rollup"; -/** @type {import('rollup').PluginImpl} */ -const cleanAfterBuild = (paths) => { +function isStringOrStringArray(value: unknown): value is string | string[] { + return ( + typeof value === "string" || + (Array.isArray(value) && value.every((v) => typeof v === "string")) + ); +} + +const cleanAfterBuild: PluginImpl<{ paths?: string | string[] }> = ({ + paths, +} = {}) => { if (!paths) throw new Error("paths must be specified"); - if (typeof paths === "string") paths = [paths]; + if (!isStringOrStringArray(paths)) + throw new Error("paths must be a string or array of strings"); + + const pathList = typeof paths === "string" ? [paths] : paths; return { name: "clean-after-build", buildEnd() { return Promise.all( - paths.map( + pathList.map( (path) => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call rimraf(path, (err) => { if (err) reject(err); else resolve(); }); }), ), - ); + ).then(() => undefined); }, }; }; const declarationDir = `dist/es/${typescriptDeclarationDir}/`; -export default { +const dtsConfig: RollupOptions = { input: getEntryFiles((k, v) => [ k, v.replace(/^src\//, declarationDir).replace(/.ts$/, ".d.ts"), @@ -38,6 +51,7 @@ export default { { dir: "dist", format: "es", + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types chunkFileNames: (info) => { const name = info.name.replace(/\.d$/, ""); return chunkFileNames @@ -46,5 +60,7 @@ export default { }, }, ], - plugins: [dts(), cleanAfterBuild(declarationDir)], + plugins: [dts(), cleanAfterBuild({ paths: declarationDir })], }; + +export default dtsConfig; diff --git a/script-modules/rollup-node.js b/script-modules/rollup-node.ts similarity index 86% rename from script-modules/rollup-node.js rename to script-modules/rollup-node.ts index 906850f..93e3f99 100644 --- a/script-modules/rollup-node.js +++ b/script-modules/rollup-node.ts @@ -4,15 +4,15 @@ import { compilePlugins } from "./rollup-common"; import { getPkgJsonBaseContents } from "./gen-pkg"; import getEntryFiles from "./util/entry-files"; import { chunkFileNames, typescriptDeclarationDir } from "./util/common"; +import type { OutputOptions, RollupOptions } from "rollup"; -/** @type { import("rollup").OutputOptions } */ -const commonOutputOptions = { +const commonOutputOptions: Partial = { exports: "auto", sourcemap: true, chunkFileNames, }; -export default { +const nodeConfig: RollupOptions = { input: getEntryFiles(), output: [ { ...commonOutputOptions, dir: "dist", format: "cjs" }, @@ -40,3 +40,5 @@ export default { }), ], }; + +export default nodeConfig; diff --git a/script-modules/rollup-shims.d.ts b/script-modules/rollup-shims.d.ts new file mode 100644 index 0000000..2fb6368 --- /dev/null +++ b/script-modules/rollup-shims.d.ts @@ -0,0 +1,4 @@ +declare module "rollup-plugin-generate-package-json" { + const generatePackageJson: import("rollup").PluginImpl; + export default generatePackageJson; +} diff --git a/script-modules/util/common.js b/script-modules/util/common.ts similarity index 100% rename from script-modules/util/common.js rename to script-modules/util/common.ts diff --git a/script-modules/util/entry-files.js b/script-modules/util/entry-files.js deleted file mode 100644 index 2623370..0000000 --- a/script-modules/util/entry-files.js +++ /dev/null @@ -1,27 +0,0 @@ -import glob from "glob"; -import path from "path"; - -/** - * - * @param { (moduleName: string, fileRelativePath: string, index: number)=> [string, string] } formatter - * @returns { Record } - */ -export default function getEntryFiles(formatter) { - let entries = [ - ...glob - .sync("src/*.ts") - .filter((f) => !/(\.(test|spec)\.ts$)|(\/__test__\/)/.test(f)) - .map((f) => [path.parse(f).name, f]), - ...glob - .sync("src/*/index.ts") - .map((f) => [path.basename(path.dirname(f)), f]), - ]; - - if (formatter) { - entries = entries.map((kv, i) => formatter(kv[0], kv[1], i)); - } - - const entryFiles = Object.fromEntries(entries); - - return entryFiles; -} diff --git a/script-modules/util/entry-files.ts b/script-modules/util/entry-files.ts new file mode 100644 index 0000000..6df0209 --- /dev/null +++ b/script-modules/util/entry-files.ts @@ -0,0 +1,30 @@ +import * as glob from "glob"; +import * as path from "path"; + +export type EntryFileFormatter = ( + moduleName: string, + fileRelativePath: string, + index: number, +) => [string, string]; + +export default function getEntryFiles( + formatter?: EntryFileFormatter, +): Record { + let entries: (readonly [string, string])[] = [ + ...glob + .sync("src/*.ts") + .filter((f) => !/(\.(test|spec)\.ts$)|(\/__test__\/)/.test(f)) + .map((f) => [path.parse(f).name, f] as const), + ...glob + .sync("src/*/index.ts") + .map((f) => [path.basename(path.dirname(f)), f] as const), + ]; + + if (formatter) { + entries = entries.map((kv, i) => formatter(kv[0], kv[1], i)); + } + + const entryFiles = Object.fromEntries(entries); + + return entryFiles; +} diff --git a/tsconfig.json b/tsconfig.json index ef63518..621857f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,9 +4,11 @@ "module": "esnext", "strict": true, "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true }, - "include": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts"], + "include": ["**/*.ts"], "ts-node": { "compilerOptions": { "skipLibCheck": true, diff --git a/yarn.lock b/yarn.lock index 8c742e1..632082d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,6 +1762,14 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/glob@*", "@types/glob@^7.1.4": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" + integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/graceful-fs@^4.1.2": version "4.1.3" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f" @@ -1801,6 +1809,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + "@types/minimist@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" @@ -1838,6 +1851,14 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== +"@types/rimraf@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.1.tgz#1bbc106f0978742289103e080d4b41b3b4656e58" + integrity sha512-CAoSlbco40aKZ0CkelBF2g3JeN6aioRaTVnqSX5pWsn/WApm6IDxI4e4tD9D0dY/meCkyyleP1IQDVN13F4maA== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"