針對webpack,是你們(前端開發)在平常的開發中都會碰見的,經過書寫的方式輸出,學習到的關於前端工程化的小知識點的總結和學習,造成本身的知識體系css
webpack官網定義:html
webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。
在開始瞭解webpack配置前,首先須要理解四個核心概念:前端
// npm 安裝 npm install webpack webpack-cli -g // yarn 安裝 yarn global add webpack webpack-cli
安裝好後,能夠在不適用配置文件的方法,直接對文件進行打包:webpack <entry> [<entry>] -o <output>
vue
新建一個項目,就一個入口文件,測試webpack打包:node
運行打包命令:webpack index.js
jquery
在這裏咱們會看見一個WARNING
的信息,這是由於沒有設置mode
,咱們只須要加一個參數-p
便可:webpack
webpack -p index
這樣默認會生成一個dist
文件夾,裏面有個main.js
文件:web
有了入口文件咱們還須要經過命令行定義一下輸入路徑dist/bundle.js
:面試
webpack -p index.js -o dist/bundle.js
命令行的打包構建方式僅限於簡單的項目,若是在生產中,項目複雜,多個入口,咱們就不可能每次打包都輸入一連串的入口文件地址,也難以記住;所以通常項目中都使用配置文件來進行打包;配置文件的命令方式以下:express
webpack [--config webpack.config.js]
配置文件默認的名稱就是webpack.config.js
,一個項目中常常會有多套配置文件,咱們能夠針對不一樣環境配置不一樣的額文件,經過--config
來進行更換:
// 開發環境 webpack --config webpack.config.dev.js // 生產環境 webpack --config webpack.config.prod.js
config
配置文件經過module.exports
導出一個配置對象:
// webpack.config.js const path = require('path') const resolve = function (dir) { return path.resolve(__dirname, dir) } module.exports = { entry: { app: resolve('../index.js') }, output: { filename: '[name].[hash:8].js', path: resolve('../dist') }, }
除了導出爲對象,還能夠導出爲一個函數,函數中會帶入命令行中傳入的環境變量等參數,這樣能夠更方便的對環境變量進行配置;好比咱們能夠經過ENV
來區分不一樣環境:
const path = require('path') const resolve = function (dir) { return path.resolve(__dirname, dir) } module.exports = function(ENV, argv) { return { // 其餘配置 entry: resolve('../index.js'), output: {} } }
還能夠導出爲一個Promise,用於異步加載配置,好比能夠動態加載入口文件:
entry: () => './demo' 或 entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))
正如在上面提到的,入口是整個依賴關係的起點入口;咱們經常使用的單入口配置是一個頁面的入口:
module.exports = { entry: resolve('../index.js') }
可是咱們項目中可能不止一個模塊,所以須要將多個依賴文件一塊兒注入,這時就須要用到數組了:
module.exports = { entry: [ '@babel/polyfill', resolve('../index.js') ] }
若是咱們項目中有多個入口起點,則就須要用到對象形式了:
// webpack 就會構建兩個不一樣的依賴關係 module.exports = { entry: { app: resolve('../index.js'), share: resolve('../share.js') } }
output
選項用來控制webpack如何輸入編譯後的文件模塊;雖然能夠有多個entry
,可是隻能配置一個output
:
module.exports = { entry: resolve('../index.js'), output: { filename: 'index.js', path: resolve('../dist') }, }
這裏咱們配置了一個單入口,輸出也就是index.js
;可是若是存在多入口的模式就行不通了,webpack會提示Conflict: Multiple chunks emit assets to the same filename
,即多個文件資源有相同的文件名稱;webpack提供了佔位符
來確保每個輸出的文件都有惟一的名稱:
module.exports = { entry: { app: resolve('../index.js'), share: resolve('../index.js'), }, output: { filename: '[name].bundle.js', path: resolve('../dist') }, }
這樣webpack打包出來的文件就會按照入口文件的名稱來進行分別打包生成三個不一樣的bundle文件;還有如下不一樣的佔位符字符串:
佔位符 | 描述 |
---|---|
[hash] | 模塊標識符(module identifier)的 hash |
[chunkhash] | chunk 內容的 hash |
[name] | 模塊名稱 |
[id] | 模塊標識符 |
[query] | 模塊的 query,例如,文件名 ? 後面的字符串 |
在這裏引入module
、chunk
和bundle
的概念,上面代碼中也常常會看到有這兩個名詞的出現,那麼他們三者到底有什麼區別呢?首先咱們發現module
是常常出如今咱們的代碼中,好比module.exports
;而chunk
常常和entry
一塊兒出現,bundle
老是和output
一塊兒出現。
咱們經過下面這張圖看能夠加深對這三個概念的理解:
理解了chunk的概念,相信上面表中chunkhash和hash的區別也很容易理解了;
在webpack2和webpack3中咱們須要手動加入插件來進行代碼的壓縮、環境變量的定義,還須要注意環境的判斷,十分的繁瑣;在webpack4中直接提供了模式這一配置,開箱便可用;若是忽略配置,webpack還會發出警告。
module.exports = { mode: 'development/production' }
開發模式是告訴webpack,我如今是開發狀態,也就是打包出來的內容要對開發友好,便於代碼調試以及實現瀏覽器實時更新。
生產模式不用對開發友好,只須要關注打包的性能和生成更小體積的bundle。看到這裏用到了不少Plugin,不用慌,下面咱們會一一解釋他們的做用。
相信不少童鞋都曾有過疑問,爲何這邊DefinePlugin
定義環境變量的時候要用JSON.stringify("production")
,直接用"production"
不是更簡單嗎?
咱們首先來看下JSON.stringify("production")
生成了什麼;運行結果是""production"
",注意這裏,並非你眼睛花了或者屏幕上有小黑點,結果確實比"production"
多嵌套了一層引號。
咱們能夠簡單的把DefinePlugin
這個插件理解爲將代碼裏的全部process.env.NODE_ENV
替換爲字符串中的內容。假如咱們在代碼中有以下判斷環境的代碼:
// webpack.config.js module.exports = { plugins: [ new webpack.DefinePlugin({ "process.env.NODE_ENV": "production" }), ] } // index.js if (process.env.NODE_ENV === 'production') { console.log('production'); }
這樣生成出來的代碼就會編譯成這樣:
//dist/bundle.js //代碼中並無定義production變量 if (production === 'production') { console.log('production'); }
可是咱們代碼中可能並無定義production
變量,所以會致使代碼直接報錯,因此咱們須要經過JSON.stringify
來包裹一層:
//webpack.config.js module.exports = { plugins: [ new webpack.DefinePlugin({ //"process.env.NODE_ENV": JSON.stringify("production") //至關於 "process.env.NODE_ENV": '"production"' }), ] } //dist/bundle.js if ("production" === 'production') { console.log('production'); }
在上面的代碼中咱們發現都是手動來生成index.html,而後引入打包後的bundle文件,可是這樣太過繁瑣,並且若是生成的bundle文件引入了hash值,每次生成的文件名稱不同,所以咱們須要一個自動生成html的插件;首先咱們須要安裝這個插件:yarn add html-webpack-plugin -D 或者 npm install html-webpack-plugin -D
使用:
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // 其餘代碼 plugins: [ new HtmlWebpackPlugin({ // 模板文件 template: resolve('../public/index.html'), // 生成的html名稱 filename: 'index.html', // icon favicon: resolve('../public/logo.ico') }), ] }
loader 用於對模塊的源代碼進行轉換。默認webpack只能識別commonjs代碼,可是咱們在代碼中會引入好比vue、ts、less等文件,webpack就處理不過來了;loader拓展了webpack處理多種文件類型的能力,將這些文件轉換成瀏覽器可以渲染的js、css。
module.rules
容許咱們配置多個loader,可以很清晰的看出當前文件類型應用了哪些loader。
module.exports = { module: { rules: [ { test: /\.css$/, use: 'css-loader' }, { test: /\.ts$/, use: 'ts-loader' } ] } };
loader 特性
loader 經過(loader)預處理函數,爲 JavaScript 生態系統提供了更多能力。 用戶如今能夠更加靈活地引入細粒度邏輯,例如壓縮、打包、語言翻譯和其餘更多。
兼容低版本瀏覽器的痛相信不少童鞋都經歷過,寫完代碼發現本身的js代碼不能運行在IE10或者IE11上,而後嘗試着引入各類polyfill;babel的出現給咱們提供了便利,將高版本的ES6甚至ES7轉爲ES5;咱們首先安裝babel所須要的依賴:yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
因爲babel-loader的轉譯速度很慢,在後面咱們加入了時間插件後能夠看到每一個loader的耗時,babel-loader是最耗時間;所以咱們要儘量少的使用babel來轉譯文件,正則上使用$來進行精確匹配,經過exclude將node_modules
中的文件進行排除,include
將只匹配src
中的文件;能夠看出來include的範圍比exclude更縮小更精確,所以也是推薦使用include。
// 省略其餘代碼 module: { rules: [ { test: /\.js$/, exclude: /node_modules/, include: [resolve('src')] use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { targets: "defaults" }] ], plugins: ['@babel/plugin-proposal-class-properties'] } } } ] }
file-loader
和url-loader
都是用來處理圖片、字體圖標等文件;url-loader
工做時分兩種狀況:當文件大小小於limit參數,url-loader
將文件轉爲base-64編碼,用於減小http請求;當文件大小大於limit參數時,調用file-loader
進行處理;所以咱們優先使用url-loader
。
module: { rules: [ { test: /\.(jpe?g|png|gif)$/i, //圖片文件 use: [ { loader: 'url-loader', options: { // 10K limit: 1024, //資源路徑 outputPath: resolve('../dist/images') }, } ], exclude: /node_modules/ }, ] }
在上面咱們都是經過命令行打包生成 dist 文件,而後直接打開html或者經過static-server來查看頁面的;可是開發中咱們寫完代碼每次都來打包會嚴重影響開發的效率,咱們指望的是寫完代碼後當即就可以看到頁面的效果;webpack-dev-server
就很好的提供了一個簡單的web服務器,可以實時從新加載。
webpack-dev-server
的用法和wepack
同樣,只不過他會額外啓動一個express
的服務器。咱們在項目中webpack.config.dev.js
配置文件對開發環境進行一個配置:
module.exports = { mode: 'development', plugins: [ new Webpack.HotModuleReplacementPlugin() ], devtool: 'cheap-module-eval-source-map', devServer: { // 端口 port: 3300, // 啓用模塊熱替換 hot: true, // 自動打開瀏覽器 open: true, // 設置代理 proxy:{ "/api/**":{ "target":"http://127.0.0.1:8075/", "changeOrigin": true } } } }
經過命令行webpack-dev-server
來啓動服務器,啓動後咱們發現根目錄並無生成任何文件,由於webpack
打包到了內存中,不生成文件的緣由在於訪問內存中的代碼比訪問文件中的代碼更快。
咱們在public/index.html
的頁面上有時候會引用一些本地的靜態文件,直接打開頁面的會發現這些靜態文件的引用失效了,咱們能夠修改server
的工做目錄,同時指定多個靜態資源的目錄:
contentBase: [ path.join(__dirname, "public"), path.join(__dirname, "assets") ]
熱更新(Hot Module Replacemen簡稱HMR)是在對代碼進行修改並保存以後,webpack對代碼從新打包,而且將新的模塊發送到瀏覽器端,瀏覽器經過新的模塊替換老的模塊,這樣就能在不刷新瀏覽器的前提下實現頁面的更新。
上面介紹了DefinePlugin、HtmlWebpackPlugin等不少插件,咱們發現這些插件都可以不一樣程度的影響着webpack的構建過程,下面還有一些經常使用的插件:
clean-webpack-pluginclean-webpack-plugin
用於在打包前清理上一次項目生成的bundle文件,它會根據output.path自動清理文件夾;這個插件在生產環境用的頻率很是高,由於生產環境常常會經過hash生成不少bundle文件,若是不進行清理的話每次都會生成新的,致使文件夾很是龐大。
const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { plugins: [ new CleanWebpackPlugin(), ], }
mini-css-extract-plugin
咱們在使用webpack構建工具的時候,經過style-loader
,能夠把解析出來的css經過js插入內部樣式表的方式到頁面中,mini-css-extract-plugin插件也是用來提取css到單獨的文件的,該插件有個前提條件,只能用於webpack 4及以上的版本,因此若是使用的webpack版本低於4,,那仍是用回extract-text-webpack-plugin插件。
const MiniCssExtractPlugin = require("mini-css-extract-plugin") module.exports = { // 省略其餘代碼 module: { rules: [ { test: /\.less$/, use: [ { loader: dev ? 'style-loader': MiniCssExtractPlugin.loader }, { loader: 'css-loader' }, { loader: 'less-loader' } ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].[hash:8].css", }) ] }
copy-webpack-plugin
咱們在public/index.html中引入了靜態資源,可是打包的時候webpack並不會幫咱們拷貝到dist目錄,所以copy-webpack-plugin就能夠很好地幫我作拷貝的工做了。
const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin([{ from: path.resolve(__dirname, '../static'), to: path.resolve(__dirname, '../dist/static') }]) ], }
ProvidePlugin
ProvidePlugin
能夠很快的幫咱們加載想要引入的模塊,而不用require
。通常咱們加載jQuery
須要先把它import
:
import $ from 'jquery' $('#layout').html('test')
可是咱們在config中配置ProvidePlugin
插件後可以不用import,直接使用$
:
module.exports = { plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), ] }
在項目中引入了太多模塊而且沒有require
會讓人摸不着頭腦,所以建議加載一些常見的好比jQuery、vue、lodash等。