前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Webpack5创建Vue2项目及优化

使用Webpack5创建Vue2项目及优化

作者头像
码客说
发布2022-09-27 15:51:30
2.5K0
发布2022-09-27 15:51:30
举报
文章被收录于专栏:码客码客

前言

之前我们大多都是用Vue-Cli来创建项目,但是Vue-Cli已经停止更新了,并且Vue-Cli相当于一堆插件的集合体,我们想替换以下,或者想根据我们的项目优化以下,提升编译的性能,这时候可以自己用Webpack来配置项目。

在搭建的时候最头疼的是两个问题

  • 依赖下载不下来
  • 依赖之间不兼容

安装cnpm 可以解决依赖无法下载的问题

代码语言:javascript
复制
npm install -g cnpm --registry=https://registry.npm.taobao.org

配置步骤

基本配置

创建项目文件夹 webpack01

进入项目文件夹根目录,运行

代码语言:javascript
复制
npm init

安装基础依赖

代码语言:javascript
复制
npm i -D webpack@5.74.0 webpack-cli@4.10.0 webpack-dev-server@4.10.0
npm i -D html-webpack-plugin@5.5.0
npm i vue@2.6.11
npm i -D vue-loader@15.10.0 vue-template-compiler@2.6.11

注意

vue-template-compiler要和vue的版本一致 html-webpack-plugin@5.x才支持webpack@5.x

创建以下文件夹及文件

image-20220826175336877
image-20220826175336877

/public/index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>标题</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

/src/App.vue

代码语言:javascript
复制
<template>
  <div>Hello</div>
</template>

<script>
export default {
  name: "App"
}
</script>

<style scoped>

</style>

/src/main.js

代码语言:javascript
复制
import App from './App';
import Vue from "vue";
new Vue({
  render: h => h(App)
}).$mount("#app");

/webpack.config.js

代码语言:javascript
复制
const path = require('path');
const {VueLoaderPlugin} = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  watch: true,
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({template: './public/index.html'}), //JS或者CSS文件可以自动引入到html中
  ],
  resolve: {
    extensions: ['.js', '.css', '.vue'],  //配置后缀名
  },
  devServer: {
    port: 8080,
    hot: true,
    open: true,
    static: {
      directory: path.join(__dirname, './'),
      watch: true
    }
  }
}

pacakge.json 中添加 scripts 配置

代码语言:javascript
复制
{
  "name": "webpack01",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack --mode production"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "~5.5.0",
    "vue-loader": "~15.10.0",
    "vue-template-compiler": "~2.6.11",
    "webpack": "~5.74.0",
    "webpack-cli": "~4.10.0",
    "webpack-dev-server": "~4.10.0"
  },
  "dependencies": {
    "vue": "~2.6.11"
  }
}

这时候就能运行了

代码语言:javascript
复制
npm run start

打包

代码语言:javascript
复制
npm run build

查看webpack的版本

代码语言:javascript
复制
npx webpack --version

Vue Loader简介

https://vue-loader.vuejs.org/zh/guide/#vue-cli

Vue Loader 的配置和其它的 loader 不太一样。

除了通过一条规则将 vue-loader 应用到所有扩展名为 .vue 的文件上之外,请确保在你的 webpack 配置中添加 Vue Loader 的插件:

代码语言:javascript
复制
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]
}

这个插件是必须的!

它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。

例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。

处理HTML

HTML中根据变量取值

代码语言:javascript
复制
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const IS_PRODUCTION = process.env.NODE_ENV === "production";

// 生产配置
const cdn_production = {
  js: ["/librarys/vue@2.6.11/vue.min.js"]
};
// 开发配置
const cdn_development = {
  js: ["/librarys/vue@2.6.11/vue.js"]
};

