feat: use enum to replace const enum (#9261)

close #1228
This commit is contained in:
ZHAO Jin-Xiang 2023-11-29 12:24:50 +08:00 committed by GitHub
parent 47ea285be3
commit fff7b864f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 397 additions and 333 deletions

View File

@ -3,6 +3,15 @@
const DOMGlobals = ['window', 'document'] const DOMGlobals = ['window', 'document']
const NodeGlobals = ['module', 'require'] 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 = { module.exports = {
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
@ -16,6 +25,7 @@ module.exports = {
'no-restricted-syntax': [ 'no-restricted-syntax': [
'error', 'error',
banConstEnum,
// since we target ES2015 for baseline support, we need to forbid object // since we target ES2015 for baseline support, we need to forbid object
// rest spread usage in destructure as it compiles into a verbose helper. // rest spread usage in destructure as it compiles into a verbose helper.
'ObjectPattern > RestElement', 'ObjectPattern > RestElement',
@ -55,7 +65,7 @@ module.exports = {
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'], files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
rules: { rules: {
'no-restricted-globals': ['error', ...DOMGlobals], 'no-restricted-globals': ['error', ...DOMGlobals],
'no-restricted-syntax': 'off' 'no-restricted-syntax': ['error', banConstEnum]
} }
}, },
// Private package, browser only + no syntax restrictions // Private package, browser only + no syntax restrictions
@ -63,7 +73,7 @@ module.exports = {
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'], files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
rules: { rules: {
'no-restricted-globals': ['error', ...NodeGlobals], 'no-restricted-globals': ['error', ...NodeGlobals],
'no-restricted-syntax': 'off' 'no-restricted-syntax': ['error', banConstEnum]
} }
}, },
// JavaScript files // JavaScript files
@ -79,7 +89,7 @@ module.exports = {
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'], files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'],
rules: { rules: {
'no-restricted-globals': 'off', 'no-restricted-globals': 'off',
'no-restricted-syntax': 'off' 'no-restricted-syntax': ['error', banConstEnum]
} }
} }
] ]

View File

@ -66,7 +66,9 @@
"@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-replace": "^5.0.4",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@types/hash-sum": "^1.0.2", "@types/hash-sum": "^1.0.2",
"@types/minimist": "^1.2.5",
"@types/node": "^20.10.0", "@types/node": "^20.10.0",
"@types/semver": "^7.5.5",
"@typescript-eslint/parser": "^6.13.0", "@typescript-eslint/parser": "^6.13.0",
"@vitest/coverage-istanbul": "^0.34.6", "@vitest/coverage-istanbul": "^0.34.6",
"@vue/consolidate": "0.17.3", "@vue/consolidate": "0.17.3",
@ -75,6 +77,7 @@
"esbuild": "^0.19.5", "esbuild": "^0.19.5",
"esbuild-plugin-polyfill-node": "^0.3.0", "esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^8.54.0", "eslint": "^8.54.0",
"eslint-define-config": "^1.24.1",
"eslint-plugin-jest": "^27.6.0", "eslint-plugin-jest": "^27.6.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"execa": "^8.0.1", "execa": "^8.0.1",

View File

