摘要:現(xiàn)在來做一個的入口讓我們在文件里進行的配置。如果想要顯示它們,我們可以在運行的時候使用你還可以使用,在改變代碼的時候自動進行打包。新建文件,里面是一段,告訴使用進行預處理。
本文譯自:Webpack your bags
這篇文章由入門到深入的介紹了webpack的功能和使用技巧,真心值得一看。由于我英語水平有限,而且很少翻譯文章,所以文中的一些語句在翻譯時做了類似語義的轉換,望諒解。要是有幸被轉還是希望能夠注明啊
by the way,打個小廣告。。把自己的github扔這兒好了,有時候會更新些譯文或者筆記什么的
你可能已經(jīng)聽說過這個酷酷的工具-Webpack。一些人稱之為類似于Gulp的工具,還有一些人則認為它類似于Browserify。如果你還沒接觸過它,那很有可能會因此感到困惑。而Webpack的主頁上則認為它是兩者的結合,那或許更讓你困惑了。
說實話,一開始的時候,“什么是Webpack”這個話題讓我很心煩,也就沒有繼續(xù)研究下去了。直到后來,當我已經(jīng)構建了幾個項目后,才真心的為之癡迷。如果你像我一樣緊隨Javascript的發(fā)展步伐,你很有可能會因為太追隨潮流跨度太大而蛋疼。在經(jīng)歷了上面這些之后,我寫下這篇文章,以便更加細致的解釋Webpack是什么,以及它如此重要的原因。
Webpack是啥?首先來讓我們回答最開始的問題:Webpack是個系統(tǒng)的構建工具,還是打包工具?答案是兩者都是--這不代表它做了這兩件事(先構建資源,在分別進行打包),而是說它將兩者結合在一起了。
更加清晰的說明:與“構建sass文件,壓縮圖片,然后引用它們,再打包,再在頁面上引用”相比,你只要這么做:
import stylesheet from "styles/my-styles.scss"; import logo from "img/my-logo.svg"; import someTemplate from "html/some-template.html"; console.log(stylesheet); // "body{font-size:12px}" console.log(logo); // "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]" console.log(someTemplate) // "Hello
"
你的所有資源都被當做包處理,可以被import,修改,控制,最終展現(xiàn)在你最后的一個bundle上。
為了能讓上面那些有效運轉,你需要在自己的Webpage配置里配置loader。loader是一個“當程序遇見XXX類型文件的時候,就做YYY”的小型插件。來看一些loader的例子:
{ // 如果引用了 .ts 文件, 將會觸發(fā) Typescript loader test: /.ts/, loader: "typescript", }, { // 如果引用了png|jpg|svg圖片,則會用 image-webpack 進行壓縮 (wrapper around imagemin) // 并轉化成 data64 URL 格式 test: /.(png|jpg|svg)/, loaders: ["url", "image-webpack"], }, { // 如果使用了 SCSS files, 則會用 node-sass 解析, 最終返回CSS格式 test: /.scss/, loaders: ["css", "autoprefixer", "sass"], }
最終在食物鏈的最底端,所有的loader都返回string,這樣Webpack就可以將它們加入到javascript模塊中去。當你的Sass文件被loader轉換之后,它的引用實際上是這樣的:
export default "body{font-size:12px}";究竟為什么要這么做?
在你理解了Webpack是做什么的之后,第二個問題就接踵而至:使用它有什么好處?“把圖片和CSS扔進我的js里?什么鬼?”其實在很久之前,為了減少HTTP request請求,我們都被教育要把所有東西寫在一個文件里面。
到了現(xiàn)在,與之類似的是,很多人把所有東西打包進app.js。這兩種方法都有一個很大的負面影響:很多時候人們在下載的是他們用不到的資源。但如果你不這么做吧,你就得手動的在每個頁面引用相應的資源,最終會混亂成一坨:哪個頁面已經(jīng)引用了它所依賴的資源?
這些方法沒有絕對的對錯。把Webpage當做一個中間件--不僅僅是打包或構建工具,而是個聰明的模塊打包系統(tǒng)。只要你設置正確,它會比你還要清楚使用的技術棧,并更好的優(yōu)化它們。
來讓我們一起構建一個簡單的App為了讓你更快捷的理解使用Webpack的好處,我們會構建一個簡單的App,并將資源打包進去。在這里教程中我推薦使用Node4(或5),以及NPM3作為包管理工具,以便在使用Webpack的時候避免大量的麻煩。如果你還沒裝NPM3,可以通過npm install npm@3 -g來安裝。
$ node --version v5.7.1 $ npm --version 3.6.0
我還要推薦你把node_modules/.bin放進你的PATH變量,以避免每次都要輸入node_modules/.bin/webpack。在下面了例子里我輸入的指令都不會再包含node_modules/.bin。
基礎指引(setup)從創(chuàng)建項目安裝Webpack開始。我們同時也安裝了jQuery以便支持后續(xù)操作。
$ npm init -y $ npm install jquery --save $ npm install webpack --save-dev
現(xiàn)在來做一個App的入口:
// src/index.js var $ = require("jquery"); $("body").html("Hello");
讓我們在webpack.config.js文件里進行的Webpack配置。Webpack配置實質上是Javascript,并且在最后export出去一個Object:
// webpack.config.js module.exports = { entry: "./src", output: { path: "builds", filename: "bundle.js", }, };
在這里,entry告訴Webpack哪些文件是應用的入口文件。它們是你的主要文件,在依賴樹的最頂端。之后,我們告訴Webpack把資源打包在builds文件夾下的bundle.js文件里。讓我們編寫index HTML文件。
My title
Click me
運行Webpack。如果一切正確那就可以看見下面的信息:
$ webpack Hash: d41fc61f5b9d72c13744 Version: webpack 1.12.14 Time: 301ms Asset Size Chunks Chunk Names bundle.js 268 kB 0 [emitted] main [0] ./src/index.js 53 bytes {0} [built] + 1 hidden modules
在這段信息里可以看出,bundle.js包含了index.js和一個隱藏的模塊。隱藏的模塊是jQuery。在默認模式下Webpack隱藏的模塊都不是你寫的。如果想要顯示它們,我們可以在運行Webpack的時候使用--display-modules:
$ webpack --display-modules bundle.js 268 kB 0 [emitted] main [0] ./src/index.js 53 bytes {0} [built] [3] ./~/jquery/dist/jquery.js 259 kB {0} [built]
你還可以使用webpack --watch,在改變代碼的時候自動進行打包。
設置第一個loader(loader-01)還記得Webpack可以處理各種資源的引用嗎?該怎么搞?如果你跟隨了這些年Web組件發(fā)展的步伐(Angular2,Vue,React,Polymer,X-Tag等等),那么你應該知道,與一堆UI相互連接組合而成的App相比,使用可維護的小型可復用的UI組件會更好:web component。
為了確保組件能夠保持獨立,它們需要在自己內部打包需要的資源。想象一個按鈕組件:除了HTML之外,還需要js以便和外部結合。噢對或許還需要一些樣式。如果能夠在需要這個按鈕組件的時候,加載所有它所依賴的資源的話那就太贊了。當我們import按鈕組件的時候,就獲取到了所有資源。
開始編寫這個按鈕組件吧。首先,假設你已經(jīng)習慣了ES2015語法,那么需要安裝第一個loader:Babel。安裝好一個loader你需要做下面這兩步:首先,通過npm install {whatever}-loader安裝你需要的loader,然后,將它加到Webpage配置的module.loaders里:
$ npm install babel-loader --save-dev
loader并不會幫我們安裝Babel所以我們要自己安裝它。需要安裝babel-core包和es2015預處理包。
$ npm install babel-core babel-preset-es2015 --save-dev
新建.babelrc文件,里面是一段JSON,告訴Babel使用es2015進行預處理。
// .babelrc { "presets": ["es2015"] }
現(xiàn)在,Babel已經(jīng)被安裝并配置完成,我們要更新Webpack配置。我們想要Babel運行在所有以.js結尾的文件里,但是要避免運行在第三方依賴包例如jQuery里面。loader擁有include和exclude規(guī)則,里面可以是一段字符串、正則、回調等等。在這個例子里,我們只想讓Babel在我們自己的文件里運行,因此使用include包含自己的資源文件夾:
module.exports = { entry: "./src", output: { path: "builds", filename: "bundle.js", }, module: { loaders: [ { test: /.js/, loader: "babel", include: __dirname + "/src", } ], } };
現(xiàn)在,我們可以用ES6語法重寫index.js了:
// index.js import $ from "jquery"; $("body").html("Hello");寫個小組件(loader-02)
來寫個按鈕組件吧,它將包含一些SCSS樣式,HTML模板和一些操作。所以我們要安裝需要的工具。首先安裝Mustache這個輕量級的模板庫,然后安裝處理Sass和HTML的loader。同樣的,為了處理Sass loader返回的結果,還要安裝CSS loader。一旦獲取到了CSS文件,我們就可以用很多種方式來處理。目前使用的是一個叫style-loader的東西,它能夠把CSS插入到包中。
$ npm install mustache --save $ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev
為了能夠讓Webpack依次處理不同loader的返回結果,我們可以將loader通過!鏈接到一起,獲取使用loaders并對應一個由loader組成的數(shù)組:
{ test: /.js/, loader: "babel", include: __dirname + "/src", }, { test: /.scss/, loader: "style!css!sass", // Or loaders: ["style", "css", "sass"], }, { test: /.html/, loader: "html", }
有了loader,我們來寫寫按鈕:
// src/Components/Button.scss .button { background: tomato; color: white; }
{{text}}
// src/Components/Button.js import $ from "jquery"; import template from "./Button.html"; import Mustache from "mustache"; import "./Button.scss"; export default class Button { constructor(link) { this.link = link; } onClick(event) { event.preventDefault(); alert(this.link); } render(node) { const text = $(node).text(); // Render our button $(node).html( Mustache.render(template, {text}) ); // Attach our listeners $(".button").click(this.onClick.bind(this)); } }
你的Button.js現(xiàn)在處于完全獨立的狀態(tài),不管何時何地的引用它,都能獲取到所有需要的依賴并渲染出來?,F(xiàn)在渲染我們的按鈕試試:
// src/index.js import Button from ‘./Components/Button’; const button = new Button(‘google.com’); button.render(‘a(chǎn)’);
運行Webpack,刷新頁面,立刻就能看見我們這個難看的按鈕了。
現(xiàn)在你已經(jīng)學習了如何安裝loader,以及定義各個依賴配置。看起來好像也沒啥。但讓我們來深入擴展一下這個例子。
代碼分離(require.ensure)上面的例子還不錯,但我們并不總是需要這個按鈕?;蛟S有的頁面沒有可以用來渲染按鈕的a,我們并不想在這樣的頁面引用按鈕的資源文件。這種時候代碼分離就能起到作用了。代碼分離是Webpack對于“整塊全部打包”vs“難以維護的手動引導”這個問題而給出的解決方案。這需要在你的代碼中設定“分離點”:代碼可以據(jù)此分離成不同區(qū)域進行按需加載。它的語法很簡單:
import $ from "jquery"; // 這個是分割點 require.ensure([], () => { // 在這里import進的代碼都會被打包到一個多帶帶的文件里 const library = require("some-big-library"); $("foo").click(() => library.doSomething()); });
在require.ensure中的東西都會在打包結果中分離開來--只有當需要加載它的時候Webpack才會通過AJAX請求進行加載。也就是說我們實際上得到的是這樣的文件:
bundle.js |- jquery.js |- index.js // 入口文件 chunk1.js |- some-big-libray.js |- index-chunk.js // 回調中的代碼在這里
你不需要在任何地方引用chunk1.js文件,Webpack會幫你在需要的時候進行請求。這意味著你可以像我們的例子一樣,根據(jù)邏輯需要引進的資源全部扔進代碼里。
只有當頁面上有鏈接存在時,再引用按鈕組件:
// src/index.js if (document.querySelectorAll("a").length) { require.ensure([], () => { const Button = require("./Components/Button").default; const button = new Button("google.com"); button.render("a"); }); }
需要注意的一點是,因為require不會同時處理default export和normal export,所以使用require引用資源里default export的時候,需要手動加上.default。相比之下,import則可以進行處理:
import foo from "bar" vs import {baz} from "bar"
此時Webpack的output將會變得更復雜了。跑下Webpack,用--display-chunks打印出來看看:
$ webpack --display-modules --display-chunks Hash: 43b51e6cec5eb6572608 Version: webpack 1.12.14 Time: 1185ms Asset Size Chunks Chunk Names bundle.js 3.82 kB 0 [emitted] main 1.bundle.js 300 kB 1 [emitted] chunk {0} bundle.js (main) 235 bytes [rendered] [0] ./src/index.js 235 bytes {0} [built] chunk {1} 1.bundle.js 290 kB {0} [rendered] [5] ./src/Components/Button.js 1.94 kB {1} [built] [6] ./~/jquery/dist/jquery.js 259 kB {1} [built] [7] ./src/Components/Button.html 72 bytes {1} [built] [8] ./~/mustache/mustache.js 19.4 kB {1} [built] [9] ./src/Components/Button.scss 1.05 kB {1} [built] [10] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built] [11] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built] [12] ./~/style-loader/addStyles.js 7.21 kB {1} [built]
正如你所見的那樣,我們的入口bundle.js值包含了一些邏輯,而其他東西(jQuery,Mustache,Button)都被打包進了1.bundle.js,并且只在需要的時候才會被引用?,F(xiàn)在為了能夠讓Webpack在AJAX的時候找到這些資源,我們需要改下配置里的output:
path: "builds", filename: "bundle.js", publicPath: "builds/",
output.publicPath告訴Webpack,從當前頁面的位置出發(fā)哪里可以找到需要的資源(在這個例子里是/builds/)。當我們加載頁面的時候一切正常,而且能夠看見Webpack已經(jīng)根據(jù)頁面上預留的“錨”加載好了包。
如果頁面上缺少“錨”(代指link),那么只會加載bundle.js。通過這種方式,你可以做到在真正需要資源的時候才進行加載,避免讓自己的頁面變成笨重的一坨。順帶一提,我們可以改變分割點的名字,不使用1.bundle.js而使用更加語義化的名稱。通過require.ensure的第三個參數(shù)來實現(xiàn):
require.ensure([], () => { const Button = require("./Components/Button").default; const button = new Button("google.com"); button.render("a"); }, "button");
這樣的話就會生成button.bundle.js而不是1.bundle.js
再加個組件(CommonChunksPlugin)來讓我們再加個組件吧:
// src/Components/Header.scss .header { font-size: 3rem; }
{{text}}
// src/Components/Header.js import $ from "jquery"; import Mustache from "mustache"; import template from "./Header.html"; import "./Header.scss"; export default class Header { render(node) { const text = $(node).text(); $(node).html( Mustache.render(template, {text}) ); } }
將它在應用中渲染出來:
// 如果有鏈接,則渲染按鈕組件 if (document.querySelectorAll("a").length) { require.ensure([], () => { const Button = require("./Components/Button"); const button = new Button("google.com"); button.render("a"); }); } // 如果有標題,則渲染標題組件 if (document.querySelectorAll("h1").length) { require.ensure([], () => { const Header = require("./Components/Header"); new Header().render("h1"); }); }
瞅瞅使用了--display-chunks --display-modules標記后Webpack的output輸出:
$ webpack --display-modules --display-chunks Hash: 178b46d1d1570ff8bceb Version: webpack 1.12.14 Time: 1548ms Asset Size Chunks Chunk Names bundle.js 4.16 kB 0 [emitted] main 1.bundle.js 300 kB 1 [emitted] 2.bundle.js 299 kB 2 [emitted] chunk {0} bundle.js (main) 550 bytes [rendered] [0] ./src/index.js 550 bytes {0} [built] chunk {1} 1.bundle.js 290 kB {0} [rendered] [14] ./src/Components/Button.js 1.94 kB {1} [built] [15] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built] [16] ./src/Components/Button.html 72 bytes {1} [built] [17] ./~/mustache/mustache.js 19.4 kB {1} {2} [built] [18] ./src/Components/Button.scss 1.05 kB {1} [built] [19] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built] [20] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built] [21] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built] chunk {2} 2.bundle.js 290 kB {0} [rendered] [22] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built] [23] ./~/mustache/mustache.js 19.4 kB {1} {2} [built] [24] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built] [25] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built] [26] ./src/Components/Header.js 1.62 kB {2} [built] [27] ./src/Components/Header.html 64 bytes {2} [built] [28] ./src/Components/Header.scss 1.05 kB {2} [built] [29] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
可以看出一點問題了:這兩個組件都需要jQuery和Mustache,這樣的話就造成了包中的依賴重復,這可不是我們想要的。盡管Webpack會在默認情況下進行一定的優(yōu)化,但還得靠插件來加足火力搞定它。
插件和loader的不同在于,loader只對一類特定的文件有效,而插件往往面向所有文件,并且并不總是會引起轉化。Webpack提供了很多插件供你優(yōu)化。在這里我們使用CommonChunksPlugin插件:它會分析你包中的重復依賴并提取出來,生成一個完全獨立的文件(例如vendor.js),甚至生成你的主文件。
現(xiàn)在,我們想要把共同的依賴包從入口中剔除。如果所有的頁面都用到了jQuery和Mustache,那么就要把它們提取出來。更新下配置吧:
var webpack = require("webpack"); module.exports = { entry: "./src", output: { // ... }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "main", // 將依賴移到我們的主文件中 children: true, // 再在所有的子文件中檢查依賴文件 minChunks: 2, // 一個依賴重復幾次會被提取出來 }), ], module: { // ... } };
再跑次Webpack,可以看出現(xiàn)在就好多了。其中,main是我們的默認依賴。
chunk {0} bundle.js (main) 287 kB [rendered] [0] ./src/index.js 550 bytes {0} [built] [30] ./~/jquery/dist/jquery.js 259 kB {0} [built] [31] ./~/mustache/mustache.js 19.4 kB {0} [built] [32] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built] [33] ./~/style-loader/addStyles.js 7.21 kB {0} [built] chunk {1} 1.bundle.js 3.28 kB {0} [rendered] [34] ./src/Components/Button.js 1.94 kB {1} [built] [35] ./src/Components/Button.html 72 bytes {1} [built] [36] ./src/Components/Button.scss 1.05 kB {1} [built] [37] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built] chunk {2} 2.bundle.js 2.92 kB {0} [rendered] [38] ./src/Components/Header.js 1.62 kB {2} [built] [39] ./src/Components/Header.html 64 bytes {2} [built] [40] ./src/Components/Header.scss 1.05 kB {2} [built] [41] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
如果我們改變下名字name: "vendor":
new webpack.optimize.CommonsChunkPlugin({ name: "vendor", children: true, minChunks: 2, }),
Webpack會在沒有該文件的情況下自動生成builds/vendor.js,之后我們可以手動引入:
你也可以通過async: true,并且不提供共同依賴包的命名,來達到異步加載共同依賴的效果。
Webpack有很多這樣給力的優(yōu)化方案。我沒法一個一個介紹它們,不過可以通過創(chuàng)造一個生產(chǎn)環(huán)境的應用來進一步學習。
飛躍到生產(chǎn)環(huán)境(production)首先,要在設置中添加幾個插件,但要求只有當NODE_ENV為production的時候才運行它們:
var webpack = require("webpack"); var production = process.env.NODE_ENV === "production"; var plugins = [ new webpack.optimize.CommonsChunkPlugin({ name: "main", children: true, minChunks: 2, }), ]; if (production) { plugins = plugins.concat([ // 生產(chǎn)環(huán)境下需要的插件 ]); } module.exports = { entry: "./src", output: { path: "builds", filename: "bundle.js", publicPath: "builds/", }, plugins: plugins, // ... };
Webpack也提供了一些可以切換生產(chǎn)環(huán)境的設置:
module.exports = { debug: !production, devtool: production ? false : "eval", }
設置中的第一行表明在開發(fā)環(huán)境下,將開啟debug模式,代碼不再混做一團,利于本地調試。第二行則用來生產(chǎn)資源地圖(sourcemaps)。Webpack有一些方法可以生成sourcemaps,而eval則是在本地表現(xiàn)最贊的一個。在生產(chǎn)環(huán)境下,我們并不關心sourcemaps,因此關閉了這個選項。
現(xiàn)在來添加生產(chǎn)環(huán)境下的插件吧:
if (production) { plugins = plugins.concat([ // 這個插件用來尋找相同的包和文件,并把它們合并在一起 new webpack.optimize.DedupePlugin(), // 這個插件根據(jù)包/庫的引用次數(shù)來優(yōu)化它們 new webpack.optimize.OccurenceOrderPlugin(), // 這個插件用來阻止Webpack把過小的文件打成多帶帶的包 new webpack.optimize.MinChunkSizePlugin({ minChunkSize: 51200, // ~50kb }), // 壓縮js文件 new webpack.optimize.UglifyJsPlugin({ mangle: true, compress: { warnings: false, // 禁止生成warning }, }), // 這個插件提供了各種可用在生產(chǎn)環(huán)境下的變量 // 通過設置為false,可避免生產(chǎn)環(huán)境下調用到它們 new webpack.DefinePlugin({ __SERVER__: !production, __DEVELOPMENT__: !production, __DEVTOOLS__: !production, "process.env": { BABEL_ENV: JSON.stringify(process.env.NODE_ENV), }, }), ]); }
我普遍使用的差不多就這么多了,不過Webpack還提供了非常多的插件,你可以自己去研究它們。也可以在NPM上找到很多用戶自己貢獻的插件。插件的鏈接在文末提供。
還有一個關于生產(chǎn)環(huán)境的優(yōu)化是給資源提供版本的概念。還記得output.filename里的bundle.js嗎?在這個配置里面,你可以使用一些變量,而[hash]則會給文件提供一段隨機的字符串。除此以外,我們想要包可以被版本化,因此添加了output.chunkFilename:
output: { path: "builds", filename: production ? "[name]-[hash].js" : "bundle.js", chunkFilename: "[name]-[chunkhash].js", publicPath: "builds/", },
因為無法得知每次打包生成的文件名,所以我們只在生產(chǎn)環(huán)境下使用它。除此之外,我們還想保證每次打包的時候,builds文件夾都會被清空以節(jié)約空間,因此使用了一個第三方插件:
$ npm install clean-webpack-plugin --save-dev
并將它添加到配置中:
var webpack = require("webpack"); var CleanPlugin = require("clean-webpack-plugin"); // ... if (production) { plugins = plugins.concat([ // 在打包前清空 builds/ 文件夾 new CleanPlugin("builds"),
做完這些漂亮的優(yōu)化,來比較下結果的不同吧:
$ webpack bundle.js 314 kB 0 [emitted] main 1-21660ec268fe9de7776c.js 4.46 kB 1 [emitted] 2-fcc95abf34773e79afda.js 4.15 kB 2 [emitted]
$ NODE_ENV=production webpack main-937cc23ccbf192c9edd6.js 97.2 kB 0 [emitted] main
來看看Webpack都做了什么:
在第一段代碼中,后兩個包非常輕量,異步請求不會占用多少HTTP帶寬,所以在生產(chǎn)環(huán)境下Webpack將它們打包進了入口文件里
所有東西都壓縮過了。從322kb降到了97kb
但是這樣下去,Webpack豈不是會將js文件合并成巨大的一坨嗎?
是的,在這個小小的應用中是這樣沒錯。但是你需要這么想:你不需要考慮在什么時候合并什么。如果你的包中含有太多的依賴,它們會被移走到異步請求包中而不會被合并起來。反之,如果它們很小,不值得獨立加載,那么就會被合并。你只需要建立規(guī)則,Webpack會最大化的將其優(yōu)化。沒有人力勞作,不需要思考依賴關系,一切都是自動化的。
或許你已經(jīng)注意到了,我沒有對HTML或CSS進行壓縮。那是因為當debug模式開啟的時候,css-loader和html-loader已經(jīng)幫我們搞好了。這也是為什么Uglify是一個獨立插件的原因:在Webpack中沒有js-loader這種東西,Webpack自己就是個JS loader。
抽?。?b>extract-text-webpack-plugin)可能你已經(jīng)注意到了,從這個教程一開始,Webpack打包好之后,我們的樣式就直接插在網(wǎng)頁頁面上,簡直不能更難看了。能通過Webpack把打包過的CSS生成獨立的文件嗎?當然沒問題:
$ npm install extract-text-webpack-plugin --save-dev
這個插件所做的就是我剛剛說的那些:從打出的最終包里面,提取出某一類內容分離開來多帶帶引用。它通常被用于提取CSS文件:
var webpack = require("webpack"); var CleanPlugin = require("clean-webpack-plugin"); var ExtractPlugin = require("extract-text-webpack-plugin"); var production = process.env.NODE_ENV === "production"; var plugins = [ new ExtractPlugin("bundle.css"), // <=== 提取出來的文件 new webpack.optimize.CommonsChunkPlugin({ name: "main", children: true, minChunks: 2, }), ]; // ... module.exports = { // ... plugins: plugins, module: { loaders: [ { test: /.scss/, loader: ExtractPlugin.extract("style", "css!sass"), }, // ... ], } };
ExtractPlugin.extrac方法接收兩個參數(shù),第一個參數(shù)代表當它處于已經(jīng)打包好的包("style")里時,如何處理那些提取出來的東西;第二個參數(shù)代表當它在主文件("css!sass")里時,如何對待提取出的東西。當它在包里時,肯定不能直接將CSS加在生成的東西后面,所以先用style-loader進行處理;而對于主文件里面的styles,則將它們放進builds/bundle.css文件。我們來給應用加一個主樣式:
// src/styles.scss body { font-family: sans-serif; background: darken(white, 0.2); }
// src/index.js import "./styles.scss"; // Rest of our file
跑下Webpack,就能看見已經(jīng)生成了bundle.css,可以把它引用進HTML里:
$ webpack bundle.js 318 kB 0 [emitted] main 1-a110b2d7814eb963b0b5.js 4.43 kB 1 [emitted] 2-03eb25b4d6b52a50eb89.js 4.1 kB 2 [emitted] bundle.css 59 bytes 0 [emitted] main
如果你想提取出所有包里的樣式,則需要設置ExtractTextPlugin("bundle.css", {allChunks: true})
順帶一提,你也可以自定義文件名,就跟之前說的改變js output-file名稱一樣:ExtractTextPlugin("[name]-[hash].css")
使用圖片(url-loader&file-loader)到目前為止,我們還沒處理例如圖片、字體這樣的資源文件。它們在Webpack中如何工作,我們又該如何優(yōu)化?接下來,我要在網(wǎng)站的背景里加入圖片,看起來一定酷酷的:
把圖片保存在img/puppy.jpg,更新下Sass文件:
// src/styles.scss body { font-family: sans-serif; background: darken(white, 0.2); background-image: url("../img/puppy.jpg"); background-size: cover; }
如果僅僅是這樣,Webpack一定會告訴你:“你特么的想讓我對JPG做啥?”,那是因為還沒有加入對應的loader。有兩種loader可以使用:file-loader和url-loader:
file-loader:返回一段指向資源的URL,允許你給文件加入版本的概念(默認)
url-loader:以data:image/jpeg;base64的形式返回URL
兩個方法不能說誰好誰壞:如果你的圖片大于2M的話那你一定不希望它直接夾雜在代碼中,而是獨立出去;而如果僅僅是2kb左右的小圖標。那么合并在一起減少HTTP請求會更好。因此,我們兩個都要設置:
$ npm install url-loader file-loader --save-dev
{ test: /.(png|gif|jpe?g|svg)$/i, loader: "url?limit=10000", },
在這里,我們給url-loader了一個limit參數(shù),這樣,當文件大小小于10kb的時候,會采取行內樣式,否則的話,會轉到file-loader進行處理。你也可以通過query傳遞一個Object來實現(xiàn)它:
{ test: /.(png|gif|jpe?g|svg)$/i, loader: "url", query: { limit: 10000, } }
來瞅一眼Webpack的輸出:
bundle.js 15 kB 0 [emitted] main 1-b8256867498f4be01fd7.js 317 kB 1 [emitted] 2-e1bc215a6b91d55a09aa.js 317 kB 2 [emitted] bundle.css 2.9 kB 0 [emitted] main
輸出里面沒有JPG圖像,那是因為我們的小狗圖片比配置里限制的大小要小,因此被加到了行內。訪問頁面,你就能看見這只可愛的小狗了。
這是一個非常強大的功能,它意味著Webpack可以智能的根據(jù)資源的大小和HTTP請求占有的比率,采取不同的優(yōu)化方案。還有一個叫做image-loader的loader,可以在打包前檢查所有圖片,避免圖片的重復壓縮。它有一個叫?bypassOnDebug 的參數(shù),通過它你可以只在生產(chǎn)環(huán)境下啟動該插件。
還有很多優(yōu)秀的插件,我強烈建議你使用文末的鏈接去查看它們。
來個牛逼的熱加載(dev-server)我們的生產(chǎn)環(huán)境以及整的差不多了,現(xiàn)在應該更多的關心一下本地開發(fā)?;蛟S你以及注意到了,當人們提及開發(fā)工具的時候,總是會提及熱加載:LiveReload,BrowserSync,或者其他的什么鬼東西。但是只有傻瓜才會整頁的刷新,我們則使用更高端的熱加載。因為Webpack可以確切的知道你依賴樹中某一點位置的代碼,因此每次的改變都會據(jù)此生成一個新的文件。簡單的說,就是不需要刷新頁面就能將改變展現(xiàn)在屏幕上。
為了能夠使用HMR,我們需要一個server來啟動熱加載。Webpack提供的dev-server可以完成這個任務:
$ npm install webpack-dev-server --save-dev
安裝下面的命令啟動server,不能再簡單了:
$ webpack-dev-server --inline --hot
第一個標記--inline是讓Webpack把HMR邏輯直接寫入頁面上而不是放到iframe里,而第二個標記則開啟了HMR。接下來,訪問http://localhost:8080/webpack-dev-server/,嗯還是那個正常的頁面。試著修改Sass文件,MAGIC!
你可以把webpack-dev-server作為自己本地的server。如果你打算一直使用HMR,就需要這么配置:
output: { path: "builds", filename: production ? "[name]-[hash].js" : "bundle.js", chunkFilename: "[name]-[chunkhash].js", publicPath: "builds/", }, devServer: { hot: true, },
這樣的話,不管我們什么時候運行webpack-dev-server,都會是HMR模式。值得一提的是,我們在這里使用webpack-dev-server對資源進行熱加載,但也可以使用在其他地方例如Express server上。Webpack提供了一個中間件,使得你可以把HMR的功能用在其他server上。
代碼不干凈的人都給我去罰站!(pre-loader & lint)如果你一直跟著本教程走,那或許會有這樣的疑問:為什么loader都在module.loaders中而插件不在?那當然是因為還有其他可以配置進module的東西~Webpack不只是有l(wèi)oader,也有pre-loader和post-loader:在main-loader運行之前和之后發(fā)動的玩意。舉個栗子:我基本可以確信自己在這個文章里面寫的代碼很糟糕,所以使用ESLint進行代碼檢查:
$ npm install eslint eslint-loader babel-eslint --save-dev
新建一個肯定會引發(fā)錯誤的.eslintrc文件:
// .eslintrc parser: "babel-eslint" rules: quotes: 2
現(xiàn)在增加pre-loader,語法和之前的一樣,只不過加在module.preLoaders里:
module: { preLoaders: [ { test: /.js/, loader: "eslint", } ],
啟動Webpack,然后淡定的看它失?。?/p>
$ webpack Hash: 33cc307122f0a9608812 Version: webpack 1.12.2 Time: 1307ms Asset Size Chunks Chunk Names bundle.js 305 kB 0 [emitted] main 1-551ae2634fda70fd8502.js 4.5 kB 1 [emitted] 2-999713ac2cd9c7cf079b.js 4.17 kB 2 [emitted] bundle.css 59 bytes 0 [emitted] main + 15 hidden modules ERROR in ./src/index.js /Users/anahkiasen/Sites/webpack/src/index.js 1:8 error Strings must use doublequote quotes 4:31 error Strings must use doublequote quotes 6:32 error Strings must use doublequote quotes 7:35 error Strings must use doublequote quotes 9:23 error Strings must use doublequote quotes 14:31 error Strings must use doublequote quotes 16:32 error Strings must use doublequote quotes 18:29 error Strings must use doublequote quotes
再舉個pre-loader的例子:每個組件里我們都引用了stylesheet,而它們都有相同命名的對應模板。使用一個pre-loader可以自動將有相同名稱的文件作為一個module載入:
$ npm install baggage-loader --save-dev
{ test: /.js/, loader: "baggage?[file].html=template&[file].scss", }
通過這樣的方式告知Webpack,如果遇見和配置相同的HTML文件,則將它作為template 引入,同時引入和它同名的Sass文件。這樣就能改寫組件文件:
將:
import $ from "jquery"; import template from "./Button.html"; import Mustache from "mustache"; import "./Button.scss";
改為:
import $ from "jquery"; import Mustache from "mustache";
你看,pre-loaders也可以很強大。在文末你可以找到更多的loader
還沒看夠?現(xiàn)在我們的應用還很小,當它變的龐大的時候,觀測依賴樹就變的非常有用了,從中可以看出我們做的是對是錯,應用的瓶頸在哪里等等。Webpack知曉這一切,不過我們得禮貌的請教它才能知曉答案。為了做到這點,你可以通過下面的命令運行Webpack:
webpack --profile --json > stats.json
第一個標記會讓Webpack生成一個profile文件,而第二個則將它轉化為JSON格式。最終,講所有的output都生成了JSON文件?,F(xiàn)在有很多網(wǎng)站都可以解析這個JSON文件,不過Webpack官方提供了一個解碼的網(wǎng)站W(wǎng)ebpack Analyze。將JSON文件導入,進入Modules板塊,就可以看見自己依賴樹的可視化圖像:
小圓點越紅,則證明在打包的時候越困難。在這個例子中,jQuery作為最大的文件而成為罪魁禍首。再瞅瞅網(wǎng)站上的其他模塊?;蛟S你無法從這個小小的例子里學到很多東西,但是這個工具在分析依賴樹和包的時候真的非常有用。
我之前提過,現(xiàn)在有很多服務提供可以對profile文件進行分析。其中一個是Webpack Visualizer,它可以以餅狀圖的形式告知你各個文件占據(jù)了多大的比重:
就先講到這兒吧對我而言,Webpack已經(jīng)取代了Grunt或者Gulp:大部分的功能可以使用Webpack替代,其他的則使用NPM腳本就夠了。在以前,每個任務中我們都要通過Aglio,把API文檔轉換為HTML,而現(xiàn)在只需要這么做:
// package.json { "scripts": { "build": "webpack", "build:api": "aglio -i docs/api/index.apib -o docs/api/index.html" } }
即便是一些不需要打包和構建的Glup任務,Webpack都貼心的提供了對應的服務。下面是一個將Glup融合進Webpack的例子:
var gulp = require("gulp"); var gutil = require("gutil"); var webpack = require("webpack"); var config = require("./webpack.config"); gulp.task("default", function(callback) { webpack(config, function(error, stats) { if (error) throw new gutil.PluginError("webpack", error); gutil.log("[webpack]", stats.toString()); callback(); }); });
因為Webpack具有Node API,因此可以很輕松了運用在其他構建體系中。不用多久你就能發(fā)現(xiàn)自己深愛著它無法自拔了。
不管怎樣,這篇文字帶你預覽了Webpack能夠幫你做的事情?;蛟S你認為我們講了很多方面,但實際上這只是個表皮而已:multiple entry points, prefetching, context replacement等等都還沒有涉及到。Webpack是個強大的工具,也因此比那些傳統(tǒng)的工具更加難懂。但一旦你知道如何使用它,它就會為你鳴奏最悅耳動聽的聲音。我曾在一些項目里使用過它,它提供的強大的優(yōu)化和自動化讓我深深不能自拔。
資源Webpack documentation
List of loaders
List of plugins
Sources for this article
Our Webpack configuration package
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/yun/79374.html
摘要:所有的高階抽象組件是通過定義選項來聲明的。所以一般在生命周期或者中,需要用實例的方法清除可當你有多個時,就需要重復性勞動銷毀這件事兒。更多的配置請看雙端開啟開啟壓縮的好處是什么可以減小文件體積,傳輸速度更快。本文目錄 接口模塊處理 Vue組件動態(tài)注冊 頁面性能調試:Hiper Vue高階組件封裝 性能優(yōu)化:eventBus封裝 webpack插件:真香 本文項目基于Vue-Cli3,想知...
摘要:發(fā)覺其實真的不難,畢竟它是一個工具,如果用起來都不順手,那為什么那么多人用,是不是。我覺得可以把當成人物養(yǎng)成游戲來玩,哦不,來學。聽說把寶石放進這工具就能自動更新打包。公司最近弄來了一些未來的文言文,你把它翻譯成現(xiàn)代文吧。 前言 這段可以跳過看下面的。 本來,這個教程想完結的了。但回頭看自己寫的,感覺就像寫明了什么意思,具體怎么使用都沒說明白,而且讓人看得會有點乏味吧。 我也是剛開始...
閱讀 1543·2019-08-30 15:44
閱讀 2002·2019-08-30 14:07
閱讀 2955·2019-08-30 13:56
閱讀 2428·2019-08-29 17:06
閱讀 1404·2019-08-29 14:13
閱讀 2139·2019-08-29 11:28
閱讀 3303·2019-08-26 13:56
閱讀 2016·2019-08-26 12:11