xyxsw
文章35
标签3
分类0
Web 开发安全 | 青训营笔记

Web 开发安全 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 17 天

课程重点

  • Web 相关的攻击介绍

  • Web 相关的防御介绍

详细介绍

XSS (Cross-Site Scripting)

主要是由于盲目信任用户输入,直接将用户输入渲染出来,导致了攻击脚本的植入

特点:

  1. 通常难以从 UI 上感知
  2. 窃取用户信息,例如 cookie 和 token

常见的会导致XSS的代码:

document.write
el.innerHTML = anyString

总结来说,所有能够渲染dom结构的函数,都有可能导致XSS攻击。

比如论坛场景中,用户提交的内容包含恶意script标签

fetch("/submit", {
    body: JSON.stringify({
        id: "1",
        content: `<script>alert("xss");</script>`
    })
})

导致代码被插入到帖子中,所有用户访问页面都会执行恶意脚本

XSS 能够分成下面四类

Stored XSS 直接将恶意脚本储存到了数据库中,导致之后的所有访问均会携带恶意脚本,危害很大

Reflected XSS` 反射型,不储存数据,仅仅从URL传入脚本导致攻击,例如 `/path?param=<script>alert('xss')</script>

DOM-based XSS 不由服务器参与,攻击的发起和执行都在浏览器,常见于前端框架中

Mutation-based XSS 利用浏览器的特性,不同浏览器会有区别,例如

<noscript><p title="</noscript><img src=x onerror=alert(1)>">

CSRF (Cross-site request forgery)

特点:

  1. 在用户不知情的前提下
  2. 利用用户权限(cookie)
  3. 构造指定HTTP请求,窃取或修改用户敏感信息

例子:

用户没有访问银行网页,但是访问了一个带有攻击内容的网页,该网页尝试请求银行接口,由于用户登录过银行,接口携带cookie,导致请求成功,使得用户受到损失。

SQL 注入 (SQL Injection)

在请求参数中构造恶意字符串,拼接SQL语句,导致服务器执行了特定的SQL语句,造成数据库内容泄露

例子:

sql.query(`
    SELECT a, b, c FROM table
    WHERE username = ${username}
    AND form_id = ${form_id}
`)

上面的这段后端代码,攻击者就能够通过构造特殊的 username 或 form_id,使得SQL语句的意义被改变

例如传入 form_id = any; DROP TABLE table;

SQL 语句拼接后变成 SELECT a,b,c FROM table WHERE xxxx AND form_id = any; DROP TABLE table;

将导致数据库被删除

SSRF (Server-Side Request Forgery)

服务端伪造请求

例子:

ctx.body = await fetch(ctx.query.callback)

导致能够通过传入的参数访问到服务器内网的相关服务

DoS (Denial of Service)

「 不搞复杂的 , 量大就完事儿了 」

通过构造特定请求,导致服务器资源被消耗,来不及响应更多请求,引发雪崩效应

例如:

  1. 耗时的同步操作
  2. 文件备份
  3. 数据库写入
  4. SQL join
  5. 循环执行逻辑

DDoS (Distributed DoS)

攻击特点

  • 直接访问 IP
  • 任意 API
  • 消耗大量带宽 ( 耗尽 )

SYN Flood

尾声

安全无小事
使用的依赖 npm package,甚至是 NodeJS 可能成为最薄弱的一环

  • 保持学习心态
    npm install 除了带来了果洞 , 还可以带来漏洞

引用

https://bytedance.feishu.cn/file/boxcn9L4YzmTK3mwE3tIBL2UVme

https://baike.baidu.com/item/XSS%E6%94%BB%E5%87%BB/954065

NextJs | 青训营笔记

NextJs | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 16 天

一、本堂课重点内容:

  1. CSR, SSR, SSG
  2. 什么是 Next.js
  3. Next.js 客户端开发
  4. Next.js服务端开发
  5. 核心功能

二、详细知识点介绍:

1. CSR, SSR, SSG

1. CSR(客户端渲染 Client Side Render)

即在客户端渲染网页结构,一般情况下我们所熟知的 Vue、React 均采用这种模式。在构建时并不会生成HTML结构,仅仅包含js代码的引用。在浏览器端将HTML结构渲染出来。客户端渲染会在项目体积较大的时候可能会导致较长的首屏加载时间,用户体验不好,且对于SSO来说并不友好。

2. SSR(服务端渲染 Server Side Render)

浏览器请求时,服务器直接生成HTML结构并返回,这种模式其实并不少见,曾经的phpjsp都是这种模式。VueReact 也对这种模式进行了支持,例如 React 下有 Vercel 主导的 Next.js 框架支持服务端渲染,Vue 下也有类似的框架 Nuxt.js。与前者的区别是,phpjsp的前后端代码实际上仍是分离的,需要动态交互的部分还是需要js介入。而ReactVue在服务端和客户端都是同一套代码,这也就导致其比前者多了一步水合 hydrate (也称注水) 的操作。

在这种模式下,服务端在生成HTML的同时对其进行“脱水”,即将渲染结构的初始数据分离出来,单独储存,以便让前端读取。在浏览器收到页面后,相关的代码读取这部分内容,再对html结构进行“注水”(水合),使得服务端和客户端的数据同步,且浏览器端的元素能够正常交互。

比起CSR,SSR能够大幅缩短首屏加载的时间,且对于SSO友好,但是需要服务器支持。

3. SSG(静态网页生成 Static Site Generation)

这种形式实际上和SSR类似,但是无需运行一个服务端,HTML结构在构建时就已经创建完了,用户访问时,仍然是个静态页,却具有SSR首屏加载时间短的优势,适合那些修改不频繁的站点,例如一些开发文档。

2.什么是 Next.js

基于 React 提供的相关服务器端渲染 API 实现 , 整个过程实现比较繁琐重复 , 从零实现对新上手同学很不友好迫切需要一个封装好的集合来快速上手服务器端渲染。

Next.js是一个构建于 Node.js之上的开源 we b 开发框架 ,支持基于 React 的 web 应用程序功能 , 例如服务端渲染和生成静态网站 。上手快 , 能力集全 , 覆盖了足够多的性能优化。

对于新同学掌握前后端一体化的开发模式很友好 。

3. Next.js 客户端开发

  1. Next.js 初始化
  2. 数据注入
  3. getlnitiaIProps
  4. getServerSideProps
  5. getStaticProps
  6. CSS Modules
  7. Layout
  8. 文件式路由
  9. B F F 层的文件式路由
  10. 路由跳转
  11. header 的修改
  12. 多媒体适配—— CSS 适配
  13. 多媒体适配—— JS 适配
  14. 大图优化—— webp

4. Next.js服务端开发

  1. BFF 层开发
  2. 调试方式
  3. Strapi —— headless CMS

5. 核心功能

首页功能实现

  1. 页面 & 动画 & 多媒体适配
  2. BFF
  3. Strapi

文章页实现

  1. 页面 & 动画 & 多媒体适配
  2. BFF
  3. Strapi 分页
  4. 多媒体格式的转换
    • markdown 转 html
    • html 转 dom
    • 公共样式的定义

主题化功能实现

  1. 基础样式和背景的抽离
  2. 主题化 context 全局注入
  3. 从注入数据中取出 themesetTheme
  4. 多进程间的主题同步

三、实践练习例子:

思考题 : http://localhost:3000http://127.0.0.1:3000 主题可以共享吗

答案是不能

如果存在localstorage

那么这俩域名不是同域名

不会共享

初始化 Next.js 项目

Next.js 提供了一个脚手架,直接就能够创建项目

npx create-next-app@latest --typescript

接下来就能够看到创建完成的项目目录了。可能会有下面这些文件

.eslintrc.json - eslint 的配置文件,配置了eslint使用的语法规则

.gitignore - git 仓库的忽略文件

next-env.d.ts - TypeScript 的类型定义,是为了方便引入Next声明的类型

next.config.js - Next.js 的配置文件

pages - 页面目录

styles - 样式目录

public - 公共资源目录,会在构建时被原样拷贝

四、课后个人总结:

本次课程讲解了 Next.js 的基础概念、客户端开发以及服务端开发,以及核心功能的实现,让我对 Next.js 拥有了更多的了解,它的客户端和服务端的开发可以让我们更有效的实现功能,并且在项目构建的时候可以利用它的脚手架快速创建项目,实现更加高效的开发。

NodeJS基础 | 青训营笔记.md

NodeJS基础 | 青训营笔记.md

这是我参与「第五届青训营」伴学笔记创作活动的第 15 天

一、本堂课重点内容:

  1. NodeJS 应用场景
  2. NodeJS 运行时结构
  3. 使用 NodeJS 编写简单的 HTTP Server
  4. NodeJS延伸

二、详细知识点介绍:

1. NodeJS应用场景

NodeJS被广泛应用于前端工程化(VueReactAngularJS等)、

Web 服务端应用(Vercel)和 跨端桌面应用(Electron等)场景

许多熟知的打包构建工具都有NodeJS参与,例如 webpackviteesbuildparcel等,也有例如babelTS等语言转换程序也由nodejs编写。

在前端工程化领域,NodeJS也有许多优势,首先是学习曲线平缓、开发效率较高、其次是运行效率较高,社区生态丰富。且由于其语言优势,和前端结合更加顺畅,例如某些服务端渲染场景。

在跨端应用方面,微软的VSCode、聊天工具Discord都由Electron开发,值得在项目选型时考虑。

难以替代

与前端结合的场景有很大的优势SSR

字节使用NodeJS,飞书就是electron应用 每年新增1000+nodejs应用

2.NodeJS运行时结构

  • 用户代码
  • npm
  • js core
  • cpp core
  • v8
  • openssl

异步、单线程、跨平台

3. 使用 NodeJS 编写简单的 HTTP Server

0 . 安装 node.js
1 . 编写 Http Server + Client, 收发 GET, POST 请求
2 . 编写静态文件服务器
3 . 编写 React SSR 服务
4 . 适用 inspector 进行调试 、 诊断
5 . 部署简介

4. NodeJS延伸

  • 编译
  • 诊断追踪
  • wasm
  • napi

三、实践练习例子:

简单的 Hello World

const http = require('http')

const port = 3000

const server = http.createServer((req, res) => {
    res.end('hello')
})

server.listen(port, () => {
    console.log(`listening port on ${port}`)
})

使用 require('http') 引用 NodeJS 自带的 http 模块。

使用 createServer 方法为请求绑定处理事件函数,

使用 res.end('hello') 来结束请求并返回 hello 字符串。

使用 listen 函数设置监听的端口,第一个参数是端口号,第二个参数是绑定成功的回调函数。

用浏览器访问127.0.0.1:3000 会显示hello

简单的静态文件服务

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

const port = 3000

const server = http.createServer((req, res) => {
    const info = url.parse(req.url)
    const file = fs.createReadStream(path.resolve(__dirname, '.' + info.pathname))
    file.pipe(res)
})

server.listen(port, () => {
    console.log(`listening port on ${port}`)
})

通过 NodeJS 自带的文件操作模块 fscreateReadStream 创建了一个文件流,

通过流内部方法pipe将其输出给res(响应对象)

能将对应路径的文件返回给浏览器

四、课后个人总结:

今天的课程主要介绍了NodeJS的应用场景以及运行时结构,并且结合实例介绍了如何使用NodeJS编写简单的HTTP Server。通过今天的学习,我更加深入地了解了NodeJS,再次感受到它的强大,它可以应用于前端工程化、Web 服务端应用以及跨端桌面应用等多种场景,它的未来也是非常可观的。

Slidev 用markdown写PPT!| 青训营笔记

Slidev 用markdown写PPT!| 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 14 天

前言

欢迎使用 Slidev!
为开发者打造的演示文稿工具

slidev是slidevjs开发的一款开源的使用markdown来编写ppt样式的前端应用的框架

只需要在单一 Markdown 文件中编写幻灯片就可以创建一个非常漂亮的前端ppt应用

这个项目自从0.20.0版本我就关注来着,当时就觉得很炫很好用

昨天看到了antfu更新说在最新版本0.39.0里支持了简单的动画切换效果

😋感觉动画切换是ppt最重要的东西吧,很好很圆满😎

来试试

img

特性

  • 支持 Markdown 语法:Slidev 使用一种扩展的 Markdown 格式,在一个纯文本文件中存储和组织你的幻灯片。这让你专注于制作内容。而且由于内容和样式是分开的,这也使得在不同的主题之间切换变得更加容易。

  • 可定制主题:Slidev 的主题可以通过 npm 包的形式来分享和安装。

  • 对开发者友好:Slidev 为开发者提供了一流的代码片段支持。它同时支持 PrismShiki 以获得像素级的完美语法高亮,并且能够随时修改代码。通过内置的 Monaco 编辑器,它还能让你在演示文稿中进行现场编码/演示,并支持自动补全、类型悬停、甚至是 TypeScript 类型检查。

  • 快速:Slidev 得益于 ViteVue 3Windi CSS,为你带来了最美妙的创作体验。你所做的每一个改变都会立即反映到你的幻灯片上。

  • 互动性 & 直观表达:你可以编写自定义的 Vue 组件并直接在你的 MarkDown 文件中使用它们。你也可以在演示文稿中与它们互动,以更深入和直观的方式表达你的想法。

  • 支持录制:Slidev 提供了内置的录音和摄像头视图。你可以将你的演示文稿与你的相机视图一起分享,或者为你的屏幕和相机分别录制并保存。所有这些都是内置的,不需要额外的工具。

  • 可移植性:用一个命令就可以将你的幻灯片导出为 PDF 或 PNG,甚至是可托管的单页应用程序(SPA),并在任何地方分享它们。

  • 可配置:由于 Slidev 基于 Web 技术,任何可以在 Web 应用中完成的事情,Slidev 也可以做到。例如,WebGLAPI请求iframes,甚至是实时共享。完全取决于你的想象力!

  • 演讲者模式: 可以使用另一个窗口,甚至是你的手机来控制幻灯片。

安装

npm init slidev

在package.json里配置

{
  "scripts": {
    "dev": "slidev", //  启动 dev server
    "build": "slidev build", // 构建生产环境的单页面应用
    "export": "slidev export" // 将幻灯片导出为 pdf 格式
  }
}

执行 npx slidev --help 命令获取更多选项的详细信息。

这样就安装完成了,你只需要在./slides.md里编写你想要的内容就行😎

推荐的目录结构

your-slidev/
  ├── components/       # 自定义组件
  ├── layouts/          # 自定义布局
  ├── public/           # 静态资源
  ├── setup/            # 自定义 setup / hooks
  ├── styles/           # 自定义样式
  ├── index.html        # 注入的 index.html
  ├── slides.md         # 幻灯片主入口
  └── vite.config.ts    # 扩展 vite 配置

Markdown 语法

分隔符

使用 --- 添加分隔符来分隔你的幻灯片。

注意 这个分隔符最好上下空两行

像这样

你可以安装一个VSCode插件 名叫slidev 来获得更好的体验,https://marketplace.visualstudio.com/items?itemName=antfu.slidev

这个#2是插件告诉你这下面是第几张幻灯片

# Slidev

Hello, World!

---

# Page 2

Directly use code blocks for highlighting

```ts
console.log('Hello, World!')
```

---

# Page 3

You can directly use Windi CSS and Vue components to style and enrich your slides.

<div class="p-3">
  <Tweet id="20" />
</div>

像这样就创建了三张幻灯片

扉页及布局

用两个分隔符来表示一个扉页块

---
layout: cover
---

两个分隔符里是yaml格式的对象

具体的语法如下

---
# 主题id 或 主题包名称
# 了解更多:https://sli.dev/themes/use.html
theme: 'default'
# 幻灯片的总标题,如果没有指定,那么将以第一张拥有标题的幻灯片的标题作为总标题
title: 'Slidev'
# titleTemplate for the webpage, `%s` will be replaced by the page's title
titleTemplate: '%s - Slidev'
# information for your slides, can be a markdown string
info: false

# 在单页(SPA)构建中启用 pdf 下载,也可以指定一个自定义 url
download: false
# 要导出文件的文件名称
exportFilename: 'slidev-exported.pdf'
# 语法高亮设置,可以使用 'prism' 或 'shiki' 方案
highlighter: 'prism'
# 在代码块中显示行号
lineNumbers: false
# 启用 monaco 编辑器,可以是 boolean,'dev' 或者 'build'
monaco: 'dev'
# 使用 vite-plugin-remote-assets 在本地下载远程资源,可以是 boolean,'dev' 或者 'build'
remoteAssets: false
# 控制幻灯片中的文本是否可以选择
selectable: true
# 启用幻灯片录制,可以是 boolean,'dev' 或者 'build'
record: 'dev'

# 幻灯片的配色方案,可以使用 'auto','light' 或者 'dark'
colorSchema: 'auto'
# vue-router 模式,可以使用 'history' 或 'hash' 模式
routerMode: 'history'
# 幻灯片的长宽比
aspectRatio: '16/9'
# canvas 的真实宽度,单位为 px
canvasWidth: 980
# 用于主题定制,会将属性 `x` 注入根样式 `--slidev-theme-x`
themeConfig:
  primary: '#5d8392'

# favicon 可以是本地文件路径,也可以是一个 URL
favicon: 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png'
# 用于渲染图表的 PlantUML 服务器的 URL
plantUmlServer: 'https://www.plantuml.com/plantuml'
# 字体将从 Google 字体自动导入
# 了解更多:https://sli.dev/custom/fonts
fonts:
  sans: 'Roboto'
  serif: 'Roboto Slab'
  mono: 'Fira Code'

# 为所有幻灯片添加默认的 frontmatter
defaults:
  layout: 'default'
  # ...

# 绘制选项
# 了解更多:https://sli.dev/guide/drawing.html
drawings:
  enabled: true
  persist: false
  presenterOnly: false
  syncAll: true
---

代码块

可以通过```来创建一个代码块

```ts
console.log('Hello, World!')
```

可以通过在语言名后写大括号的形式来指定特定行高亮{}

注意:行号从 1 开始计算。

```ts {2-3|5|all}
function add(
  a: Ref<number> | number,
  b: Ref<number> | number
) {
  return computed(() => unref(a) + unref(b))
}
```

这段代码的意思是 点进来先是2到3行高亮,再点一下2到3不亮第5行亮,再点一下整个都亮

Monaco 编辑器

只需在语言 id 后添加 {monaco} 就可以启用monaco

什么是monaco呢,可以理解为网页版的vscode那样的,可以在线编辑的

```ts {monaco}
console.log('HelloWorld')
```

内联样式

你可以在 Markdown 中直接使用 <style> 标签来覆盖当前幻灯片的样式。

Slidev支持windicss的原子化写法和指令集 例如@apply

# Slidev

> Hello `world`

<style>
blockquote {
  code {
    @apply text-teal-500 dark:text-teal-400;
  }
}
</style>

静态资源

和编写 Markdown 的方式一样,你可以使用本地或远程的 URL 的图片。

远程资源会缓存

本地资源请放到public文件夹

备注

你也可以为每张幻灯片编写备注。它们将展示在 演讲者模式 中,供你在演示时参考。

在每一页幻灯片内容最后

添加注释即可

---
layout: cover
---

# 第 1 页

This is the cover page.

<!-- 这是一条备注 -->

---

# 第 2 页

<!-- 这不是一条备注,因为它在幻灯片内容前 -->

The second page

<!--
这是另一条备注
-->

icon图标

很喜欢的功能

Slidev 允许你在 Markdown 中直接访问几乎所有的开源的图标集。

你可以通过 Icônes 来浏览访问所有可用的图标。

更详细的请看https://cn.sli.dev/guide/syntax.html#icons

LaTeX

这个不是很熟啊

https://demo.sli.dev/starter/8

可以看下示例 和 文档

https://katex.org/

图标

这个东西也没怎么用过,只知道github什么的支持这种语法 基于https://github.com/mermaid-js/mermaid

被标记为 mermaid 的代码块将被转换为图形,例如:

```mermaid
sequenceDiagram
  Alice->John: Hello John, how are you?
  Note over Alice,John: A typical interaction
```
sequenceDiagram
  Alice->John: Hello John, how are you?
  Note over Alice,John: A typical interaction

我在typora也可以创建这个图表,感觉很好用

导航

导航栏默认在左下角,鼠标移动上去显示

可以用right / space来切换下一张幻灯片

g跳出一个栏,输入页码可以跳转

更多快捷键详见https://cn.sli.dev/guide/navigation.html#navigation-bar

动画

重头戏

这个版本就更新了这个

点击动画

v-click 可以用<v-click></v-click>标签或在标签内写<div v-click class="text-xl p-2">这样的指令

以下指令写法同理

v-afterv-click绑定,这俩会一起显示

v-click-hide点击后不可见

v-clicks 只能使用标签写法 可以方便的展示一个列表

<v-clicks>

- Item 1
- Item 2
- Item 3
- Item 4

</v-clicks>

你还可以手动指定过渡动画 覆写两个类的动画

// the default

.slidev-vclick-target {
  transition: opacity 100ms ease;
}

.slidev-vclick-hidden {
  opacity: 0;
  pointer-events: none;
}

运动

https://vueuse.org/functions.html#category=%40Motion

内置了vueuse的motion功能

你可以对任何元素应用 v-motion 指令,以对它们施加运动效果。例如:

<div
  v-motion
  :initial="{ x: -80 }"
  :enter="{ x: 0 }">
  Slidev
</div>

文本 Slidev 将从其初始化位置 -80px 移至其原始位置。

页面过渡

🤩新功能!

可以在扉页中填写

---
transition: slide-left
---

这将给你一个很好的滑动效果。将其设置在首页,将适用于所有的幻灯片。您还可以为每张幻灯片设置不同的过渡。

自带的过渡效果

fade - Crossfade in/out
fade-out - Fade out and then fade in
slide-left - Slides to the left, slide to right when going back
slide-right - Slides to the right, slide to left when going back
slide-top - Slides to the top, slide to bottom when going back
slide-bottom - Slides to the bottom, slide to top when going back

还可以自定义过渡效果

---
transition: my-transition
---
.my-transition-enter-active,
.my-transition-leave-active {
  transition: opacity 0.5s ease;
}

.my-transition-enter-from,
.my-transition-leave-to {
  opacity: 0;
}

导出

导出为pdf 依赖于微软开源的playwright框架

playwright-chromium

需要先安装

npm i -D playwright-chromium

使用

slidev export

可导出pdf格式

slidev export --format png

可导出png格式

静态部署

slidev build

编译当前幻灯片生成在dist/目录下,可以部署到静态托管平台

提供可下载的 PDF

在扉页中设置

---
download: true
---

会在单页应用中展示下载按钮

也可以自定义url

---
download: 'https://myside.com/my-talk.pdf'
---

部署到netlify

创建netlify.toml文件 配置

[build.environment]
  NODE_VERSION = "14"

[build]
  publish = "dist"
  command = "npm run build"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

部署到vercel

创建vercel.json文件 配置

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

演讲录制

摄像头

导航面板的小人按钮可以开启摄像头

你可以拖动它,并使用右下角的把手来调整大小。尺寸和位置将持久化存储在 localStorage 中,因此,可以保证多次刷新后的展示一致,无需担心位置和大小丢失的问题。

录制

导航面板的摄像机按钮

可以录制麦克风和摄像头

演讲者模式

点击小人带麦克风的按钮

可以进入演讲者模式

需要双端在同一网络下

其他页面的实例会自动和演讲者的页面同步

绘图

可以在页面上画画

可以在演讲者模式中启用,会实时自动同步

支持触控笔压感

编辑器整合

你可以在运行中在网页上更改你的源码,他会自动同步到slide.md里

主题

在这里浏览主题并应用到你的实例中

https://cn.sli.dev/themes/gallery.html

布局

https://cn.sli.dev/builtin/layouts.html

总结

这篇写了不少,详细的写了我比较喜欢的ppt框架

我有一个部署的版本,地址在 https://botppt.netlify.app/

是很久之前写的辣,版本还是很老的,有时间会更新一下

😎

引用

https://botppt.netlify.app/1

https://cn.sli.dev/showcases.html

https://cn.sli.dev/

https://github.com/slidevjs/slidev

Nuxt3与服务端渲染SSR(高级篇) | 青训营笔记

Nuxt3与服务端渲染SSR(高级篇) | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 13 天

前言

前两篇写了一些nuxt3和SSR的基础

这一篇讲讲nuxt3比较进阶的部分

自动引入

nuxt3自带自动引入

包括一些nuxt3的api都是不用导入的直接就可以用

例如

  • 数据获取的函数useFetch useAsyncData

  • 状态管理函数useState

  • 运行变量获取函数 useRuntimeConfig

  • 运行时上下文获取函数 useNuxtApp

还有一些Vue的api

例如

  • 响应式的函数ref

  • 计算属性 computed

文件夹下的内容自动引入

例如

  • components/文件夹下的组件
  • composables/文件夹下的响应式函数
  • utils/文件夹下的工具函数

可以使用#imports来使导入明确

<script setup>
  import { ref, computed } from '#imports'

  const count = ref(1)
  const double = computed(() => count.value * 2)
</script>

关闭自动引入

nuxtconfig里加入

export default defineNuxtConfig({
  imports: {
    autoImport: false
  }
})

(😡谁会这么做

自动引入的组件

假设目录如下

| components/
--| base/
----| foo/
------| Button.vue

此时可以使用<BaseFooButton />标签引入这个button组件

另外,可以使用一个Lazy前缀来懒加载组件

例如

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    }
  }
}
</script>

这样可以减少打包大小,增加性能

路由

写在pages/里的组件会自动写入路由

pages/index.vue会匹配路由/

只需要在app.vue里添加

<NuxtPage />标签

可以通过插槽写法例如写[id].vue来匹配路由内的参数

例如如下文件树

-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue

[id].vue里写下

<template>
  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>

读取group参数和id参数

访问路由/users-admins/123会渲染成

<p>admins - 123</p>

渲染模式

传统的Vue项目渲染方式为 客户端渲染

浏览器会先下载到空的html文件→下载js→渲染出来

这种方式的优点是:

  • 开发速度快:当完全在客户端上工作时,我们不必担心代码的服务器兼容性,例如,使用像windows这样的浏览器api。

  • 更便宜的服务器成本:运行一个服务器会增加一个基础设施的成本,因为您需要在一个支持JavaScript的平台上运行。我们可以在任何具有HTML、CSS和JavaScript文件的静态服务器上托管仅限客户端的应用程序。

  • 下载好后离线:因为代码完全在浏览器中运行,所以它可以很好地在互联网不可用时很好地保持工作。

缺点为:

  • 性能不好:用户必须等待浏览器下载、解析和运行JavaScript文件。根据下载部分的网络和用户的解析和执行设备,这可能需要一些时间,并影响用户的体验。
  • 不友好的搜索引擎优化:索引和更新通过客户端呈现交付的内容比使用服务器呈现的HTML文档需要更多的时间。

nuxt3的服务端渲染

有内容的html文件会下载到浏览器→用户在此时可以点击和浏览页面上的内容(部分功能)→需要的组件渲染完成、此时的页面有全部功能

这种方式的优点是:

  • 性能好:用户可以立即访问页面的内容,因为浏览器显示静态内容的速度比javascript生成的静态内容要快得多。
  • 搜索引擎优化:通用呈现将页面的整个HTML内容作为一个经典的服务器应用程序传递到浏览器。Web爬虫可以直接索引页面的内容。

缺点是:

  • 开发约束:服务器和浏览器环境不提供相同的api,而且编写可以在两边无缝运行的代码可能会很棘手。幸运的是,Nuxt提供了特定的变量来同步这些。
  • 成本高:服务器需要运行,才能动态地呈现页面。这就像任何传统服务器一样,增加了每月的成本。但是,由于浏览器接管了客户端导航的通用渲染,服务器调用大大减少了。

类型检查

使用 TypeScript 后可以使用nuxi带的语法检查

yarn nuxi typecheck

nuxt也会自动生成类型

.nuxt/nuxt.d.ts目录下

文件目录

一个完整的nuxt3项目文件目录应该包含以下文件夹和文件

.nuxt                      nuxt生成的文件目录里面有 运行的类型等
.output                    build产物文件夹
assets                     css文件、字体文件、图片文件
components                 组件目录
composables                响应式函数的文件夹
content                    基于文件的cms的文件夹
layouts                    布局组件的文件夹
middleware                 中间件文件夹
node_modules               依赖文件夹
pages                      路由的页面
plugins                    nuxt插件
public                     公共文件文件夹,可以放ico图标,图片
server                     服务端api文件夹
utils                      工具函数文件夹
.gitignore                 git忽略文件
.nuxtignore                不加入nuxt编译的忽略文件
app.config.ts              config文件 可以使用useAppConfig读取
app.vue                    vue入口 
nuxt.config.ts             nuxt配置文件
package.json               依赖描述文件
tsconfig.json              ts检查配置文件

https://nuxt.com/docs/guide/directory-structure/nuxt

总结

本篇文章介绍了nuxt3的高级用法

我也是第一次使用nuxt3来开发项目

踩到的坑还是很多的,预计可以写一篇踩坑记录

😎🥰

参考

https://nuxt.com/docs/guide/concepts/auto-imports

https://nuxt.com/docs/guide/directory-structure/composables

https://nuxt.com/docs/guide/directory-structure/components

https://github.com/unjs/nitro

青训营任务-我的名片-可爱捏☺

青训营任务-我的名片-可爱捏☺

当青训营遇上码上掘金

主题介绍

  • 主题 1:我的名片

    名片是向人介绍自我的重要工具,作为一名程序员用代码做自我介绍是一件非常酷炫的事情。请大家围绕“我的名片”这个主题进行代码创作。

思路

既然说整个酷炫的

直接上框架

https://github.com/antfu/vitesse-webext

这个模板是基于Vue的webext(浏览器插件手脚架)

webext是一个可以帮助构建、运行和测试Web扩展的命令行工具。

https://wiki.mozilla.org/WebExtensions

webext的目标是以一种标准的、可移植、跨平台的方式支持浏览器扩展。最初,它将为开发火狐扩展提供一个简化的体验。

我们的目标是实现一个浏览器扩展类型的名片

技术栈

  • ⚡️ Vite 快速构建
  • 🥝 Vue 3 -组合式api 3.2+setup语法
  • 💬webext-bridge 的local storage 和 VueUse 的使用结合
  • 🌈 UnoCSS - 原子化CSS引擎
  • 🦾 TypeScript 语法支持
  • 📦 Components auto importing 自动引入
  • 🌍 WebExtension - 标准浏览器扩展手脚架 可以一键生成chromium、Firefox适配的扩展
  • 😴 mv3 - manifest v3

本身这个项目没什么坑的、只是

😡

浏览器在2023年抛弃manifest v2

所以使用manifest v2打包后的浏览器扩展浏览器不认

会显示

😱

还有一点就是本身不太会CSS

调CSS花了很长时间

但是unocss还是很方便的

代码实现

<script setup>
import { useImage } from '@vueuse/core'
const avatarUrl = 'https://s1.ax1x.com/2023/01/23/pSYuIYD.png'
const waka = 'https://wakatime.com/badge/user/84476697-3421-4720-bcdc-efb5c0fbc1b8.svg?style=flat-square'
const kita = 'https://www.z4a.net/images/2023/01/28/kita.gif'
const { isLoading } = useImage({ src: avatarUrl })
const { isLoading_waka } = useImage({ src: waka })
const { isLoading_kita } = useImage({ src: kita })
const to_left = () => {
  document.getElementsByClassName('img')[0].classList.remove('to_right')
  document.getElementsByClassName('right_text')[0].classList.remove('to_right_text')
  document.getElementsByClassName('right_block')[0].classList.remove('to_right_block')
  document.getElementsByClassName('left_block')[0].classList.remove('to_left_block')
  document.getElementsByClassName('left_img')[0].classList.remove('to_left_img')
}
const to_right = () => {
  document.getElementsByClassName('img')[0].classList.add('to_right')
  document.getElementsByClassName('right_text')[0].classList.add('to_right_text')
  document.getElementsByClassName('right_block')[0].classList.add('to_right_block')
  document.getElementsByClassName('left_block')[0].classList.add('to_left_block')
  document.getElementsByClassName('left_img')[0].classList.add('to_left_img')
}
</script>

<template>
  <main class="w-[384px] h-[256px] text-center text-light-700 bg-[#f69db2] font-sans">
    <div class="h-100% w-100% flex flex-row justify-between p-5 items-center gap-5">
      <div class="absolute left-0 top-0 translate-x-[-100%]">
        <div class="flex flex-col justify-end left_block duration-700 ">
          <div class="bg-[#60c5e7] p-5" />
          <div class="bg-[#f3d263] p-5" />
        </div>
      </div>
      <div class="absolute left-0 top-0 translate-x-[-100%]">
        <span v-if="isLoading" class="animate-pulse bg-[#0A4445] px-11.5 py-16 text-center font-black">Loading</span>
        <img v-else :src="kita" class="left_img duration-400">
      </div>
      <span v-if="isLoading" class="animate-pulse bg-[#0A4445] px-11.5 py-16 text-center font-black">Loading</span>
      <img v-else :src="avatarUrl" class="w-35 h-35 img duration-700">
      <div class="flex flex-col gap-5 h-119%">
        <div class="flex flex-row justify-end translate-x-4.9 right_block duration-700 ">
          <div class="bg-[#60c5e7] p-5" />
          <div class="bg-[#f3d263] p-5" />
        </div>
        <div class="flex flex-col gap-5 pt-8 right_text duration-700 ">
          <div class="text-center font-bold text-lg text-[#df4b57]">
            👋  Hi, I’m  @camera-2018
          </div>
          <span v-if="isLoading" class="animate-pulse bg-[#0A4445] px-15 text-center font-black">Loading</span>
          <a v-else href="https://wakatime.com/@84476697-3421-4720-bcdc-efb5c0fbc1b8"><img :src="waka" alt="Total time coded since May 9 2022"></a>
        </div>
      </div>
    </div>
    <footer class="bg-[#f69db2] ">
      <button class="i-carbon-caret-left hover:bg-[#1DCED1] bg-light-50" @click="to_left" />
      <button class="i-carbon-caret-right hover:bg-[#1DCED1] bg-light-50" @click="to_right" />
    </footer>
  </main>
</template>

<style>
.to_right{
  @apply translate-x-142% transition
}

.to_right_text {
  @apply translate-x-190% transition
}

.to_right_block {
  @apply translate-y-[-120%] transition
}

.to_left_block {
  @apply translate-x-100% transition
}

.to_left_img {
  @apply translate-x-90% transition
}
</style>

主要的代码就这么多

大概思路是

  1. 通过unocss的预设添加使用@apply的样式

  2. 再通过js添加样式到元素上

  3. 使用过渡效果 添加过渡时间

  4. 图片加载部分使用vueuse的useImage 没加载出来之前是有个loading的界面

  5. 使用了一个wakatime的svg展示最近敲代码时间点击可看

效果

灵感

https://mzh.moegirl.org.cn/%E5%90%8E%E8%97%A4%E4%B8%80%E9%87%8C

复刻了孤独摇滚 后藤一里 的配色 右上角或左上角的方块为波奇酱的发饰

源代码

代码开源于

https://github.com/camera-2018/business-card-mv3

可以在Releases中下载编译好的扩展 解压然后点击浏览器的扩展程序选项卡 chrome://extensions/

记得要开右上角的开发者模式哦

然后加载已解压的扩展程序就可以了

popup部分代码

popup部分代码放在码上掘金上

https://code.juejin.cn/api/raw/7193666089241772090?id=7193666089241821242

由于依赖比较多,而码上掘金拉不到cdnjsdelivr的数据

所以在码上掘金上是无法正常显示的😥 想试试的话请去https://github.com/camera-2018/business-card-mv3源码

参考

https://code.juejin.cn/pen/7193666089241772090

https://juejin.cn/post/7187753682421678137

https://mzh.moegirl.org.cn/%E5%90%8E%E8%97%A4%E4%B8%80%E9%87%8C

https://github.com/antfu/vitesse-webext

Nuxt3与服务端渲染SSR(下) | 青训营笔记

Nuxt3与服务端渲染SSR(下) | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 12 天

前言

上一篇文章介绍了大概一半的nuxt3特性,这一篇会介绍另外一半

我也是第一次使用nuxt3来开发项目

踩到的坑还是很多的,预计可以写一篇踩坑记录

过渡

Nuxt利用Vue的 <Transition>组件来应用页面和布局之间的转换。

只需要在nuxt config里设置

export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' }
  },
})

然后在app.vue里设置你想要的过渡动画

<template>
  <NuxtPage />
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}
</style>

类名必须是这几个

就可以实现一个切换页面有虚化效果的动画

更高级的用法详见官方文档

https://nuxt.com/docs/getting-started/transitions

使用Api获取数据

nuxt3自带一个vueuse里的 useFetch api

使用方法为

<script setup>
const { data: count } = await useFetch('/api/count')
</script>

<template>
  Page visits: {{ count }}
</template>

可以使用useAsyncData来异步获取数据

<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script>

<template>
  Page visits: {{ data }}
</template>

更高级的用法详见官方文档

https://nuxt.com/docs/getting-started/data-fetching

状态管理

nuxt3提供了vueuse的一个api叫useState来实现全局状态管理

无需创建state文件

直接使用

<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    Counter: {{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>

这个例子创建了一个state叫counter 里面默认记录了一个随机数

点击按钮会让随机数进行加减

useState默认为响应式 很方便

nuxt3的auto import特性可以使state简写为

const counter = useCounter() // Same as useState('counter')

错误处理

nuxt提供了很多高级的错误处理方式

一个最简单的api是useError

const error = useError()

这句代码可以快速的抛出一条错误

也可以使用createError方法

<script setup>
const route = useRoute()
const { data } = await useFetch(`/api/movies/${route.params.slug}`)
if (!data.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}
</script>

部署

正常的使用nuxt build命令会生成一个在node环境中运行的运行时

.mjs文件

node .output/server/index.mjs

即可运行

$ node .output/server/index.mjs
Listening on http://localhost:3000

可以设置环境变量例如

PORT=3001 node .output/server/index.mjs

在3001端口运行

nuxt.config 里添加

export default {
  nitro: {
    preset: 'node-server'
  }
}

或者NITRO_PRESET=node-server nuxt build

preset字段可以更改生成的代码的依赖平台

支持的平台有

测试

可以添加vitest库来进行测试

yarn add --dev @nuxt/test-utils vitest

方法如下

新建test目录 创建文件 demo.test.ts

import { describe, test } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils'

describe('My test', async () => {
  await setup({
    // test context options
  })

  test('my test', () => {
    // ...
  })
})

运行vitest即可获得通过或不通过提示

结尾

到这里nuxt3基础就几乎结束了

还有一些更高级的用法没有讲,例如生命周期啊,nuxt-kit啊,自动导入啊

有机会可以写一篇高级教程

😋😎

参考

https://nuxt.com/docs/getting-started/state-management

https://nuxt.com/docs/api/composables/use-state

https://juejin.cn/post/7193305766692913189

https://vueuse.org/

Nuxt3与服务端渲染SSR(上) | 青训营笔记

Nuxt3与服务端渲染SSR(上) | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 11 天

前言

Nuxt同时提供了前端和后端功能,因此您可以专注于重要的事情:创建web应用程序。

Nuxt3 是基于 ViteVue3NitroNuxt 框架的重构,具有一流的 Typescript 支持,且这次更新对内核进行了精简,使之速度更快,体验更好。

了解 SSR

Nuxt 是一个基于 Vue.js 的服务端渲染应用框架

什么是 SSR

服务器端渲染(Server-Side Rendering)是指由服务端完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。

简单理解就是html是由服务端写出,可以动态改变页面内容,即所谓的动态页面。早年的 php 、 asp 、 jsp 这些 Server page 都是 SSR 的。

为什么使用 SSR

  • 网页内容在服务器端渲染完成,一次性传输到浏览器,所以 首屏加载速度非常快
  • 有利于SEO,因为服务器返回的是一个完整的 html,在浏览器可以看到完整的 dom,对于爬虫、百度搜索等引擎就比较友好;

Nuxt 3

Nuxt 是一个基于 Vue.js 的服务端渲染应用框架,

刚去查了一下 nuxt3在1月23日变成了默认版本

在1月25日发布了3.1.0版本

  • 实验组件群岛 准备就绪

  • 新的api

  • Nitro v2 ,vite 4 ,rollup3

  • 性能改进

什么是Nuxt?

要理解Nuxt是什么,我们需要了解创建一个现代应用程序需要什么:

  • JavaScript框架: 一个带来JavaScript组件的JavaScript框架,支持Vue.js。
  • 打包工具:支持开发中的热模块替换和生产代码,支持webpack 5和Vite。
  • 最新JavaScript语法:在支持遗留浏览器的同时编写最新的JavaScript语法的转换器,支持esbuild。
  • 服务器端:Nuxt是一个在开发中服务于应用程序的服务器,同时也支持服务器端呈现或API路由,它使用h3进行部署的多功能性,如serverless, workers, Node.js和无与伦比的性能。
  • 路由:一个处理客户端导航的路由库,支持 vue-router.。

初始化项目

https://nuxt.com/docs/getting-started/installation#prerequisites

pnpm dlx nuxi init <project-name>
code <project-name>
# Make sure you have `shamefully-hoist=true` in `.npmrc` before running pnpm install
pnpm install

注意: 如果你用 pnpm 安装依赖,请创建一个 .npmrc 文件,且设置:

shamefully-hoist=true

安装完成😋

启动

pnpm dev -o

看一眼文件树

抛开编译结果.nuxt文件夹不谈 就只有一个app.vuenuxt.config.ts

很简陋 怎么会这样呢?我们一般写的vue框架也不是这样的啊

创建目录

创建 components/ pages/

具体操作见官方文档

https://nuxt.com/docs/getting-started/views

也可以新建src目录 把 components/ pages/目录放到src

在nuxt里组件是自动导入的,不用配置

路由跳转

路由是自动创建的

<NuxtLink to="/about">About</NuxtLink>

以上的代码点击会自动跳到page/about.vue

也可以使用路由

<script setup>
const route = useRoute()

// When accessing /posts/1, route.params.id will be 1
console.log(route.params.id)
</script>

/posts/1是创建的/posts/[id].vue这样id会被当做props传入

SEO优化

nuxt服务端渲染最重要的就是搜索引擎优化

可以被拉取到带内容的html

而不是一般vue项目的空html

export default defineNuxtConfig({
  app: {
    head: {
      charset: 'utf-16',
      viewport: 'width=500, initial-scale=1',
      title: 'My App',
      meta: [
        // <meta name="description" content="My amazing site">
        { name: 'description', content: 'My amazing site.' }
      ],
    }
  }
})

nuxt.config.ts配置如上内容

或者在app.vue里添加

<script setup lang="ts">
useHead({
  title: 'My App',
  meta: [
    { name: 'description', content: 'My amazing site.' }
  ],
  bodyAttrs: {
    class: 'test'
  },
  script: [ { children: 'console.log(\'Hello world\')' } ]
})
useServerSeoMeta({
  title: 'My Amazing Site',
  ogTitle: 'My Amazing Site',
  description: 'This is my amazing site, let me tell you all about it.',
  ogDescription: 'This is my amazing site, let me tell you all about it.',
  ogImage: 'https://example.com/image.png',
  twitterCard: 'summary_large_image',
})
</script>

这几个函数可以添加meta头

还有一些设置可以看官方文档 https://nuxt.com/docs/getting-started/seo-meta

也可以使用 https://nuxt.com/modules/seo-kit

这个模块来配置SEO

很方便😎

结尾

本篇文章介绍了大概一半的nuxt3特性,下一篇会介绍另外一半

我也是第一次使用nuxt3来开发项目

踩到的坑还是很多的,预计可以写一篇踩坑记录

😎

参考

https://nuxt.com/modules/seo-kit

https://nuxt.com/docs/getting-started/seo-meta

https://nuxt.com/docs/getting-started/views

https://nuxt.com/docs

https://juejin.cn/post/7170746000112353293

https://juejin.cn/post/7037336504418435103

UnoCSS实战 | 青训营笔记

UnoCSS实战 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 10 天

前言

原子化css体验

重新构想原子化 CSS (antfu.me)

本篇介绍一下如何配置UnoCSS、UnoCSS怎么使用

安装

先创建一个vite模板 https://cn.vitejs.dev/guide/#scaffolding-your-first-vite-project

这里使用pnpm

pnpm create vite

选择vue、typescript

cd vite-project
pnpm install
pnpm run dev

运行一下

初始化成功了,接下来安装unocss https://github.com/unocss/unocss/tree/main/packages/vite

使用文档给的vite安装方式

pnpm i -D unocss

在vite config里写上

// vite.config.ts
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({ /* options */ }),
  ],
}

配置完全的vite config是这样的

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import { presetUno } from 'unocss'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    UnoCSS({
      presets: [
        presetUno(),
      ],
    }),
  ],
})

然后在main.ts里加入

// main.ts
import 'uno.css'

这里没有eslint还是很不舒服的,推荐一个antfu的eslint配置,来安装一下eslint

https://github.com/antfu/eslint-config

pnpm add -D eslint @antfu/eslint-config

创建.eslintrc写入

{
  "extends": "@antfu"
}

在package json里加入

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

如果你使用的是VSCode 下载 VS Code ESLint extension 然后编辑 .vscode/settings.json

{
  "prettier.enable": false,
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

之后重启vscode

安装完成😎

使用

我们来写一个小demo

就把原本的默认界面改成unocss好了

先看app.vue

<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo">
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo">
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

改成原子化css的形式

这里最好先配置一下vite config 多加一点预设

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import { presetAttributify, presetIcons, presetTypography, presetUno, presetWebFonts, transformerDirectives, transformerVariantGroup } from 'unocss'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    UnoCSS(
      {
        presets: [
          presetUno(),
          presetAttributify(),
          presetIcons({
            scale: 1.2,
          }),
          presetTypography(),
          presetWebFonts({
            fonts: {
              sans: 'DM Sans',
              serif: 'DM Serif Display',
              mono: 'DM Mono',
            },
          }),
        ],
        transformers: [
          transformerDirectives(),
          transformerVariantGroup(),
        ],
      },
    ),
  ],
})

我们先用@apply将css改一下 <style scoped>

.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}

这里不会的语法可以看tailwindcss文档

改成

.logo {
  @apply h-6em p-1.5em
}
.logo {
  @apply hover:drop-shadow-[0_0_2em_#646cffaa]
}
.logo.vue {
  @apply hover:drop-shadow-[0_0_2em_#42b883aa]
}

怎么样?是不是感觉很清晰明了

这是在tailwindcss里很受欢迎的@apply命令 通过预设导入了unocss

但是这样体现不出原子化css的方便,虽然非常好看

我们把css写入html

<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="h-6em p-1.5em hover:drop-shadow-[0_0_2em_#646cffaa]" alt="Vite logo">
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="hover:drop-shadow-[0_0_2em_#42b883aa]" alt="Vue logo">
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
</style>

这样写就很方便了

再看HelloWorld.vue

<script setup lang="ts">
import { ref } from 'vue'

defineProps<{ msg: string }>()

const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>

  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
      >create-vue</a
    >, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>

这个改的比较少

<script setup lang="ts">
import { ref } from 'vue'

defineProps<{ msg: string }>()

const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">
      count is {{ count }}
    </button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>

  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="text-[#888]">
    Click on the Vite and Vue logos to learn more
  </p>
</template>

<style>
</style>

改成这样就行

再看style.css文件 内容比较多

:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

.card {
  padding: 2em;
}

#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}

一点点儿改

最后改成了这样 还挺还原的是不是

有一些媒体查询不会改

根元素没改

代码如下

// style.css
:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}


@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}
// HelloWorld.vue
<script setup lang="ts">
import { ref } from 'vue'

defineProps<{ msg: string }>()

const count = ref(0)
</script>

<template>
  <h1 class="text-3.2em leading-tight">
    {{ msg }}
  </h1>

  <div class="p-2em flex flex-col place-items-center">
    <button
      type="button" class="
      rounded-8px border-1px border-solid border-transparent
      py-0.6em px-1.2em text-1em font-medium
      bg-[#1a1a1a] cursor-pointer transition duration-250
      hover:border-color-[#646cff]
      focus:outline-4 focus:outline-[-webkit-focus-ring-color]
      focus-visible:outline-4 focus-visible:outline-[-webkit-focus-ring-color]
    " @click="count++"
    >
      count is {{ count }}
    </button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>

  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" class="font-medium color-[#646cff] hover:color-[#535bf2]">create-vue</a>, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank" class="font-medium color-[#646cff] hover:color-[#535bf2]">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="text-[#888]">
    Click on the Vite and Vue logos to learn more
  </p>
</template>

<style>
</style>
// App.vue
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div class="m-0 flex flex-col place-items-center min-w-320px min-h-100vh">
    <div class="max-w-1280px my-0 mx-auto p-2rem text-center">
      <a href="https://vitejs.dev" target="_blank" class="font-medium color-[#646cff] hover:color-[#535bf2]">
        <img src="/vite.svg" class="h-6em p-1.5em hover:drop-shadow-[0_0_2em_#646cffaa]" alt="Vite logo">
      </a>
      <a href="https://vuejs.org/" target="_blank" class="font-medium color-[#646cff] hover:color-[#535bf2]">
        <img src="./assets/vue.svg" class="h-6em p-1.5em hover:drop-shadow-[0_0_2em_#42b883aa]" alt="Vue logo">
      </a>
    </div>
    <HelloWorld msg="Vite + Vue" />
  </div>
</template>

<style>
</style>

可以看出来虽然写的简单,但是还是比较乱

这样写的好处是,不用想类名,

如果加了@apply会更美观,但是还是要写类名,和普通css的区别就是简单了一些

同时还有一些自带的样式预设

结尾

本篇我们介绍了UnoCSS的特点,UnoCSS的使用,还是改了挺长时间的,主要是不会写语法,要查tailwind文档 https://tailwindcss.com/docs

还有一些好几个值的css 不知道什么意思

outline: green solid 3px;
/* mdn:颜色 | 样式 | 宽度 */
outline: 4px auto -webkit-focus-ring-color;
/* style.css */

关于这种就不知道怎么写

最后只能掰成

focus:outline-4 focus:outline-[-webkit-focus-ring-color]

题外话:

Anthony Fu大佬真的是我目前的偶像,狂热的开源爱好者,人长得又帅,做的东西又好

🤣这是我在掘金搜unocss搜到的一篇文章中的第一句话 文章在这 https://juejin.cn/post/7028841960752283656

antfu大佬确实也是我的偶像

22年秋季 antfu大佬在b站办过几场直播写代码,我基本上是每期必看,这个人非常有意思,有很多新鲜的点子,狠活也多 比如说

https://github.com/antfu/1990-script 这个仓库教你怎么把GitHub历史穿越回1990年🤭

他的每个star比较多的仓库我都体验过,感觉设计灵感真的让人眼前一亮,启发基于社区,创造更快的轮子,更好玩的轮子

https://github.com/antfu/retypewriter 这个是直播写的雏形 让写的代码在vsc里回放,便于理清思路

https://github.com/antfu/vue-minesweeper 这个是首次直播写的扫雷,很清晰的vue代码,看他写真的开窍

https://github.com/antfu/vue-starport 这个也是直播写的雏形,为了使组件有更好的过渡

就说这么多,人也长得帅

🥰😘

参考

https://developer.mozilla.org/zh-CN/docs/Web/CSS

https://tailwindcss.com/docs

https://www.tailwindcss.cn/docs/functions-and-directives#apply

https://github.com/unocss/unocss

https://cn.vitejs.dev/

原子化CSS| 青训营笔记

原子化CSS| 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 9 天

前言

原子化 CSS是一个很新颖的定义 出自https://CSS-tricks.com/lets-define-exactly-atomic-CSS/

意思是原子化 CSS是一种 CSS的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。

什么是原子化 CSS?

与一般的我们写正常CSS不同,原子化CSS会把元素本身的属性提取出来,加以视觉效果命名,例如:

如下的html

<button class="btn">Basic</button>

正常的CSS

.btn {
    padding: 1rem, 2rem;
    font-family: 'semi';
    font-weight: bold; 
}

使用原子化CSS后,将不用写CSS文件,改为写class属性

<button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700">
  Click me
</button>

市面上有不少实用至上的 CSS框架,如 Tailwind CSSWindi CSS 以及 Tachyons 等。

同时有些 UI 库也会附带一些 CSS工具类作为框架的补充,如 BootstrapChakra UI

写之前,我们谈谈优势

  • 类名自由:你绞尽脑汁想这个元素的类名怎么写。在你面前的是一点击就会变颜色的按钮,你想给他起个class或者id,你想了半天,最后取了class名为click-it-and-turn-it-from-blue-to-green😰。
  • html嵌入:正常情况我们要引入CSS文件去写这个样式,切来切去不太方便。
  • 预设的原子化CSS:可以引入插件、其他基于原子化CSS写的组件库。
  • IDE支持:插件支持类名提示,很好写。
  • 写的快:比如说hover属性,原子化CSS可以直接写在class里,很快很方便,比如说tailwind自带的响应式布局,不用自己写媒体查询就可以适配移动端。

常用的原子化CSS框架

本文的重点:UnoCSS

讲这么多,我们终于回到主题UnoCSS上

antfu重新构想原子化CSS 这篇文章讲了unocss是怎么诞生的

Windi CSS已经足够优秀,但antfu大佬还是不够满意,对于框架预设外的自定义工具的额外配置上,还是比较繁琐,而且配置的方式也不够简便

由于 Windi 需要与 Tailwind 兼容,它还必须使用与 Tailwind 完全相同的配置项。尽管数字推断的问题得到了解决,但如果你想添加一些自定义的工具,这将是一场噩梦。

所以经过重新构想原子化CSS,UnoCSS出现了

UnoCSS - 具有高性能且极具灵活性的即时原子化 CSS引擎。

它不是像TailWind CSS和Windi CSS属于框架,而是一个引擎,它没有提供预设的原子化CSS工具类

UnoCSS通过编写规则来定制工具类

静态规则

rules: [
    ['m-1', { margin: '0.25rem' }]
]

使用正则表达式来做动态规则

rules: [
  [/^m-(\d)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  [/^p-(\d)$/, (match) => ({ padding: `${match[1] / 4}rem` })],
]

当然这些比较常见的规则unocss已经提供了预设在@unocss/preset-uno

<div class="m-100">
  <button class="m-3">
    <icon class="p-5" />
    My Button
  </button>
</div>

生成出的css为

.m-100 { margin: 25rem; }
.m-3 { margin: 0.75rem; }
.p-5 { padding: 1.25rem; }

unocss提供了一个叫shortcuts的功能,可以批量生成css

shortcuts: [
  // you could still have object style
  {
    btn: 'py-2 px-4 font-semibold rounded-lg shadow-md',
  },
  // dynamic shortcuts
  [/^btn-(.*)$/, ([, c]) => `bg-${c}-400 text-${c}-100 py-2 px-4 rounded-lg`],
]

只需要配置这个字段

在使用中可以使用 btn-greenbtn-red

生成的css如下

.btn-green {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  --un-bg-opacity: 1;
  background-color: rgba(74, 222, 128, var(--un-bg-opacity));
  border-radius: 0.5rem;
  --un-text-opacity: 1;
  color: rgba(220, 252, 231, var(--un-text-opacity));
}
.btn-red {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  --un-bg-opacity: 1;
  background-color: rgba(248, 113, 113, var(--un-bg-opacity));
  border-radius: 0.5rem;
  --un-text-opacity: 1;
  color: rgba(254, 226, 226, var(--un-text-opacity));
}

unocss提供了很多预设

Official Presets
Community Presets

UnoCSS的优点

对比其他原子化框架来说

  • 完全可定制:作为一款引擎没有核心实用程序,所有的功能都是通过预设提供的。

  • 扩展性强:通过各种预设组合达到windi或者tailwind的效果。

  • 快:没有解析,没有AST,没有扫描,它是即时的。

  • 小:运行库打包好只有6kb,无痛

  • Shortcuts:可以写Shortcuts来预设一些常用样式

  • Attributify mode :将class写成组,避免无法阅读的问题

  • Pure CSSIcons :纯 CSSicon 可以在 https://icones.js.org/ 找到并导入成千上万的icon 安装只需要找到你喜欢的 iconfy 库 @iconify-json/[the-collection-you-want] 这样安装就行

  • Variant Groups :和 Attributify mode 看起来几乎是一样的功能,写成组

  • CSSDirectives :可以在style标签里写 @apply 把一系列样式打包成一个样式,在windi里是个很受欢迎的功能

  • Compilation mode :打包优化,生成随机class名

  • Inspector :自带的debug工具

  • CSS-in-JS Runtime build :可以CDN导入

  • VS Code extension:自带VSCode提示扩展,很方便

结尾

本篇我们介绍了UnoCSS的特点,原子化CSS的好处,下一篇讲讲unocss实战,安装使用之类的

题外话:

Anthony Fu大佬真的是我目前的偶像,狂热的开源爱好者,人长得又帅,做的东西又好

🤣这是我在掘金搜unocss搜到的一篇文章中的第一句话 文章在这 https://juejin.cn/post/7028841960752283656

antfu大佬确实也是我的偶像

22年秋季 antfu大佬在b站办过几场直播写代码,我基本上是每期必看,这个人非常有意思,有很多新鲜的点子,狠活也多 比如说

https://github.com/antfu/1990-script 这个仓库教你怎么把GitHub历史穿越回1990年🤭

他的每个star比较多的仓库我都体验过,感觉设计灵感真的让人眼前一亮,启发基于社区,创造更快的轮子,更好玩的轮子

https://github.com/antfu/retypewriter 这个是直播写的雏形 让写的代码在vsc里回放,便于理清思路

https://github.com/antfu/vue-minesweeper 这个是首次直播写的扫雷,很清晰的vue代码,看他写真的开窍

https://github.com/antfu/vue-starport 这个也是直播写的雏形,为了使组件有更好的过渡

就说这么多,人也长得帅

🥰😘

参考

https://juejin.cn/post/7028841960752283656

https://antfu.me/posts/reimagine-atomic-css-zh

https://juejin.cn/post/7027414082378530852

https://juejin.cn/post/7161211941652791304

https://www.tailwindcss.cn/docs/