webpack專案開發完後,如何最佳化打包速度?

ajajaz發表於2024-06-20

Gzip壓縮

前端頁面檔案快取

我們先來簡單回顧下 http 快取的知識:

  • HTTP1.0 是透過Expires(檔案過期時間)和Last-Modified(最近修改時間)來告訴瀏覽器進行快取的,這兩個欄位都是 UTC 時間(絕對時間)。Expires 過期控制不穩定,因為瀏覽器端可以隨意修改本地時間,導致快取使用不精準。而且 Last-Modified 過期時間只能精確到秒。
  • HTTP1.1 透過Cache-Contorl和 Etag(版本號)進行快取控制。瀏覽器先檢查 Cache-Control,如果有,則以Cache-Control 為準,忽略Expires。如果沒有 Cache-Control,則以Expires 為準。

Cache-Control 除了可以設定 max-age(相對過期時間,以秒為單位)以外,還可以設定如下幾種常用值:

  • public,資源允許被中間伺服器快取。瀏覽器請求伺服器時,如果快取時間沒到,中間伺服器直接返回給瀏覽器內容,而不必請求源伺服器。
  • private,資源不允許被中間代理伺服器快取。瀏覽器請求伺服器時,中間伺服器都要把瀏覽器的請求透傳給伺服器。
  • no-cache,不管本地副本是否過期,每次訪問資源,瀏覽器都要向伺服器詢問,如果檔案沒變化,伺服器只告訴瀏覽器繼續使用快取(304)。
  • no-store,瀏覽器和中間代理伺服器都不能快取資源。每次訪問資源,瀏覽器都必須請求伺服器,並且,伺服器不去檢查檔案是否變化,而是直接返回完整的資源。
  • must-revalidate,本地副本過期前,可以使用本地副本;本地副本一旦過期,必須去源伺服器進行有效性校驗。
  • proxy-revalidate,要求代理伺服器針對快取資源向源伺服器進行確認。
  • s-maxage:快取伺服器對資源快取的最大時間。

現在 99%的瀏覽器都是 HTTP1.1 及以上版本,我們配置快取就使用 Cache-Contorl 和 Etag 配合就好了。

伺服器配置快取

檔名帶 hash 的(即 css、js、font 和 img 目錄下的所有檔案)設定一個月快取,瀏覽器可以直接使用快取不需要請求伺服器。其他的檔案(index.html 和 static 目錄下的檔案)設定為 no-cache,即每次都來伺服器檢查是否最新。nginx 配置如下:

server {
    location = /index.html {
        add_header Cache-Control no-cache;
    }

    location ~ /static/ {
        add_header Cache-Control no-cache;
    }

    location ~ /(js/_|css/_|img/_|font/_) {
        expires 30d;
        add_header Cache-Control public;
    }
}

前端檔案設定 gzip 壓縮

首先需要安裝一個 webpack 外掛,作用是將大檔案壓縮成 gzip 的格式。執行一下命令進行安裝:

npm install --save-dev compression-webpack-plugin

安裝成功後,在 vue.config.js 進行配置,配置如下:

const CompressionWebpackPlugin = require("compression-webpack-plugin");
// 可加入需要的其他檔案型別,比如json
// 圖片不要壓縮,體積會比原來還大
const productionGzipExtensions = ["js", "css"];

module.exports = {
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === "production") {
      return {
        plugins: [
          new CompressionWebpackPlugin({
            // filename: '[path].gz[query]',
            algorithm: "gzip",
            test: new RegExp(
              "\\.(" + productionGzipExtensions.join("|") + ")$"
            ),
            threshold: 10240, //對超過10k的資料進行壓縮
            minRatio: 0.6, // 壓縮比例,值為0 ~ 1
          }),
        ],
      };
    }
  },
};

這樣配置後,打包完的 js 和 css 檔案就多了字尾名為 gz 的檔案,下面是是否開啟 gzip 壓縮的檔案大小對比:

File Size Gzipped
dist\static\lib\echarts.4.0.6.min.js 729.99KB 243.57KB
dis\static\lib\jquery.3.3.1.min.js 84.89KB 29.65KB
dist\js\chuck-vendors.41428558.js 2316.10KB 623.74KB
dist\js\app.aea87398.js 1701.24KB 447.00KB
dist\css\app.c6e9f88a.css 464.43KB 137.84KB
dist\css\chunk-vendors.aa340280.css 263.42KB 38.56KB

