普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月12日首页

Vite 到底能干嘛?为什么 Vue3 官方推荐它做工程化工具?

作者 刘大华
2025年12月12日 08:26

很多朋友在开发项目的时候其实都有用过Vite,也知道它是现代化构建工具,但却不清楚它是怎么用的。

只是知道项目里集成了Vite,开发的时候启动很快,配置文件也很清晰,但很少去了解它是什么,起到了什么作用。

这篇文章我们就来了解一下。

一、Vite 是什么?

它的名字来源于法语单词"vite",意思是"快速",由 Vue.js 作者尤雨溪开发的一款现代化前端构建工具

它的目标很简单:让开发体验更快、更简单

Vite不仅支持Vue,还原生支持React、Svelte、Preact、Lit等主流框架,甚至可以用于纯 HTML + JavaScript 项目。


二、Vite 的核心优势

Vite之所以火,是因为它解决了传统构建工具的几个痛点:

启动极快 开发服务器启动几乎秒开,不打包!

热更新飞快 修改代码后,浏览器只更新改动的部分,毫秒级响应

原生 ES 模块支持 直接利用现代浏览器的 ES Module 能力

零配置上手 创建项目只需一条命令,无需复杂配置

插件生态丰富 兼容 Rollup 插件,生态强大

开箱即用的丰富功能 支持 TypeScript、JSX、CSS 预处理器等,无需复杂配置。


三、与传统工具(如 Webpack)对比

1. 工作方式的根本不同

Webpack:开发时会先打包整个项目(把所有 JS、CSS 合并成 bundle),然后启动服务器。项目越大,打包越慢。

Vite:开发时不打包!直接利用浏览器原生支持的 ES 模块(<script type="module">),按需加载文件。

2. 举个实际例子

假设你有一个简单的项目结构:

src/
├── main.js
└── utils.js

使用 Webpack(传统方式)

// utils.js
export const add = (a, b) => a + b;

// main.js
import { add } from './utils.js';
console.log(add(1, 2));

Webpack 会: 1.把main.jsutils.js打包成一个bundle.js 2.启动本地服务器,返回这个bundle 3.浏览器加载整个bundle

即使你只改了utils.js中的一行,Webpack也要重新分析依赖、重新打包整个应用(虽然有缓存优化,但仍有延迟)。

使用 Vite

Vite 在开发时直接生成这样的 HTML:

<!-- index.html -->
<script type="module" src="/src/main.js"></script>

浏览器会: 1.请求 /src/main.js 2.发现里面 import './utils.js',自动再请求 /src/utils.js 3.按需加载,无需打包!

当你修改utils.js,Vite只告诉浏览器:“喂,utils.js更新了”, 浏览器只重新加载这一个文件,热更新速度接近实时

Vite 在开发阶段用原生 ESM,在生产阶段会用 Rollup 打包,兼顾速度和兼容性。


四、Vite 的工作原理:为什么这么快?

关键就两点:

1. 利用现代浏览器的原生 ES 模块(ESM)

现代浏览器(Chrome、Firefox、Edge 等)早就支持 <script type="module">,可以直接 import/export 模块,无需打包。

Vite 直接把这个能力用起来——开发时不打包,让浏览器自己去加载模块

2. 依赖预构建(Dependency Pre-bundling)

你可能会问:那 node_modules 里的包怎么办?它们很多不是 ESM 格式啊!

Vite会在首次启动时,用 esbuild(超快的 JS 打包器)把 node_modules 里的依赖预构建为 ESM 格式,并缓存起来。

esbuild 是用 Go 写的,比 Webpack 快 10~100 倍!

预构建只做一次,后续开发直接用缓存

这样既保证了兼容性,又不影响开发速度。


五、如何使用 Vite?

1. 创建项目

# 使用 npm
npm create vite@latest my-vue-app -- --template vue

# 使用 yarn
yarn create vite my-react-app --template react

# 使用 pnpm
pnpm create vite my-vanilla-app --template vanilla

2. 项目结构

my-vite-project/
├── index.html
├── package.json
├── vite.config.js
├── public/
│   └── favicon.ico
└── src/
    ├── main.js
    ├── App.vue
    ├── components/
    └── styles/

3. 开发服务器

# 进入项目目录
cd my-vue-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

访问 http://localhost:5173 即可看到你的应用!

在这里插入图片描述

