webpack4 教程 轉載自陳三博客

webpack 4 教程
by 陳三 on 
2015-08-28
Topics:  webpack

本文基於 webpack 4,babel 7javascript

若是你的代碼不須要模塊化,那麼你不須要 webpack;若是你的代碼須要模塊化,那麼你可能須要 webpack;若是你的代碼裏,JavaScript、圖片、CSS、JSON 等等千奇百怪的文件都要模塊化,那麼你必定須要 webpack。css

爲何選擇 webpack

webpack 官網 是這樣定義 webpack 的:html

webpack is a module bundlerjava

什麼是 module?咱們首先會想到 JavaScript 的 ES 模塊、AMD 模塊,又或 Node.js 下的 CommonJS 模塊。node

只不過在 webpack 下,全部類型的文件均可以是模塊,包括 JavaScript、CSS、圖片、JSON。react

咱們固然知道在 JavaScript 裏 import/require 一張圖片會報錯。但在 webpack 下,這是容許的,這歸功於加載器(loader)。經過加載器,webpack 將 JavaScript 的模塊化推廣到其它類型文件 - 這正是 webpack 跨出的不同凡響的一步 - 但也致使它配置繁多,廣受詬病。webpack

但明白 webpack 這一用意,咱們就掌握了 webpack 核心,接下來的事,查查文檔基本都能搞定。git

React.js 項目

我在這裏草擬了一個簡單的單頁面應用,頁面上有一張圖片,點擊圖片,圖片會旋轉,再次點擊則恢復原來的位置。github

接下來咱們將從零開始,用 webpack 搭建一個 React.js 開發環境,並完成編碼、打包、部署。web

初始化項目

首先,咱們來初始化項目:

$ cd ~
$ mkdir -p tmp/webpack-demo && cd tmp/webpack-demo
$ npm init -y

項目初始化完成後,webpack-demo 目錄下多出 package.json 文件,內容以下:

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

新建 HTML

在 webpack-demo 下新建 index.html:

<!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>webpack 教程</title> </head> <body> </body> </html> 

咱們暫時尚未引用 JavaScript。

建立 JavaScript 文件

在 webpack-demo 目錄下新建 index.js,內容暫時留空。

咱們須要安裝 react 及 react-dom

$ npm i react react-dom

裝好後,在 index.js 文件中 import 它們:

import React from 'react' import ReactDOM from 'react-dom' 

好了,如今在 index.html 中引用 index.js

<body> + <script src="index.js"></script> </body> 

如今在瀏覽器中打開 index.html,不出意外,console 中報錯了,只是不一樣瀏覽器下報的錯不同:

  1. Firefox 59.0.2: SyntaxError: import declarations may only appear at top level of a module
  2. Safari 11.0.3: SyntaxError: Unexpected identifier 'React'. import call expects exactly one argument.
  3. Chrome 65.0.3325.181: Uncaught SyntaxError: Unexpected identifier

怎麼辦?瀏覽器的兼容性問題幾乎是與生俱來的 - 要怎麼抹平它們的差異,好讓代碼可以運行在儘量多的瀏覽器平臺上?

咱們要用 webpack 來構建咱們的源代碼。

安裝 webpack

在 webpack-demo 項目根目錄中安裝 webpack

$ npm install -D webpack

安裝完成後,咱們就會看到當前安裝的 webpack 版本號。

編譯 JavaScript

webpack 是個打包工具,那麼就須要一個輸入(入口文件),一個輸出(目標文件)。

咱們試試在命令行下將 index.js 打包成 build.min.js

$ npx webpack index.js -o build.min.js
The CLI moved into a separate package: webpack-cli
Would you like to install webpack-cli? (That will run npm install -D webpack-cli) (yes/NO)

沒有打包,反而是提示咱們是否安裝 webpack-cli。這是由於 webpack 在 webpack 4 裏將命令行相關的都遷移至 webpack-cli 包。

輸入 yes 而後回車,稍等一下子,webpack-cli 就安裝好了。以後咱們的命令繼續執行,結果以下:

Hash: cbb2732def750315c477
Version: webpack 4.5.0
Time: 243ms
Built at: 2018-4-7 09:56:02
       Asset     Size  Chunks             Chunk Names
build.min.js  105 KiB       0  [emitted]  main
Entrypoint main = build.min.js
  [13] ./index.js 58 bytes {0} [built]
    + 13 hidden modules

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

打包成功,但出現黃色警告。這是 webpack 4 引入的模式,包括 developmentproductionnone 三個值,咱們不傳入值的話,默認使用 production

咱們來調整下命令:

$ npx webpack index.js -o build.min.js --mode development

再也不出現黃色警告了。

你可能好奇,不指定輸入、輸出的話會怎樣?

咱們來試試:

$ npx webpack --mode development
Hash: 86d52d22c7b7982a7ebf
Version: webpack 4.5.0
Time: 41ms
Built at: 2018-4-7 10:00:39

ERROR in Entry module not found: Error: Can't resolve './src' in '/Users/sam/tmp/webpack-demo'

報錯,說在 ./src 下找不到 index.js 文件 - 這也是 webpack 4 引入的約定,不指定輸入文件的話,則默認爲 src/index.js。咱們按照約定將項目根目錄下的 index.js 移動到 src/index.js

$ mkdir src
$ mv index.js src

而後再打包一遍:

$ npx webpack --mode development
Hash: 4726bc2e160759010056
Version: webpack 4.5.0
Time: 321ms
Built at: 2018-4-7 10:03:24
  Asset     Size  Chunks             Chunk Names
main.js  688 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 58 bytes {main} [built]
    + 21 hidden modules

打包成功,且目錄下多出 dist/main.js - 這也正是 webpack 4 未指定輸出文件時默認的位置。也就是說,咱們按照 webpack 約定組織代碼文件的話,能夠省去許多配置。

打包完成後,咱們將 index.html 中對 JavaScript 引用調整爲編譯後的 dist/main.js

<body> - <script src="./index.js"></script> + <script src="dist/main.js"></script> </body> 

再刷新瀏覽器,控制檯已經再也不報錯。

但這樣的代碼結構在部署時十分不便,咱們除了要拷貝 dist 目錄外,還要從衆多文件中拷貝出 index.html 文件,從部署上說,只需拷貝一個目錄是最方便的,所以將 index.html 也移入 dist 目錄,順便清理 build.min.js 文件:

$ mv index.html dist
$ rm build.min.js

而後再調整 index.html 中的 js 引用:

<body> - <script src="dist/main.js"></script> + <script src="main.js"></script> </body> 

實時刷新

在 index.html 文件中引用 main.js 文件後,咱們有幾個問題須要解決。

  1. 入口文件 src/index.js 的變化,包括它所引用的其它模塊的變化如何通知給 webpack,以便從新構建 dist/main.js 文件?
  2. dist/main.js 文件更新後,瀏覽器中打開的頁面該如何自動刷新?

監控文件

第一個問題,webpack 有好幾個解決辦法,其中 watch 選項最直觀,咱們可讓 webpack 監控文件變化,一旦文件有變化,就從新構建。但默認狀況下,watch 選項是禁用的。

咱們能夠在命令行下啓用它:

$ npx webpack --mode development --watch

Webpack is watching the files…

Hash: 4726bc2e160759010056
Version: webpack 4.5.0
Time: 367ms
Built at: 2018-4-7 10:12:59
  Asset     Size  Chunks             Chunk Names
main.js  688 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 58 bytes {main} [built]
    + 21 hidden modules

咱們試試在 index.js 文件中添加一行 console.log('hello webpack'),保存文件後就會看到命令行下的變化:

Hash: 0d285ea4d1c26ac17380
Version: webpack 4.5.0
Time: 19ms
Built at: 2018-4-7 10:13:35
  Asset     Size  Chunks             Chunk Names
main.js  688 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 87 bytes {main} [built]
    + 21 hidden modules

webpack 監控到 src/index.js 文件的變化,從新構建了 dist/main.js,耗時 19ms。

刷新瀏覽器

至於自動刷新瀏覽器的問題,webpack 提供 webpack-dev-server 來解決,它是一個基於 expressjs 的開發服務器,提供實時刷新瀏覽器頁面的功能。不過目前 webpack-dev-server 已經進入維護模式,文檔中推薦咱們使用 webpack-serve

但這裏暫時仍是使用 webpack-dev-server 來講明。

webpack-dev-server

首先在項目下安裝 webpack-dev-server

$ npm install -D webpack-dev-server

安裝完成後在命令行下執行 webpack-dev-server

