Vite / Node 搭建 Less 本地编译输出 CSS 链接服务

工作中遇到个特殊的需求,项目要求改造一些老框架平台的样式。有多个平台要改,色彩主题是通用的,但是不同平台之间有一些小的差别。因为代码比较老,很多地方都是没办法用react、vue封装好的组件的,而且要求只改样式,不能动其他地方。

我们把样式写在我们的 monorepo 组件库里面,其他地方直接引用就行了

但是遇到一个问题,我们是用 less 写的,本地开发调试的时候预览比较麻烦,要反复跑编译的命令,所以就想整一个本地脚手架服务,自动监听 less 文件变更,然后输出单独的 css 链接地址,引用这个本地地址就行,页面只要刷新一下就能加载最新的样式

实现方法有很多,之前用 vite 写插件的时候觉得挺方便的,第一时间就先用 vite 写个本地插件,很简单的解决了这个问题

vite 插件实现

我们仓库里面 vite 跑的是 react 的模板,其他地方就不讲了,直接创建一个 plugins 文件夹,创建一个 online-less-server.ts 文件

在这里插入图片描述
这是我为了演示本地快速跑的 vite 脚手架,其他文件都是 vite 自带的引导创建的

第一个就是需要提供本地 less 编译服务的源文件,我们需要把 less 文件夹下面的所有 less 文件编译成 css,并且能直接通过一个本地的 URL 就能够访问到

如果你是第一次创建这个 react 模板,那还没办法直接用 less 文件,安装一下 less 就行,不需要做其他处理,vite 都处理好了

npm i less

不过,因为创建的插件是 ts 格式的,需要标记一下目录,让 ts 将对应文件转译成 js,打开 tsconfig.node.json

{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts", "plugins"]
}

这里只改了 include,增加了 plugins 这个目录

接下来就是插件代码了,我们打开 online-less-server.ts,根据约定,文件需要默认导出一个函数

export default function lessPlugin() {
  return {
    name: 'online-less-server',
  }
}

我们需要知道的是,如果我们的页面代码里面用到了 less 类型的文件,在已经安装了 less 的情况下,vite 是已经将对应的文件处理好了的,不需要我们再去处理。所以这里我的想法就是拿到这个已经处理好的文件

怎么拿呢

我是通过 transform 这个钩子拿的

let lessCodeMap = {}

export default function lessPlugin() {
  return {
    name: 'online-less-server',

    // 在其他钩子中使用存储的配置
    transform(code, id) {
      if (/\.less\??[^.]*$/.test(id)) {
        // 使用 lessCodeMap 存储 code
      }
    },
  }
}

transform 函数会传两个参数进来,第一个是编译好的代码,第二个是资源的源路径

这里用了一个正则去匹配以 less 结尾的文件。当然,如果我们仅针对特定目录下的 less 文件,就需要改造一下正则

let lessCodeMap = {}

export default function lessPlugin() {
  return {
    name: 'online-less-server',
    // 在其他钩子中使用存储的配置
    transform(code, id) {
      let n
      if (n = /\/less\/[A-Za-z\-]*\.less\??[^.]*$/.exec(id)) {
        lessCodeMap[n[0].split('/')[2]] = code;
      }
    },
  }
}

这里仅供参考,大家根据自己的文件结构来

这样我们就有了一个存储编译后 css 代码的对象,它的结构是这样的

{
  "文件名": "代码"
  ···
}

接下来是怎么使用这个对象

我们需要另外一个钩子,能够帮我们拦截指定路径的请求,我们拦截后返回对应的代码内容

这个钩子是 configureServer

let lessCodeMap = {}

export default function lessPlugin() {
  return {
    name: 'online-less-server',

    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        const pathList = req.url.split('/')
        if (pathList[1] === 'online-less-server' && lessCodeMap[pathList[2]]) {
          res.end(lessCodeMap[pathList[2]])
          return
        }
        next()
      })
    },

    // 在其他钩子中使用存储的配置
    transform(code, id) {
      let n
      if (n = /\/less\/[A-Za-z\-]*\.less\??[^.]*$/.exec(id)) {
        lessCodeMap[n[0].split('/')[2]] = code;
      }
    },
  }
}