module.exports = {

  externals: {
    BMap: "BMap",
    vue: "Vue",
    "vue-router": "VueRouter",
    vuex: "Vuex",
    echarts: "echarts",
    axios: "axios",
    "view-design": "iview",
    mathjs: "math",
    xlsx: "XLSX2",
    "xlsx-style": "XLSX",
    "crypto-js": "CryptoJS",
    "v-viewer": "VueViewer",
    AgoraRTC_N: "AgoraRTC",
    html2canvas: "html2canvas"
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      cdn: IS_PRODUCTION ? cdn_production : cdn_development
    }), //JS或者CSS文件可以自动引入到html中
  ],
}

HTML中取值

代码语言:javascript
复制
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
  <script src="/librarys/axios@0.21.1/axios.min.js"></script>
  <script src="/librarys/vue-router@3.2.0/vue-router.min.js"></script>
  <script src="/librarys/vuex@3.2.0/vuex.min.js"></script>

处理JS

安装babel

添加依赖

代码语言:javascript
复制
npm i -D babel-loader@8.2.5 @babel/core@7.18.13
npm i -D @babel/preset-env@7.18.10 @babel/polyfill@7.12.1
npm i -D @babel/plugin-transform-runtime@7.18.10
npm i -S @babel/runtime@7.18.9 @babel/runtime-corejs2@7.18.9

@babel/plugin-transform-runtime有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。

现在我们除了安装@babel/runtime包提供辅助函数模块,还要安装Babel插件@babel/plugin-transform-runtime来自动替换辅助函数。

作用

babel-loader:只是和webpack之间的桥梁,并不会把es6语法进行转换。 @babel/preset-env @babel/polyfill是做转换的。 以上babel的配置是官网提供主要用来解决业务代码js语法转译用的,当要生成类库或者组件库时上面这种配置会污染全局变量,需要使用@babel/plugin-transform-runtime

在根目录下创建 babel 配置文件 .babelrc:

代码语言:javascript
复制
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
}

注意:"corejs": 2, // 这里设置2是因为上面安装的版本是 @babel/runtime-corejs2

配置webpack.config.js设置使用babel的规则

代码语言:javascript
复制
module.exports = {
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
    ]
  }
}

缓存

代码语言:javascript
复制
{
  test: /\.js$/i,
  include: resolve('src'),
  exclude: /node_modules/,
  use: [
    {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true // 启用缓存
      }
    },
  ]
},

处理CSS

其中less和sass任选其一即可。

处理css文件

添加依赖

代码语言:javascript
复制
npm i -D style-loader@3.3.1 css-loader@6.7.1

在webpack.config.js这个配置文件设置匹配css文件处理的插件

代码语言:javascript
复制
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },

处理less

添加依赖

代码语言:javascript
复制
npm i less-loader@11.0.0 less@4.1.3 -D

在webpack.config.js配置文件设置匹配less文件的处理

代码语言:javascript
复制
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },

处理sass

安装sass-loader node-sass工具来处理sass文件

代码语言:javascript
复制
npm i sass-loader node-sass -D
npm i sass fiber -D

在webpack.config.js配置文件设置匹配scss文件的处理

代码语言:javascript
复制
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },

处理URL

图片

安装url-loader

代码语言:javascript
复制
npm i url-loader@4.1.1 file-loader@6.2.0 -D

在webpack.config.js中添加处理url路径的loader模块:

代码语言:javascript
复制
{test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?esModule=false&limit=500&name=imgs/[hash:8]-[name].[ext]'},

上面这种输入参数的方式还有另一种方式,以对象的键值对方式,如下:

代码语言:javascript
复制
{
  test: /\.(jpg|png|gif|bmp|jpeg|jfif)$/,
  use: [{
    loader: 'url-loader',
    options: {
      esModule: false,
      limit: 500,   //是把小于500B的文件打成Base64的格式,写入JS
      name: 'imgs/[hash:8]-[name].[ext]' // [hash:8] 在名称前面设置8位哈希值,[name] 设置文件的原名, [ext] 设置文件的原后缀
    }
  }]
},// 处理 图片路径的 loader

对比

file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理图片文件,可以使用相对路径而不用担心部署时 URL 的问题。使用正确的配置,webpack 将会在打包输出中自动重写文件路径为正确的 URL。 url-loader 允许你有条件地将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求数。如果文件大于该阈值,会自动的交给 file-loader 处理。

字体

不要把字体也用url-loader 来处理,把字体文件转成base64是浏览器无法识别的

代码语言:javascript
复制
{
  test: /\.(woff|woff2|eot|ttf|otf)$/i,
  loader: 'file-loader',
  options: {
    esModule: false
  }
}

音频

代码语言:javascript
复制
{
  test: /\.(mp3)(\?.*)?$/,
  loader: 'url-loader',
  options: {
    name:'audios/[name].[ext]',
    limit:10
  }
}

静态文件处理

https://www.webpackjs.com/plugins/copy-webpack-plugin/#install

https://github.com/webpack-contrib/copy-webpack-plugin/tree/v9.1.0

代码语言:javascript
复制
npm install copy-webpack-plugin@9 -D

配置

代码语言:javascript
复制
//webpack.config.js
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const mCopyWebpackPlugin =  new CopyWebpackPlugin({
  patterns: [
    {
      from: "public",
      to: "./",
      toType: "dir",
      globOptions: {
        ignore: [
          "**/index.html",
        ],
      },
    },
  ],
});

module.exports = {
  plugins:[
    mCopyWebpackPlugin,
  ]
}

注意:

版本不同,配置也不一样。 to配置的相对路径是相对于发布目录的。 如果from所在目录中排除文件后没有文件的时候会报错。

我的配置

package.json

代码语言:javascript
复制
{
  "name": "webpack01",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack --mode production"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "~7.18.13",
    "@babel/plugin-transform-runtime": "~7.18.10",
    "@babel/polyfill": "~7.12.1",
    "@babel/preset-env": "~7.18.10",
    "babel-loader": "~8.2.5",
    "cache-loader": "^4.1.0",
    "copy-webpack-plugin": "~9.1.0",
    "css-loader": "~6.7.1",
    "file-loader": "~6.2.0",
    "html-webpack-plugin": "~5.5.0",
    "less": "~4.1.3",
    "less-loader": "~11.0.0",
    "style-loader": "~3.3.1",
    "thread-loader": "~3.0.4",
    "url-loader": "~4.1.1",
    "vue-loader": "~15.10.0",
    "vue-template-compiler": "~2.6.11",
    "webpack": "~5.74.0",
    "webpack-cli": "~4.10.0",
    "webpack-dev-server": "~4.10.0",
    "webpackbar": "~5.0.2"
  },
  "dependencies": {
    "@babel/runtime": "~7.18.9",
    "@babel/runtime-corejs2": "~7.18.9",
    "vue": "~2.6.11"
  }
}

webpack.config.js

代码语言:javascript
复制
const path = require('path');

function resolve(dir) {
  return path.join(__dirname, dir);
}

const IS_PRODUCTION = process.env.NODE_ENV === "production";
const {VueLoaderPlugin} = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 复制插件
const CopyWebpackPlugin = require('copy-webpack-plugin');
const mCopyWebpackPlugin = new CopyWebpackPlugin({
  patterns: [
    {
      from: "public",
      to: "./",
      toType: "dir",
      globOptions: {
        ignore: [
          "**/index.html",
        ],
      },
    },
  ],
});

// 生产配置
const cdn_production = {
  js: ["/librarys/vue@2.6.11/vue.min.js"]
};
// 开发配置
const cdn_development = {
  js: ["/librarys/vue@2.6.11/vue.js"]
};

// 进度条
const WebpackBar = require('webpackbar');
let progressPlugin = new WebpackBar({
  color: "#85d",  // 默认green,进度条颜色支持HEX
  basic: false,   // 默认true,启用一个简单的日志报告器
  profile: false,  // 默认false,启用探查器。
})

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  externals: {
    "vue": "Vue",
    "vue-router": "VueRouter",
    "vuex": "Vuex",
    "axios": "axios",
  },
  watch: true,
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/, exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader', // 开启多进程打包
            options: {
              worker: 3,
            }
          },
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true // 启用缓存
            }
          },
        ]
      },
      {test: /\.css$/, use: ['style-loader', 'css-loader']},
      {test: /\.less$/, use: ['style-loader', 'cache-loader', 'css-loader', 'less-loader']},
      {
        test: /\.(jpg|png|gif|bmp|jpeg|svg)$/,
        use: 'url-loader?esModule=false&limit=500&name=imgs/[hash:8]-[name].[ext]'
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        loader: 'file-loader',
        options: {
          esModule: false
        }
      },
      {
        test: /\.(mp3)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          name: 'audios/[name].[ext]',
          limit: 10
        }
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      cdn: IS_PRODUCTION ? cdn_production : cdn_development
    }),
    mCopyWebpackPlugin,
    progressPlugin,
  ],
  resolve: {
    extensions: ['.js', '.css', '.json', '.vue'],  //配置后缀名
    alias: {
      '~': resolve('src'),
      '@': resolve('src'),
      'components': resolve('src/components'),
    }
  },
  devServer: {
    port: 8080,
    hot: true,
    open: true,
    static: {
      directory: path.join(__dirname, './'),
      watch: true
    }
  }
}

.babelrc

代码语言:javascript
复制
{
  "presets": ["@babel/preset-env"],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": 2,
          "helpers": true,
          "regenerator": true,
          "useESModules": false
        }
      ]
    ]
}

/public/index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>标题</title>
</head>

<body>
  <div id="app"></div>
</body>
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<script src="/librarys/vue-router@3.2.0/vue-router.min.js"></script>
<script src="/librarys/vuex@3.2.0/vuex.min.js"></script>
<script src="/librarys/axios@0.21.1/axios.min.js"></script>
<style>
  body{
    margin: 0;
    padding: 0;
  }
</style>
</html>

/src/App.vue

代码语言:javascript
复制
<template>
  <div class="app">
    <div class="div1">Hello</div>
    <div class="div2">Word</div>
    <img src="/imgs/qrcode.png" alt="">
    <img src="@/assets/imgs/test.png" alt="">
  </div>
</template>

<script>
export default {
  name: "App"
}
</script>

<style lang="less" scoped>

.app{
  background: #f3f3f3;
  width: 100vw;
  height: 100vh;
  font-size: 60px;

  .div1{
    font-size: 60px;
  }

  .div2{
    font-size: 80px;
  }
}
</style>

/src/main.js

代码语言:javascript
复制
import App from './App';
import Vue from "vue";
new Vue({
  render: h => h(App)
}).$mount("#app");

优化

优化构建速度

耗时分析

首先安装一下

代码语言:javascript
复制
npm i -D speed-measure-webpack-plugin

修改我们的配置文件 webpack.config.js

代码语言:javascript
复制
// 费时分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
module.exports = {
  plugins: [
    new SpeedMeasurePlugin(),
  ],
}

范围优化

resolve

1、alias

alias 用的创建 importrequire 的别名,用来简化模块引用,项目中基本都需要进行配置。

代码语言:javascript
复制
const path = require('path')
// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  resolve:{
    // 配置别名
    alias: {
      '~': resolve('src'),
      '@': resolve('src'),
      'components': resolve('src/components'),
    }
  }
};

配置完成之后,我们在项目中就可以

代码语言:javascript
复制
// 使用 src 别名 ~ 
import '~/fonts/iconfont.css'

// 使用 src 别名 @ 
import '@/fonts/iconfont.css'

// 使用 components 别名
import footer from "components/footer";

2、extensions

webpack 默认配置

