webpack5(v5.59.1)打包流程詳解及搭建項目


前言

近期對自己負責的項目進行優化,但 webpack 這塊一直有着茫然的不熟悉,本着對 webpack 打包配置及項目優化的進一步理解和學習,所以記錄下自己學習的流程,加深印象,有深入的理解后再進行補充。另外,對不同版本的 webpack(比如 webpack4 和 webpack5),在一些配置的參考上會略有不同。

此測試項目已上傳至gitee,有錯誤及不准確之處,歡迎評論指正~

一、初始化及安裝

1. 初始化項目

新建文件夾 demo_webpack,在此目錄下執行命令 npm init,執行后項目文件夾里會生成包管理器 package.json

$ npm init

2. 安裝打包工具 webpack

# 安裝webpack腳手架
$ npm i webpack-cli -D

# 安裝webpack
$ npm i webpack -D

二、基礎配置

1. 配置出入口

(1)配置入口

新建 build/webpack.common.js

// webpack.common.js
const path = require('path')

module.exports = {
  entry: path.resolve(__dirname, '../src/main.js'), // 入口文件
}

(2)配置出口

// webpack.common.js
const path = require('path')

module.exports = {
  output: {
    path: path.join(__dirname, '../dist'), // 打包后生成的文件夾
    filename: '[name].[contenthash:8].js', // js文件名稱
    clean: true, // 每次構建都清除dist包
  },
}

2. 添加 webpack 構建腳本

// package.json

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

最后可以運行打包命令

$ npm run build

打包后目錄結構

三、插件(plugins)

插件(Plugins)是用來拓展 Webpack 功能的,包括:打包優化、資源管理、注入環境變量。插件使用:只需要 require()它,然后把它添加到 plugins 數組中。

1. html-webpack-plugin

該插件將為你生成一個 HTML5 文件, 其中包括使用 script 標簽的 body 中的所有 webpack 包。 只需添加插件到你的 webpack 配置如下

(1)安裝

$ npm i html-webpack-plugin -D

(2)新建 index.html

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <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>demo_webpack</title>
  </head>
  <body>
    <div id="app">
      <div>這是一段測試文字...</div>
    </div>
  </body>
</html>

(3)配置

// webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../index.html'), // html模板
      filename: 'index.html',
    }),
  ],
}

(4)運行

$ npm run build

打包完成后可以看到 dist 下多了 index.html 文件,並且打包生成的 js 文件在 index.html 中通過 script 標簽被引入。

2. progress-bar-webpack-plugin

打包時可以顯示打包進度

(1)安裝

$ npm i progress-bar-webpack-plugin -D

(2)配置

// webpack.common.js
const ProgressBarPlugin = require('progress-bar-webpack-plugin')

module.exports = {
  plugins: [
    new ProgressBarPlugin({
      complete: '█',
    }),
  ],
}

(3)運行

$ npm run build

可以看到執行打包命令后顯示出了打包進度...

四、loader

webpack 使 loader 能夠對文件進行預處理,用於對模塊的源代碼進行轉換,loader 都在 module 下的 rules 中配置。

rules 是一個數組,數組每一項是一個 js 對象,該對象有兩個關鍵屬性 test 和 use。loader 配置項:

  • test: 是一個正則表達式或正則表達式數組,模塊文件名與正則匹配的會被 use 里的 loader 處理(必選)
  • loader 調用 loader 的名稱 / use 鏈式調用 loader (必選,兩種調用方式選其一)
  • exclude: 有一些文件不想被正則匹配到的 loader 處理。值是字符串或正則表達式,字符串需要是絕對路徑(可選)
  • include: 表示的意思正好與 exclude 相反,它表示只對匹配到的文件處理(可選)
  • options: 為 loader 提供額外的參數配置(可選)

1.css-loader 和 style-loader

用於加載解析 css

css-loader: 解釋(interpret) @import 和 url() ,會 import/require() 后再解析(resolve)它們

style-loader: 將 CSS 注入到 JavaScript 中,通過 DOM 操作控制 css

(1)安裝

$ npm i css-loader style-loader -D

(2)配置

// webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'], //從右向左解析
      },
    ],
  },
}

2.url-loader 和 file-loader

加載 images 圖像,字體,視頻資源

url-loader: 功能類似於 file-loader,但是在文件大小(單位 byte)低於指定的限制時,可以返回一個 DataURL

file-loader: 默認情況下,生成的文件的文件名就是文件內容的 MD5 哈希值並會保留所引用資源的原始擴展名

(1)安裝

$ npm i url-loader file-loader -D

(2)配置

// webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/, // 圖片、gif、svg、ico資源
        type: 'javascript/auto', // 解決asset重復
        loader: 'url-loader', // 將文件轉為base64內聯到bundle中,如果超出限制的大小,則使用file-loader將文件移動到輸出的目錄中
        options: {
          esModule: false, // 關閉es6模塊化解析
          limit: 10000, // 圖片大於10kb,就會被base64處理
          name: 'img/[name].[hash:7].[ext]', // [hash:10]取圖片的hash的前10位, [ext]取文件原來擴展名
        },
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 視頻資源
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'media/[name].[hash:7].[ext]',
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字體資源
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'fonts/[name].[hash:7].[ext]',
        },
      },
    ],
  },
}

(3)拓展

在 webpack5 中,內置了資源模塊(asset module),代替了 file-loader 和 url-loader

例如,處理 png 圖片資源,如下配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource',
        generator: {
          filename: 'img/[name].[hash:7].[ext]',
        },
      },
    ],
  },
}

3. babel-loader

解析 es6,jsx

Babel 其實是幾個模塊化的包,使用@區分其他非官方包:

  • @babel/core:babel 核心庫
  • babel-loader:webpack 的 babel 插件,讓我們可以在 webpack 中運行 babel
  • @babel/preset-env:將 ES6 轉換為向后兼容的 JavaScript
  • @babel/plugin-transform-runtime:處理 async,await、import()等語法關鍵字的幫助函數

(1)安裝

$ npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -D

(2)配置

// webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /(\.jsx|\.js)$/,
        use: ['babel-loader'],
        exclude: /node_modules/,
      },
    ],
  },
}

(3)添加 babel 額外配置項

在根目錄下新建.babelrc,配置如下

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

3. html-loader

將 HTML 導出為字符串,處理 HTML 中引入的靜態資源。當編譯器需要時,將壓縮 HTML 字符串

(1)安裝

$ npm i html-loader -D

(2)配置

// webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.html$/i,
        loader: 'html-loader',
        options: {
          esModule: false, // 在開發環境中啟用false
        },
      },
    ],
  },
}

五、搭建環境

1. 搭建本地環境服務器

(1)安裝

$ npm i webpack-dev-server -D

(2)配置

// webpack.common.js

module.exports = {
  devServer: {
    hot: true,
    open: false,
    port: 8088,
    compress: true, // 開啟gzip壓縮
    static: {
      // 托管靜態資源文件, 可通過數組的方式托管多個靜態資源文件
      directory: path.join(__dirname, '../public'),
    },
    client: {
      progress: false, // 在瀏覽器端打印編譯速度
    },
  },
}

(3)添加啟動命令

// package.json

"scripts": {
  "dev":"webpack serve --config build/webpack.common.js",
}

2. 區分生產環境與開發環境

build 下新建 webpack.dev.jswebpack.prod.js。根目錄下新建文件夾config,在config中新建dev.env.jsprod.env.js

// dev.env.js

module.exports = {
  NODE_ENV: 'development',
}

// prod.env.js

module.exports = {
  NODE_ENV: 'prodction',
}

(1)安裝

$ npm i -D webpack-merge

(2)更改公共配置文件 webpack.common.js

// webpack.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: path.resolve(__dirname, '../src/main.js'),
  output: {
    path: path.join(__dirname, '../dist'), // 打包后生成的文件夾
    filename: '[name].[contenthash:8].js', // js文件名稱
    clean: true, // 每次構建都清除dist包
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../index.html'),
      filename: 'index.html',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /(\.jsx|\.js)$/,
        use: ['babel-loader'],
        exclude: /node_modules/,
      },
      {
        test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
        type: 'javascript/auto',
        loader: 'url-loader',
        options: {
          esModule: false,
          limit: 10000,
          name: 'img/[name].[hash:7].[ext]',
        },
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'media/[name].[hash:7].[ext]',
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'fonts/[name].[hash:7].[ext]',
        },
      },
    ],
  },
}

