一、构建工具
1. Webpack核心概念与配置
核心概念:
javascript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// 1. 入口
entry: {
main: './src/index.js',
vendor: './src/vendor.js'
},
// 2. 输出
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: '/'
},
// 3. 模式
mode: 'production', // development, production, none
// 4. Loader
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB以下转base64
}
},
generator: {
filename: 'images/[name].[hash][ext]'
}
}
]
},
// 5. 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css'
})
],
// 6. 解析
resolve: {
extensions: ['.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
// 7. 优化
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
},
// 8. DevServer
devServer: {
port: 3000,
hot: true,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
},
// 9. Source Map
devtool: 'source-map' // eval, source-map, cheap-module-source-map
};
常用Loader:
| Loader | 作用 |
|---|---|
| babel-loader | 转译ES6+ |
| css-loader | 处理CSS |
| style-loader | 注入CSS到DOM |
| sass-loader | 编译Sass |
| less-loader | 编译Less |
| postcss-loader | CSS后处理(autoprefixer) |
| file-loader | 处理文件 |
| url-loader | 转base64 |
| vue-loader | 处理.vue文件 |
| ts-loader | 编译TypeScript |
常用Plugin:
| Plugin | 作用 |
|---|---|
| HtmlWebpackPlugin | 生成HTML |
| MiniCssExtractPlugin | 提取CSS |
| CleanWebpackPlugin | 清理输出目录 |
| CopyWebpackPlugin | 复制文件 |
| DefinePlugin | 定义全局常量 |
| CompressionPlugin | Gzip压缩 |
| BundleAnalyzerPlugin | 分析打包体积 |
2. Vite原理与优势
核心特点:
javascript
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [vue(), react()],
// 开发服务器
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建选项
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia']
}
}
}
},
// 路径别名
resolve: {
alias: {
'@': '/src'
}
},
// CSS配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
}
}
});
Vite vs Webpack:
| 特性 | Vite | Webpack |
|---|---|---|
| 开发服务器启动 | 极快(无需打包) | 慢(需要打包) |
| 热更新速度 | 极快(ESM) | 较慢 |
| 生产构建 | Rollup | Webpack |
| 配置复杂度 | 简单 | 复杂 |
| 生态成熟度 | 较新 | 成熟 |
| 浏览器兼容性 | 现代浏览器 | 可配置 |
Vite原理:
javascript
// 开发环境
// 1. 利用浏览器原生ESM,无需打包
import { createApp } from 'vue'; // 直接请求node_modules
// 2. 按需编译
// 只编译当前访问的模块,不是全量编译
// 3. HMR(热模块替换)
// 利用ESM的import.meta.hot实现精确更新
// 生产环境
// 使用Rollup打包,生成优化的bundles
3. Babel配置与原理
基本配置:
javascript
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: '> 0.25%, not dead', // 目标浏览器
useBuiltIns: 'usage', // 按需引入polyfill
corejs: 3
}
],
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-transform-runtime'
]
};
编译过程:
源代码 → 解析(Parse) → AST → 转换(Transform) → 新AST → 生成(Generate) → 目标代码
示例:
javascript
// 源代码
const add = (a, b) => a + b;
// AST(简化)
{
type: 'VariableDeclaration',
declarations: [{
type: 'VariableDeclarator',
id: { type: 'Identifier', name: 'add' },
init: {
type: 'ArrowFunctionExpression',
params: [
{ type: 'Identifier', name: 'a' },
{ type: 'Identifier', name: 'b' }
],
body: {
type: 'BinaryExpression',
operator: '+',
left: { type: 'Identifier', name: 'a' },
right: { type: 'Identifier', name: 'b' }
}
}
}]
}
// 转换后代码
var add = function(a, b) {
return a + b;
};
二、性能优化
4. 首屏加载优化
优化策略:
javascript
// 1. 代码分割
// 路由懒加载
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');
// 动态导入
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.init();
});
// 2. 资源压缩
// Webpack配置
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
}
}
}),
new CssMinimizerPlugin()
]
}
};
// 3. Gzip压缩
const CompressionPlugin = require('compression-webpack-plugin');
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html)$/,
threshold: 10240, // 10KB以上才压缩
minRatio: 0.8
})
];
// 4. Tree Shaking
// package.json
{
"sideEffects": false // 标记无副作用
}
// 只导入需要的模块
import { debounce } from 'lodash-es';
// 5. 预加载关键资源
<link rel="preload" href="main.js" as="script">
<link rel="preload" href="styles.css" as="style">
<link rel="prefetch" href="next-page.js">
// 6. 图片优化
// WebP格式
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="">
</picture>
// 懒加载
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
// 7. CDN
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
// 8. 骨架屏
// 在首屏加载时显示占位内容
<div class="skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-content"></div>
</div>
// 9. 服务端渲染(SSR)
// Nuxt.js (Vue) / Next.js (React)
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
性能指标:
javascript
// 1. FCP (First Contentful Paint) - 首次内容绘制
// 目标: < 1.8s
// 2. LCP (Largest Contentful Paint) - 最大内容绘制
// 目标: < 2.5s
// 3. FID (First Input Delay) - 首次输入延迟
// 目标: < 100ms
// 4. CLS (Cumulative Layout Shift) - 累积布局偏移
// 目标: < 0.1
// 5. TTFB (Time to First Byte) - 首字节时间
// 目标: < 600ms
// 使用Performance API测量
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
}
});
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint'] });
5. 运行时性能优化
JavaScript优化:
javascript
// 1. 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用
input.addEventListener('input', debounce((e) => {
fetchSearchResults(e.target.value);
}, 300));
// 2. 节流
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用
window.addEventListener('scroll', throttle(() => {
console.log('scrolling');
}, 100));
// 3. 使用Web Workers处理密集计算
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ numbers: largeArray });
worker.onmessage = (e) => {
console.log('计算结果:', e.data);
};
// worker.js
self.onmessage = (e) => {
const result = e.data.numbers.reduce((sum, num) => sum + num, 0);
self.postMessage(result);
};
// 4. requestAnimationFrame优化动画
function animate() {
element.style.transform = `translateX(${position}px)`;
position += 1;
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// 5. IntersectionObserver懒加载
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
// 6. 避免强制同步布局
// ❌ 强制同步布局(慢)
for (let i = 0; i < elements.length; i++) {
const height = elements[i].offsetHeight; // 读取
elements[i].style.height = height * 2 + 'px'; // 写入
}
// ✅ 批量读取,批量写入
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => {
el.style.height = heights[i] * 2 + 'px';
});
CSS优化:
css
/* 1. 使用transform代替位置属性 */
/* ❌ 触发回流 */
.box {
left: 100px;
top: 100px;
}
/* ✅ 只触发合成 */
.box {
transform: translate(100px, 100px);
}
/* 2. 使用will-change提示浏览器 */
.box {
will-change: transform;
}
/* 3. 避免复杂选择器 */
/* ❌ 性能差 */
.container > .list li:nth-child(odd) .item .text {
color: red;
}
/* ✅ 使用类名 */
.item-text-odd {
color: red;
}
/* 4. 使用contain提高渲染性能 */
.card {
contain: layout style paint;
}
/* 5. 使用content-visibility延迟渲染 */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px;
}
6. 网络优化
HTTP优化:
javascript
// 1. HTTP/2多路复用
// 服务器配置启用HTTP/2
// 2. 资源合并(HTTP/1.1)
// 合并CSS、JS文件
// 3. 域名分片(HTTP/1.1)
// 静态资源使用多个域名
// 4. 使用Service Worker缓存
// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = [
'/',
'/styles.css',
'/script.js',
'/images/logo.png'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response; // 缓存命中
}
return fetch(event.request); // 网络请求
})
);
});
// 5. 资源提示
<link rel="dns-prefetch" href="https://api.example.com">
<link rel="preconnect" href="https://cdn.example.com">
<link rel="prefetch" href="/next-page.js">
<link rel="preload" href="/critical.css" as="style">
// 6. 使用HTTP缓存
// 服务器响应头
Cache-Control: max-age=31536000, immutable // 静态资源
Cache-Control: no-cache // HTML文件
// 7. 启用Brotli压缩
// 服务器配置
Content-Encoding: br
API优化:
javascript
// 1. 接口聚合
// ❌ 多个请求
const user = await fetch('/api/user/1');
const posts = await fetch('/api/user/1/posts');
const comments = await fetch('/api/user/1/comments');
// ✅ 单个请求
const data = await fetch('/api/user/1?include=posts,comments');
// 2. GraphQL按需查询
query {
user(id: 1) {
name
email
posts {
title
}
}
}
// 3. 分页加载
async function loadMore() {
const response = await fetch(`/api/posts?page=${page}&limit=20`);
const posts = await response.json();
appendPosts(posts);
page++;
}
// 4. 请求取消
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求已取消');
}
});
// 取消请求
controller.abort();
// 5. 请求缓存
const cache = new Map();
async function fetchWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
7. 打包优化
Webpack优化:
javascript
// webpack.config.js
module.exports = {
// 1. 缩小构建目标
resolve: {
extensions: ['.js', '.jsx'], // 减少扩展名
modules: [path.resolve(__dirname, 'src'), 'node_modules'], // 指定查找路径
alias: {
'@': path.resolve(__dirname, 'src')
}
},
// 2. noParse跳过解析
module: {
noParse: /jquery|lodash/
},
// 3. 多线程构建
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: { workers: 4 }
},
'babel-loader'
]
}
]
},
// 4. 缓存
cache: {
type: 'filesystem'
},
// 5. DLL预编译
// webpack.dll.config.js
module.exports = {
entry: {
vendor: ['react', 'react-dom', 'lodash']
},
output: {
path: path.join(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'dll', '[name]-manifest.json'),
name: '[name]_library'
})
]
};
// webpack.config.js
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor-manifest.json')
})
],
// 6. 分析打包
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin()
]
};
按需加载:
javascript
// 1. React懒加载
import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
// 2. Vue懒加载
const LazyComponent = () => import('./LazyComponent.vue');
// 3. 动态导入
button.addEventListener('click', async () => {
const { default: module } = await import('./module.js');
module.init();
});