# vue-repl支持less

参考 vue 官方 Vue Playground

Vue Playground

Vue Playground,它提供一个在线的可交互编辑环境,实现在线上直接调试文档提供的案例代码, 而且十分的轻量。

# 1、什么是REPL(交互式解释器)

REPL(Read Eval Print Loop:交互式解释器) 表示一个我们可以在其中输入命令或者代码,并且可以接收到解释器响应的一个环境。主要有四大特征组成。

  • 读取 Read - 读取用户输入,解析输入的数据结构并存储在内存中。
  • 执行 Eval - 执行输入的数据结构
  • 打印 Print - 输出结果
  • 循环 Loop - 循环操作以上步骤直到用户退出。

也就是说我们现在所用的所有代码编辑器其实都是一个REPL(交互式解释器)

# 2、什么是在线REPL(交互解释器)

我们日常使用的代码编辑器大多是通过软件包下载到本地安装的,因为在本地系统环境下可以得到更好更流畅的交互和编译体验。但是在某些场景下,网页版的交互解释器更具有优势,比如

  • 我有一段代码,我想分享给别人看看,通过网页URL的形式明显比传输代码包的形式更方便
  • 我写了一个组件,需要有个预览地址或者内嵌一个iframe预览链接放在我的文档里面
  • 我想在最简环境下复现一个bug,但是我又觉得重新开一个项目太麻烦,我希望有一个网页,打开就能直接写代码,最好我常用的依赖都内置在上面
  • 可以把在线编辑的代码下载下来在本地运行

很巧,上面这些场景Vue的Playground都比较好的解决了

# 3、存在的问题

虽然 Vue 的 Playground 具备了在线交互解释器的所有特征,但是他只预置了Vue的运行环境,假设我想预置更多的模块,比如我想让他天然支持less,支持scss,应该怎么做呢?

# 4、如何实现

首先打开Vue Playground的源码可以发现,整个交互解释器的核心在@vue/repl这个依赖中,Vue Playground也只是对@vue/repl进行了集成。

@vue/repl 源码 (opens new window)

# 4.1 下载源码

直接 git clone https://github.com/vuejs/repl.git

# 4.2 安装库

安装库 pnpm i

安装less pnpm i less -D

安装less type pnpm i --save-dev @types/less

# 4.3 修改源码

(1) 导入 less

./src/transform.ts

import { Store, File } from './store'
import {
  SFCDescriptor,
  BindingMetadata,
  shouldTransformRef,
  transformRef,
  CompilerOptions
} from 'vue/compiler-sfc'
import { transform } from 'sucrase'
// 新增 导入 less
import less from 'less'

...
1
2
3
4
5
6
7
8
9
10
11
12
13

(2)在 compileFile 方法里面加入.less判断

export async function compileFile(
  store: Store,
  { filename, code, compiled }: File
) {
  if (!code.trim()) {
    store.state.errors = []
    return
  }

  if (filename.endsWith('.css')) {
    compiled.css = code
    store.state.errors = []
    return
  }

  // 加入 less文件判断
  if (filename.endsWith('.less')) {
    const outStyle = await less.render(code) // 使用less.render将 less code 转换成css
    compiled.css = outStyle.css
    store.state.errors = []
    return
  }

  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

(3)移除判断 lang


if (
    descriptor.styles.some((s) => s.lang) ||
    (descriptor.template && descriptor.template.lang)
  ) {
    store.state.errors = [
      `lang="x" pre-processors for <template> or <style> are currently not ` +
        `supported.`
    ]
    return
  }

// 改为
if (
    // descriptor.styles.some((s) => s.lang) || // 主要移除这段代码
    (descriptor.template && descriptor.template.lang)
  ) {
    store.state.errors = [
      `lang="x" pre-processors for <template> or <style> are currently not ` +
        `supported.`
    ]
    return
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

(4)添加 lang=less 判断

找到 styles 代码如下:

// styles
  let css = ''
  for (const style of descriptor.styles) {
    if (style.module) {
      store.state.errors = [
        `<style module> is not supported in the playground.`
      ]
      return
    }

    // 添加新代码
    let contentStyle = style.content
    if (style.lang === 'less') {
      const outStyle = await less.render(contentStyle)
      contentStyle = outStyle.css
    }

    const styleResult = await store.compiler.compileStyleAsync({
      ...store.options?.style,
      source: contentStyle,  // 这里将 style.content 修改为 contentStyle
      filename,
      id,
      scoped: style.scoped,
      modules: !!style.module
    })
    if (styleResult.errors.length) {
      // postcss uses pathToFileURL which isn't polyfilled in the browser
      // ignore these errors for now
      if (!styleResult.errors[0].message.includes('pathToFileURL')) {
        store.state.errors = styleResult.errors
      }
      // proceed even if css compile errors
    } else {
      css += styleResult.code + '\n'
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 4.4 修改支持 .less 文件的添加

./src/editor/FileSelector.vue

找到 doneAddFile 方法,修改添加 .less 过滤

function doneAddFile() {
  if (!pending.value) return
  const filename = pendingFilename.value

  if (!/\.(vue|js|ts|css|less)$/.test(filename)) { // 添加 |less
    store.state.errors = [
      `Playground only supports *.vue, *.js, *.ts, *.css files.`
    ]
    return
  }

  if (filename in store.state.files) {
    store.state.errors = [`File "${filename}" already exists.`]
    return
  }

  store.state.errors = []
  cancelAddFile()
  store.addFile(filename)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5、测试编译 less

在示例中加入 less 代码或者 导入 less 代码

<script setup>
import './aa.less' // 导入 less
import {list} from './t.ts'
import { ref } from 'vue'

const msg = ref('Hello world!')
console.log(list)
</script>

<template>
  <h1>{{ msg }}</h1>
  <div class="test">
    111
    <div class="test-page">
      page1233
    </div>
  </div>
  <input v-model="msg">
</template>
<style lang="less" scoped>
  h1{
    color:red
  }
  .test{
    font-size:18px;
    &-page{
      font-size:30px;
    }
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

效果如下:

Vue Playground and Less

aa.less 代码:

Less code

参考:https://juejin.cn/post/7064864648729722887

上次更新: 11/28/2023, 7:06:34 PM