入門 Webpack,看這篇就夠了
轉:https://segmentfault.com/a/1190000006178770
2018年8月25日更新,目前 webpack 已經更新值 4.17.1 ,本文所用到的各種庫或多或少有些過時,跟著代碼操作下來可能會遇到各種問題,不過 webpack 的主體思想沒變,所以還是希望本文對新學 webpack 的你,有所幫助。此外用基於 webpack 4.17.1 寫了一個簡單的demo,如果遇到啥問題,可以參考,之後應該會逐步來完善這個demo,如果有啥通用的想實現的功能,也可以在裏面提 issue。2017年12月7日更新,添加了
clean-webpack-plugin
,babel-env-preset
,添加本文涉及到的所有代碼的示例,如果你在學習過程中出錯了,可點擊此處參考(有些過時了,不要再 fork 了)
寫在前面的話
閱讀本文之前,先看下面這個webpack的配置文件,如果每一項你都懂,那本文能帶給你的收獲也許就比較有限,你可以快速瀏覽或直接跳過;如果你和十天前的我一樣,對很多選項存在著疑惑,那花一段時間慢慢閱讀本文,你的疑惑一定一個一個都會消失;如果你以前沒怎麽接觸過Webpack,而你又你對webpack感興趣,那麽動手跟著本文中那個貫穿始終的例子寫一次,寫完以後你會發現你已明明白白的走進了Webpack的大門。
// 一個常見的`webpack`配置文件 const webpack = require(‘webpack‘); const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const ExtractTextPlugin= require(‘extract-text-webpack-plugin‘); module.exports = { entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle-[hash].js" }, devtool: ‘none‘, devServer: { contentBase: "./public", //本地服務器所加載的頁面所在的目錄 historyApiFallback: true, //不跳轉 inline: true, hot: true }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { modules: true, localIdentName: ‘[name]__[local]--[hash:base64:5]‘ } }, { loader: "postcss-loader" }], }) } } ] }, plugins: [ new webpack.BannerPlugin(‘版權所有,翻版必究‘), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html" //new 一個這個插件的實例,並傳入相關的參數 }), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("style.css") ] };
什麽是WebPack,為什麽要使用它?
為什要使用WebPack
現今的很多網頁其實可以看做是功能豐富的應用,它們擁有著復雜的JavaScript代碼和一大堆依賴包。為了簡化開發的復雜度,前端社區湧現出了很多好的實踐方法
- 模塊化,讓我們可以把復雜的程序細化為小的文件;
- 類似於TypeScript這種在JavaScript基礎上拓展的開發語言:使我們能夠實現目前版本的JavaScript不能直接使用的特性,並且之後還能轉換為JavaScript文件使瀏覽器可以識別;
- Scss,less等CSS預處理器
- ...
這些改進確實大大的提高了我們的開發效率,但是利用它們開發的文件往往需要進行額外的處理才能讓瀏覽器識別,而手動處理又是非常繁瑣的,這就為WebPack類的工具的出現提供了需求。
什麽是Webpack
WebPack可以看做是模塊打包機:它做的事情是,分析你的項目結構,找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Scss,TypeScript等),並將其轉換和打包為合適的格式供瀏覽器使用。
WebPack和Grunt以及Gulp相比有什麽特性
其實Webpack和另外兩個並沒有太多的可比性,Gulp/Grunt是一種能夠優化前端的開發流程的工具,而WebPack是一種模塊化的解決方案,不過Webpack的優點使得Webpack在很多場景下可以替代Gulp/Grunt類的工具。
Grunt和Gulp的工作方式是:在一個配置文件中,指明對某些文件進行類似編譯,組合,壓縮等任務的具體步驟,工具之後可以自動替你完成這些任務。
Webpack的工作方式是:把你的項目當做一個整體,通過一個給定的主文件(如:index.js),Webpack將從這個文件開始找到你的項目的所有依賴文件,使用loaders處理它們,最後打包為一個(或多個)瀏覽器可識別的JavaScript文件。
如果實在要把二者進行比較,Webpack的處理速度更快更直接,能打包更多不同類型的文件。
開始使用Webpack
初步了解了Webpack工作方式後,我們一步步的開始學習使用Webpack。
安裝
Webpack可以使用npm安裝,新建一個空的練習文件夾(此處命名為webpack sample project),在終端中轉到該文件夾後執行下述指令就可以完成安裝。
//全局安裝 npm install -g webpack //安裝到你的項目目錄 npm install --save-dev webpack
正式使用Webpack前的準備
- 在上述練習文件夾中創建一個package.json文件,這是一個標準的npm說明文件,裏面蘊含了豐富的信息,包括當前項目的依賴模塊,自定義的腳本任務等等。在終端中使用
npm init
命令可以自動創建這個package.json文件
npm init
輸入這個命令後,終端會問你一系列諸如項目名稱,項目描述,作者等信息,不過不用擔心,如果你不準備在npm中發布你的模塊,這些問題的答案都不重要,回車默認即可。
- package.json文件已經就緒,我們在本項目中安裝Webpack作為依賴包
// 安裝Webpack npm install --save-dev webpack
- 回到之前的空文件夾,並在裏面創建兩個文件夾,app文件夾和public文件夾,app文件夾用來存放原始數據和我們將寫的JavaScript模塊,public文件夾用來存放之後供瀏覽器讀取的文件(包括使用webpack打包生成的js文件以及一個
index.html
文件)。接下來我們再創建三個文件:
index.html
--放在public文件夾中;Greeter.js
-- 放在app文件夾中;main.js
-- 放在app文件夾中;
此時項目結構如下圖所示
我們在index.html文件中寫入最基礎的html代碼,它在這裏目的在於引入打包後的js文件(這裏我們先把之後打包後的js文件命名為bundle.js
,之後我們還會詳細講述)。
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Webpack Sample Project</title> </head> <body> <div id=‘root‘> </div> <script src="bundle.js"></script> </body> </html>
我們在Greeter.js
中定義一個返回包含問候信息的html
元素的函數,並依據CommonJS規範導出這個函數為一個模塊:
// Greeter.js module.exports = function() { var greet = document.createElement(‘div‘); greet.textContent = "Hi there and greetings!"; return greet; };
main.js
文件中我們寫入下述代碼,用以把Greeter模塊
返回的節點插入頁面。
//main.js const greeter = require(‘./Greeter.js‘); document.querySelector("#root").appendChild(greeter());
正式使用Webpack
webpack可以在終端中使用,在基本的使用方法如下:
# {extry file}出填寫入口文件的路徑,本文中就是上述main.js的路徑, # {destination for bundled file}處填寫打包文件的存放路徑 # 填寫路徑的時候不用添加{} webpack {entry file} -o {destination for bundled file}
指定入口文件後,webpack將自動識別項目所依賴的其它文件,不過需要註意的是如果你的webpack不是全局安裝的,那麽當你在終端中使用此命令時,需要額外指定其在node_modules中的地址,繼續上面的例子,在終端中輸入如下命令
# webpack非全局安裝的情況 node_modules/.bin/webpack app/main.js -o public/bundle.js
結果如下
可以看出webpack
同時編譯了main.js
和Greeter,js
,現在打開index.html
,可以看到如下結果
有沒有很激動,已經成功的使用Webpack
打包了一個文件了。不過在終端中進行復雜的操作,其實是不太方便且容易出錯的,接下來看看Webpack的另一種更常見的使用方法。
通過配置文件來使用Webpack
Webpack擁有很多其它的比較高級的功能(比如說本文後面會介紹的loaders
和plugins
),這些功能其實都可以通過命令行模式實現,但是正如前面提到的,這樣不太方便且容易出錯的,更好的辦法是定義一個配置文件,這個配置文件其實也是一個簡單的JavaScript模塊,我們可以把所有的與打包相關的信息放在裏面。
繼續上面的例子來說明如何寫這個配置文件,在當前練習文件夾的根目錄下新建一個名為webpack.config.js
的文件,我們在其中寫入如下所示的簡單配置代碼,目前的配置主要涉及到的內容是入口文件路徑和打包後文件的存放路徑。
module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包後的文件存放的地方 filename: "bundle.js"//打包後輸出文件的文件名 } }
註:“__dirname”是node.js中的一個全局變量,它指向當前執行腳本所在的目錄。
有了這個配置之後,再打包文件,只需在終端裏運行webpack(非全局安裝需使用node_modules/.bin/webpack)
命令就可以了,這條命令會自動引用webpack.config.js
文件中的配置選項,示例如下:
又學會了一種使用Webpack
的方法,這種方法不用管那煩人的命令行參數,有沒有感覺很爽。如果我們可以連webpack(非全局安裝需使用node_modules/.bin/webpack)
這條命令都可以不用,那種感覺會不會更爽~,繼續看下文。
更快捷的執行打包任務
在命令行中輸入命令需要代碼類似於node_modules/.bin/webpack
這樣的路徑其實是比較煩人的,不過值得慶幸的是npm
可以引導任務執行,對npm
進行配置後可以在命令行中使用簡單的npm start
命令來替代上面略微繁瑣的命令。在package.json
中對scripts
對象進行相關設置即可,設置方法如下。
{ "name": "webpack-sample-project", "version": "1.0.0", "description": "Sample webpack project", "scripts": { "start": "webpack" // 修改的是這裏,JSON文件不支持註釋,引用時請清除 }, "author": "zhang", "license": "ISC", "devDependencies": { "webpack": "3.10.0" } }
註:package.json
中的script
會安裝一定順序尋找命令對應位置,本地的node_modules/.bin
路徑就在這個尋找清單中,所以無論是全局還是局部安裝的Webpack,你都不需要寫前面那指明詳細的路徑了。
npm的start
命令是一個特殊的腳本名稱,其特殊性表現在,在命令行中使用npm start
就可以執行其對於的命令,如果對應的此腳本名稱不是start
,想要在命令行中運行時,需要這樣用npm run {script name}
如npm run build
,我們在命令行中輸入npm start
試試,輸出結果如下:
現在只需要使用npm start
就可以打包文件了,有沒有覺得webpack
也不過如此嘛,不過不要太小瞧webpack
,要充分發揮其強大的功能我們需要修改配置文件的其它選項,一項項來看。
Webpack的強大功能
生成Source Maps(使調試更容易)
開發總是離不開調試,方便的調試能極大的提高開發效率,不過有時候通過打包後的文件,你是不容易找到出錯了的地方,對應的你寫的代碼的位置的,Source Maps
就是來幫我們解決這個問題的。
通過簡單的配置,webpack
就可以在打包時為我們生成的source maps
,這為我們提供了一種對應編譯文件和源文件的方法,使得編譯後的代碼可讀性更高,也更容易調試。
在webpack
的配置文件中配置source maps
,需要配置devtool
,它有以下四種不同的配置選項,各具優缺點,描述如下:
devtool選項 | 配置結果 |
---|---|
source-map |
在一個單獨的文件中產生一個完整且功能完全的文件。這個文件具有最好的source map ,但是它會減慢打包速度; |
cheap-module-source-map |
在一個單獨的文件中生成一個不帶列映射的map ,不帶列映射提高了打包速度,但是也使得瀏覽器開發者工具只能對應到具體的行,不能對應到具體的列(符號),會對調試造成不便; |
eval-source-map |
使用eval 打包源文件模塊,在同一個文件中生成幹凈的完整的source map 。這個選項可以在不影響構建速度的前提下生成完整的sourcemap ,但是對打包後輸出的JS文件的執行具有性能和安全的隱患。在開發階段這是一個非常好的選項,在生產階段則一定不要啟用這個選項; |
cheap-module-eval-source-map |
這是在打包文件時最快的生成source map 的方法,生成的Source Map 會和打包後的JavaScript 文件同行顯示,沒有列映射,和eval-source-map 選項具有相似的缺點; |
正如上表所述,上述選項由上到下打包速度越來越快,不過同時也具有越來越多的負面作用,較快的打包速度的後果就是對打包後的文件的的執行有一定影響。
對小到中型的項目中,eval-source-map
是一個很好的選項,再次強調你只應該開發階段使用它,我們繼續對上文新建的webpack.config.js
,進行如下配置:
module.exports = { devtool: ‘eval-source-map‘, entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" } }
cheap-module-eval-source-map
方法構建速度更快,但是不利於調試,推薦在大型項目考慮時間成本時使用。
使用webpack構建本地服務器
想不想讓你的瀏覽器監聽你的代碼的修改,並自動刷新顯示修改後的結果,其實Webpack
提供一個可選的本地開發服務器,這個本地服務器基於node.js構建,可以實現你想要的這些功能,不過它是一個單獨的組件,在webpack中進行配置之前需要單獨安裝它作為項目依賴
npm install --save-dev webpack-dev-server
devserver作為webpack配置選項中的一項,以下是它的一些配置選項,更多配置可參考這裏
devserver的配置選項 | 功能描述 |
---|---|
contentBase | 默認webpack-dev-server會為根文件夾提供本地服務器,如果想為另外一個目錄下的文件提供本地服務器,應該在這裏設置其所在目錄(本例設置到“public"目錄) |
port | 設置默認監聽端口,如果省略,默認為”8080“ |
inline | 設置為true ,當源文件改變時會自動刷新頁面 |
historyApiFallback | 在開發單頁應用時非常有用,它依賴於HTML5 history API,如果設置為true ,所有的跳轉將指向index.html |
把這些命令加到webpack的配置文件中,現在的配置文件webpack.config.js
如下所示
module.exports = { devtool: ‘eval-source-map‘, entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" }, devServer: { contentBase: "./public",//本地服務器所加載的頁面所在的目錄 historyApiFallback: true,//不跳轉 inline: true//實時刷新 } }
在package.json
中的scripts
對象中添加如下命令,用以開啟本地服務器:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack", "server": "webpack-dev-server --open" },
在終端中輸入npm run server
即可在本地的8080
端口查看結果
Loaders
鼎鼎大名的Loaders登場了!
Loaders
是webpack
提供的最激動人心的功能之一了。通過使用不同的loader
,webpack
有能力調用外部的腳本或工具,實現對不同格式的文件的處理,比如說分析轉換scss為css,或者把下一代的JS文件(ES6,ES7)轉換為現代瀏覽器兼容的JS文件,對React的開發而言,合適的Loaders可以把React的中用到的JSX文件轉換為JS文件。
Loaders需要單獨安裝並且需要在webpack.config.js
中的modules
關鍵字下進行配置,Loaders的配置包括以下幾方面:
test
:一個用以匹配loaders所處理文件的拓展名的正則表達式(必須)loader
:loader的名稱(必須)include/exclude
:手動添加必須處理的文件(文件夾)或屏蔽不需要處理的文件(文件夾)(可選);query
:為loaders提供額外的設置選項(可選)
不過在配置loader之前,我們把Greeter.js
裏的問候消息放在一個單獨的JSON文件裏,並通過合適的配置使Greeter.js
可以讀取該JSON文件的值,各文件修改後的代碼如下:
在app文件夾中創建帶有問候信息的JSON文件(命名為config.json
)
{ "greetText": "Hi there and greetings from JSON!" }
更新後的Greeter.js
var config = require(‘./config.json‘); module.exports = function() { var greet = document.createElement(‘div‘); greet.textContent = config.greetText; return greet; };
註 由於webpack3.*/webpack2.*
已經內置可處理JSON文件,這裏我們無需再添加webpack1.*
需要的json-loader
。在看如何具體使用loader之前我們先看看Babel是什麽?
Babel
Babel其實是一個編譯JavaScript的平臺,它可以編譯代碼幫你達到以下目的:
- 讓你能使用最新的JavaScript代碼(ES6,ES7...),而不用管新標準是否被當前使用的瀏覽器完全支持;
- 讓你能使用基於JavaScript進行了拓展的語言,比如React的JSX;
Babel的安裝與配置
Babel其實是幾個模塊化的包,其核心功能位於稱為babel-core
的npm包中,webpack可以把其不同的包整合在一起使用,對於每一個你需要的功能或拓展,你都需要安裝單獨的包(用得最多的是解析Es6的babel-env-preset
包和解析JSX的babel-preset-react
包)。
我們先來一次性安裝這些依賴包
// npm一次性安裝多個依賴模塊,模塊之間用空格隔開 npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
在webpack
中配置Babel的方法如下:
module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包後的文件存放的地方 filename: "bundle.js"//打包後輸出文件的文件名 }, devtool: ‘eval-source-map‘, devServer: { contentBase: "./public",//本地服務器所加載的頁面所在的目錄 historyApiFallback: true,//不跳轉 inline: true//實時刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: [ "env", "react" ] } }, exclude: /node_modules/ } ] } };
現在你的webpack的配置已經允許你使用ES6以及JSX的語法了。繼續用上面的例子進行測試,不過這次我們會使用React,記得先安裝 React 和 React-DOM
npm install --save react react-dom
接下來我們使用ES6的語法,更新Greeter.js
並返回一個React組件
//Greeter,js import React, {Component} from ‘react‘ import config from ‘./config.json‘; class Greeter extends Component{ render() { return ( <div> {config.greetText} </div> ); } } export default Greeter
修改main.js
如下,使用ES6的模塊定義和渲染Greeter模塊
// main.js import React from ‘react‘; import {render} from ‘react-dom‘; import Greeter from ‘./Greeter‘; render(<Greeter />, document.getElementById(‘root‘));
重新使用npm start
打包,如果之前打開的本地服務器沒有關閉,你應該可以在localhost:8080
下看到與之前一樣的內容,這說明react
和es6
被正常打包了。
Babel的配置
Babel其實可以完全在 webpack.config.js
中進行配置,但是考慮到babel具有非常多的配置選項,在單一的webpack.config.js
文件中進行配置往往使得這個文件顯得太復雜,因此一些開發者支持把babel的配置選項放在一個單獨的名為 ".babelrc" 的配置文件中。我們現在的babel的配置並不算復雜,不過之後我們會再加一些東西,因此現在我們就提取出相關部分,分兩個配置文件進行配置(webpack會自動調用.babelrc
裏的babel配置選項),如下:
module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包後的文件存放的地方 filename: "bundle.js"//打包後輸出文件的文件名 }, devtool: ‘eval-source-map‘, devServer: { contentBase: "./public",//本地服務器所加載的頁面所在的目錄 historyApiFallback: true,//不跳轉 inline: true//實時刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ } ] } };
//.babelrc { "presets": ["react", "env"] }
到目前為止,我們已經知道了,對於模塊,Webpack能提供非常強大的處理功能,那那些是模塊呢。
一切皆模塊
Webpack有一個不可不說的優點,它把所有的文件都都當做模塊處理,JavaScript代碼,CSS和fonts以及圖片等等通過合適的loader都可以被處理。
CSS
webpack提供兩個工具處理樣式表,css-loader
和 style-loader
,二者處理的任務不同,css-loader
使你能夠使用類似@import
和 url(...)
的方法實現 require()
的功能,style-loader
將所有的計算後的樣式加入頁面中,二者組合在一起使你能夠把樣式表嵌入webpack打包後的JS文件中。
繼續上面的例子
//安裝 npm install --save-dev style-loader css-loader
//使用 module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ] } ] } };
請註意這裏對同一個文件引入多個loader的方法。
接下來,在app文件夾裏創建一個名字為"main.css"的文件,對一些元素設置樣式
/* main.css */ html { box-sizing: border-box; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } *, *:before, *:after { box-sizing: inherit; } body { margin: 0; font-family: ‘Helvetica Neue‘, Helvetica, Arial, sans-serif; } h1, h2, h3, h4, h5, h6, p, ul { margin: 0; padding: 0; }
我們這裏例子中用到的webpack
只有單一的入口,其它的模塊需要通過 import
, require
, url
等與入口文件建立其關聯,為了讓webpack能找到”main.css“文件,我們把它導入”main.js “中,如下
//main.js import React from ‘react‘; import {render} from ‘react-dom‘; import Greeter from ‘./Greeter‘; import ‘./main.css‘;//使用require導入css文件 render(<Greeter />, document.getElementById(‘root‘));
通常情況下,css會和js打包到同一個文件中,並不會打包為一個單獨的css文件,不過通過合適的配置webpack也可以把css打包為單獨的文件的。
上面的代碼說明webpack是怎麽把css當做模塊看待的,咱們繼續看一個更加真實的css模塊實踐。
CSS module
在過去的一些年裏,JavaScript通過一些新的語言特性,更好的工具以及更好的實踐方法(比如說模塊化)發展得非常迅速。模塊使得開發者把復雜的代碼轉化為小的,幹凈的,依賴聲明明確的單元,配合優化工具,依賴管理和加載管理可以自動完成。
不過前端的另外一部分,CSS發展就相對慢一些,大多的樣式表卻依舊巨大且充滿了全局類名,維護和修改都非常困難。
被稱為CSS modules
的技術意在把JS的模塊化思想帶入CSS中來,通過CSS模塊,所有的類名,動畫名默認都只作用於當前模塊。Webpack對CSS模塊化提供了非常好的支持,只需要在CSS loader中進行簡單配置即可,然後就可以直接把CSS的類名傳遞到組件的代碼中,這樣做有效避免了全局汙染。具體的代碼如下
module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true, // 指定啟用css modules localIdentName: ‘[name]__[local]--[hash:base64:5]‘ // 指定css的類名格式 } } ] } ] } };
我們在app文件夾下創建一個Greeter.css
文件來進行一下測試
/* Greeter.css */ .root { background-color: #eee; padding: 10px; border: 3px solid #ccc; }
導入.root
到Greeter.js中
import React, {Component} from ‘react‘; import config from ‘./config.json‘; import styles from ‘./Greeter.css‘;//導入 class Greeter extends Component{ render() { return ( <div className={styles.root}> //使用cssModule添加類名的方法 {config.greetText} </div> ); } } export default Greeter
放心使用把,相同的類名也不會造成不同組件之間的汙染。
入門 Webpack,看這篇就夠了