伺服器配置 gzip 壓縮

Nginx 伺服器的配置檔案 nginx.conf 的 http 模組:

server {
  # 開啟gzip on為開啟,off為關閉
  gzip on;
  # 檢查是否存在請求靜態檔案的gz結尾的檔案,如果有則直接返回該gz檔案內容,不存在則先壓縮再返回
  gzip_static on;
  # 設定允許壓縮的頁面最小位元組數,頁面位元組數從header頭中的Content-Length中進行獲取。
  # 預設值是0,不管頁面多大都壓縮。
  # 建議設定成大於10k的位元組數,配合compression-webpack-plugin
  gzip_min_length 10k;
  # 對特定的MIME型別生效,其中'text/html’被系統強制啟用
  gzip_types text/javascript application/javascript text/css application/json;
  # Nginx作為反向代理的時候啟用,開啟或者關閉後端伺服器返回的結果
  # 匹配的前提是後端伺服器必須要返回包含"Via"的 header頭
  # off(關閉所有代理結果的資料的壓縮)
  # expired(啟用壓縮,如果header頭中包括"Expires"頭資訊)
  # no-cache(啟用壓縮,header頭中包含"Cache-Control:no-cache")
  # no-store(啟用壓縮,header頭中包含"Cache-Control:no-store")
  # private(啟用壓縮,header頭中包含"Cache-Control:private")
  # no_last_modefied(啟用壓縮,header頭中不包含"Last-Modified")
  # no_etag(啟用壓縮,如果header頭中不包含"Etag"頭資訊)
  # auth(啟用壓縮,如果header頭中包含"Authorization"頭資訊)
  # any - 無條件啟用壓縮
  gzip_proxied any;
  # 請求加個 vary頭,給代理伺服器用的,有的瀏覽器支援壓縮,有的不支援,所以避免浪費不支援的也壓縮
  gzip_vary on;
  # 同 compression-webpack-plugin 外掛一樣,gzip壓縮比(1~9),
  # 越小壓縮效果越差,但是越大處理越慢,一般取中間值
  gzip_comp_level 6;
  # 獲取多少記憶體用於快取壓縮結果,‘16  8k’表示以8k*16 為單位獲得。
  # PS: 如果沒有.gz檔案,是需要Nginx實時壓縮的
  gzip_buffers 16 8k;
  # 注:99.99%的瀏覽器基本上都支援gzip解壓了,所以可以不用設這個值,保持系統預設即可。
  gzip_http_version 1.1;
}

配置完 nginx 然後進行重啟,如果此時發現報錯資訊是 unknown directive "gzip_static" ,意味著 nginx 沒有安裝該模組,解決辦法如下: 進入到 nginx 安裝目錄,執行以下命令:
進入到 nginx 安裝目錄,執行以下命令:

./configure --with-http_gzip_static_module

然後執行:

make && make install

關閉 nginx:

systemctl stop nginx

啟動 nginx:

systemctl start nginx

檢查 gzip 是否生效

瀏覽器檔案請求的請求頭包含欄位 Accept-Encoding: gzip 代表瀏覽器支援 gzip 壓縮檔案,檔案響應頭包含欄位 Content-Encoding: gzip 代表返回的是壓縮檔案。

上面 nginx 配置 gzip_static on; 當我們不在 nginx 開啟 gzip_static 的時候,發現生產的 gz 檔案並沒有被執行。gzip_static 是會自動執行 gz 檔案的,這樣的就避免了透過 gzip 自動壓縮。換句話說,開啟之後 nginx 會優先使用我們的 gz 檔案。

如果 nginx 使用了已有的 gz 檔案,那麼這個請求的 etag 值不帶有 W/,反之,如果檔案是 nginx 壓縮的,etag 值則會帶有 W/。我們以剛才羅列的 app.aea87398.js 為例,下面是 Response Headers :

