前端打包工具介紹和對比

語言: CN / TW / HK

在日常專案開發中構建工具是必不可少的,所以也是我們需要掌握學習的一個重要部分,在技術蓬勃發展的今天,許多優秀的構建工具湧現出來,前端主流打包工具主要有 grunt,gulp,rollup,webpack,vite,parcel等

那麼接下來我們就介紹下比較常用的三個打包工具rollup,webpack,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 字首 // 相當於[email protected]的 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/, '') }, } } })

```

| 對比項| rollup | webpack | vite |
| --- | --- | --- | --- | | 啟動速度 | /| 慢:執行機制:會先進行打包操作,完成之後服務再去請求資源,時間自然就長了 ;底層原理:js寫的| 快:執行機制:不進行打包操作就不用分析模組依賴,編譯等操作,直接啟動開發伺服器,然後按需編譯(得益於現代瀏覽器本身支援es-module);底層原理:基於esbuild進行預構建,速度只有webpack的2%-3%;esbuild用go寫的,速度比js寫的快10-100倍,go(納秒級)語言本身相對js(毫秒級)來說就有很大的優勢,(並行,執行緒之間共享記憶體等)https://blog.csdn.net/weixin_43867717/article/details/122854181完全是自己開發的沒有第三番 | 熱更新速度 | / | 慢:需要重新將這個模組的所有依賴重新編譯 | 快:某個模組內容改變,重新請求該模組 | 打包速度 | | | | 打包體積大小 | 藉助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" 1. 不支援require改用import下的方法引入資源,官方解釋,ssr不支援require 靜態資源匯入可使用一下兩種方法進行匯入替代require,或者直接使用路徑匯入

image.png

image.png