# vue-dist-demo **Repository Path**: vv_bug/vue-dist-demo ## Basic Information - **Project Name**: vue-dist-demo - **Description**: csdn-demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-09 - **Last Updated**: 2024-11-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Vue 各个资源包之间的区别(vue.common.dev.js、vue.runtime.esm.js 等等) ## 问题描述 今天有童鞋在用 vue 项目写代码的时候,问我为啥会出现以下报错? ```markdown [Vue 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. (found in ) ``` ![1-1](./1-1.png) 问这个问题的童鞋估计是没怎么看过 vue 的源码,vue 官网是没有这一部分的介绍的,得自己看源码! ## 问题原因 由于项目中引用的是 `vue.runtime.esm.js`,然后在代码中又用了 `template` 属性,导致无法解析 `template` 里面内容,项目报错。 ## 解决方案 针对不同脚手架可以这样做: ### vue-cli 脚手架创建的项目 创建或修改 `vue.config.js` 文件,修改 `webpack` 配置,把项目中的 vue 换成 “编译 + 运行” 版本。 ``` js module.exports = { ... chainWebpack: config => { config.resolve.alias .set("vue$",'vue/dist/vue.esm.js'); ... } }; ``` ### 普通 Webpack 项目 修改一下 `webpack` 配置,把项目中的 vue 换成 “编译 + 运行” 版本。 ```js module.exports = { // ... resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 } } ``` ### Rollup 项目 修改` Rollup` 配置,把项目中的 vue 换成 “编译 + 运行” 版本。 ```js const alias = require('rollup-plugin-alias') rollup({ // ... plugins: [ alias({ 'vue': 'vue/dist/vue.esm.js' }) ] }) ``` ### 浏览器 直接 `CDN` 引用 `UMD` 类型的 “编译 + 运行” 版本。 ```html ``` ok!下面我们结合 Demo 分析一下 vue 的各个 dist(资源包)之间的区别。 ## 开始 为了更好的去分析 vue 各个资源包之间的区别,我们简单的搭建一个 vue 项目。 首先创建一个目录叫 `vue-dist-demo`,然后初始化 `npm`: ```bash mkdir vue-dist-demo && cd vue-dist-demo && npm init ``` 接下来我们需要安装 webpack 相关的依赖: #### 安装 webpack webpack 核心库。 在工程目录 `vue-dist-demo` 执行以下命令安装 webpack: ```bash npm install -D webpack --registry https://registry.npm.taobao.org ``` #### 安装 webpack-cli webpack 指令库。 在工程目录 `vue-dist-demo` 执行以下命令: ```bash npm install -D webpack-cli --registry https://registry.npm.taobao.org ``` #### 安装 webpack-dev-server webpack 开发者服务框架。 在工程目录 `vue-dist-demo` 执行以下命令: ```bash npm install -D webpack-dev-server --registry https://registry.npm.taobao.org ``` #### 安装 webpack-chain webpack 配置工具。 在工程目录 `vue-dist-demo` 执行以下命令: ```bash npm install -D webpack-chain --registry https://registry.npm.taobao.org ``` #### 创建 webpack 配置 在工程目录 `vue-dist-demo` 下创建一个 `webpack.config.js` 文件: ```bash touch webpack.config.js ``` 然后对 `webpack.config.js` 进行配置,用 `webpack-chain` 导入一个 webpack 配置: ```js const config = new (require('webpack-chain'))(); module.exports = config.toConfig(); ``` 为了开发方便,我们在 `package.json` 中声明两个脚本 `build` 跟 `dev`: ```json { "name": "vue-dist-demo", "version": "1.0.0", "description": "## 问题描述", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rimraf dist && webpack --mode=production", "dev": "webpack-dev-server --mode=development --progress" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^5.4.0", "webpack-dev-server": "^3.11.0", "webpack-chain": "^6.5.1", "webpack-cli": "^3.3.12" } } ``` #### 入口与出口 我们首先在工程目录 `vue-dist-demo` 下创建一个 `src` 目录,然后在 `src` 目录下创建一个 `main.s` 文件: ```bash mkdir src && cd src && touch main.js && cd .. ``` 然后我们找到 `webpack.config.js` 文件,对 webpack 的入口和出口进行配置: ```js const path = require('path'); const config = new (require('webpack-chain'))(); config .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录 .entry('app') // 入口文件名称为 app .add('./src/main.js') // 入口文件为 ./src/main.ts .end() .output .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录 .filename('[name].[hash:8].js') // 打包出来的 bundle 名称为 "[name].[hash:8].js" .publicPath('/') // publicpath 配置为 "/" .end() ``` #### 安装 vue vue 核心 API。 ```bash npm install vue --registry https://registry.npm.taobao.org ``` #### 安装 vue-loader `.vue` 文件加载器。 ```bash npm install vue-loader -D --registry https://registry.npm.taobao.org ``` #### 安装 vue-template-compiler `.vue` 文件模版解析器。 ```bash npm install vue-template-compiler -D --registry https://registry.npm.taobao.org ``` #### 安装 html-webpack-plugin ```bash npm install -D html-webpack-plugin -D --registry https://registry.npm.taobao.org ``` 接下来我们在工程目录 `sy_webpack-wedding` 底下创建一个 `public` 目录,然后在 `public` 目录下创建一个 `index.html` 文件作为我们 app 的入口页面: ``` bash mkdir public && touch public/index.html ``` 然后将以下内容写入 `public/index.html`: ```html Title
``` webpack 配置全部内容: ```js const path = require('path'); const config = new (require('webpack-chain'))(); config .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录 .entry('app') // 入口文件名称为 app .add('./src/main.js') // 入口文件为 ./src/main.ts .end() .output .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录 .filename('[name].[hash:8].js') // 打包出来的 bundle 名称为 "[name].[hash:8].js" .publicPath('/') // publicpath 配置为 "/" .end() .resolve .extensions .add('.js') .add('.vue') // 配置以 .js 等结尾的文件当模块使用的时候都可以省略后缀 .end() .end() .module .rule('vue') // vue-loader 相关配置 .test(/\.vue$/) // 匹配 .vue 文件 .use('vue-loader') .loader('vue-loader') .end() .end() .end() .plugin('vue-loader-plugin') // vue-loader 必须要添加 vue-loader-plugin .use(require('vue-loader').VueLoaderPlugin, []) .end() .plugin('html') // 添加 html-webpack-plugin 插件 .use(require('html-webpack-plugin'), [ { template: path.resolve(__dirname, './public/index.html'), // 指定模版文件 chunks: ['app'], // 指定需要加载的 chunk inject: 'body', // 指定 script 脚本注入的位置为 body }, ]) .end() .devServer .host('0.0.0.0') // 服务器外部可访问 .disableHostCheck(true) // 关闭白名单校验 .contentBase(path.resolve(__dirname, './public')) // 设置一个 express 静态目录 .historyApiFallback({ disableDotRule: true, // 禁止在链接中使用 "." 符号 rewrites: [ { from: /^\/$/, to: '/index.html' }, // 将所有的 404 响应重定向到 index.html 页面 ], }) .port(8080) // 当前端口号 .hot(true) // 打开页面热载功能 .sockPort('location') // 设置成平台自己的端口 .open(true); module.exports = config.toConfig(); ``` #### 测试 我们在 `src` 目录下创建一个 `app.vue` 文件: ```bash touch src/app.vue ``` 然后将以下内容写入其中: ```tsx ``` 很简单,就是一个简单的 vue 文件。 然后在 `main.js` 中引入 app.vue 文件: ```js import Vue from 'vue'; import App from './app.vue'; new Vue({ el: '#app', render: (h) => h(App), }); ``` ok!一切准备完毕后,我们直接在工程目录运行 `npm run dev` 命令: ```bash npm run dev ``` 运行完毕后浏览器打开: ![1-2](./1-2.png) 可以看到,一个简单的 vue 项目就搭建并且运行起来了。 其实如果对 webpack 很熟悉的话,从 0 开始搭建一个 vue 项目就很简单了,所以强烈推荐大家去看一下我上一篇 webpack 的文章,[来和 webpack 谈场恋爱吧](https://vvbug.blog.csdn.net/article/details/108981728)。 ok!我们看一下目前的 `src/main.js` 文件: ```js import Vue from 'vue'; import App from './app.vue'; new Vue({ el: '#app', render: (h) => h(App), }); ``` 我们试着换一种方式来实现渲染: ```js import Vue from 'vue'; import App from './app.vue'; new Vue({ el: '#app', components:{ App, }, template: "" }); ``` 可以看到,我们注册了一个 `App` 组件,然后通过 vue 的 template 渲染了一个 `App` 组件。有点 vue 基础的童鞋,这个代码还是很容易看懂的,我就不一一解释了。 我们运行一下 `npm run dev` 命令,然后打开浏览器看效果: ```bash npm run dev ``` ![1-3](./1-3.png) 可以看到,跟我们文章一开始报的错误一样了,那么我们可以怎么修改呢?前面已经给了解决方法。 1. 修改 `webpack.config.js` 文件,把项目中的 vue 换成 “编译 + 运行” 版本。 ```js const path = require('path'); const config = new (require('webpack-chain'))(); config .context(path.resolve(__dirname, '.')) // webpack 上下文目录为项目根目录 .entry('app') // 入口文件名称为 app .add('./src/main.js') // 入口文件为 ./src/main.ts .end() .output .path(path.join(__dirname, './dist')) // webpack 输出的目录为根目录的 dist 目录 .filename('[name].[hash:8].js') // 打包出来的 bundle 名称为 "[name].[hash:8].js" .publicPath('/') // publicpath 配置为 "/" .end() .resolve .extensions .add('.js') .add('.vue') // 配置以 .js 等结尾的文件当模块使用的时候都可以省略后缀 .end() .alias .set('vue$', 'vue/dist/vue.esm.js') .end() .end() .module .rule('vue') // vue-loader 相关配置 .test(/\.vue$/) // 匹配 .vue 文件 .use('vue-loader') .loader('vue-loader') .end() .end() .end() .plugin('vue-loader-plugin') // vue-loader 必须要添加 vue-loader-plugin .use(require('vue-loader').VueLoaderPlugin, []) .end() .plugin('html') // 添加 html-webpack-plugin 插件 .use(require('html-webpack-plugin'), [ { template: path.resolve(__dirname, './public/index.html'), // 指定模版文件 chunks: ['app'], // 指定需要加载的 chunk inject: 'body', // 指定 script 脚本注入的位置为 body }, ]) .end() .devServer .host('0.0.0.0') // 服务器外部可访问 .disableHostCheck(true) // 关闭白名单校验 .contentBase(path.resolve(__dirname, './public')) // 设置一个 express 静态目录 .historyApiFallback({ disableDotRule: true, // 禁止在链接中使用 "." 符号 rewrites: [ { from: /^\/$/, to: '/index.html' }, // 将所有的 404 响应重定向到 index.html 页面 ], }) .port(8080) // 当前端口号 .hot(true) // 打开页面热载功能 .sockPort('location') // 设置成平台自己的端口 .open(true); module.exports = config.toConfig(); ``` 可以看到,我们指定 webpack 的 `alias`,给 `vue` 源码指向了 `vue/dist/vue.esm.js` 文件,效果我就不演示了哈,小伙伴自己运行看效果。 2. 引入 vue 的时候直接引入 `vue/dist/vue.esm.js` 文件。 修改一下 `src/main.js` 文件: ```js // import Vue from 'vue'; import Vue from 'vue/dist/vue.esm'; import App from './app.vue'; new Vue({ el: '#app', components:{ App, }, template: "" }); ``` 可以看到,我们引入 vue 的时候直接指定了 `vue/dist/vue.esm.js` 文件,小伙伴自己运行看效果。 ## 分析 效果我们看到了,怎么解决报错我们也知道了,那么各个 vue 源码文件到底有什么区别呢? 我们先看一下到底有哪些 vue 源码文件?我们找到 vue 的依赖: ![1-4](./1-4.png) 可以看到,vue 的源码文件还是比较多的,那么之间的区别又是啥呢?我们平时的项目又该怎么选呢? 其实官方已经给了一段解释,但是解释的很简单,我们可以找到 `vue/dist/README.md` 文件: > | | UMD | CommonJS | ES Module | > | --- | --- | --- | --- | > | **Full** | vue.js | vue.common.js | vue.esm.js | > | **Runtime-only** | vue.runtime.js | vue.runtime.common.js | > > vue.runtime.esm.js | > | **Full (production)** | vue.min.js | | | > | **Runtime-only (production)** | vue.runtime.min.js | | | 其实我们发现,虽然源码文件很多,但是区别好像就是一个加了 `compiler` ,一个没加 `compiler`,比如:`vue.js` 跟 `vue.runtime.js`。 我们去 vue 的官网找到一份打包各个版本的脚本文件 [https://github.com/vuejs/vue/blob/dev/scripts/config.js](https://github.com/vuejs/vue/blob/dev/scripts/config.js): ```js ... const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs-dev': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.dev.js'), format: 'cjs', env: 'development', banner }, 'web-runtime-cjs-prod': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.prod.js'), format: 'cjs', env: 'production', banner }, // Runtime+compiler CommonJS build (CommonJS) 'web-full-cjs-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.dev.js'), format: 'cjs', env: 'development', alias: { he: './entity-decoder' }, banner }, 'web-full-cjs-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.prod.js'), format: 'cjs', env: 'production', alias: { he: './entity-decoder' }, banner }, // Runtime only ES modules build (for bundlers) 'web-runtime-esm': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.esm.js'), format: 'es', banner }, // Runtime+compiler ES modules build (for bundlers) 'web-full-esm': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.js'), format: 'es', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler ES modules build (for direct import in browser) 'web-full-esm-browser-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.browser.js'), format: 'es', transpile: false, env: 'development', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler ES modules build (for direct import in browser) 'web-full-esm-browser-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.browser.min.js'), format: 'es', transpile: false, env: 'production', alias: { he: './entity-decoder' }, banner }, // runtime-only build (Browser) 'web-runtime-dev': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.js'), format: 'umd', env: 'development', banner }, // runtime-only production build (Browser) 'web-runtime-prod': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.min.js'), format: 'umd', env: 'production', banner }, // Runtime+compiler development build (Browser) 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler production build (Browser) 'web-full-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.min.js'), format: 'umd', env: 'production', alias: { he: './entity-decoder' }, banner }, // Web compiler (CommonJS). 'web-compiler': { entry: resolve('web/entry-compiler.js'), dest: resolve('packages/vue-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies) }, // Web compiler (UMD for in-browser use). 'web-compiler-browser': { entry: resolve('web/entry-compiler.js'), dest: resolve('packages/vue-template-compiler/browser.js'), format: 'umd', env: 'development', moduleName: 'VueTemplateCompiler', plugins: [node(), cjs()] }, // Web server renderer (CommonJS). 'web-server-renderer-dev': { entry: resolve('web/entry-server-renderer.js'), dest: resolve('packages/vue-server-renderer/build.dev.js'), format: 'cjs', env: 'development', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-prod': { entry: resolve('web/entry-server-renderer.js'), dest: resolve('packages/vue-server-renderer/build.prod.js'), format: 'cjs', env: 'production', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-basic': { entry: resolve('web/entry-server-basic-renderer.js'), dest: resolve('packages/vue-server-renderer/basic.js'), format: 'umd', env: 'development', moduleName: 'renderVueComponentToString', plugins: [node(), cjs()] }, 'web-server-renderer-webpack-server-plugin': { entry: resolve('server/webpack-plugin/server.js'), dest: resolve('packages/vue-server-renderer/server-plugin.js'), format: 'cjs', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-webpack-client-plugin': { entry: resolve('server/webpack-plugin/client.js'), dest: resolve('packages/vue-server-renderer/client-plugin.js'), format: 'cjs', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, // Weex runtime factory 'weex-factory': { weex: true, entry: resolve('weex/entry-runtime-factory.js'), dest: resolve('packages/weex-vue-framework/factory.js'), format: 'cjs', plugins: [weexFactoryPlugin] }, // Weex runtime framework (CommonJS). 'weex-framework': { weex: true, entry: resolve('weex/entry-framework.js'), dest: resolve('packages/weex-vue-framework/index.js'), format: 'cjs' }, // Weex compiler (CommonJS). Used by Weex's Webpack loader. 'weex-compiler': { weex: true, entry: resolve('weex/entry-compiler.js'), dest: resolve('packages/weex-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies) } } ... ``` 我们挑两个文件来分析一下,`vue.js` 跟 `vue.runtime.js`。 #### vue.runtime.js 找到 `vue.runtime.js` 的脚本配置: ```js // runtime-only build (Browser) // 运行时代码 'web-runtime-dev': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.js'), format: 'umd', env: 'development', banner }, ``` 找到 `vue.runtime.js` 的入口文件 `/web/entry-runtime.js`: ```js /* @flow */ import Vue from './runtime/index' export default Vue ``` 很简单,就直接导出了 `Vue`。 #### vue.js 找到 `vue.js` 的脚本配置: ```js // Runtime+compiler development build (Browser) // 运行时代码 + 编译时代码 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner }, ``` 找到 `vue.js` 的入口文件 `web/entry-runtime-with-compiler.js`: ```js /* @flow */ import config from 'core/config' import { warn, cached } from 'core/util/index' import { mark, measure } from 'core/util/perf' import Vue from './runtime/index' import { query } from './util/index' import { compileToFunctions } from './compiler/index' import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' const idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML }) const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to or - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */ function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML } } Vue.compile = compileToFunctions export default Vue ``` 可以看到,相比 `entry-runtime.js`,`entry-runtime-with-compiler.js` 重写了 Vue 的`$mount` 方法,在 `$mount` 方法里面利用了 `compileToFunctions` 方法把 `template` 编译成了 `render` 函数,更多 Vue 源码的解析可以参考我之前的几篇 Vue 源码的文章: - [Vue源码解析(一)](https://vvbug.blog.csdn.net/article/details/84954607) - [Vue源码解析二(render&mount)](https://vvbug.blog.csdn.net/article/details/85057354) - [Vue源码解析之(computed&watch&观察者模式)](https://vvbug.blog.csdn.net/article/details/102519987) 其它几个源码文件大家可以按照一样的方式去分析,我就不一一解析了。 ## 总结 如果你需要用到 vue 的 compiler 功能,你就需要用带 compiler 功能的 vue 源码,如果你不需要的话,只需要导入 runtime 版本的 vue 源码即可,因为带 compiler 功能的 vue 源码比runtime 版本的 vue 源码多了模版编译的功能,会使项目的 size 更大一些。