代码语言:javascript
复制
module.exports = {
  resolve: {
    extensions: ['.js', '.css', '.json', '.vue'],  //配置后缀名
  },
};

如果用户引入模块时不带扩展名,例如

代码语言:javascript
复制
import file from '../path/to/file';

那么 webpack 就会按照 extensions 配置的数组从左到右的顺序去尝试解析模块

需要注意的是:

  1. 高频文件后缀名放前面;
  2. 手动配置后,默认配置会被覆盖

如果想保留默认配置,可以用 ... 扩展运算符代表默认配置,例如

代码语言:javascript
复制
module.exports = {
  //...
  resolve: {
    extensions: ['.ts', '...'], 
  },
};

3、modules

告诉 webpack 解析模块时应该搜索的目录,常见配置如下

代码语言:javascript
复制
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  //...
  resolve: {
     modules: [resolve('src'), 'node_modules'],
  },
};

告诉 webpack 优先 src 目录下查找需要解析的文件,会大大节省查找时间。

4、resolveLoader

resolveLoader 与上面的 resolve 对象的属性集合相同, 但仅用于解析 webpack 的 loader 包。

一般情况下保持默认配置就可以了,但如果你有自定义的 Loader 就需要配置一下,不配可能会因为找不到 loader 报错。例如:我们在 loader 文件夹下面,放着我们自己写的 loader。我们就可以怎么配置

代码语言:javascript
复制
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  //...
  resolveLoader: {
    modules: ['node_modules',resolve('loader')]
  },
};
externals

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。此功能通常对 library 开发人员来说是最有用的,然而也会有各种各样的应用程序用到它。例如,从 CDN 引入 jQuery,而不是把它打包:

1、引入链接

代码语言:javascript
复制
<script
        src="https://code.jquery.com/jquery-3.1.0.js"
        integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
        crossorigin="anonymous">
</script>

2、配置 externals

代码语言:javascript
复制
module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

3、使用 jQuery

代码语言:javascript
复制
import $ from 'jquery';

$('.my-element').animate(/* ... */);

我们可以用这样的方法来剥离不需要改动的一些依赖,大大节省打包构建的时间。

缩小范围

在配置 loader 的时候,我们需要更精确的去指定 loader 的作用目录或者需要排除的目录,通过使用 includeexclude 两个配置项,可以实现这个功能,常见的例如:

  • include:符合条件的模块进行解析
  • exclude:排除符合条件的模块,不解析
  • exclude 优先级更高

例如在配置 babel 的时候

代码语言:javascript
复制
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};
noParse
  • 不需要解析依赖的第三方大型类库等,可以通过这个字段进行配置,以提高构建速度
  • 使用 noParse 进行忽略的模块文件中不会解析 importrequire 等语法
代码语言:javascript
复制
module.exports = {
  //...
  module: { 
    noParse: /jquery|lodash/,
    rules:[]
  }
};
IgnorePlugin

防止在 importrequire 调用时,生成以下正则表达式匹配的模块:

  • requestRegExp 匹配(test)资源请求路径的正则表达式。
  • contextRegExp 匹配(test)资源上下文(目录)的正则表达式
代码语言:javascript
复制
new webpack.IgnorePlugin({ resourceRegExp, contextRegExp });

以下示例演示了此插件的用法。

1、安装 moment 插件(时间处理库)

代码语言:javascript
复制
npm i -S moment

2、配置 IgnorePlugin

代码语言:javascript
复制
// 引入 webpack
const webpack = require('webpack')

module.exports = {
  plugins:[ // 配置插件
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/,
    }),
  ]  
};

目的是将插件中的非中文语音排除掉,这样就可以大大节省打包的体积了

多进程

配置在 thread-loader 之后的 loader 都会在一个单独的 worker 池(worker pool)中运行

1、安装

代码语言:javascript
复制
npm i -D thread-loader@3.0.4

2、配置