这里我们拦截了路径 /online-less-server 的请求,res.end 返回对应的代码内容,路径匹配不上就调用 next,让 vite 走默认处理

这样这个插件就写完了,代码很少,非常简单

我们在 vite.config.ts 中使用我们的插件

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import lessPlugin from './plugins/online-less-server'

export default defineConfig({
  plugins: [react(), lessPlugin()],
})

还没结束,刚才也提到了,我们是用了 vite 已经帮我们处理好的 less,也就是这些 less 文件必须明确在代码中用到或者说引入了,vite 才会帮我们处理。我们需要在一个文件中引入所有需要使用的 less

如果你和我一样把 less 文件夹放在最外层的话,直接引入 less 会引起 ts 报错,如果报错了需要改一下 tsconfig.json

"include": ["src", "less"]

其他地方不改,只改 include

然后我们就可以测试我们的本地 less 自动编译服务了,随便写几行 less

@default-color: aqua;

.root {
  font-size: 30px;
  color: @default-color;
  .content {
    background-color: white;
    color: black;
  }
}

在这里插入图片描述

直接用 node 实现

再讲一下不用 vite,直接用 node 和各种包本地启一个简单的 less 服务。优点是足够简单,但是缺点也是太简单了容易出问题,很多地方的细节需要自己去完善。

我们新建一个文件夹,然后在这个新文件夹下做一下 npm 的初始化

npm init

直接一直回车就行

首先我们创建一个 app.js,本地启动一个服务需要装几个包

npm i connect http

然后是启动服务的代码

const connect = require('connect')
const http = require('http')

const app = connect()

app.use(function (req, res) {
  res.end('Hello from Connect!\n')
})

http.createServer(app).listen(3000)

为了方便,我们在 package.json 增加一段脚本

"scripts": {
  "dev": "node app.js"
},

写完后我们直接在控制台 yarn dev / npm run dev 就行

在这里插入图片描述
这样子服务就算启起来了

接下来处理 less 代码,和上面一样,我们设置特定的路径 /online-less-server ,只在请求该路径下的 less 资源的时候我们在做处理,或者按自己的想法来

然后处理 less,需要安装额外的依赖

npm i less

const fs = require('fs')

try {
  fs.readFile(lessInput, async (err, data) => {
    if (!err) {
      const text = data.toString()
      const lessOutPut = await less.render(text, { filename: lessInput })
      res.end(lessOutPut.css)
    } else throw err
  })
} catch {
  res.end('Error')
}

这部分可以就是 less 函数的用法了,可以参考 less 的官网

lessOutPut.css 就是最终转换好的 css ,res.end 将内容返回给客户端,整理一下,完整代码如下

const connect = require('connect')
const http = require('http')
const fs = require('fs')
const path = require('path')

const less = require('less')

const app = connect()

const lessPath = './less'	// less 的文件夹(自定义)

app.use(function (req, res) {
  const pathList = req.url.split('/')
  if (pathList[1] === 'online-less-server') {
    try {
      const target = path.join(lessPath, pathList[2])
      fs.readFile(target, async (err, data) => {
        if (!err) {
          const text = data.toString()
          const lessOutPut = await less.render(text, { filename: target })
          res.end(lessOutPut.css)
        } else throw err
      })
    } catch {
      res.end('Error')
    }
    return;
  }
  res.end('Hello from Connect!\n')
})

http.createServer(app).listen(3000)

接下来写两个 less 文件,需要新建一个 less 文件夹

在这里插入图片描述

// global.less
@default-font-colot: black;
@default-btn-colot: rgb(102, 156, 255);

// a.less
@import './global.less';

.root {
  display: block;
  font-size: 20px;
  .content {
    background-color: white;
    color: @default-font-colot;
    font-weight: bold;
  }
  .btn {
    background-color: @default-btn-colot;
    &:hover {
      color: white;
    }
  }
}

执行 yarn dev / npm run dev,把服务跑起来,输入指定的地址

http://localhost:3000/online-less-server/a.less

查看结果

在这里插入图片描述
可以看到,less 会自动帮我们处理引入的文件(global.less),然后输出

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值