init (graduate from prototype)

This commit is contained in:
Evan You 2018-09-19 11:35:38 -04:00
commit 3401f6b460
63 changed files with 8372 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
dist
.DS_Store
node_modules
explorations
TODOs.md

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
semi: false
singleQuote: true
printWidth: 80

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

6
lerna.json Normal file
View File

@ -0,0 +1,6 @@
{
"packages": [
"packages/*"
],
"version": "3.0.0-alpha.1"
}

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"lint": "prettier --write --parser typescript 'packages/*/src/**/*.ts'"
},
"devDependencies": {
"chalk": "^2.4.1",
"dts-bundle": "^0.7.3",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"lerna": "^3.4.0",
"minimist": "^1.2.0",
"prettier": "^1.14.2",
"rollup": "^0.65.0",
"rollup-plugin-alias": "^1.4.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-terser": "^2.0.2",
"rollup-plugin-typescript2": "^0.17.0",
"typescript": "^3.0.3"
}
}

3
packages/core/.npmignore Normal file
View File

@ -0,0 +1,3 @@
__tests__/
__mocks__/
dist/packages

3
packages/core/README.md Normal file
View File

@ -0,0 +1,3 @@
# @vue/core
> This package is published only for typing and building custom renderers. It is NOT meant to be used in applications.

7
packages/core/index.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/core.cjs.prod.js')
} else {
module.exports = require('./dist/core.cjs.js')
}

View File

@ -0,0 +1,24 @@
{
"name": "@vue/core",
"version": "3.0.0-alpha.1",
"description": "@vue/core",
"main": "index.js",
"module": "dist/core.esm.js",
"typings": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/core#readme",
"dependencies": {
"@vue/observer": "3.0.0-alpha.1"
}
}

View File

@ -0,0 +1,171 @@
import { EMPTY_OBJ } from './utils'
import { VNode, Slots, RenderNode, RenderFragment } from './vdom'
import {
Data,
RenderFunction,
ComponentOptions,
ComponentPropsOptions
} from './componentOptions'
import { setupWatcher } from './componentWatch'
import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
type Flatten<T> = { [K in keyof T]: T[K] }
export interface ComponentClass extends Flatten<typeof Component> {
new <D = Data, P = Data>(): MountedComponent<D, P> & D & P
}
export interface FunctionalComponent<P = Data> extends RenderFunction<P> {
pure?: boolean
props?: ComponentPropsOptions<P>
}
// this interface is merged with the class type
// to represent a mounted component
export interface MountedComponent<D = Data, P = Data> extends Component {
$vnode: VNode
$data: D
$props: P
$computed: Data
$slots: Slots
$root: MountedComponent
$children: MountedComponent[]
$options: ComponentOptions<D, P>
render: RenderFunction<P>
data?(): Partial<D>
beforeCreate?(): void
created?(): void
beforeMount?(): void
mounted?(): void
beforeUpdate?(e: DebuggerEvent): void
updated?(): void
beforeDestroy?(): void
destroyed?(): void
_updateHandle: Autorun
$forceUpdate: () => void
_self: MountedComponent<D, P> // on proxies only
}
export class Component {
public static options?: ComponentOptions
public get $el(): RenderNode | RenderFragment | null {
return this.$vnode && this.$vnode.el
}
public $vnode: VNode | null = null
public $parentVNode: VNode | null = null
public $data: Data | null = null
public $props: Data | null = null
public $computed: Data | null = null
public $slots: Slots | null = null
public $root: MountedComponent | null = null
public $parent: MountedComponent | null = null
public $children: MountedComponent[] = []
public $options: any
public $proxy: any = null
public $forceUpdate: (() => void) | null = null
public _rawData: Data | null = null
public _computedGetters: Record<string, ComputedGetter> | null = null
public _watchHandles: Set<Autorun> | null = null
public _mounted: boolean = false
public _destroyed: boolean = false
public _events: { [event: string]: Function[] | null } | null = null
public _updateHandle: Autorun | null = null
public _revokeProxy: () => void
public _isVue: boolean = true
constructor(options?: ComponentOptions) {
this.$options = options || (this.constructor as any).options || EMPTY_OBJ
// root instance
if (options !== void 0) {
// mount this
}
}
$watch(
this: MountedComponent,
keyOrFn: string | (() => any),
cb: () => void
) {
return setupWatcher(this, keyOrFn, cb)
}
// eventEmitter interface
$on(event: string, fn: Function): Component {
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$on(event[i], fn)
}
} else {
const events = this._events || (this._events = Object.create(null))
;(events[event] || (events[event] = [])).push(fn)
}
return this
}
$once(event: string, fn: Function): Component {
const onceFn = (...args: any[]) => {
this.$off(event, onceFn)
fn.apply(this, args)
}
;(onceFn as any).fn = fn
return this.$on(event, onceFn)
}
$off(event?: string, fn?: Function) {
if (this._events) {
if (!event && !fn) {
this._events = null
} else if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$off(event[i], fn)
}
} else if (!fn) {
this._events[event as string] = null
} else {
const fns = this._events[event as string]
if (fns) {
for (let i = 0; i < fns.length; i++) {
const f = fns[i]
if (fn === f || fn === (f as any).fn) {
fns.splice(i, 1)
break
}
}
}
}
}
return this
}
$emit(this: MountedComponent, name: string, ...payload: any[]) {
const parentListener =
this.$props['on' + name] || this.$props['on' + name.toLowerCase()]
if (parentListener) {
invokeListeners(parentListener, payload)
}
if (this._events) {
const handlers = this._events[name]
if (handlers) {
invokeListeners(handlers, payload)
}
}
return this
}
}
function invokeListeners(value: Function | Function[], payload: any[]) {
// TODO handle error
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i](...payload)
}
} else {
value(...payload)
}
}

View File

@ -0,0 +1,65 @@
import { EMPTY_OBJ } from './utils'
import { computed, ComputedGetter } from '@vue/observer'
import { Component, ComponentClass } from './component'
import { ComponentComputedOptions } from './componentOptions'
const extractionCache: WeakMap<
ComponentClass,
ComponentComputedOptions
> = new WeakMap()
export function getComputedOptions(
comp: ComponentClass
): ComponentComputedOptions {
let computedOptions = extractionCache.get(comp)
if (computedOptions) {
return computedOptions
}
computedOptions = {}
const descriptors = Object.getOwnPropertyDescriptors(comp.prototype as any)
for (const key in descriptors) {
const d = descriptors[key]
if (d.get) {
computedOptions[key] = d.get
// there's no need to do anything for the setter
// as it's already defined on the prototype
}
}
return computedOptions
}
export function initializeComputed(
instance: Component,
computedOptions: ComponentComputedOptions | undefined
) {
if (!computedOptions) {
instance.$computed = EMPTY_OBJ
return
}
const handles: Record<
string,
ComputedGetter
> = (instance._computedGetters = {})
const proxy = instance.$proxy
for (const key in computedOptions) {
handles[key] = computed(computedOptions[key], proxy)
}
instance.$computed = new Proxy(
{},
{
get(_, key: any) {
return handles[key]()
}
// TODO should be readonly
}
)
}
export function teardownComputed(instance: Component) {
const handles = instance._computedGetters
if (handles !== null) {
for (const key in handles) {
handles[key].stop()
}
}
}

View File

@ -0,0 +1,51 @@
import { Slots } from './vdom'
import { MountedComponent } from './component'
export type Data = Record<string, any>
export interface RenderFunction<P = Data> {
(props: P, slots: Slots): any
}
export interface ComponentOptions<D = Data, P = Data> {
data?: () => Partial<D>
props?: ComponentPropsOptions<P>
computed?: ComponentComputedOptions<D, P>
watch?: ComponentWatchOptions<D, P>
render?: RenderFunction<P>
// TODO other options
readonly [key: string]: any
}
export type ComponentPropsOptions<P = Data> = {
[K in keyof P]: PropValidator<P[K]>
}
export type NormalizedPropsOptions<P = Data> = {
[K in keyof P]: PropOptions<P[K]>
}
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
export type PropType<T> = Prop<T> | Prop<T>[]
export type PropValidator<T> = PropOptions<T> | PropType<T>
export interface PropOptions<T = any> {
type?: PropType<T>
required?: boolean
default?: T | null | undefined | (() => T | null | undefined)
validator?(value: T): boolean
}
export interface ComponentComputedOptions<D = Data, P = Data> {
[key: string]: (this: MountedComponent<D, P> & D & P, c: any) => any
}
export interface ComponentWatchOptions<D = Data, P = Data> {
[key: string]: (
this: MountedComponent<D, P> & D & P,
oldValue: any,
newValue: any
) => void
}

View File

@ -0,0 +1,103 @@
import { EMPTY_OBJ, isReservedProp } from './utils'
import { Component, ComponentClass, MountedComponent } from './component'
import { immutable, unwrap, lock, unlock } from '@vue/observer'
import {
Data,
ComponentPropsOptions,
NormalizedPropsOptions,
PropValidator,
PropOptions
} from './componentOptions'
export function initializeProps(instance: Component, props: Data | null) {
instance.$props = immutable(props || {})
}
export function updateProps(instance: MountedComponent, nextProps: Data) {
// instance.$props is an observable that should not be replaced.
// instead, we mutate it to match latest props, which will trigger updates
// if any value has changed.
if (nextProps != null) {
const props = instance.$props
const rawProps = unwrap(props)
// unlock to temporarily allow mutatiing props
unlock()
for (const key in rawProps) {
if (!nextProps.hasOwnProperty(key)) {
delete props[key]
}
}
for (const key in nextProps) {
props[key] = nextProps[key]
}
lock()
}
}
// This is called for every component vnode created. This also means the data
// on every component vnode is guarunteed to be a fresh object.
export function normalizeComponentProps(
raw: any,
options: ComponentPropsOptions,
Component: ComponentClass
): Data {
if (!raw) {
return EMPTY_OBJ
}
const res: Data = {}
const normalizedOptions = options && normalizePropsOptions(options)
for (const key in raw) {
if (isReservedProp(key)) {
continue
}
if (__DEV__ && normalizedOptions != null) {
validateProp(key, raw[key], normalizedOptions[key], Component)
} else {
res[key] = raw[key]
}
}
// set default values
if (normalizedOptions != null) {
for (const key in normalizedOptions) {
if (res[key] === void 0) {
const opt = normalizedOptions[key]
if (opt != null && opt.hasOwnProperty('default')) {
const defaultValue = opt.default
res[key] =
typeof defaultValue === 'function' ? defaultValue() : defaultValue
}
}
}
}
return res
}
const normalizeCache: WeakMap<
ComponentPropsOptions,
NormalizedPropsOptions
> = new WeakMap()
function normalizePropsOptions(
raw: ComponentPropsOptions
): NormalizedPropsOptions {
let cached = normalizeCache.get(raw)
if (cached) {
return cached
}
const normalized: NormalizedPropsOptions = {}
for (const key in raw) {
const opt = raw[key]
normalized[key] =
typeof opt === 'function' ? { type: opt } : (opt as PropOptions)
}
normalizeCache.set(raw, normalized)
return normalized
}
function validateProp(
key: string,
value: any,
validator: PropValidator<any>,
Component: ComponentClass
) {
// TODO
}

View File

@ -0,0 +1,82 @@
import { Component, MountedComponent } from './component'
const bindCache = new WeakMap()
function getBoundMethod(fn: Function, target: any, receiver: any): Function {
let boundMethodsForTarget = bindCache.get(target)
if (boundMethodsForTarget === void 0) {
bindCache.set(target, (boundMethodsForTarget = new Map()))
}
let boundFn = boundMethodsForTarget.get(fn)
if (boundFn === void 0) {
boundMethodsForTarget.set(fn, (boundFn = fn.bind(receiver)))
}
return boundFn
}
const renderProxyHandlers = {
get(target: MountedComponent, key: string, receiver: any) {
if (key === '_self') {
return target
} else if (
target._rawData !== null &&
target._rawData.hasOwnProperty(key)
) {
// data
return target.$data[key]
} else if (
target.$options.props != null &&
target.$options.props.hasOwnProperty(key)
) {
// props are only proxied if declared
return target.$props[key]
} else if (
target._computedGetters !== null &&
target._computedGetters.hasOwnProperty(key)
) {
// computed
return target._computedGetters[key]()
} else {
if (__DEV__ && !(key in target)) {
// TODO warn non-present property
}
const value = Reflect.get(target, key, receiver)
if (typeof value === 'function') {
// auto bind
return getBoundMethod(value, target, receiver)
} else {
return value
}
}
},
set(
target: MountedComponent,
key: string,
value: any,
receiver: any
): boolean {
if (__DEV__) {
if (typeof key === 'string' && key[0] === '$') {
// TODO warn setting immutable properties
return false
}
if (
target.$options.props != null &&
target.$options.props.hasOwnProperty(key)
) {
// TODO warn props are immutable
return false
}
}
if (target._rawData !== null && target._rawData.hasOwnProperty(key)) {
target.$data[key] = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
}
}
export function createRenderProxy(instance: Component): MountedComponent {
return new Proxy(instance, renderProxyHandlers) as MountedComponent
}

View File

@ -0,0 +1,12 @@
import { EMPTY_OBJ } from './utils'
import { MountedComponent } from './component'
import { observable } from '@vue/observer'
export function initializeState(instance: MountedComponent) {
if (instance.data) {
instance._rawData = instance.data()
instance.$data = observable(instance._rawData)
} else {
instance.$data = EMPTY_OBJ
}
}

View File

@ -0,0 +1,180 @@
import { VNodeFlags } from './flags'
import { EMPTY_OBJ } from './utils'
import { VNode, createFragment } from './vdom'
import { Component, MountedComponent, ComponentClass } from './component'
import { createTextVNode, cloneVNode } from './vdom'
import { initializeState } from './componentState'
import { initializeProps } from './componentProps'
import {
initializeComputed,
getComputedOptions,
teardownComputed
} from './componentComputed'
import { initializeWatch, teardownWatch } from './componentWatch'
import { Data, ComponentOptions } from './componentOptions'
import { createRenderProxy } from './componentProxy'
export function createComponentInstance(
vnode: VNode,
Component: ComponentClass,
parentComponent: MountedComponent | null
): MountedComponent {
const instance = (vnode.children = new Component()) as MountedComponent
instance.$parentVNode = vnode
// renderProxy
const proxy = (instance.$proxy = createRenderProxy(instance))
// pointer management
if (parentComponent) {
instance.$parent = parentComponent.$proxy
instance.$root = parentComponent.$root
parentComponent.$children.push(proxy)
} else {
instance.$root = proxy
}
// lifecycle
if (instance.beforeCreate) {
instance.beforeCreate.call(proxy)
}
// TODO provide/inject
initializeProps(instance, vnode.data)
initializeState(instance)
initializeComputed(instance, getComputedOptions(Component))
initializeWatch(instance, instance.$options.watch)
instance.$slots = vnode.slots || EMPTY_OBJ
if (instance.created) {
instance.created.call(proxy)
}
return instance as MountedComponent
}
export function renderInstanceRoot(instance: MountedComponent) {
// TODO handle render error
return normalizeComponentRoot(
instance.render.call(instance.$proxy, instance.$props, instance.$slots),
instance.$parentVNode
)
}
export function teardownComponentInstance(instance: MountedComponent) {
const parentComponent = instance.$parent && instance.$parent._self
if (parentComponent && !parentComponent._destroyed) {
parentComponent.$children.splice(
parentComponent.$children.indexOf(instance.$proxy),
1
)
}
teardownComputed(instance)
teardownWatch(instance)
}
export function normalizeComponentRoot(
vnode: any,
componentVNode: VNode | null
): VNode {
if (vnode == null) {
vnode = createTextVNode('')
} else if (typeof vnode !== 'object') {
vnode = createTextVNode(vnode + '')
} else if (Array.isArray(vnode)) {
vnode = createFragment(vnode)
} else {
const { flags } = vnode
// parentVNode data merge down
if (
componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
const parentData = componentVNode.data || EMPTY_OBJ
const childData = vnode.data || EMPTY_OBJ
let extraData: any = null
for (const key in parentData) {
// class/style bindings on parentVNode are merged down to child
// component root.
if (key === 'class') {
;(extraData || (extraData = {})).class = childData.class
? [].concat(childData.class, parentData.class)
: parentData.class
} else if (key === 'style') {
;(extraData || (extraData = {})).style = childData.style
? [].concat(childData.style, parentData.style)
: parentData.style
} else if (key.startsWith('nativeOn')) {
// nativeOn* handlers are merged down to child root as native listeners
const event = 'on' + key.slice(8)
;(extraData || (extraData = {}))[event] = childData.event
? [].concat(childData.event, parentData[key])
: parentData[key]
}
}
if (extraData) {
vnode = cloneVNode(vnode, extraData)
}
if (vnode.el) {
vnode = cloneVNode(vnode)
}
if (flags & VNodeFlags.COMPONENT) {
vnode.parentVNode = componentVNode
}
} else if (vnode.el) {
vnode = cloneVNode(vnode)
}
}
return vnode
}
export function shouldUpdateFunctionalComponent(
prevProps: Data | null,
nextProps: Data | null
): boolean {
if (prevProps === nextProps) {
return false
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
let shouldUpdate = true
const nextKeys = Object.keys(nextProps)
if (nextKeys.length === Object.keys(prevProps).length) {
shouldUpdate = false
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
shouldUpdate = true
}
}
}
return shouldUpdate
}
export function createComponentClassFromOptions(
options: ComponentOptions
): ComponentClass {
class ObjectComponent extends Component {
constructor() {
super()
this.$options = options
}
}
for (const key in options) {
const value = options[key]
if (typeof value === 'function') {
;(ObjectComponent.prototype as any)[key] = value
}
if (key === 'computed') {
const isGet = typeof value === 'function'
Object.defineProperty(ObjectComponent.prototype, key, {
configurable: true,
get: isGet ? value : value.get,
set: isGet ? undefined : value.set
})
}
}
return ObjectComponent as ComponentClass
}

View File

@ -0,0 +1,50 @@
import { MountedComponent } from './component'
import { ComponentWatchOptions } from './componentOptions'
import { autorun, stop, Autorun } from '@vue/observer'
export function initializeWatch(
instance: MountedComponent,
options: ComponentWatchOptions | undefined
) {
if (options !== void 0) {
for (const key in options) {
setupWatcher(instance, key, options[key])
}
}
}
// TODO deep watch
export function setupWatcher(
instance: MountedComponent,
keyOrFn: string | Function,
cb: Function
): () => void {
const handles = instance._watchHandles || (instance._watchHandles = new Set())
const proxy = instance.$proxy
const rawGetter =
typeof keyOrFn === 'string'
? () => proxy[keyOrFn]
: () => keyOrFn.call(proxy)
let oldValue: any
const runner = autorun(rawGetter, {
scheduler: (runner: Autorun) => {
const newValue = runner()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
}
})
oldValue = runner()
handles.add(runner)
return () => {
stop(runner)
handles.delete(runner)
}
}
export function teardownWatch(instance: MountedComponent) {
if (instance._watchHandles !== null) {
instance._watchHandles.forEach(stop)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
import { MountedComponent } from './component'
export const enum ErrorTypes {
LIFECYCLE = 1,
RENDER = 2,
NATIVE_EVENT_HANDLER = 3,
COMPONENT_EVENT_HANDLER = 4
}
const globalHandlers: Function[] = []
export function globalHandleError(handler: () => void) {
globalHandlers.push(handler)
return () => {
globalHandlers.splice(globalHandlers.indexOf(handler), 1)
}
}
export function handleError(
err: Error,
instance: MountedComponent,
type: ErrorTypes,
code: number
) {
// TODO
}

View File

@ -0,0 +1,31 @@
// vnode flags
export const enum VNodeFlags {
ELEMENT_HTML = 1,
ELEMENT_SVG = 1 << 1,
ELEMENT = ELEMENT_HTML | ELEMENT_SVG,
COMPONENT_UNKNOWN = 1 << 2,
COMPONENT_STATEFUL = 1 << 3,
COMPONENT_FUNCTIONAL = 1 << 4,
COMPONENT_ASYNC = 1 << 5,
COMPONENT = COMPONENT_UNKNOWN |
COMPONENT_STATEFUL |
COMPONENT_FUNCTIONAL |
COMPONENT_ASYNC,
TEXT = 1 << 6,
FRAGMENT = 1 << 7,
PORTAL = 1 << 8
}
export const enum ChildrenFlags {
UNKNOWN_CHILDREN = 0,
NO_CHILDREN = 1,
SINGLE_VNODE = 1 << 1,
KEYED_VNODES = 1 << 2,
NONE_KEYED_VNODES = 1 << 3,
STABLE_SLOTS = 1 << 4,
DYNAMIC_SLOTS = 1 << 5,
HAS_SLOTS = STABLE_SLOTS | DYNAMIC_SLOTS,
MULTIPLE_VNODES = KEYED_VNODES | NONE_KEYED_VNODES
}

104
packages/core/src/h.ts Normal file
View File

@ -0,0 +1,104 @@
import { ChildrenFlags } from './flags'
import { ComponentClass, FunctionalComponent } from './component'
import { ComponentOptions } from './componentOptions'
import {
VNode,
createElementVNode,
createComponentVNode,
createTextVNode,
createFragment,
createPortal
} from './vdom'
export const Fragment = Symbol()
export const Portal = Symbol()
type ElementType =
| string
| FunctionalComponent
| ComponentClass
| ComponentOptions
| typeof Fragment
| typeof Portal
export interface createElement {
(tag: ElementType, data: any, children: any): VNode
c: typeof createComponentVNode
e: typeof createElementVNode
t: typeof createTextVNode
f: typeof createFragment
p: typeof createPortal
}
export const h = ((tag: ElementType, data: any, children: any): VNode => {
if (Array.isArray(data) || (data !== void 0 && typeof data !== 'object')) {
children = data
data = null
}
// TODO clone data if it is observed
let key = null
let ref = null
let portalTarget = null
if (data != null) {
if (data.slots != null) {
children = data.slots
}
if (data.key != null) {
;({ key } = data)
}
if (data.ref != null) {
;({ ref } = data)
}
if (data.target != null) {
portalTarget = data.target
}
}
if (typeof tag === 'string') {
// element
return createElementVNode(
tag,
data,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
} else if (tag === Fragment) {
if (__DEV__ && ref) {
// TODO warn fragment cannot have ref
}
return createFragment(children, ChildrenFlags.UNKNOWN_CHILDREN, key)
} else if (tag === Portal) {
if (__DEV__ && !portalTarget) {
// TODO warn portal must have a target
}
return createPortal(
portalTarget,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
} else {
// TODO: handle fragment & portal types
// TODO: warn ref on fragment
// component
return createComponentVNode(
tag,
data,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
}
}) as createElement
h.c = createComponentVNode
h.e = createElementVNode
h.t = createTextVNode
h.f = createFragment
h.p = createPortal

View File

@ -0,0 +1,29 @@
// render api
export { h, Fragment, Portal } from './h'
export { cloneVNode, createPortal, createFragment } from './vdom'
export { createRenderer } from './createRenderer'
import { Component as InternalComponent, ComponentClass } from './component'
// the public component constructor with proper type inference.
export const Component = InternalComponent as ComponentClass
// observer api
export {
autorun,
stop,
observable,
immutable,
computed,
isObservable,
isImmutable,
markImmutable,
markNonReactive,
unwrap
} from '@vue/observer'
// flags & types
export { FunctionalComponent } from './component'
export { ComponentOptions, PropType } from './componentOptions'
export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, VNodeData, VNodeChildren, Key, Ref, Slots, Slot } from './vdom'

View File

@ -0,0 +1,12 @@
export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})
export const isReservedProp = (key: string): boolean => {
switch (key) {
case 'key':
case 'ref':
case 'slots':
return true
default:
return key.startsWith('nativeOn')
}
}

360
packages/core/src/vdom.ts Normal file
View File

@ -0,0 +1,360 @@
import {
MountedComponent,
ComponentClass,
FunctionalComponent
} from './component'
import { VNodeFlags, ChildrenFlags } from './flags'
import { normalizeComponentProps } from './componentProps'
import { createComponentClassFromOptions } from './componentUtils'
import { ComponentPropsOptions } from './componentOptions'
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
export interface RenderNode {
vnode?: VNode | null
// technically this doesn't exist on platforn render nodes,
// but we list it here so that TS can figure out union types
$f: false
}
export interface RenderFragment {
children: (RenderNode | RenderFragment)[]
$f: true
}
export interface VNode {
_isVNode: true
flags: VNodeFlags
tag: string | FunctionalComponent | ComponentClass | RenderNode | null
data: VNodeData | null
children: VNodeChildren
childFlags: ChildrenFlags
key: Key | null
ref: Ref | null
slots: Slots | null
// only on mounted nodes
el: RenderNode | RenderFragment | null
// only on mounted component root nodes
// points to component node in parent tree
parentVNode: VNode | null
}
export interface MountedVNode extends VNode {
el: RenderNode | RenderFragment
}
export type MountedVNodes = MountedVNode[]
export interface VNodeData {
key?: Key | null
ref?: Ref | null
slots?: Slots | null
[key: string]: any
}
export type VNodeChildren =
| VNode[] // ELEMENT | PORTAL
| MountedComponent // COMPONENT_STATEFUL
| VNode // COMPONENT_FUNCTIONAL
| string // TEXT
| null
export type Key = string | number
export type Ref = (t: RenderNode | MountedComponent | null) => void
export interface Slots {
[name: string]: Slot
}
export type Slot = (...args: any[]) => VNode[]
export function createVNode(
flags: VNodeFlags,
tag: string | FunctionalComponent | ComponentClass | RenderNode | null,
data: VNodeData | null,
children: VNodeChildren | null,
childFlags: ChildrenFlags,
key: Key | null | undefined,
ref: Ref | null | undefined,
slots: Slots | null | undefined
): VNode {
const vnode: VNode = {
_isVNode: true,
flags,
tag,
data,
children,
childFlags,
key: key === void 0 ? null : key,
ref: ref === void 0 ? null : ref,
slots: slots === void 0 ? null : slots,
el: null,
parentVNode: null
}
if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
normalizeChildren(vnode, children)
}
return vnode
}
export function createElementVNode(
tag: string,
data: VNodeData | null,
children: VNodeChildren,
childFlags: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
) {
const flags = tag === 'svg' ? VNodeFlags.ELEMENT_SVG : VNodeFlags.ELEMENT_HTML
return createVNode(flags, tag, data, children, childFlags, key, ref, null)
}
export function createComponentVNode(
comp: any,
data: VNodeData | null,
children: VNodeChildren,
childFlags: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
) {
// resolve type
let flags: VNodeFlags
let propsOptions: ComponentPropsOptions
// flags
const compType = typeof comp
if (__COMPAT__ && compType === 'object') {
if (comp.functional) {
// object literal functional
flags = VNodeFlags.COMPONENT_FUNCTIONAL
const { render } = comp
if (!comp._normalized) {
render.pure = comp.pure
render.props = comp.props
comp._normalized = true
}
comp = render
propsOptions = comp.props
} else {
// object literal stateful
flags = VNodeFlags.COMPONENT_STATEFUL
comp =
comp._normalized ||
(comp._normalized = createComponentClassFromOptions(comp))
propsOptions = comp.options && comp.options.props
}
} else {
// assumes comp is function here now
if (__DEV__ && compType !== 'function') {
// TODO warn invalid comp value in dev
}
if (comp.prototype && comp.prototype.render) {
flags = VNodeFlags.COMPONENT_STATEFUL
propsOptions = comp.options && comp.options.props
} else {
flags = VNodeFlags.COMPONENT_FUNCTIONAL
propsOptions = comp.props
}
}
if (__DEV__ && flags === VNodeFlags.COMPONENT_FUNCTIONAL && ref) {
// TODO warn functional component cannot have ref
}
// props
const props = normalizeComponentProps(data, propsOptions, comp)
// slots
let slots: any
if (childFlags == null) {
childFlags = children
? ChildrenFlags.DYNAMIC_SLOTS
: ChildrenFlags.NO_CHILDREN
if (children != null) {
const childrenType = typeof children
if (childrenType === 'function') {
// function as children
slots = { default: children }
} else if (childrenType === 'object' && !(children as VNode)._isVNode) {
// slot object as children
slots = children
} else {
slots = { default: () => children }
}
slots = normalizeSlots(slots)
}
}
return createVNode(
flags,
comp,
props,
null, // to be set during mount
childFlags,
key,
ref,
slots
)
}
export function createTextVNode(text: string): VNode {
return createVNode(
VNodeFlags.TEXT,
null,
null,
text == null ? '' : text,
ChildrenFlags.NO_CHILDREN,
null,
null,
null
)
}
export function createFragment(
children: VNodeChildren,
childFlags?: ChildrenFlags,
key?: Key | null
) {
return createVNode(
VNodeFlags.FRAGMENT,
null,
null,
children,
childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
key,
null,
null
)
}
export function createPortal(
target: RenderNode | string,
children: VNodeChildren,
childFlags?: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
): VNode {
return createVNode(
VNodeFlags.PORTAL,
target,
null,
children,
childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
key,
ref,
null
)
}
export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
const { flags, data } = vnode
if (flags & VNodeFlags.ELEMENT || flags & VNodeFlags.COMPONENT) {
let clonedData = data
if (extraData != null) {
clonedData = {}
if (data != null) {
for (const key in data) {
clonedData[key] = data[key]
}
}
for (const key in extraData) {
clonedData[key] = extraData[key]
}
}
return createVNode(
flags,
vnode.tag,
clonedData,
vnode.children,
vnode.childFlags,
vnode.key,
vnode.ref,
vnode.slots
)
} else if (flags & VNodeFlags.TEXT) {
return createTextVNode(vnode.children as string)
} else {
return vnode
}
}
function normalizeChildren(vnode: VNode, children: any) {
let childFlags
if (Array.isArray(children)) {
const { length } = children
if (length === 0) {
childFlags = ChildrenFlags.NO_CHILDREN
children = null
} else if (length === 1) {
childFlags = ChildrenFlags.SINGLE_VNODE
children = children[0]
if (children.el) {
children = cloneVNode(children)
}
} else {
childFlags = ChildrenFlags.KEYED_VNODES
children = normalizeVNodes(children)
}
} else if (children == null) {
childFlags = ChildrenFlags.NO_CHILDREN
} else if (children._isVNode) {
childFlags = ChildrenFlags.SINGLE_VNODE
if (children.el) {
children = cloneVNode(children)
}
} else {
// primitives or invalid values, cast to string
childFlags = ChildrenFlags.SINGLE_VNODE
children = createTextVNode(children + '')
}
vnode.children = children
vnode.childFlags = childFlags
}
export function normalizeVNodes(
children: any[],
newChildren: VNode[] = [],
currentPrefix: string = ''
): VNode[] {
for (let i = 0; i < children.length; i++) {
const child = children[i]
let newChild
if (child == null) {
newChild = createTextVNode('')
} else if (child._isVNode) {
newChild = child.el ? cloneVNode(child) : child
} else if (Array.isArray(child)) {
normalizeVNodes(child, newChildren, currentPrefix + i + '|')
} else {
newChild = createTextVNode(child + '')
}
if (newChild) {
if (newChild.key == null) {
newChild.key = currentPrefix + i
}
newChildren.push(newChild)
}
}
return newChildren
}
// ensure all slot functions return Arrays
function normalizeSlots(slots: { [name: string]: any }): Slots {
const normalized: Slots = {}
for (const name in slots) {
normalized[name] = (...args) => normalizeSlot(slots[name](...args))
}
return normalized
}
function normalizeSlot(value: any): VNode[] {
if (value == null) {
return [createTextVNode('')]
} else if (Array.isArray(value)) {
return normalizeVNodes(value)
} else if (value._isVNode) {
return [value]
} else {
return [createTextVNode(value + '')]
}
}

3
packages/global.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
// Global compile-time constants
declare var __DEV__: boolean
declare var __COMPAT__: boolean

View File

@ -0,0 +1,3 @@
__tests__/
__mocks__/
dist/packages

View File

@ -0,0 +1,3 @@
# @vue/observer
> This package is inlined into UMD & Browser ESM builds of user-facing renderers (e.g. `@vue/runtime-dom`), but also published as a package that can be used standalone. The standalone build should not be used alongside a pre-bundled build of a user-facing renderer, as they will have different internal storage for reactivity connections. A user-facing renderer should re-export all APIs from this package.

View File

@ -0,0 +1,7 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/observer.cjs.prod.js')
} else {
module.exports = require('./dist/observer.cjs.js')
}

View File

@ -0,0 +1,26 @@
{
"name": "@vue/observer",
"version": "3.0.0-alpha.1",
"description": "@vue/observer",
"main": "index.js",
"module": "dist/observer.esm.js",
"typings": "dist/index.d.ts",
"unpkg": "dist/observer.umd.js",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"buildOptions": {
"name": "VueObserver",
"formats": ["esm", "cjs", "umd", "esm-browser"]
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/observer#readme"
}

View File

@ -0,0 +1,164 @@
import { OperationTypes } from './operations'
import { Dep, KeyToDepMap, targetMap } from './state'
export interface Autorun {
(): any
isAutorun: true
active: boolean
raw: Function
deps: Array<Dep>
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
}
export interface AutorunOptions {
lazy?: boolean
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
}
export type Scheduler = (run: () => any) => void
export type DebuggerEvent = {
runner: Autorun
target: any
type: OperationTypes
key: string | symbol | undefined
}
export type Debugger = (event: DebuggerEvent) => void
export const activeAutorunStack: Autorun[] = []
const ITERATE_KEY = Symbol('iterate')
export function createAutorun(fn: Function, options: AutorunOptions): Autorun {
const runner = function runner(...args): any {
return run(runner as Autorun, fn, args)
} as Autorun
runner.active = true
runner.raw = fn
runner.scheduler = options.scheduler
runner.onTrack = options.onTrack
runner.onTrigger = options.onTrigger
runner.deps = []
return runner
}
function run(runner: Autorun, fn: Function, args: any[]): any {
if (!runner.active) {
return fn(...args)
}
if (activeAutorunStack.indexOf(runner) === -1) {
cleanup(runner)
try {
activeAutorunStack.push(runner)
return fn(...args)
} finally {
activeAutorunStack.pop()
}
}
}
export function cleanup(runner: Autorun) {
for (let i = 0; i < runner.deps.length; i++) {
runner.deps[i].delete(runner)
}
runner.deps = []
}
export function track(
target: any,
type: OperationTypes,
key?: string | symbol
) {
const runner = activeAutorunStack[activeAutorunStack.length - 1]
if (runner) {
if (type === OperationTypes.ITERATE) {
key = ITERATE_KEY
}
// keyMap must exist because only an observed target can call this function
const depsMap = targetMap.get(target) as KeyToDepMap
let dep = depsMap.get(key as string | symbol)
if (!dep) {
depsMap.set(key as string | symbol, (dep = new Set()))
}
if (!dep.has(runner)) {
dep.add(runner)
runner.deps.push(dep)
if (__DEV__ && runner.onTrack) {
runner.onTrack({
runner,
target,
type,
key
})
}
}
}
}
export function trigger(
target: any,
type: OperationTypes,
key?: string | symbol,
extraInfo?: any
) {
const depsMap = targetMap.get(target) as KeyToDepMap
const runners = new Set()
if (type === OperationTypes.CLEAR) {
// collection being cleared, trigger all runners for target
depsMap.forEach(dep => {
addRunners(runners, dep)
})
} else {
// schedule runs for SET | ADD | DELETE
addRunners(runners, depsMap.get(key as string | symbol))
// also run for iteration key on ADD | DELETE
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(runners, depsMap.get(iterationKey))
}
}
runners.forEach(runner => {
scheduleRun(runner, target, type, key, extraInfo)
})
}
function addRunners(
runners: Set<Autorun>,
runnersToAdd: Set<Autorun> | undefined
) {
if (runnersToAdd !== void 0) {
runnersToAdd.forEach(runners.add, runners)
}
}
function scheduleRun(
runner: Autorun,
target: any,
type: OperationTypes,
key: string | symbol | undefined,
extraInfo: any
) {
if (__DEV__ && runner.onTrigger) {
runner.onTrigger(
Object.assign(
{
runner,
target,
key,
type
},
extraInfo
)
)
}
if (runner.scheduler !== void 0) {
runner.scheduler(runner)
} else {
runner()
}
}

View File

@ -0,0 +1,120 @@
import { observable, immutable, unwrap } from './index'
import { OperationTypes } from './operations'
import { track, trigger } from './autorun'
import { LOCKED } from './lock'
const hasOwnProperty = Object.prototype.hasOwnProperty
const builtInSymbols = new Set(
Object.getOwnPropertyNames(Symbol)
.map(key => (Symbol as any)[key])
.filter(value => typeof value === 'symbol')
)
function get(
target: any,
key: string | symbol,
receiver: any,
toObsevable: (t: any) => any
) {
const res = Reflect.get(target, key, receiver)
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
track(target, OperationTypes.GET, key)
return res !== null && typeof res === 'object' ? toObsevable(res) : res
}
function set(
target: any,
key: string | symbol,
value: any,
receiver: any
): boolean {
value = unwrap(value)
const hadKey = hasOwnProperty.call(target, key)
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === unwrap(receiver)) {
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
trigger(target, OperationTypes.ADD, key)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
function deleteProperty(target: any, key: string | symbol): boolean {
const hadKey = hasOwnProperty.call(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (hadKey) {
if (__DEV__) {
trigger(target, OperationTypes.DELETE, key, { oldValue })
} else {
trigger(target, OperationTypes.DELETE, key)
}
}
return result
}
function has(target: any, key: string | symbol): boolean {
const result = Reflect.has(target, key)
track(target, OperationTypes.HAS, key)
return result
}
function ownKeys(target: any): (string | number | symbol)[] {
track(target, OperationTypes.ITERATE)
return Reflect.ownKeys(target)
}
export const mutableHandlers: ProxyHandler<any> = {
get: (target: any, key: string | symbol, receiver: any) =>
get(target, key, receiver, observable),
set,
deleteProperty,
has,
ownKeys
}
export const immutableHandlers: ProxyHandler<any> = {
get: (target: any, key: string | symbol, receiver: any) =>
get(target, key, receiver, LOCKED ? immutable : observable),
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(`Set operation failed: target is immutable.`, target)
}
return true
} else {
return set(target, key, value, receiver)
}
},
deleteProperty(target: any, key: string | symbol): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(`Delete operation failed: target is immutable.`, target)
}
return true
} else {
return deleteProperty(target, key)
}
},
has,
ownKeys
}

View File

@ -0,0 +1,161 @@
import { unwrap } from './index'
import { track, trigger } from './autorun'
import { OperationTypes } from './operations'
function instrument(
target: any,
key: string | symbol,
args: any[],
type: OperationTypes
) {
target = unwrap(target)
const proto: any = Reflect.getPrototypeOf(target)
track(target, type)
return proto[key].apply(target, args)
}
function get(key: string | symbol) {
return instrument(this, key, [key], OperationTypes.GET)
}
function has(key: string | symbol): boolean {
return instrument(this, key, [key], OperationTypes.HAS)
}
function size(target: any) {
target = unwrap(target)
const proto = Reflect.getPrototypeOf(target)
track(target, OperationTypes.ITERATE)
return Reflect.get(proto, 'size', target)
}
function makeWarning(type: OperationTypes) {
return function() {
if (__DEV__) {
console.warn(
`${type} operation failed: target is immutable.`,
unwrap(this)
)
}
}
}
const mutableInstrumentations: any = {
get,
has,
get size() {
return size(this)
},
add(key: any) {
const target = unwrap(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, key)
const result = proto.add.apply(target, arguments)
if (!hadKey) {
if (__DEV__) {
trigger(target, OperationTypes.ADD, key, { value: key })
} else {
trigger(target, OperationTypes.ADD, key)
}
}
return result
},
set(key: any, value: any) {
const target = unwrap(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, key)
const oldValue = proto.get.call(target, key)
const result = proto.set.apply(target, arguments)
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
trigger(target, OperationTypes.ADD, key)
} else {
trigger(target, OperationTypes.SET, key)
}
}
return result
},
delete(key: any) {
const target = unwrap(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, key)
const oldValue = proto.get ? proto.get.call(target, key) : undefined
// forward the operation before queueing reactions
const result = proto.delete.apply(target, arguments)
if (hadKey) {
if (__DEV__) {
trigger(target, OperationTypes.DELETE, key, { oldValue })
} else {
trigger(target, OperationTypes.DELETE, key)
}
}
return result
},
clear() {
const target = unwrap(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadItems = target.size !== 0
const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
// forward the operation before queueing reactions
const result = proto.clear.apply(target, arguments)
if (hadItems) {
if (__DEV__) {
trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
} else {
trigger(target, OperationTypes.CLEAR)
}
}
return result
}
}
const immutableInstrumentations: any = {
get,
has,
get size() {
return size(this)
},
add: makeWarning(OperationTypes.ADD),
set: makeWarning(OperationTypes.SET),
delete: makeWarning(OperationTypes.DELETE),
clear: makeWarning(OperationTypes.CLEAR)
}
;['forEach', 'keys', 'values', 'entries', Symbol.iterator].forEach(key => {
mutableInstrumentations[key] = immutableInstrumentations[key] = function(
...args: any[]
) {
return instrument(this, key, args, OperationTypes.ITERATE)
}
})
function getInstrumented(
target: any,
key: string | symbol,
receiver: any,
instrumentations: any
) {
target = instrumentations.hasOwnProperty(key) ? instrumentations : target
return Reflect.get(target, key, receiver)
}
export const mutableCollectionHandlers: ProxyHandler<any> = {
get: (target: any, key: string | symbol, receiver: any) =>
getInstrumented(target, key, receiver, mutableInstrumentations)
}
export const immutableCollectionHandlers: ProxyHandler<any> = {
get: (target: any, key: string | symbol, receiver: any) =>
getInstrumented(target, key, receiver, immutableInstrumentations)
}

View File

@ -0,0 +1,44 @@
import { autorun, stop } from './index'
import { Autorun, activeAutorunStack } from './autorun'
export interface ComputedGetter {
(): any
stop: () => void
}
export function computed(getter: Function, context?: any): ComputedGetter {
let dirty: boolean = true
let value: any = undefined
const runner = autorun(() => getter.call(context, context), {
lazy: true,
scheduler: () => {
dirty = true
}
})
const computedGetter = (() => {
if (dirty) {
value = runner()
dirty = false
}
// When computed autoruns are accessed in a parent autorun, the parent
// should track all the dependencies the computed property has tracked.
// This should also apply for chained computed properties.
trackChildRun(runner)
return value
}) as ComputedGetter
computedGetter.stop = () => stop(runner)
return computedGetter
}
function trackChildRun(childRunner: Autorun) {
const parentRunner = activeAutorunStack[activeAutorunStack.length - 1]
if (parentRunner) {
for (let i = 0; i < childRunner.deps.length; i++) {
const dep = childRunner.deps[i]
if (!dep.has(parentRunner)) {
dep.add(parentRunner)
parentRunner.deps.push(dep)
}
}
}
}

View File

@ -0,0 +1,152 @@
import { mutableHandlers, immutableHandlers } from './baseHandlers'
import {
mutableCollectionHandlers,
immutableCollectionHandlers
} from './collectionHandlers'
import {
targetMap,
observedToRaw,
rawToObserved,
immutableToRaw,
rawToImmutable,
immutableValues,
nonReactiveValues
} from './state'
import {
createAutorun,
cleanup,
Autorun,
AutorunOptions,
DebuggerEvent
} from './autorun'
export { Autorun, DebuggerEvent }
export { computed, ComputedGetter } from './computed'
export { lock, unlock } from './lock'
const EMPTY_OBJ = {}
const collectionTypes: Set<any> = new Set([Set, Map, WeakMap, WeakSet])
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
observableValueRE.test(Object.prototype.toString.call(value)) &&
!nonReactiveValues.has(value)
)
}
type identity = <T>(target: T) => T
export const observable = ((target: any = {}): any => {
// if trying to observe an immutable proxy, return the immutable version.
if (immutableToRaw.has(target)) {
return target
}
// target is explicitly marked as immutable by user
if (immutableValues.has(target)) {
return immutable(target)
}
return createObservable(
target,
rawToObserved,
observedToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}) as identity
export const immutable = ((target: any = {}): any => {
// value is a mutable observable, retrive its original and return
// a readonly version.
if (observedToRaw.has(target)) {
target = observedToRaw.get(target)
}
return createObservable(
target,
rawToImmutable,
immutableToRaw,
immutableHandlers,
immutableCollectionHandlers
)
}) as identity
function createObservable(
target: any,
toProxy: WeakMap<any, any>,
toRaw: WeakMap<any, any>,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if ((__DEV__ && target === null) || typeof target !== 'object') {
throw new Error(`value is not observable: ${String(target)}`)
}
// target already has corresponding Proxy
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
targetMap.set(target, new Map())
return observed
}
export function autorun(
fn: Function,
options: AutorunOptions = EMPTY_OBJ
): Autorun {
if ((fn as Autorun).isAutorun) {
fn = (fn as Autorun).raw
}
const runner = createAutorun(fn, options)
if (!options.lazy) {
runner()
}
return runner
}
export function stop(runner: Autorun) {
if (runner.active) {
cleanup(runner)
runner.active = false
}
}
export function isObservable(value: any): boolean {
return observedToRaw.has(value) || immutableToRaw.has(value)
}
export function isImmutable(value: any): boolean {
return immutableToRaw.has(value)
}
export function unwrap<T>(observed: T): T {
return observedToRaw.get(observed) || immutableToRaw.get(observed) || observed
}
export function markImmutable<T>(value: T): T {
immutableValues.add(value)
return value
}
export function markNonReactive<T>(value: T): T {
nonReactiveValues.add(value)
return value
}

View File

@ -0,0 +1,10 @@
// global immutability lock
export let LOCKED = true
export function lock() {
LOCKED = true
}
export function unlock() {
LOCKED = false
}

View File

@ -0,0 +1,11 @@
export const enum OperationTypes {
// using literal strings instead of numbers so that it's easier to inspect
// debugger events
SET = 'set',
ADD = 'add',
DELETE = 'delete',
CLEAR = 'clear',
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
}

View File

@ -0,0 +1,20 @@
import { Autorun } from './autorun'
// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
export type Dep = Set<Autorun>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
// WeakMaps that store {raw <-> observed} pairs.
export const rawToObserved: WeakMap<any, any> = new WeakMap()
export const observedToRaw: WeakMap<any, any> = new WeakMap()
export const rawToImmutable: WeakMap<any, any> = new WeakMap()
export const immutableToRaw: WeakMap<any, any> = new WeakMap()
// WeakSets for values that are marked immutable or non-reactive during
// observable creation.
export const immutableValues: WeakSet<any> = new WeakSet()
export const nonReactiveValues: WeakSet<any> = new WeakSet()

View File

@ -0,0 +1,3 @@
__tests__/
__mocks__/
dist/packages

View File

@ -0,0 +1,21 @@
# @vue/runtime-dom
``` js
import { h, render, Component } from '@vue/runtime-dom'
class App extends Component {
data () {
return {
msg: 'Hello World!'
}
}
render () {
return h('div', this.msg)
}
}
render(
h(App),
document.getElementById('app')
)
```

View File

@ -0,0 +1,7 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/runtime-dom.cjs.prod.js')
} else {
module.exports = require('./dist/runtime-dom.cjs.js')
}

View File

@ -0,0 +1,30 @@
{
"name": "@vue/runtime-dom",
"version": "3.0.0-alpha.1",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm.js",
"typings": "dist/index.d.ts",
"unpkg": "dist/runtime-dom.umd.js",
"buildOptions": {
"name": "Vue",
"formats": ["esm", "cjs", "umd", "esm-browser"]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-dom#readme",
"dependencies": {
"@vue/core": "3.0.0-alpha.1",
"@vue/scheduler": "3.0.0-alpha.1"
}
}

View File

@ -0,0 +1,39 @@
import {
h,
cloneVNode,
createPortal,
Component,
createRenderer
} from '@vue/core'
import { queueJob, nextTick } from '@vue/scheduler'
import { nodeOps } from './nodeOps'
import { patchData } from './patchData'
import { teardownVNode } from './teardownVNode'
const { render } = createRenderer({
queueJob,
nodeOps,
patchData,
teardownVNode
})
// important: inline the definition for nextTick
const publicNextTick = nextTick as (fn: Function) => Promise<void>
export { h, cloneVNode, createPortal, Component, render, publicNextTick as nextTick }
// also expose observer API
export {
autorun,
stop,
observable,
immutable,
computed,
isObservable,
isImmutable,
markImmutable,
markNonReactive,
unwrap
} from '@vue/core'

View File

@ -0,0 +1,31 @@
export function patchAttr(
el: Element,
key: string,
value: any,
isSVG: boolean
) {
// isSVG short-circuits isXlink check
if (isSVG && isXlink(key)) {
if (value == null) {
el.removeAttributeNS(xlinkNS, getXlinkProp(key))
} else {
el.setAttributeNS(xlinkNS, key, value)
}
} else {
if (value == null) {
el.removeAttribute(key)
} else {
el.setAttribute(key, value)
}
}
}
const xlinkNS = 'http://www.w3.org/1999/xlink'
function isXlink(name: string): boolean {
return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'
}
function getXlinkProp(name: string): string {
return isXlink(name) ? name.slice(6, name.length) : ''
}

View File

@ -0,0 +1,29 @@
// compiler should normlaize class + :class bindings on the same element
// into a single binding ['staticClass', dynamic]
export function patchClass(el: Element, value: any, isSVG: boolean) {
// directly setting className should be faster than setAttribute in theory
if (isSVG) {
el.setAttribute('class', normalizeClass(value))
} else {
el.className = normalizeClass(value)
}
}
function normalizeClass(value: any): string {
let res = ''
if (typeof value === 'string') {
res = value
} else if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
res += normalizeClass(value[i]) + ' '
}
} else if (typeof value === 'object') {
for (const name in value) {
if (value[name]) {
res += name + ' '
}
}
}
return res.trim()
}

View File

@ -0,0 +1,142 @@
const delegateRE = /^(?:click|dblclick|submit|(?:key|mouse|touch).*)$/
type EventValue = Function | Function[]
type TargetRef = { el: Element | Document }
export function patchEvent(
el: Element,
name: string,
prevValue: EventValue | null,
nextValue: EventValue | null
) {
if (delegateRE.test(name)) {
handleDelegatedEvent(el, name, nextValue)
} else {
handleNormalEvent(el, name, prevValue, nextValue)
}
}
const eventCounts: Record<string, number> = {}
const attachedGlobalHandlers: Record<string, Function> = {}
export function handleDelegatedEvent(
el: any,
name: string,
value: EventValue | null
) {
const count = eventCounts[name]
let store = el.__events
if (value) {
if (!count) {
attachGlobalHandler(name)
}
if (!store) {
store = el.__events = {}
}
if (!store[name]) {
eventCounts[name]++
}
store[name] = value
} else if (store && store[name]) {
eventCounts[name]--
store[name] = null
if (count === 1) {
removeGlobalHandler(name)
}
}
}
function attachGlobalHandler(name: string) {
const handler = (attachedGlobalHandlers[name] = (e: Event) => {
const { type } = e
const isClick = type === 'click' || type === 'dblclick'
if (isClick && (e as MouseEvent).button !== 0) {
e.stopPropagation()
return false
}
e.stopPropagation = stopPropagation
const targetRef: TargetRef = { el: document }
Object.defineProperty(e, 'currentTarget', {
configurable: true,
get() {
return targetRef.el
}
})
dispatchEvent(e, name, isClick, targetRef)
})
document.addEventListener(name, handler)
eventCounts[name] = 0
}
function stopPropagation() {
this.cancelBubble = true
if (!this.immediatePropagationStopped) {
this.stopImmediatePropagation()
}
}
function dispatchEvent(
e: Event,
name: string,
isClick: boolean,
targetRef: TargetRef
) {
let el = e.target as any
while (el != null) {
// Don't process clicks on disabled elements
if (isClick && el.disabled) {
break
}
const store = el.__events
if (store) {
const value = store[name]
if (value) {
targetRef.el = el
invokeEvents(e, value)
if (e.cancelBubble) {
break
}
}
}
el = el.parentNode
}
}
function invokeEvents(e: Event, value: EventValue) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i](e)
}
} else {
value(e)
}
}
function removeGlobalHandler(name: string) {
document.removeEventListener(name, attachedGlobalHandlers[name] as any)
eventCounts[name] = 0
}
function handleNormalEvent(el: Element, name: string, prev: any, next: any) {
const invoker = prev && prev.invoker
if (next) {
if (invoker) {
prev.invoker = null
invoker.value = next
next.invoker = invoker
} else {
el.addEventListener(name, createInvoker(next))
}
} else if (invoker) {
el.removeEventListener(name, invoker)
}
}
function createInvoker(value: any) {
const invoker = ((e: Event) => {
invokeEvents(e, invoker.value)
}) as any
invoker.value = value
value.invoker = invoker
return invoker
}