代码语言:javascript
复制
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  //...
  module: { 
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'thread-loader', // 开启多进程打包
            options: {
              worker: 3,
            }
          },
          'babel-loader',
        ]
      },
      // ...
    ]
  }
};

缓存

利用缓存可以大幅提升重复构建的速度

JS缓存

babel-loader 开启缓存

  • babel 在转译 js 过程中时间开销比价大,将 babel-loader 的执行结果缓存起来,重新打包的时候,直接读取缓存
  • 缓存位置: node_modules/.cache/babel-loader

配置

代码语言:javascript
复制
const path = require('path');

// 路径处理方法
function resolve(dir){
  return path.join(__dirname, dir);
}

module.exports = {
  module: { 
    noParse: /jquery|lodash/,
    rules: [
      {
        test: /\.js$/i,
        include: resolve('src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true // 启用缓存
            }
          },
        ]
      },
    ]
  }
}
CSS缓存

cache-loader

  • 缓存一些性能开销比较大的 loader 的处理结果
  • 缓存位置:node_modules/.cache/cache-loader

1、安装

代码语言:javascript
复制
npm i -D cache-loader@4.1.0

2、配置 cache-loader

代码语言:javascript
复制
module.exports = {
 module: { 
    // ...
    rules: [
      {
        test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
        use: [
          'style-loader',
          'cache-loader', // 获取前面 loader 转换的结果
          'css-loader',
          'postcss-loader',
          'sass-loader', 
        ]
      }, 
    ]
  }
}

less

代码语言:javascript
复制
{test: /\.less$/, use: ['style-loader','cache-loader', 'css-loader', 'less-loader']},
其他

hard-source-webpack-plugin

hard-source-webpack-plugin 为模块提供了中间缓存,重复构建时间大约可以减少 80%,但是在 webpack5 中已经内置了模块缓存,不需要再使用此插件

持久化缓存

通过配置cache缓存生成的 webpack 模块和 chunk,来改善构建速度。

代码语言:javascript
复制
module.exports = {
  cache: {
    type: 'filesystem',
  },
};

优化构建结果

优化构建结果是为了让打包出来的文件尽可能小,这样势必会增加构建时间。

结果分析

借助插件webpack-bundle-analyzer我们可以直观的看到打包结果中,文件的体积大小、各模块依赖关系、文件是够重复等问题,极大的方便我们在进行项目优化的时候,进行问题诊断。

1、安装

代码语言:javascript
复制
npm i -D webpack-bundle-analyzer

2、配置插件

代码语言:javascript
复制
// 引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  // ...
  plugins:[ 
    // ...
    // 配置插件 
    new BundleAnalyzerPlugin({
      // analyzerMode: 'disabled',  // 不启动展示打包报告的http服务器
      // generateStatsFile: true, // 是否生成stats.json文件
    })
  ],
};

3、修改启动命令

代码语言:javascript
复制
"scripts": {
   "analyzer": "cross-env NODE_ENV=prod webpack --progress --mode production"
 },

4、执行编译命令 npm run analyzer

打包结束后,会自行启动地址为 http://127.0.0.1:8888 的 web 服务

如果,我们只想保留数据不想启动 web 服务,这个时候,我们可以加上两个配置

代码语言:javascript
复制
new BundleAnalyzerPlugin({
   analyzerMode: 'disabled',  // 不启动展示打包报告的http服务器
   generateStatsFile: true, // 是否生成stats.json文件
})

这样再次执行打包的时候就只会产生 state.json 的文件了

压缩 CSS

1、安装 optimize-css-assets-webpack-plugin

代码语言:javascript
复制
npm install -D optimize-css-assets-webpack-plugin

2、修改 webapck.config.js 配置

代码语言:javascript
复制
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      // 添加 css 压缩配置
      new OptimizeCssAssetsPlugin({}),
    ]
  },
}

压缩 JS

