Webpack 入門指迷

jopen 8年前發布 | 34K 次閱讀 前端技術 webpack

大概算是一份教程吧, 只不過效果肯定不如視頻演示之類的好..
Webpack 最近在英文社區上經常看到, 留了心, 但進一步了解是通過下邊的視頻:
視頻: How Instagram.com Works, Peter Hunt
Peter Hunt 也是 React 的傳教士, 我由于對 React 的關注因此細看了視頻
再后來是出現 React Hot Loader 這樣的開發神器, 我認為 Webpack 應該很棒
http://gaearon.github.io/react-hot-loader/
為了解決簡聊當中一些問題, 我消耗了很多時間了解 Webpack, 整理在這里

Webpack 是什么

https://github.com/webpack
Webpack 是德國開發者 Tobias Koppers 開發的模塊加載器
Instagram 工程師認為這個方案很棒, 似乎還把作者招過去了
在 Webpack 當中, 所有的資源都被當作是模塊, js, css, 圖片等等..
因此, Webpack 當中 js 可以引用 css, css 中可以嵌入圖片 dataUrl

對應各種不同文件類型的資源, Webpack 有對應的模塊 loader
比如 CoffeeScript 用的是 coffee-loader, 其他還有很多:
http://webpack.github.io/docs/list-of-loaders.html
大致的寫法也就這樣子:

  module: {
    loaders: [
      { test: /\.coffee$/, loader: 'coffee-loader' },
      { test: /\.js$/, loader: 'jsx-loader?harmony' } // loaders can take parameters as a querystring
    ]
  },

CommonJS 與 AMD 支持

Webpack 對 CommonJS 的 AMD 的語法做了兼容, 方便遷移代碼
不過實際上, 引用模塊的規則是依據 CommonJS 來的

jsrequire('lodash') // 從模塊目錄查找
require('./file') // 按相對路徑查找

AMD 語法中, 也要注意, 是按 CommonJS 的方案查找的

coffeedefine (require, exports. module) ->
  require('lodash') # commonjs 當中這樣是查找模塊的
  require('./file')

特殊模塊的 Shim

比如某個模塊依賴 window.jQuery, 需要從 npm 模塊中將 jquery 掛載到全局
Webpack 有不少的 Shim 的模塊, 比如 expose-loader 用于解決這個問題
https://github.com/webpack/docs/wiki/shimming-modules
其他比如從模塊中導出變量...具體說明有點晦澀..

手頭的兩個例子, 比如我們用到 Pen 這個模塊,
這個模塊對依賴一個 window.jQuery, 可我手頭的 jQuery 是 CommonJS 語法的
Pen 對象又是生成好了綁在全局的, 可是我又需要通過 require('pen') 獲取變量
最終的寫法就是做 Shim 處理直接提供支持:

js{test: require.resolve('jquery'), loader: 'expose?jQuery'},
{test: require.resolve('pen'), loader: 'exports?window.Pen'},

基本的使用

安裝 webpack 模塊之后, 可是使用 webpack 這個命令行工具
可以使用參數, 也可以配置 webpack.config.js 文件直接運行 webpack 調用
建議按照 Peter Hunt 給的教程走一遍, 基本的功能都會用到了
https://github.com/petehunt/webpack-howto

簡單的例子就是這樣一個文件, 可以把 ./main.js 作為入口打包 bundle.js:

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

查找依賴

Webpack 是類似 Browserify 那樣在本地按目錄對依賴進行查找的
可以構造一個例子, 用 --display-error-details 查看查找過程,
例子當中 resolve.extensions 用于指明程序自動補全識別哪些后綴,
注意一下, extensions 第一個是空字符串! 對應不需要后綴的情況.

js// webpack.config.js
module.exports = {
  entry: './a.js',
  output: {
    filename: 'b.js'
  },
  resolve: {
    extensions: ['', '.coffee', '.js']
  }
}
js// a.js
require('./c')
?? webpack --display-error-details
Hash: e38f7089c39a1cf34032
Version: webpack 1.5.3
Time: 54ms
Asset  Size  Chunks             Chunk Names
 b.js  1646       0  [emitted]  main
   [0] ./a.js 15 {0} [built] [1 error]

ERROR in ./a.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./c in /Users/chen/Drafts/webpack/details
resolve file
  /Users/chen/Drafts/webpack/details/c doesn't exist
  /Users/chen/Drafts/webpack/details/c.coffee doesn't exist
  /Users/chen/Drafts/webpack/details/c.js doesn't exist
