開發工具誕生的目的永遠是加速開發. 程序員應該不斷追求更快更好的開發工具.
前文一步步學Webpack4(0)-- 實戰起步已經完成了Webpack環境的搭建以及實現了一句命令自動打包項目,這一次咱們繼續使用以前的項目webpack-stepbystep來嘗試搭建適合對開發者友好的項目開發環境.css
本章按照如下步驟進行:html
寫Webpack文章不寫版本都是耍流氓,這篇文章基於當下最新的 webpack v4.22.0 以及 webpack-cli v3.1.2 編寫.前端
Eating your own dog food
嘗試深刻探索學習Webpack的人大概都有一顆想給本身寫個順手的手腳架的心吧,吃本身的狗糧這件事對開發者確定是好事,可是前提是本身真正懂得本身的需求.webpack
對於一個普通前端開發者來講,一個簡單項目的手腳架必須具有必定的能力,總結一下一些必不可少的需求吧:git
接下來咱們就來藉助Webpack的能力,一個個實現這些需求~程序員
source map
實現調試信息追溯文章跟隨 一步步學Webpack4(0)-- 實戰起步 繼續開發.github
項目已經可以使用Webpack打包了,咱們如今使用這個項目來隨便寫點會發生錯誤的代碼,例如在方法第一行加入 console.abg('generate component')
:web
index.jsnpm
import _ from 'lodash'; function component () { console.abg('generate component'); let element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); return element; } document.body.appendChild(component());
而後在終端中運行 webpack
完成打包,運行結果以下:json
發現錯誤是被指向了編譯後的文件 main.js
,這並非咱們想要的. 發生錯誤的時候瀏覽器若是不能追溯到源代碼發生錯誤的位置,這將增大調試的難度,幸虧Webpack已經提供解決這個問題的方法--source map
,咱們只須要簡單地修改一下配置文件:
webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, devtool: 'inline-source-map', plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ inject: false, template: 'index.html', filename: 'index.html' }) ] };
上面代碼中加入了一句 devtool: 'inline-source-map'
, 從新在終端中運行 webpack
完成打包,運行結果以下:
成功了,藉助source map
的力量, 錯誤發生時瀏覽器從 main.js
追溯到了源代碼 index.js
中. 至此咱們已經成功實現了第一個需求「方便的錯誤信息追溯」. 另外要特別注意的是,source map
只能在開發環境中使用以方便調試,千萬不能用於生產環境,簡單緣由看看添加了 source map
以後的main.js文件大小就知道了(逃
固然 source map
還有許多配置能夠選擇, 不過與本章的學習關係不大, 先繼續往下學習吧~
刀耕火種時期每次保存完代碼都要F5,在項目中應用了Webpack以後每次保存完代碼竟然須要先Webpack打包再F5,這麼愚蠢的事情程序員怎麼可能容許呢,因而開發工具們開始誕生了:
嚴格來講這不算是一種額外的開發工具,這只是Webpack的一種運行模式,能夠在終端輸入 webpack --watch
開始持續監聽文件變化,只要修改代碼並保存,webpack將會自動幫你打包項目,聽起來還不錯可以自動打包,可是這種模式並不能幫助開發者更新頁面內容也就是說, 你仍是須要本身按F5刷新..., 感受仍是有點慘啊.
算了 =。= Next one
這是一個官方推薦的新手友好的開發工具,webpack-dev-server
提供了一個具有實時重載功能的簡單服務器,只須要下載到工程中,並對配置文件進行簡單配置便可使用,安裝命令以下:
npm i -D webpack-dev-server
此處基本使用 webpack-dev-server 的默認配置,惟一修改的地方是配置開啓服務器的位置即 contentBase: './dist'
,整份配置以下所示:
webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, devtool: 'inline-source-map', devServer: { contentBase: './dist' }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ inject: false, template: 'index.html', filename: 'index.html' }) ] };
接下來打開 package.json
文件,在"scripts"中添加一行運行腳本 "start": "webpakc-dev-server --open"
,完整package.json文件以下:
package.json
{ "name": "webpack-stepbystep", "version": "1.0.0", "description": "", "private": true, "scripts": { "start": "webpack-dev-server --open" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "clean-webpack-plugin": "^0.1.19", "html-webpack-plugin": "^3.2.0", "webpack": "^4.22.0", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" }, "dependencies": { "lodash": "^4.17.11" } }
而後打開終端,運行命令 npm start
,稍後就能看到瀏覽器自動打開了 localhost:8080 界面,若是對代碼進行保存修改,web服務器就會自動從新打包代碼並將更新應用到瀏覽器網頁上~至此前端程序員的對於手腳架的幾個需求已經徹底獲得了知足,如今已經能夠舒舒服服地開始前端開發了~
至此項目提交爲 feat(project): add source map & devtools .
P.S. 官方文檔中還有一個開發工具 webpack-dev-middleware
, 與Node.js結合可以進行更多的自定義配置,不過暫時咱們不須要用到它.
完成上一小節的配置以後,咱們能夠開始嘗試在當前項目中編寫代碼了,首先咱們固然是先來改正第一小節的錯誤,將 index.js 中的console.abg('generate component');
改成 console.log('generate component');
,文件完整代碼以下所示:
index.js
import _ from 'lodash'; function component () { console.log('generate component'); let element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); return element; } document.body.appendChild(component());
保存以後,很快就能看到頁面上出現了熟悉的 Hello Webpack~ 修改代碼以後只須要保存,剩下的事情Webpack都會幫你自動搞定,自動更新的效果不錯嘛~
然而,這只是一個小小的項目。設想一下,你如今正在調試一個規模比較大的項目,在最後一步按下"提交button"以前,你忽然想起"提交button"綁定錯了觸發的事件. 若是此時修改代碼並保存,應用頁面將會被刷新,也就是說你剛剛選擇的許多選項的狀態會被重置回初始值。你的粗心讓你須要把以前的選擇流程走一遍,若是以後又發現了另外一個小錯誤那麼又要再走一遍流程...此時的你多麼但願有一個工具能讓你保持着頁面當前的狀態,並偷偷地幫你更新修改好的綁定關係, 你只須要在完成更新後從容按下"提交button"就完事. 沒錯,這就是這一小節的重點 模塊熱更新 Hot Module Replacement(HMR)!
說了那麼多,不如show me your code. 好,如今立刻經過實戰來見識一下 HMR
的厲害.
咱們新建一個模塊稱爲printMe, 負責打印一段文字, 在index.js中引用該模塊併爲編寫一個button來觸發它,完整代碼以下所示:
print.js
export default function printMe () { console.log('Updating print.js'); }
index.js
import _ from 'lodash'; import printMe from './print'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } document.body.appendChild(component());
修改 print.js
的打印內容並保存,當前效果是:整個頁面直接經過刷新來更新界面.
步驟一:首先更新webpack的配置文件. 在配置頭部加入對webpack的引用,而後在devServer對象中配置 hot: true
來開啓 HMR
,最後在plugins對象中配置 HotModuleReplacementPlugin
插件以替換模塊,完整代碼以下所示:
webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, devtool: 'inline-source-map', devServer: { contentBase: './dist', hot: true }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ inject: false, template: 'index.html', filename: 'index.html' }), new webpack.HotModuleReplacementPlugin() ] };
步驟二:修改 index.js 文件,在底部加入 HMR
相關代碼,令其在 printMe 模塊發生改變時能夠接受更新的模塊,完整代碼以下所示:
index.js
import _ from 'lodash'; import printMe from './print'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } document.body.appendChild(component()); if (module.hot) { module.hot.accept('./print.js', function () { console.log('Accepting the updated printMe module!'); printMe(); }); }
步驟四:你覺得就這樣結束了?其實並無,HMR 手擼的話仍是比較坑的. 點擊button你會發現控制檯中打印的東西一直都是最初始的打印值,這是由於button的事件依然綁定在舊的函數上,爲了解決這個問題,咱們將經過 index.js 底部 HMR
代碼更新button的事件綁定,具體完整代碼以下所示:
index.js
import _ from 'lodash'; import printMe from './print'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } // document.body.appendChild(component()); let ele = component(); document.body.appendChild(ele); if (module.hot) { module.hot.accept('./print.js', function () { console.log('Accepting the updated printMe module!'); // printMe(); document.body.removeChild(ele); ele = component(); document.body.appendChild(ele); }); }
如今再修改 print.js 的打印文字並保存,經過觀察控制打印結果,發現頁面完成了修改而且沒有產生刷新,而且點擊以後控制檯會出現新修改的文字. HMR
配置成功~
如今以爲 HMR
開發很難?Webpack的開發者天然考慮到了這一點,Webpack 的 loader將會幫你把這一個過程變得簡單.
只需下載loader並完成配置,以後的同類型改動須要更新時,loader會自動在幕後經過 module.hot.accept
完成對於內容的修補.
此次實戰咱們先安裝並配置 styleloader & cssloader, 而後藉助loader的力量幫助咱們實現頁面樣式的模塊熱更新,體驗loader帶來的便利.
npm i -D style-loader css-loader
webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: { app: './src/index.js' }, output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, devtool: 'inline-source-map', devServer: { contentBase: './dist', hot: true }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ inject: false, template: 'index.html', filename: 'index.html' }), new webpack.HotModuleReplacementPlugin() ] };
styles.css
並在 index.js 引用:styles.css
body { background-color: blue; }
index.js
import _ from 'lodash'; import printMe from './print'; import './styles.css'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } // document.body.appendChild(component()); let ele = component(); document.body.appendChild(ele); if (module.hot) { module.hot.accept('./print.js', function () { console.log('Accepting the updated printMe module!'); // printMe(); document.body.removeChild(ele); ele = component(); document.body.appendChild(ele); }); }
npm start
, 確認應用開啓完畢後,修改 styles.css
, 保存後觀察控制檯的打印:body { background-color: #fff; }
發現頁面在沒刷新的狀況下完成了背景顏色的變化. 藉助loader的力量成功實現了 HMR
的效果.
Hot Module Replacement(HMR)是Webpack最棒的特性之一,當代碼修完並保存以後,Webpack將從新打包項目,並將新的模塊發送到瀏覽器端,瀏覽器更新對應的模塊,以此達到更新應用頁面的目的.
不一樣於實時刷新的開發工具庫,HMR 在更新以後依舊可以保持原有的應用狀態,提升了開發者的開發效率.
至此項目提交爲 feat(project): finish dev-server & HMR config .
在Webpack出現以前,前端工程師們使用的打包工具一般是 grunt 或者 gulp, 這些工具處理圖片等資源的方式一般是複製,也就是將文件複製一份到打包目錄下.
可是Webpack不一樣,它對於js和資源文件一視同仁,也就是將資源也看做模塊,使用到這些模塊的地方須要顯示調用資源,而後由Webpack動態構建依賴圖完成統一打包, Webpack經過資源間的強依賴關係,完美避開了隱式引用和無效引用形成的錯誤和浪費.
爲了完成對任何類型資源的引用,社區出現了各類格式的loader來幫助Webpack完成這個任務. 比較通用的loader有:
以上資源能夠直接放在一個控件目錄下,並經過顯式聲明依賴創建起該控件的依賴關係圖. 這樣的控件更具有可移植性.
具體使用操做能夠跟隨官方文檔的Asset Management章節跑一波,目標是認識經常使用loader並跟隨文檔完成當前項目配置便可.
至此項目提交爲feat(project): finish loaders study
本章以開發需求探索開始, 根據總結的需求提出解決方案並選擇新手友好的開發工具 webpack-dev-server
, 接着進一步瞭解方便開發者調試修改應用特性 HMR
, 最後再學習並使用loader完成項目的基礎配置. 簡單的開發環境搭建已經完成了,如今可使用這個環境試試愉快的代碼編寫吧~
To be continued...