From 2d5f53f14ea825bc36ea802e4e7d936a987f3c0e Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Wed, 25 Jun 2025 23:09:47 +0200 Subject: [PATCH 1/2] refactor: enable type checking --- lib/index.js | 129 ++++++++++++++---------- lib/utils/getAdditionalEntries.js | 2 +- lib/utils/getLibraryNamespace.js | 20 ++++ lib/utils/index.js | 2 + lib/utils/injectRefreshLoader.js | 24 +++-- lib/utils/makeRefreshRuntimeModule.js | 2 +- lib/utils/normalizeOptions.js | 2 +- loader/index.js | 16 ++- loader/utils/getModuleSystem.js | 4 +- loader/utils/getRefreshModuleRuntime.js | 2 +- loader/utils/normalizeOptions.js | 2 +- options/index.js | 5 +- package.json | 4 +- tsconfig.json | 1 + types/lib/index.d.ts | 2 +- types/loader/index.d.ts | 2 +- types/options/index.d.ts | 13 ++- yarn.lock | 16 +-- 18 files changed, 151 insertions(+), 97 deletions(-) create mode 100644 lib/utils/getLibraryNamespace.js diff --git a/lib/index.js b/lib/index.js index db5196ef..380e23db 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,6 +3,7 @@ const { getRefreshGlobalScope } = require('./globals'); const { getAdditionalEntries, getIntegrationEntry, + getLibraryNamespace, getSocketIntegration, injectRefreshLoader, makeRefreshRuntimeModule, @@ -15,7 +16,7 @@ class ReactRefreshPlugin { * @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin. */ constructor(options = {}) { - validateOptions(schema, options, { + validateOptions(/** @type {Parameters[0]} */ (schema), options, { name: 'React Refresh Plugin', baseDataPath: 'options', }); @@ -53,13 +54,13 @@ class ReactRefreshPlugin { const webpack = compiler.webpack || require('webpack'); const { DefinePlugin, - EntryDependency, EntryPlugin, ModuleFilenameHelpers, NormalModule, ProvidePlugin, RuntimeGlobals, Template, + dependencies: { ModuleDependency }, } = webpack; // Inject react-refresh context to all Webpack entry points. @@ -70,23 +71,33 @@ class ReactRefreshPlugin { new EntryPlugin(compiler.context, entry, { name: undefined }).apply(compiler); } - const integrationEntry = getIntegrationEntry(this.options.overlay.sockIntegration); + /** @type {{ name?: string; index?: number }[]} */ const socketEntryData = []; - compiler.hooks.make.tap( - { name: this.constructor.name, stage: Number.POSITIVE_INFINITY }, - (compilation) => { - // Exhaustively search all entries for `integrationEntry`. - // If found, mark those entries and the index of `integrationEntry`. - for (const [name, entryData] of compilation.entries.entries()) { - const index = entryData.dependencies.findIndex( - (dep) => dep.request && dep.request.includes(integrationEntry) - ); - if (index !== -1) { - socketEntryData.push({ name, index }); + + /** @type {string | undefined} */ + let integrationEntry; + if (this.options.overlay && this.options.overlay.sockIntegration) { + integrationEntry = getIntegrationEntry(this.options.overlay.sockIntegration); + } + + if (integrationEntry) { + compiler.hooks.make.tap( + { name: this.constructor.name, stage: Number.POSITIVE_INFINITY }, + (compilation) => { + // Exhaustively search all entries for `integrationEntry`. + // If found, mark those entries and the index of `integrationEntry`. + for (const [name, entryData] of compilation.entries.entries()) { + const index = entryData.dependencies.findIndex((dep) => { + if (!(dep instanceof ModuleDependency)) return false; + return dep.request.includes(integrationEntry); + }); + if (index !== -1) { + socketEntryData.push({ name, index }); + } } } - } - ); + ); + } // Overlay entries need to be injected AFTER integration's entry, // so we will loop through everything in `finishMake` instead of `make`. @@ -97,37 +108,39 @@ class ReactRefreshPlugin { name: this.constructor.name, stage: Number.MIN_SAFE_INTEGER + (overlayEntries.length - idx - 1), }, - (compilation) => { + async (compilation) => { // Only hook into the current compiler - if (compilation.compiler !== compiler) { - return Promise.resolve(); - } + if (compilation.compiler !== compiler) return; const injectData = socketEntryData.length ? socketEntryData : [{ name: undefined }]; - return Promise.all( + await Promise.all( injectData.map(({ name, index }) => { - return new Promise((resolve, reject) => { - const options = { name }; - const dep = EntryPlugin.createDependency(entry, options); - compilation.addEntry(compiler.context, dep, options, (err) => { - if (err) return reject(err); - - // If the entry is not a global one, - // and we have registered the index for integration entry, - // we will reorder all entry dependencies to our desired order. - // That is, to have additional entries DIRECTLY behind integration entry. - if (name && typeof index !== 'undefined') { - const entryData = compilation.entries.get(name); - entryData.dependencies.splice( - index + 1, - 0, - entryData.dependencies.splice(entryData.dependencies.length - 1, 1)[0] - ); - } - - resolve(); - }); - }); + return /** @type {Promise} */ ( + new Promise((resolve, reject) => { + const options = { name }; + const dep = EntryPlugin.createDependency(entry, options); + compilation.addEntry(compiler.context, dep, options, (err) => { + if (err) return reject(err); + + // If the entry is not a global one, + // and we have registered the index for integration entry, + // we will reorder all entry dependencies to our desired order. + // That is, to have additional entries DIRECTLY behind integration entry. + if (name && typeof index !== 'undefined') { + const entryData = compilation.entries.get(name); + if (entryData) { + entryData.dependencies.splice( + index + 1, + 0, + entryData.dependencies.splice(entryData.dependencies.length - 1, 1)[0] + ); + } + } + + resolve(); + }); + }) + ); }) ); } @@ -143,21 +156,20 @@ class ReactRefreshPlugin { $RefreshSig$: `${refreshGlobal}.signature`, 'typeof $RefreshReg$': 'function', 'typeof $RefreshSig$': 'function', - - // Library mode - __react_refresh_library__: JSON.stringify( - Template.toIdentifier( - this.options.library || - compiler.options.output.uniqueName || - compiler.options.output.library - ) - ), }; /** @type {Record} */ const providedModules = { __react_refresh_utils__: require.resolve('./runtime/RefreshUtils'), }; + // Library mode + const libraryNamespace = getLibraryNamespace(this.options, compiler.options.output); + if (libraryNamespace) { + definedModules['__react_refresh_library__'] = JSON.stringify( + Template.toIdentifier(libraryNamespace) + ); + } + if (this.options.overlay === false) { // Stub errorOverlay module so their calls can be erased definedModules.__react_refresh_error_overlay__ = false; @@ -178,7 +190,15 @@ class ReactRefreshPlugin { new DefinePlugin(definedModules).apply(compiler); new ProvidePlugin(providedModules).apply(compiler); - const match = ModuleFilenameHelpers.matchObject.bind(undefined, this.options); + /** + * @param {string} [str] + * @returns boolean + */ + const match = (str) => { + if (str == null) return false; + return ModuleFilenameHelpers.matchObject(this.options, str); + }; + let loggedHotWarning = false; compiler.hooks.compilation.tap( this.constructor.name, @@ -188,9 +208,6 @@ class ReactRefreshPlugin { return; } - // Set factory for EntryDependency which is used to initialise the module - compilation.dependencyFactories.set(EntryDependency, normalModuleFactory); - const ReactRefreshRuntimeModule = makeRefreshRuntimeModule(webpack); compilation.hooks.additionalTreeRuntimeRequirements.tap( this.constructor.name, diff --git a/lib/utils/getAdditionalEntries.js b/lib/utils/getAdditionalEntries.js index 109ad33a..0c658fe2 100644 --- a/lib/utils/getAdditionalEntries.js +++ b/lib/utils/getAdditionalEntries.js @@ -18,7 +18,7 @@ function getAdditionalEntries(options) { const overlayEntries = [ // Error overlay runtime options.overlay && options.overlay.entry && require.resolve(options.overlay.entry), - ].filter(Boolean); + ].filter((x) => typeof x === 'string'); return { prependEntries, overlayEntries }; } diff --git a/lib/utils/getLibraryNamespace.js b/lib/utils/getLibraryNamespace.js new file mode 100644 index 00000000..0509b145 --- /dev/null +++ b/lib/utils/getLibraryNamespace.js @@ -0,0 +1,20 @@ +/** + * Gets the library namespace for React Refresh injection. + * @param {import('../types').NormalizedPluginOptions} pluginOptions Configuration options for this plugin. + * @param {import('webpack').Compilation["options"]["output"]} outputOptions Configuration options for Webpack output. + * @returns {string | undefined} The library namespace for React Refresh injection. + */ +function getLibraryNamespace(pluginOptions, outputOptions) { + if (pluginOptions.library) return pluginOptions.library; + if (outputOptions.uniqueName) return outputOptions.uniqueName; + + if (!outputOptions.library || !outputOptions.library.name) return; + + const libraryName = outputOptions.library.name; + if (Array.isArray(libraryName)) return libraryName[0]; + if (typeof libraryName === 'string') return libraryName; + if (Array.isArray(libraryName.root)) return libraryName.root[0]; + if (typeof libraryName.root === 'string') return libraryName.root; +} + +module.exports = getLibraryNamespace; diff --git a/lib/utils/index.js b/lib/utils/index.js index ebe6a367..8aa02c49 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,5 +1,6 @@ const getAdditionalEntries = require('./getAdditionalEntries'); const getIntegrationEntry = require('./getIntegrationEntry'); +const getLibraryNamespace = require('./getLibraryNamespace'); const getSocketIntegration = require('./getSocketIntegration'); const injectRefreshLoader = require('./injectRefreshLoader'); const makeRefreshRuntimeModule = require('./makeRefreshRuntimeModule'); @@ -8,6 +9,7 @@ const normalizeOptions = require('./normalizeOptions'); module.exports = { getAdditionalEntries, getIntegrationEntry, + getLibraryNamespace, getSocketIntegration, injectRefreshLoader, makeRefreshRuntimeModule, diff --git a/lib/utils/injectRefreshLoader.js b/lib/utils/injectRefreshLoader.js index 542cbba9..a5c1955d 100644 --- a/lib/utils/injectRefreshLoader.js +++ b/lib/utils/injectRefreshLoader.js @@ -18,31 +18,39 @@ const refreshUtilsPath = path.join(__dirname, '../runtime/RefreshUtils'); /** * Injects refresh loader to all JavaScript-like and user-specified files. - * @param {*} moduleData Module factory creation data. + * @param {import('webpack').ResolveData["createData"]} moduleData Module factory creation data. * @param {InjectLoaderOptions} injectOptions Options to alter how the loader is injected. - * @returns {*} The injected module factory creation data. + * @returns {import('webpack').ResolveData["createData"]} The injected module factory creation data. */ function injectRefreshLoader(moduleData, injectOptions) { const { match, options } = injectOptions; // Include and exclude user-specified files - if (!match(moduleData.matchResource || moduleData.resource)) return moduleData; + if (!match(moduleData.matchResource || moduleData.resource || '')) return moduleData; // Include and exclude dynamically generated modules from other loaders if (moduleData.matchResource && !match(moduleData.request)) return moduleData; // Exclude files referenced as assets - if (moduleData.type.includes('asset')) return moduleData; + if (moduleData.type != null && moduleData.type.includes('asset')) return moduleData; // Check to prevent double injection - if (moduleData.loaders.find(({ loader }) => loader === resolvedLoader)) return moduleData; + if ( + moduleData.loaders != null && + moduleData.loaders.find(({ loader }) => loader === resolvedLoader) + ) { + return moduleData; + } // Skip react-refresh and the plugin's runtime utils to prevent self-referencing - // this is useful when using the plugin as a direct dependency, // or when node_modules are specified to be processed. if ( - moduleData.resource.includes(reactRefreshPath) || - moduleData.resource.includes(refreshUtilsPath) + moduleData.resource != null && + (moduleData.resource.includes(reactRefreshPath) || + moduleData.resource.includes(refreshUtilsPath)) ) { return moduleData; } + if (moduleData.loaders == null) moduleData.loaders = []; + // As we inject runtime code for each module, // it is important to run the injected loader after everything. // This way we can ensure that all code-processing have been done, @@ -50,6 +58,8 @@ function injectRefreshLoader(moduleData, injectOptions) { moduleData.loaders.unshift({ loader: resolvedLoader, options, + ident: null, + type: null, }); return moduleData; diff --git a/lib/utils/makeRefreshRuntimeModule.js b/lib/utils/makeRefreshRuntimeModule.js index 875b7fca..471bcfdc 100644 --- a/lib/utils/makeRefreshRuntimeModule.js +++ b/lib/utils/makeRefreshRuntimeModule.js @@ -3,7 +3,7 @@ * This module creates an isolated `__webpack_require__` function for each module, * and injects a `$Refresh$` object into it for use by React Refresh. * @param {import('webpack')} webpack The Webpack exports. - * @returns {typeof import('webpack').RuntimeModule} The runtime module class. + * @returns {new () => import('webpack').RuntimeModule} The runtime module class. */ function makeRefreshRuntimeModule(webpack) { return class ReactRefreshRuntimeModule extends webpack.RuntimeModule { diff --git a/lib/utils/normalizeOptions.js b/lib/utils/normalizeOptions.js index 0130b58c..dbefd911 100644 --- a/lib/utils/normalizeOptions.js +++ b/lib/utils/normalizeOptions.js @@ -33,7 +33,7 @@ const normalizeOptions = (options) => { return overlay; }); - return options; + return /** @type {import('../types').NormalizedPluginOptions} */ (options); }; module.exports = normalizeOptions; diff --git a/loader/index.js b/loader/index.js index a2e066a0..fec7a137 100644 --- a/loader/index.js +++ b/loader/index.js @@ -3,6 +3,7 @@ // That check, however, will break when `fetch` polyfills are used for SSR setups. // We "reset" the polyfill here to ensure it won't interfere with source-map generation. const originalFetch = global.fetch; +// @ts-expect-error delete global.fetch; const { validate: validateOptions } = require('schema-utils'); @@ -31,17 +32,22 @@ const RefreshRuntimePath = require * @returns {void} */ function ReactRefreshLoader(source, inputSourceMap, meta) { - let options = this.getOptions(); - validateOptions(schema, options, { + const _options = this.getOptions(); + validateOptions(/** @type {Parameters[0]} */ (schema), _options, { baseDataPath: 'options', name: 'React Refresh Loader', }); - options = normalizeOptions(options); + const options = normalizeOptions(_options); const callback = this.async(); - const { ModuleFilenameHelpers, Template } = this._compiler.webpack || require('webpack'); + /** @type {import('webpack')} */ + let webpack; + if (this._compiler != null && this._compiler.webpack) webpack = this._compiler.webpack; + else webpack = require('webpack'); + + const { ModuleFilenameHelpers, Template } = webpack; const RefreshSetupRuntimes = { cjs: Template.asString( @@ -57,7 +63,7 @@ function ReactRefreshLoader(source, inputSourceMap, meta) { * @this {import('webpack').LoaderContext} * @param {string} source * @param {import('source-map').RawSourceMap} [inputSourceMap] - * @returns {Promise<[string, import('source-map').RawSourceMap]>} + * @returns {Promise<[string, import('source-map').RawSourceMap?]>} */ async function _loader(source, inputSourceMap) { /** @type {'esm' | 'cjs'} */ diff --git a/loader/utils/getModuleSystem.js b/loader/utils/getModuleSystem.js index 0ba67a60..817d5d73 100644 --- a/loader/utils/getModuleSystem.js +++ b/loader/utils/getModuleSystem.js @@ -6,7 +6,7 @@ let packageJsonTypeMap = new Map(); /** * Infers the current active module system from loader context and options. - * @this {import('webpack').loader.LoaderContext} + * @this {import('webpack').LoaderContext} * @param {import('webpack').ModuleFilenameHelpers} ModuleFilenameHelpers Webpack's module filename helpers. * @param {import('../types').NormalizedLoaderOptions} options The normalized loader options. * @return {Promise<'esm' | 'cjs'>} The inferred module system. @@ -73,7 +73,7 @@ async function getModuleSystem(ModuleFilenameHelpers, options) { // from the `resourcePath` folder up to the matching `searchPath`, // to avoid retracing these steps when processing sibling resources. if (packageJsonTypeMap.has(searchPath)) { - packageJsonType = packageJsonTypeMap.get(searchPath); + packageJsonType = /** @type {string} */ (packageJsonTypeMap.get(searchPath)); let currentPath = resourceContext; while (currentPath !== searchPath) { diff --git a/loader/utils/getRefreshModuleRuntime.js b/loader/utils/getRefreshModuleRuntime.js index ac853f9d..2d68749b 100644 --- a/loader/utils/getRefreshModuleRuntime.js +++ b/loader/utils/getRefreshModuleRuntime.js @@ -12,7 +12,7 @@ * [Reference for Runtime Injection](https://github.com/webpack/webpack/blob/b07d3b67d2252f08e4bb65d354a11c9b69f8b434/lib/HotModuleReplacementPlugin.js#L419) * [Reference for HMR Error Recovery](https://github.com/webpack/webpack/issues/418#issuecomment-490296365) * - * @param {import('webpack').Template} Webpack's templating helpers. + * @param {typeof import('webpack').Template} Template Webpack's templating helpers. * @param {ModuleRuntimeOptions} options The refresh module runtime options. * @returns {string} The refresh module runtime template. */ diff --git a/loader/utils/normalizeOptions.js b/loader/utils/normalizeOptions.js index 40684480..2fb78af3 100644 --- a/loader/utils/normalizeOptions.js +++ b/loader/utils/normalizeOptions.js @@ -19,7 +19,7 @@ const normalizeOptions = (options) => { return esModule; }); - return options; + return /** @type {import('../types').NormalizedLoaderOptions} */ (options); }; module.exports = normalizeOptions; diff --git a/options/index.js b/options/index.js index d29effe9..c02bea16 100644 --- a/options/index.js +++ b/options/index.js @@ -18,11 +18,10 @@ const d = (object, property, defaultValue) => { * Resolves the value for a nested object option. * @template T * @template {keyof T} Property - * @template Result * @param {T} object An object. * @param {Property} property A property of the provided object. - * @param {function(T | undefined): Result} fn The handler to resolve the property's value. - * @returns {Result} The resolved option value. + * @param {function(T[Property] | undefined): T[Property]} fn The handler to resolve the property's value. + * @returns {T[Property]} The resolved option value. */ const n = (object, property, fn) => { object[property] = fn(object[property]); diff --git a/package.json b/package.json index 98cb4b25..86bdb329 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,8 @@ "react-refresh": "^0.17.0", "sourcemap-validator": "^2.1.0", "terser-webpack-plugin": "^5.3.10", - "type-fest": "^4.18.3", - "typescript": "~5.4.5", + "type-fest": "^4.41.0", + "typescript": "~5.8.3", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", diff --git a/tsconfig.json b/tsconfig.json index 38bd7839..d696b634 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowJs": true, + "checkJs": true, "declaration": true, "emitDeclarationOnly": true, "esModuleInterop": true, diff --git a/types/lib/index.d.ts b/types/lib/index.d.ts index 571bc6a9..f327f75b 100644 --- a/types/lib/index.d.ts +++ b/types/lib/index.d.ts @@ -3,7 +3,7 @@ declare class ReactRefreshPlugin { /** * @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin. */ - constructor(options?: import('./types').ReactRefreshPluginOptions | undefined); + constructor(options?: import('./types').ReactRefreshPluginOptions); /** * @readonly * @type {import('./types').NormalizedPluginOptions} diff --git a/types/loader/index.d.ts b/types/loader/index.d.ts index f4d3245a..8693b483 100644 --- a/types/loader/index.d.ts +++ b/types/loader/index.d.ts @@ -12,6 +12,6 @@ export = ReactRefreshLoader; declare function ReactRefreshLoader( this: import('webpack').LoaderContext, source: string, - inputSourceMap?: import('source-map').RawSourceMap | undefined, + inputSourceMap?: import('source-map').RawSourceMap, meta?: any ): void; diff --git a/types/options/index.d.ts b/types/options/index.d.ts index bc106a29..b61268fb 100644 --- a/types/options/index.d.ts +++ b/types/options/index.d.ts @@ -10,20 +10,19 @@ export function d( object: T, property: Property, - defaultValue?: T[Property] | undefined + defaultValue?: T[Property] ): T[Property]; /** * Resolves the value for a nested object option. * @template T * @template {keyof T} Property - * @template Result * @param {T} object An object. * @param {Property} property A property of the provided object. - * @param {function(T | undefined): Result} fn The handler to resolve the property's value. - * @returns {Result} The resolved option value. + * @param {function(T[Property] | undefined): T[Property]} fn The handler to resolve the property's value. + * @returns {T[Property]} The resolved option value. */ -export function n( +export function n( object: T, property: Property, - fn: (arg0: T | undefined) => Result -): Result; + fn: (arg0: T[Property] | undefined) => T[Property] +): T[Property]; diff --git a/yarn.lock b/yarn.lock index bbe22d13..e1b84680 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5447,10 +5447,10 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2, type-fest@^0.21.3, type-fest@^4.18.3, type-fest@^4.20.0: - version "4.38.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.38.0.tgz#659fa14d1a71c2811400aa3b5272627e0c1e6b96" - integrity sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg== +type-fest@^0.20.2, type-fest@^0.21.3, type-fest@^4.20.0, type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== type-is@^1.6.16, type-is@~1.6.18: version "1.6.18" @@ -5465,10 +5465,10 @@ typed-query-selector@^2.12.0: resolved "https://registry.yarnpkg.com/typed-query-selector/-/typed-query-selector-2.12.0.tgz#92b65dbc0a42655fccf4aeb1a08b1dddce8af5f2" integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg== -typescript@~5.4.5: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@~5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== undici-types@~6.20.0: version "6.20.0" From 69aef7fd0b3b75c4117b8e97bac203cf267a09fe Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Wed, 25 Jun 2025 23:10:51 +0200 Subject: [PATCH 2/2] chore: add type check command and CI --- .github/workflows/ci.yml | 2 ++ package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6844ff31..484d31da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,8 @@ jobs: run: yarn install --frozen-lockfile - name: Check Linting run: yarn lint + - name: Check Types + run: yarn types:check - name: Check Formatting run: yarn format:check diff --git a/package.json b/package.json index 86bdb329..02ffa7d5 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "lint:fix": "yarn lint --fix", "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"", "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md}\"", + "types:check": "tsc --noEmit", "types:clean": "del types", "types:compile": "tsc", "types:prune-private": "del \"types/*/*\" \"!types/{lib,loader,options}/{index,types}.d.ts\"",