$ npx webpack-dev-server --mode development --content-base ./dist
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/sam/tmp/webpack-demo/dist
ℹ 「wdm」: Hash: 441c738298078bd7a80b
Version: webpack 4.5.0
Time: 508ms
Built at: 2018-4-13 19:52:45
  Asset      Size  Chunks             Chunk Names
main.js  1020 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/ansi-html/index.js] 4.16 KiB {main} [built]
[./node_modules/loglevel/lib/loglevel.js] 7.68 KiB {main} [built]
[./node_modules/react-dom/index.js] 1.33 KiB {main} [built]
[./node_modules/react/index.js] 190 bytes {main} [built]
[./node_modules/sockjs-client/dist/sockjs.js] 176 KiB {main} [built]
[./node_modules/url/url.js] 22.8 KiB {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:8080] (webpack)-dev-server/client?http://localhost:8080 7.75 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.58 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js](webpack)-dev-server/client/socket.js 1.05 KiB {main} [built]
[./node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
[./node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 77 bytes {main} [built]
   [0] multi (webpack)-dev-server/client?http://localhost:8080 ./src 40 bytes {main} [built]
[./node_modules/webpack/hot/log.js] (webpack)/hot/log.js 1.03 KiB {main} [optional] [built]
[./src/index.js] 87 bytes {main} [built]
    + 32 hidden modules
ℹ 「wdm」: Compiled successfully.

Ooops,使人暈眩的輸出結果。

不過且忽視它們,咱們如今能夠在 http://localhost:8080/ 訪問咱們的 index.html

在入口文件 src/index.js 裏再添加一行代碼驗證下瀏覽器頁面的實時刷新功能:

console.log('webpack live reload is working') 

webpack 從新打包了 dist/main.js,瀏覽器中打開的頁面同時也刷新了。實際上,咱們看 webpack-dev-server 的命令行會發現,它一樣監控入口文件的變化並從新編譯 - 換句話說,webpack-dev-server 已經啓用 --watch 效果。

編碼

咱們在 src/index.js 中先寫個簡單的 React 代碼:

import React from 'react' -import ReactDOM from 'react-dom' +import ReactDOM from 'react-dom' +ReactDOM.render(<div>hello webpack</div>, document.body) 

注意,React 不推薦使用 body 上,這裏只是圖方便才這麼寫。

查看 webpack-dev-server 的狀態:

[./src/index.js] 255 bytes {main} [built] [failed][1 error]
    + 25 hidden modules

ERROR in ./src/index.js
Module parse failed: Unexpected token (3:16)
You may need an appropriate loader to handle this file type.
| import React from 'react'
| import ReactDOM from 'react-dom'
| ReactDOM.render(<div>hello webpack</div>, document.body)
|
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./src
ℹ 「wdm」: Failed to compile.

報錯。爲何?由於咱們寫了 JSX 語法,webpack 不認識。怎麼辦,找 babel-loader 來翻譯。

$ npm install -D "babel-loader@^8.0.0-beta" @babel/core @babel/preset-react

接着重啓 webpack-dev-server:

$ npx webpack-dev-server --mode development --content-base ./dist --module-bind 'js=babel-loader?presets[]=@babel/preset-react'

再也不報錯,且頁面上已經顯示 hello webpack

webpack 配置文件

隨着咱們配置的插件愈來愈多,命令行會愈來愈長,這時,咱們能夠將命令行參數移入 webpack 配置文件。

咱們來新建一個 webpack.config.js 文件:

const path = require('path') module.exports = { mode: 'development', devServer: { contentBase: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, include: [ path.resolve(__dirname, 'src') ], loader: 'babel-loader', options: { presets: ['@babel/preset-react'] } } ] } } 

這樣,咱們只要執行 npx webpack-dev-server 就行,再也不須要輸入一長串命令行。

圖片加載器

咱們須要一張圖片,我從 unsplash 找來了一張玫瑰,放到 src/img/rose.jpg 位置。

咱們在 src/index.js 中 import 它:

import ReactDOM from 'react-dom' +import Rose from './img/rose.jpg' ReactDOM.render(<div>hello webpack</div>, document.body) 

不出意外,報錯了:

[./src/img/rose.jpg] 177 bytes {main} [built] [failed] [1 error]
[./src/index.js] 178 bytes {main} [built]
    + 46 hidden modules

ERROR in ./src/img/rose.jpg
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type.
(Source code omitted for this binary file)
 @ ./src/index.js 3:0-34
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./src
ℹ 「wdm」: Failed to compile.

和處理 JSX 一個道理,咱們須要翻譯(即加載器)。

在 webpack 裏,負責圖片翻譯的是 file-loader

$ npm install -D file-loader

接着是配置 webpack.config.js

+      },
+      {
+        test: /\.(png|jpg|gif)$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {}
+          }
+        ]
       }
     ]

重啓 webpack-dev-server,發現 webpack 已經能正常編譯了 - 圖片搖身一變,也是一個模塊。

並且,webpack 在最終構建時,會自動將模塊中引用的圖片拷貝到相應目錄 - 謝天謝地,不再用手動拷貝。

確保圖片加載沒問題後,咱們來完善下代碼:

-ReactDOM.render(<div>hello webpack</div>, document.body) +class App extends React.Component { + render () { + return ( + <div><img src={Rose} alt='玫瑰' /></div> + ) + } +} +ReactDOM.render(<App />, document.body) 

查看瀏覽器,圖片已經渲染出來 - 可是圖片太大了。咱們須要 CSS 來控制圖片尺寸。

加載 CSS 文件

在 React.js 裏,CSS 有不少種寫法,好比咱們能夠直接寫在 style 中:

<img src={Rose} alt='玫瑰' style={{maxWidth: 500}} /> 

由於這就是 JavaScript,咱們也就不須要額外處理。

但咱們也能夠寫在 css 文件中。

在 src 下新增 index.css

.flower { max-width: 500px; } 

而後在 index.js 中引入並應用:

import React from 'react' import ReactDOM from 'react-dom' import Rose from './img/rose.jpg' +import './index.css' class App extends React.Component { render () { return ( - <div><img src={Rose} alt='玫瑰' /></div> + <div><img src={Rose} alt='玫瑰' className='flower' /></div> ) } } 

但 webpack-dev-server 又報錯了:

[./src/index.css] 166 bytes {main} [built] [failed] [1 error]
[./src/index.js] 391 bytes {main} [built]
    + 47 hidden modules

ERROR in ./src/index.css
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
| .flower {
|   max-width: 500px;
| }
 @ ./src/index.js 4:0-21
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./src
ℹ 「wdm」: Failed to compile.

一樣的,咱們須要 CSS 加載器:

  1. css-loader - 預處理 CSS 文件
  2. style-loader - 將 CSS 插入到 DOM 中的 style 標籤

注意,咱們若是隻使用了 css-loader,則 webpack 只是將 CSS 文件預處理成模塊而後打包到構建文件中,並不會插入到頁面 - 這是 style-loader 的做用。

咱們先來安裝它們:

$ npm install -D css-loader style-loader

而後修改 webpack.config.js 配置:

+ }, + { + test: /\.css$/, + use: [{ loader: 'style-loader' }, { loader: 'css-loader' }] } ] } 

請注意,loader 的順序很重要,好比上面新增的這一節,若是把 style-loader 放到 css-loader 後面,咱們就會撞見錯誤:

ERROR in ./index.css
Module build failed: Unknown word (5:1)

  3 | // load the styles
  4 | var content = require("!!./index.css");
> 5 | if(typeof content === 'string') content = [[module.id, content, '']];
    | ^
  6 | // Prepare cssTransformation
  7 | var transform;
  8 |

 @ ./index.js 3:0-21
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./index.js
webpack: Failed to compile

由於 style-loader 沒法理解 CSS 文件,須要先經 css-loader 預處理 - 是的,加載器的執行順序是從後往前的。

重啓 webpack-dev-server,編譯正常,css 已生效。

只不過,這裏的 CSS 雖然是 import 進來的,但還是全局的,等效於咱們日常使用 <link href="" /> 引用 CSS。webpack 還提供 CSS Modules,能夠將樣式真正意義上的模塊化。

除了 CSS Modules 外,咱們還有不少 CSS in js 方案可選,它們容許咱們將樣式跟 HTML、JS 放到一塊兒 - 若是你寫過 Vue.js 的單文件模塊,可能會很喜歡。

在成功配置圖片與 CSS 後,咱們能夠繼續完善代碼:

src/index.css

+} +.flower--rotate { + transform: rotate(30deg); } 

src/index.js

class App extends React.Component { + state = { + reset: 'yes' + } + onClick = () => { + this.setState({ + reset: this.state.reset === 'yes' ? 'no' : 'yes' + }) + } render () { return ( - <div><img src={Rose} alt='玫瑰' className='flower' /></div> + <div><img src={Rose} alt='玫瑰' className={this.state.reset === 'yes' ? 'flower' : 'flower flower--rotate'} onClick={this.onClick} /></div> ) } } 

