因爲web應用擴展地得極其迅猛,前端技術也是突飛猛進,前端的苦不是有多難學,而是我剛學完,這東西就被淘汰了(手動哭臉)。框架方面咱們有vue、react、angular,咱們須要寫vue單文件,也須要寫jsx語法;js方面咱們有Typescript、es6(七、八、9),如今是每一年一個小版本的迭代,語法也是在不斷更新和淘汰;模塊方面咱們有es6的modlue、CommonJS、AMD;css方面咱們有sass、less、postcss。新技術層出不窮,瀏覽器實現跟不上,還要考慮到瀏覽器兼容性問題,是一個使人頭大的問題。可是webpack能夠輕鬆幫咱們解決這些問題,咱們能夠在webpack的世界中放心地使用上述提到的技術,以更方便、舒服的方式完成咱們開發。css
At its core, webpack is a static module bundler for modern JavaScript applications.
這是官網的定義,webpack就是一個靜態模塊的打包工具。webpack能夠將工程中的靜態資源根據咱們聲明的依賴關係,打包出最後的正常運行的輸出文件。官網顯示的這幅圖很形象地描述了這個過程:html
在webpack中,全部的靜態資源均可以被處理爲一個模塊,包括js、圖片、css、字體。模塊化是前端開發的主要模式,模塊化的好處有不少,我想到的有如下3種:前端
在es6以前,原生js是不支持模塊化開發。由於js設計之初,只是爲了實現表單驗證等簡單的交互,並無考慮到要用js來寫大型的web應用。隨着js承擔地職責愈來愈大,模塊化開發的需求愈來愈急迫。因而,js社區誕生出了CommonJS、AMD這樣的js模塊化標準,隨後在es6中,也終於加入了module的原生支持。如今最新版本的各大瀏覽器均已實現了對es6的module語法支持,但也僅限於最新的幾個版本,詳情能夠查看一下can i ues。咱們能夠把webpack當成是模塊化標準的實現方案,但webpack的功能不只限於此。vue
webpack支持多種模塊使用方式,包括es6的module、CommonJS、AMD。推薦使用es6的module語法,一方面是由於它是標準,是之後模塊化語法的主要使用方式;另外一方面,是由於它是靜態的,webpack能夠依靠靜態分析,作一些優化,好比Treeshaking。webpack自帶js模塊處理功能,其餘類型的靜態資源咱們須要經過配置相應的loader去處理。node
webpack中最重要的概念有如下幾個:react
webpack通常根據配置文件去執行打包任務,咱們建立一個webpack.config.js文件來編寫咱們的打包配置。jquery
Entry,顧名思義就是工程的入口文件,Entry的配置寫法有三種:webpack
module.exports = { entry: { app: './src/app.js', vendors: './src/vendors.js' } };
module.exports = { entry: './path/to/my/entry/file.js' };
module.exports = { entry: ['./path/to/my/entry/file.js', './path/to/my/entry/file1.js'] };
入口通常用對象寫法便可,其餘兩種寫法的可忽略。git
Output用於配置打包輸出的文件,包括輸出文件的文件名、輸出路徑、靜態資源地址,這裏列出最經常使用的4種:es6
module.exports = { entry: { app: './src/app.js', search: './src/search.js' }, output: { filename: 'js/[name].js', chunkFilename: 'js/[name].js', path: __dirname + '/dist', publicPath: 'http://cdn.example.com/assets/[hash]/' } };
配置項以下:
filename: 配置輸出文件名,可添加路徑配置(例子中js/),可以使用佔位符,佔位符有如下5種:
?
後面的內容publicPath: 用於設置打包過程當中產生的靜態文件的最終引用地址,靜態文件的最終引用地址爲output.publicPath + output.filename
,不少時候,你的靜態文件放置在CDN上,經過publicPath就能夠很方便地設置。若是你的靜態引用地址在運行時才能肯定,能夠在入口文件中設置__webpack_public_path__
來決定publicPath的值:
__webpack_public_path__ = myRuntimePublicPath; // rest of your application entry
CommonsChunkPlugin
中產生,這是一個Plugin,用於抽取公共代碼或者進行代碼分割等操做,該plugin已經在webpack4中廢除,由webpac4內置的optimization.splitChunks
替代,後面會講到output還有其餘不少配置,這4個是經常使用配置。
Loaders能夠理解爲不一樣類型模塊的處理器,將這些類型的模塊處理爲瀏覽器可運行和識別的代碼。好比babel-loader將es6以上代碼轉換爲es5代碼;sass-loader將sass代碼解析爲css代碼;url-loader和file-loader能夠將圖片、字體等靜態文件解析爲base64碼或者靜態文件地址。Loaders給咱們提供了處理模塊的入口,在裏面可使用所有的js功能,從而使webpack具備了強大而靈活的能力。webpack及webpack社區提供了功能強大的loader供開發者使用,你也能夠本身編寫loader。下面介紹一下在工程中經常使用的loader。
使用babel將ES2015+的代碼轉碼爲ES5的代碼,babel的具體配置可參考babel官網
modlue: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: 'babel-loader } ] }
exclude表示不處理的目錄,通常node_modules
中的第三方js文件不在咱們的處理範圍內。
該loader使對應的js文件在全局環境中運行一次。好比咱們在工程中使用了jquery的插件,須要全局暴露$,咱們就須要讓jquery文件在全局環境中運行,以便讓它把$掛載到全局變量中,效果和在瀏覽器中加script標籤同樣。
import 'jquery'; module: { rules: [ { test: /jquery$/, use: [ 'script-loader' ] } ] }
解析一個sass文件,並不僅須要一個loader,它須要多個loader串行處理,webpack能夠配置多個loader串行處理:
module: { rules: [ { test: /\.sass$/, use: [ 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ] } ] }
咱們能夠將use配置爲一個數組,loader從右往左依次執行,且前一個loader的結果是下一個loader的輸入。最後一個loader的輸出就是咱們最終要的結果。一個sass文件首先通過sass-loader
處理,變成css文件,又通過postcss-loader
處理,添加瀏覽器前綴等功能,接着交給css-loader
去解析css文件引用的靜態變量,最後由style-loader
以script
標籤的形式加入到html中。
url-loader
和file-loader
是一對用來處理圖片、svg、視頻、字體等靜態資源文件的loader。通常體積比較小的資源交給url-loader
處理,編碼爲base64字符串,直接嵌入js文件中。體積較大的文件由file-loader處理,直接釋放爲了一個輸出文件。
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'img/[name].[hash:7].[ext]' } },
通常只配置url-loader
便可,在資源超過limit的時候,url-loader
會將資源自動交給file-loader
處理,並將options內容也傳遞給file-loader。
loaders用來轉換某種特定類型的module,plugins則用來在一些合適的時機執行一些特定的任務,好比代碼分割、靜態資源處理、環境變量的注入、將全部css的module抽取爲單個文件等。webpack自身也是用插件系統構建起來的。插件的目的是作任何loaders作不了的事情。
HtmlWebpackPlugin插件能夠用來生成包含你全部打包文件(js和css)的html文件,特別是你在打包文件名配置了hash,就不得不用這個插件了。
module.exports = { plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src/index.html'), filename: 'static/index.[hash].html', inject: true, minify: false, chunks: ['app', 'vendor'] }) ] };
MiniCssExtractPlugin
將一個chunk中的css抽取爲一個單獨的css文件,若是chunk中不包含css,則不生成文件。且支持按需加載和sourceMap。webpack4新增插件,在webpack4以前是使用ExtractTextWebpackPlugin
來作這件事。官方文檔總結了MiniCssExtractPlugin
相對ExtractTextWebpackPlugin
的四個優點:
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const devMode = process.env.NODE_ENV !== 'production' module.exports = { plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: "[name].css", chunkFilename: "[id].css" }) ], module: { rules: [ { test: /\.css$/, use: [ devMode ? 'style-loader' : { // MiniCssExtractPlugin目前尚未HMR功能,因此最好只在生成環境使用 loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' // 可單獨配置publicPath,默認採用output中的publicPath } }, "css-loader" ] } ] } }
CopyWebpackPlugin用來處理靜態文件,能夠將文件或者文件夾原封不動地移動到打包目錄。
const CopyWebpackPlugin = require('copy-webpack-plugin') const config = { plugins: [ new CopyWebpackPlugin([ { from: 'source', to: 'dest' }, { from: 'source', to: 'dest', toType: 'dir|file|template' }, // 手動設置to的類型,好比設置成dir,即便to設置爲a.js,最後也會生成a.js文件夾 { from: 'source', to: 'dest', context: '/app' }, // 基準目錄,from相對於context解析 { from: 'source', to: 'dest', ignore: ['*.js'] }, // ignore, 忽略匹配的文件 'source' // 只有from,to默認爲output的path ], { context: '/app', // 同上 ignore: ['*.js'], // 同上 }) ] }
CleanWebpackPlugin用來清除打包目錄,主要用於每次從新生成的時候,清除殘留文件。在文件有hash值的狀況下,是必要的。
const CleanWebpackPlugin = require('clean-webpack-plugin'); const config = { plugins: [ new CleanWebpackPlugin(['dist', 'bulid/*.js'], { watch: false, // 是否在--watch模式下也清除files而後從新編譯,默認爲false exclude: [ 'files', 'svg', 'css' ] // 不刪除的子目錄和文件 }), ] }
DefinePlugin用來定義webpack編譯期間的全局變量。咱們能夠根據這些變量,來作不一樣的動做。最典型的就是能夠區分開發環境和生產環境,好比在開發環境打印各類警告、錯誤,在生產環境去掉這些跟業務無關的代碼。
DefinePlugin的參數是一個對象,鍵名是一個標識符,或者用.
隔開的多級標識符,參數遵循如下規則
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production'), PRODUCTION: JSON.stringify(true), VERSION: JSON.stringify('5fa3b9'), BROWSER_SUPPORTS_HTML5: true, TWO: '1+1', 'typeof window': JSON.stringify('object') });
index.js
console.log('PRODUCTION', PRODUCTION); console.log('VERSION', VERSION); console.log('BROWSER_SUPPORTS_HTML5', BROWSER_SUPPORTS_HTML5); console.log('TWO', TWO); console.log('Object', typeof window);
被編譯爲
console.log('PRODUCTION', true); console.log('VERSION', "5fa3b9"); console.log('BROWSER_SUPPORTS_HTML5', true); console.log('TWO', 1+1); console.log('Object', false ? undefined : _typeof(window)); // 不太明白
DefinePlugin的原理很簡單,只是在編譯過程當中遇到這些定義好的鍵名,就用鍵值作簡單的文本替換。因此,你若是想給全局變量賦一個字符串,須要這樣寫'"production"'
,通常使用JSON.stringify
來轉一下。
webpack4新增了mode
配置。webpack會根據mode
值自動幫你作一個不一樣的優化:
production(默認值)
DefinePlugin
中將process.env.NODE_ENV
設置爲production
FlagDependencyUsagePlugin
, FlagIncludedChunksPlugin
, ModuleConcatenationPlugin
, NoEmitOnErrorsPlugin
, OccurrenceOrderPlugin
, SideEffectsFlagPlugin
and UglifyJsPlugin
development:
DefinePlugin
中將process.env.NODE_ENV
設置爲development
NamedChunksPlugin
, NamedModulesPlugin
mode
的兩種使用方式
module.exports = { mode: 'production' };
webpack --mode=production
代碼分割能夠把代碼按照必定的邏輯分割,用來作按需加載或者並行加載,以減小加載時間。在webpack中,主要有如下3中方式,實現代碼分割:
entry
入口配置處配置多個入口import()
語法,webpack使用SplitChunks
將import()
加載的module分割爲一個單獨的chunk,並在函數執行時加載該chunk對應的js文件。第一種沒什麼好說的,主要說一下後兩種。
在webpack4以前的版本,都是使用CommonsChunkPlugin
來抽取公共chunk,在webpack4中廢棄了CommonsChunkPlugin
,轉而支持內置的optimization.splitChunks
。
splitChunks
默認只對按需加載的chunk起做用,會自動將import()引入的module分割爲單獨chunk,可是須要知足如下條件:
node_modules
在執行後兩個原則的時候,體積大的chunk被優先生成。
splitChunks
的默認配置以下,咱們能夠看出正好是和上面4個條件對應的:
module.exports = { optimization: { splitChunks: { chunks: 'async', minSize: 30000, // chunk只有超過這個大小纔會被分割 maxSize: 0, // 大於這個體積的chunk會被自動分割爲更小的chunk minChunks: 1, // 一個模塊被共享的chunk數量大於minChunks時,纔會被分割出來 maxAsyncRequests: 5, // 按需加載最大的並行數 maxInitialRequests: 3, // 初始加載最大的並行數 automaticNameDelimiter: '~', // name爲true時,新chunk的文件名由cacheGroups的key加上chunks屬性的一些信息生成,automaticNameDelimiter是分隔符 name: true, cacheGroups: { // 配置拆分規則,會繼承splitChunks全部的配置項,全部splitChunks配置項均可以在這裏重寫覆蓋,test、prioprity、reuseExistingChunk是cacheGroups獨有的屬性 vendors: { test: /[\\/]node_modules[\\/]/, // 模塊匹配規則,能夠是正則表達式或者函數,不寫默認選擇全部模塊 priority: -10 // 優先級,當同一個模塊同時包含在不一樣cacheGroup中,該模塊將被劃分到優先級高的組中 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true // 若是該chunk包含的modules都已經另外一個被分割的chunk中存在,那麼直接引用已存在的chunk,不會再從新產生一個 } } } } };
splitChunks
的chunks屬性表示做用的chunk範圍。chunks能夠是一個函數,徹底由開發者控制;也能夠是一個字符串,字符串一共有3個值:
name屬性表示最後生成chunk的文件名,有如下3中類型取值:
true
,則會根據cacheGroup的key和chunks屬性的信息自動生成splitChunks
下,那全部chunk設置爲相同的名稱,這會形成不一樣的chunk合成爲一個chunk,固然你能夠設置在cacheGroup
下Tree Shaking
此次詞很形象,搖樹,把爛掉的樹葉搖下來。咱們工程中的爛樹葉
就是那些咱們導出了,可是沒有用到的代碼,Tree Shaking
能夠幫助咱們去除無效代碼,減少打包體積,有其在大工程中,效果明顯。
Tree Shaking
的使用三部曲:
使用Tree Shaking
的第一個前提條件就是必須使用ES2015的模塊語法,import
,export
。由於ES2015的模塊語法是靜態加載
的,而CommonJS和AMD都是動態加載
。靜態加載
是指在編譯階段你就能肯定導出和加載的內容。ES2015在語法上規定:你只能在頂層模塊做用域進行導入導出操做,不容許在條件語句中導入導出,也不容許導入和導出的內容有變量。這意味着你只分析源碼就能夠肯定導入和導出的內容,而不是等到運行時才能肯定。好比說下面CommonJS
的語法,在ES6中就是不能使用的:
var my_lib; if (Math.random()) { my_lib = require('foo'); } else { my_lib = require('bar'); }
你只有在運行的時候,才知道到底加載是的foo
仍是bar
。ES6強制模塊語法靜態化,失去了必定的靈活性,可是帶來了更多的好處,其中之一就是咱們能夠經過靜態分析去實現Tree Shaking。
在徹底的Es6模塊世界中,代碼是沒有反作用的,可是如今咱們可能用到的各類地方庫會有反作用。反作用
是指代碼除了導入和導出,還作了一些其餘影響了其餘代碼的行爲,好比定義了全局變量。最典型的例子就是polyfills,好比Promise的polyfills就定義了Promise全局變量。這時候若是咱們分析到Promise有未使用的導出代碼,則不能刪除,不然可能會影響Promise的使用。哪些是有反作用
的代碼,須要你識別,而且告訴webpack,方式就是經過設置sideEffects,能夠設置在package.json文件中,也能夠設置的module.rules中。
{ "name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js", "*.css" ] }
webpack會經過靜態分析找到冗餘代碼,並打上標記,咱們看一下官網例子:
math.js
export function square(x) { return x * x; } export function cube(x) { return x * x * x; }
index.js
import { cube } from './math.js'; function component() { var element = document.createElement('pre'); element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + cube(5) ].join('\n\n'); return element; } document.body.appendChild(component());
在development
模式下打包,內容以下:
/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__['a'] = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; } });
咱們能夠看到代碼中並未用到math.js
中的square
方法,webpack輸出文件中表示它未被使用。若是想去掉未被使用的代碼,則須要用到UglifyJSPlugin
插件,它是用來壓縮js文件的,自動啓用了去除冗餘代碼的功能。咱們能夠在production
模式下打包,會發現square
的代碼已經被去除。