在生成环境下打包默认会开启 js 压缩,但是当我们手动配置optimization选项之后,就不再默认对 js 进行压缩,需要我们手动去配置。

因为 webpack5 内置了terser-webpack-plugin插件,所以我们不需重复安装,直接引用就可以了,具体配置如下

代码语言:javascript
复制
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...
  optimization: {
    minimize: true, // 开启最小化
    minimizer: [
      // ...
      new TerserPlugin({})
    ]
  },
  // ...
}

清除无用的 CSS

purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS

1、安装插件

代码语言:javascript
复制
$ npm i -D purgecss-webpack-plugin

2、添加配置

代码语言:javascript
复制
// ...
const PurgecssWebpackPlugin = require('purgecss-webpack-plugin')
const glob = require('glob'); // 文件匹配模式
// ...

function resolve(dir){
  return path.join(__dirname, dir);
}

const PATHS = {
  src: resolve('src')
}

module.exports = {
  plugins:[ // 配置插件
    // ...
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true})
    }),
  ]
}

3、index.html 新增节点

代码语言:javascript
复制
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ITEM</title>
</head>
<body>
  <p></p>
  <!-- 使用字体图标文件 -->
  <i class="iconfont icon-member"></i>
  <div id="imgBox"></div>
  
   <!-- 新增 div,设置 class 为 used -->
  <div class="used"></div>
</body>
</html>

4、在 sass.scss 中添加样式

代码语言:javascript
复制
.used {
  width: 200px;
  height: 200px;
  background: #ccc;
}

.unused {
  background: chocolate;
}

5、执行一下打包

我们可以看到只有 .used 被保存下来

如何证明是这个插件的作用呢?注释掉再打包就可以看到,.unused 也会被打包进去,由此可证…

Tree-shaking

Tree-shaking 作用是剔除没有使用的代码,以降低包的体积

了解更多 Tree-shaking 知识,推荐阅读 从过去到现在,聊聊 Tree-shaking

webpack5tree-shaking 中的配置

打开项目下 package.json, 加入配置 "sideEffects"

sideEffects 有三种情况

  1. sideEffects:true 所有文件都有副作用,全都不可 tree-shaking
  2. sideEffects:false 有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件
  3. sideEffects:[] 部分 tree-shaking , 除了数组外都 tree-shaking

所谓 副作用 指的是 在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。

举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

对于某些代码,可能没有被导出和使用,但是却不能删除。

因为仅仅是引入这个文件(比如import './index.less' ),或者执行了某个表达式(比如Array.prototype.slice = null),都会对结果造成影响,所以不能被轻易删除。

webpack认为这些代码是有“副作用(Side Effects)”的。

Scope Hoisting

Scope Hoisting 即作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销

  • webpack 默认支持,在生产环境下默认开启
  • 只支持 es6 代码

优化运行时体验

运行时优化的核心就是提升首屏的加载速度,主要的方式就是:降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载

splitChunks 分包配置

optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的。默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。

webpack 将根据以下条件自动拆分 chunks:

  • 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
  • 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
  • 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
  • 当加载初始化页面时,并发请求的最大数量小于或等于 30

1、默认配置介绍

代码语言:javascript
复制
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 有效值为 `all`,`async` 和 `initial`
      minSize: 20000, // 生成 chunk 的最小体积(≈ 20kb)
      minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块
      minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。
      maxAsyncRequests: 30, // 最大的按需(异步)加载次数
      maxInitialRequests: 30, // 打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件)
      enforceSizeThreshold: 50000,
      cacheGroups: { // 配置提取模块的方案
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

2、项目中的使用

代码语言:javascript
复制
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: { // 配置提取模块的方案
        default: false,
        styles: {
          name: 'styles',
          test: /\.(s?css|less|sass)$/,
          chunks: 'all',
          enforce: true,
          priority: 10,
        },
        common: {
          name: 'chunk-common',
          chunks: 'all',
          minChunks: 2,
          maxInitialRequests: 5,
          minSize: 0,
          priority: 1,
          enforce: true,
          reuseExistingChunk: true,
        },
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          priority: 2,
          enforce: true,
          reuseExistingChunk: true,
        },
        // ... 根据不同项目再细化拆分内容
      },
    },
  },
}