4. 基础配置示例

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  // 插件配置
  plugins: [vue()],
  
  // 开发服务器配置
  server: {
    port: 5173,
    open: true // 自动打开浏览器
  },
  
  // 构建配置
  build: {
    outDir: 'dist',
    sourcemap: true
  },
  
  // 路径别名
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

5. 完整的Vue组件示例

<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的Vite应用</title>
</head>
<body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
</body>
</html>
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'

createApp(App).mount('#app')
<!-- src/App.vue -->
<template>
  <div class="app">
    <h1>欢迎使用 Vite!</h1>
    <p>当前计数: {{ count }}</p>
    <button @click="increment">点击我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<style>
.app {
  text-align: center;
  padding: 2rem;
}

button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
}
</style>
/* src/style.css */
body {
  margin: 0;
  font-family: Arial, sans-serif;
  background-color: #f5f5f5;
}

* {
  box-sizing: border-box;
}

6. 构建生产版本

# 构建生产版本
npm run build

# 预览生产版本
npm run preview

结语

总的来说,Vite 通过利用现代浏览器的原生能力,在开发阶段省去了打包步骤,大大提升了开发效率。同时,它配置简单、上手容易,还拥有强大的插件生态,非常适合现代前端项目。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《async/await 到底要不要加 try-catch?异步错误处理最佳实践》

《如何查看 SpringBoot 当前线程数?3 种方法亲测有效》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 10 个 MySQL 高级用法,让你的代码又快又好看》

昨天以前首页

Vue3和Vue2的核心区别?很多开发者都没完全搞懂的10个细节

作者 刘大华
2025年12月11日 11:08

大家好,我是大华!这篇我们来讲解Vue2和Vue3的核心区别在哪里?

Vue3是Vue2的升级版,不仅更快,还更好用。它解决了Vue2中一些让人头疼的问题,比如动态添加属性不响应、组件必须包在一个根元素里等等。

下面通过10个常见的对比例子,让你快速看懂Vue3到底新在哪儿、好在哪儿。

1. 响应式系统:Object.defineProperty vs Proxy

Vue 2 无法监听动态添加的属性(除非用 Vue.set);Vue 3 可以直接响应。

// Vue 2 不会触发更新
this.obj.newProp = 'hello'

// Vue 2 正确方式
this.$set(this.obj, 'newProp', 'hello')

// Vue 3 直接赋值即可响应
this.obj.newProp = 'hello'

2. Composition API(组合式 API)



export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}




import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++


3. TypeScript 支持

// Vue 3 + TypeScript(能更好的支持)

interface Props {
  msg: string
}
const props = defineProps()

Vue 2 虽可通过 vue-class-componentvue-property-decorator 支持 TS,但配置复杂且类型推导弱。


4. Fragment(多根节点)



  <header>Header</header>
  <main>Main</main>




  <header>Header</header>
  <main>Main</main>


5. Teleport(传送门)

将 modal 渲染到 body 下,避免样式嵌套问题


  Open Modal
  
    <div class="modal">
      <p>Hello from Teleport!</p>
      Close
    </div>
  



import { ref } from 'vue'
const open = ref(false)

Vue 2 需手动操作 DOM 或使用第三方库(如 portal-vue)。


6. Suspense(异步组件加载)




const res = await fetch('/api/data')
const data = await res.json()



  <div>{{ data }}</div>



  
    
      
    
    
      <div>Loading...</div>
    
  

Vue 2 无原生 ``,需自行管理 loading 状态。


7. 全局 API 变更

// Vue 2
Vue.component('MyButton', MyButton)
Vue.directive('focus', focusDirective)

// Vue 3
import { createApp } from 'vue'
const app = createApp(App)
app.component('MyButton', MyButton)
app.directive('focus', focusDirective)
app.mount('#app')

Vue 3 的应用实例彼此隔离,适合微前端或多实例场景。


8. 生命周期钩子命名变化

// Vue 2
export default {
  beforeDestroy() { /* cleanup */ },
  destroyed() { /* final */ }
}

// Vue 3(Options API 写法)
export default {
  beforeUnmount() { /* cleanup */ },
  unmounted() { /* final */ }
}

// Vue 3(Composition API)
import { onBeforeUnmount, onUnmounted } from 'vue'
onBeforeUnmount(() => { /* cleanup */ })
onUnmounted(() => { /* final */ })

9. v-model 多绑定












10. 显式声明 emits(推荐)



const emit = defineEmits(['submit', 'cancel'])

