Bootstrap

Vue脚手架文件夹结构分析

1.vue脚手架的文件结构

1.1 src源文件夹

  • api
  • asset
  • components
  • lang
  • router
  • store
  • styles
  • pages/views
  • utils(工具文件夹)
  • app.vue
  • main.js

1.2 dist打包文件夹

1.3 其它文件

  • .gitignore
  • package.json
  • package-lock.json
  • babel.config.js
  • webpack.config.js

2.如何将template转为render函数

template--->ast---->render函数

// 将模板编译为render函数
const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)

2.1 第一步:调用parse方法将template转化为ast(抽象语法树)

constast = parse(template.trim(), options)

parse的目标:把template转换为AST树,它是一种用 JavaScript对象的形式来描述整个模板。
解析过程:利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造AST树的目的。

2.2 第二步:对静态节点做优化

optimize(ast,options)

这个过程主要分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。深度遍历AST,查看每个子树的节点元素是否为静态节点或者静态节点根。如果为静态节点,他们生成的DOM永远不会改变,这对运行时模板更新起到了极大的优化作用。

2.3 第三步:生成代码

const code = generate(ast, options)

generateAST抽象语法树编译成 render字符串并将静态部分放到 staticRenderFns中,最后通过new Function(`` render``)生成render函数(会生成虚拟dom)。

3.runtime-only和runtime-compiler的区别

3.1 runtime-compiler

这个包里面代码量更多,是完整版的vue,包含核心功能和模板解析器(compiler)
在代码中,可以有template,因为complier可以用于编译template
template->ast->render->vdom->ui

new Vue({
  el: '#app',
  ...
  components: { App },
  template: '<App/>'
})

3.2 runtime-only

是不完整版的vue,不包含模板解析器(compiler)。
在代码中,不可以有任何template,只能识别render函数。
render->vdom->ui

let vue = new Vue({
  el: '#app',
  ...
  render: h => h(App),
  ...
})

4.package.json和package-lock.json有什么区别?

4.1 版本号

版本由三部分组成:major.minor.patch,即主版本号、次版本号、修补版本号。一个完整的版本号可以理解为:**[主要版本号,次要版本号,补丁版本号]。**下面是三种版本号的区别:

  • 会匹配最新的小版本依赖包,如1.2.x会匹配1.2.x最新的版本,但是不包括1.3.0
  • ^会匹配最新的大版本依赖包,比如^1.2.3会匹配1.x.x最新的包,但是不包括2.0.0版本
  • *安装最新版本的依赖包

4.2 package.json和package-lock.json

package.json是包管理工具,里面含有nameversondescriptionauthorscriptsdependenciesdevDependencies等多个选项。下面是文件内部大致结构:

{
  "name": "as-common-test-tools",
  "version": "0.1.4",
  "description": "前端测试工具,含有很多公共测试方法",
  "main": "./src/index.js",
  "keywords": [
    "as-common-test-tools",
    "vue",
    "前端",
    "工具",
    "网站快速成型工具"
  ],
  "author": "chourunfat",
  "files": [
    "dist"
  ],
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "npm run build:js",
    "lint": "vue-cli-service lint",
    "build:js": "webpack --config ./webpack.config.js"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^2.6.14",
    "webpack": "^5.92.1",
    "css-loader": "^7.1.2",
    "style-loader": "^4.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@babel/preset-env": "^7.24.8",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "node-sass": "^4.14.1",
    "sass": "^1.26.5",
    "sass-loader": "^7.3.1",
    "vue-loader": "^15.10.1",
    "vue-template-compiler": "^2.7.16",
    "webpack": "^5.92.1",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.4"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

下面重点说一下包依赖dependencies/devDependencies,它用于对各种依赖包的管理。其中*^~在上文中已经讲述了。我们如果直接用*^~,看似在本地不会有问题,但如果其他人下载该项目(node_modules不会上传到git远程)时候,会下载当前的次版本的最新的依赖包,这可能导致新的依赖包和其它的代码发生各种问题。而package-lock.json可以通过锁定依赖包版本来解决这个问题。

4.3 package-lock.json的由来

下面来举一个例子说明package-lock.json的作用。

例如,有一天,我用命令行输入npm install --save-dev eslint,立马在package.json生成一条记录 "eslint": "^6.7.1"

当我们使用npm install命令时,实际安装的npm包不一定是package.json上的版本号,可能是6.8.x6.9.x,因为以 npm 默认配置插入的记录是 ^6.7.1(上面讲了^的意思是会匹配最新的大版本依赖包)。

这样就会导致一个问题:每次安装的 npm 包版本可能都不一样,同时会带来一些风险。比如:6.7.1 版本的包没问题,然后最新的6.8.0版本有bug。此时我们一位新同事把项目拉取下来之后,使用npm install安装,然后自然会安装到6.8.0版本,这时候可能项目在新同事电脑就跑不起来了。这种情况是会影响到我们项目的稳定性的。(尤其是不遵循语义化控制的 npm 包,强烈谴责

我们要知道,npm install的输入是package.json,它的输出是一棵node_modules树。理想情况下,npm install应该像纯函数一样工作,对于同一个package.json总是生成完全相同的node_modules树。在某些情况下,确实如此。但在其他很多情况中,npm 无法做到这一点。有以下原因:

  • 不同版本的 npm 的安装算法不同。
  • 某些依赖项自上次安装以来,可能已发布了新版本,因此将根据package.json中的semver-range version更新依赖。
  • 某个依赖项的依赖项可能已发布新版本,即使您使用了固定依赖项说明符(是 "1.2.3"而不是 "^1.2.3"),它也会更新,因为你无法固定子依赖项的版本。

而依赖项版本更新可能会带来一些问题,例如:同事A新建了一个项目,生成了上面这份 package.json文件,但同事 A安装依赖的时间比较早,此时packageA的最新版本是6.7.1,该版本与代码兼容,没有出现bug。后来同事B克隆了同事 A 的项目,在安装依赖时packageA的最新版本是 6.8.0,那么根据语义化npm 会去安装6.8.0的版本,但6.8.0版本的 API 可能发生了改动,导致代码出现 bug。

这就是package.json会带来的问题,同一份package.json在不同的时间和环境下安装会产生不同的结果。

理论上这个问题是不应该出现的,因为npm 作为开源世界的一部分,也遵循一个发布原则:相同大版本号下的新版本应该兼容旧版本。即6.7.1升级到6.8.0时 API 不应该发生变化。

但很多开源库的开发者并没有严格遵守这个发布原则,导致了上面的这个问题。

为了在不同的环境下生成相同的node_modules,于是package-lock.json就出来了。无论何时运行 npm install,npm 都会生成或更新package-lock.json

那么有了package-lock.json之后,输入了npm install之后有哪些过程呢?

4.4 npm install 后发生了什么

在这里插入图片描述
如上:

  • npm5.0.x版本
    不管package.json中依赖是否有更新,npm i都会根据package-lock.json下载。
  • npm5.1.0版本后
    package.json中的依赖项有新版本时,npm install会无视package-lock.json,按照package.json安装,并且更新到package-lock.json
  • npm5.4.2 版本后:
    package.json声明的依赖版本规范和package-lock.json安装版本兼容,根据package-lock.json安装;如果当package.json声明的依赖版本规范和package-lock.json安装版本不兼容,根据package-lock.json安装,并且更新到package-lock.json
    举个例子:如果package.jsonsemver-range versionpackage-lock.json中版本兼容(package-lock.json版本在package.json指定的版本范围内),即使此时package.json中有新的版本,执行npm i也还是会根据 package-lock.json下载。如果手动修改了package.jsonversion ranges,且和package-lock.json中版本不兼容,那么执行npm ipackage-lock.json将会更新到兼容package.json的版本。
;