resolve directory
  /Users/chen/Drafts/webpack/details/c doesn't exist (directory default file)
  /Users/chen/Drafts/webpack/details/c/package.json doesn't exist (directory description file)
[/Users/chen/Drafts/webpack/details/c]
[/Users/chen/Drafts/webpack/details/c.coffee]
[/Users/chen/Drafts/webpack/details/c.js]
 @ ./a.js 2:0-14

./c 是不存在, 從這個錯誤信息當中我們大致能了解 Webpack 是怎樣查找的
大概就是會嘗試各種文件名, 會嘗試作為模塊, 等等
一般模塊就是查找 node_modules, 但這個也是能被配置的:
http://webpack.github.io/docs/configuration.html#resolve-modulesdirectories

CSS 及圖片的引用

英文的教程上有明確的例子:
https://github.com/petehunt/webpack-howto#5-stylesheets-and-images

jsrequire('./bootstrap.css');
require('./myapp.less');

var img = document.createElement('img');
img.src = require('./glyph.png');

上邊的是 JavaScript 代碼, CSS 跟 LESS, 還有圖片, 被直接引用了
實際上 CSS 被轉化為 <style> 標簽, 而圖片可能被轉化成 base64 格式的 dataUrl
但是要主要在 webpack.config.js 文件寫好對應的 loader:

js// webpack.config.js
module.exports = {
  entry: './main.js',
  output: {
    path: './build', // This is where images AND js will go
    publicPath: 'http://mycdn.com/', // This is used to generate URLs to e.g. images
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
      { test: /\.css$/, loader: 'style-loader!css-loader' },
      {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} // inline base64 URLs for <=8k images, direct URLs for the rest
    ]
  }
};

url-loader

稍微啰嗦一下這個 loader, 這個 loader 實際上是對 file-loader 的封裝
https://github.com/webpack/url-loader
比如 CSS 文件當中有這樣的引用:

css.demo {
  background-image: url('a.png');
}

那么對應這樣的 loader 配置就能把 a.png 抓出來,
并且按照文件大小, 或者轉化為 base64, 或者單獨作為文件:

module: {
  loaders: [
    {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} // inline base64 URLs for <=8k images, direct URLs for the rest
  ]
}

上邊 ? 后邊的 query 有兩種寫法, 可以看下文檔:
http://webpack.github.io/docs/using-loaders.html#query-parameters

  • file-loader

由于 url-loader 是對 file-loader 的一個封裝, 以因此帶有后者一些功能:
https://github.com/webpack/file-loader
比如說, file-loader 有不弱的定義文件名的功能

jsrequire("file?name=[path][name].[ext]?[hash]!./dir/file.png")

對應 url-loader 當中如果文件超出體積, 就給一個這樣的文件名..

打成多個包

有時考慮類庫代碼的緩存, 我們會考慮打成多個包, 這樣不難
比如下邊的配置, 首先 entry 有多個屬性, 對應多個 JavaScript 包,
然后 commonsPlugin 可以用于分析模塊的共用代碼, 單獨打一個包出來:
https://github.com/petehunt/webpack-howto#8-optimizing-common-code
https://github.com/webpack/docs/wiki/optimization#multi-page-app

js// webpack.config.js
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');

module.exports = {
  entry: {
    Profile: './profile.js',
    Feed: './feed.js'
  },
  output: {
    path: 'build',
    filename: '[name].js' // Template based on keys in entry above
  },
  plugins: [commonsPlugin]
};

對文件做 revision

這個在文檔上做了說明, 可以自動生成 js 文件的 Hash:
http://webpack.github.io/docs/long-term-caching.html

jsoutput: { chunkFilename: "[chunkhash].bundle.js" }
jsplugins: [
  function() {
    this.plugin("done", function(stats) {
      require("fs").writeFileSync(
        path.join(__dirname, "...", "stats.json"),
        JSON.stringify(stats.toJson()));
    });
  }
]

同時, 可以注冊事件, 拿到生成的帶 Hash 的文件的一個表
但是拿到那個表之后, 就需要自己寫代碼進行替換了.. 這有點麻煩
官網的 Issue 里提到個辦法是生成 HTML 時引用 stats.json 的數據,
我此前的方案是生成 HTML 之后再進行替換, 相對賴上生成時寫入更好一些

上線

  • 另一份配置文件

webpack --config webpack.min.js 指定另一個名字的配置文件
這個文件當中可以寫不一樣配置, 專門用于代碼上線時的操作

  • 壓縮 JavaScript

因為代碼都是 JavaScript, 所以壓縮就很簡單了, 加上一行 plugin 就好了
http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin

jsplugins: [
   new webpack.optimize.MinChunkSizePlugin(minSize)
]
  • 壓縮 React

React 官方提供的代碼是已經合并的, 這個是 Webpack 不推薦的用法,
在合并話的代碼上進行定制有點麻煩, Webpack 提供了設置環境變量來優化代碼的方案:

jsnew webpack.DefinePlugin({
  "process.env": {
    NODE_ENV: JSON.stringify("production")
  }
})

https://github.com/webpack/webpack/issues/292#issuecomment-44804366

  • CDN

替換 CDN 這個工作, Webpack 也內置了, 設置 output.publicPath 即可
http://webpack.github.io/docs/configuration.html#output-publicpath

代碼熱替換

雖然文檔上寫得挺復雜的, 但如果只是簡單的功能還是很容易的

  1. 第一步, 把 'webpack/hot/dev-server' 加入到打包的代碼當中,
    這個是對應 node_modules/webpack/ 目錄當中的文件的:
js  entry: {
    main: ['webpack/hot/dev-server', './main'],
    vendor: ['lodash', './styles']
  },
  1. 啟動服務器, 比如我是這樣子的
bashwebpack-dev-server --hot --quiet

正常可以看到提示說服務器已經起來了
http://localhost:8080/webpack-dev-server/
如果有 index.html 的話, 直接訪問網址應該就能開始調試了

React Hot Replace

調試 React 的話, 有這樣的工具簡直是神器了, 甚至不用刷新頁面!
http://gaearon.github.io/react-hot-loader/getstarted/

entry: [
  'webpack-dev-server/client?http://0.0.0.0:8080', // WebpackDevServer host and port
  'webpack/hot/only-dev-server',
  './scripts/index' // Your app?s entry point
]

我特意問了下作者為什么上邊配置看起來不一樣..
https://github.com/gaearon/react-hot-loader/issues/73#issuecomment-73679446
回復大致說是為了避免自動的強制刷新他用了特別的寫法..
關于這項功能具體如何實現, 我沒有深入了解過...

hot replace 非靜態的網頁

上邊 localhost:8080 的方案并不適合復雜的頁面,
于是文檔上給出了一套稍微復雜一些的方案, 用來配合其他的服務器調試
大致的思路是這樣的:

  1. Webpack 打包生成的那些靜態資源用服務器 A 進行 serve
    這里說的 A 就是上邊說的這個:
webpack-dev-server --hot --quiet
  1. 我們的 HTML 由 B 渲染, B 會引用 A serve 的靜態資源
    B 生成的頁面當中加上類似這樣的代碼:
<script src="http://<A 的地址>/assets/bundle.js">

還可能要設置一下 output.publicPath, 把所有靜態資源指向 A
3. 文件修改時, webpack-dev-server 通過 socket.io 通知客戶端更新

這個步驟在文檔上寫得有點難懂, 大概要多嘗試幾次才行, 我也弄錯很多次
http://webpack.github.io/docs/webpack-dev-server.html

單獨打包 CSS

因為公司里有這個需要求, 強制把 CSS 從 js 文件當中獨立出來.
官方文檔是以插件的形式做的:
http://webpack.github.io/docs/stylesheets.html#separate-css-bundle
參考文檔但是注意一下函數參數, 第一第二個參數是有區別的, 比如這樣用:

jsExtractTextPlugin.extract('style-loader', 'css!less')

第一個參數是編譯好以后的代碼用的, 第二個參數是編譯到源代碼用的.. 有點難懂..

感想

Webpack 的報錯挺不友好的, 最初的時候我看著模塊找不到沒法搞明白
這種時候把中間過程打印出來看是不錯的選擇:

webpack --display-error-details

另一個報錯是沒有對應 loader 的提示.. log 可能很長找不到重點
我建議是先自己去想想什么地方需要考慮 loader 吧... 可能就知道了
我還遇到就是源碼里有使用 dataUrl 導致報錯... 確實奇怪了

不說這些坑的話, Webpack 我認為是我目前接觸到最好的前端開發方案
很多功能之前 FIS 文檔上看到過, 但 FIS 相對重一些我始終沒上手
而 Webpack 一上來就繞過了此前公司用 RequireJS 打包時遇到的各種問題

如果去掃 Webpack 的文檔的話, 還有很多功能我完全沒涉及到..
http://webpack.github.io/docs/

來自: http://segmentfault.com/a/1190000002551952

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!