手把手教你從零認識webpack4.0


原文鏈接:https://www.jianshu.com/p/f931f47cbf75

前言: 作為一個現代javascript 應用程序的靜態模塊打包器,webpack能將各種資源,如js,css, 圖片等作為模塊來處理,是當下前端工程化的一個很受歡迎的工具,webpack目前最新的版本是4.0,文章將在4.0 的基礎上,從使用者的角度,一步步教你認識並搭建一個簡單的webpack配置項目,當然webpack的配置和使用較為豐富且復雜,更多的內容需要參考webpack官網

1. 兩個基本的依賴:

首先webpack 項目的兩個核心基礎模塊是webpack 和webpack-cli,這是webpack項目構建的前提

npm install --save-dev webpack webpack-cli

2. 運行webpack

默認情況下,webpack 運行構建指令默認 以項目文件夾下的 src/index.js 作為入口文件, 運行 webpack指令會執行默認的webpack 配置文件。

而在一般情況下,需要構建符合項目要求的配置文件,可在package.json 中同過--config配置webpack的執行文件(如下)

"script":
    {
        "build": "webpack --config ./config/webpack.base.js"
    }

3. webpack 配置文件的設置

通過指定配置文件后,接下來的工作是根據需要配置執行的配置文件

// webpack.base.js

module.exports = {
    
}
3.1 入口文件

指定項目的入口文件

module.exports = {
    entry: "./****", // 指定入口文件
}
3.1 出口文件
module.exports = {
    entry: "./****", // 指定入口文件
    output: {
        path: path.resolve(__dirname, ....),// 輸出路徑,一般為絕對路徑
        filename: '****', // 輸出文件名  [hash]可以用來每次以hash值的區別生成文件
        publicPath: 'static', //輸出解析文件的目錄,url 相對於 HTML 頁面
    },
    chunkFilename: '***'
}

注意:

// publicPath 的解釋
比如 publicPath  設置為static 之后,html 頁面中引用的url 會自動加上static

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>test</title>
</head>
<body>
<script type="text/javascript" src="static/bundle.67c249f5bcf3681ab97e.js"></script></body>
</html>

tips: 如何理解chunkFileName:
chunkname 是未被列入entry 中, 卻有需要被打包出來的文件命名配置, 例如,某些公共模塊需要單獨的抽離出來,這些公共模塊就可以用chunkname 來命名
可以見下面的代碼分離部分

3.2 配置多個入口文件
{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}

// 寫入到硬盤:./dist/app.js, ./dist/search.js
3.3 clean-webpack-plugin

不斷運行 webpack 的指令,每次都會生成不同的不同hash 值的js 腳本,因此,我們需要一個插件,每次構建項目之前,將原先的構建完成的文件夾刪除,首選 clean-webpack-plugin 的插件 配置相關如下

 const CleanWebpackPlugin = require('clean-webpack-plugin');

 module.exports = {
     plugins: [
        new CleanWebpackPlugin(['dist'], {
            {
                root: '', // 刪除文件夾的根路徑
                verbose: true, // 是否打開日志
            }
        }) //  第一個參數為刪除的文件夾數組
     ]
     
 }

相關參數配置clean-webpack-plugin

3.4 html-webpack-plugin

webpack 構建項目時, 通過指定的入口文件,會將所有的js css 等以依賴模塊的形式打包成一個或多個的腳本文件,通常情況下,腳本文件會附屬於html 文件運行,這時候需要將 打包好的腳本文件,注入到html 中, html-webpack-plugin 插件的目的是, 以一個html 為模板, 將打包好的腳本注入到模板中, 相關的配置如下

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

new HtmlWebpackPlugin()  // 不帶任何配置時, 默認會以一個內置普通的html 作為模板html

new HtmlWebpackPlugin({
    title: 'title', // 給模板中的html 注入標題, 需要在模板的html 中指明配置, <%= htmlWebpackPlugin.options.title %>
    filename: '', // 指定轉換后的html 文件名
    template: './',// 模板文件的路徑
    chunk: ['main']// chunk 指定了該模板導入的模塊,在多頁面的配置中,可以在該屬性中配置多個入口中的一個或者多個腳本文件
})

4. mode 模式

所謂模式,webpack4.0默認的模式是 'production',可以通過 mode 來更改模式為'development'

module.exports = {
    mode: 'development' // 會將  process.env.NODE_ENV === 'development'
    mode: 'production' // 會將 process.env.NODE_ENV === 'production'
}

相關文章

5. webpack-dev-server

5.1 生產配置/ 開發配置

  • 生產模式下的要求: 注重模塊的大小

  • 開發模式下的要求: 調試, 熱更新

在生產環境中,默認會進行腳本的壓縮。

在開發環境中,我們需要快速的調試代碼,因此需要有一個本地的服務器環境,用於訪問 webpack 構建好的靜態文件,webpack-dev-server 是 webpack 官方提供的一個工具,可以基於當前的 webpack 構建配置快速啟動一個靜態服務。當 mode 為 development 時,會具備 hot reload 的功能,即當源碼文件變化時,會即時更新當前頁面,以便你看到最新的效果。

根據需要,需要將配置文件抽離成生產配置和開發配置,並留一個共同的配置文件

使用 webpack-merge 來合並對象

npm i --save-dev webpack-dev-server
// package.json
{
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "webpack.config.js",
    "scripts": {
     "dev": "webpack-dev-server --open", // webpack-dev-server 啟動
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
  }
webpack.dev.js
    
    const merge = require('webpack-merge')
    
    module.exports = merge(base, {
        devtool: "cheap-module-eval-source-map"  
        devServer: {
            port: 8088,
            compress: true,
            ...
        }
    })
    
    

講講webpack-dev-server 的配置,webpack-dev-server 的配置比較多,具體可以參考webpack-dev-server官方文檔

常見的配置:

  • public: 指定靜態服務的域名,當你使用 Nginx 來做反向代理時,應該就需要使用該配置來指定 Nginx 配置使用的服務域名
  • port : 指定端口號
  • openPage: 指定初次訪問的頁面
  • publicPath:指定構建好的靜態文件在瀏覽器中用什么路徑去訪問,默認是 /,比如設為 static 時, 默認訪問靜態的路徑變成 http://localhost:8080/static/bundle.js
  • proxy 用於配置 webpack-dev-server 將特定 URL 的請求代理到另外一台服務器上。當你有單獨的后端開發服務器用於請求 API 時,這個配置相當有用
proxy: {
  '/api': {
    target: "http://localhost:3000", // 將 URL 中帶有 /api 的請求代理到本地的 3000 端口的服務上
    pathRewrite: { '^/api': '' }, // 把 URL 中 path 部分的 `api` 移除掉
  },
}...

devtool 的各種模式會有另一篇文章進行講解,敬請期待。。。

6. loader

webpack 中提供一種處理多種文件格式的機制,便是使用 loader。我們可以把 loader 理解為是一個轉換器,負責把某種文件格式的內容轉換成 webpack 可以支持打包的模塊。

舉個例子,在沒有添加額外插件的情況下,webpack 會默認把所有依賴打包成 js 文件,如果入口文件依賴一個 .hbs 的模板文件以及一個 .css 的樣式文件,那么我們需要 handlebars-loader 來處理 .hbs 文件,需要 css-loader 來處理 .css 文件(這里其實還需要 style-loader,后續詳解),最終把不同格式的文件都解析成 js 代碼,以便打包后在瀏覽器中運行。...

6.1 css-loader

css-loader 負責解析 CSS 代碼,主要是為了處理 CSS 中的依賴,例如 @import 和 url() 等引用外部文件的聲明

6.2 style-loader

style-loader 會將 css-loader 解析的結果轉變成 JS 代碼,運行時動態插入 style 標簽來讓 CSS 代碼生效。

6.3 file-loader

file-loader 用來處理jpg/png/gif 等文件格式

6.4 sass-loader node-sass

這兩個loader 用於解析scss 文件

module.exports = {
    module: {
        rules: [
            {
                test: /\.(css|scss)$/,
                use: [
                  process.env.NODE ? 'style-loader' : MiniCssExtractPlugin.loader,
                  'css-loader',
                  'sass-loader'
                ]
          }
        ]
    }
}
6.5 babel 相關

為什么要使用babel, 目前低版本的部分瀏覽器,並不支持es6的相關語法,babel 的目的就是為了講es6 的相關語法,轉換成es5 的語法

npm install -D babel-loader @babel/core @babel/preset-env

webpack 4中需要安裝這三個loader
配置如下:

module.exports = {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
            }
        }
      }
    ]
}

babel的三個loader 可以解決 箭頭函數這類es6 語法, 但是無法解決promise, symbol 這類新增的內建對象,
因此需要引入 babel-polyfill來進行全局的轉換

ps: babel-polyfill 需要放在生產依賴中, 即npm i --save babel-polyfill
並且需要配置 entry

module.exports = {
  entry: ['babel-polyfill', './src/main.js'],
  ···
}

使用前

main.f66bf9d8d1fd5b0f62ae.css   88 bytes       0  [emitted]  main
bundle.f66bf9d8d1fd5b0f62ae.js   66.6 KiB       0  [emitted]  main
main.f66bf9d8d1fd5b0f62ae.css.map  194 bytes       0  [emitted]  main
bundle.f66bf9d8d1fd5b0f62ae.js.map    219 KiB       0  [emitted]  main

使用后

main.0c090c129f1d9d4804b0.css   88 bytes       0  [emitted]  main
bundle.0c090c129f1d9d4804b0.js    154 KiB       0  [emitted]  main
main.0c090c129f1d9d4804b0.css.map  194 bytes       0  [emitted]  main
bundle.0c090c129f1d9d4804b0.js.map    219 KiB       0  [emitted]  main

tips:
可以很明顯的發現,打包后的bundle會比之前要大大概100k 左右,原因是, babel-polyfill 會對全局進行改寫,這樣其實壞處是污染了全局的環境,並且增加了打包后的文件大小,這也是要進行安裝在dependency 而不是 devDependency的原因

因此, webpack4.0 提供了另外的 plugin-transform-runtime

npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime


{
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime']
          }
        }
      }

7.其他plugin

在 webpack 的構建流程中,plugin 用於處理更多其他的一些構建任務。可以這么理解,模塊代碼轉換的工作由 loader 來處理,除此之外的其他任何工作都可以交由 plugin 來完成

7.1 uglifyjs-webpack-plugin

uglifyjs-webpack-plugin 是用來對js 代碼進行壓縮體積用的,在webpack4.0中, 默認的配置是進行壓縮,可以通過 mode 模式的 development 來設置成不進行壓縮,默認模式是production

其他的默認配置可以參考:
uglifyjs-webpack-plugin

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  //...
  optimization: {
    minimizer: [
      new UglifyJsPlugin()
    ]
  }
}
7.2 DefinePlugin

DefinePlugin 是 webpack 內置的插件,可以使用 webpack.DefinePlugin 直接獲取。
這個插件用於創建一些在編譯時可以配置的全局常量,這些常量的值我們可以在 webpack 的配置中去指定,例如:

module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      PRODUCTION: JSON.stringify(true), // const PRODUCTION = true
      VERSION: JSON.stringify('5fa3b9'), // const VERSION = '5fa3b9'
      BROWSER_SUPPORTS_HTML5: true, // const BROWSER_SUPPORTS_HTML5 = 'true'
      TWO: '1+1', // const TWO = 1 + 1,
      CONSTANTS: {
        APP_VERSION: JSON.stringify('1.1.2') // const CONSTANTS = { APP_VERSION: '1.1.2' }
      }
    }),
  ],
}...

有了上面的配置,就可以在應用代碼文件中,訪問配置好的變量了,如:

console.log("Running App version " + VERSION);

if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
7.3 copy-webpack-plugin

這個插件看名字就知道它有什么作用,沒錯,就是用來復制文件的。

我們一般會把開發的所有源碼和資源文件放在 src/ 目錄下,構建的時候產出一個 build/ 目錄,通常會直接拿 build 中的所有文件來發布。有些文件沒經過 webpack 處理,但是我們希望它們也能出現在 build 目錄下,這時就可以使用 CopyWebpackPlugin 來處理了。

我們來看下如何配置這個插件:...

const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
  // ...
  plugins: [
    new CopyWebpackPlugin([
      { from: 'src/file.txt', to: 'build/file.txt', }, // 顧名思義,from 配置來源,to 配置目標路徑
      { from: 'src/*.ico', to: 'build/*.ico' }, // 配置項可以使用 glob
      // 可以配置很多項復制規則
    ]),
  ],
}...

7.4 extract-text-webpack-plugin

extract-text-webpack-plugin 是在webpack4.0 之前用來把 依賴的css 分離出來成為單獨的文件,可以讓腳本文件變得更小,
webpack 4.0 不再使用extra-text-webpack-plugin來分離css 轉而使用mini-css-extract-plugin

7.5 mini-css-extract-plugin

mini-css-extract-plugin 既需要配置plugin 也需要配置loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
    plugins: [
		new MiniCssExtractPlugin({
			// Options similar to the same options in webpackOptions.output
			// both options are optional
			filename: "[name].[contenthash].css",
			chunkFilename: "[id].css"
		})
	],
	module: {
		rules: [{
			test: /\.(css|scss)$/,
			include: [path.resolve(__dirname, '../src')],
			use: [
				MiniCssExtractPlugin.loader,
				'css-loader'
			]
		}, ]
	}
}

tips: 這個插件一般在生產環境中使用,並且使用時不能使用style-loader==
ps: contenthash 和 hash 的區別 見第八部分

7.5 IgnorePlugin

IgnorePlugin 和 ProvidePlugin 一樣,也是一個 webpack 內置的插件,可以直接使用 webpack.IgnorePlugin 來獲取。

這個插件用於忽略某些特定的模塊,讓 webpack 不把這些指定的模塊打包進去。例如我們使用 moment.js,直接引用后,里邊有大量的 i18n 的代碼,導致最后打包出來的文件比較大,而實際場景並不需要這些 i18n 的代碼,這時我們可以使用 IgnorePlugin 來忽略掉這些代碼文件,配置如下:...

module.exports = {
  // ...
  plugins: [
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ]
}

IgnorePlugin 配置的參數有兩個,第一個是匹配引入模塊路徑的正則表達式,第二個是匹配模塊的對應上下文,即所在目錄名。

8 分離代碼文件

為了實現減小打包后代碼的體積,利用緩存來加速靜態資源訪問,需要將不同,且相互不影響的代碼塊分離出來, 在plugin 中介紹過mini-css-extract-plugin 來對css 文件進行分離, 除此之外, 還建議 公共使用的第三方類庫顯式地配置為公共的部分,因為第三方庫在實際開發中,改變的頻率比較小,可以避免因公共 chunk 的頻繁變更而導致緩存失效。

module.exports = {
  entry: {
    vendor: ["react", "lodash", "angular", ...], // 指定公共使用的第三方類庫
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: "initial",
          test: "vendor",
          name: "vendor", // 使用 vendor 入口作為公共部分
          enforce: true,
        },
      },
    },
  },
  // ... 其他配置
}

// 或者
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /react|angluar|lodash/, // 直接使用 test 來做路徑匹配
          chunks: "initial",
          name: "vendor",
          enforce: true,
        },
      },
    },
  },
}

// 或者
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: "initial",
          test: path.resolve(__dirname, "node_modules") // 路徑在 node_modules 目錄下的都作為公共部分
          name: "vendor", // 使用 vendor 入口作為公共部分
          enforce: true,
        },
      },
    },
  },
}...

ps: 在次基礎上, 需要在配置的output 中設置 chunkFilename 來配置打包后這些第三方庫的名字

module.exports = {
    output: {
        path: path.resolve(__dirname, '../dist'),
        filename: 'bundle.[hash].js',
        chunkFilename: 'vendor.[chunkhash].js'
    },
}

ps: hash 和 chunkhash, contenthash 的區別

  • hash 在每次構建的時候都會重新全部生成 ,所有的文件的hash 都是同一個值, 無論是否修改了文件,所有的文件都將重新生成, 起不到緩存的效果; chunkhash根據不同的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值,比如我們將一些公共模塊,或者第三方依賴包獨立開來,接着用chunkhash 生成哈希值,只要不改變公共代碼,就不需要重新構建;
    然而當chunkhash 用在css 中時, 由於css 和js 用了同一個chunkhash,所以當只改變js 時,css 文件也會重新生成, 所以css 中我們使用contenthash

9 其他一些常用配置

resolve

resolve.alias

配置常用模塊的相對路徑

module.exports = {
  //...
  resolve: {
    alias: {
      Util: path.resolve(__dirname, 'src/util/'),
    }
  }
};

實際引用中
imoprt uitl from 'Util/sss'
原始:
import util from './src/util/sss'

resolve.extensions

這個配置可以定義在進行模塊路徑解析時,webpack 會嘗試幫你補全那些后綴名來進行查找

resolve: {
    extensions: [".js", ".vue"],
  },


免責聲明!

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



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