Debugging Environment Setup

  • Git clone github.com/vuejs/vue-n…

  • Install dependency: yarn –ignore-scripts

  • Generate the sourcemap file, package.json

      "dev": "node scripts/dev.js --sourcemap"
    Copy the code
  • Compile: yarn dev

Package execution process

graph TD
package.json  --> dev.js --> rollup.config.js

dev.js


const execa = require('execa')
const { fuzzyMatchTarget } = require('./utils')
const args = require('minimist')(process.argv.slice(2))
// Use vue folder by default, target folder
const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'vue'
// Set the format to support the way commonJS umd
const formats = args.formats || args.f
// Source mapping
const sourceMap = args.sourcemap || args.s
const commit = execa.sync('git'['rev-parse'.'HEAD']).stdout.slice(0.7)

execa(
  'rollup'['-wc'.'--environment'[`COMMIT:${commit}`.`TARGET:${target}`.`FORMATS:${formats || 'global'}`,
      sourceMap ? `SOURCE_MAP:true` : ` `
    ]
      .filter(Boolean)
      .join(', ')] and {stdio: 'inherit'})Copy the code

rollup.config.js

import path from 'path'
import ts from 'rollup-plugin-typescript2'
import replace from '@rollup/plugin-replace'
import json from '@rollup/plugin-json'

if(! process.env.TARGET) {throw new Error('TARGET package must be specified via --environment flag.')}const masterVersion = require('./package.json').version
/ / package
const packagesDir = path.resolve(__dirname, 'packages')
// Submodule directory, default vue
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 || {}

// ensure TS checks only once for each build
let hasTSChecked = false