@ -19,13 +19,13 @@ import { ImportItem, TransformContext } from './transform'
// More namespaces can be declared by platform specific compilers. // More namespaces can be declared by platform specific compilers.
export type Namespace = number export type Namespace = number
export const enum Namespaces { export enum Namespaces {
HTML, HTML,
SVG, SVG,
MATH_ML MATH_ML
} }
export const enum NodeTypes { export enum NodeTypes {
ROOT, ROOT,
ELEMENT, ELEMENT,
TEXT, TEXT,
@ -59,7 +59,7 @@ export const enum NodeTypes {
JS_RETURN_STATEMENT JS_RETURN_STATEMENT
} }
export const enum ElementTypes { export enum ElementTypes {
ELEMENT, ELEMENT,
COMPONENT, COMPONENT,
SLOT, SLOT,
@ -214,7 +214,7 @@ export interface DirectiveNode extends Node {
* Higher levels implies lower levels. e.g. a node that can be stringified * Higher levels implies lower levels. e.g. a node that can be stringified
* can always be hoisted and skipped for patch. * can always be hoisted and skipped for patch.
*/ */
export const enum ConstantTypes { export enum ConstantTypes {
NOT_CONSTANT = 0, NOT_CONSTANT = 0,
CAN_SKIP_PATCH, CAN_SKIP_PATCH,
CAN_HOIST, CAN_HOIST,

View File

@ -69,7 +69,7 @@ export interface CodegenResult {
map?: RawSourceMap map?: RawSourceMap
} }
const enum NewlineType { enum NewlineType {
Start = 0, Start = 0,
End = -1, End = -1,
None = -2, None = -2,

View File

@ -13,7 +13,7 @@ export interface CompilerCompatOptions {
compatConfig?: CompilerCompatConfig compatConfig?: CompilerCompatConfig
} }
export const enum CompilerDeprecationTypes { export enum CompilerDeprecationTypes {
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT', COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC', COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER', COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',

View File

@ -37,7 +37,7 @@ export function createCompilerError<T extends number>(
return error return error
} }
export const enum ErrorCodes { export enum ErrorCodes {
// parse errors // parse errors
ABRUPT_CLOSING_OF_EMPTY_COMMENT, ABRUPT_CLOSING_OF_EMPTY_COMMENT,
CDATA_IN_HTML_CONTENT, CDATA_IN_HTML_CONTENT,

View File

@ -94,7 +94,7 @@ export type HoistTransform = (
parent: ParentNode parent: ParentNode
) => void ) => void
export const enum BindingTypes { export enum BindingTypes {
/** /**
* returned from data() * returned from data()
*/ */

View File

@ -38,13 +38,13 @@ import {
fromCodePoint fromCodePoint
} from 'entities/lib/decode.js' } from 'entities/lib/decode.js'
export const enum ParseMode { export enum ParseMode {
BASE, BASE,
HTML, HTML,
SFC SFC
} }
export const enum CharCodes { export enum CharCodes {
Tab = 0x9, // "\t" Tab = 0x9, // "\t"
NewLine = 0xa, // "\n" NewLine = 0xa, // "\n"
FormFeed = 0xc, // "\f" FormFeed = 0xc, // "\f"
@ -72,7 +72,6 @@ export const enum CharCodes {
UpperZ = 0x5a, // "Z" UpperZ = 0x5a, // "Z"
LowerZ = 0x7a, // "z" LowerZ = 0x7a, // "z"
LowerX = 0x78, // "x" LowerX = 0x78, // "x"
OpeningSquareBracket = 0x5b, // "["
LowerV = 0x76, // "v" LowerV = 0x76, // "v"
Dot = 0x2e, // "." Dot = 0x2e, // "."
Colon = 0x3a, // ":" Colon = 0x3a, // ":"
@ -85,7 +84,7 @@ const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{"
const defaultDelimitersClose = new Uint8Array([125, 125]) // "}}" const defaultDelimitersClose = new Uint8Array([125, 125]) // "}}"
/** All the states the tokenizer can be in. */ /** All the states the tokenizer can be in. */
export const enum State { export enum State {
Text = 1, Text = 1,
// interpolation // interpolation
@ -820,7 +819,7 @@ export default class Tokenizer {
} }
} }
private stateBeforeDeclaration(c: number): void { private stateBeforeDeclaration(c: number): void {
if (c === CharCodes.OpeningSquareBracket) { if (c === CharCodes.LeftSqaure) {
this.state = State.CDATASequence this.state = State.CDATASequence
this.sequenceIndex = 0 this.sequenceIndex = 0
} else { } else {

View File

@ -65,7 +65,7 @@ const nonIdentifierRE = /^\d|[^\$\w]/
export const isSimpleIdentifier = (name: string): boolean => export const isSimpleIdentifier = (name: string): boolean =>
!nonIdentifierRE.test(name) !nonIdentifierRE.test(name)
const enum MemberExpLexState { enum MemberExpLexState {
inMemberExp, inMemberExp,
inBrackets, inBrackets,
inParens, inParens,

View File

@ -20,7 +20,7 @@ export function createDOMCompilerError(
) as DOMCompilerError ) as DOMCompilerError
} }
export const enum DOMErrorCodes { export enum DOMErrorCodes {
X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */, X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
X_V_HTML_WITH_CHILDREN, X_V_HTML_WITH_CHILDREN,
X_V_TEXT_NO_EXPRESSION, X_V_TEXT_NO_EXPRESSION,
@ -36,7 +36,7 @@ export const enum DOMErrorCodes {
} }
if (__TEST__) { 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 // file, so we have to manually keep them in sync. this check ensures it
// errors out if there are collisions. // errors out if there are collisions.
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) { if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {

View File

@ -33,7 +33,7 @@ import {
isBooleanAttr isBooleanAttr
} from '@vue/shared' } from '@vue/shared'
export const enum StringifyThresholds { export enum StringifyThresholds {
ELEMENT_WITH_BINDING_COUNT = 5, ELEMENT_WITH_BINDING_COUNT = 5,
NODE_COUNT = 20 NODE_COUNT = 20
} }

View File

@ -70,7 +70,7 @@ export function parseCssVars(sfc: SFCDescriptor): string[] {
return vars return vars
} }
const enum LexerState { enum LexerState {
inParens, inParens,
inSingleQuoteString, inSingleQuoteString,
inDoubleQuoteString inDoubleQuoteString

View File

@ -16,14 +16,14 @@ export function createSSRCompilerError(
return createCompilerError(code, loc, SSRErrorMessages) as SSRCompilerError 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_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */,
X_SSR_NO_TELEPORT_TARGET, X_SSR_NO_TELEPORT_TARGET,
X_SSR_INVALID_AST_NODE X_SSR_INVALID_AST_NODE
} }
if (__TEST__) { 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 // file, so we have to manually keep them in sync. this check ensures it
// errors out if there are collisions. // errors out if there are collisions.
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) { if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {

View File

@ -1,20 +1,20 @@
// using literal strings instead of numbers so that it's easier to inspect // using literal strings instead of numbers so that it's easier to inspect
// debugger events // debugger events
export const enum TrackOpTypes { export enum TrackOpTypes {
GET = 'get', GET = 'get',
HAS = 'has', HAS = 'has',
ITERATE = 'iterate' ITERATE = 'iterate'
} }
export const enum TriggerOpTypes { export enum TriggerOpTypes {
SET = 'set', SET = 'set',
ADD = 'add', ADD = 'add',
DELETE = 'delete', DELETE = 'delete',
CLEAR = 'clear' CLEAR = 'clear'
} }
export const enum ReactiveFlags { export enum ReactiveFlags {
SKIP = '__v_skip', SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive', IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly', IS_READONLY = '__v_isReadonly',
@ -22,7 +22,7 @@ export const enum ReactiveFlags {
RAW = '__v_raw' RAW = '__v_raw'
} }
export const enum DirtyLevels { export enum DirtyLevels {
NotDirty = 0, NotDirty = 0,
ComputedValueMaybeDirty = 1, ComputedValueMaybeDirty = 1,
ComputedValueDirty = 2, ComputedValueDirty = 2,

View File

@ -68,8 +68,4 @@ export {
getCurrentScope, getCurrentScope,
onScopeDispose onScopeDispose
} from './effectScope' } from './effectScope'
export { export { TrackOpTypes, TriggerOpTypes, ReactiveFlags } from './constants'
TrackOpTypes /* @remove */,
TriggerOpTypes /* @remove */,
ReactiveFlags /* @remove */
} from './constants'

View File

@ -27,7 +27,7 @@ export const shallowReactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>() export const readonlyMap = new WeakMap<Target, any>()
export const shallowReadonlyMap = new WeakMap<Target, any>() export const shallowReadonlyMap = new WeakMap<Target, any>()
const enum TargetType { enum TargetType {
INVALID = 0, INVALID = 0,
COMMON = 1, COMMON = 1,
COLLECTION = 2 COLLECTION = 2

View File

@ -10,7 +10,7 @@ import {
} from '../component' } from '../component'
import { warn } from '../warning' import { warn } from '../warning'
export const enum DeprecationTypes { export enum DeprecationTypes {
GLOBAL_MOUNT = 'GLOBAL_MOUNT', GLOBAL_MOUNT = 'GLOBAL_MOUNT',
GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER', GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER',
GLOBAL_EXTEND = 'GLOBAL_EXTEND', GLOBAL_EXTEND = 'GLOBAL_EXTEND',

View File

@ -586,7 +586,7 @@ export type OptionTypesType<
Defaults: Defaults Defaults: Defaults
} }
const enum OptionTypes { enum OptionTypes {
PROPS = 'Props', PROPS = 'Props',
DATA = 'Data', DATA = 'Data',
COMPUTED = 'Computed', COMPUTED = 'Computed',

View File

@ -164,7 +164,7 @@ export type ExtractPublicPropTypes<O> = {
[K in keyof Pick<O, PublicOptionalKeys<O>>]?: InferPropType<O[K]> [K in keyof Pick<O, PublicOptionalKeys<O>>]?: InferPropType<O[K]>
} }
const enum BooleanFlags { enum BooleanFlags {
shouldCast, shouldCast,
shouldCastTrue shouldCastTrue
} }

View File

@ -281,7 +281,7 @@ if (__COMPAT__) {
installCompatInstanceProperties(publicPropertiesMap) installCompatInstanceProperties(publicPropertiesMap)
} }
const enum AccessTypes { enum AccessTypes {
OTHER, OTHER,
SETUP, SETUP,
DATA, DATA,

View File

@ -269,7 +269,7 @@ export const TeleportImpl = {
hydrate: hydrateTeleport hydrate: hydrateTeleport
} }
export const enum TeleportMoveTypes { export enum TeleportMoveTypes {
TARGET_CHANGE, TARGET_CHANGE,
TOGGLE, // enable / disable TOGGLE, // enable / disable
REORDER // moved in the main view REORDER // moved in the main view

View File

@ -10,7 +10,7 @@ interface AppRecord {
types: Record<string, string | Symbol> types: Record<string, string | Symbol>
} }
const enum DevtoolsHooks { enum DevtoolsHooks {
APP_INIT = 'app:init', APP_INIT = 'app:init',
APP_UNMOUNT = 'app:unmount', APP_UNMOUNT = 'app:unmount',
COMPONENT_UPDATED = 'component:updated', COMPONENT_UPDATED = 'component:updated',

View File

@ -1,4 +1,4 @@
export const enum LifecycleHooks { export enum LifecycleHooks {
BEFORE_CREATE = 'bc', BEFORE_CREATE = 'bc',
CREATED = 'c', CREATED = 'c',
BEFORE_MOUNT = 'bm', BEFORE_MOUNT = 'bm',

View File

@ -6,7 +6,7 @@ import { LifecycleHooks } from './enums'
// contexts where user provided function may be executed, in addition to // contexts where user provided function may be executed, in addition to
// lifecycle hooks. // lifecycle hooks.
export const enum ErrorCodes { export enum ErrorCodes {
SETUP_FUNCTION, SETUP_FUNCTION,
RENDER_FUNCTION, RENDER_FUNCTION,
WATCH_GETTER, WATCH_GETTER,

View File

@ -30,7 +30,7 @@ export type RootHydrateFunction = (
container: (Element | ShadowRoot) & { _vnode?: VNode } container: (Element | ShadowRoot) & { _vnode?: VNode }
) => void ) => void
const enum DOMNodeTypes { enum DOMNodeTypes {
ELEMENT = 1, ELEMENT = 1,
TEXT = 3, TEXT = 3,
COMMENT = 8 COMMENT = 8

View File

@ -361,7 +361,7 @@ export const ssrUtils = (__SSR__ ? _ssrUtils : null) as typeof _ssrUtils
// 2.x COMPAT ------------------------------------------------------------------ // 2.x COMPAT ------------------------------------------------------------------
export { DeprecationTypes } from './compat/compatConfig' import { DeprecationTypes as _DeprecationTypes } from './compat/compatConfig'
export type { CompatVue } from './compat/global' export type { CompatVue } from './compat/global'
export type { LegacyConfig } from './compat/globalConfig' export type { LegacyConfig } from './compat/globalConfig'
@ -393,3 +393,7 @@ const _compatUtils = {
export const compatUtils = ( export const compatUtils = (
__COMPAT__ ? _compatUtils : null __COMPAT__ ? _compatUtils : null
) as typeof _compatUtils ) as typeof _compatUtils
export const DeprecationTypes = (
__COMPAT__ ? _DeprecationTypes : null
) as typeof _DeprecationTypes

View File

@ -265,7 +265,7 @@ export type SetupRenderEffectFn = (
optimized: boolean optimized: boolean
) => void ) => void
export const enum MoveType { export enum MoveType {
ENTER, ENTER,
LEAVE, LEAVE,
REORDER REORDER

View File

@ -1,12 +1,12 @@
import { markRaw } from '@vue/reactivity' import { markRaw } from '@vue/reactivity'
export const enum TestNodeTypes { export enum TestNodeTypes {
TEXT = 'text', TEXT = 'text',
ELEMENT = 'element', ELEMENT = 'element',
COMMENT = 'comment' COMMENT = 'comment'
} }
export const enum NodeOpTypes { export enum NodeOpTypes {
CREATE = 'create', CREATE = 'create',
INSERT = 'insert', INSERT = 'insert',
REMOVE = 'remove', REMOVE = 'remove',

View File

@ -16,7 +16,7 @@
* Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the * Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the
* flags are handled during diff. * flags are handled during diff.
*/ */
export const enum PatchFlags { export enum PatchFlags {
/** /**
* Indicates an element with dynamic textContent (children fast path) * Indicates an element with dynamic textContent (children fast path)
*/ */

View File

@ -1,4 +1,4 @@
export const enum ShapeFlags { export enum ShapeFlags {
ELEMENT = 1, ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1, FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2, STATEFUL_COMPONENT = 1 << 2,

View File

@ -1,4 +1,4 @@
export const enum SlotFlags { export enum SlotFlags {
/** /**
* Stable slots that only reference slot props or context state. The slot * 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 * can fully capture its own dependencies so when passed down the parent won't

View File

@ -10,7 +10,7 @@ import {
export declare const RefType: unique symbol export declare const RefType: unique symbol
export declare const enum RefTypes { export declare enum RefTypes {
Ref = 1, Ref = 1,
ComputedRef = 2, ComputedRef = 2,
WritableComputedRef = 3 WritableComputedRef = 3

View File

@ -35,9 +35,15 @@ importers:
'@types/hash-sum': '@types/hash-sum':
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2 version: 1.0.2
'@types/minimist':
specifier: ^1.2.5
version: 1.2.5
'@types/node': '@types/node':
specifier: ^20.10.0 specifier: ^20.10.0
version: 20.10.0 version: 20.10.0
'@types/semver':
specifier: ^7.5.5
version: 7.5.5
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^6.13.0 specifier: ^6.13.0
version: 6.13.0(eslint@8.54.0)(typescript@5.2.2) version: 6.13.0(eslint@8.54.0)(typescript@5.2.2)
@ -62,6 +68,9 @@ importers:
eslint: eslint:
specifier: ^8.54.0 specifier: ^8.54.0
version: 8.54.0 version: 8.54.0
eslint-define-config:
specifier: ^1.24.1
version: 1.24.1
eslint-plugin-jest: eslint-plugin-jest:
specifier: ^27.6.0 specifier: ^27.6.0
version: 27.6.0(eslint@8.54.0)(typescript@5.2.2) version: 27.6.0(eslint@8.54.0)(typescript@5.2.2)
@ -1514,6 +1523,10 @@ packages:
resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==}
dev: true dev: true
/@types/minimist@1.2.5:
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
dev: true
/@types/node@20.10.0: /@types/node@20.10.0:
resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==} resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==}
dependencies: dependencies:
@ -1528,8 +1541,8 @@ packages:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: true dev: true
/@types/semver@7.5.4: /@types/semver@7.5.5:
resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==}
dev: true dev: true
/@types/yauzl@2.10.2: /@types/yauzl@2.10.2:
@ -1637,7 +1650,7 @@ packages:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0)
'@types/json-schema': 7.0.14 '@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/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0 '@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2)
@ -2842,6 +2855,11 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true 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): /eslint-plugin-jest@27.6.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==} resolution: {integrity: sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}

View File

@ -12,7 +12,7 @@ import terser from '@rollup/plugin-terser'
import esbuild from 'rollup-plugin-esbuild' import esbuild from 'rollup-plugin-esbuild'
import alias from '@rollup/plugin-alias' import alias from '@rollup/plugin-alias'
import { entries } from './scripts/aliases.js' import { entries } from './scripts/aliases.js'
import { constEnum } from './scripts/const-enum.js' import { inlineEnums } from './scripts/inline-enums.js'
if (!process.env.TARGET) { if (!process.env.TARGET) {
throw new Error('TARGET package must be specified via --environment flag.') 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 packageOptions = pkg.buildOptions || {}
const name = packageOptions.filename || path.basename(packageDir) const name = packageOptions.filename || path.basename(packageDir)
const [enumPlugin, enumDefines] = constEnum() const [enumPlugin, enumDefines] = inlineEnums()
const outputConfigs = { const outputConfigs = {
'esm-bundler': { 'esm-bundler': {

View File

@ -17,26 +17,29 @@ const targetPackages = targets
? packages.filter(pkg => targets.includes(pkg)) ? packages.filter(pkg => targets.includes(pkg))
: packages : packages
export default targetPackages.map(pkg => { export default targetPackages.map(
return { /** @returns {import('rollup').RollupOptions} */
input: `./temp/packages/${pkg}/src/index.d.ts`, pkg => {
output: { return {
file: `packages/${pkg}/dist/${pkg}.d.ts`, input: `./temp/packages/${pkg}/src/index.d.ts`,
format: 'es' output: {
}, file: `packages/${pkg}/dist/${pkg}.d.ts`,
plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])], format: 'es'
onwarn(warning, warn) { },
// during dts rollup, everything is externalized by default plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])],
if ( onwarn(warning, warn) {
warning.code === 'UNRESOLVED_IMPORT' && // during dts rollup, everything is externalized by default
!warning.exporter.startsWith('.') if (
) { warning.code === 'UNRESOLVED_IMPORT' &&
return !warning.exporter?.startsWith('.')
) {
return
}
warn(warning)
} }
warn(warning)
} }
} }
}) )
/** /**
* Patch the dts generated by rollup-plugin-dts * 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 * otherwise it gets weird in vitepress `defineComponent` call with
* "the inferred type cannot be named without a reference" * "the inferred type cannot be named without a reference"
* 2. Append custom augmentations (jsx, macros) * 2. Append custom augmentations (jsx, macros)
*
* @param {string} pkg
* @returns {import('rollup').Plugin} * @returns {import('rollup').Plugin}
*/ */
function patchTypes(pkg) { function patchTypes(pkg) {

View File

@ -26,7 +26,7 @@ import { execa, execaSync } from 'execa'
import { cpus } from 'node:os' import { cpus } from 'node:os'
import { createRequire } from 'node:module' import { createRequire } from 'node:module'
import { targets as allTargets, fuzzyMatchTarget } from './utils.js' 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' import prettyBytes from 'pretty-bytes'
const require = createRequire(import.meta.url) const require = createRequire(import.meta.url)

View File

@ -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<string, [number, number][]>, defines: Record<string, string>, 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<string, string>]}
*/
export function constEnum() {
if (!existsSync(ENUM_CACHE_PATH)) {
throw new Error('enum cache needs to be initialized before creating plugin')
}
/**
* @type {{ ranges: Record<string, [number, number][]>, defines: Record<string, string>, 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]
}

View File

@ -41,6 +41,7 @@ const relativeOutfile = relative(process.cwd(), outfile)
// resolve externals // resolve externals
// TODO this logic is largely duplicated from rollup.config.js // TODO this logic is largely duplicated from rollup.config.js
/** @type {string[]} */
let external = [] let external = []
if (!inlineDeps) { if (!inlineDeps) {
// cjs & esm-bundler: external all deps // cjs & esm-bundler: external all deps
@ -80,7 +81,7 @@ if (!inlineDeps) {
] ]
} }
} }
/** @type {Array<import('esbuild').Plugin>} */
const plugins = [ const plugins = [
{ {
name: 'log-rebuild', name: 'log-rebuild',

279
scripts/inline-enums.js Normal file
View File

@ -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<EnumMember>}} EnumDeclaration
* @typedef {{ readonly declarations: { readonly [file: string] : ReadonlyArray<EnumDeclaration>}, 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<string>} */
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<EnumMember>} */
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<string, string>]}
*/
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]
}

View File

@ -10,6 +10,8 @@
"packages/runtime-test", "packages/runtime-test",
"packages/template-explorer", "packages/template-explorer",
"packages/sfc-playground", "packages/sfc-playground",
"packages/dts-test" "packages/dts-test",
"rollup.config.js",
"scripts/*"
] ]
} }

View File

@ -8,7 +8,7 @@
"useDefineForClassFields": false, "useDefineForClassFields": false,
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowJs": false, "allowJs": true,
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"experimentalDecorators": true, "experimentalDecorators": true,
@ -34,6 +34,8 @@
"packages/*/__tests__", "packages/*/__tests__",
"packages/dts-test", "packages/dts-test",
"packages/vue/jsx-runtime", "packages/vue/jsx-runtime",
"scripts/setupVitest.ts" "scripts/*",
] "rollup.*.js"
],
"exclude": ["rollup.config.js", "scripts/*"]
} }