View File

@ -0,0 +1,18 @@
import { VNode, ChildrenFlags } from '@vue/core'
export function patchDOMProp(
el: any,
key: string,
value: any,
prevVNode: VNode,
unmountChildren: any
) {
if (key === 'innerHTML' || key === 'textContent') {
if (prevVNode && prevVNode.children) {
unmountChildren(prevVNode.children, prevVNode.childFlags)
prevVNode.children = null
prevVNode.childFlags = ChildrenFlags.NO_CHILDREN
}
}
el[key] = value
}

View File

@ -0,0 +1,54 @@
import { isObservable } from '@vue/core'
// style properties that should NOT have "px" added when numeric
const nonNumericRE = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i
export function patchStyle(el: any, prev: any, next: any, data: any) {
// If next is observed, the user is likely to mutate the style object.
// We need to normalize + clone it and replace data.style with the clone.
if (isObservable(next)) {
data.style = normalizeStyle(next)
}
const { style } = el
if (!next) {
el.removeAttribute('style')
} else if (typeof next === 'string') {
style.cssText = next
} else {
// TODO: warn invalid value in dev
next = normalizeStyle(next)
for (const key in next) {
let value = next[key]
if (typeof value === 'number' && !nonNumericRE.test(key)) {
value = value + 'px'
}
style.setProperty(key, value)
}
if (prev && typeof prev !== 'string') {
prev = normalizeStyle(prev)
for (const key in prev) {
if (!next[key]) {
style.setProperty(key, '')
}
}
}
}
}
function normalizeStyle(value: any): Record<string, string | number> | void {
if (value && typeof value === 'object') {
return value
} else if (Array.isArray(value)) {
const res: Record<string, string | number> = {}
for (let i = 0; i < value.length; i++) {
const normalized = normalizeStyle(value[i])
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key]
}
}
}
return res
}
}

View File

@ -0,0 +1,39 @@
const svgNS = 'http://www.w3.org/2000/svg'
export const nodeOps = {
createElement: (tag: string, isSVG?: boolean): Element =>
isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag),
createText: (text: string): Text => document.createTextNode(text),
setText: (node: Text, text: string) => {
node.nodeValue = text
},
appendChild: (parent: Node, child: Node) => {
parent.appendChild(child)
},
insertBefore: (parent: Node, child: Node, ref: Node) => {
parent.insertBefore(child, ref)
},
replaceChild: (parent: Node, oldChild: Node, newChild: Node) => {
parent.replaceChild(newChild, oldChild)
},
removeChild: (parent: Node, child: Node) => {
parent.removeChild(child)
},
clearContent: (node: Node) => {
node.textContent = ''
},
parentNode: (node: Node): Node | null => node.parentNode,
nextSibling: (node: Node): Node | null => node.nextSibling,
querySelector: (selector: string): Node | null =>
document.querySelector(selector)
}

View File

@ -0,0 +1,42 @@
import { VNode } from '@vue/core'
import { patchClass } from './modules/class'
import { patchStyle } from './modules/style'
import { patchAttr } from './modules/attrs'
import { patchDOMProp } from './modules/props'
import { patchEvent } from './modules/events'
export function patchData(
el: Element,
key: string,
prevValue: any,
nextValue: any,
prevVNode: VNode,
nextVNode: VNode,
isSVG: boolean,
unmountChildren: any
) {
switch (key) {
// special
case 'class':
patchClass(el, nextValue, isSVG)
break
case 'style':
patchStyle(el, prevValue, nextValue, nextVNode.data)
break
default:
if (key.startsWith('on')) {
patchEvent(el, key.toLowerCase().slice(2), prevValue, nextValue)
} else if (key.startsWith('domProps')) {
patchDOMProp(
el,
key[8].toLowerCase() + key.slice(9),
nextValue,
prevVNode,
unmountChildren
)
} else {
patchAttr(el, key, nextValue, isSVG)
}
break
}
}

View File

@ -0,0 +1,13 @@
import { VNode } from '@vue/core'
import { handleDelegatedEvent } from './modules/events'
export function teardownVNode(vnode: VNode) {
const { el, data } = vnode
if (data != null) {
for (const key in data) {
if (key.startsWith('on')) {
handleDelegatedEvent(el, key.toLowerCase().slice(2), null)
}
}
}
}

View File

@ -0,0 +1,3 @@
__tests__/
__mocks__/
dist/packages

View File

@ -0,0 +1,3 @@
# @vue/scheduler
> This package is published only for typing and building custom renderers. It is NOT meant to be used in applications.

View File

@ -0,0 +1,7 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/scheduler.cjs.prod.js')
} else {
module.exports = require('./dist/scheduler.cjs.js')
}

View File

@ -0,0 +1,21 @@
{
"name": "@vue/scheduler",
"version": "3.0.0-alpha.1",
"description": "@vue/scheduler",
"main": "index.js",
"module": "dist/scheduler.esm.js",
"typings": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/scheduler#readme"
}

View File

@ -0,0 +1,40 @@
const queue: Array<() => void> = []
const postFlushCbs: Array<() => void> = []
const p = Promise.resolve()
let flushing = false
export function nextTick(fn: () => void) {
p.then(fn)
}
export function queueJob(job: () => void, postFlushCb?: () => void) {
if (queue.indexOf(job) === -1) {
if (flushing) {
job()
} else {
queue.push(job)
}
}
if (postFlushCb) {
queuePostFlushCb(postFlushCb)
}
if (!flushing) {
nextTick(flushJobs)
}
}
export function queuePostFlushCb(cb: () => void) {
postFlushCbs.push(cb)
}
export function flushJobs() {
flushing = true
let job
while ((job = queue.shift())) {
job()
}
while ((job = postFlushCbs.shift())) {
job()
}
flushing = false
}

147
rollup.config.js Normal file
View File