const handleSubmit = () => emit('submit')




const emit = defineEmits({
  submit: (payload) => typeof payload === 'string',
  cancel: null
})

Vue 2 中 $emit 无需声明,但不利于工具链和文档生成。


这些示例覆盖了 Vue2 和 Vue3 比较关键的差异点。通过代码对比,可以更清楚地看到 Vue3 在开发体验、性能、灵活性和工程化方面有明细的提升。

结尾

总的来说,Vue3 在保持简单上手的同时,增加了更多实用又强大的功能。不管是写代码更轻松了,还是对 TypeScript、大型项目的支持更好了,都让开发者的工作变得更高效。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《async/await 到底要不要加 try-catch?异步错误处理最佳实践》

《如何查看 SpringBoot 当前线程数?3 种方法亲测有效》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 10 个 MySQL 高级用法,让你的代码又快又好看》

前端怎么防止用户复制?这10种方法让你的内容更安全

作者 刘大华
2025年12月10日 10:54

大家好,我是大华!在我们写前端的时候,有时候会遇到这种禁止用户复杂网页内容的需求,这里来分享几种常见的方法,但是这些方法也不能完全阻止内容被复制。

因为用户还是可以通过开发者工具,截图提取文字等等这些方式来进行复制。不过我们也可以在一定程度上提升复制的门槛,防止普通用户随意复制。


以下是几种常用方案:

1. 禁用文本选择

使用 CSS 禁止用户选中文本:

body {
  -webkit-user-select: none; /* Safari */
  -moz-user-select: none;    /* Firefox */
  -ms-user-select: none;     /* IE/Edge */
  user-select: none;         /* 标准语法 */
}

也可以针对特定元素设置:

<div style="user-select: none;">这段文字无法被选中</div>

用户仍可查看源代码或使用其他手段复制。


2. 监听并阻止复制、剪切、粘贴事件

通过 JavaScript 拦截相关键盘和剪贴板事件:

document.addEventListener('copy', (e) => {
  e.preventDefault();
  alert('复制已被禁止');
});

document.addEventListener('cut', (e) => {
  e.preventDefault();
  alert('剪切已被禁止');
});

document.addEventListener('paste', (e) => {
  e.preventDefault();
  alert('粘贴已被禁止');
});

用户还是可以按下F12禁用 JavaScript 或绕过事件监听。


3. 将内容嵌入图片或 Canvas

将关键内容渲染为图片或使用 <canvas> 绘制,使文本不可直接选中:

<img src="text-as-image.png" alt="不可复制的文字">

或使用 canvas 动态绘制:

<canvas id="myCanvas"></canvas>
<script>
  const ctx = document.getElementById('myCanvas').getContext('2d');
  ctx.font = '20px Arial';
  ctx.fillText('这段文字无法复制', 10, 50);
</script>

不利于 SEO、无障碍访问(屏幕阅读器无法读取),且加载慢。


4.用户按F12就出发debug功能

当用户按下F12时,触发debug调试,让用户无法选中页面内容。

<script>
(function antiDebug() {
  let devOpen = false;
  const threshold = 100;

  function check() {
    const start = performance.now();
    debugger;
    const end = performance.now();

    if (end - start > threshold) {
      if (!devOpen) {
        devOpen = true;
        document.body.innerHTML = '<h2>检测到开发者工具,请关闭后重试。</h2>';
        // 可选:上报用户行为
        // fetch('/log-devtools', { method: 'POST' });
      }
    } else {
      devOpen = false;
    }

    setTimeout(check, 1000);
  }

  check();
})();
</script>

5. 动态文本拆分与重组

将文本内容拆分成多个DOM节点,增加复制难度:

<div id="protected-text">
  <!-- 文本将被JavaScript拆分并插入 -->
</div>

<script>
  const text = "这是一段需要保护的机密内容,不能被轻易复制";
  const container = document.getElementById('protected-text');
  
  // 将每个字符用span包裹
  text.split('').forEach(char => {
    const span = document.createElement('span');
    span.textContent = char;
    span.style.display = 'inline-block'; // 增加选择难度
    container.appendChild(span);
  });
</script>

进阶版:随机插入不可见字符或零宽字符:

function obfuscateText(text) {
  const zeroWidthSpace = '\u200B'; // 零宽空格
  return text.split('').map(char => 
    char + zeroWidthSpace.repeat(Math.floor(Math.random() * 3))
  ).join('');
}

