Vue(八):webpack

webpack

webpack 是一個打包器。

什麼是打包器?就是爲了把 node.js 引入的模塊方案讓前端也可以用上,作爲打包器的 webpack 存在的目的,就是把模塊方案編譯爲前端瀏覽器可以識別的格式。

webpack模塊化方案

文件 b 引出一個函數 b:

// b.js
exports.b = function b() {
    console.log("b");
}

文件 a 引入此模塊,並調用模塊的引出函數 b:

// a.js
var b = require("./b.js");
b.b();

調用並查看輸出:

$node a.js
b

這樣的開發套路(創建並引入模塊)很常見。然而,這麼簡單好用的模塊方案在前端卻並不存在!但是有了 webpack 就可以了,要做的就是使用 webpack 對以上代碼做一次編譯:

$webpack a.js magic.js

然後,使用 html 引入它:

<html>
  <body>
    <script type="text/javascript" src="magic.js"></script>
  </body>
</html>

打開瀏覽器訪問此文件,就可以看到在瀏覽器的控制檯內輸出了 b

模塊是一個古老的分而治之的技術,從結構化編程範式開始就有了。然而:

  • 一方面,js 在語言層面,在客戶端是不支持的,它必須靠外在的 html 標籤 <script> 來實現粗淺的、僅僅能用的模塊。
  • 另外一個方面,js 因爲語言的柔性,卻是有可能實現自己的相對更好的模塊,包括變量和函數的局部化等。

當然,在使用命令行指定編譯參數之外,更好的習慣是做一個配置文件:

// webpack.config.js
module.exports = {
  entry: './a.js',
  output: {
    filename: 'bundle.js'
  }
};

有了它,程序員就不必每次敲入 webpack a.js magic.js ,而只要 webpack 即可。配置文件略囉嗦,但是可以看出來就是替代了本有的 webpack 的命令行參數,然後各就各位。當執行 webpack 時:

$ webpack
Hash: ed9f2c850698ca3d8863
Version: webpack 1.13.1
Time: 51ms
    Asset     Size  Chunks             Chunk Names
bundle.js  1.55 kB       0  [emitted]  main
   [0] ./a.js 31 bytes {0} [built]
   [1] ./b.js 45 bytes {0} [built]

輸出表明 a 文件,和它引入的 b 文件,都已經被轉譯完畢。轉譯到 bundle 文件內。

加載css

<!-- c.html -->
<html>
  <body>
    <div>Hello css</div>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

我們希望通過 css 來讓 div 變成紅色的字體,文件爲:

/* b.css  */
div {
    color:red;
}

我們只需要在 js 的入口文件內引用此 css:

require("./b.css")

並修改 webpack 的配置文件,以便通知 css 文件由 css-loader 加載,並由 style-loader 插入到 html 文件內:

// webpack.config.js
module.exports = {
  entry: './a.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.css$/, loader: 'style-loader!css-loader' },
    ]
  }
};

因爲需要引入模塊 css-loaderstyle-loader,我們需要安裝一下:

npm i css-loader style-loader --save-dev

隨後是熟悉的編譯命令:

webpack

現在工作全部做完,可以用瀏覽器打開文件 c.html,發現 html 內的文字變紅,說明 css 生效了。

加載svg

<!-- main.html -->
<html>
  <body>
    <div>Hello svg</div>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

svg 文件就是繪製了一個填充了黑色的圓(文件名爲100.svg):

<svg xmlns="http://www.w3.org/2000/svg"><circle cx="10" cy="10" r="10" fill="#000"/></svg>

依然在 js 的入口文件內引用此 svg:

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

並修改 webpack 的配置文件,加入一個新的 svg-url-loader(文件名 webpack.config.js):

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      {test: /\.svg/, loader: 'svg-url-loader?limit=1'},
    ]
  }
};

svg-url-loader 參數 limit 指明再小也得使用外部引用文件形式。

因爲需要引入模塊 svg-url-loader,安裝:

npm i svg-url-loader --save-dev

轉譯:

webpack

現在工作全部做完,可以用瀏覽器打開文件 main.html,發現圖片已經加入到頁面內了。

加載圖片

加載圖片也可以使用模塊方案,也就是 require 函數方式。

// main.js
var img1 = document.createElement("img");
img1.src = require("./small.png");
document.body.appendChild(img1);
<!-- 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: {
    loaders:[
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
    ]
  }
};

要想打包這個圖片,webpack 需要一個 loader 來轉換 png 文件。承擔此責任的就是 url-loader

它的參數 limit=8192 指明,如果圖片大小小於8192,就直接使用Data URL,否則就是正常的URL。
Data URL無需引入外部文件,而是把內容直接編碼在src屬性內,編碼格式爲base64。

安裝:

npm i url-loader --save-dev

打包:

webpack

隨後打開文件 index.html 。你可以看到瀏覽器內已經顯示了此圖片。說明打包成功。

對於 Data URL 有些好奇的人,可以看看生成的 bundle.js 文件的最後幾行,最後賦值給 img.src 屬性的,是類似這樣的數據:
"data:image/png;base64,iVBORw0....."


創建api-server

問題:服務端 api 代碼應該放置於何處?纔可以:

  • 在開發階段,繼續利用 webpack 的熱加載。
  • 在發佈階段,可以不必改變任何 api 代碼就可以繼續使用。
  • 這些代碼不應該在 dev-server.js 內修改或者添加,而最好獨立於 dev-server.js 存在。

答案是使用腳手架代碼中的 config/index.js 內的 proxyTable 屬性的配置,把到達 dev-server.jsapi 訪問轉發給我的 api server

🌰 一個 hello 組件,從服務器的 api/who 提取消息,並綁定到客戶端組件內。使用的技術如下:

  • vue-cli
  • express
  • vue-resource

首先,我們創建腳手架代碼:

vue init webpack helloapi   #此爲3.0腳手架前的版本
cd helloapi
npm i
npm run dev

此時可以看到瀏覽器打開,顯示我特別熟悉的 vue 默認的 html 頁面:

Welcome to Your Vue.js App

我們現在提供一個 api 實現(api server),爲默認的 vue 的歡迎頁面消息做一個修改,服務器端來提供它:

var express = require('express');
var app = express();
app.get('/api', function (req, res) {
  var j = {msg:'Hello From Server'}
  res.end(JSON.stringify(j));
})
var server = app.listen(8181, function () {
  var host = server.address().address
  var port = server.address().port
  console.log("listening at http://%s:%s", host, port)
})

客戶端需要安裝 vue-resource

npm i vue-resource --save

並在把 src/components/Hello.vue 替換爲如下代碼,以便實際發起 GET 請求:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'hello',
  data () {
    return {
      msg: 'Welcome'
    }
  },
  mounted(){
      this.$http.get('/api').then((response) => {
        var j = JSON.parse(response.body)
        this.msg = j.msg
      }, (response) => {
        console.log('error',response)
      });
  }
}
</script>

src/main.js 內插入如下代碼,以便引入 vue-resource

import r from 'Vue-Resource'
Vue.use(r)

config/index.js 內添加代理轉發,把本來發給 dev-server.jsapi rul 轉發給我們的 api server

module.exports = {
  ..
  dev: {
    ...
    proxyTable: {
        '/api': 'http://localhost:8181',
    },
  }
}

啓動 api server:node server.js
啓動 dev-server.js: npm run dev

客戶端看到:

Hello From Server

這樣,開發階段我們已經做到了 apiserverdev-server.js 的代碼分離,並且繼續利用本有的熱加載能力。現在,我們需要驗證:如果發佈了此代碼,api server 代碼中和 api 有關的代碼,是否可以無絲毫修改就可以繼續複用。

首先,發佈當前代碼:

npm run build

該命令會創建一個 dist 目錄,內有編譯打包好的全部 js 代碼和資源代碼。儘管其中有 index.html,但是直接用瀏覽器打開是無效的。首先要啓動一個服務器,所有的資源文件必須通過瀏覽器發起,有服務器服務纔可以正常運行。我們可以稍稍修改api server,引入插件,讓此服務器除了提供 api 服務外,也可以對整個 dist 目錄提供服務。只要添加代碼:

var path = require('path')
var dist = path.join(__dirname, 'dist')
app.use('/',express.static(dist))

然後啓動服務:

node server.js

打開瀏覽器,訪問 http://localhost:8181,可以看到和 dev-server.js 下一樣的結果。

這說明,api server 可以在發佈後不做修改(修改時爲了提供服務靜態內容的能力,對於 api 提供者的代碼是不做修改的)繼續使用。

熱加載

使用 webpack 的模塊熱加載可以加快開發的速度。它無需刷新,只要修改了文件,客戶端就立刻做熱加載。

  • dev-middleware 的利用方法
  • HMR(webpack-hot-middleware) 的利用方法

這次提供熱加載的代碼共兩個文件(放置於src 內),a 依賴於 b,並調用 b 的引出函數:

// a.js
var b = require("./b.js")
b.b()

// b.js
exports.b = function b(){
    console.log("h")
}

首先我需要使用 dev-middleware 讓使用 require 函數成爲可能,其次我希望使用 HMR,當 b 文件內修改時,可以自動熱加載,而不是必須完整 reload 纔可以。

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

希望熱加載的代碼就是這樣了。目錄結構如下:

├── output
│   └── index.html
├── server.js
└── src
    ├── a.js
    └── b.js

其中的 server.js 在隨後創建。現在我們創建環境,讓它可以熱加載: 創建目錄環境的命令爲:

mkdir src
touch src/a.js
touch src/b.js
mkdir output
touch output/index.html
touch server.js

創建環境:

npm init -y
npm install express --save
npm install webpack webpack-dev-middleware webpack-hot-middleware --save-dev

創建服務器文件:
此服務器文件使用 express 創建服務器監聽,使用 dev 中間件,HMR 中間件:

var express = require('express')
var webpack = require('webpack')
var path = require('path')
var app = express()
var webpackMiddleware = require("webpack-dev-middleware");
var compiler = webpack({
    entry:
    ["./src/a.js",
    'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
    ],
    output: {
        path: path.resolve(__dirname, './output/'),
        filename:'bundle.js',
    },
    plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ]
})
var options ={
    publicPath: "/",
}
app.use(webpackMiddleware(compiler, options));
app.use(require("webpack-hot-middleware")(compiler));
app.use(express.static('output'))
app.listen(8080, function () {
  console.log('Example app listening on!')
})

其中,dev 中間件中涉及到的入口文件的做法,和一般的 webpack 做法一樣,但是多出一個 webpack-hot-middleware/client 文件,此文件用來傳遞到客戶端,並和服務器的 HMR 插件聯絡,聯絡的 URL 爲 path=/__webpack_hmr&amp;timeout=20000,其中 path 有HMR服務監聽,timeout:知道失聯的話,達到20000毫秒就算超時,不必再做嘗試。

爲了讓 HMR 知道 a、b 文件是可以熱加載的, a.js 得修改爲:

// a.js
var b = require("./b.js")
b.b()

if (module.hot) {
  module.hot.accept();
}

現在執行服務: node server.js

打開瀏覽器,訪問 localhost:8080 ,並打開 Chrome devtools,看到:

bundle.js:1916 h
bundle.js:1626 [HMR] connected

現在修改 b.js 內的字符串爲 hello HMR,看到 Console 輸出:

Hello HMR
bundle.js:1847 [HMR] Updated modules:
bundle.js:1849 [HMR]  - ./src/b.js
bundle.js:1849 [HMR]  - ./src/a.js
bundle.js:1854 [HMR] App is up to date.

就是說 HMR 已經激活。
ref : https://github.com/ahfarmer/webpack-hmr-starter-middleware




🔗:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章