const outputConfigs = {
  'esm-bundler': {
    file: resolve(`dist/${name}.esm-bundler.js`),
    format: `es`
  },
  'esm-browser': {
    file: resolve(`dist/${name}.esm-browser.js`),
    format: `es`
  },
  cjs: { / / the node. Js
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {// Immediate execution Mode Specifies the default execution mode
    file: resolve(`dist/${name}.global.js`),
    format: `iife`
  },

  // runtime-only builds, for main "vue" package only
  'esm-bundler-runtime': {
    file: resolve(`dist/${name}.runtime.esm-bundler.js`),
    format: `es`
  },
  'esm-browser-runtime': { //
    file: resolve(`dist/${name}.runtime.esm-browser.js`),
    format: 'es'
  },
  'global-runtime': {
    file: resolve(`dist/${name}.runtime.global.js`),
    format: 'iife'}}const defaultFormats = ['esm-bundler'.'cjs']
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(', ')
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats
const packageConfigs = process.env.PROD_ONLY
  ? []
  : packageFormats.map(format= > createConfig(format, outputConfigs[format]))

if (process.env.NODE_ENV === 'production') {
  packageFormats.forEach(format= > {
    if (packageOptions.prod === false) {
      return
    }
    if (format === 'cjs') {
      packageConfigs.push(createProductionConfig(format))
    }
    if (/^(global|esm-browser)(-runtime)? /.test(format)) {
      packageConfigs.push(createMinifiedConfig(format))
    }
  })
}

export default packageConfigs

function createConfig(format, output, plugins = []) {
  if(! output) {console.log(require('chalk').yellow(`invalid format: "${format}"`))
    process.exit(1)}// Generate the corresponding mapping configuration for the source codeoutput.sourcemap = !! process.env.SOURCE_MAP output.externalLiveBindings =false

  const isProductionBuild =
    process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)
  const isBundlerESMBuild = /esm-bundler/.test(format)
  const isBrowserESMBuild = /esm-browser/.test(format)
  const isNodeBuild = format === 'cjs'
  const isGlobalBuild = /global/.test(format)

  if (isGlobalBuild) {
    output.name = packageOptions.name
  }

  constshouldEmitDeclarations = process.env.TYPES ! =null && !hasTSChecked

  // TS mapping support
  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: {
        sourceMap: output.sourcemap,
        declaration: shouldEmitDeclarations,
        declarationMap: shouldEmitDeclarations
      },
      exclude: ['**/__tests__'.'test-dts']}})// 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

  // Import file
  const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`

  const external =
    isGlobalBuild || isBrowserESMBuild
      ? packageOptions.enableNonBrowserBranches
        ? // externalize postcss for @vue/compiler-sfc
          // because @rollup/plugin-commonjs cannot bundle it properly
          ['postcss']
        : // normal browser builds - non-browser only imports are tree-shaken,
          // they are only listed here to suppress warnings.
          ['source-map'.'@babel/parser'.'estree-walker']
      : // Node / esm-bundler builds. Externalize everything.
        [
          ...Object.keys(pkg.dependencies || {}),
          ...Object.keys(pkg.peerDependencies || {}),
          ...['path'.'url'] // for @vue/compiler-sfc
        ]

  // the browser builds of @vue/compiler-sfc requires postcss to be available
  // as a global (e.g. http://wzrd.in/standalone/postcss)
  output.globals = {
    postcss: 'postcss'
  }

  constnodePlugins = packageOptions.enableNonBrowserBranches && format ! = ='cjs'
      ? [
          require('@rollup/plugin-node-resolve').nodeResolve({
            preferBuiltins: true
          }),
          require('@rollup/plugin-commonjs') ({sourceMap: false
          }),
          require('rollup-plugin-node-builtins') (the),require('rollup-plugin-node-globals') (the)] : []// Here is the main configuration information
  return {
    input: resolve(entryFile),
    // Global and Browser ESM builds inlines everything so that they can be
    // used alone.
    external,
    plugins: [
      json({
        namedExports: false
      }),
      tsPlugin,
      createReplacePlugin(
        isProductionBuild,
        isBundlerESMBuild,
        isBrowserESMBuild,
        // isBrowserBuild?(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) && ! packageOptions.enableNonBrowserBranches, isGlobalBuild, isNodeBuild ), ... nodePlugins, ... plugins ], output,onwarn: (msg, warn) = > {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    },
    treeshake: {
      moduleSideEffects: false}}}function createProductionConfig(format) {
  return createConfig(format, {
    file: resolve(`dist/${name}.${format}.prod.js`),
    format: outputConfigs[format].format
  })
}

function createMinifiedConfig(format) {
  const { terser } = require('rollup-plugin-terser')
  return createConfig(
    format,
    {
      file: outputConfigs[format].file.replace(/\.js$/.'.prod.js'),
      format: outputConfigs[format].format
    },
    [
      terser({
        module: /^esm/.test(format),
        compress: {
          ecma: 2015.pure_getters: true}})])}Copy the code

Generate results:

  • packages\vue\dist\vue.global.js
  • packages\vue\dist\vue.global.js.map

Run the yarn serve

Source code analysis

Compiler and runtime environment.

The compiler

  • Compiler-core Core compiler logic
  • Compiler-dom compiles logic for the browser platform
  • Compiler-sfc compiles logic for single-file components
  • Compiler-ssr renders the compilation logic for the server

Runtime environment

  • Run-time core Specifies the runtime core
  • Run-time dom runtime logic for browsers
  • Run-time test Completes the test environment simulation outside the browser
  • Reactivity reactive logic
  • Template-explorer Specifies the template browser
  • Vue code entry, integrate compiler and runtime
  • Server-renderer Server-side rendering
  • Share public method

Test cases

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial - scale = 1.0 "> < title > Document < / title > < / head > < body > < div id =" app "> < h1 @ click =" onclick "> {{MSG}} < / h1 > <comp></comp> </div> <script src=".. /dist/vue.global.js"></script> <script> const {createApp} = vue // instance creation const app = createApp({data() {// even if the root object Return {MSG: 'hello, vue3'}}, methods: {onclick() {console.log('click me'); } }, }) app.component('comp', { template:'<div>comp</div>', }) app.mount('#app') </script> </body> </html>Copy the code

Composition API

The composition API was created to implement a function-based logic reuse mechanism.

Instances are instantiated via the factory function return.

advantage

  1. You can use tree-shking to remove useless code during packaging.
  2. Eliminate this and just build a simple context
  3. The same business code is written together, avoiding the problem of code scrolling up and down frequently on complex pages
<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src=".. /dist/vue.global.js"></script>
</head>

<body>
<div id="app">
    <h1>Composition API</h1>
    <div>count: {{ state.count }}</div>
</div>
<script>
    const {
        createApp,
        reactive,
        onMounted
    } = Vue;
    // Declare the component
    const App = {
        // Setup is a new component option that is the entry point for using the Composition API within a component
        // The call time is after the initialization attribute is determined and before the beforeCreate
        setup() {
            // Responsivity: receives an object and returns a responsive proxy object
            const state = reactive({ count: 0 })
              onMounted(() = > {
              console.log('mounted');
            })
            // Repeat logic can be registered multiple times to achieve business split calls
            onMounted(() = > {
              console.log('mounted2');
            })
            // The return object will be merged with the render function context
            return { state }
        }
    }
    createApp(App).mount('#app')
</script>
</body>

</html>// Mounted //mounted2 Is displayedCopy the code

Commonly used API

// Calculate attributes
<div>doubleCount: {{doubleCount}}</div>

const { computed } = Vue;
const App = {
    setup() {
        const state = reactive({
            count: 0.// Computed () returns an immutable responsive reference object
            // It encapsulates the return value of the getter
            doubleCount: computed(() = > state.count * 2)}}}// Event processing
<div @click="add">count: {{ state.count }}</div>

const App = {
    setup() {
        Declare an add function in setup
        function add() {
            state.count++
        }
        // Pass in the render function context
        return { state, add }
    }
}

// Listener: watch()
const { watch } = Vue;
const App = {
    setup() {
        // state.count Changes to cb will be performed
        watch(() = > state.count, (val, oldval) = > {
            console.log('the count has changed:+ val); }}})// Reference object: single-value responsivity, mainly to achieve a business code cohesion, avoid code scrolling up and down
<div>counter: {{ counter }}</div>

const { ref } = Vue;
const App = {
    setup() {
        // return a responsive Ref object
        const counter = ref(1)
        setTimeout(() = > {
            // Change the value of the object
            counter.value++
        }, 1000);
        / / add a counter
        return { state, add, counter }
    }
}
Copy the code

The logical combination

//03-logic-composition.html

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, "> <title>composition API </title> <script SRC =".. / dist/vue. Global. Js "> < / script > < / head > < body > < div > < h1 > logic combination < / h1 > < div id =" app "> < / div > < / div > < script > const { createApp, reactive, onMounted, onUnmounted, toRefs } = Vue; Function useMouse() {reactive({x: 0, y: reactive); 0 }) const update = e => { state.x = e.pageX state.y = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', Function useTime() {const state = reactive({time: new Date() }) onMounted(() => { setInterval(() => { state.time = new Date() }, 1000)}) return toRefs(state)} const MyComp = {template: '<div>x: {{x}} y: {{y}}</div> <p>time: {{time}}</p> ', setup() {const {x, Y} = useMouse() const {time} = useTime() {return {x, y, time } } } createApp(MyComp).mount('#app') </script> </body> </html>Copy the code

Mixins,

  • X,y, and time come from clear sources
  • Does not conflict with names such as data and props
  • Increased maintainability:

Vue3 response principle

Vue2 principle

// 1. Object responsivity: iterate over each key and define getters and setters
// 2. Array responsivity: Override the array prototype method with additional notification logic
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto) ; ['push'.'pop'.'shift'.'unshift'.'splice'.'reverse'.'sort'].forEach(
        method= > {
            arrayProto[method] = function () {
                originalProto[method].apply(this.arguments)
                notifyUpdate()
            }
        }
    )
    
  function observe(obj) {
    if (typeofobj ! = ='object' || obj == null) {
        return
    }
    // Add array type judgment, if array overrides its prototype
    if (Array.isArray(obj)) {
        Object.setPrototypeOf(obj, arrayProto)
    } else {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            defineReactive(obj, key, obj[key])
        }
    }
}
function defineReactive(obj, key, val) {
    observe(val) // Resolve nested objects
    Object.defineProperty(obj, key, {
        get() {
            return val
        },
        set(newVal) {
            if(newVal ! == val) { observe(newVal)// The new value is the case of the object
                val = newVal
                notifyUpdate()
            }
        }
    })
}
function notifyUpdate() {
    console.log('Page updated! ')}Copy the code

Disadvantages of vuE2 response type:

  • The reactivity process requires recursive traversal and consumes a lot of energy
  • New or deleted attributes cannot be listened on
  • Array responsivity requires additional implementation
  • Map, Set, and Class cannot respond
  • There are limitations to changing the syntax

Vue3 response principle analysis

Vue3 uses ES6 Proxy features to address these issues.

advantage

The proxy is added at runtime without having to walk through the object in advance to build binding related methods and maintain relationships

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of "meta programming", that is, programming a programming language.Copy the code
//reactivity.js
function reactive(obj) {
    if (typeofobj ! = ='object'&& obj ! =null) {
        return obj
    }
    // Proxy adds interception to an object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            // Reflect is used to perform the default action on objects, which is more formal and friendlier
            // Both Proxy and Object methods Reflect
            // http://es6.ruanyifeng.com/#docs/reflect
            const res = Reflect.get(target, key, receiver)
            console.log(` access${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(` set${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(` delete${key}:${res}`)
            return res
        }
    })
    return observed
}

// Test the code
const state = reactive({
    foo: 'foo'
})
/ / 1. Access
state.foo // ok
// 2. Set existing properties
state.foo = 'fooooooo' // ok
// 3. Set nonexistent properties
state.dong = 'dong' // ok
// 4. Delete attributes
delete state.dong // ok
Copy the code

Nested object responsive

// Test: nested objects cannot respond
const state = reactive({
    bar: { a: 1}})// Set nested object properties
state.bar.a = 10 // no ok
Copy the code

Add object type recursion

// Extract the help method
const isObject = val= >val ! = =null && typeof val === 'object'
function reactive(obj) {
    // Determine whether the object is an object
    if(! isObject(obj)) {return obj
    }
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            // ...
            // If it is an object, you need to recurse
            return isObject(res) ? reactive(res) : res
        },
        / /...
    }



// Avoid duplicate proxies
reactive(data) // The proxied pure object
reactive(react) // Proxy object

// Cache the previous proxy result and use it directly when getting
const toProxy = new WeakMap(a)/ / form such as obj: observed
    const toRaw = new WeakMap(a)/ / form such as observed: obj
    function reactive(obj) {
        / /...
        // Find the cache to avoid duplicate proxies
        if (toProxy.has(obj)) {
            return toProxy.get(obj)
        }
        if (toRaw.has(obj)) {
            return obj
        }
        const observed = new Proxy(...).// Cache proxy results
        toProxy.set(obj, observed)
        toRaw.set(observed, obj)
        return observed
    }
    // Test the effect
    console.log(reactive(data) === state)
    console.log(reactive(state) === state)
Copy the code

Depend on the collection

Establish the correspondence between the response data key and the update function.

// Use to set the response function
effect(() = > console.log(state.foo))
// When the user modifies the associated data, the response function is triggered
state.foo = 'xxx'
Copy the code

Implement three functions:

  • Effect: Saves the callback function for later use, and immediately executes the callback function to trigger the getter for some of the response data in it
  • Track: Call track in the getter to map the previously stored callback function to the current target and key
  • Trigger: Trigger is called in the setter, and the target and key response functions are executed

Target, key, and response function mappings

  • The general structure is shown below
  • WeakMap Map Set
  • {target: {key: [effect1,…] }}

implementation

Set the response function and create the effect function, where only the variables used in the effect function are treated in a reactive manner.

The complete code

// Save the current active response function as a bridge between the getter and effect
const effectStack = []
// effect task: execute fn and push it onto the stack
function effect(fn) {
    const rxEffect = function () {
        // 1. Catch possible exceptions
        try {
            // 2. Push for subsequent dependency collection
            effectStack.push(rxEffect)
            // 3. Run fn to trigger dependency collection
            return fn()
        } finally {
            // 4. The execution is complete and the stack is removed
            effectStack.pop()
        }
    }
    // The response function is executed once by default
    rxEffect()
    // Return the response function
    return rxEffect
}

// Rely on collection and triggering
function reactive(obj) {
  if(! isObject(obj)) {return obj
  }
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      // Rely on collection
      track(target, key)
      return isObject(res) ? reactive(res) : res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      trigger(target, key)
      return res
    }
  })
  return observed
}
// Create a mapping table.
// {target: {key: [fn1,fn2]}}
let targetMap = new WeakMap(a)function track(target, key) {
    // Remove the response function from the stack
    const effect = effectStack[effectStack.length - 1]
    if (effect) {
        // Get the dependency table for target
        let depsMap = targetMap.get(target)
        if(! depsMap) { depsMap =new Map()
            targetMap.set(target, depsMap)
        }
        // Get the set of response functions corresponding to key
        let deps = depsMap.get(key)
        if(! deps) { deps =new Set()
            depsMap.set(key, deps)
        }
        // Add the response function to the corresponding collection
        if(! deps.has(effect)) { deps.add(effect) } } }// Trigger the target.key response function
function trigger(target, key) {
    // Get the dependency table
    const depsMap = targetMap.get(target)
    if (depsMap) {
        // Get the set of response functions
        const deps = depsMap.get(key)
        if (deps) {
            // Execute all response functions
            deps.forEach(effect= > {
                effect()
            })
        }
    }
}



// Test the code
const state = reactive({
  foo: 'foo'.bar: { a: 1 }
})

effect(() = > {// This method can be understood as vue performs dispatch comparison virtual DOM refresh page
  console.log('effect:', state.foo)
})

// state.foo
// state.foo = 'fooooooo'
// state.dong = 'dong'
// delete state.dong
// Run time proxy
// state.bar.a = 10

Copy the code

The test code

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="./reactive.js"></script>
  <script>
    const data = reactive({
      msg: 'xxx'
    })

    effect(() = > {// This method can be understood as vue performs dispatch comparison virtual DOM refresh page
      app.innerText = data.msg
    })

    setTimeout(() = > {
      data.msg = 'yyyy'
    }, 1000);
  </script>
</body>
</html>
Copy the code