但隨即發現 webpack-dev-server 又拋出錯誤:

[./src/index.js] 1.49 KiB {main} [built] [failed] [1 error]
    + 25 hidden modules

ERROR in ./src/index.js
Module build failed: SyntaxError: /Users/sam/tmp/webpack-demo/src/index.js: Support for the experimental syntax 'classProperties' isn't currently enabled (6:9):

  4 | import './index.css'
  5 | class App extends React.Component {
> 6 |   state = {
    |         ^
  7 |     reset: 'yes'
  8 |   }
  9 |   onClick = () => {

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.
    at _class.raise (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:779:15)
    at _class.expectPlugin (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:2087:18)
    at _class.parseClassProperty (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4904:12)
    at _class.pushClassProperty (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4871:30)
    at _class.parseClassMemberWithIsStatic (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4804:14)
    at _class.parseClassMember (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4741:10)
    at _class.parseClassBody (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4696:12)
    at _class.parseClass (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:4646:10)
    at _class.parseStatementContent (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:3987:21)
    at _class.parseStatement (/Users/sam/tmp/webpack-demo/node_modules/babylon/lib/index.js:3959:17)
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./src
ℹ 「wdm」: Failed to compile.

這是由於咱們用了 JavaScript 的新特性 - 須要 @babel/plugin-proposal-class-properties 插件的支持。

首先是安裝該插件:

$ npm i -D @babel/plugin-proposal-class-properties

而後調整 webpack.config.js 配置:

options: { presets: ['@babel/preset-react'] + plugins: ['@babel/plugin-proposal-class-properties'] } }, 

重啓 webpack-dev-server,編譯正常。

查看瀏覽器,圖片已經能夠點擊了。

打包

在完成項目開發後,咱們須要輸出文件給生產環境部署,只要執行:

$ npx webpack --mode production

就能夠打包出全部靜態資源。

部署

部署時,拷貝 dist 目錄便可。

清理 dist

隨着某些文件的增刪,咱們的 dist 目錄下會產生一些再也不使用的文件,咱們不想這些文件也部署到生產環境上佔用空間,因此 webpack 在打包前最好能刪除 dist 目錄。

咱們來試試 clean-webpack-plugin

首先是安裝:

$ npm i -D clean-webpack-plugin

而後在 webpack.config.js 中調用:

const path = require('path') +const CleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { mode: 'development', devServer: { contentBase: path.resolve(__dirname, 'dist') }, + plugins: [ + new CleanWebpackPlugin(['dist']) + ], module: { 

再執行 npx webpack --mode production,webpack 確實會在打包前清空 dist 目錄,但咱們的 index.html 也一塊兒被清空了。

html-webpack-plugin

咱們使用 html-webpack-plugin 來自動生成 index.html

首先是安裝:

$ npm i --save-dev html-webpack-plugin

調整 webpack.config.js

const CleanWebpackPlugin = require('clean-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', devServer: { contentBase: path.resolve(__dirname, 'dist') }, plugins: [ - new CleanWebpackPlugin(['dist']) + new CleanWebpackPlugin(['dist']), + new HtmlWebpackPlugin() ], 

再運行 npx webpack --mode productiondist 下已經自動生成 index.html,再 title 倒是 Webpack App,咱們須要再調整一下 webpack.config.js

plugins: [ new CleanWebpackPlugin(['dist']), - new HtmlWebpackPlugin() + new HtmlWebpackPlugin({ + title: 'webpack 教程' + }) ], 

腳手架

前面的步驟裏,咱們幾乎是一步、一步手動配置每一個類型文件的加載器,一次添加一小節,而後重啓 webpack-dev-server,恐怕沒人喜歡這樣幹活。因此市面上有很是多的 boilerplates、presets 等,其中比較出名的有:

  1. create-react-app react 官方出品的一套,只適用開發 react.js 項目;
  2. neutrino.js 這是 Mozilla 出品的一套解決方案,Web、React、Node.js 等方案均有;
  3. Parcel 最近新出的一套方案,零配置。
Copyright © 陳三
Made with  okmarvin &  @okmarvin/january
 
 
原文地址:https://blog.zfanw.com/webpack-tutorial/
相關文章
相關標籤/搜索