Cache-Control: max-age=2592000
Cache-Control: public
Content-Encoding: gzip
Content-Length: 455941
Content-Type: application/javascript
Date: Thu, 06 Aug 2020 03:17:24 GMT
Etag: "5f2b6d5e-6f505"
Expires: Sat, 05 Sep 2020 03:17:24 GMT
Last-Modified: Thu, 06 Aug 2020 02:39:26 GMT
Server: nginx/1.9.9
Vary: Accept-Encoding

會發現 Etag 的值為"5f2b6d5e-6f505" ,該值不以 W/ 開頭,則意味著使用了我們自己打包生成的 gz 檔案。

webpack效能最佳化

體積分析

經過webpack 打包後的體積最佳化是一個很重要的點,比如引入的第三方庫是否過大,能否對體積過大的庫進行最佳化。此時需要用到一款外掛,叫做webpack-bundle-analyzer 。它可以用互動式可縮放樹形圖顯示webpack輸出檔案的大小,用起來非常的方便。
首先安裝外掛:
npm install --save-dev webpack-bundle-analyzer
然後在vue.config.js 中引入:

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

module.exports = {
  plugins: [new BundleAnalyzerPlugin()],
};

然後npm run serve 啟動專案,此時會預設啟動 http://127.0.0.1:8888,頁面裡可以清晰的檢視包占比的大小。建議只在開發環境啟用,生產環境採用預設配置會打包失敗。以tieshangongzhu 專案為例,打包結果中佔比比較大的第三方庫包括:iview.js 、moment.js 、lodash.js 等。下面介紹如何最佳化這些大的資源。

體積最佳化

這裡介紹支援按需引入的babel 外掛babel-plugin-import ,用來最佳化lodash 。

首先安裝外掛:
npm install babel-plugin-import --save-dev
然後在babel.config.js 中的plugins 陣列中新增一下配置:
["import", { libraryName: "lodash", libraryDirectory: "" }];
透過上述配置就完成了lodash 的按需載入。
接著我們來最佳化moment ,透過分析頁面檢視可知,moment 很大部分佔比是語言包,但我們基本用不到,於是我們可以藉助webpack自帶的外掛來忽略語言包。配置如下:
plugins: [new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)];
透過上述配置即可去除語言包,減少大概 70%的大小。

多程序構建

大家都知道 webpack 是執行在 node 環境中,而 node 是單執行緒的。webpack 的打包過程是 io 密集和計算密集型的操作,如果能同時 fork 多個程序並行處理各個任務,將會有效的縮短構建時間。
這裡採用thread-loader 進行多程序構建。
首先安裝loader :
npm install --save-dev thread-loader
然後在vue.config.js 新增如下配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ["thread-loader", "babel-loader"],
      },
    ],
  },
};

然後執行npm run build ,統計打包時長。透過對比會發現,引入前打包耗時 37s,引入後打包耗時 18s。速度有了一倍的提升。透過檢視可選項配置:

options: {
      // the number of spawned workers, defaults to (number of cpus - 1) or
      // fallback to 1 when require('os').cpus() is undefined
      workers: 2,

      // number of jobs a worker processes in parallel
      // defaults to 20
      workerParallelJobs: 50,

      // additional node.js arguments
      workerNodeArgs: ['--max-old-space-size=1024'],

      // Allow to respawn a dead worker pool
      // respawning slows down the entire compilation
      // and should be set to false for development
      poolRespawn: false,

      // timeout for killing the worker processes when idle
      // defaults to 500 (ms)
      // can be set to Infinity for watching builds to keep workers alive
      poolTimeout: 2000,

      // number of jobs the poll distributes to the workers
      // defaults to 200
      // decrease of less efficient but more fair distribution
      poolParallelJobs: 50,

      // name of the pool
      // can be used to create different pools with elsewise identical options
      name: "my-pool"
    }

可以看到,預設是啟用cpus - 1 個worker 來實現多程序打包。

多程序並行壓縮程式碼

上面我們提到了多程序打包,接下來應用一下可以並行壓縮JavaScript 程式碼的外掛。

webpack預設提供了UglifyJS外掛來壓縮JS程式碼,但是它使用的是單執行緒壓縮程式碼,也就是說多個js檔案需要被壓縮,它需要一個個檔案進行壓縮。所以說在正式環境打包壓縮程式碼速度非常慢(因為壓縮JS程式碼需要先把程式碼解析成用Object抽象表示的AST語法樹,再應用各種規則分析和處理AST,導致這個過程耗時非常大)。

這裡介紹一款可以並行壓縮程式碼的外掛:terser-webpack-plugin 。

首先安裝外掛:
npm install terser-webpack-plugin --save-dev
然後在vue.config.js 中新增配置,注意該配置位於configureWebpack 下,並且建議在生產環境開啟。

optimization: {
    minimize: true,
    minimizer: [
        new TerserPlugin({
            parallel: true,
            terserOptions: {
                output: {
                    comments: false, // remove comments
                },
                compress: {
                    warnings: false,
                    drop_console: true,
                    drop_debugger: true,
                    pure_funcs: ['console.log'], // remove console.log
                },
            },
            extractComments: false,
        }),
    ],
},

按照上述配置後,即可開啟並行壓縮 js 程式碼的功能。需要注意的是,V8 在系統上有記憶體的限制,預設情況下,32 位系統限制為 512M,64 位系統限制為 1024M。因為如果不加以限制,大型專案構建的時候可能會出現記憶體溢位的情況。也就是說,可能無法開啟並行壓縮的功能。但是壓縮程式碼的功能還是可以正常使用的。

利用快取提升二次構建速度

這裡討論下在webpack中如何利用快取來提升二次構建速度。

在webpack中利用快取一般有以下幾種思路:

  • babel-loader開啟快取
  • 使用cache-loader
  • 使用hard-source-webpack-plugin
    這裡重點介紹下第三種。HardSourceWebpackPlugin 為模組提供了中間快取,快取預設的存放路是: node_modules/.cache/hard-source。

配置 hard-source-webpack-plugin後,首次構建時間並不會有太大的變化,但是從第二次開始,構建時間大約可以減少 80%左右。

首先安裝外掛:

npm install --save-dev hard-source-webpack-plugin

然後在vue.config.js 中新增配置,建議配置在開發環境,生產環境可能沒有效果。

module.exports = {
  plugins: [new HardSourceWebpackPlugin()],
};

在第二次執行npm run serve 後,可以看到終端會有以下字樣:

[hardsource:5e5f2c56] Using 144 MB of disk space.
[hardsource:5e5f2c56] Tracking node dependencies with: package-lock.json.
[hardsource:5e5f2c56] Reading from cache 5e5f2c56...

也就意味著構建從硬碟中讀取快取,加快了構建速度。

縮小構建目標

主要是exclude 與 include的使用:

  • exclude: 不需要被解析的模組
  • include: 需要被解析的模組
    用的比較多的是排除/node_modules/ 模組。需要注意的是,exclude 權重更高,exclude 會覆蓋 include 裡的配置。

減少檔案搜尋範圍

這個主要是resolve相關的配置,用來設定模組如何被解析。透過resolve的配置,可以幫助Webpack快速查詢依賴,也可以替換對應的依賴。

  • resolve.modules:告訴 webpack 解析模組時應該搜尋的目錄
  • resolve.mainFields:當從 npm 包中匯入模組時(例如,import * as React from 'react'),此選項將決定在 package.json 中使用哪個欄位匯入模組。根據 webpack 配置中指定的 target 不同,預設值也會有所不同
  • resolve.mainFiles:解析目錄時要使用的檔名,預設是index
  • resolve.extensions:副檔名
resolve: {
    alias: {
      react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js')
    }, //直接指定react搜尋模組,不設定預設會一層層的搜尋
    modules: [path.resolve(__dirname, 'node_modules')], //限定模組路徑
    extensions: ['.js'], //限定副檔名
    mainFields: ['main'] //限定模組入口檔名
}

預編譯資源模組

在使用webpack進行打包時候,對於依賴的第三方庫,比如vue,vuex等這些不會修改的依賴,我們可以讓它和我們自己編寫的程式碼分開打包,這樣做的好處是每次更改原生代碼的檔案的時候,webpack只需要打包專案本身的檔案程式碼,而不會再去編譯第三方庫。

那麼第三方庫在第一次打包的時候只打包一次,以後只要我們不升級第三方包的時候,那麼webpack就不會對這些庫去打包,這樣的可以快速的提高打包的速度。其實也就是預編譯資源模組。

webpack中,我們可以結合DllPlugin 和 DllReferencePlugin外掛來實現。

DllPlugin 是什麼

DLLPlugin 外掛是在一個額外獨立的webpack設定中建立一個只有dll的bundle,也就是說我們在專案根目錄下除了有vue.config.js,還會新建一個webpack.dll.config.js檔案。

webpack.dll.config.js的作用是把所有的第三方庫依賴打包到一個bundle的dll檔案裡面,還會生成一個名為 manifest.json檔案。該manifest.json的作用是用來讓 DllReferencePlugin 對映到相關的依賴上去的。

DllReferencePlugin 又是什麼

這個外掛是在vue.config.js中使用的,該外掛的作用是把剛剛在webpack.dll.config.js中打包生成的dll檔案引用到需要的預編譯的依賴上來。

什麼意思呢?就是說在webpack.dll.config.js中打包後比如會生成 vendor.dll.js檔案和vendor-manifest.json檔案,vendor.dll.js檔案包含了所有的第三方庫檔案,vendor-manifest.json檔案會包含所有庫程式碼的一個索引,當在使用vue.config.js檔案打包DllReferencePlugin外掛的時候,會使用該DllReferencePlugin外掛讀取vendor-manifest.json檔案,看看是否有該第三方庫。

vendor-manifest.json檔案就是一個第三方庫的對映而已。

話不多說,接下來看看怎麼應用到專案中。

首先我們來編寫webpack.dll.config.js 檔案,內容如下:

const webpack = require("webpack");
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "production",
  entry: {
    vendor: [
      "vue/dist/vue.runtime.esm.js",
      "vuex",
      "vue-router",
      "vue-resource",
      "iview",
    ], // 這裡是vue專案依賴的庫
    util: ["lodash", "jquery", "moment"], // 這裡是與框架無關的第三方庫
  },
  output: {
    filename: "[name].dll.js",
    path: path.resolve(__dirname, "dll"),
    library: "dll_[name]",
  },
  plugins: [
    new CleanWebpackPlugin(), // clean-webpack-plugin目前已經更新到2.0.0,不需要傳引數path
    new webpack.DllPlugin({
      name: "dll_[name]",
      path: path.join(__dirname, "dll", "[name].manifest.json"),
      context: __dirname,
    }),
  ],
};

在專案根目錄上新建webpack.dll.config.js ,填寫以上內容,同時還需要安裝CleanWebpackPlugin ,步驟省略。

然後我們需要執行命令將第三方庫打包到dll 資料夾下,該資料夾位於專案根目錄。

執行命令如下:
webpack --config ./webpack.dll.config.js
執行上述命令時如果提示Do you want to install 'webpack-cli' (yes/no) ,輸入yes 進行安裝webpack-cli 。成功後會發現專案根目錄生成dll 資料夾。資料夾下包含:
-util.dll.js - util.manifest.json - vendor.dll.js - vendor.manifest.json;
為了生成dll 資料夾方便,在package.json裡面再新增一條指令碼:

"scripts": {
    "build:dll": "webpack --config ./webpack.dll.config.js",
},

以後就可以執行npm run build:dll來生成 了。

接下來需要在vue.config.js 中新增以下程式碼:

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); // 如果未安裝請先安裝


const dllReference = (config) => {
    config.plugin('vendorDll')
        .use(webpack.DllReferencePlugin, [{
            context: __dirname,
            manifest: require('./dll/vendor.manifest.json'),
        }]);


    config.plugin('utilDll')
        .use(webpack.DllReferencePlugin, [{
            context: __dirname,
            manifest: require('./dll/util.manifest.json'),
        }]);


    config.plugin('addAssetHtml')
        .use(AddAssetHtmlPlugin, [ // add-asset-html-webpack-plugin外掛必須在html-webpack-plugin之後使用,因此這裡要用webpack-chain來進行配置
            [
                {
                    filepath: require.resolve(path.resolve(__dirname, 'dll/vendor.dll.js')),
                    outputPath: 'dll',
                    publicPath: '/dll', // 這裡的公共路徑與專案配置有關,如果頂層publicPath下有值,請新增到dll字首
                },
                {
                    filepath: require.resolve(path.resolve(__dirname, 'dll/util.dll.js')),
                    outputPath: 'dll',
                    publicPath: '/dll', // 這裡的公共路徑與專案配置有關,如果頂層publicPath下有值,請新增到dll字首
                },
            ],
        ])
        .after('html'); // 'html'代表html-webpack-plugin,是因為@vue/cli-servide/lib/config/app.js裡是用plugin('html')來對映的
};


