[转] 编译输出文件的区别

Vue 源码是选用了 rollup 作为 bundler ,看 Vue 的源码时发现: npm script 对应了不同的构建选项。这也对应了最后打包构建后产出的不同的包。

不同于其他的 library , Vue 为什么要在最后的打包构建环节输出不同类型的包呢?接下来我们通过 Vue 的源码以及对应的构建配置中简单的去分析下。

由于 Vue 是基于 rollup 进行构建的,我们先来简单了解下 rollup 这个 bundler : rollup 是默认使用 ES Module 规范而非 CommonJS ,因此如果你在你的项目中使用 rollup 作为构建工具的话,那么可以放心的使用 ES Module 规范,但是如果要引入只遵循了 CommonJs 规范的第三包的话,还需要使用相关的插件,插件会帮你将 CommonJs 规范的代码转为 ES Module 。得益于 ES Module , rollup 在构建前进行静态分析,进行 tree-shaking 。关于 tree-shaking 的描述 请戳我 。在构建输出环节, rollup 提供了多种文件输出类型:

  • iife : 立即执行函数
  • cjs : 遵循 CommonJs Module 规范的文件输出
  • amd : 遵循 AMD Module 规范的文件输出
  • umd : 支持 外链 / CommonJs Module / AMD Module 规范的文件输出
  • es : 将多个遵循 ES6 Module 的文件编译成1个 ES6 Module

接下来我们就看看 Vue 的使用 rollup 进行构建的几个不同的版本(使用于 browser 的版本)。

npm run dev 对应

rollup -w -c build/config.js --environment TARGET:web-full-dev

rollup 对应的配置信息为:

// Runtime+compiler development build (Browser)
 ‘web-full-dev‘: {
    entry: resolve(‘web/runtime-with-compiler.js‘),
    dest: resolve(‘dist/vue.js‘),
    format: ‘umd‘,
    env: ‘development‘,
    alias: { he: ‘./entity-decoder‘ },
    banner
  },

开发环境下输出的 umd 格式的代码,入口文件是 runtime-with-compiler.js ,这个入口文件中是将 Vue 的 构建时 和 运行时 的代码都统一进行打包了,通过查看这个入口文件,我们注意到

...
import { compileToFunctions } from ‘./compiler/index‘
...

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function () {

}

我们发现,这个文件当中,首先将原来定义的 Vue.prototype.$mount 方法缓存起来,然后将这个方法进行重写,重写后的方法当中,首先判断是否有自定义的 render 函数,如果有自定义的 render 函数的话, Vue 不会通过自带的 compiler 对模板进行编译并生成 render 函数。但是如果没有自定义的 render 函数,那么会调用 compiler 对你定义的模板进行编译,并生成 render 函数,所以通过这个 rollup 的配置构建出来的代码既支持自定义 render 函数,又支持 template 模板编译:

// 将模板编译成render函数,并挂载到vm实例的options属性上
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      ...
    // 调用之前缓存的mount函数,TODO: 关于这个函数里面发生了什么请戳我
    return mount.call(this, el, hydrating)

接下来看第二种构建方式:

npm run dev:cjs 对应的构建脚本

rollup -w -c build/config.js --environment TARGET:web-runtime-cjs

rollup 对应的配置信息为:

// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  ‘web-runtime-cjs‘: {
    entry: resolve(‘web/runtime.js‘),
    dest: resolve(‘dist/vue.runtime.common.js‘),
    format: ‘cjs‘,
    banner
  }

最后编译输出的文件是遵循 CommonJs Module 同时只包含 runtime 部分的代码,它能直接被 webpack 1.x 和 Browserify 直接 load 。它对应的入口文件是 runtime.js :

import Vue from ‘./runtime/index‘

export default Vue

这里没有重写 Vue.prototye.$mount 方法,因此在 vm 实例的生命周期中,进行到 beforeMount 阶段时:

// vm挂载的根元素
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // vm.$el为真实的node
  vm.$el = el
  // 如果vm上没有挂载render函数
  if (!vm.$options.render) {
    // 空节点
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== ‘production‘) {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== ‘#‘) ||
        vm.$options.el || el) {
        warn(
          ‘You are using the runtime-only build of Vue where the template ‘ +
          ‘compiler is not available. Either pre-compile the templates into ‘ +
          ‘render functions, or use the compiler-included build.‘,
          vm
        )
      } else {
        warn(
          ‘Failed to mount component: template or render function not defined.‘,
          vm
        )
      }
    }
  }
  // 钩子函数
  callHook(vm, ‘beforeMount‘)

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== ‘production‘ && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`${name} patch`, startTag, endTag)
    }
  } else {
    // updateComponent为监听函数, new Watcher(vm, updateComponent, noop)
    updateComponent = () => {
      // Vue.prototype._render 渲染函数
      // vm._render() 返回一个VNode
      // 更新dom
      // vm._render()调用render函数,会返回一个VNode,在生成VNode的过程中,会动态计算getter,同时推入到dep里面
      // 在非ssr情况下hydrating为false
      vm._update(vm._render(), hydrating)
    }
  }

  // 新建一个_watcher对象
  // vm实例上挂载的_watcher主要是为了更新DOM
  // 在实例化watcher的过程中,就会执行updateComponent,完成对依赖的变量的收集过程
  // vm/expression/cb
  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, ‘mounted‘)
  }
  return vm
}

首先判断 vm 实例上是否定义了 render 函数。如果没有,那么就会新建一个新的空 vnode 并挂载到 render 函数上。此外,如果页面的渲染是通过传入 根节点 的形式:

new Vue({
      el: ‘#app‘
    })

Vue 便会打出 log 信息:

warn(
          ‘You are using the runtime-only build of Vue where the template ‘ +
          ‘compiler is not available. Either pre-compile the templates into ‘ +
          ‘render functions, or use the compiler-included build.‘

意思就是你当前使用的是只包含 runtime 打包后的代码,模板的编译器(即构建时)的代码并不包含在里面。因此,你不能通过挂根节点或者是声明式模板的方式去组织你的 html 内容,而只能使用 render 函数去书写模板内容。不过报错信息里面也给出了提示信息就是,你还可以选择 pre-compile 预编译工具去将 template 模板编译成 render 函数( vue-loader 就起到了这个作用)或者是使用包含了 compiler 的输出包,也就是上面分析的即包含 compiler ,又包含 runtime 的包。

第三种构建方式:

npm run dev:esm 对应的构建脚本为:

rollup -w -c build/config.js --environment TARGET:web-runtime-esm

入口文件及最后构建出来的代码内容和第二种一样,只包含 runtime 部分的代码,但是输出代码是遵循 ES Module 规范的。可以被支持 ES Module 的 bundler 直接加载,如 webpack2 和 rollup 。

第四种构建方式:

npm run dev:compiler 对应的构建脚本为:

rollup -w -c build/config.js --environment TARGET:web-compiler

不同于前面3种构建方式:

// Web compiler (CommonJS).
‘web-compiler‘: {
    entry: resolve(‘web/compiler.js‘),
    dest: resolve(‘packages/vue-template-compiler/build.js‘),
    format: ‘cjs‘,
    external: Object.keys(require(‘../packages/vue-template-compiler/package.json‘).dependencies)
  },

这一构建对应于将关于 Vue 模板编译的成 render 函数的 compiler.js 单独进行打包输出。最后输出的 packages/vue-template-compiler/build.js 文件会单独作为一个 node_modules 进行发布,在你的开发过程中,如果使用了 webpack 作为构建工具,以及 vue-loader ,在开发构建环节, vue-loader 便会通过 web compiler 去处理你的 *.vue 文件中的模板 <template> 当中的内容,将这些模板字符串编译为 render 函数。

原文地址:https://www.cnblogs.com/chris-oil/p/8855893.html

时间: 2024-10-08 06:40:14

[转] 编译输出文件的区别的相关文章

Android内核开发:系统编译输出的镜像文件

本文是<Android内核开发>的第四篇文章,主要介绍一下源码编译输出的几个重要的镜像文件,这里把bootloader源码和Linux内核源码的编译输出也算在其中,因为毕竟Android系统缺少了这两个部分在设备上也是跑不起来的. 1. MLO, u-boot.img 任何操作系统的启动,都离不开"引导程序",比如桌面Windows系统的BIOS.桌面Linux系统常用的是Grub,而在嵌入式系统中,这个引导程序通常叫做"bootloader",它通常由

uboot学习之二----主Makefile学习之四----两种编译方法:原地编译和单独输出文件夹编译

第57-123行: 57 # 58 # U-boot build supports producing a object files to the separate external 59 # directory. Two use cases are supported: 60 # 61 # 1) Add O= to the make command line 62 # 'make O=/tmp/build all' 63 # 64 # 2) Set environement variable

Qt中将编译输出路径设置在其他文件

在进行Qt程序编译的时候,经常用Qtcreator来搞定,在这上面当然简单了,完全的图形界面操作,然后还可以设置编译输出的路径.然而由于项目需要,当把程序放到嵌入式上运行时就不能这么做了,因为很多嵌入式平台没有那么多资源,只能在命令行里qmake和make了,但是这里会出现一个问题,就是单纯地在项目目录下qmake和make会使得所有编译出来的文件全部堆在项目目录下,这样就会使项目的管理比较混乱,这里使用了两种方法可以使得编译的文件不会出现在项目文件下. 第一种是给qmake和make加一点参数

lib和dll文件的区别和联系

什么是lib文件,lib和dll的关系如何 (2008-04-18 19:44:37)    (1)lib是编译时需要的,dll是运行时需要的. 如果要完成源代码的编译,有lib就够了. 如果也使动态连接的程序运行起来,有dll就够了. 在开发和调试阶段,当然最好都有. (2)一般的动态库程序有lib文件和dll文件.lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的.如果有 dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中.如果只有lib

编程经验:由于路径设置引起的VS2010不能正确输出文件的bug~

前段时间,偶然遇到这样一个bug,我把静态库项目都配置好了,编译一切正常,但是提示了一个警告: 1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppBuild.targets(1153,5): warning MSB8012: TargetName(stereo_vc9) 与 Library 的 OutputFile 属性值()不匹配.这可能导致项目生成不正确.若要更正此问题,请确保 $(OutDir).$(Targe

编译与解释的区别

博主今年大三,学校开展了<编译原理>这门课,而老师提的第一个问题便是编译与解释的区别,下面我将会说说自己的看法. 首先我们要知道市面上大致上是有两种类型的语言的:静态语言与动态语言. 静态语言:C/C++,JAVA,C#等等:动态语言:Javascript,Python等等 而静态语言之所以是静态的正是应为它的源文件是需要使用编译器将源程序文件翻译成二进制文件. 我们拿JAVA举例,java是一门典型的强类型静态语言.首先,我们在One.java文件中编写一段程序如下 package com.

Cmd命令行编译c#文件

使用命令行编译C#文件的方法: 首先,在系统变量的Path变量中添加csc.exe文件路径 路径大概是这个样子:C:\Windows\Microsoft.NET\Framework64\v4.0.30319;  (.net版本也许不同) 在cmd中输入csc.exe验证是否成功. cs文件生成exe文件:(若要编译生成EXE文件则源文件中需包含main 方法) csc /out:file.exe  file.cs       //使用 /out 指定输出的文件 如果编译的stu.cs 和 fil

访问svc 文件,编译器错误消息: CS0016,未能写入输出文件

编译错误              说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码.             编译器错误消息: CS0016: 未能写入输出文件“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\ncs.qms.apphost.branch\930ee5f1\66b34343\App_global.asax.dorw-abx.dll”

编译型和解析型区别

先看看编译型,其实它和汇编语言是一样的:也是有一个负责翻译的程序来对我们的源代码进行转换,生成相对应的可执行代码.这个过程说得 专业一点,就称为编译(Compile),而负责编译的程序自然就称为编译器(Compiler).如果我们写的程序代码都包含在一个源文件中,那么通常 编译之后就会直接生成一个可执行文件,我们就可以直接运行了.但对于一个比较复杂的项目,为了方便管理,我们通常把代码分散在各个源文件中,作为不同的模 块来组织.这时编译各个文件时就会生成目标文件(Object   file)而不是