(3)更改開發環境配置文件 webpack.dev.js

// webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const path = require('path')
const env = require('../config/dev.env')
const webpack = require('webpack')

module.exports = merge(common, {
  stats: 'errors-only', // 去除控制台webpack打印的無用信息
  devServer: {
    hot: true,
    open: false,
    port: 8088,
    compress: true, // 開啟gzip壓縮
    static: {
      // 托管靜態資源文件, 可通過數組的方式托管多個靜態資源文件
      directory: path.join(__dirname, '../public'),
    },
    client: {
      progress: false, // 在瀏覽器端打印編譯速度
    },
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(env.NODE_ENV),
      },
    }),
  ],
})

(4)更改生產環境配置文件 webpack.prod.js

// webpack.prod.js

/*
 * @Date: 2021-10-22 15:49:07
 * @information: 生產配置
 */
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const env = require('../config/prod.env.js')
const path = require('path')
// 打包進度顯示
const ProgressBarPlugin = require('progress-bar-webpack-plugin')

module.exports = merge(common, {
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'js/[name].[chunkhash].js', // 此選項決定了每個輸出 bundle 的名稱
    chunkFilename: 'js/[id].[chunkhash].js', // 此選項決定了非入口(non-entry) chunk 文件的名稱
  },
  module: {
    rules: [
      {
        test: /\.html$/i, // 將HTML導出為字符串,處理HTML中引入的靜態資源
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(env.NODE_ENV),
      },
    }),
    new ProgressBarPlugin({
      complete: '█',
    }),
  ],
})

(5)修改 package.json 中啟動和打包命令

"scripts": {
    "dev": "webpack serve --config build/webpack.dev.js",
    "build": "webpack --config build/webpack.prod.js",
  },

3. 配置別名

// webpack.common.js

module.exports = {
  resolve: {
    extensions: ['.js', '.jsx', '.json', '.vue'], // 省略文件后綴
    alias: {
      // 配置別名
      '@': path.resolve(__dirname, '../src'),
    },
  },
}

六、代碼分離

1. mini-css-extract-plugin

分離 css 文件

(1)安裝

$ npm i mini-css-extract-plugin -D

(2)配置

// webpack.common.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name]_[contenthash:8].css', // 抽離整的css文件名稱
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.(scss|sass)$/, // 解析scss、sass,需安裝sass-loader
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ],
  },
}

七、打包優化

1. 開啟 gzip 壓縮

(1)安裝

$ npm i compression-webpack-plugin -D

(2)配置

//webpack.prod.js
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = {
  plugins: [
    new CompressionPlugin({
      asset: '[path].gz[query]', // 目標資源名稱。[file] 會被替換成原資源。[path] 會被替換成原資源路徑,[query] 替換成原查詢字符串
      algorithm: 'gzip',
      // test: new RegExp('\\.(js|css)$'),
      test: /\.(js|css)$/,
      threshold: 10240, // 只處理比這個值大的資源。按字節計算, 默認為0
      minRatio: 0.8, // 只有壓縮率比這個值小的資源才會被處理, 默認為0.8
    }),
  ],
}

2. externals

防止將外部資源包打包到自己的 bundle 中,例如從 cdn 引入的資源防止進行打包處理

(1)cdn 引入資源

<!-- index.html -->

<!DOCTYPE html>
<html lang="zh_CN">
  <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>demo_webpack</title>
  </head>
  <body>
    <div id="app">
      <div>這是一段測試文字...</div>

      <div class="green">這是一段綠色的字啊222</div>

      <div class="red">這是一段紅色的字啊</div>
    </div>

    <!-- 引入cdn -->
    <script
      src="https://code.jquery.com/jquery-3.1.0.js"
      integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
      crossorigin="anonymous"
    ></script>
  </body>
</html>

(2)配置

//webpack.common.js

module.exports = {
  externals: {
    jquery: 'jQuery',
  },
}

(3)在業務 js 文件中引入

import $ from 'jquery'

八、 引入 Vue

1. 安裝

vue-loader,用於解析.vue 文件
vue-template-compiler,用於模板編譯

$ npm i -D vue-template-compiler vue-loader vue-style-loader

2. 配置

// webpack.common.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  plugins: [new VueLoaderPlugin()],
  module: {
    rules: [
      // vue-loader 要放在匹配規則的第一個,否則會報錯
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        include: [path.resolve(__dirname, '../src')],
      },
    ],
  },
}

3. 配置 externals

(1)在 index.html 文件中引入 vue、vue-router

<!-- index.html -->

<script src="https://cdn.bootcss.com/vue/2.6.12/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.5.1/vue-router.min.js"></script>

(2)在 webpack.common.js 中引入 vue、vue-router

// webpack.common.js

module.exports = {
  externals: {
    vue: 'Vue',
    'vue-router': 'VueRouter',
  },
}

4. 使用

此時可以正常使用 vue 和 vue-router 了...

比如新建 src/App.vue、src/Home.vue、src/Mine.vue 等頁面,新建 src/routers/router.js 路由文件。並寫入內容及配置,此處省略,可參考gitee代碼。

(1)配置 main.js

// main.js

import Vue from 'vue'
import App from './App.vue'
import router from './routers/router'
Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
})

九、一些資源處理及優化插件

1. copy-webpack-plugin

拷貝插件,將一些靜態資源(如圖片、視頻、文件)拷貝至打包后的文件夾中

$ npm i -D copy-webpack-plugin

// webpack.common.js
const CopyPlugin = require("copy-webpack-plugin")

module.exports = {
  new CopyPlugin({
    patterns: [
      // 說明:在此測試項目中,`public/static`中的內容打包時會被復制到`dist/static`中...
      {
        from: path.resolve(__dirname, '../public'), // 定義要拷貝的源目錄
        to: '', // 定義要拷貝到的目標目錄,非必填,不填寫則拷貝到打包的output輸出地址中
      },
    ],
  }),
}

2. friendly-errors-webpack-plugin

友好的終端錯誤顯示方式,項目啟動后可以在終端中提示一些自定義信息

$ npm i -D friendly-errors-webpack-plugin

// webpack.dev.js
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')

module.exports = {
  plugins: [
    new FriendlyErrorsWebpackPlugin({
      // 運行成功
      compilationSuccessInfo: {
        messages: ['Your Application is: http://localhost:8088'],
        notes: ['有些附加說明要在成功編輯時顯示'],
      },
      // 運行錯誤 - //您可以收聽插件轉換和優先級的錯誤, 嚴重性可以是'錯誤'或'警告'
      onErrors: utils.createNotifierCallback(),
      clearConsole: true, // 是否每次編譯之間清除控制台, 默認為true
    }),
  ],
}

// build/utils.js

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier')

  return (severity, errors) => {
    if (severity !== 'error') return

    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()

    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      // icon: path.join(__dirname, 'logo.png')
    })
  }
}

3. terser-webpack-plugin

壓縮 js,去除注釋等..(PS: webpack5+已內置壓縮 js 的插件功能,但若想自定義,需額外引包寫配置。)

$ npm i -D terser-webpack-plugin

// webpack.prod.js
const TerserPlugin= require('terser-webpack-plugin')

module.exports = {
  plugins: [
    new TerserPlugin({
      parallel: true, // 多進程
      terserOptions: {
        ecma: undefined,
        warnings: false,
        parse: {},
        compress: {
          drop_console: true,
          drop_debugger: true,
          pure_funcs: ['console.log'], // 移除console
        },
      },
    }),
  ],
}

附 packag.json 部分配置

使用 cross-env 進行啟動和打包項目的命令,設置環境變量等。

$ npm i -D cross-env

// packag.json

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "npm run dev",
  "dev": "cross-env NODE_ENV=development webpack server --progress --config build/webpack.dev.js",
  "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js"
},

文件結構

打包后的文件結構


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM