[翻譯]阮一峰webpack教程(Demo集合)

愛吃叉燒發表於2018-07-09

翻譯它主要是用於學習Webpack,原地址為github.com/ruanyf/webp…

安裝使用指南

首先,全域性安裝Webpack和webpack-dev-server

$ npm i -g webpack webpack-dev-server
複製程式碼

然後克隆clone阮一峰的倉庫

$ git clone https://github.com/ruanyf/webpack-demos.git
複製程式碼

安裝依賴

$ cd webpack-demos
$ npm install
複製程式碼

現在開始進入demo*目錄並且執行它們

$ cd demo01
$ npm run dev
複製程式碼

上面的程式碼不會自動的開啟你的瀏覽器,需要手動訪問http://127.0.0.1:8080

前言:Webpack是什麼

Webpack用於構建Javascript模組指令碼來給瀏覽器使用的前端工具。
它和Browserify很像,但是能做更多的事~

$ browserify main.js > bundle.js
# 上下程式碼作用相同
$ webpack main.js bundle.js
複製程式碼

Webpack需要一個名為webpack.config.js的配置檔案,這個檔案就是一個CommonJS的模組(module)

// webpack.config.js的內容
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};
複製程式碼

當建好webpack.config.js後就能直接執行Webpack,而不加引數

$ webpack
複製程式碼

有一些必須知道的引數選項如下

  • webpack——開發時的構建命令
  • webpack -p——釋出產品時的構建命令
  • webpack --watch——用於增量開發的構建
  • webpack -d——包括source maps
  • webpack --colors——讓構建輸出更好看

可以在定製webpack.config.js中的scripts選項,如下所示

// package.json
{
  // ...
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors",
    "deploy": "NODE_ENV=production webpack -p"
  },
  // ...
}
複製程式碼

Demo01:入口檔案

入口檔案用來Webpack讀取它然後構建bundle.js
例如下面,main.js就是一個入口檔案

// main.js
document.write('<h1>Hello World</h1>');
複製程式碼

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>
複製程式碼

Webpack依據webpack.config.js來構建bundle.js

// webpack.config.js
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};
複製程式碼

執行下面的命令然後訪問http://127.0.0.1:8080

$ cd demo01
$ npm run dev
複製程式碼

Demo02:多個入口檔案

Webpack允許多個入口檔案存在,在多頁面的app中很有用,每個頁面有不同的入口檔案。

// main1.js
document.write('<h1>Hello World</h1>');

// main2.js
document.write('<h2>Hello Webpack</h2>');
複製程式碼

index.html

<html>
  <body>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>
複製程式碼

webpack.config.js

module.exports = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js'
  },
  output: {
    filename: '[name].js'
  }
};
複製程式碼

Demo03:Babel-loader

載入器(Loaders)是一些前處理器,用於在Webpack的構建過程前,將你app裡的一些資原始檔進行轉換。
例如,Babel-loader能夠將JSX/ES6檔案轉為普通的JS檔案,之後Webpack能夠開始構建它們。Webpack官方文件有一個載入器的列表地址
main.jsx是一個JSX檔案

// main.jsx
const React = require('react');
const ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.querySelector('#wrapper')
);
複製程式碼

index.html

<html>
  <body>
    <div id="wrapper"></div>
    <script src="bundle.js"></script>
  </body>
</html>
複製程式碼

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      }
    ]
  }
};
複製程式碼

在上面的程式碼裡需要babel-loader的兩個外掛babel-preset-es2015和babel-preset-react來轉換ES6和React(翻譯:現在為廢棄了,詳情)

Demo04:CSS-loader

Webpack允許在JS檔案中包含CSS,需要CSS-loader對這些CSS進行處理
main.js

require('./app.css');
複製程式碼

app.css

body {
  background-color: blue;
}
複製程式碼

index.html

<html>
  <head>
    <script type="text/javascript" src="bundle.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>
複製程式碼

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
    ]
  }
};
複製程式碼

注意!必須使用兩個載入器來轉換CSS檔案。CSS-loader用來讀取CSS檔案來轉換,另一個Style-loader用來往HTML中插入<style>標籤。
然後開啟server

$ cd demo04
$ npm run dev
複製程式碼

事實上,Webpack將CSS檔案的內容直接插入到index.html

<head>
  <script type="text/javascript" src="bundle.js"></script>
  <style type="text/css">
    body {
      background-color: blue;
    }
  </style>
</head>
複製程式碼

Demo5: Image loader

Webpack能夠將圖片包含進JS檔案中
main.js

var img1 = document.createElement("img");
img1.src = require("./small.png");
document.body.appendChild(img1);

var img2 = document.createElement("img");
img2.src = require("./big.png");
document.body.appendChild(img2);
複製程式碼

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>
複製程式碼

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.(png|jpg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  }
};
複製程式碼

url-loader將image檔案轉為<img>標籤,如果圖片大小小魚8192位元組,它將轉換為Data url(翻譯:圖片變為Base64編碼,減少請求次數),否則,他將轉為普通檔案URL。

<img src="data:image/png;base64,iVBOR...uQmCC">
<img src="4853ca667a2b8b8844eb2693ac1b2578.png">
複製程式碼

Demo06:CSS Module

css-loader?modules(請求引數為modules)能夠使用CSS Module,CSS Module帶給你的JS檔案模組中的CSS一個區域性作用域,也可以使用:global(selector)讓CSS變為全域性作用。
index.html

<html>
<body>
  <h1 class="h1">Hello World</h1>
  <h2 class="h2">Hello Webpack</h2>
  <div id="example"></div>
  <script src="./bundle.js"></script>
</body>
</html>
複製程式碼

app.css

/* local scope */
.h1 {
  color:red;
}

/* global scope */
:global(.h2) {
  color: blue;
}
複製程式碼

main.jsx

var React = require('react');
var ReactDOM = require('react-dom');
var style = require('./app.css');

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello World</h1>
    <h2 className="h2">Hello Webpack</h2>
  </div>,
  document.getElementById('example')
);
複製程式碼

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
             loader: 'css-loader',
             options: {
               modules: true
             }
          }
        ]
      }
    ]
  }
};
複製程式碼

訪問 http://127.0.0.1:8080會看到只有h1是紅色的,因為他的CSS是區域性作用域,然後所以h2都是藍色的,因為它是全域性作用域。

Demo07:UglifyJs Plugin

Webpack用外掛系統來擴充套件他的功能。例如,UglifyJs Plugin,它用來壓縮JS程式碼,使得JS檔案體積變小。
main.js

var longVariableName = 'Hello';
longVariableName += ' World';
document.write('<h1>' + longVariableName + '</h1>');
複製程式碼

index.html

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>
複製程式碼

webpack.config.js

var webpack = require('webpack');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new UglifyJsPlugin()
  ]
};
複製程式碼

在訪問伺服器後,可以看到main.js最小化為如下程式碼:

var o="Hello";o+=" World",document.write("<h1>"+o+"</h1>")
複製程式碼

(翻譯:是在bundle.js的最後幾個程式碼那,是這些程式碼)

Demo08:HTML Webpack Plugin和Open Browser Webpack Plugin

這個demo用來展示如何載入第三方外掛
html-webpack-plugin能為你建立index.htmlopen-browser-webpack-plugin能夠在Webpack載入時開啟一個新的瀏覽器標籤(tab)
main.js

document.write('<h1>Hello World</h1>');
複製程式碼

webpack.config.js

var HtmlwebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlwebpackPlugin({
      title: 'Webpack-demos',
      filename: 'index.html'
    }),
    new OpenBrowserPlugin({
      url: 'http://localhost:8080'
    })
  ]
};
複製程式碼

現在你不用手動建立index.html也不用手動開啟瀏覽器了。

Demo09:環境變數(Environment flags)

使用環境變數讓一些程式碼只能在開發環境時使用。
main.js

document.write('<h1>Hello World</h1>');

if (__DEV__) {
  document.write(new Date());
}
複製程式碼

index.html

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>
複製程式碼

webpack.config.js

var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [devFlagPlugin]
};
複製程式碼

現在傳遞環境變數給Webpack。開啟demo09/package.json,找到如下scripts選項

// package.json
{
  // ...
  "scripts": {
    "dev": "cross-env DEBUG=true webpack-dev-server --open",
  },
  // ...
}
複製程式碼

Demo10:程式碼分離

在大型web應用中,將所有程式碼放入一個檔案是十分低效的。Webpack允許你將大型JS檔案分成多塊。特別的,如果一些程式碼塊只是在某些情況下需要,那麼這些程式碼塊會按需載入。
Webpack使用require.ensure來定義一個分割點

// main.js
require.ensure(['./a'], function (require) {
  var content = require('./a');             
  document.open();
  document.write('<h1>' + content + '</h1>');
  document.close();
});
複製程式碼

require.ensure告訴Webpack ./a,js需要從bundle.js中分離出來作為一個單獨的塊檔案

// a.js
module.exports = 'Hello World';
複製程式碼

現在Webpack關心依賴、輸出檔案、執行時的東西。你不必將多餘的東西放到index.htmlwebpack.config.js
index.html

<html>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>
複製程式碼


webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};
複製程式碼

訪問伺服器後,你感覺不到任何不同。實際上,Webpack將構建main.jsa.js到不同的塊中(bundle.js0.bundle.js),然後從bundle.js中按需載入0.bundle.js

Demo11:bundle-loader下的程式碼分離

另一個程式碼分割的方式是bundle-loader

// main.js

// Now a.js is requested, it will be bundled into another file
var load = require('bundle-loader!./a.js');

// To wait until a.js is available (and get the exports)
//  you need to async wait for it.
load(function(file) {
  document.open();
  document.write('<h1>' + file + '</h1>');
  document.close();
});
複製程式碼

require('bundle-loader!./a.js')告訴Webpack從其他塊載入a.js
現在main.js構建為bundle.jsa.js構建為0.bundle.js

Demo12:通用塊

在多個JS指令碼中有通用塊,通過CommonsChunkPlugin你能提取不同檔案中的通用部分,對於瀏覽器快取來節省頻寬是非常有用的。

// main1.jsx
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>Hello World</h1>,
  document.getElementById('a')
);

// main2.jsx
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h2>Hello Webpack</h2>,
  document.getElementById('b')
);
複製程式碼

index.html

<html>
  <body>
    <div id="a"></div>
    <div id="b"></div>
    <script src="commons.js"></script>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>
複製程式碼

上面的commons.jsmain1.jsxmain2.jsx的通用部分。正如你想的,commons.js包括reactreact-dom
Webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    bundle1: './main1.jsx',
    bundle2: './main2.jsx'
  },
  output: {
    filename: '[name].js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "commons",
      // (the commons chunk name)

      filename: "commons.js",
      // (the filename of the commons chunk)
    })
  ]
}
複製程式碼

Demo13:Vendor chunk

通過CommonsChunkPlugin,你能從JS指令碼提取官方庫到單獨的檔案中
main.js

var $ = require('jquery');
$('h1').text('Hello World');
複製程式碼

index.html

<html>
  <body>
    <h1></h1>
    <script src="vendor.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>
複製程式碼

webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js',
    vendor: ['jquery'],
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.js'
    })
  ]
};
複製程式碼

在上面的程式碼中,entry.vendor:['jquery']告訴Webpack,jquery應該被包括到通用塊vendor.js中。
如果你想要一個模組作為全域性變數來在不同的模組中使用,比如不用在每個檔案中require('jquery'),而是讓$或者jQuery作為全域性變數,需要使用ProvidePlugin,它能夠自動載入模組而不需要到處import或者是require

// main.js
$('h1').text('Hello World');


// webpack.config.js
var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js'
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
};
複製程式碼

當然,在Demo13中,你需要自己全域性載入jquery.js

Demo14:公開全域性變數

如果你想用一些全域性變數,不需要在Webpack包中包含他們,你可以使用webpack.config.js裡的externals欄位
例如,我們有一個data.js

// data.js
var data = 'Hello World';
複製程式碼

index.html

<html>
  <body>
    <script src="data.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>
複製程式碼

注意,Webpack只會構建bundle.js, 而不會構建data.js
我們可以把data作為全域性變數

// webpack.config.js
module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules:[
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
    ]
  },
  externals: {
    // require('data') is external and available
    //  on the global var data
    'data': 'data'
  }
};
複製程式碼

此時,你可以require('data')作為模組變數,實際上它是一個全域性變數

// main.jsx
var data = require('data');
var React = require('react');
var ReactDOM = require('react-dom');

ReactDOM.render(
  <h1>{data}</h1>,
  document.body
);
複製程式碼

同樣可以將reactreact-dom放入externals,這樣顯著的降低構建bundle.js的時間和檔案大小

Demo15:React router

這個Demo使用Webpack來構建React router的官方樣例
先想象一個有控制板、收件箱、日曆的小應用

+---------------------------------------------------------+
| +---------+ +-------+ +--------+                        |
| |Dashboard| | Inbox | |Calendar|      Logged in as Jane |
| +---------+ +-------+ +--------+                        |
+---------------------------------------------------------+
|                                                         |
|                        Dashboard                        |
|                                                         |
|                                                         |
|   +---------------------+    +----------------------+   |
|   |                     |    |                      |   |
|   | +              +    |    +--------->            |   |
|   | |              |    |    |                      |   |
|   | |   +          |    |    +------------->        |   |
|   | |   |    +     |    |    |                      |   |
|   | |   |    |     |    |    |                      |   |
|   +-+---+----+-----+----+    +----------------------+   |
|                                                         |
+---------------------------------------------------------+

複製程式碼

webpack.config.js

module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
    ]
  }
};
複製程式碼

index.js

import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';

import './app.css';

class App extends React.Component {
  render() {
    return (
      <div>
        <header>
          <ul>
            <li><Link to="/app">Dashboard</Link></li>
            <li><Link to="/inbox">Inbox</Link></li>
            <li><Link to="/calendar">Calendar</Link></li>
          </ul>
          Logged in as Jane
        </header>
        <main>
          <Switch>
            <Route exact path="/" component={Dashboard}/>
            <Route path="/app" component={Dashboard}/>
            <Route path="/inbox" component={Inbox}/>
            <Route path="/calendar" component={Calendar}/>
            <Route path="*" component={Dashboard}/>
          </Switch>
        </main>
      </div>
    );
  }
};

class Dashboard extends React.Component {
  render() {
    return (
      <div>
        <p>Dashboard</p>
      </div>
    );
  }
};

class Inbox extends React.Component {
  render() {
    return (
      <div>
        <p>Inbox</p>
      </div>
    );
  }
};

class Calendar extends React.Component {
  render() {
    return (
      <div>
        <p>Calendar</p>
      </div>
    );
  }
};

render((
  <BrowserRouter>
    <Route path="/" component={App} />
  </BrowserRouter>
), document.querySelector('#app'));
複製程式碼

index.html

<html>
  <body>
    <div id="app"></div>
    <script src="/bundle.js"></script>
  </body>
</htmL>

複製程式碼

然後訪問http://127.0.0.1:8080

相關文章