普通视图

发现新文章,点击刷新页面。
今天 — 2025年7月3日首页

vite和webpack打包lib库细节

作者 gnip
2025年7月3日 10:49

概述

常见的组件库,业务工程项目,都会用到各式各样的npm包,打包的格式也很多元,比如umd,cjs,es等,不同打包格式,适合不同环境不同导入方式,以下是关于现在主流webpack和vite这两个环境打包的配置信息,如果自己要写npm包给别人用,打包配置必不可少。

webpack配置

webpack.config.js配置如下, 依赖如下:

  • webpack
  • webpack-cli
  • vue-loader
  • clean-webpack-plugin
  • ts-loader
  • style-loader
  • css-loader
  • postcss-loader
  • sass-loader/less-loader(根据情况定)

如下配置会将所有资源输出到一份文件中,如果需要将资源分块输出,可以参考webpack分解到不同文件配置

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');

//注意:wenbapack单入口输出多文件,需要数组形式,CleanWebpackPlugin关闭,不然会覆盖其他生成的文件
const baseConfig = {
  mode: 'production',
  entry: path.resolve(__dirname, './src/index.js'),
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          reactivityTransform: true // 可选:启用 Vue 3 响应性语法糖
        }
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
          transpileOnly: true,
          // 使用项目中的 tsconfig.json
          configFile: path.resolve(__dirname, 'tsconfig.json')
        },
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [
         'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.scss$/,
        use: [
         'style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/,
        type: 'asset/inline',
      },
      {
        test: /\.(woff2?|eot|ttf|otf)$/,
        type: 'asset/inline',
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    // new CleanWebpackPlugin(),
  ],
  optimization: {
    minimize: true,
    splitChunks: false // 禁用代码分割,因为我们打包的是库
  },
  performance: {
    hints: false,
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
  }
};
module.exports = (env, argv) => {
  return [
    {
      ...baseConfig,
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'index.umd.js',
        library: {
          name: ['myNamespace','MyComponent'],
          type: 'umd',
        },
        umdNamedDefine: false,
  
      },
    },
    {
      ...baseConfig,
      experiments: {
        outputModule: true // 启用实验性 ESM 输出支持
          },
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'index.es.js',
  
        library: {
          type: 'module',
        },
        umdNamedDefine: false,
  
      },
    },
    {
      ...baseConfig,
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'index.cjs',
        library: {
          name: ['myNamespace','MyComponent'],
          type: 'commonjs',
        },
        umdNamedDefine: false,
  
      },
    },
  ]
};

vite配置

vite.config.js 依赖如下:

  • vite-plugin-css-injected-by-js(将所有资源内聚到js中)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
// https://vite.dev/config/
export default defineConfig({
 plugins: [vue(),
   cssInjectedByJsPlugin() // 强制 CSS 内联到 JS
],
 build: {
   minify: true, 
   emptyOutDir: true ,
   cssCodeSplit: false, //禁止代码分割  
   assetsInlineLimit: 100 * 1024 * 1024, // 100MB
   lib: {
     entry: './src/index.js', // 组件入口文件
     name: `myNamespace.MyComponent`,     // 全局变量名(与平台约定)
     formats: ['umd',"es","cjs"],        // 必须使用UMD格式
     fileName: 'index' // 输出文件名
   },
   rollupOptions: {
    
     output: {
       manualChunks: undefined, // 强制内联动态导入
       inlineDynamicImports: true,
       entryFileNames:'[name].[format].js'
     }
   }
 }
})

最后执行输入如下

  • vite

image.png

  • webpack

image.png

昨天 — 2025年7月2日首页

彻底搞懂浏览器事件循环:从原理到最佳实践

作者 gnip
2025年7月1日 09:28

概述

要真正理解事件循环,我们需要先了解浏览器的多进程架构:

  • 浏览器主进程:负责界面显示、用户交互

  • GPU进程:处理图形渲染

  • 网络进程:处理网络请求

  • 渲染进程(核心):每个标签页一个渲染进程,包含:

    • 主线程:执行JS、解析HTML/CSS、布局、绘制(就是我们常说的JS线程)
    • 合成线程:负责图层分割
    • 光栅线程:将图层转换为像素

🔍 关键点:JS引擎(如V8)只是渲染进程的一部分,JS的"单线程"指的是主线程的单线程,浏览器整体是多线程的。

1. 事件循环的完整运行机制

1.1 核心组件详解

(1) 调用栈(Call Stack)

  • 本质:记录函数调用的数据结构(LIFO栈)

  • 特点:

    • 每次函数调用都会创建新的栈帧(包含参数、局部变量等)
    • 栈溢出:当递归深度超过最大调用栈大小(Chrome约1万层)
// 栈溢出示例
function stackOverflow() {
  stackOverflow()
}
stackOverflow() // Uncaught RangeError

(2) 堆内存(Heap)

  • 存储引用类型(对象、数组等)的内存区域

  • 与栈的区别:

    • 栈:自动分配固定大小内存(基础类型、指针)
    • 堆:动态分配内存,需要垃圾回收

(3) 任务队列系统

队列类型 触发方式 优先级 示例
微任务队列 JS引擎直接管理 Promise.then, queueMicrotask
宏任务队列 由浏览器宿主环境管理 setTimeout, 事件回调
动画回调队列 requestAnimationFrame 特殊 动画更新
空闲回调队列 requestIdleCallback 最低 非关键任务

💡 重要细节:微任务会在每个宏任务执行完后立即清空,包括渲染前和事件循环的每个阶段之间。

1.2 完整事件循环流程

image.png

关键阶段说明:

  1. 微任务检查点

    • 在每个宏任务结束后
    • 在每次事件循环迭代开始时
  2. 渲染时机

    • 约60fps(每16.6ms)
    • 受垂直同步信号影响
  3. 任务优先级

    用户交互 > 微任务 > 宏任务 > 空闲任务
    

2. 深度解析异步任务

2.1 宏任务(MacroTask)详解

  • 本质:由浏览器环境(而非JS引擎)管理的任务

  • 完整分类

    • DOM事件(click等)
    • 网络回调(XHR/Fetch)
    • IndexedDB操作
    • History API
    • setTimeout/setInterval
    • postMessage
    • MessageChannel
// 宏任务执行顺序测试
setTimeout(() => console.log('timeout1'), 0)
const channel = new MessageChannel()
channel.port1.postMessage(null)
channel.port2.onmessage = () => console.log('message channel')
setTimeout(() => console.log('timeout2'), 0)
// 输出顺序:message channel → timeout1 → timeout2

2.2 微任务(MicroTask)深度解析

  • 运行时机

    • 在每个宏任务之后
    • 在每次事件循环开始前
  • 特殊行为

    • 微任务中可以递归添加微任务
    • 微任务队列必须完全清空才会继续事件循环
// 微任务递归示例
function recursiveMicrotask() {
  Promise.resolve().then(() => {
    console.log('微任务执行')
    recursiveMicrotask()
  })
}
// 会导致页面卡死,因为微任务不断产生

2.3 requestAnimationFrame的特殊性

  • 执行时机:在样式计算和布局之后,绘制之前

  • 与事件循环的关系:

image.png

3. 浏览器渲染机制与事件循环

3.1 渲染管线关键阶段

  1. JavaScript:改变DOM/CSS
  2. 样式计算:计算最终CSS
  3. 布局:计算元素几何信息
  4. 绘制:生成绘制指令
  5. 合成:图层合并

3.2 事件循环与渲染的协同

function testRender() {
  box.style.width = '100px' // 触发重排
  requestAnimationFrame(() => {
    console.log('RAF:', box.offsetWidth) // 读取最新布局
  })
  box.style.height = '200px' // 再次重排
}
// 现代浏览器会合并样式修改

4. 实战中的性能优化

4.1 避免布局抖动

// 反模式:强制同步布局
function layoutThrashing() {
  for(let i = 0; i < 100; i++) {
    el.style.width = el.offsetWidth + 1 + 'px'
  }
}

// 优化方案:批量读取和修改
function optimizedLayout() {
  const width = el.offsetWidth
  for(let i = 0; i < 100; i++) {
    width += 1
  }
  el.style.width = width + 'px'
}

4.2 合理使用任务拆分

function processLargeTask() {
  const chunkSize = 1000
  let i = 0
  
  function processChunk() {
    const end = Math.min(i + chunkSize, data.length)
    for (; i < end; i++) {
      // 处理数据
    }
    if (i < data.length) {
      // 使用宏任务让出主线程
      setTimeout(processChunk, 0)
      // 或者使用更现代的API
      // scheduler.postTask(() => processChunk(), {priority: 'background'})
    }
  }
  
  processChunk()
}

5. Node.js与浏览器事件循环差异

特性 浏览器 Node.js
微任务执行时机 每个宏任务之后 每个事件循环阶段之间
setImmediate 不存在 专门阶段
优先级 微任务 > 宏任务 微任务 > setImmediate
渲染时机 每帧检查 不适用

6. 最新规范变化

  • isInputPending API:检测是否有挂起的用户输入
  • scheduler API:更细粒度的任务调度
  • 优先级API:可以指定任务优先级

总结与思考题

关键结论

  1. 微任务会在当前宏任务结束后立即执行
  2. 渲染发生在微任务执行后、下一个宏任务前
  3. RAF回调在渲染前执行
  4. 长时间运行的微任务会阻塞渲染

思考题

console.log('script start')

setTimeout(() => {
  console.log('timeout1')
  Promise.resolve().then(() => console.log('promise1'))
}, 0)

requestAnimationFrame(() => {
  console.log('rAF')
  Promise.resolve().then(() => console.log('promise2'))
})

Promise.resolve().then(() => {
  console.log('promise3')
  setTimeout(() => console.log('timeout2'), 0)
})

console.log('script end')

请分析输出顺序,并说明每一部分的执行时机,可以尝试判断输出结果


如果觉得本文有帮助,给个点赞收藏+关注  🚀

❌
❌