webpack项目开发完后,如何优化打包速度?

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": "",
        },
      },
    },
  },
});
posted @ 2024-06-20 17:04  ajajaz  阅读(132)  评论(0编辑  收藏  举报