對於現代web前端工程化,webpack起到了絕大的做用,此篇文章致力於讓你學習webpack的配置,可以從無到有的配置一套屬於本身的webpack配置,此教程從基礎配置,到優化兩個維度進行講解,其中有大量的示例,文尾部分會提供git倉庫以供代碼下載。javascript
這裏只講解配置相關的細節,不講解原理性的知識,建議熟練使用webpack以後,學習下webpack是如何工做的,這樣能夠定製loader和plugin,可以更加高效的解決項目上的問題。css
前端工程化最基礎的東西,就是npm包管理器,這一切都是創建在這上面的,首先運行。html
npm init -y前端
初始化package.jsonvue
前提是你安裝了node.js,下載地址java
nodejs.org/en/node
其中 -y 指令是跳過package.json的配置,直接初始化一個package.json文件。建議去掉-y指令,進行一次定製化的配置。python
創建一個build目錄,用於存放webpack相關配置,創建一個src目錄,用於存放工程文件。jquery
在build目錄下新建一個webpack.base.js文件,這個文件用於存放公共的webpack配置項,好比sass,babel什麼的,由於一個工程可能不止一個需求,因此創建一個公共的配置項方便其餘需求的配置。webpack
安裝webpack依賴,推薦使用yarn
npm i yarn -g
yarn add webpack --dev
由於是配置4.0版本,因此還須要安裝webpack-cli
yarn add webpack-cli --dev
配置完成後,在webpack.base.js進行如下配置,測試是否安裝成功
const path = require('path')
const getPathByRoot = function(targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: './static/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
}
}
複製代碼
這是一個最簡單的配置,引入了 node 的path模塊,用於路徑解析,定義了webpack入口文件,以及出口的路徑及文件名,注意文件名後面有一個[hash],這個的做用是給打包後的文件添加一個hash值,用戶解決瀏覽器緩存的問題。
再看看入口文件。
class Test {
constructor() {
alert(1)
}
}
new Test()
複製代碼
很簡單的代碼,只是爲了查看打包後的文件。
很關鍵的一步就是須要在package.json文件中定義打包命令,在package.json文件的script中添加如下代碼:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/webpack.base.js --mode=production"
}
複製代碼
build命令的大體意思是,以production模式運行webpack使用build目錄下的webpack.base.js配置文件。
上述文件添加完成後,運行:
npm run build
能夠看見生成了一個dist目錄,目錄下有一個static目錄,其中有一個index.js文件,其中的代碼以下:
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="./",r(r.s=0)}([function(e,t){new class{constructor(){alert(1)}}}]);
複製代碼
多了一些文件,在最後能夠看到:
new class{constructor(){alert(1)}}}
複製代碼
入口文件的代碼已經被打包了,可是仍是以前的es6的代碼,若是想要轉換成es5,這就要用到babel了。
webpack的loader做用是,loader經過正則表達式將匹配到的文件中的字符串,通過本身的處理後,再交給下一個loader處理,因此最終獲得的文件,是一個本身須要的文件。
這裏介紹babel-loader的配置,babel的做用是將低版本瀏覽器不支持的es6 7 8 的代碼轉換成可執行的es5代碼,而babel-loader的做用是將匹配到的js文件交給babel轉換器處理成es5的代碼,再交給下一個loader,先安裝babel相關的依賴
yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader --dev
yarn add @babel/runtime
用兩條命令的緣由是, @babel/runtime 是生產依賴,用於將babel不轉換的es6 函數如promise,set, map等轉換成es5的 polyfill,而且經過 @babel/plugin-transform-runtime 插件將這些函數提取成公共的函數,以避免重複轉換,增長代碼量。
完成依賴的安裝後,咱們須要配置webpack的loader,以下圖。
const path = require('path')
const getPathByRoot = function(targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: './static/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}]
}
}
複製代碼
添加了一個module屬性,其中有一個字段是rules,值是一個數組,表示能夠處理多種格式的文件。test是一個正則表達式,表示須要通過loader處理的文件類型。use對應的是須要使用的loader,是一個數組,能夠添加多個loader,處理的順序是,最後一個loader先處理,第一個loader最後處理,這個後面會詳細講解,如今不懂不要緊。
而後在項目根目錄新建一個文件,名爲 .babelrc ,內容以下:
{
"presets": [
"@babel/env"
]
}
複製代碼
關於babelrc文件具體的講解能夠看這篇文章,這裏我使用的是最新的 "@babel/env"
運行 npm run build 命令,獲得以下代碼
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="./",t(t.s=0)}([function(e,n){new function e(){!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),alert(1)}}]);
複製代碼
在文件的最後,能夠看到 es6 的 class 語法已經轉換成了es5的代碼,至此,babel就配置成功了。
其次一個web前端項目確定是少不了html文件的,因此接下來教你們處理html文件。
html-loader能夠解析html文檔中的圖片資源,交給其餘的loader處理,如url-loader或者是file-loader等。
html-webpacl-plugin的功能是將一個html做爲模版,打包完成後會將打包後的資源好比js css,自動添加進html文檔中。
首先進行安裝:
yarn add html-loader html-webpack-plugin --dev
再向webpack.base.config中添加如下配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const getPathByRoot = function(targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /.js$/,
use: ['babel-loader']
}, {
test: /.html/,
use: ['html-loader']
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
複製代碼
而後在項目根目錄下添加一個 index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>learn webpack</title>
</head>
<body>
</body>
</html>
複製代碼
此時運行打包命令,能夠看見輸出的文件下已經有了index.html文件,而且已經引入了打包後的js文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>learn webpack</title>
</head>
<body>
<script type="text/javascript" src="./static/index.js"></script></body>
</html>
複製代碼
在瀏覽器中打開index.html,已經能夠正常運行了。
預處理器做爲前端css高效的工具,不可不用,這一節教學sass的安裝與配置。首先安裝css sass的依賴以及style-loader。
yarn add node-sass sass-loader css-loader style-loader --dev
若是提示編譯失敗,須要安裝python2的話,就使用cnpm 安裝 node-sass
cnpm i node-sass --save-dev
其中style-loader的做用是,將處理完成的css文件使用style標籤內聯進html文檔中,以後會講解將css文件抽離出js文件。
安裝完成後進行webpack配置,以下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const getPathByRoot = function(targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /.js$/,
use: ['babel-loader']
}, {
test: /.html$/,
use: ['html-loader']
}, {
test: /.(sc|c)ss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
複製代碼
這裏咱們同時匹配了css 以及scss文件,由於scss是css的一個超集,因此sass-loader也能夠解析css文件,減小了沒必要要的配置。
再看看使用的loader的順序,值得注意的是,在這個數組中,webpack使用的loader是倒序執行的,也是就是先執行的sass-loader再執行的css-loader,其次纔是style-loader,要是反過來,是會報錯的哦。
執行 build 命令獲得如下代碼:
!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="./",n(n.s=0)}([function(t,e,n){"use strict";n.r(e);n(1);new function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),alert(1)}},function(t,e,n){var r=n(2);"string"==typeof r&&(r=[[t.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(4)(r,o);r.locals&&(t.exports=r.locals)},function(t,e,n){(t.exports=n(3)(!1)).push([t.i,".test {\n height: 100px; }\n .test .test1 {\n width: 100px; }\n",""])},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n=t[1]||"",r=t[3];if(!r)return n;if(e&&"function"==typeof btoa){var o=function(t){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t))))+" */"}(r),i=r.sources.map(function(t){return"/*# sourceURL="+r.sourceRoot+t+" */"});return[n].concat(i).concat([o]).join("\n")}return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},o=0;o<this.length;o++){var i=this[o][0];"number"==typeof i&&(r[i]=!0)}for(o=0;o<t.length;o++){var s=t[o];"number"==typeof s[0]&&r[s[0]]||(n&&!s[2]?s[2]=n:n&&(s[2]="("+s[2]+") and ("+n+")"),e.push(s))}},e}},function(t,e,n){var r={},o=function(t){var e;return function(){return void 0===e&&(e=t.apply(this,arguments)),e}}(function(){return window&&document&&document.all&&!window.atob}),i=function(t){var e={};return function(t,n){if("function"==typeof t)return t();if(void 0===e[t]){var r=function(t,e){return e?e.querySelector(t):document.querySelector(t)}.call(this,t,n);if(window.HTMLIFrameElement&&r instanceof window.HTMLIFrameElement)try{r=r.contentDocument.head}catch(t){r=null}e[t]=r}return e[t]}}(),s=null,a=0,u=[],f=n(5);function c(t,e){for(var n=0;n<t.length;n++){var o=t[n],i=r[o.id];if(i){i.refs++;for(var s=0;s<i.parts.length;s++)i.parts[s](o.parts[s]);for(;s<o.parts.length;s++)i.parts.push(b(o.parts[s],e))}else{var a=[];for(s=0;s<o.parts.length;s++)a.push(b(o.parts[s],e));r[o.id]={id:o.id,refs:1,parts:a}}}}function l(t,e){for(var n=[],r={},o=0;o<t.length;o++){var i=t[o],s=e.base?i[0]+e.base:i[0],a={css:i[1],media:i[2],sourceMap:i[3]};r[s]?r[s].parts.push(a):n.push(r[s]={id:s,parts:[a]})}return n}function p(t,e){var n=i(t.insertInto);if(!n)throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");var r=u[u.length-1];if("top"===t.insertAt)r?r.nextSibling?n.insertBefore(e,r.nextSibling):n.appendChild(e):n.insertBefore(e,n.firstChild),u.push(e);else if("bottom"===t.insertAt)n.appendChild(e);else{if("object"!=typeof t.insertAt||!t.insertAt.before)throw new Error("[Style Loader]\n\n Invalid value for parameter 'insertAt' ('options.insertAt') found.\n Must be 'top', 'bottom', or Object.\n (https://github.com/webpack-contrib/style-loader#insertat)\n");var o=i(t.insertAt.before,n);n.insertBefore(e,o)}}function d(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t);var e=u.indexOf(t);e>=0&&u.splice(e,1)}function h(t){var e=document.createElement("style");if(void 0===t.attrs.type&&(t.attrs.type="text/css"),void 0===t.attrs.nonce){var r=function(){0;return n.nc}();r&&(t.attrs.nonce=r)}return v(e,t.attrs),p(t,e),e}function v(t,e){Object.keys(e).forEach(function(n){t.setAttribute(n,e[n])})}function b(t,e){var n,r,o,i;if(e.transform&&t.css){if(!(i=e.transform(t.css)))return function(){};t.css=i}if(e.singleton){var u=a++;n=s||(s=h(e)),r=m.bind(null,n,u,!1),o=m.bind(null,n,u,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(t){var e=document.createElement("link");return void 0===t.attrs.type&&(t.attrs.type="text/css"),t.attrs.rel="stylesheet",v(e,t.attrs),p(t,e),e}(e),r=function(t,e,n){var r=n.css,o=n.sourceMap,i=void 0===e.convertToAbsoluteUrls&&o;(e.convertToAbsoluteUrls||i)&&(r=f(r));o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var s=new Blob([r],{type:"text/css"}),a=t.href;t.href=URL.createObjectURL(s),a&&URL.revokeObjectURL(a)}.bind(null,n,e),o=function(){d(n),n.href&&URL.revokeObjectURL(n.href)}):(n=h(e),r=function(t,e){var n=e.css,r=e.media;r&&t.setAttribute("media",r);if(t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){d(n)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else o()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=o()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var n=l(t,e);return c(n,e),function(t){for(var o=[],i=0;i<n.length;i++){var s=n[i];(a=r[s.id]).refs--,o.push(a)}t&&c(l(t,e),e);for(i=0;i<o.length;i++){var a;if(0===(a=o[i]).refs){for(var u=0;u<a.parts.length;u++)a.parts[u]();delete r[a.id]}}}};var y=function(){var t=[];return function(e,n){return t[e]=n,t.filter(Boolean).join("\n")}}();function m(t,e,n,r){var o=n?"":r.css;if(t.styleSheet)t.styleSheet.cssText=y(e,o);else{var i=document.createTextNode(o),s=t.childNodes;s[e]&&t.removeChild(s[e]),s.length?t.insertBefore(i,s[e]):t.appendChild(i)}}},function(t,e){t.exports=function(t){var e="undefined"!=typeof window&&window.location;if(!e)throw new Error("fixUrls requires window.location");if(!t||"string"!=typeof t)return t;var n=e.protocol+"//"+e.host,r=n+e.pathname.replace(/\/[^\/]*$/,"/");return t.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi,function(t,e){var o,i=e.trim().replace(/^"(.*)"$/,function(t,e){return e}).replace(/^'(.*)'$/,function(t,e){return e});return/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(i)?t:(o=0===i.indexOf("//")?i:0===i.indexOf("/")?n+i:r+i.replace(/^\.\//,""),"url("+JSON.stringify(o)+")")})}}]);
複製代碼
能夠看見sass文件已經轉成了css文件,而且內嵌進了js文件中,可是由於js要將css插入頁面渲染,因此多了一堆的代碼,這樣很不利於優化,因此咱們接下來將css文件抽離出js文件。
這裏咱們使用的是 mini-css-extract-plugin,首先進行安裝。
yarn add mini-css-extract-plugin --dev
安裝完成後,進行webpack配置,以下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const getPathByRoot = function(targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'static/css/main.[hash].css'
})
]
}
複製代碼
在plugins中實例化了 MiniCssExtractPlugin 而且設置了打包後的名稱,以及在rules中修改了最終的loader。
執行打包命令後,能夠看見,dist目錄下static目錄中多了一個index.css文件,而且index.html也自動引入了這個css文件,而且js文件的體積也小了不少。
對於文件的處理,可使用 url-loader 和 file-loader,url-loader 能夠將很小的icon文件轉換成base64編碼內聯進js文件中,這樣能夠減小http的請求次數,file-loader 能夠制定一個可讓文件輸出的路徑便於管理,更加好用的功能是可使用import 或者 require 圖片文件。首先安裝依賴:
yarn add file-loader url-loader --dev
其次進行webpack的配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const getPathByRoot = function (targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}, {
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash].[ext]',
outputPath: 'static/images',
fallback: 'file-loader',
}
}
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'static/css/main.[hash].css'
})
]
}
複製代碼
能夠看見首先對於文件,使用了url-loader,若是文件體積小於8192b 也就是 8kb 左右,那麼url-loader會將圖片轉換爲base64內聯進js文件中,若是文件大於設定的值,那麼就會將文件交給file-loader處理,而且將options選項交給file-loader這個是url的使用方式。
作一個實驗,在index.html文件中添加一個img標籤,執行打包。若是圖片大於8kb圖片會被存儲到dist/static/images目錄下。
若是文件小於8kb,那麼文件會被內聯到Html或者js中,這取決因而哪一個類型的文件引用了這張圖片。
到這一步,一個項目最基礎的幾個功能都已經配置完成了,可是隻是這樣怎麼行,無法高效率的開發,接下來就要介紹webpack-dev-server的用法了。
熱模塊加載依賴於 webpack-dev-server,首先安裝
yarn add webpack-dev-server --dev
接下來進行webpack的配置:
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const getPathByRoot = function (targetPath) {
path.resolve(__dirname, '../', targetPath)
}
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /\.(sc|c)ss$/,
// use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
use: ['style-loader', 'css-loader', 'sass-loader']
}, {
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash].[ext]',
outputPath: 'static/images',
fallback: 'file-loader',
}
}
]
}]
},
devtool: 'inline-source-map',
devServer: {
host: '0.0.0.0',
publicPath: '/',
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'static/css/index.[hash].css'
}),
new webpack.HotModuleReplacementPlugin()
]
}
複製代碼
首先require了webpack模塊,由於 HotModuleReplacementPlugin 是webpack內置的插件,因此不用引入。
其次將css解析的loader中,移除了 MiniCssExtractPlugin.loader ,改爲了 style-loader ,將樣式內聯進html文檔,這樣作是爲了熱更新,若是是用了抽離css的 MiniCssExtractPlugin.loader ,就沒法熱更新了(以前配置是能夠熱更新的,但此次遇到了未知的狀況,因此換一個處理方式),不過這個問題,後期能夠將開發以及生產的webpack分開配置來實現不一樣的需求。
而後添加了這些配置項
{
devtool: 'inline-source-map',
devServer: {
host: '0.0.0.0',
publicPath: '/',
hot: true,
}
}
複製代碼
inline-source-map 的做用爲將js文件打包後同時生成source map文件用於開發時的debugger,生產環境下建議不配置,後期也會將它抽離出公共的配置。 devServer 是用於配置開發環境下的配置項,更多的參數能夠查看官方文檔,這裏不作詳細介紹。
最後配置package.json的script,用於快速啓動開發服務:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/webpack.base.js --mode=production",
"dev": "webpack-dev-server --config ./build/webpack.base.js --mode=development --color --open"
},
複製代碼
此時運行
npm run dev
打開控制檯提示的運行的地址
Project is running at http://localhost:8080/
打開這個地址就可以看到站點了,同時修改js文件以及css文件,能夠看見頁面的同步更改。
完成上面的步驟基本上已經能夠進行開發和生產了,可是對於工程的優化可不能停下腳步,因此得進行如下步驟。
就像以前說過的,維護一個公共的配置項,非公共的配置項抽離成幾個不一樣的功能以適應不一樣的需求。這裏須要使用到webpack-merge功能,先進行依賴的安裝
yarn add webpack-merge --dev
新建三個文件,分別是 webpack.pro.js 用於配置打包生產環境的文件, webpack.dev.js 用於配置開發環境的文件,utils 用於抽離公共的工具函數。文件以下:
// utils.js
const path = require('path')
module.exports.getPathByRoot = function (targetPath) {
return path.resolve(__dirname, '../', targetPath)
}
複製代碼
// webpack.base.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const getPathByRoot = require('./utils').getPathByRoot
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash].[ext]',
outputPath: 'static/images',
fallback: 'file-loader',
}
}
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
複製代碼
// webpack.pro.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
module: {
rules: [{
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/index.[hash].css'
})
]
})
複製代碼
// webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base')
module.exports = merge(webpackBaseConfig, {
module: {
rules: [{
test: /\.(sc|c)ss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
},
devtool: 'inline-source-map',
devServer: {
host: '0.0.0.0',
publicPath: '/',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
複製代碼
修改package.json的script:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/webpack.pro.js --mode=production",
"dev": "webpack-dev-server --config ./build/webpack.dev.js --mode=development --color --open"
}
複製代碼
主要是修改了使用的配置文件爲pro 和 dev 文件。
每次打包若是文件修改了,那麼就會修改文件的hash值,因此文件不會存在衝突的狀況,因此上一次打包的文件還會存在於dist目錄,這樣會形成打包後的文件過大不便於管理,也有可能由於瀏覽器緩存的緣由,請求的是以前的文件,因此得將以前的打包文件刪除掉,因此這裏咱們須要使用到 clean-webpack-plugin,首先安裝依賴。
yarn add clean-webpack-plugin --dev
而後修改配置項:
// webpack.pro.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base')
const WebpackCleanPlugin = require('clean-webpack-plugin')
const getPathByRoot = require('./utils').getPathByRoot
module.exports = merge(webpackBaseConfig, {
module: {
rules: [{
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/index.[hash].css'
}),
new WebpackCleanPlugin(getPathByRoot('./dist'), {
allowExternal: true
})
]
})
複製代碼
運行打包命令,能夠看見在打包以前,刪除了dist目錄。
gzip的壓縮,能夠極大的減小http請求的size,優化站點的加載速度,需進行如下配置:
先安裝依賴 compression-webpack-plugin
yarn add compression-webpack-plugin@1.1.12 --dev
這裏指定版本號爲1.1.12,由於2.0的版本在我這個環境下報錯了,因此使用1.1.12
其次修改webpack配置:
//webpack.pro.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base')
const WebpackCleanPlugin = require('clean-webpack-plugin')
const CompressionPlugin = require("compression-webpack-plugin")
const getPathByRoot = require('./utils').getPathByRoot
module.exports = merge(webpackBaseConfig, {
module: {
rules: [{
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}]
},
plugins: [
new CompressionPlugin({
test: [/\.js$/, /\.css$/],
asset: '[path].gz',
algorithm: 'gzip'
}),
new MiniCssExtractPlugin({
filename: 'static/css/main.[hash].css'
}),
new WebpackCleanPlugin(getPathByRoot('./dist'), {
allowExternal: true
})
]
})
複製代碼
完成配置後就能夠打包出gzip文件了,不過還得nginx服務器打開gzip的支持才行。
使用 progress-bar-webpack-plugin 插件,首先安裝
yarn add progress-bar-webpack-plugin --dev
其次修改webpack配置:
// webpack.base.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const getPathByRoot = require('./utils').getPathByRoot
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
exclude: /\/node_modules/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash].[ext]',
outputPath: 'static/images',
fallback: 'file-loader',
}
}
]
}]
},
plugins: [
new ProgressBarPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
複製代碼
進度條就添加了。
由於是模塊式的開發,對於jquery vue等庫的文件,須要屢次引入,若是不提取公共依賴項,那麼就會致使每一個js文件中都會有這兩個庫的文件。若是提取出來,將會大大減小文件的體積,優化瀏覽器的下載速度,並且能夠將公共依賴項設置成強緩存,能夠進一步減小http請求的開支,下面咱們就添加這個功能,代碼以下:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const getPathByRoot = require('./utils').getPathByRoot
module.exports = {
entry: getPathByRoot('src/index.js'),
output: {
filename: 'static/js/index.[hash].js',
path: getPathByRoot('dist'),
publicPath: './'
},
module: {
rules: [{
test: /\.js$/,
exclude: /\/node_modules/,
use: ['babel-loader']
}, {
test: /\.html$/,
use: ['html-loader']
}, {
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[hash].[ext]',
outputPath: 'static/images',
fallback: 'file-loader',
}
}
]
}]
},
optimization: {
splitChunks: {
chunks: 'all',
name: 'commons',
filename: 'static/js/[name].[hash].js'
}
},
plugins: [
new ProgressBarPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
複製代碼
由於在webpack4.0中提取公共文件已是內置功能了,因此不須要插件。只須要在配置文件中插入這樣的一個配置就行:
optimization: {
splitChunks: {
chunks: 'all',
name: 'commons',
filename: 'static/js/[name].[hash].js'
}
},
複製代碼
安裝jqery 和 vue進行一次測試
yarn add jquery vue
而後在src/index.js中引入這兩個庫文件。
import './index.scss'
import $ from 'jquery'
import Vue from 'vue'
new Vue({
el: '#app'
})
$('#app').on('click', function() {
})
複製代碼
運行打包命令後查看js文件會發現多出了一個common.js文件,打開能夠看見內容只包含了jquery 和 vue 的源碼,打開打包後的index.html也能夠看見引入了common.js文件,說明提取公共依賴項成功了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="./static/css/main.bf73755b575b70e4fc39.css" rel="stylesheet"></head>
<body>
<img src="./static/images/WechatIMG5.72047b6d3b0f1c1f1dc0a1fa9c81188f.png" alt="">
<script type="text/javascript" src="./static/js/commons.bf73755b575b70e4fc39.js"></script>
<script type="text/javascript" src="./static/js/index.bf73755b575b70e4fc39.js"></script>
</body>
</html>
複製代碼
使用 webpack-bundle-analyzer 插件,能夠查看到打包後的js文件的內部細節,能夠很清楚的分析哪些細節能夠優化,首先安裝依賴。
yarn add webpack-bundle-analyzer --dev
而後進行webpack配置
//webpack.pro.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const webpackBaseConfig = require('./webpack.base')
const WebpackCleanPlugin = require('clean-webpack-plugin')
const CompressionPlugin = require("compression-webpack-plugin")
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const getPathByRoot = require('./utils').getPathByRoot
module.exports = merge(webpackBaseConfig, {
module: {
rules: [{
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}]
},
plugins: [
new CompressionPlugin({
test: [/\.js$/, /\.css$/],
asset: '[path].gz',
algorithm: 'gzip'
}),
new MiniCssExtractPlugin({
filename: 'static/css/main.[hash].css'
}),
new WebpackCleanPlugin(getPathByRoot('./dist'), {
allowExternal: true
}),
new BundleAnalyzerPlugin()
]
})
複製代碼
運行打包後會打開瀏覽器有如下界面,能夠很清楚的看見js文件的內容:
借用官方文檔的動圖,能夠看見更直觀的細節:
到這裏,基本的配置都已經完成了,若是你想實現更多的功能,就須要本身去探索啦。 工程地址:github.com/Richard-Cho…
例外有兩個以前配置的webpack腳手架能夠看一看:
用於vue組件系統開發的webpack配置: github.com/Richard-Cho…
多頁面開發的webpack配置:github.com/Richard-Cho…