const originalText = "保护内容";
document.getElementById('text').textContent = obfuscateText(originalText);

6. 使用CSS伪元素显示内容

通过CSS的::before::after伪元素显示文本:

<style>
.protected-content::before {
  content: "这段文字通过CSS生成,无法直接选中和复制";
}
</style>

<div class="protected-content"></div>

7. 字体反爬虫技术

使用自定义字体文件,将字符映射关系打乱:

@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
}

.protected-text {
  font-family: 'CustomFont';
}

在字体文件中,将实际字符与显示字符的映射关系打乱,比如:

  • 字符a在字体中实际显示为b
  • 字符b显示为c,以此类推

后端配合:服务器动态生成字体文件,定期更换映射关系。


8. Canvas + WebGL 渲染文本

使用更复杂的图形渲染技术:

<canvas id="textCanvas" width="800" height="100"></canvas>
<script>
  const canvas = document.getElementById('textCanvas');
  const ctx = canvas.getContext('2d');
  
  // 设置文字样式
  ctx.font = '24px Arial';
  ctx.fillStyle = '#333';
  
  // 绘制干扰元素
  ctx.fillText('保护内容', 50, 50);
  
  // 添加噪声干扰
  for(let i = 0; i < 100; i++) {
    ctx.fillRect(
      Math.random() * 800, 
      Math.random() * 100, 
      1, 1
    );
  }
</script>

9. SVG 文本渲染

使用SVG渲染文本,增加选择难度:

<svg width="400" height="100">
  <text x="10" y="30" font-family="Arial" font-size="20" 
        fill="black" style="user-select: none;">
    这段SVG文本难以复制
  </text>
  <!-- 添加干扰路径 -->
  <path d="M10,40 L390,40" stroke="#eee" stroke-width="1"/>
</svg>

10. 实时DOM监控与修复

监控DOM变化,防止用户通过开发者工具修改内容:

const contentElement = document.getElementById('protected-content');
const originalContent = contentElement.innerHTML;

// 定时检查内容完整性
setInterval(() => {
  if (contentElement.innerHTML !== originalContent) {
    contentElement.innerHTML = originalContent;
    console.log('检测到内容篡改,已恢复');
  }
}, 500);

// 使用MutationObserver更精确的监控
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'childList' || mutation.type === 'characterData') {
      contentElement.innerHTML = originalContent;
    }
  });
});

observer.observe(contentElement, {
  childList: true,
  characterData: true,
  subtree: true
});

综合防御策略建议

对于高安全要求的场景,建议采用分层防御

class ContentProtector {
  constructor() {
    this.init();
  }
  
  init() {
    this.disableSelection();
    this.bindEvents();
    this.startMonitoring();
    this.obfuscateContent();
  }
  
  disableSelection() {
    document.head.insertAdjacentHTML('beforeend', `
      <style>
        body { -webkit-user-select: none; user-select: none; }
        .protected { cursor: default; }
      </style>
    `);
  }
  
  bindEvents() {
    // 绑定所有阻止事件
    ['copy', 'cut', 'paste', 'contextmenu', 'keydown'].forEach(event => {
      document.addEventListener(event, this.preventDefault);
    });
  }
  
  preventDefault(e) {
    e.preventDefault();
    return false;
  }
  
  startMonitoring() {
    // 启动各种监控
    this.monitorDevTools();
    this.monitorDOMChanges();
  }
  
  obfuscateContent() {
    // 内容混淆处理
    // ...
  }
  
  monitorDevTools() {
    // 开发者工具检测
    // ...
  }
  
  monitorDOMChanges() {
    // DOM变化监控
    // ...
  }
}

// 初始化保护
new ContentProtector();

总结

这些方法只能防普通用户,防不住真正想复制的人。

因为别人还是可以通过看源码、截图、关掉 JS 等方式绕过限制。

所以建议:

  • 别过度防护,以免影响正常用户和 SEO;
  • 重要内容靠后端控制(比如登录才能看全文);
  • 组合使用几种方法,提高门槛就好,别追求绝对安全。

毕竟前端展示的内容,就默认是能被看到的,也就能被复制的。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《async/await 到底要不要加 try-catch?异步错误处理最佳实践》

《都在用 Java8 和 Java17,那 Java9 到 16 呢?他们真的没用吗?》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 10 个 MySQL 高级用法,让你的代码又快又好看》

❌
❌