代码懒加载

针对首屏加载不太需要的一些资源,我们可以通过懒加载的方式去实现。

下面看一个小需求:点击图片给图片加一个描述

1、新建图片描述信息 desc.js

代码语言:javascript
复制
const ele = document.createElement('div')
ele.innerHTML = '我是图片描述'
module.exports = ele

2、点击图片引入描述 index.js

代码语言:javascript
复制
import './main.css';
import './sass.scss'
import logo from '../public/avatar.png'

import '@/fonts/iconfont.css'

const a = 'Hello ITEM'
console.log(a)

const img = new Image()
img.src = logo

document.getElementById('imgBox').appendChild(img)

// 按需加载
img.addEventListener('click', () => {
  import('./desc').then(({ default: element }) => {
    console.log(element)
    document.body.appendChild(element)
  })
})

prefetch 与 preload

上面我们使用异步加载的方式引入图片的描述,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取

prefetch

prefetch (预获取):浏览器空闲的时候进行资源的拉取

改造一下上面的代码

代码语言:javascript
复制
// 按需加载
img.addEventListener('click', () => {
  import( /* webpackPrefetch: true */ './desc').then(({ default: element }) => {
    console.log(element)
    document.body.appendChild(element)
  })
})

preload

  • preload (预加载):提前加载后面会用到的关键资源
  • 因为会提前拉取资源,如果不是特殊需要,谨慎使用

官网示例:

代码语言:javascript
复制
import(/* webpackPreload: true */ 'ChartingLibrary');

其他插件

构建进度条插件

代码语言:javascript
复制
npm i -D webpackbar@5.0.2

配置

代码语言:javascript
复制
const WebpackBar = require('webpackbar');
let progressPlugin = new WebpackBar({
  color: "#85d",  // 默认green,进度条颜色支持HEX
  basic: false,   // 默认true,启用一个简单的日志报告器
  profile:false,  // 默认false,启用探查器。
})
plugins.push(progressPlugin)

当然里面还有一个属性就是reporters还没有写上,可以在里面注册事件,也可以理解为各种钩子函数。

如下:

代码语言:javascript
复制
{ 
    start(context) {
      // 在(重新)编译开始时调用
      const { start, progress, message, details, request, hasErrors } = context
    },
    change(context) {
      // 在 watch 模式下文件更改时调用
    },
    update(context) {
      // 在每次进度更新后调用
    },
    done(context) {
      // 编译完成时调用
    },
    progress(context) {
      // 构建进度更新时调用
    },
    allDone(context) {
      // 当编译完成时调用
    },
    beforeAllDone(context) {
      // 当编译完成前调用
    },
    afterAllDone(context) {
      // 当编译完成后调用
    },
}

当然多数情况下,我们并不会使用这些,基本默认就足够了。

本文参与?腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 配置步骤
    • 基本配置
      • Vue Loader简介
    • 处理HTML
      • 处理JS
        • 处理CSS
          • 处理css文件
          • 处理less
          • 处理sass
        • 处理URL
          • 静态文件处理
          • 我的配置
          • 优化
            • 优化构建速度
              • 耗时分析
              • 范围优化
              • 多进程
              • 缓存
            • 优化构建结果
              • 结果分析
              • 压缩 CSS
              • 压缩 JS
              • 清除无用的 CSS
              • Tree-shaking
              • Scope Hoisting
            • 优化运行时体验
              • splitChunks 分包配置
              • 代码懒加载
              • prefetch 与 preload
          • 其他插件
            • 构建进度条插件
            相关产品与服务
            内容分发网络 CDN
            内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com