精华内容
下载资源
问答
  • vite源码梳理

    2021-07-17 17:59:15
    vite 一共有dev | build | optimize | preview四种命令,文章对 vite 整个流程做了梳理,对于dev | build的主要步骤做了简答的拆分,便于大家对 vite 源码形成一个整体的认识,有不对的地地方欢迎大家指正。...

    简介

    vite 一共有dev | build | optimize | preview四种命令,文章对 vite 整个流程做了梳理,对于dev | build的主要步骤做了简答的拆分,便于大家对 vite 源码形成一个整体的认识,有不对的地地方欢迎大家指正。

    启动

    vite 项目默认的两条命令npm run serve npm run build都是启动了 vite 命令,打开/node_modules/.bin目录,找到vite文件,发现其最主要的流程就是执行start函数,加载 node/cli 文件

    // 此处源码如下
    ...
    function start() {
      require('../dist/node/cli')
    }
    ...
    start()
    ...
    

    然后到到 vite 源码的 cli 文件vite/src/node/cli.ts

    vite 脚手架/src/node/cli.ts

    可以看出 vite 脚手架一共包括四个命令,dev | build | optimize | preview ,本文主要是梳理dev | build

    dev 开发环境
    build 构建
    preview  vite预览
    optimize 优化
    

    vite dev

    cli.ts

    cli.ts中关于 dev 命令引用了./servercreateServer,并触发listen()进行监听

    code.png

    /node/server/index.ts

    createServer就是要创建并返回一个 server,具体的,做了以下几件事:

    1. 整合配置文件 vite.config.js 和 命令行里的配置到 config 中
    const config = await resolveConfig(inlineConfig, "serve", "development");
    
    1. 启动一个 http(s)server,并升级为 websocket(当然前一步要先把 httpserver 的相关配置参数处理)
    const httpsOptions = await resolveHttpsConfig(config);
    
      const middlewares = connect() as Connect.Server;
      const httpServer = middlewareMode
        ? null
        : await resolveHttpServer(serverConfig, middlewares, httpsOptions);
      const ws = createWebSocketServer(httpServer, config, httpsOptions);
    
    1. 使用 chokidar 监听文件变化(这是进行热更新的基础)
    const watcher = chokidar.watch(path.resolve(root), {
        ignored: ["**/node_modules/**", "**/.git/**", ...ignored],
        ignoreInitial: true,
        ignorePermissionErrors: true,
        disableGlobbing: true,
        ...watchOptions,
      }) as FSWatcher;
    
    1. 将所有的 plugin 统一进行处理,保存到 container 中
    const container = await createPluginContainer(config, watcher);
    
    1. 根据 container 生成 moduleGraph(这里还没有细读,vite 中的解释是 moduleGraph 用于记录 import 的关系、url 到 file 的映射及热更新相关
      • and hmr state`)
    const moduleGraph = new ModuleGraph(container);
    
    // 下面是moduleGraph的ts定义
     /**
        * Module graph that tracks the import relationships, url to file mapping
        * and hmr state.
        */
    
    
    1. 初始化后面要返回的 vite-dev-server,绑定了一些属性和方法
    const server: ViteDevServer = {
    ...
    }
    
    1. watcher 发生变化的时候,进行相应的热更新处理
    watcher.on('change', fn)
    watcher.on('add', fn)
    watcher.on('unlink', fn)
    
    1. 执行 vite 钩子 configureServer,这里 postHooks 只收集有 configureServer 的 plugin
    const postHooks: ((() => void) | void)[] = [];
      for (const plugin of plugins) {
        if (plugin.configureServer) {
          // WK 执行配置了configureServer的plugin
          postHooks.push(await plugin.configureServer(server));
        }
      }
    
    1. 内部中间件的使用
    ...
    middlewares.use(corsMiddleware(typeof cors === "boolean" ? {} : cors));
    
    middlewares.use(proxyMiddleware(httpServer, config));
    ...
    
    
    1. 执行 posHooks 里的 plugins
    postHooks.forEach((fn) => fn && fn());
    
    1. 转换 index.html
    middlewares.use(indexHtmlMiddleware(server));
    
    1. 在 listen()之前
      1. 执行 vite 钩子 buildStart
      2. 执行 runOptimize(),进行启动前的优化
    if (!middlewareMode && httpServer) {
        // overwrite listen to run optimizer before server start
        const listen = httpServer.listen.bind(httpServer);
        httpServer.listen = (async (port: number, ...args: any[]) => {
          try {
            await container.buildStart({});
            await runOptimize();
          } catch (e) {
            httpServer.emit("error", e);
            return;
          }
          return listen(port, ...args);
        }) as any;
    
        httpServer.once("listening", () => {
          // update actual port since this may be different from initial value
          serverConfig.port = (httpServer.address() as AddressInfo).port;
        });
      } else {
        await container.buildStart({});
        await runOptimize();
      }
    
    
    1. 返回 server

    vite build

    cli.ts

    build命令就是引入了./build文件,并执行build()

    code.png

    rollup 打包

    vite 使用 rollup 进行打包的,在阅读相关方法之前,有必要对 rollup 进行一些基本了解。以下是 rollup 官网对其打包过程的代码描述。rollup javascript API
    image.png

    可以看出 rollup 一般的打包需要

    1. 打包的配置参数inputOptions
    2. 打包生成文件的配置参数 outputOptions
    3. 调用rollup.rollup()返回一个 bundle 对象
    4. 调用bundle.generate() 或者bundle.write() 完成打包
      那么 vite 的 build 也应该是按照这个流程来的

    build.ts

    build.tsbuild方法主要是指行了dobuild方法,dobuild做了以下几件事(ssr 相关的先不考虑):

    1. 整理配置参数=> config
    const config = await resolveConfig(inlineConfig, "build", "production");
    
    1. rollup 打包输入参数=> RollupOptions,在此之前处理了一下对于 RollupOptions 对象比较重要的 input 参数和 external
    const RollupOptions: RollupOptions = {
        input,
        preserveEntrySignatures: ssr
          ? "allow-extension"
          : libOptions
          ? "strict"
          : false,
        ...options.rollupOptions,
        plugins,
        external,
        onwarn(warning, warn) {
          onRollupWarning(warning, warn, config);
        },
      };
    
    1. rollup 打包输出参数 outputs(一般我们在项目开发中 outputs 就是个 obj,但在构建库时可能需要生成不同格式的包,所以 outputs 也可能是个数组)
    const outputs = resolveBuildOutputs(
      options.rollupOptions?.output,
      libOptions,
      config.logger
    );
    
    1. rollup 还提供了一个 watch 功能,vite 这里也进行相应的实现直达 rollup-watch
    if (config.build.watch) {
      config.logger.info(chalk.cyanBright(`\nwatching for file changes...`));
    
      const output: OutputOptions[] = [];
      if (Array.isArray(outputs)) {
        for (const resolvedOutput of outputs) {
          output.push(buildOuputOptions(resolvedOutput));
        }
      } else {
        output.push(buildOuputOptions(outputs));
      }
    
      const watcherOptions = config.build.watch;
      const watcher = rollup.watch({
        ...rollupOptions,
        output,
        watch: {
          ...watcherOptions,
          chokidar: {
            ignored: [
              "**/node_modules/**",
              "**/.git/**",
              ...(watcherOptions?.chokidar?.ignored || []),
            ],
            ignoreInitial: true,
            ignorePermissionErrors: true,
            ...watcherOptions.chokidar,
          },
        },
      });
    
      watcher.on("event", (event) => {
        if (event.code === "BUNDLE_START") {
          config.logger.info(chalk.cyanBright(`\nbuild started...`));
          if (options.write) {
            prepareOutDir(outDir, options.emptyOutDir, config);
          }
        } else if (event.code === "BUNDLE_END") {
          event.result.close();
          config.logger.info(chalk.cyanBright(`built in ${event.duration}ms.`));
        } else if (event.code === "ERROR") {
          outputBuildError(event.error);
        }
      });
    
      // stop watching
      watcher.close();
    
      return watcher;
    }
    
    
    1. 生成 bundle 对象
    const bundle = await rollup.rollup(rollupOptions);
    
    1. 调用 bundle.write 方法,写到文件中,大功告成。在这之前还调用prepareOutDir方法(确认了打包目录是否存在及清理了该目录)
    if (options.write) {
      prepareOutDir(outDir, options.emptyOutDir, config);
    }
    
    if (Array.isArray(outputs)) {
      const res = [];
      for (const output of outputs) {
        res.push(await generate(output));
      }
      return res;
    } else {
      return await generate(outputs);
    }
    

    后续计划

    精读 vite,看一些细节部分的实现,如对import { createApp } from 'vue'的处理

    其他

    本次阅读相关的注释在vite 源码梳理,可以通过 vscode 插件 todo tree, 前缀’WK’的都是我看的过程中加的注释

    image.png

    展开全文
  • VueJS 3具有: Vite2 TS(x) 埃斯林特
  • 1. 前言大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。已进行四个月了,很多小伙伴表示收获颇丰。...

    1. 前言

    大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。已进行四个月了,很多小伙伴表示收获颇丰。

    想学源码,极力推荐之前我写的《学习源码整体架构系列》 20余篇源码文章。

    本文仓库 only-allow-analysis,求个star^_^[1]

    最近组织了源码共读活动,每周大家一起学习200行左右的源码。每周一期,已进行到14期。于是搜寻各种值得我们学习,且代码行数不多的源码。

    阅读本文,你将学到:

    1. 如何学习调试源码
    2. 学会 npm 钩子
    3. 学会 "preinstall": "npx only-allow pnpm" 一行代码统一规范包管理器
    4. 学到 only-allow 原理
    5. 等等

    2. 场景

    我们项目开发时,常需要安装依赖,虽说一般用文档可以说明。但不是比较强制的约束。是人就容易犯错或者疏忽,假如规定是用的npm,而团队里有人某一天不小心使用了其他包管理器安装了的其他依赖,上传了代码,严重时可能导致线上问题。所以我们需要借助工具(代码)来强制约束。

    在源码共读第12期[2]中,我们学习了尤雨溪推荐神器 ni ,能替代 npm/yarn/pnpm ?简单好用!源码揭秘!根据锁文件自动匹配相应的包管理器,运行相应的命令。

    在源码共读第3期[3]中,我们学习了Vue 3.2 发布了,那尤雨溪是怎么发布 Vue.js 的?

    其中 Vue3 源码用了 npm 的 preinstall 钩子[4] 约束,只能使用 pnpm 安装依赖。我们接着来看其实现。

    3. Vue3 源码 && npm 命令钩子

    // vue-next/package.json
    {
      "private": true,
      "version": "3.2.22",
      "scripts": {
        "preinstall": "node ./scripts/preinstall.js",
      }
    }
    依次执行
    # install 之前执行这个脚本
    preinstall
    # 执行 install 脚本
    install
    # install 之后执行这个脚本
    postinstall

    当然也支持自定义的命令。

    更多可以查看官方文档钩子[5]

    接着我们来看 preinstall[6] 源码。

    // vue-next/scripts/preinstall.js
    
    if (!/pnpm/.test(process.env.npm_execpath || '')) {
      console.warn(
        `\u001b[33mThis repository requires using pnpm as the package manager ` +
          ` for scripts to work properly.\u001b[39m\n`
      )
      process.exit(1)
    }

    这段代码也相对简单,校验如果不是 pnpm 执行脚本则报错,退出进程。

    关于 process 对象可以查看 阮一峰老师 process 对象[7]

    process.argv 属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是 node,第二个成员是脚本文件名,其余成员是脚本文件的参数。

    这段代码能解决文章开头场景提出的问题,但是总不能每个项目都复制粘贴这段代码吧。我们是不是可以封装成 npm 包使用。当时我也没想太多,也没有封装 npm 包。直到我翻看 vite[8] 源码发现了 only-allow[9] 这个包。一行代码统一规范包管理器

    {
      "scripts": {
        "preinstall": "npx only-allow pnpm -y"
      }
    }

    当时看到这段代码时,我就在想:他们咋知道这个的。当时依旧也没想太多。直到有一天,发现 pnpm 文档 Only allow pnpm 文档[10] 上就有这个。好吧,吃了没看文档的亏。那时我打算分析下这个only-allow 包的源码[11],打开一看惊喜万分,才 36 行,写它,于是写了这篇文章。

    按照惯例,看源码前先准备环境。

    4. 环境准备

    先克隆代码。

    4.1 克隆代码

    # 推荐克隆我的源码库
    git clone https://github.com/lxchuan12/only-allow-analysis.git
    cd only-allow-analysis/only-allow
    # npm i -g pnpm
    pnpm i
    
    # 或者克隆官方仓库
    git clone https://github.com/pnpm/only-allow.git
    cd only-allow
    # npm i -g pnpm
    pnpm i

    开源项目一般先看README.md[12]

    Force a specific package manager to be used on a project

    强制在项目上使用特定的包管理器

    Usage

    Add a preinstall script to your project's package.json.

    If you want to force yarn[13], add:

    {
      "scripts": {
        "preinstall": "npx only-allow yarn"
      }
    }

    同理可得:强制使用 npmpnpm也是类似设置。

    4.2 调试源码

    我们通过查看 package.json 文件。

    // only-allow/package.json
    {
      "bin": "bin.js",
    }

    确定主入口文件为 only-allow/bin.js

    在最新版的 VSCode 中,auto attach 功能,默认支持智能调试,如果发现不支持,可以通过快捷键 ctrl + shift + p 查看是否启用。

    于是我们在 only-allow/package.json 文件中,添加如下命令。

    // only-allow/package.json
    {
      "scripts": {
        "preinstall": "node bin.js pnpm"
      },
    }

    可以提前在 only-allow/bin.js 文件打上断点 const usedPM = whichPMRuns()

    按快捷键 ctrl + ` 快捷键打开终端。输入如下 yarn add release-it -D 命令,即可调试 only-allow/bin.js

    3765aa9c5feb908512584d4d60661af4.png

    调试截图

    最终调试完会在终端报错提示使用 pnpm install

    如下图所示:

    92c761744ed79af76c0f4dd9f88e0c1a.png

    终端报错截图

    更多调试细节可以看我的这篇文章:新手向:前端程序员必学基本技能——调试JS代码[14]

    接着我们按调试来看源码主流程。

    5. only-allow 源码

    // only-allow/bin.js
    #!/usr/bin/env node
    const whichPMRuns = require('which-pm-runs')
    // 输出边框盒子
    const boxen = require('boxen')
    
    const argv = process.argv.slice(2)
    if (argv.length === 0) {
      console.log('Please specify the wanted package manager: only-allow <npm|pnpm|yarn>')
      process.exit(1)
    }
    // 第一个参数则是 用户传入的希望使用的包管理器
    // 比如 npx only-allow pnpm 
    // 这里调试是 node bin.js pnpm
    const wantedPM = argv[0]
    // npm pnpm yarn 都不是,则报错
    if (wantedPM !== 'npm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn') {
      console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, pnpm, or yarn.`)
      process.exit(1)
    }
    // 使用的包管理器
    const usedPM = whichPMRuns()
    // 希望使用的包管理器 不相等,则报错。
    // - npm  提示使用 npm install
    // - pnpm 提示使用 pnpm install
    // - yarn 提示使用 yarn install
    // 最后退出进程
    if (usedPM && usedPM.name !== wantedPM) {
      const boxenOpts = { borderColor: 'red', borderStyle: 'double', padding: 1 }
      switch (wantedPM) {
        case 'npm':
          console.log(boxen('Use "npm install" for installation in this project', boxenOpts))
          break
        case 'pnpm':
          console.log(boxen(`Use "pnpm install" for installation in this project.
    
    If you don't have pnpm, install it via "npm i -g pnpm".
    For more details, go to https://pnpm.js.org/`, boxenOpts))
          break
        case 'yarn':
          console.log(boxen(`Use "yarn" for installation in this project.
    
    If you don't have Yarn, install it via "npm i -g yarn".
    For more details, go to https://yarnpkg.com/`, boxenOpts))
          break
      }
      process.exit(1)
    }

    跟着断点,我们可以查看到 which-pm-runs[15]

    6. which-pm-runs 当前运行的是哪一个包管理器

    最终返回包管理器和版本号。

    根据调试可知,process.env.npm_config_user_agent 是类似这样的字符串。

    "yarn/1.22.10 npm/? node/v14.16.0 linux x64"

    'use strict'
    
    module.exports = function () {
      if (!process.env.npm_config_user_agent) {
        return undefined
      }
      return pmFromUserAgent(process.env.npm_config_user_agent)
    }
    
    function pmFromUserAgent (userAgent) {
      const pmSpec = userAgent.split(' ')[0]
      const separatorPos = pmSpec.lastIndexOf('/')
      return {
        name: pmSpec.substr(0, separatorPos),
        version: pmSpec.substr(separatorPos + 1)
      }
    }

    6.1 String.prototype.substr 截取字符串

    顺带提下。我之前在 vue-next 源码看到的 pull request => chore: remove deprecated String.prototype.substr[16]

    String.prototype.substr is deprecated.

    也就是说不推荐使用 substr。推荐使用 slice

    ecma 规范[17]

    7. 总结

    我们通过从团队需要规范统一包管理器的实际场景出发,讲了 vue3 源码中 preinstall 钩子 约束只能使用 pnpm 。同时通过查看 vite[18] 源码和 pnpm[19] 文档,了解到 only-allow[20] 这个包。可以做到一行代码统一规范包管理器"preinstall": "npx only-allow pnpm"

    也学习了其原理。only-allow 期待的包管理器和运行的包管理器对比。匹配失败,则报错。而which-pm-runs 通过获取 process.env.npm_config_user_agent 变量获取到当前运行脚本的包管理器和版本号。

    我们通过文档和沟通约束,不如用工具(代码)约束。

    文章写到这里,让我想起我2018年写的文章参加有赞前端技术开放日所感所想[21]

    当时演讲的大佬说过一句话。无比赞同。

    技术(开源)项目本质上是:理念、套路、规范的工具化。

    同时给我们的启发也是要多看官方文档和规范。

    建议读者克隆我的仓库[22]动手实践调试源码学习。

    最后可以持续关注我@若川。欢迎加我微信 ruochuan12[23] 交流,参与 源码共读[24] 活动,每周大家一起学习200行左右的源码,共同进步。

    参考资料

    [1]

    本文仓库 only-allow-analysis,求个star^_^: https://github.com/lxchuan12/only-allow-analysis.git

    最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。

    推荐阅读

    1个月,200+人,一起读了4周源码
    我历时3年才写了10余篇源码文章,但收获了100w+阅读

    老姚浅谈:怎么学JavaScript?

    我在阿里招前端,该怎么帮你(可进面试群)

    aacbcea36749e00447e5a51007bcc265.gif

    ················· 若川简介 ·················

    你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》10余篇,在知乎、掘金收获超百万阅读。
    从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结
    同时,最近组织了源码共读活动,帮助1000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

    99c451672624f46aacf7a0247525fbf8.png

    识别方二维码加我微信、拉你进源码共读

    今日话题

    略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

    展开全文
  • vite-源码

    2021-03-08 15:18:33
    学习访问 参考 逻辑 创建服务 托管静态资源 重建模块路劲 解析模块路径 客户端注入 解析.vue文件 分析性解析 处理拆解内容
  • // vite.config.js import VitePluginHtmlEnv from 'vite-plugin-html-env' export default { plugins : [ VitePluginHtmlEnv ( ) , // or // VitePluginHtmlEnv({ // CUSTOM_FIELD // }) ] } 建议使用VITE...
  • vite2.0源码解读学习

    2021-08-12 13:57:58
    vite采用http请求的方式 递归请求 vue文件,转换成浏览器认识的javascript文件(注意不再是vue文件) 预编译(预打包) 将请求到的文件递归方式预编译,采用相对路径方式加载node_modules中当前插件(比如vue)下的...
    1. 浏览器采用es module imports 关键的变化是index.html 中 main.js的引入方式
      在这里插入图片描述

    2. vite采用http请求的方式 递归请求 vue文件,转换成浏览器认识的javascript文件(注意不再是vue文件)

    3. 预编译(预打包) 将请求到的文件递归方式预编译,采用相对路径方式加载node_modules中当前插件(比如vue)下的package.json中的module,这个地址代表打包js文件地址,浏览器需要加载这个地址。
      在这里插入图片描述
      然后 ctx.body = rewriteImport(上文提到的打包的js文件) ( rewriteImport())采用递归方式处理 比如 import vue from ‘vue’
      将这种文件处理成 相对路径的方式 去加载module

    展开全文
  • vite原始码阅读 1,基本流程 2,HMR
  • [存档] create-vite-app 此已移至 。
  • vite-sample:vite示例项目
  • create-vite源码解析

    千次阅读 2021-12-29 17:39:26
    create-vite

    create-vite

    今天我们来看看vite的脚手架。

    这个包主要是用于创建一个项目并根据用户选择配置的template将模板文件写入当前创建的目录中。

    vite提供了多个模板及其ts版本。

    使用minimist解析命令行参数
    使用prompts包来实现命令行指引配置的功能。
    使用kolorist包实现不同颜色的关键词。

    来看一下这些包的简单使用,方便后续查看create-vite中的源码。

    minimist

    通过process.argv获取命令行参数的时候前两个值是固定的,第一个是node程序路径,第二个则是当前执行的文件路径。之后的才是输入的各种参数
    请添加图片描述

    const argv = require('minimist')(process.argv.slice(2), { string: ['_'] })
    

    使用minimist后可以把输入的参数进行解析,使用_保存命令中的各种参数,当匹配到-或者--字符时忽略后边的所有的参数。---字符后边的命令会添加到对象中,当命令后边有参数(非options),那么该命令的值就是后边的参数,否则值为true

    请添加图片描述

    比如上边简单的例子,在-命令前有俩参数,则被push进_数组中,-v后边的参数也是options,因此值为true,-f后边的参数是12,因此值为12。

    prompts

    轻量级,美观且用户友好的交互式提示库。

    单个提示的传入一个对象即可

    const prompts = require('prompts');
    
    (async () => {
      const response = await prompts({
        type: 'text',
        name: 'weather',
        message: 'What is the weather today?',
      });
    
      console.log(response);
    })();
    

    请添加图片描述

    多个就需要传入数组。

    const prompts = require('prompts');
    
    (async () => {
      const response = await prompts([
        {
          type: 'text',
          name: 'weather',
          message: 'What is the weather today?',
        },
        {
          type: 'confirm',
          name: 'out',
          message: 'Are you going out for fun now?',
        },
      ]);
    
      console.log(response);
    })();
    

    请添加图片描述

    动态prompts

    typenull时可以跳过当前这个prompt

    (async () => {
      const response = await prompts([
        {
          type: 'text',
          name: 'weather',
          message: 'What is the weather today?',
        },
        {
          type: 'confirm',
          name: 'out',
          message: 'Are you going out for fun now?',
        },
        {
          type: (pre) => (pre ? 'text' : null),
          name: 'fun',
          message: 'Have fun then',
        },
      ]);
    
      console.log(response);
    })();
    

    请添加图片描述

    type为一个函数时,有3个参数,第一个是上一条prompt的值,第二个是之前所有prompts的键值组成的对象,第三个则是当前prompt对象。

    kolorist

    用于输出不同颜色的字符。

    const { yellow, green, cyan, blue } = require('kolorist');
    
    console.log(yellow('yellow'));
    console.log(green('green'));
    console.log(cyan('cyan'));
    console.log(blue('blue'));
    

    请添加图片描述

    了解以上几个包的简单使用后,我们来看看create-vite是如何使用这些包来搭建脚手架的。

    脚手架配置

    项目目录:

    请添加图片描述

    1. 配置项目名

    获取命令后的第一个参数默认为项目名,当未获取到参数时提示用户进行配置,否则跳过进行下一步。

    let targetDir = argv._[0]
    
    {
        type: targetDir ? null : 'text',
        name: 'projectName',
        message: 'Project name:',
        initial: defaultProjectName,
        onState: (state) =>
          (targetDir = state.value.trim() || defaultProjectName)
    },
    

    2. 判断项目名是否跟当前已存在的文件名冲突

    如果当前目录下不存在重名的文件或者重名文件夹为空,那么跳过进行下一步,否则提示是否覆盖当前文件,为true进行下一步,为false终止当前命令。

    {
        type: () =>
          !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
        name: 'overwrite',
        message: () =>
          (targetDir === '.'
            ? 'Current directory'
            : `Target directory "${targetDir}"`) +
          ` is not empty. Remove existing files and continue?`
    }
    

    3. 覆盖文件

    当上一步选择覆盖文件时则会进行该提示,用户选择true时进行下一步,否则终止命令。

    {
        type: (_, { overwrite } = {}) => {
          if (overwrite === false) {
            throw new Error(red('✖') + ' Operation cancelled')
          }
          return null
        },
        name: 'overwriteChecker'
    }
    

    4. 判断文件名是否有效

    当文件名有效时跳过进行下一步,如果当前输入的文件名无效,则会提示重新输入,并且对新名字进行相应的匹配判断,只有当validate方法的值为true才会进行下一步。

    // 匹配规则
    function isValidPackageName(projectName) {
      return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
        projectName
      )
    }
    
    // 替换规则
    function toValidPackageName(projectName) {
      return projectName
        .trim()
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/^[._]/, '')
        .replace(/[^a-z0-9-~]+/g, '-')
    }
    
    {
        type: () => (isValidPackageName(targetDir) ? null : 'text'),
        name: 'packageName',
        message: 'Package name:',
        initial: () => toValidPackageName(targetDir),
        validate: (dir) =>
          isValidPackageName(dir) || 'Invalid package.json name'
    }
    

    5. 模板选择

    首先会判断命令行中有没有携带模板参数且该模板存在,有的话进行下一步,没有就展示已有的多个模板提供用户进行选择

    let template = argv.template || argv.t
    
    {
        type: template && TEMPLATES.includes(template) ? null : 'select',
        name: 'framework',
        message:
          typeof template === 'string' && !TEMPLATES.includes(template)
            ? `"${template}" isn't a valid template. Please choose from below: `
            : 'Select a framework:',
        initial: 0,
        choices: FRAMEWORKS.map((framework) => {
          const frameworkColor = framework.color
          return {
            title: frameworkColor(framework.name),
            value: framework
          }
        })
    }
    

    6. 语言选择

    当选完模板后,vite还提供了jsts俩种语言,继续选择。

    {
        type: (framework) =>
          framework && framework.variants ? 'select' : null,
        name: 'variant',
        message: 'Select a variant:',
        // @ts-ignore
        choices: (framework) =>
          framework.variants.map((variant) => {
            const variantColor = variant.color
            return {
              title: variantColor(variant.name),
              value: variant.name
            }
        })
    }
    

    7. 生成目录

    执行完后就会返回一个对象,包含了所有命令配置信息。

    根据之前的是否覆盖选项,进行文件夹生成或者文件清空操作。

    const { framework, overwrite, packageName, variant } = result
    const root = path.join(cwd, targetDir);
    
    if (overwrite) {
      emptyDir(root);
    } else if (!fs.existsSync(root)) {
      fs.mkdirSync(root);
    }
    

    8. 文件写入

    根据选择的模板,将模板文件写入上一步的目录中。

    package.json文件需要额外处理一下,因为要把项目名称及后续的一些提示也给写上去。

    template = variant || framework || template;
    
    console.log(`\nScaffolding project in ${root}...`);
    
    const templateDir = path.join(__dirname, `template-${template}`);
    
    const write = (file, content) => {
      const targetPath = renameFiles[file]
        ? path.join(root, renameFiles[file])
        : path.join(root, file);
      if (content) {
        fs.writeFileSync(targetPath, content);
      } else {
        copy(path.join(templateDir, file), targetPath);
      }
    };
    
    const files = fs.readdirSync(templateDir);
    for (const file of files.filter((f) => f !== 'package.json')) {
      write(file);
    }
    
    const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
    const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
    
    switch (pkgManager) {
      case 'yarn':
        console.log('  yarn');
        console.log('  yarn dev');
        break;
      default:
        console.log(`  ${pkgManager} install`);
        console.log(`  ${pkgManager} run dev`);
        break;
    }
    

    整一个create-vite包原理其实就是这么简单,模板文件那些就不进行分析了,后续继续推出vite核心原理解析。

    展开全文
  • 照顾孩子一般为你讲解Vite源码。 NPM 依赖解析和预构建: 全面提升页面重载速度和强缓存依赖。 Plugins 插件:可以利用 Rollup 插件的强大生态系统,同时根据需要也能够扩展开发服务器和 SSR 功能。 动态模块...
  • vite-test-example 测试的目录结构和命令与@vue/cli创建的项目相同。 端到端测试 该存储库以赛普拉斯为例,说明了如何使用Vite设置端到端测试。 要尝试,请运行: yarn test:e2e 使用Vite运行端到端测试与其他...
  • 本文将会回答Vite为什么选择Rollup作为构建工具,为什么不用Rollup的热更新,以及为什么不用webpack。一、Vite生产环境为什么选择Rollup做构建工具。Vite是一个由原生ESM驱动的Web开发构建工具。在选择构建工具的...
  • Vue 3 +打字稿+ Vite模板 开发前须知 编辑器推荐使用VS Code,当使用VS Code进行开发的时候,需要安装下面的插件以提供更好的开发体验: 威图 更漂亮-代码格式化程序 VS Code的EditorConfig ESLint 代码提交使用...
  • vite预构建源码梳理

    2021-08-15 18:03:17
    "这个问题vite 文档已经解释的很清楚了,那么预构建大概的流程是什么样的呢? 启动预构建 从文档中我们知道在服务启动前会进行预构建,对应源码位置在src/node/server/index.ts,预构建的函数名是optimizeDeps ... ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,366
精华内容 946
关键字:

vite源码