From fff7b864f4292d0430ba2bda7098ad43876b0210 Mon Sep 17 00:00:00 2001 From: ZHAO Jin-Xiang Date: Wed, 29 Nov 2023 12:24:50 +0800 Subject: [PATCH] feat: use enum to replace const enum (#9261) close #1228 --- .eslintrc.cjs | 16 +- package.json | 3 + packages/compiler-core/src/ast.ts | 8 +- packages/compiler-core/src/codegen.ts | 2 +- .../compiler-core/src/compat/compatConfig.ts | 2 +- packages/compiler-core/src/errors.ts | 2 +- packages/compiler-core/src/options.ts | 2 +- packages/compiler-core/src/tokenizer.ts | 9 +- packages/compiler-core/src/utils.ts | 2 +- packages/compiler-dom/src/errors.ts | 4 +- .../src/transforms/stringifyStatic.ts | 2 +- packages/compiler-sfc/src/style/cssVars.ts | 2 +- packages/compiler-ssr/src/errors.ts | 4 +- packages/reactivity/src/constants.ts | 8 +- packages/reactivity/src/index.ts | 6 +- packages/reactivity/src/reactive.ts | 2 +- .../runtime-core/src/compat/compatConfig.ts | 2 +- packages/runtime-core/src/componentOptions.ts | 2 +- packages/runtime-core/src/componentProps.ts | 2 +- .../src/componentPublicInstance.ts | 2 +- .../runtime-core/src/components/Teleport.ts | 2 +- packages/runtime-core/src/devtools.ts | 2 +- packages/runtime-core/src/enums.ts | 2 +- packages/runtime-core/src/errorHandling.ts | 2 +- packages/runtime-core/src/hydration.ts | 2 +- packages/runtime-core/src/index.ts | 6 +- packages/runtime-core/src/renderer.ts | 2 +- packages/runtime-test/src/nodeOps.ts | 4 +- packages/shared/src/patchFlags.ts | 2 +- packages/shared/src/shapeFlags.ts | 2 +- packages/shared/src/slotFlags.ts | 2 +- packages/vue/macros.d.ts | 2 +- pnpm-lock.yaml | 24 +- rollup.config.js | 4 +- rollup.dts.config.js | 39 +-- scripts/build.js | 2 +- scripts/const-enum.js | 255 ---------------- scripts/dev.js | 3 +- scripts/inline-enums.js | 279 ++++++++++++++++++ tsconfig.build.json | 4 +- tsconfig.json | 8 +- 41 files changed, 397 insertions(+), 333 deletions(-) delete mode 100644 scripts/const-enum.js create mode 100644 scripts/inline-enums.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 70135b1036e..92b5b534293 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,6 +3,15 @@ const DOMGlobals = ['window', 'document'] const NodeGlobals = ['module', 'require'] +const banConstEnum = { + selector: 'TSEnumDeclaration[const=true]', + message: + 'Please use non-const enums. This project automatically inlines enums.' +} + +/** + * @type {import('eslint-define-config').ESLintConfig} + */ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { @@ -16,6 +25,7 @@ module.exports = { 'no-restricted-syntax': [ 'error', + banConstEnum, // since we target ES2015 for baseline support, we need to forbid object // rest spread usage in destructure as it compiles into a verbose helper. 'ObjectPattern > RestElement', @@ -55,7 +65,7 @@ module.exports = { files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'], rules: { 'no-restricted-globals': ['error', ...DOMGlobals], - 'no-restricted-syntax': 'off' + 'no-restricted-syntax': ['error', banConstEnum] } }, // Private package, browser only + no syntax restrictions @@ -63,7 +73,7 @@ module.exports = { files: ['packages/template-explorer/**', 'packages/sfc-playground/**'], rules: { 'no-restricted-globals': ['error', ...NodeGlobals], - 'no-restricted-syntax': 'off' + 'no-restricted-syntax': ['error', banConstEnum] } }, // JavaScript files @@ -79,7 +89,7 @@ module.exports = { files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'], rules: { 'no-restricted-globals': 'off', - 'no-restricted-syntax': 'off' + 'no-restricted-syntax': ['error', banConstEnum] } } ] diff --git a/package.json b/package.json index f7d420ab88f..d4e6e727054 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,9 @@ "@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-terser": "^0.4.4", "@types/hash-sum": "^1.0.2", + "@types/minimist": "^1.2.5", "@types/node": "^20.10.0", + "@types/semver": "^7.5.5", "@typescript-eslint/parser": "^6.13.0", "@vitest/coverage-istanbul": "^0.34.6", "@vue/consolidate": "0.17.3", @@ -75,6 +77,7 @@ "esbuild": "^0.19.5", "esbuild-plugin-polyfill-node": "^0.3.0", "eslint": "^8.54.0", + "eslint-define-config": "^1.24.1", "eslint-plugin-jest": "^27.6.0", "estree-walker": "^2.0.2", "execa": "^8.0.1", diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 05c187feec0..2bc85bf53d8 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -19,13 +19,13 @@ import { ImportItem, TransformContext } from './transform' // More namespaces can be declared by platform specific compilers. export type Namespace = number -export const enum Namespaces { +export enum Namespaces { HTML, SVG, MATH_ML } -export const enum NodeTypes { +export enum NodeTypes { ROOT, ELEMENT, TEXT, @@ -59,7 +59,7 @@ export const enum NodeTypes { JS_RETURN_STATEMENT } -export const enum ElementTypes { +export enum ElementTypes { ELEMENT, COMPONENT, SLOT, @@ -214,7 +214,7 @@ export interface DirectiveNode extends Node { * Higher levels implies lower levels. e.g. a node that can be stringified * can always be hoisted and skipped for patch. */ -export const enum ConstantTypes { +export enum ConstantTypes { NOT_CONSTANT = 0, CAN_SKIP_PATCH, CAN_HOIST, diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 9fe2bb24973..890ef9bfda4 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -69,7 +69,7 @@ export interface CodegenResult { map?: RawSourceMap } -const enum NewlineType { +enum NewlineType { Start = 0, End = -1, None = -2, diff --git a/packages/compiler-core/src/compat/compatConfig.ts b/packages/compiler-core/src/compat/compatConfig.ts index 1ca59b5b31d..eec0499876d 100644 --- a/packages/compiler-core/src/compat/compatConfig.ts +++ b/packages/compiler-core/src/compat/compatConfig.ts @@ -13,7 +13,7 @@ export interface CompilerCompatOptions { compatConfig?: CompilerCompatConfig } -export const enum CompilerDeprecationTypes { +export enum CompilerDeprecationTypes { COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT', COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC', COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER', diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index dac779dab54..ac11b7e3d55 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -37,7 +37,7 @@ export function createCompilerError( return error } -export const enum ErrorCodes { +export enum ErrorCodes { // parse errors ABRUPT_CLOSING_OF_EMPTY_COMMENT, CDATA_IN_HTML_CONTENT, diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index f7f9f2c1118..b0e3835e6a5 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -94,7 +94,7 @@ export type HoistTransform = ( parent: ParentNode ) => void -export const enum BindingTypes { +export enum BindingTypes { /** * returned from data() */ diff --git a/packages/compiler-core/src/tokenizer.ts b/packages/compiler-core/src/tokenizer.ts index ea6d0592e0a..861107ef8ba 100644 --- a/packages/compiler-core/src/tokenizer.ts +++ b/packages/compiler-core/src/tokenizer.ts @@ -38,13 +38,13 @@ import { fromCodePoint } from 'entities/lib/decode.js' -export const enum ParseMode { +export enum ParseMode { BASE, HTML, SFC } -export const enum CharCodes { +export enum CharCodes { Tab = 0x9, // "\t" NewLine = 0xa, // "\n" FormFeed = 0xc, // "\f" @@ -72,7 +72,6 @@ export const enum CharCodes { UpperZ = 0x5a, // "Z" LowerZ = 0x7a, // "z" LowerX = 0x78, // "x" - OpeningSquareBracket = 0x5b, // "[" LowerV = 0x76, // "v" Dot = 0x2e, // "." Colon = 0x3a, // ":" @@ -85,7 +84,7 @@ const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{" const defaultDelimitersClose = new Uint8Array([125, 125]) // "}}" /** All the states the tokenizer can be in. */ -export const enum State { +export enum State { Text = 1, // interpolation @@ -820,7 +819,7 @@ export default class Tokenizer { } } private stateBeforeDeclaration(c: number): void { - if (c === CharCodes.OpeningSquareBracket) { + if (c === CharCodes.LeftSqaure) { this.state = State.CDATASequence this.sequenceIndex = 0 } else { diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 0d6e0f5fe14..a159d2eedc7 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -65,7 +65,7 @@ const nonIdentifierRE = /^\d|[^\$\w]/ export const isSimpleIdentifier = (name: string): boolean => !nonIdentifierRE.test(name) -const enum MemberExpLexState { +enum MemberExpLexState { inMemberExp, inBrackets, inParens, diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts index b519dbdb762..f8582c0b6ac 100644 --- a/packages/compiler-dom/src/errors.ts +++ b/packages/compiler-dom/src/errors.ts @@ -20,7 +20,7 @@ export function createDOMCompilerError( ) as DOMCompilerError } -export const enum DOMErrorCodes { +export enum DOMErrorCodes { X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */, X_V_HTML_WITH_CHILDREN, X_V_TEXT_NO_EXPRESSION, @@ -36,7 +36,7 @@ export const enum DOMErrorCodes { } if (__TEST__) { - // esbuild cannot infer const enum increments if first value is from another + // esbuild cannot infer enum increments if first value is from another // file, so we have to manually keep them in sync. this check ensures it // errors out if there are collisions. if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) { diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts index 02a7cb031bf..32e5b3c2289 100644 --- a/packages/compiler-dom/src/transforms/stringifyStatic.ts +++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts @@ -33,7 +33,7 @@ import { isBooleanAttr } from '@vue/shared' -export const enum StringifyThresholds { +export enum StringifyThresholds { ELEMENT_WITH_BINDING_COUNT = 5, NODE_COUNT = 20 } diff --git a/packages/compiler-sfc/src/style/cssVars.ts b/packages/compiler-sfc/src/style/cssVars.ts index 9fe727bc5dc..fc14969dea8 100644 --- a/packages/compiler-sfc/src/style/cssVars.ts +++ b/packages/compiler-sfc/src/style/cssVars.ts @@ -70,7 +70,7 @@ export function parseCssVars(sfc: SFCDescriptor): string[] { return vars } -const enum LexerState { +enum LexerState { inParens, inSingleQuoteString, inDoubleQuoteString diff --git a/packages/compiler-ssr/src/errors.ts b/packages/compiler-ssr/src/errors.ts index e8e5a3a541d..c75a87e2a00 100644 --- a/packages/compiler-ssr/src/errors.ts +++ b/packages/compiler-ssr/src/errors.ts @@ -16,14 +16,14 @@ export function createSSRCompilerError( return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError } -export const enum SSRErrorCodes { +export enum SSRErrorCodes { X_SSR_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */, X_SSR_NO_TELEPORT_TARGET, X_SSR_INVALID_AST_NODE } if (__TEST__) { - // esbuild cannot infer const enum increments if first value is from another + // esbuild cannot infer enum increments if first value is from another // file, so we have to manually keep them in sync. this check ensures it // errors out if there are collisions. if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) { diff --git a/packages/reactivity/src/constants.ts b/packages/reactivity/src/constants.ts index 4ad2ec3c7da..f87c62604a8 100644 --- a/packages/reactivity/src/constants.ts +++ b/packages/reactivity/src/constants.ts @@ -1,20 +1,20 @@ // using literal strings instead of numbers so that it's easier to inspect // debugger events -export const enum TrackOpTypes { +export enum TrackOpTypes { GET = 'get', HAS = 'has', ITERATE = 'iterate' } -export const enum TriggerOpTypes { +export enum TriggerOpTypes { SET = 'set', ADD = 'add', DELETE = 'delete', CLEAR = 'clear' } -export const enum ReactiveFlags { +export enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', @@ -22,7 +22,7 @@ export const enum ReactiveFlags { RAW = '__v_raw' } -export const enum DirtyLevels { +export enum DirtyLevels { NotDirty = 0, ComputedValueMaybeDirty = 1, ComputedValueDirty = 2, diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 9497527e81e..2a9615b14b7 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -68,8 +68,4 @@ export { getCurrentScope, onScopeDispose } from './effectScope' -export { - TrackOpTypes /* @remove */, - TriggerOpTypes /* @remove */, - ReactiveFlags /* @remove */ -} from './constants' +export { TrackOpTypes, TriggerOpTypes, ReactiveFlags } from './constants' diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index c4888f6fba5..4bb1c198896 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -27,7 +27,7 @@ export const shallowReactiveMap = new WeakMap() export const readonlyMap = new WeakMap() export const shallowReadonlyMap = new WeakMap() -const enum TargetType { +enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 diff --git a/packages/runtime-core/src/compat/compatConfig.ts b/packages/runtime-core/src/compat/compatConfig.ts index 88b58fdfab0..9ae34c07a5c 100644 --- a/packages/runtime-core/src/compat/compatConfig.ts +++ b/packages/runtime-core/src/compat/compatConfig.ts @@ -10,7 +10,7 @@ import { } from '../component' import { warn } from '../warning' -export const enum DeprecationTypes { +export enum DeprecationTypes { GLOBAL_MOUNT = 'GLOBAL_MOUNT', GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER', GLOBAL_EXTEND = 'GLOBAL_EXTEND', diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index c7128ecfa89..9633cbfe907 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -586,7 +586,7 @@ export type OptionTypesType< Defaults: Defaults } -const enum OptionTypes { +enum OptionTypes { PROPS = 'Props', DATA = 'Data', COMPUTED = 'Computed', diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index b8e9bf66db5..bbe88bf7b82 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -164,7 +164,7 @@ export type ExtractPublicPropTypes = { [K in keyof Pick>]?: InferPropType } -const enum BooleanFlags { +enum BooleanFlags { shouldCast, shouldCastTrue } diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 2f934b14089..06e9965094b 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -281,7 +281,7 @@ if (__COMPAT__) { installCompatInstanceProperties(publicPropertiesMap) } -const enum AccessTypes { +enum AccessTypes { OTHER, SETUP, DATA, diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index b9413598e54..d1327db8ee8 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -269,7 +269,7 @@ export const TeleportImpl = { hydrate: hydrateTeleport } -export const enum TeleportMoveTypes { +export enum TeleportMoveTypes { TARGET_CHANGE, TOGGLE, // enable / disable REORDER // moved in the main view diff --git a/packages/runtime-core/src/devtools.ts b/packages/runtime-core/src/devtools.ts index bf67093f5c3..240a4aa04a7 100644 --- a/packages/runtime-core/src/devtools.ts +++ b/packages/runtime-core/src/devtools.ts @@ -10,7 +10,7 @@ interface AppRecord { types: Record } -const enum DevtoolsHooks { +enum DevtoolsHooks { APP_INIT = 'app:init', APP_UNMOUNT = 'app:unmount', COMPONENT_UPDATED = 'component:updated', diff --git a/packages/runtime-core/src/enums.ts b/packages/runtime-core/src/enums.ts index 63d829d7a87..829e2555d32 100644 --- a/packages/runtime-core/src/enums.ts +++ b/packages/runtime-core/src/enums.ts @@ -1,4 +1,4 @@ -export const enum LifecycleHooks { +export enum LifecycleHooks { BEFORE_CREATE = 'bc', CREATED = 'c', BEFORE_MOUNT = 'bm', diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index f3c03cc9e4f..aff4f5567fa 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -6,7 +6,7 @@ import { LifecycleHooks } from './enums' // contexts where user provided function may be executed, in addition to // lifecycle hooks. -export const enum ErrorCodes { +export enum ErrorCodes { SETUP_FUNCTION, RENDER_FUNCTION, WATCH_GETTER, diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 94d6e8f6277..d79c09d3d36 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -30,7 +30,7 @@ export type RootHydrateFunction = ( container: (Element | ShadowRoot) & { _vnode?: VNode } ) => void -const enum DOMNodeTypes { +enum DOMNodeTypes { ELEMENT = 1, TEXT = 3, COMMENT = 8 diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 85bd92e75b0..fcc460c43d0 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -361,7 +361,7 @@ export const ssrUtils = (__SSR__ ? _ssrUtils : null) as typeof _ssrUtils // 2.x COMPAT ------------------------------------------------------------------ -export { DeprecationTypes } from './compat/compatConfig' +import { DeprecationTypes as _DeprecationTypes } from './compat/compatConfig' export type { CompatVue } from './compat/global' export type { LegacyConfig } from './compat/globalConfig' @@ -393,3 +393,7 @@ const _compatUtils = { export const compatUtils = ( __COMPAT__ ? _compatUtils : null ) as typeof _compatUtils + +export const DeprecationTypes = ( + __COMPAT__ ? _DeprecationTypes : null +) as typeof _DeprecationTypes diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 3483f95d309..fc762af3d96 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -265,7 +265,7 @@ export type SetupRenderEffectFn = ( optimized: boolean ) => void -export const enum MoveType { +export enum MoveType { ENTER, LEAVE, REORDER diff --git a/packages/runtime-test/src/nodeOps.ts b/packages/runtime-test/src/nodeOps.ts index a3a8012f280..e45dda1a06f 100644 --- a/packages/runtime-test/src/nodeOps.ts +++ b/packages/runtime-test/src/nodeOps.ts @@ -1,12 +1,12 @@ import { markRaw } from '@vue/reactivity' -export const enum TestNodeTypes { +export enum TestNodeTypes { TEXT = 'text', ELEMENT = 'element', COMMENT = 'comment' } -export const enum NodeOpTypes { +export enum NodeOpTypes { CREATE = 'create', INSERT = 'insert', REMOVE = 'remove', diff --git a/packages/shared/src/patchFlags.ts b/packages/shared/src/patchFlags.ts index af5ee7b49e2..49ee70dc21c 100644 --- a/packages/shared/src/patchFlags.ts +++ b/packages/shared/src/patchFlags.ts @@ -16,7 +16,7 @@ * Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the * flags are handled during diff. */ -export const enum PatchFlags { +export enum PatchFlags { /** * Indicates an element with dynamic textContent (children fast path) */ diff --git a/packages/shared/src/shapeFlags.ts b/packages/shared/src/shapeFlags.ts index 8defb8a3bf3..84825c7f198 100644 --- a/packages/shared/src/shapeFlags.ts +++ b/packages/shared/src/shapeFlags.ts @@ -1,4 +1,4 @@ -export const enum ShapeFlags { +export enum ShapeFlags { ELEMENT = 1, FUNCTIONAL_COMPONENT = 1 << 1, STATEFUL_COMPONENT = 1 << 2, diff --git a/packages/shared/src/slotFlags.ts b/packages/shared/src/slotFlags.ts index 53860ff4dd1..805c7d7c693 100644 --- a/packages/shared/src/slotFlags.ts +++ b/packages/shared/src/slotFlags.ts @@ -1,4 +1,4 @@ -export const enum SlotFlags { +export enum SlotFlags { /** * Stable slots that only reference slot props or context state. The slot * can fully capture its own dependencies so when passed down the parent won't diff --git a/packages/vue/macros.d.ts b/packages/vue/macros.d.ts index 54fa154af7d..7feeb42ff58 100644 --- a/packages/vue/macros.d.ts +++ b/packages/vue/macros.d.ts @@ -10,7 +10,7 @@ import { export declare const RefType: unique symbol -export declare const enum RefTypes { +export declare enum RefTypes { Ref = 1, ComputedRef = 2, WritableComputedRef = 3 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2c545f6b70..2435b035085 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,15 @@ importers: '@types/hash-sum': specifier: ^1.0.2 version: 1.0.2 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 '@types/node': specifier: ^20.10.0 version: 20.10.0 + '@types/semver': + specifier: ^7.5.5 + version: 7.5.5 '@typescript-eslint/parser': specifier: ^6.13.0 version: 6.13.0(eslint@8.54.0)(typescript@5.2.2) @@ -62,6 +68,9 @@ importers: eslint: specifier: ^8.54.0 version: 8.54.0 + eslint-define-config: + specifier: ^1.24.1 + version: 1.24.1 eslint-plugin-jest: specifier: ^27.6.0 version: 27.6.0(eslint@8.54.0)(typescript@5.2.2) @@ -1514,6 +1523,10 @@ packages: resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} dev: true + /@types/minimist@1.2.5: + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + dev: true + /@types/node@20.10.0: resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==} dependencies: @@ -1528,8 +1541,8 @@ packages: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true - /@types/semver@7.5.4: - resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} + /@types/semver@7.5.5: + resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==} dev: true /@types/yauzl@2.10.2: @@ -1637,7 +1650,7 @@ packages: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) '@types/json-schema': 7.0.14 - '@types/semver': 7.5.4 + '@types/semver': 7.5.5 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) @@ -2842,6 +2855,11 @@ packages: source-map: 0.6.1 dev: true + /eslint-define-config@1.24.1: + resolution: {integrity: sha512-o36vBhPSWyIQlHoMqGhhcGmOOm2A2ccBVIdLTG/AWdm9YmjpsLpf+5ntf9LlHR6dduLREgxtGwvwPwSt7vnXJg==} + engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>= 8.6.0'} + dev: true + /eslint-plugin-jest@27.6.0(eslint@8.54.0)(typescript@5.2.2): resolution: {integrity: sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} diff --git a/rollup.config.js b/rollup.config.js index 44d01861da2..421fa9c3781 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,7 +12,7 @@ import terser from '@rollup/plugin-terser' import esbuild from 'rollup-plugin-esbuild' import alias from '@rollup/plugin-alias' import { entries } from './scripts/aliases.js' -import { constEnum } from './scripts/const-enum.js' +import { inlineEnums } from './scripts/inline-enums.js' if (!process.env.TARGET) { throw new Error('TARGET package must be specified via --environment flag.') @@ -32,7 +32,7 @@ const pkg = require(resolve(`package.json`)) const packageOptions = pkg.buildOptions || {} const name = packageOptions.filename || path.basename(packageDir) -const [enumPlugin, enumDefines] = constEnum() +const [enumPlugin, enumDefines] = inlineEnums() const outputConfigs = { 'esm-bundler': { diff --git a/rollup.dts.config.js b/rollup.dts.config.js index 39d2331e5f7..3e45167bc40 100644 --- a/rollup.dts.config.js +++ b/rollup.dts.config.js @@ -17,26 +17,29 @@ const targetPackages = targets ? packages.filter(pkg => targets.includes(pkg)) : packages -export default targetPackages.map(pkg => { - return { - input: `./temp/packages/${pkg}/src/index.d.ts`, - output: { - file: `packages/${pkg}/dist/${pkg}.d.ts`, - format: 'es' - }, - plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])], - onwarn(warning, warn) { - // during dts rollup, everything is externalized by default - if ( - warning.code === 'UNRESOLVED_IMPORT' && - !warning.exporter.startsWith('.') - ) { - return +export default targetPackages.map( + /** @returns {import('rollup').RollupOptions} */ + pkg => { + return { + input: `./temp/packages/${pkg}/src/index.d.ts`, + output: { + file: `packages/${pkg}/dist/${pkg}.d.ts`, + format: 'es' + }, + plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])], + onwarn(warning, warn) { + // during dts rollup, everything is externalized by default + if ( + warning.code === 'UNRESOLVED_IMPORT' && + !warning.exporter?.startsWith('.') + ) { + return + } + warn(warning) } - warn(warning) } } -}) +) /** * Patch the dts generated by rollup-plugin-dts @@ -45,6 +48,8 @@ export default targetPackages.map(pkg => { * otherwise it gets weird in vitepress `defineComponent` call with * "the inferred type cannot be named without a reference" * 2. Append custom augmentations (jsx, macros) + * + * @param {string} pkg * @returns {import('rollup').Plugin} */ function patchTypes(pkg) { diff --git a/scripts/build.js b/scripts/build.js index 162380900e7..b96e576caa9 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -26,7 +26,7 @@ import { execa, execaSync } from 'execa' import { cpus } from 'node:os' import { createRequire } from 'node:module' import { targets as allTargets, fuzzyMatchTarget } from './utils.js' -import { scanEnums } from './const-enum.js' +import { scanEnums } from './inline-enums.js' import prettyBytes from 'pretty-bytes' const require = createRequire(import.meta.url) diff --git a/scripts/const-enum.js b/scripts/const-enum.js deleted file mode 100644 index e9f25bcef50..00000000000 --- a/scripts/const-enum.js +++ /dev/null @@ -1,255 +0,0 @@ -// @ts-check - -/** - * We use rollup-plugin-esbuild for faster builds, but esbuild in isolation - * mode compiles const enums into runtime enums, bloating bundle size. - * - * Here we pre-process all the const enums in the project and turn them into - * global replacements, and remove the original declarations and re-exports. - * - * This erases the const enums before the esbuild transform so that we can - * leverage esbuild's speed while retaining the DX and bundle size benefits - * of const enums. - * - * This file is expected to be executed with project root as cwd. - */ - -import { execaSync } from 'execa' -import { - existsSync, - mkdirSync, - readFileSync, - rmSync, - writeFileSync -} from 'node:fs' -import { parse } from '@babel/parser' -import path from 'node:path' -import MagicString from 'magic-string' - -const ENUM_CACHE_PATH = 'temp/enum.json' - -function evaluate(exp) { - return new Function(`return ${exp}`)() -} - -// this is called in the build script entry once -// so the data can be shared across concurrent Rollup processes -export function scanEnums() { - /** - * @type {{ ranges: Record, defines: Record, ids: string[] }} - */ - const enumData = { - ranges: {}, - defines: {}, - ids: [] - } - - // 1. grep for files with exported const enum - const { stdout } = execaSync('git', ['grep', `export const enum`]) - const files = [...new Set(stdout.split('\n').map(line => line.split(':')[0]))] - - // 2. parse matched files to collect enum info - for (const relativeFile of files) { - const file = path.resolve(process.cwd(), relativeFile) - const content = readFileSync(file, 'utf-8') - const ast = parse(content, { - plugins: ['typescript'], - sourceType: 'module' - }) - - for (const node of ast.program.body) { - if ( - node.type === 'ExportNamedDeclaration' && - node.declaration && - node.declaration.type === 'TSEnumDeclaration' - ) { - if (file in enumData.ranges) { - // @ts-ignore - enumData.ranges[file].push([node.start, node.end]) - } else { - // @ts-ignore - enumData.ranges[file] = [[node.start, node.end]] - } - - const decl = node.declaration - let lastInitialized - for (let i = 0; i < decl.members.length; i++) { - const e = decl.members[i] - const id = decl.id.name - if (!enumData.ids.includes(id)) { - enumData.ids.push(id) - } - const key = e.id.type === 'Identifier' ? e.id.name : e.id.value - const fullKey = `${id}.${key}` - const saveValue = value => { - if (fullKey in enumData.defines) { - throw new Error(`name conflict for enum ${id} in ${file}`) - } - enumData.defines[fullKey] = JSON.stringify(value) - } - const init = e.initializer - if (init) { - let value - if ( - init.type === 'StringLiteral' || - init.type === 'NumericLiteral' - ) { - value = init.value - } - - // e.g. 1 << 2 - if (init.type === 'BinaryExpression') { - const resolveValue = node => { - if ( - node.type === 'NumericLiteral' || - node.type === 'StringLiteral' - ) { - return node.value - } else if (node.type === 'MemberExpression') { - const exp = content.slice(node.start, node.end) - if (!(exp in enumData.defines)) { - throw new Error( - `unhandled enum initialization expression ${exp} in ${file}` - ) - } - return enumData.defines[exp] - } else { - throw new Error( - `unhandled BinaryExpression operand type ${node.type} in ${file}` - ) - } - } - const exp = `${resolveValue(init.left)}${ - init.operator - }${resolveValue(init.right)}` - value = evaluate(exp) - } - - if (init.type === 'UnaryExpression') { - if ( - init.argument.type === 'StringLiteral' || - init.argument.type === 'NumericLiteral' - ) { - const exp = `${init.operator}${init.argument.value}` - value = evaluate(exp) - } else { - throw new Error( - `unhandled UnaryExpression argument type ${init.argument.type} in ${file}` - ) - } - } - - if (value === undefined) { - throw new Error( - `unhandled initializer type ${init.type} for ${fullKey} in ${file}` - ) - } - saveValue(value) - lastInitialized = value - } else { - if (lastInitialized === undefined) { - // first initialized - saveValue((lastInitialized = 0)) - } else if (typeof lastInitialized === 'number') { - saveValue(++lastInitialized) - } else { - // should not happen - throw new Error(`wrong enum initialization sequence in ${file}`) - } - } - } - } - } - } - - // 3. save cache - if (!existsSync('temp')) mkdirSync('temp') - writeFileSync(ENUM_CACHE_PATH, JSON.stringify(enumData)) - - return () => { - rmSync(ENUM_CACHE_PATH, { force: true }) - } -} - -/** - * @returns {[import('rollup').Plugin, Record]} - */ -export function constEnum() { - if (!existsSync(ENUM_CACHE_PATH)) { - throw new Error('enum cache needs to be initialized before creating plugin') - } - /** - * @type {{ ranges: Record, defines: Record, ids: string[] }} - */ - const enumData = JSON.parse(readFileSync(ENUM_CACHE_PATH, 'utf-8')) - - // construct a regex for matching re-exports of known const enums - const reExportsRE = new RegExp( - `export {[^}]*?\\b(${enumData.ids.join('|')})\\b[^]*?}` - ) - - // 3. during transform: - // 3.1 files w/ const enum declaration: remove declaration - // 3.2 files using const enum: inject into esbuild define - /** - * @type {import('rollup').Plugin} - */ - const plugin = { - name: 'remove-const-enum', - transform(code, id) { - let s - - if (id in enumData.ranges) { - s = s || new MagicString(code) - for (const [start, end] of enumData.ranges[id]) { - s.remove(start, end) - } - } - - // check for const enum re-exports that must be removed - if (reExportsRE.test(code)) { - s = s || new MagicString(code) - const ast = parse(code, { - plugins: ['typescript'], - sourceType: 'module' - }) - for (const node of ast.program.body) { - if ( - node.type === 'ExportNamedDeclaration' && - node.exportKind !== 'type' && - node.source - ) { - for (let i = 0; i < node.specifiers.length; i++) { - const spec = node.specifiers[i] - if ( - spec.type === 'ExportSpecifier' && - spec.exportKind !== 'type' && - enumData.ids.includes(spec.local.name) - ) { - const next = node.specifiers[i + 1] - if (next) { - // @ts-ignore - s.remove(spec.start, next.start) - } else { - // last one - const prev = node.specifiers[i - 1] - // @ts-ignore - s.remove(prev ? prev.end : spec.start, spec.end) - } - } - } - } - } - } - - if (s) { - return { - code: s.toString(), - map: s.generateMap() - } - } - } - } - - return [plugin, enumData.defines] -} diff --git a/scripts/dev.js b/scripts/dev.js index ec5f9c674cd..e31216ca4ef 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -41,6 +41,7 @@ const relativeOutfile = relative(process.cwd(), outfile) // resolve externals // TODO this logic is largely duplicated from rollup.config.js +/** @type {string[]} */ let external = [] if (!inlineDeps) { // cjs & esm-bundler: external all deps @@ -80,7 +81,7 @@ if (!inlineDeps) { ] } } - +/** @type {Array} */ const plugins = [ { name: 'log-rebuild', diff --git a/scripts/inline-enums.js b/scripts/inline-enums.js new file mode 100644 index 00000000000..0835dd8859e --- /dev/null +++ b/scripts/inline-enums.js @@ -0,0 +1,279 @@ +// @ts-check + +/** + * We used const enums before, but it caused some issues: #1228, so we + * switched to regular enums. But we still want to keep the zero-cost benefit + * of const enums, and minimize the impact on bundle size as much as possible. + * + * Here we pre-process all the enums in the project and turn them into + * global replacements, and rewrite the original declarations as object literals. + * + * This file is expected to be executed with project root as cwd. + */ + +import * as assert from 'node:assert' +import { + existsSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync +} from 'node:fs' +import * as path from 'node:path' +import { parse } from '@babel/parser' +import { execaSync } from 'execa' +import MagicString from 'magic-string' + +/** + * @typedef {{ readonly name: string, readonly value: string | number }} EnumMember + * @typedef {{ readonly id: string, readonly range: readonly [start: number, end: number], readonly members: ReadonlyArray}} EnumDeclaration + * @typedef {{ readonly declarations: { readonly [file: string] : ReadonlyArray}, readonly defines: { readonly [ id_key: `${string}.${string}`]: string } }} EnumData + */ + +const ENUM_CACHE_PATH = 'temp/enum.json' + +/** + * @param {string} exp + * @returns {string | number} + */ +function evaluate(exp) { + return new Function(`return ${exp}`)() +} + +// this is called in the build script entry once +// so the data can be shared across concurrent Rollup processes +export function scanEnums() { + /** @type {{ [file: string]: EnumDeclaration[] }} */ + const declarations = Object.create(null) + /** @type {{ [id_key: `${string}.${string}`]: string; }} */ + const defines = Object.create(null) + + // 1. grep for files with exported enum + const { stdout } = execaSync('git', ['grep', `export enum`]) + const files = [...new Set(stdout.split('\n').map(line => line.split(':')[0]))] + + // 2. parse matched files to collect enum info + for (const relativeFile of files) { + const file = path.resolve(process.cwd(), relativeFile) + const content = readFileSync(file, 'utf-8') + const ast = parse(content, { + plugins: ['typescript'], + sourceType: 'module' + }) + + /** @type {Set} */ + const enumIds = new Set() + for (const node of ast.program.body) { + if ( + node.type === 'ExportNamedDeclaration' && + node.declaration && + node.declaration.type === 'TSEnumDeclaration' + ) { + const decl = node.declaration + const id = decl.id.name + if (enumIds.has(id)) { + throw new Error( + `not support declaration merging for enum ${id} in ${file}` + ) + } + enumIds.add(id) + /** @type {string | number | undefined} */ + let lastInitialized + /** @type {Array} */ + const members = [] + + for (let i = 0; i < decl.members.length; i++) { + const e = decl.members[i] + const key = e.id.type === 'Identifier' ? e.id.name : e.id.value + const fullKey = /** @type {const} */ (`${id}.${key}`) + const saveValue = (/** @type {string | number} */ value) => { + // We need allow same name enum in different file. + // For example: enum ErrorCodes exist in both @vue/compiler-core and @vue/runtime-core + // But not allow `ErrorCodes.__EXTEND_POINT__` appear in two same name enum + if (fullKey in defines) { + throw new Error(`name conflict for enum ${id} in ${file}`) + } + members.push({ + name: key, + value + }) + defines[fullKey] = JSON.stringify(value) + } + const init = e.initializer + if (init) { + /** @type {string | number} */ + let value + if ( + init.type === 'StringLiteral' || + init.type === 'NumericLiteral' + ) { + value = init.value + } + // e.g. 1 << 2 + else if (init.type === 'BinaryExpression') { + const resolveValue = ( + /** @type {import('@babel/types').Expression | import('@babel/types').PrivateName} */ node + ) => { + assert.ok(typeof node.start === 'number') + assert.ok(typeof node.end === 'number') + if ( + node.type === 'NumericLiteral' || + node.type === 'StringLiteral' + ) { + return node.value + } else if (node.type === 'MemberExpression') { + const exp = /** @type {`${string}.${string}`} */ ( + content.slice(node.start, node.end) + ) + if (!(exp in defines)) { + throw new Error( + `unhandled enum initialization expression ${exp} in ${file}` + ) + } + return defines[exp] + } else { + throw new Error( + `unhandled BinaryExpression operand type ${node.type} in ${file}` + ) + } + } + const exp = `${resolveValue(init.left)}${ + init.operator + }${resolveValue(init.right)}` + value = evaluate(exp) + } else if (init.type === 'UnaryExpression') { + if ( + init.argument.type === 'StringLiteral' || + init.argument.type === 'NumericLiteral' + ) { + const exp = `${init.operator}${init.argument.value}` + value = evaluate(exp) + } else { + throw new Error( + `unhandled UnaryExpression argument type ${init.argument.type} in ${file}` + ) + } + } else { + throw new Error( + `unhandled initializer type ${init.type} for ${fullKey} in ${file}` + ) + } + lastInitialized = value + saveValue(lastInitialized) + } else { + if (lastInitialized === undefined) { + // first initialized + lastInitialized = 0 + saveValue(lastInitialized) + } else if (typeof lastInitialized === 'number') { + lastInitialized++ + saveValue(lastInitialized) + } else { + // should not happen + throw new Error(`wrong enum initialization sequence in ${file}`) + } + } + } + + if (!(file in declarations)) { + declarations[file] = [] + } + assert.ok(typeof node.start === 'number') + assert.ok(typeof node.end === 'number') + declarations[file].push({ + id, + range: [node.start, node.end], + members + }) + } + } + } + + // 3. save cache + if (!existsSync('temp')) mkdirSync('temp') + + /** @type {EnumData} */ + const enumData = { + declarations, + defines + } + + writeFileSync(ENUM_CACHE_PATH, JSON.stringify(enumData)) + + return () => { + rmSync(ENUM_CACHE_PATH, { force: true }) + } +} + +/** + * @returns {[import('rollup').Plugin, Record]} + */ +export function inlineEnums() { + if (!existsSync(ENUM_CACHE_PATH)) { + throw new Error('enum cache needs to be initialized before creating plugin') + } + /** + * @type {EnumData} + */ + const enumData = JSON.parse(readFileSync(ENUM_CACHE_PATH, 'utf-8')) + + // 3. during transform: + // 3.1 files w/ enum declaration: rewrite declaration as object literal + // 3.2 files using enum: inject into esbuild define + /** + * @type {import('rollup').Plugin} + */ + const plugin = { + name: 'inline-enum', + transform(code, id) { + /** + * @type {MagicString | undefined} + */ + let s + + if (id in enumData.declarations) { + s = s || new MagicString(code) + for (const declaration of enumData.declarations[id]) { + const { + range: [start, end], + id, + members + } = declaration + s.update( + start, + end, + `export const ${id} = {${members + .flatMap(({ name, value }) => { + const forwardMapping = + JSON.stringify(name) + ': ' + JSON.stringify(value) + const reverseMapping = + JSON.stringify(value.toString()) + ': ' + JSON.stringify(name) + + // see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings + return typeof value === 'string' + ? [ + forwardMapping + // string enum members do not get a reverse mapping generated at all + ] + : [ + forwardMapping, + // other enum members should support enum reverse mapping + reverseMapping + ] + }) + .join(',\n')}}` + ) + } + } + + if (s) { + return { + code: s.toString(), + map: s.generateMap() + } + } + } + } + + return [plugin, enumData.defines] +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 954103c0f2f..28036b1bed9 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -10,6 +10,8 @@ "packages/runtime-test", "packages/template-explorer", "packages/sfc-playground", - "packages/dts-test" + "packages/dts-test", + "rollup.config.js", + "scripts/*" ] } diff --git a/tsconfig.json b/tsconfig.json index 5d7789b082c..0aad03ad6ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "useDefineForClassFields": false, "module": "esnext", "moduleResolution": "bundler", - "allowJs": false, + "allowJs": true, "strict": true, "noUnusedLocals": true, "experimentalDecorators": true, @@ -34,6 +34,8 @@ "packages/*/__tests__", "packages/dts-test", "packages/vue/jsx-runtime", - "scripts/setupVitest.ts" - ] + "scripts/*", + "rollup.*.js" + ], + "exclude": ["rollup.config.js", "scripts/*"] }