前端打包工具(工具链)介绍和对比

11,344 阅读7分钟

在日常项目开发中构建工具是必不可少的,所以也是我们需要掌握学习的一个重要部分,在技术蓬勃发展的今天,许多优秀的构建工具涌现出来,前端主流打包工具主要有 grunt,gulp,rollup,webpack,vite,parcel等

那么接下来我们就介绍下比较常用的三个打包工具rollup,webpack,vite对比下这几个打包工具的优缺点和适用场景

tip:vite是下一代的前端工具链而非传统意义上的打包工具(这里有点标题党了,感谢大佬指正)

rollup---适合打包类库

Rollup 是一个 JavaScript 模块打包器,他所负责的工作只是把我们写的代码转化为js

特点:原生支持tree shaking(去除无用代码),支持同时生成umd、commonjs,es的代码

缺点:

  1. 模块过于静态化,HMR很难实现
  2. 仅面向ES module,无法可靠的处理commonjs以及umd依赖

rollup基本配置

import resolve from 'rollup-plugin-node-resolve';// 提供解析import引入第三方依赖支持(node_modules里的文件)
import commonjs from '@rollup/plugin-commonjs';//插件将它们转换为ES6版本
import postcss from 'rollup-plugin-postcss';
import image from '@rollup/plugin-image';
import babel from 'rollup-plugin-babel';//将代码转译为浏览器或者node支持的语法,
import json from 'rollup-plugin-json'//引入该插件可以让源码中可以用import引入json文件
import serve from 'rollup-plugin-serve';//本地服务
import livereload from 'rollup-plugin-livereload';//热更新
const input = 'src/index.js'
export default {
  input, // 打包入口//必填
  external:[],//指出应该将哪些模块看做外部模块,不和我们的源码包打在一起,该参数接收数组或者参数为模块名称的函数,返回true则将被看做外部引入不打包在一起
  output: [{ // 打包出口
    file: 'dist/index.js', // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
    format: 'umd', // 必填//umd是兼容amd/cjs/iife的通用打包格式,适合浏览器//不设置该选项导出代码无法执行(无法识别export)
    name:'index',//导出的文件名称//必填
    globals:{//设置全局变量?
      jquery:"$"
    }
  },
  {
    file: input.replace('src/', 'dist/').replace('.js', '.cjs'),
    format: 'cjs'
  },{
    file: input.replace('src/', 'dist/').replace('.js', '.mjs'),
    format: 'esm'
  }],
  plugins: [ // 打包插件
    postcss({
             extensions: [ '.css' ],
           }),
    resolve(), // 查找和打包node_modules中的第三方模块
    commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理,用在其他插件转换源码之前,放置其他插件改变破坏CommonJS的检测
    babel({
      exclude: 'node_modules/**' // 只转译我们的源代码
  }),
    image(),
    json(),//让源码可以引入json 文件
     // 热更新 默认监听根文件夹
     livereload({watch:'dist'}),
     // 本地服务器
     serve({
       open: true, // 自动打开页面
       port: 8000, 
       openPage: '/src/test.html', // 打开的页面
       contentBase: ''
     })
  ]
};

webpack---适合打包项目

配置项复杂,社区丰富 在webpack项目中需要使用的静态资源,css,以及less等预处理器需要手动引入对应的loader才能使用, 支持代码分割,热更新,丰富的插件系统,通过使用loader可以解析各种类型的资源等

缺点:冷启动,热更新随着项目体量增大而变慢,体量大的项目热更新一次甚至需要几分钟

webpack的底层原理 找到入口->形成文件之间的依赖关系树=>通过loader处理对应资源=>生成打包结果=>启动本地服务进行渲染=>浏览器请求,整个包返回

在整个工作过程的每个环节都有预留钩子,插件可以在不同环节进行一些自定义任务

webpack原理图:

image.png

webpack基本配置

//webpack.base.config.js
const path = require('path')// 引入 path 模块
const CopyWebpackPlugin = require('copy-webpack-plugin')// 引入静态资源复制插件
const isProduction = process.env.NODE_ENV === 'production'// 判断当前环境是否是生产环境
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 样式单独分离到一个文件中
module.exports = {
  // 打包入口地址
  entry: {
    // 由于可能是多页,所以采用对象的形式
    index: ['./src/views/index/index.js']
  },
  // 模块resolve的规则
  resolve: {
    //自动的扩展后缀,比如一个js文件,则引用时书写可不要写.js
    extensions: ['.js', '.json', '.css', '.less'],
    // 路径别名
    alias: {
      '@': path.join(__dirname, '../src')
    }
  },
  // 构建目标
  target: ['web', 'es5'],
  // context 是 webpack entry 的上下文,是入口文件所处的目录的绝对路径,默认情况下是当前根目录。
  // 由于我们 webpack 配置文件放于 build 目录下,所以需要重新设置下 context ,使其指向根目录。
  context: path.resolve(__dirname, '../'),
  plugins: [
    // 把public的一些静态文件复制到指定位置,排除 html 文件
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../public'),
          globOptions: {
            dot: true,
            gitignore: false,
            ignore: ['**/*.html']
          }
        }
      ]
    })
  ],
 module: {  // 不同类型模块的处理规则
     rules: [// 处理 css、less 文件
       {
         test: /\.(css|less)$/,
         use: [
           isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
           {
             loader: 'css-loader',
             options: {
               sourceMap: !isProduction, // 是否使用source-map
               esModule: false// 兼容IE11
             }
           },
           {
             loader: 'postcss-loader',
             options: {
               sourceMap: !isProduction // 是否使用source-map
             }
           },
           'less-loader'
         ]
       },
      {// 解析 html 中的 src 路径
         test: /\.html$/,
         use: 'html-loader'
       },
       {// 对图片资源文件进行处理,webpack5已经废弃了url-loader,改为type
         test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
         type: 'asset',
         exclude: [path.resolve(__dirname, 'src/assets/imgs')],
         generator: {
           filename: 'imgs/[name].[contenthash][ext]'
         }
       },
      {// 对字体资源文件进行处理,webpack5已经废弃了url-loader,改为type
         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
         type: 'asset',
         generator: {
           filename: 'fonts/[name].[contenthash][ext]'
         }
       },
      {// 对音频资源文件进行处理,webpack5已经废弃了url-loader,改为type
         test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
         type: 'asset',
         exclude: [path.resolve(__dirname, 'src/assets/medias')],
         generator: {
           filename: 'medias/[name].[contenthash][ext]'
         }
       },
       {
        test: /\.(m|j)s$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true
            }
          }
        ]
      }
     ]
   }
}

//webapck.dev.config.js
const { merge } = require('webpack-merge')// 引入合并对象插件
const path = require('path')// 引入 path 模块
const HtmlWebpackPlugin = require('html-webpack-plugin')// 引入打包html插件
const baseWebpackConfig = require('./webpack.base.config')// 引入基础配置文件
const devWebpackConfig = merge(baseWebpackConfig, {
  // 模式,必填项
  mode: 'development',
  // 开启持久化缓存
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },
  output: {
    // 输出文件目录
    path: path.resolve(__dirname, '../dist'),
    // 输出文件名
    filename: 'js/[name].js',
  },
  // 源码映射
  devtool: 'eval-cheap-module-source-map',
  // 开发服务配置
  devServer: {
    // 服务器 host,默认为 localhost
    host: '0.0.0.0',
    // 服务器端口号,默认 8080
    port: 7001,
    // 静态资源属性
    static: {
      // 挂载到服务器中间件的可访问虚拟地址
      // 例如设置为 /static,在访问服务器静态文件时,就需要使用 /static 前缀
      // 相当于webpack-dev-server@3.X的 contentBasePublicPath 属性
      publicPath: './',
      // 告诉服务器从哪里提供内容
      directory: path.join(__dirname, '../public')
    },
    // 需要监听的文件,由于是多页应用,无法实现热更新,所以都只能刷新页面
    watchFiles: ['src/**/*']
  },
  // 插件
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/views/index/index.html',
      favicon: path.resolve(__dirname, '../public/favicon.ico')
    })
  ]
})
module.exports = devWebpackConfig

vite---适合打包项目

开箱即用 对于项目中需要的静态资源,html,less等无需手动引入loader来处理这些资源,直接引入对应的库即可

vite的底层原理 开启web服务器=>浏览器请求对应资源=>本地服务器编译对应文件=>编译结果返回给浏览器渲染;

vite原理图

image.png 启动服务器=> 请求模块时按需动态编译显示;跟webpack相比开发环境的又是在项目大的时候优势就很明显了

开发环境:

基于esbuild进行预构建,不进行打包操作,依托于浏览器本身对es module的解析,热更新时只更新修改部分,从而达到短时间更新的效果

生产环境:

通过rollup进行打包,rollup支持原生的tree shaking所以打出来的包体量小 配置项主要分为以下几大类,

  • resolve,plugins等共享配置,
  • build,打包配置
  • build.rollupOptions(rollup底层的配置,将合并vite本身默认的rollup选项),
  • server,本地服务配置
  • preview,预览配置
  • optimizeDeps,
  • ssr:服务端渲染配置

vite基本配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import jsx from '@vitejs/plugin-vue-jsx'
// 加别名的三种方法
// const path = require("path");//2
import { resolve } from "path" //3 主要用于alias文件路径别名
// // 加别名的函数3
// function pathResolve(dir) {
//   return resolve(__dirname, ".", dir)
// }
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(),jsx()],
  resolve:{
    alias:[{//1别名配置
      find: '@',
      replacement: '/src',
    },{
      find: '_v',
      replacement: '/src/views',
    },{
      find: '_c',
      replacement: '/src/components',
    }],
    //  alias:{ "/c": path.resolve(__dirname, "./src/components"),},//2需要用绝对路径
    // alias:{ "/c": pathResolve("src/components"),},//3文件格式已被处理,根目录前不用加/
    extensions:['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json','vue']
  },
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        // nested: resolve(__dirname, 'nested/index.html')
      }
    }
  },
  server:{
    hmr: true,  // 开启热更新
    proxy:{
       // 选项写法
       '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
    }
  }
})

对比项rollupwebpackvite
启动速度/慢:运行机制:会先进行打包操作,完成之后服务再去请求资源,时间自然就长了 ;底层原理:js写的快:运行机制:不进行打包操作就不用分析模块依赖,编译等操作,直接启动开发服务器,然后按需编译(得益于现代浏览器本身支持es-module);底层原理:基于esbuild进行预构建,速度只有webpack的2%-3%;esbuild用go写的,速度比js写的快10-100倍,go(纳秒级)语言本身相对js(毫秒级)来说就有很大的优势,(并行,线程之间共享内存等)blog.csdn.net/weixin_4386…
热更新速度/慢:需要重新将这个模块的所有依赖重新编译快:某个模块内容改变,重新请求该模块
打包速度
打包体积大小借助es6模块的静态分析,只打包用的到代码,比Webpack和Browserify使用的CommonJS模块机制更高效需手动优化,通过插件(uglify)来进行代码压缩 tree shaking有默认配置项,利用rollup进行打包,rollup原生支持tree shaking和esm
配置难度小,开箱即用,有默认的优化配置
兼容性//开发环境因为esm不支持IE,移动端百度,uc不支持
生态,社区活跃度活跃度高,社区丰富,老牌打包工具新崛起的工具,社区相比webpack没那么丰富
使用上/文件结构不同,可以用require引入资源,通过process.env获取环境变量等项目中不支持reuqire,通过import引入资源,import.meta.env获取环境变量,变量名必须以VITE_开头

问题集锦

  1. 既然esbuild构建速度这么快为什么vite不用esbuild打包呢?

答:目前暂不支持代码分隔和css相关处理,但是生产环境的代码仍需一些分包功能来提升应用性能

vite使用问题集锦(vite2.x+vue2.x)

  1. 使用jsx报错,在script里加上lang="jsx"
  2. 不支持require改用import下的方法引入资源,官方解释,ssr不支持require 静态资源导入可使用一下两种方法进行导入替代require,或者直接使用路径导入

image.png

image.png

tip:需要转载的小伙伴请注明出处哦