module.exports = {
    publicPath: '/', // 頂層publiePath在這裡
    chainWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') { // 在開發環境中不使用dllPlugin是因為chrome的vue devtool是不能檢測壓縮後的vue原始碼,不方便程式碼除錯
            dllReference(config);
        }
    }

Vue專案引入tailwind.css

安裝

npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

由於專案中依賴的元件庫的postCSS版本是7.X,使用npm install tailwindcss安裝的版本依賴postCSS的8.X,因此需要安裝指定版本。

配置

npx tailwindcss init -p

透過執行以上命令來生成tailwind.config.js和postcss.config.js,如果專案中存在postcss.config.js,則只生成前者,不會覆蓋已有配置檔案。
tailwind.config.js位於專案的根目錄,預設配置如下:

// tailwind.config.js
module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

修改postcss.config.js配置:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

引入

在專案的公共CSS檔案中新增如下配置,Tailwind將在構建時將這些指令替換為基於配置的設計系統生成的所有樣式:

/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;

請務必新增詭異的註釋/*! @import */,避免在開發過程中Chrome DevTools中的效能問題。 最後在./src/main.js中引入css檔案即可。

自定義

一般來說,開發過程中使用最多的無外乎padding 、margin、width、height等屬性,這些屬性有一個共性就是需要指定具體的值。tailwind.config.js可以在theme下指定特殊的值來覆蓋預設值。

// tailwind.config.js
module.exports = {
  purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {
      borderWidth: {
        default: '1px',
        '0': '0',
        '2': '2px',
        '4': '4px',
      },
      spacing: {
        '5': '5px',
        '10': '10px',
        '15': '15px',
        '20': '20px',
        '25': '25px',
        '30': '30px',
      },
      colors: {
        'gray-border': "#e8e8e8",
        'gray-disabled': "#c5c8ce",
        'gray-background': "#f8f8f9",
        'blue-text': "rgba(25,118,253,.85)",
        'blue-primary': "#3B91FF",
        'blue-info': "#2db7f5",
        'green-success': "#19be6b",
        'yellow-warning': "#ff9900",
        'red-error': "#ed4014",
        'black-title': '#17233d',
        'black-content': '#515a6e'
      },
    },
  },
  plugins: [],
  prefix: 'tw-',
  important: false, // Tailwind with existing CSS that has high specificity selectors
}

以上配置是目前專案中暫時用到的自定義值。

優勢

  • 不用想破腦袋的去給class命名,不用給諸如只需要宣告flex樣式來增加一個class。
  • css檔案不再增長。使用tailwind,所有內容都是可重用的,因此幾乎不需要編寫新的CSS。
  • 更安全的變更,css是全域性性的,容易引發衝突;而class是區域性的,不需要擔心會產生衝突。

與內聯樣式相比,有以下優點:

  • 有約束的設計。使用內聯樣式,每個值都是一個魔術數字。使用Tailwind,可以從預定義的設計系統中選擇樣式,這將使構建外觀一致的UI變得更加容易。
  • 響應式設計。不能以內聯樣式使用媒體查詢,但是可以使用Tailwind的響應實用程式輕鬆構建完全響應的介面。
  • 偽類。內聯樣式無法定位諸如懸停或焦點之類的狀態,但是Tailwind的偽類變體使使用實用程式類對這些狀態進行樣式設定變得容易。

vue.config.js程式碼

/**
 * 配置參考: https://cli.vuejs.org/zh/config/
 */
const Timestamp = new Date().getTime()
var path = require("path");
// var webpack = require("webpack");
// 壓縮外掛
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 自定義配置外掛
const TerserPlugin = require('terser-webpack-plugin')
//獲取絕對地址
function resolve(dir) {
  return path.join(__dirname, dir);
}
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  runtimeCompiler: true,
  lintOnSave: false,
  productionSourceMap: false,
  publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
  chainWebpack: (config) => {
    // // ============壓縮圖片 start============
    // if (process.env.NODE_ENV === 'production') {
    //   config.module
    //     .rule('images')
    //     .use('image-webpack-loader')
    //     .loader('image-webpack-loader')
    //     .options({ bypassOnDebug: true })
    //     .end()
    //   // ============壓縮圖片 end============
    //   config.plugins.delete('prefetch') // vue-cli3.0   加上這行才能按需載入  移除 prefetch 外掛
    //   // 移除 preload 外掛
    //   config.plugins.delete('preload')
    //   // 壓縮程式碼
    //   config.optimization.minimize(true)
    //   // 分割程式碼
    //   config.optimization.splitChunks({
    //     chunks: 'initial', // async非同步程式碼分割 initial同步程式碼分割 all同步非同步分割都開啟
    //     minSize: 30000, // 位元組 引入的檔案大於30kb才進行分割
    //     // maxSize: 50000,         //50kb,嘗試將大於50kb的檔案拆分成n個50kb的檔案
    //     minChunks: 1, // 模組至少使用次數
    //     maxAsyncRequests: 5, // 同時載入的模組數量最多是5個,只分割出同時引入的前5個檔案
    //     maxInitialRequests: 3, // 首頁載入的時候引入的檔案最多3個
    //     automaticNameDelimiter: '~', // 快取組和生成檔名稱之間的連線符
    //     name: true, // 快取組裡面的filename生效,覆蓋預設命名
    //     cacheGroups: { // 快取組,將所有載入模組放在快取裡面一起分割打包
    //       vendors: { // 自定義打包模組
    //         test: /[\\/]node_modules[\\/]/,
    //         priority: -10, // 優先順序,先打包到哪個組裡面,值越大,優先順序越高
    //         filename: 'vendors.js'
    //       },
    //       default: { // 預設打包模組
    //         priority: -20,
    //         reuseExistingChunk: true, // 模組巢狀引入時,判斷是否複用已經被打包的模組
    //         filename: 'common.js'
    //       }
    //     }
    //   })
    // }

    config.module.rule("svg").exclude.add(resolve("src/icons")).end();
    config.module.rule("thread")
      .test(/\.js$/)
      .exclude.add(resolve("/node_modules/"))
      .end()
      .use("thread-loader")
      .loader("thread-loader")
      .end()
      .use("babel-loader")
      .loader("babel-loader")
      .end();
    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "[name]",
      })
      .end();
    config.module
      .rule("js")
      .include.add(/src/)
      .add(/test/)
      .add(/node_modules\/npm-lifecycle\/node-gyp-bin/)
      .add(/node_modules\/@wangeditor/)
      .end();
  },
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      // webpack 配置
      config.plugins.push(
        new CompressionWebpackPlugin({
          test: /\.js$|\.html$|\.map$|\.css$/,
          // 超過4kb壓縮
          threshold: 4096,
          deleteOriginalAssets: true, // 刪除原始檔案
        })
      )
      config.plugins.push(
        new TerserPlugin({
          terserOptions: {
            // 自動刪除console
            compress: {
              // warnings: false, // 若打包錯誤,則註釋這行
              drop_debugger: true,
              drop_console: true,
              pure_funcs: ['console.log']
            }
          },
          cache: true,
          sourceMap: false,
          parallel: true
        })
      )
      config.output.filename = `[name].${Timestamp}.js`
      config.output.chunkFilename = `[name].${Timestamp}.js`
    } else {
      config.devtool = 'source-map'
    }
  },
  css: {
    loaderOptions: {
      // 沒有分號會報錯
      sass: {
        sourceMap: true,
        additionalData: `
          @import "@/assets/scss/variables.scss";
        `,
      },
    },
  },
  productionSourceMap: false,
  devServer: {
    port: 8002,
    proxy: {
      "/api": {
        target: "http://192.168.5.23:8408/api",
        ws: true,
        changeOrigin: true,
        pathRewrite: {
          "^/api": "",
        },
      },
    },
  },
});

相關文章