26

编写一个Vite插件

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI0MDIwNTQ1Mg%3D%3D&%3Bmid=2676494547&%3Bidx=2&%3Bsn=f5fe5677d7784cb8795a1e57b537ba75
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

EBjmaeU.png!mobile

来源:https://medium.com/@axwdev
作者:Andrew Walker

随着Vue 3最近进入发布候选阶段,是你尝试一下的绝佳时机。要马上开始,你需要使用vite,Vue的创建者提供的新的web开发构建工具。Vite提供了一个新的插件系统来扩展Vite的功能。今天我们来看看如何设置一个简单的vite插件。

目录:

  • 这个插件会做什么?

  • 关于vite

    • 什么是vite?

    • Vite插件概念

  • 编写我们的插件

    • 自动生成vue-router路由

    • 创建一个空插件

    • 为本地开发开辟道路

    • 在生产版本中访问我们的路由

    • 添加自定义块

    • 未来的工作

  • 结束

这个插件会做什么?

我们的插件将根据Vue组件的目录自动生成Vue-Router路由。这从Nuxt的路由功能得来的灵感,即我们想把这个目录结构:

src/
|-- pages/
    |-- about.vue
    |-- contact.vue

自动进入以下路由:

[
  {
    name: 'about',
    path: '/about',
    component: '/src/pages/about.vue'
  },
  {
    name: 'contact',
    path: '/contact',
    component: '/src/pages/contact.vue'
  },
]

关于vite

什么是vite?

vite 是一个基于 Vue3 单文件组件的非打包开发服务器,Vite提供了比vue-cli和其他基于webpack的设置更快的构建时间。它做到了本地快速开发启动:

  1. 快速的冷启动,不需要等待打包操作;

  2. 即时的热模块更新,替换性能和模块数量的解耦让更新飞起;

  3. 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。

Vite插件概念

在撰写本文时,还没有真正的文档来创建vite插件。vite仓库自述文件中讨论了一些概念,但是你需要在vite源代码中进行一些挖掘,以确切地了解如何编写插件。让我们讨论一些关键概念。

dev server:当涉及到javascript时,现代浏览器可以处理(某种程度上)ES模块导入,但是如果遇到其他文件类型的导入则不能。每次浏览器在代码中找到导入时,它将先通过vite的开发服务器,然后直接提供给浏览器。这些导入可以是javascript,vue,css——如果你告诉vite如何处理它,则可以是任何东西。这就是为什么Vite开发服务器如此有用的原因——它为你解决了该浏览器的限制。

rollup production bundle:对于静态内容,你不需要在生产构建中使用vite dev服务器。因此,vite使用 rollup [1] 将你的代码捆绑用于生产。

Vue自定义块转换:

有时你使用的其他库会让你在vue文件中添加自定义块,比如 <docs><story> 。Vite让你指定遇到这些块时如何处理。

编写我们的插件

我们采用的一般方法是自动生成一个 vue-auto-routes.js 文件,然后从中导出路由数组。我们将把该文件视为虚拟文件,该文件是在运行时(对于开发服务器)和构建时(对于汇总)动态生成的。然后,我们可以在我们的代码中导入这些路由,并在我们的应用程序中使用它们。

自动生成vue-router路由

为了填充我们的 vue-auto-routes.js 文件,我们需要解析我们的 src/pages 目录,并把它变成一些导入语句和一个路由数组。我们将粗略地使用内置的 fs Node模块执行此操作:

function parsePagesDirectory() {
  const files = fs
    .readdirSync('./src/pages')
    .map((f) => ({ name: f.split('.')[0], importPath: `/src/pages/${f}` }))

  const imports = files.map((f) => `import ${f.name} from '${f.importPath}'`)

  const routes = files.map(
    (f) => `{
        name: '${f.name}',
        path: '/${f.name}',
        component: ${f.name},
      }
      `,
  )

  return { imports, routes }
}

我们从该函数返回两个列表:

  • imports :我们的导入语句列表,例如“import about from 'src/pages/about.vue'”
  • routes :我们的路由数组,例如“{ name: 'about', path: '/about', component: about }”

请记住,现在这些是字符串,因为我们正在使用它们创建一个javascript文件。请注意,对于路由数组中的组件,我们使用导入的变量,而不是字符串或动态组件——你将在后面看到原因。

创建一个空插件

Vite插件只是一个添加了各种选项的对象。我们将很快开始添加这些选项。让我们创建一个实际上可以用作插件的javascript文件:

module.exports = function() {
  return {}
}

然后,我们可以在 vite.config.js 中使用它,如下所示:

const viteAutoRoute = require('./plugin.js')

module.exports = {
  plugins: [viteAutoRoute()],
}

我们使用一个函数而不只是一个普通的对象,以便将来我们可以向插件添加自己的自定义选项。例如,也许我们想使 用 viteAutoRoute({ pagesDir: './src/docs' }) 之类的名称指定自定义页面目录。

为本地开发开辟道路

现在让我们实际利用我们之前创建的那些路由。为此我们将使用 configureServer vite插件选项。

module.exports = function () {
  const { imports, routes } = parsePagesDirectory()

  const moduleContent = `
    ${imports.join('\n')}
    export const routes = [${routes.join(', \n')}]
  `

  const configureServer = [
    async ({ app }) => {
      app.use(async (ctx, next) => {
        if (ctx.path.startsWith('/@modules/vue-auto-routes')) {
          ctx.type = 'js'
          ctx.body = moduleContent
        } else {
          await next()
        }
      })
    },
  ]
  
  return { configureServer }
}

首先,我们创建一个字符串 moduleContent ,其中包含路由javascript文件的所需内容。然后,我们创建 configureServer ——将用作vite开发服务器中的附加中间件的函数列表。每当vite在你的javascript或vue文件中遇到导入时,它就会向其开发服务器发出请求,在必要时进行一些转换,然后以浏览器可以处理的形式发送回来。

当它遇到类似 import { routes } from 'vue-auto-routes' 这样的东西时,它将会发出 @/modules/vue-auto-routes 的请求。因此,我们要做的是拦截该请求,并将生成的 moduleContent 作为正文返回,并将其声明为 js 类型。

最后,我们将此 configureServer 数组添加到返回的对象中,以供Vite使用。Vite将看到这一点,并将我们的清单(共1个)中间件与自己的中间件合并。现在,我们可以在自己的路由器中使用这些动态生成的路由:

import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import { routes } from 'vue-auto-routes'
import App from './App.vue'

const router = createRouter({
  history: createWebHashHistory(),
  routes,
})
createApp(App).use(router).mount('#app')

现在,当我们运行 yarn dev 时,我们的路由将起作用,例如http://localhost:3000/#/about :tada:

请注意,我们使用的是 vue-router-next [2] ——Vue 3即将推出的路由器。

在生产版本中访问我们的路由

当我们运行 yarn build 时,它不会查看我们刚刚制作的 configureServer 东西,因为它使用rollup而不是vite的dev服务器。因此,我们需要添加一些其他配置以使其在生产中正常工作:

const virtual = require('@rollup/plugin-virtual')

module.exports = function () {
  // 我们之前的moduleContent和configureServer ...

  const rollupInputOptions = {
    plugins: [virtual({ 'vue-auto-routes': moduleContent })],
  }

  return { configureServer, rollupInputOptions }
}

在这里,我们使用 rollupInputOptions vite插件选项,这使我们可以传递rollup插件。我们使用 @rollup/plugin-virtual 来获取模块名称,并让你返回所需的任何javascript内容。本质上与开发服务器解决方案做相同的事情。

现在,我们的路由将在本地开发和生产中都起作用。

添加自定义块

各个Vue页面可能希望为其所对应的路由提供其他选项。例如,我们可能想要向我们的路由添加一些其他元选项,或使用自定义路由名称。为此,我们将重新实现 vue-cli-plugin-auto-routing [3] 的路由块,以便在vue组件中执行以下操作:

<route>
{
  "meta": {
    "requiresLogin": true,
  }
}
</route>

为此,我们将使用 vueCustomBlockTransforms 选项,这允许你告诉vite在遇到vue文件中的自定义块时如何处理它们。由于这是我们要添加的最后一件事,因此让我们将其作为整个插件的一部分来看一下:

const fs = require('fs')
const virtual = require('@rollup/plugin-virtual')

function parsePagesDirectory() {
  const files = fs
    .readdirSync('./src/pages')
    .map((f) => ({ name: f.split('.')[0], importPath: `/src/pages/${f}` }))

  const imports = files.map((f) => `import ${f.name} from '${f.importPath}'`)

  const routes = files.map(
    (f) => `{
        name: '${f.name}',
        path: '/${f.name}',
        component: ${f.name},
        ...(${f.name}.__routeOptions || {}),
      }
      `,
  )

  return { imports, routes }
}

module.exports = function () {
  const { imports, routes } = parsePagesDirectory()

  const moduleContent = `
    ${imports.join('\n')}
    export const routes = [${routes.join(', \n')}]
  `

  const configureServer = [
    async ({ app }) => {
      app.use(async (ctx, next) => {
        if (ctx.path.startsWith('/@modules/vue-auto-routes')) {
          ctx.type = 'js'
          ctx.body = moduleContent
        } else {
          await next()
        }
      })
    },
  ]

  const rollupInputOptions = {
    plugins: [virtual({ 'vue-auto-routes': moduleContent })],
  }

  const vueCustomBlockTransforms = {
    route: ({ code }) => {
      return `
        export default function (Component) {
          Component.__routeOptions = ${code}
        }
      `
    },
  }

  return { configureServer, rollupInputOptions, vueCustomBlockTransforms }
}

我们已经添加了一个 vueCustomBlockTransforms 对象,该对象将关键 route (我们的块名)映射到一个函数,该函数本质上是返回另一个 虚拟 javascript文件。它有一个默认的导出函数,它为相关的 vue 文件取了一个组件,并添加了一个我们选择的额外字段 __routeOptions ,它映射到我们在任何自定义 <route> 块中声明的代码。

然后,我们在路由生成代码中使用它 ( ...(${f.name}.__routeOptions || {}) ),并将其与特定路由的其他设置合并。这就是为什么我们在虚拟的 vue-auto-routes 文件中导入组件的原因——因此我们可以访问已添加的 __routeOptions 字段。

现在,我们可以在vue页面组件中使用 $route.meta.requiresLogin 之类的东西!

未来的工作

我们希望在不久的将来发布这个插件的更完整的版本,增加更多的功能,比如改进的热重载,自动路由参数,嵌套路由等等。你可以在 这里 [4] 看到很早的开始。

结束

希望你已经了解了Vite的工作原理以及如何编写一些基本的插件。请留意Vue在不久的将来正式发布的V3版本,希望他们会提供更多关于你可以使用vite插件做的其他事情的文档。

推荐阅读

朝九晚五的工作,是否是这个时代安身立命的标准?

如果可以自由工作,人生又有多少种可能?

为了寻找答案,自媒体人林安探访了许多不上班的人,

希望他们不一样的人生故事,能给你疲惫的生活带去英雄梦想。

作者林安以“都市青年观察者”的身份深度采访了20个 自由职业者 ,以感性、细腻但又不失洞察力的笔触将20位自由职业者“非常态”的工作、生活方式展现在读者面前。 书中记录了勇士们的梦想、奋斗、坚持、迷茫与困惑,让读者了解那些不上班的人生,也许他们也可以踏出勇于改变的第一步。

粉丝福利资源

Web安全从入门到“放弃”视频教程

https://pan.baidu.com/s/1XdBdKk9zjGM0Dr5SxX2mcQ  密码: llgl

更多资源请查看本公众号“发现”菜单

NjUrEfj.jpg!mobile

参考资料

[1]

rollup: https://rollupjs.org/guide/en/

[2]

vue-router-next: https://github.com/vuejs/vue-router-next

[3]

vue-cli-plugin-auto-routing: https://github.com/ktsn/vue-cli-plugin-auto-routing

[4]

这里: https://github.com/ImpactBox/vite-plugin-auto-routes

如果对你有帮助,还可以 在看、留言、 转发

,这是对作者最大的帮助

uYJnEbJ.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK