Webpack4 不深不淺的實踐教程

Webpack Legato

本文偏入門&實踐,從零開始配置 Webpack; 實際項目開發,零配置是不存在的。

🚀 安裝&快速開始

快速初始化配置文件 package.jsonjavascript

// npm i yarn -g
yarn init -y

// yarn init --yes
// yarn init --yes=true // 即所有選項默認爲 yes

接下來將 webpack 添加到 package.json => devDependenciescss

yarn add webpack -D

安裝成功後,建立目錄 src/index.js 並添加以下內容 (默認入口爲 src)html

document.write("Hello webpack4!");

命令行輸入:java

webpack --mode=development

成功後顯示,打開 dist 文件夾會看到 main.js (默認輸出到 dist)node

Hash: 771a2645c2d430fa3bb4
Version: webpack 4.5.0
Time: 128ms
Built at: 2020-4-10 03:14:23
  Asset      Size  Chunks             Chunk Names
main.js  2.81 KiB    main  [emitted]  main
Entrypoint main = main.js
[./index.js] 34 bytes {main} [built]
--mode 模式 (必選,否則會有 WARNING),是 webpack4 新增的參數選項,默認是 production
  • --mode production 生產環境react

    • 提供 uglifyjs-webpack-plugin 代碼壓縮
    • 不須要定義 new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }) 默認 production
    • 默認開啓 NoEmitOnErrorsPlugin -> optimization.noEmitOnErrors, 編譯出錯時跳過輸出,以確保輸出資源不包含錯誤
    • 默認開啓 ModuleConcatenationPlugin -> optimization.concatenateModules, webpack3 添加的做用域提高(Scope Hoisting)
  • --mode development 開發環境webpack

    • 使用 eval 構建 module, 提高增量構建速度
    • 不須要定義 new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }) 默認 development
    • 默認開啓 NamedModulesPlugin -> optimization.namedModules 使用模塊熱替換(HMR)時會顯示模塊的相對路徑

接下來建立 dist/index.html 並引入 main.js, 瀏覽器中打開看內容。git

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>webpack-simple</title>
</head>
<body>
  <script type="text/javascript" src="./main.js"></script>
</body>
</html>

再建立一個文件 src/content.js, 在 src/index.js 中引入該模塊github

// content.js
module.exports = 'Looooooooooooooong content!';
// index.js
document.write(`Hello webpack4!${require('./content.js')}`);

再次執行 webpack --mode=development 完了打開 index.htmlweb

// 內容
Hello webpack4!Looooooooooooooong content!

Demo

🍌 快速出土 webpack.config.js

安裝 webpack-cli 來初始化配置

yarn add webpack-cli -D
webpack-cli init

1. Will your application have multiple bundles? No // 單入口 string, 多頁面 object
2. Which module will be the first to enter the application? [example: './src/index'] ./src/index // 程序入口
3. What is the location of "app"? [example: "./src/app"] './src/index' // 程序主文件
4. Which folder will your generated bundles be in? [default: dist]: // 輸出目錄,默認 dist
5. Are you going to use this in production? No // (Yes 第9步默認'config', No 則爲 'prod')
6. Will you be using ES2015? Yes // 會添加 ES6 => ES5 的配置
7. Will you use one of the below CSS solutions? CSS // 選一種樣式語言,會生成對應的 loader 配置
8. If you want to bundle your CSS files, what will you name the bundle? (press enter to skip) // 回車跳過
9. Name your 'webpack.[name].js?' [default: 'config']: // webpack.config.js

Congratulations! Your new webpack configuration file has been created!

配置生成OK,以下

// webpack.config.js

const webpack = require('webpack');
const path = require('path');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: './src/index.js',

  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: ['env']
        }
      },
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader', options: { sourceMap: true } },
          { loader: 'css-loader' }
        ]
      }
    ]
  },

  plugins: [new UglifyJSPlugin()]
  // 這款插件用於壓縮 JS 代碼,減小資源體積大小
};

再度執行編譯一切OK, 打開 index.html 查看內容

webpack --mode=development

Hash: c30d4f489db4d568ee0b
Version: webpack 4.5.0
Time: 1308ms
Built at: 2020-4-11 04:14:23
Asset      Size  Chunks             Chunk Names
app.38de904fed135db4bf0a.js  1.17 KiB     app  [emitted]  app
Entrypoint app = app.38de904fed135db4bf0a.js
[./src/content.js] 62 bytes {app} [built]
[./src/index.js] 80 bytes {app} [built]

Demo

接下來就是在這份配置上,作一些實踐。

💄 使用 html-webpack-plugin 建立 html 文件

  • 該插件簡化了建立 HTML 文件的建立,服務於 webpack bundle。
  • 解決的問題:每次編譯完成後不用再去手動修改 index.html, 它會與 JS 生成在同一目錄 dist 並引入 app.38de904fed135db4bf0a.js
yarn add html-webpack-plugin -D

安裝完成後,在 webpack.config.js 下配置 更多可選的配置項

// webpack.config.js
+ const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
  new UglifyJSPlugin(),
+ new HtmlWebpackPlugin({
    title: 'webpack-cli'
  }),
]

從新執行 webpack --mode=development, dist 目錄就會多個 index.html 並引入了 main.bundle.js.

⚛️ Webpack4 配置 React 開發環境

上面配置中的 module.rules babel-loader 的應用

babel-loader 將 ES6* 代碼轉化爲 ES5 代碼

Babel 默認只轉換新的 JavaScript 句法 (syntax), 而不轉換新的 API, 好比 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign)都不會轉碼。

舉例來講,ES6 在 Array 對象上新增了 Array.from 方法。Babel 就不會轉碼這個方法。若是想讓這個方法運行,必須使用 babel-polyfill,爲當前環境提供一個墊片。—— 摘自 阮一峯 Babel 入門教程

yarn add react react-dom babel-preset-react
babel-preset-react 用於解析 react 的語法;

babel-preset-env 初始化配置時已經安裝。它的前身是 babel-preset-es2015/es2016/es2017 之後要用新特性這個包就能夠搞定一切。

安裝完成,修改 src/index.js 的內容爲

import React from 'react';
import { render } from 'react-dom';

render(<h1>Hello world!</h1>, document.querySelector('#root'));

webpack.config.js module.rules babel-loader 配置 presets 刪掉。
在項目根目錄新建 .babelrc 文件,內容以下

// .babelrc
{
    "presets": [
        "env",
        "react"
    ]
}
// webpack.config.js
plugins: [
  new HtmlWebpackPlugin({
+   template: './index.html' // 添加模版文件
  }),
]
// index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack4-react16</title>
  </head>
  <body>
    <div id="root" />
</html>

再次執行 webpack --mode=development, ok!

Demo

🔄 實時刷新頁面 webpack-dev-server

yarn add webpack-dev-server -D

打開 package.json 添加構建腳本

"scripts": {
    "dev": "webpack-dev-server --mode=development --open --hot"
    "//": "webpack-dev-server --mode=development --host 0.0.0.0"
    "//": "使用本機 IP 訪問項目 [Your IP]:8080 => 192.168.0.111:8080"
},

執行 yarn dev, 自動刷新完成。

🔁 模塊熱替換 Hot Module Replacement

即在不重載頁面的狀況下,實時替換更新修改的模塊。提升開發效率。
本文使用 React, 因此用 react-hot-loader

Webpack HMR 原理解析

yarn add react-hot-loader -D

項目根目錄下新建文件 .babelrc, 添加內容:

{
+  "plugins": ["react-hot-loader/babel"]
}

src 目錄下添加文件 App.js

// src/App.js
import React from 'react';
import { hot } from 'react-hot-loader';

const App = () => <div>Hello World!</div>;

export default hot(module)(App)

應用入口引入 App.js

// src/index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App';

render(<App />, document.querySelector('#root'));

從新執行 yarn dev, 修改下 App.js 的代碼,注意看瀏覽器與 console.

[HMR]  - ./src/App.js
log.js:24 [HMR] App is up to date.

若是 hot(module)(App)render 一個文件則會收到警告

🌀 Webpack4 加載 CSS

在 4.x 版本以前,用的是 extract-text-webpack-plugin,不過 webpack@4.3.0 不支持使用。

做者推薦使用 mini-css-extract-plugin

yarn add mini-css-extract-plugin -D
// module.rules
{
  test: /\.css$/,
  use: [
    MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader',
      options: {
        
      }
    }
  ]
}

plugins: [
  new MiniCssExtractPlugin({
    filename: "[name].[contenthash].css",
    chunkFilename: "[id].[contenthash].css"
  })
],

🔢 按需加載 React 組件

React Loadable 簡介

yarn add react-loadable
yarn add babel-preset-stage-2 -D // for 動態 import() 語法
import Loadable from 'react-loadable';

const Loading = () => 'Loading...';
const Home = Loadable({ loader: () => import('./Home'), loading: Loading });

效果如圖

按需加載

按需加載OK,不過發現個問題,這個 Header 組件被多處調用,樣式&JS都存在屢次加載。

樣式JS重複加載

接下來要作的就是把共用的代碼提取出來。

🚾 Webpack4 提取公共 CSS&JS

optimization.splitChunks 文檔

配置以下

// webpack.config.js
optimization: {
  splitChunks: {
    cacheGroups: {
      commons: {
        name: 'commons',
        priority: 10,
        chunks: 'initial'
      },
      styles: {
        name: 'styles',
        test: /\.css$/,
        chunks: 'all',
        minChunks: 2,
        enforce: true
      }
    }
  }
}

提取共同CSS

css header 被單獨提取到 styles.css

🥝 分離第三方庫

entry: {
  app: './src/index.js',
+ ramda: ['ramda'],
}

new HtmlWebpackPlugin({
  template: './index.html',
+ chunks: ['app', 'commons', 'ramda']
})

2.e9dc7e430f6a31c868b2.css   45 bytes        2  [emitted]
                   app.bundle.js    9.6 KiB      app  [emitted]  app
      0.decbf5b19337a4ce4aac.css   61 bytes        0  [emitted]
                     0.bundle.js   4.01 KiB        0  [emitted]
+                ramda.bundle.js   7.99 KiB    ramda  [emitted]  ramda
                      index.html  393 bytes           [emitted]

🎨 Antd 定製主題色

yarn add antd
yarn add less less-loader babel-plugin-import -D
// .babelrc 添加
{
  "plugins": [
    [
      "import",
      {
        "style": true,
        "libraryName": "antd"
      }
    ]
  ]
}
// webpack.config.js module.rules 添加

{
  test: /\.less$/,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    {
      loader: 'less-loader',
      options: {
        sourceMap: true,
        javascriptEnabled: true,
        modifyVars: {
          'primary-color': '#531dab'
        }
      }
    }
  ]
}

🔨 autoprefixer 處理瀏覽器前綴

display: -webkit-box;
display: -ms-flexbox;
display: flex;
yarn add autoprefixer postcss-loader -D

項目根目錄新建 postcss.config.js

// postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')({
      'browsers': ['> 1%', 'last 2 versions']
    })
  ]
};
// webpack.config.js module.rules
{
  test: /\.css$/,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
+   'postcss-loader'
  ]
}

Demo: webpack4-react16-react-router4

🚑 一些報錯 & 解決辦法

Uncaught Error: [HMR] Hot Module Replacement is disabled.

運行 webpack-dev-server --mode=development 報錯。

webpack.config.js devSever hot: true, inline: true 刪掉,
添加 webpack-dev-server --mode=development --hot --inline

或者

plugins: [
+    new webpack.HotModuleReplacementPlugin(),
]

ERROR in 0.js from UglifyJs TypeError: Cannot read property 'sections' of null

查看 webpack/issues/1385

TypeError: Cannot read property 'sections' of null 👉 Remove `new UglifyJsPlugin` from plugins part
schema id ignored LoaderOptionsPlugin              👉 Remove `new LoaderOptionsPlugin` plugin from config
schema id ignored SourceMapDevToolPlugin           👉 Remove `devtool` config

ERROR in chunk app [entry] [name].[chunkhash].js

查看 webpack/issues/2393

📚 參考資料

Webpack 4 tutorial: All You Need to Know, from 0 Conf to Production Mode
A tale of Webpack 4 and how to finally configure it in the right way
webpack 4: mode and optimization
webpack split chunks
webpack init
Webpack HMR 原理解析
精讀《webpack4.0 升級指南》

擠時間敲了幾天,教程終於告一段落!後續還會繼續完善其餘的配置(HappyPack, DllReferencePlugin...)實踐; 本文若有錯誤,歡迎指正,很是感謝。
相關文章
相關標籤/搜索