vue框架设计核心要素

62 阅读5分钟

如何构建框架

提升用户的开发体验

  1. 有良好的提示
  2. 可以结合DevTools自定义打印格式

控制代码的体积

  1. 区分开发环境和生产环境,让警告、异常代码成为dead code ,最终构建时不会存在这些代码

Tree shaking

  1. 使用rollup 或者wepack 可以进行简单Tree Shaking
  2. rollup 或者wepack中,合理的运用/*#__PURE__*/ 可以将代码作为dead code 处理。

输出怎样的构建产物

  1. 希望在HTML中直接通过script标签引入使用: 所以你输出了IIFE格式资源
  2. 希望通过type=modle的script标签引入使用:输出了ESM资源
  3. 希望服务端渲染,即nodeJs中require使用:输出了cjs资源

错误处理

  1. 统一错误处理
  2. 用户可以注册错误处理程序

vue3框架如何构建

Monorepo管理项目

Monorepo 一种项目管理方式。概念上很好理解,就是把多个项目放在一个仓库里面。

一般 Monorepo 的目录如下所示,在 packages 存放多个子项目,并且每个子项目都有自己的package.json

 ├── packages
 |   ├── pkg1
 |   |   ├── package.json
 |   ├── pkg2
 |   |   ├── package.json
 ├── package.json

pnpm管理包

pnpm就是一个包管理工具,原生支持Monorepo,比npm和yarn更快一些。

优势

  • 包安装速度极快。

    pnpm在绝多大数场景下,包安装的速度都是明显优于 npm/yarn,速度会比 npm/yarn 快 2-3 倍。

  • 磁盘空间利用非常高效。

    用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。

    使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink;即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink,仅仅写入那一个新增的文件

  • 支持 monorepo

  • 安全性高

举个栗子

vue3中使用pnpm,workpace来实现Monorepo。那么如何实现的?

  1. 安装pnpm

     npm install pnpm -g //全局安装pnpm
     pnpm init -y //初始化配置文件package.json
    
  2. 根目录下新建pnpm-workpace.yaml文件 和packages目录

    yaml 是一种配置文件的格式,在这里相当于告诉pnpm需要Monorepo的环境;

    接下来我们写的所有包都会放在packages目录

     # pnpm-workpace.yaml
     packages:
       - 'packages/*' # 声明所有包都在packages目录下,并且这些包都可以基于node_modules下的包来构建
    
  3. 安装共享的包(如:pnpm install vue -w)

    -w : 表示放在根模块下,作为一个共享的包。(即:workspace-root)

     // 目录结构
     ├── node_modules
     |   ├── .pnpm // 依赖的模块都放在这里
     |   ├── vue
     ​
     // 举个栗子 : vue 中的响应式模块、运行时模块等这些vue依赖的模块,都放在了.pnpm目录下
    

    此时假设我们想用使用vue 中的响应式模块,由于它放在了.pnpm目录下,使用起来并不方便;为了开发方便我们希望在使用pnpm安装的时候也能像npm一样,将依赖的包都放在node_modules下。

    根目录下新建.npmrc文件。(pnpm的的配置文件)

     // .npmrc
     shamefully-hoist = true // 羞耻的提升
    
     // 加了.npmrc配置的目录结构
     ├── node_modules
     |   ├── .pnpm 
     |   ├── nanoid
     |   ├── csstype
     |   ├── vue
     |   ├── ...
     ​
     // 不难发现vue中的幽灵依赖都出现在了node_modules中,可以随时使用
    

    这时候我们就可以安装vue3需要的共享模块

    支持TS

    命令行解析工具(minimist)

    打包工具(esbuild)

     pnpm install typescript minimist esbuild
    
  4. packages中进行配置

     c:\vue3\packages\reactivity> pnpm init // 生成配置文件package.json
     ​
     // package.json中配置打包格式等信息
     {
       "name": "@vue/reactivity",
       "version": "1.0.0",
       "description": "",
       "main": "index.js",
       "buildOptions": {
         "name": "VueReactivity",
         "formats": [
           "global",
           "cjs",
           "esm-bundler"
         ]
       }
     }
    
  5. 让TS支持在包内导入

     pnpm tsc --init
    

    根目录下新建tsconfig.json文件

     // tsconfig.json
     {
       "compilerOptions": {
         //输出的目录,
         "outDir": "dist",
         //采用sourcemap,
         "sourceMap": true,
         //目标语法,
         "target": "es2016",
         //模块格式,
         "module": "esnext",
         //模块解析方式,
         "moduleResolution": "node",
         //严格模式,
         "strict": false,
         //解析json模块,
         "resolveJsonModule": true,
         //允许通过es6语法引入commonjs模块,
         "esModuleInterop": true,
         //jsx不转义,
         "jsx": "preserve",
         //支持的类库esnext及dom,
         "lib": [
           "esnext",
           "dom"
         ],
         // 以当前路径为基准,如果你的路径 "@vue/*" 我就帮你找到 "packages/*/src"
         "baseUrl": ".",
         "paths": {
           "@vue/*": [
             "packages/*/src"
           ]
         }
       }
     }
    

    此时就已经完成了环境的搭建工作,可以开始尝试开发vue3的响应式模块等了。

  6. 实现构建流程

    在package.json中scripts加入打包命令

     // package.json
     {
       "name": "vue3-code",
       "version": "1.0.0",
       "description": "",
       "main": "index.js",
       "scripts": {
         "dev": "node scripts/dev.js reactivity -f global"
       },
       "keywords": [],
       "author": "",
       "license": "ISC",
       "devDependencies": {
         "esbuild": "^0.14.43",
         "minimist": "^1.2.6",
         "typescript": "^4.7.3"
       }
     }
    

    新建scripts\dev.js文件,配置构建产物信息

     //解析命令行
     const args = require('minimist')(process.argv.slice(2)) //node scripts/dev.js reactivity -f global
     const {resolve} = require('path'); //node中的内置模块
     const {build} = require('esbuild')
     ​
     const target = args._[0] || 'reactivity';
     const format = args.f || 'global';
     ​
     //开发环境只打包某一个 (找到对应包下的package.json)
     const pkg = require(resolve(__dirname, `../packages/${target}/package.json`))
     ​
     //iife立即执行函数(function(){})()
     //cjs node中的模块 module.exports
     //esm 刘览器中的esModule模块 import
     const outputFormat = format.startsWith('global') ? 'iife' : format == 'cjs' ? 'cjs' : 'esm'
     const outfile = resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`)
     ​
     // 天生支持TS
     build({
         entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],//打包目标
         outfile,//输出目标
         bundle: true,//把所有包打包到一起
         sourcemap: true,
         format: outputFormat, //输出的格式
         globalName: pkg.buildOptions?.name,//打包全局的名字
         platform: format === 'cjs' ? 'node' : 'browser', //平台
         watch: {
             //监控文件变化
             onRebuild(error) {
                 if (!error) console.log(`rebuilt~~`)
             }
         }
     }).then(() => {
         console.log('watching~~')
     })
    

    此时就已经完成了环境的搭建工作,可以开始尝试在packagess开发vue3的响应式模块等了。

最后一句

学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。