前端的構建打包工具不少,好比grunt,gulp。相信這二者你們應該是耳熟能詳的,上手相對簡單,並且所需手敲的代碼都是比較簡單的。而後webpack的出現,讓這二者打包工具都有點失寵了。webpack比起前二者打包工具,對於前端程序員JS編程能力的要求仍是挺高的。不過須要兼容ie8及如下的小夥伴們,就不要考慮webpack了,他很傲嬌地不兼容!css
webpack,這是一個組合詞「web」+「pack」,web就是網站的意思,「pack」有打包的意思,webpack組合在一塊兒就是網站打包的意思,這個名字至關暴力簡單明瞭啊。webpack這款工具雖然很難學,可是自由度很大,玩轉以後有種爲所欲爲的感受。html
在學習webpack以前,有幾個基礎的概念:前端
在使用webpack以前,咱們須要瞭解webpack的工做原理。webpack打包出來的JS不只僅是壓縮混淆咱們的源文件,並且還對它作了其餘的處理。node
下面是webpack打包出來的JS文件和源文件:webpack
"./src/index.js"
源文件let str="index" console.log(str)
(function(modules) { // webpackBootstrap /*此處省略N+1行*/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) ({ "./src/index.js":(function(module, exports) { eval("let str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?"); }) });
是否是感受原本小巧的JS,一會兒變得臃腫了??彷佛用webpack沒有意義啊!不只不能忙我壓縮文件,還把源文件變胖了。git
不要急,咱們再看一個例子:程序員
"./src/index.js"
源文件require("./page1.js") let str="index" console.log(str)
"./src/page1.js"
源文件let str="page1" console.log(str)
(function(modules) { // webpackBootstrap /*此處省略N+1行*/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) ({ "./src/index.js": (function(module, exports, __webpack_require__) { eval("__webpack_require__(/*! ./page1.js */ \"./src/page1.js\")\r\nlet str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?"); }), "./src/page1.js":(function(module, exports) { eval("let str=\"page1\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/page1.js?"); }) });
當有模塊導入的時候,這個胖JS就展示了他真正的實力。經過__webpack_require__
來實現JS之間導入的功能。至關於咱們再也不須要用requirejs,seajs此類包管理器管理咱們的前端模塊了。webpack幫助咱們完成了此類工做。是否是忽然以爲這個胖JS不胖了。github
webpack的打包原理,就是將各個模塊變成字符串,存入健值或者數組之中,而後每一個模塊之間的關係,經過__webpack_require__
這個方法來實現。最後經過eval
這個函數將字符串變成可執行代碼。web
若是你們對__webpack_require__
的實現原理感興趣,能夠本身打包一個文件,不要壓縮混淆,而後研究研究。typescript
webpack這個工具,不可能只有打包壓縮這個功能吧。既然是前端工具,那麼必然要具有如下功能:
若是你以前並未使用過webpack,那麼就須要安裝一下webpack,順便學習下如何啓動webpack。
webpack從4開始,webpack分紅了兩個包一個webpack一個webpack-cli,因此安裝的時候要安裝兩個包,以及這個包咱們是工具,非網站所依賴的包,因此記得放在開發依賴包之中。
npm install webpack webpack-cli -save-dev
也許咱們想能夠直接安裝webpack,不要webpack-cli。可是現實很殘酷,若是沒有安裝CLI,系統就會告訴你,cli是必不可少的,否則webpack就罷工了。
One CLI for webpack must be installed.
安裝好了以後,咱們應該怎麼運行呢?這裏有兩個途徑:
npx
,這個是幹嗎的呢,是幫忙咱們直接執行.bin,目錄下的文件。node_modules\.bin\webpack.cmd
在這個路徑下有webpack的執行命令,咱們能夠打開看看。當咱們npx webpack
的時候,就是運行了這個文件。package.json
來運行文件,有個字段叫作scripts
,咱們加一個start
,而後後面跟上命令。到時候咱們呼喚npm start
就要能夠運行webpack了。"scripts": { "start": "webpack --config webpack.config.js" }
webpack4開始支持零配置,也就是說我不用寫webpack.config.js
也能夠運行。那咱們就運行試試,結果出現了一個警告:
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
這個警告就是告訴咱們,webpack4中的mode
參數默認是production
,因此若是是development
的狀況就必定要配置了。感受是零配置彷佛是很是牛逼的一個操做,可是實際上仍是須要手動配置的,由於這個零配置只是幫咱們作掉了一些簡單的事,好比線上就壓縮JS,開發版就不壓縮JS,還有一些默認的路徑之類的。實際上開發的時候,默認的路徑確定是不夠用的。咱們仍是老老實實寫配置吧。
咱們配置一下,而且運行一下,在開發環境下打包,生成了一個/dist/main.js
文件。奇怪個人html文件怎麼沒有打包過來?對,HTML文件須要咱們本身在dist之中建立的,也就是/dist/index.html
。而且路徑要寫好即將生成的JS連接。好比/dist/main.js
在html中引入,我就須要寫成<script src="./main.js"></script>
module.exports = { mode:"development", };
這個配置文件,你們都沒有以爲寫法很熟悉?對!就是CommonJs規範!下一節會詳細解釋webpack.config.js
該如何配置。
webpack的一切操做都配置在webpack.config.js
之中,能夠說配好webpack.config.js
,咱們就能夠坐等新鮮出爐的網站了。
./src/index.js
。./dist/main.js
。production
和development
,這個是webpack4新增的一個屬性,用於區分開發版與線上版,也是很貼心的設置了。在學些webpack的配置以前,咱們最早接觸的就是輸入Entry和輸出Output的配置。這裏須要引入一個chunk的概念,咱們在配置Entry的時候,有時候會設置好多個入口,這每個入口都是一個chunk,也就是說chunk是根據Entry的配置而來的。你們必定要區分清楚chunk和module的概念啊。module就是編程的時候,咱們所寫的一塊一塊的功能塊,而後模塊之間會有依賴。而後chunk只是將當前模塊和他的依賴模塊,一塊兒打包起來的代碼塊。
配置Entry,切入點JS入口也不是件容易的事。
./src/index.js
。單個文件之間傳入字符串便可。entry: '須要打包的JS的相對或者絕對地址'
entry: ["待打包JS-1","待打包JS-2"]
entry: { JS1: "待打包JS-1", JS2: "待打包JS-2" }
entry: { JS1: ["待打包JS1-1","待打包JS1-2"], JS2: ["待打包JS2-1","待打包JS2-2"] }
輸出口,安放打包好的JS,不配置就打包到默認文件,默認./dist/main.js
。
若是不須要分入口點,整個網站用一個JS。那麼配置一個文件名就能夠了。
output: { filename: 'bundle.js', }
須要指定文件夾的操做,就再加一個path字段便可。
output: { filename: 'bundle.js', path: __dirname + '/dist' }
然而現實中,咱們不可能只有一個JS,因此這個時候咱們就須要配置多個輸出口,不過這個不像entry能夠配置健值。可是有一個很簡便的辦法filename: '[name].js'
,文件名咱們用[name]
,這樣打包出來的Js文件就會按照Entry配置的chunk名,來命名了。
固然咱們常常回碰到CDN的問題,一個JS會被緩住,這時候咱們能夠用[hash]
這個參數,來幫咱們filename: '[name].[hash].js'
這樣每次生成的JS名就不同了。
在webpack中,任何文件均可以變成一個模塊,而後被打包到JS之中。可是JS只認識JS,像CSS,或者typescript這類的非標準JS,該如何處理?這個時候Loader就出現了,他幫助webpack將CSS此類文件變成JS可識別的內容而後打包。全部的loader都須要額外下載安裝,這裏以最經常使用的CSS爲例子,看咱們如何將CSS打包到JS之中。
npm install --save-dev css-loader
關於css-loader的用法,你們能夠參考下官網。
module.rule
。module: { rules: [ { test: /\.css$/, use: [ { loader: 'style-loader'}, { loader: 'css-loader',options: {modules: true}} ] } ] }
也就是說loader全部的配置都在rules之下。這裏我還配置了style-loader,那麼咱們既然又了css-loader爲何還要style-loader呢?感受很累贅啊。那麼接下來就要說說這兩個loader的不一樣了。
打開styleloader的官網,咱們能夠發現:
Adds CSS to the DOM by injecting a
<style>
tag
也就是說style-loader就幹一件事就是將咱們處理好的CSS插入到DOM之中,不然咱們的CSS只編譯不生效。
若是咱們不喜歡內聯樣式,而且以爲CSS文件不必編譯到JS文件之中,那麼咱們能夠直接引入一個文件。咱們能夠這樣配置。
module: { rules: [ { test: /\.css$/, use: [ { loader: 'style-loader/url'}, { loader: "file-loader" } ] } ] }
利用style-loader/url
和file-loader
來加載文件。這個時候會在咱們的生產文件夾下新建一個css文件,而後js中會加載這個新建的css文件的路徑。咱們無需在頁面上配置link,js會幫助咱們自動生成一個link,引入咱們的css文件。這樣咱們就不用將css和js打包到一塊兒啦。
若是說loader只是對於JS的一個操做,好比將CSS轉化到JS之中啦,那麼plugins的功能就更加普遍,並不侷限加載編譯JS,好比HTML文件的操做。
這裏有一個我剛開始的遇到的問題,就是:
webpack主要是負責JS的編譯管理,那麼個人HTML文件呢?難道要我一個個在dist之中建立好嗎??
這個時候HTML Webpack Plugin出現啦,這個插件是專門用於建立管理HTML的。
首先是安裝npm i --save-dev html-webpack-plugin
,而後是配置webpack:
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode:"development", plugins: [ new HtmlWebpackPlugin() ] }
通常插件都是建立一個新的實例,而後加入plugins這個數組之中。
而後咱們來看看這個HtmlWebpackPlugin插件,這個插件很強大,咱們不只能夠控制模版,還能夠配置頁面內容,像下方這樣。
test.html
<body> <%= htmlWebpackPlugin.options.title %> </body>
webpack.config.js
plugins: [ new HtmlWebpackPlugin(), //生成自動的index.html new HtmlWebpackPlugin({ // 生成一個test.html title: 'Custom template using Handlebars', filename: 'test.html', template: path.join(__dirname,'src/test.html') }) ],
由上述例子能夠看出,爲了保證插件的靈活性,好比我每一個頁面的配置不同,咱們就能夠new好幾個插件來處理咱們的html文件。一個實例處理一個頁面。
MODE有三個參數production
,development
,none
,前兩個是有預設的插件,而最後一個則是什麼都沒有,也就是說設置爲none
的話,webpack就是最初的樣子,無任何預設,須要從無到有開始配置。
咱們來研究下他們之間的配置的區別,首先是二者都有的一個new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development|production") })
,這個是用來讓咱們能夠直接在js中引用"process.env.NODE_ENV"
的值,這樣就能夠在JS之中經過這個值來區別開發板與先上版本的不一樣腳本。
編譯以前的index.js
console.log(process.env.NODE_ENV)
編譯以後的index.js
console.log("development")
咱們能夠看到直接將咱們的process.env.NODE_ENV
替換成了因此定義的內容。這個小功能能夠幫助咱們在寫業務JS的時候,區分線上版本與開發版本。
咱們接着看看其餘的開發中使用的插件NamedModulesPlugin
和NamedChunksPlugin
,本來咱們的webpack並不會給打包的模塊加上姓名,通常都是按照序號來,從0開始,而後加載第幾個模塊。這個對機器來講無所謂,查找載入很快,可是對於人腦來講就是災難了,因此這個時候給各個module和chunk加上姓名,便於開發的時候查找。
在沒有mode的狀況下,這些插件須要本身配置,而有了mode以後,咱們的配置就能夠省略了。
// webpack.development.config.js module.exports = { + mode: 'development' - devtool: 'eval', - plugins: [ - new webpack.NamedModulesPlugin(), - new webpack.NamedChunksPlugin(), - new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }), - ] }
在線上版本中,咱們第一個須要處理的就要混淆&壓縮JS了吧。在線上mode中,自帶JS混淆壓縮,能夠說這個功能很方便了。
// webpack.production.config.js module.exports = { + mode: 'production', - plugins: [ - new UglifyJsPlugin(/* ... */), - new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }), - new webpack.optimize.ModuleConcatenationPlugin(), - new webpack.NoEmitOnErrorsPlugin() - ] }
官方文檔一共給出了3中實時編譯的方法:--watch
,webpack-dev-server``和
webpack-dev-middleware`
--watch |
webpack-dev-server |
webpack-dev-middleware |
|
---|---|---|---|
實時編譯 | yes | yes | yes |
服務器 | no | yes | yes |
hot | no | yes | yes |
代碼上手 | 簡單 | 中等 | 稍困難 |
--watch是個好方法,運行以後,會自動給咱們編譯文件。可是瀏覽器須要手動刷新才能出現最新的內容。
webpack-dev-server
雖然,能夠直接在config中配置參數,可是仍是須要安裝一下,纔可使用。
npm install --save-dev webpack-dev-server
webpack不產出任何編譯後的文件。他只提供內存中的代碼,僞裝是真是的代碼。若是你但願在其餘目錄中讀取文件。能夠更換publicPath 選項。每次修改都會實時編譯。
可是使用webpack-dev-server
,修改文件,並不會實時刷新瀏覽器,咱們須要一些配置才能夠。
首先須要在pligns中加入new webpack.HotModuleReplacementPlugin()
。
plugins: [ new webpack.HotModuleReplacementPlugin() ], devServer: { contentBase: path.join(__dirname, 'dist'), publicPath:"/", },
webpack-dev-server
雖然很方便,配置也簡單,可是他編譯出來的文件與npx webpack
編譯出來的並不同,所以調試起來未必很方便。
看見middleware就應該知道這個是一箇中間件,用於連接webpack的編譯功能和其餘nodejs服務器框架的橋樑,這邊咱們選擇express這個框架。
首先是安裝這兩個包。
npm install --save-dev express webpack-dev-middleware
這個比webpack-dev-server
要複雜一些,還須要安裝一個express。可是這個的編譯的內容是會寫入dist文件的,實時更新,徹底按照webpack
的編譯來。她的原理就是先執行webpack,在更新到服務器上,這樣咱們訪問的就是最新的內容了。
既然是中間件,那麼就不是webpack親生的,就須要在webpack-dev-server
配置的基礎上加點料。
咱們要在須要監控的入口點加入監控的js,像這樣寫:
entry:{ index:['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true',path.join(__dirname,"/src/index.js")], },
接着就是server.js的編寫,想要寫好這一部分,你們要先學會express,以及express中間件的用法。而後再是將webpack掛載到express之上。
const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const app = express(); const config = require('./webpack.config.js'); const compiler = webpack(config); app.use(webpackDevMiddleware(compiler, { publicPath: config.output.publicPath })); app.use(require("webpack-hot-middleware")(compiler)); app.listen(8080, function () { console.log('Example app listening on port 8080!\n'); });
這樣配置雖然麻煩,可是咱們能看到實時編譯的JS文件,對於網站的總體細節把控會更好。
感受寫了一篇超長的入門文章,列出了webpack的配置用法,以及webpack插件的用法,能夠說webpack插件是webpack之魂,擴展了許多其餘的功能。還有如何實時編譯咱們的網站。