@ -0,0 +1,147 @@
const fs = require('fs')
const path = require('path')
const ts = require('rollup-plugin-typescript2')
const replace = require('rollup-plugin-replace')
const alias = require('rollup-plugin-alias')
if (!process.env.TARGET) {
throw new Error('TARGET package must be specified via --environment flag.')
}
const packagesDir = path.resolve(__dirname, 'packages')
const packageDir = path.resolve(packagesDir, process.env.TARGET)
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
const pkg = require(resolve(`package.json`))
const packageOptions = pkg.buildOptions || {}
// build aliases dynamically
const aliasOptions = { resolve: ['.ts'] }
fs.readdirSync(packagesDir).forEach(dir => {
if (fs.statSync(path.resolve(packagesDir, dir)).isDirectory()) {
aliasOptions[`@vue/${dir}`] = path.resolve(packagesDir, `${dir}/src/index`)
}
})
const aliasPlugin = alias(aliasOptions)
// ensure TS checks only once for each build
let hasTSChecked = false
const configs = {
esm: {
file: resolve(`dist/${name}.esm.js`),
format: `es`
},
cjs: {
file: resolve(`dist/${name}.cjs.js`),
format: `cjs`
},
umd: {
file: resolve(`dist/${name}.umd.js`),
format: `umd`
},
'esm-browser': {
file: resolve(`dist/${name}.esm-browser.js`),
format: `es`
}
}
const defaultFormats = ['esm', 'cjs']
const inlineFromats = process.env.FORMATS && process.env.FORMATS.split(',')
const packageFormats = inlineFromats || packageOptions.formats || defaultFormats
const packageConfigs = packageFormats.map(format => createConfig(configs[format]))
if (process.env.NODE_ENV === 'production') {
packageFormats.forEach(format => {
if (format === 'cjs') {
packageConfigs.push(createProductionConfig(format))
}
if (format === 'umd' || format === 'esm-browser') {
packageConfigs.push(createMinifiedConfig(format))
}
})
}
module.exports = packageConfigs
function createConfig(output, plugins = []) {
const isProductionBuild = /\.prod\.js$/.test(output.file)
const isUMDBuild = /\.umd(\.prod)?\.js$/.test(output.file)
const isBunlderESMBuild = /\.esm\.js$/.test(output.file)
const isBrowserESMBuild = /esm-browser(\.prod)?\.js$/.test(output.file)
if (isUMDBuild) {
output.name = packageOptions.name
}
const tsPlugin = ts({
check: process.env.NODE_ENV === 'production' && !hasTSChecked,
tsconfig: path.resolve(__dirname, 'tsconfig.json'),
cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
tsconfigOverride: {
compilerOptions: {
declaration: process.env.NODE_ENV === 'production' && !hasTSChecked
}
}
})
// we only need to check TS and generate declarations once for each build.
// it also seems to run into weird issues when checking multiple times
// during a single build.
hasTSChecked = true
return {
input: resolve(`src/index.ts`),
// UMD and Browser ESM builds inlines everything so that they can be
// used alone.
external: isUMDBuild || isBrowserESMBuild
? []
: Object.keys(aliasOptions),
plugins: [
tsPlugin,
aliasPlugin,
createReplacePlugin(isProductionBuild, isBunlderESMBuild),
...plugins
],
output,
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
}
function createReplacePlugin(isProduction, isBunlderESMBuild) {
return replace({
__DEV__: isBunlderESMBuild
// preserve to be handled by bundlers
? `process.env.NODE_ENV !== 'production'`
// hard coded dev/prod builds
: !isProduction,
// compatibility builds
__COMPAT__: !!process.env.COMPAT
})
}
function createProductionConfig(format) {
return createConfig({
file: resolve(`dist/${name}.${format}.prod.js`),
format: /^esm/.test(format) ? 'es' : format
})
}
function createMinifiedConfig(format) {
const { terser } = require('rollup-plugin-terser')
const isESM = /^esm/.test(format)
return createConfig(
{
file: resolve(`dist/${name}.${format}.prod.js`),
format: isESM ? 'es' : format
},
[
terser({
module: isESM
})
]
)
}

75
scripts/bootstrap.js vendored Normal file
View File

@ -0,0 +1,75 @@
// create package.json, README, etc. for packages that don't have them yet
const args = require('minimist')(process.argv.slice(2))
const fs = require('fs')
const path = require('path')
const baseVersion = require('../lerna.json').version
const packagesDir = path.resolve(__dirname, '../packages')
const files = fs.readdirSync(packagesDir)
files.forEach(shortName => {
if (!fs.statSync(path.join(packagesDir, shortName)).isDirectory()) {
return
}
const name = shortName === `vue` ? shortName : `@vue/${shortName}`
const pkgPath = path.join(packagesDir, shortName, `package.json`)
if (args.force || !fs.existsSync(pkgPath)) {
const json = {
name,
version: baseVersion,
description: name,
main: 'index.js',
module: `dist/${shortName}.esm.js`,
typings: 'dist/index.d.ts',
repository: {
type: 'git',
url: 'git+https://github.com/vuejs/vue.git'
},
keywords: ['vue'],
author: 'Evan You',
license: 'MIT',
bugs: {
url: 'https://github.com/vuejs/vue/issues'
},
homepage: `https://github.com/vuejs/vue/tree/dev/packages/${shortName}#readme`
}
fs.writeFileSync(pkgPath, JSON.stringify(json, null, 2))
}
const readmePath = path.join(packagesDir, shortName, `README.md`)
if (args.force || !fs.existsSync(readmePath)) {
fs.writeFileSync(readmePath, `# ${name}`)
}
const npmIgnorePath = path.join(packagesDir, shortName, `.npmignore`)
if (args.force || !fs.existsSync(npmIgnorePath)) {
fs.writeFileSync(npmIgnorePath, `__tests__/\n__mocks__/\ndist/packages`)
}
const srcDir = path.join(packagesDir, shortName, `src`)
const indexPath = path.join(packagesDir, shortName, `src/index.ts`)
if (args.force || !fs.existsSync(indexPath)) {
if (!fs.existsSync(srcDir)) {
fs.mkdirSync(srcDir)
}
fs.writeFileSync(indexPath, ``)
}
const nodeIndexPath = path.join(packagesDir, shortName, 'index.js')
if (args.force || !fs.existsSync(nodeIndexPath)) {
fs.writeFileSync(
nodeIndexPath,
`
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/${shortName}.cjs.prod.js')
} else {
module.exports = require('./dist/${shortName}.cjs.js')
}
`.trim() + '\n'
)
}
})

70
scripts/build.js Normal file
View File

@ -0,0 +1,70 @@
const fs = require('fs-extra')
const path = require('path')
const zlib = require('zlib')
const chalk = require('chalk')
const execa = require('execa')
const dts = require('dts-bundle')
const { targets, fuzzyMatchTarget } = require('./utils')
const target = process.argv[2]
;(async () => {
if (!target) {
await buildAll(targets)
checkAllSizes(targets)
} else {
await buildAll(fuzzyMatchTarget(target))
checkAllSizes(fuzzyMatchTarget(target))
}
})()
async function buildAll (targets) {
for (const target of targets) {
await build(target)
}
}
async function build (target) {
const pkgDir = path.resolve(`packages/${target}`)
await fs.remove(`${pkgDir}/dist`)
await execa('rollup', [
'-c',
'--environment',
`NODE_ENV:production,TARGET:${target}`
], { stdio: 'inherit' })
const dtsOptions = {
name: target === 'vue' ? target : `@vue/${target}`,
main: `${pkgDir}/dist/packages/${target}/src/index.d.ts`,
out: `${pkgDir}/dist/index.d.ts`
}
dts.bundle(dtsOptions)
console.log()
console.log(chalk.blue(chalk.bold(`generated typings at ${dtsOptions.out}`)))
await fs.remove(`${pkgDir}/dist/packages`)
}
function checkAllSizes (targets) {
console.log()
for (const target of targets) {
checkSize(target)
}
console.log()
}
function checkSize (target) {
const pkgDir = path.resolve(`packages/${target}`)
const esmProdBuild = `${pkgDir}/dist/${target}.esm-browser.prod.js`
if (fs.existsSync(esmProdBuild)) {
const file = fs.readFileSync(esmProdBuild)
const minSize = (file.length / 1024).toFixed(2) + 'kb'
const gzipped = zlib.gzipSync(file)
const gzipSize = (gzipped.length / 1024).toFixed(2) + 'kb'
console.log(`${
chalk.gray(chalk.bold(target))
} min:${minSize} / gzip:${gzipSize}`)
}
}

25
scripts/dev.js Normal file
View File

@ -0,0 +1,25 @@
// Run Rollup in watch mode for a single package for development.
// Only the ES modules format will be generated, as it is expected to be tested
// in a modern browser using <script type="module">.
// Defaults to watch the `vue` meta package.
// To specific the package to watch, simply pass its name. e.g.
// ```
// yarn dev observer
// ```
const execa = require('execa')
const { targets, fuzzyMatchTarget } = require('./utils')
const target = fuzzyMatchTarget(process.argv[2] || 'runtime-dom')
execa(
'rollup',
[
'-wc',
'--environment',
`TARGET:${target},FORMATS:umd`
],
{
stdio: 'inherit'
}
)

19
scripts/utils.js Normal file
View File

@ -0,0 +1,19 @@
const fs = require('fs')
const targets = exports.targets = fs.readdirSync('packages').filter(f => {
return fs.statSync(`packages/${f}`).isDirectory()
})
exports.fuzzyMatchTarget = partialTarget => {
const matched = []
for (const target of targets) {
if (target.match(partialTarget)) {
matched.push(target)
}
}
if (matched.length) {
return matched
} else {
throw new Error(`Target ${partialTarget} not found!`)
}
}

32
scripts/verifyCommit.js Normal file
View File

@ -0,0 +1,32 @@
// Invoked on the commit-msg git hook by yorkie.
const chalk = require('chalk')
const msgPath = process.env.GIT_PARAMS
const msg = require('fs')
.readFileSync(msgPath, 'utf-8')
.trim()
const commitRE = /^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types)(\(.+\))?: .{1,50}/
if (!commitRE.test(msg)) {
console.log()
console.error(
` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
`invalid commit message format.`
)}\n\n` +
chalk.red(
` Proper commit message format is required for automated changelog generation. Examples:\n\n`
) +
` ${chalk.green(`feat(compiler): add 'comments' option`)}\n` +
` ${chalk.green(
`fix(v-model): handle events on blur (close #28)`
)}\n\n` +
chalk.red(` See .github/COMMIT_CONVENTION.md for more details.\n`) +
chalk.red(
` You can also use ${chalk.cyan(
`npm run commit`
)} to interactively generate a commit message.\n`
)
)
process.exit(1)
}

31
tsconfig.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"sourceMap": false,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
// "declaration": true,
"allowJs": false,
"noUnusedLocals": true,
"strictNullChecks": true,
"noImplicitAny": true,
"experimentalDecorators": true,
"removeComments": false,
"lib": [
"esnext",
"dom"
],
"rootDir": ".",
"paths": {
"@vue/core": ["packages/core/src"],
"@vue/observer": ["packages/observer/src"],
"@vue/scheduler": ["packages/scheduler/src"]
}
},
"include": [
"packages/global.d.ts",
"packages/*/src"
]
}

4042
yarn.lock Normal file

File diff suppressed because it is too large Load Diff