webpack
立刻要出5了,徹底手寫一個優化後的腳手架是不可或缺的技能。
2019年5月9日
, webpack
版本 4.30.0
最新版本《前端進階》
之後都是高贊高質量文章5分鐘
的技術,咱們先深刻原理再寫配置,那會簡單不少。我這套代碼,在開發環境中性能不是完美的,可是構建速度打包生產環境代碼是極快
的,請你必定要去看個人git
倉庫,如今已經加入了項目實踐
,也在裏面,能夠的話給個star
哦
JSX
文件tree shaking
搖樹優化 刪除掉無用代碼async / await
和 箭頭函數PWA
功能,熱刷新,安裝後當即接管瀏覽器 離線後仍讓能夠訪問網站 還能夠在手機上添加網站到桌面使用preload
預加載資源 prefetch
按需請求資源CSS
模塊化,不怕命名衝突base64
處理jsx js json
等less sass stylus
等預處理code spliting
優化首屏加載時間 不讓一個文件體積過大dns-prefetch
和preload
預請求必要的資源,加快首屏渲染。prerender
,極大加快首屏渲染速度。chunk
chunk
有對應的chunkhash
,每一個文件有對應的contenthash
,方便瀏覽器區別緩存CSS
壓縮CSS
前綴 兼容各類瀏覽器babel
的編譯結果,加快編譯速度chunk
,打包出來後對應一個文件 也是code spliting
HTML
文件的註釋等無用內容CSS
文件單獨抽取出來webpack
中文官網的標語是 :讓一切都變得簡單
webpack
是一個現代 JavaScript
應用程序的靜態模塊打包器(module bundler
)。當 webpack
處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph
),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle
。 webpack v4.0.0
開始,能夠不用引入一個配置文件。然而,webpack 仍然仍是高度可配置的。在開始前你須要先理解四個核心概念:entry
)output
)loader
plugins
)本文旨在給出這些概念的高度概述,同時提供具體概念的詳盡相關用例。
css
讓咱們一塊兒來複習一下最基礎的
Webpack
知識,若是你是高手,那麼請直接忽略這些往下看吧....
入口html
bundles
的文件中,咱們將在下一章節詳細討論這個過程。webpack
配置中配置 entry
屬性,來指定一個入口起點(或多個入口起點)。默認值爲 ./src
。接下來咱們看一個 entry
配置的最簡單例子:前端
webpack.config.js module.exports = { entry: './path/to/my/entry/file.js' };
入口能夠是一個對象,也能夠是一個純數組node
entry: { app: ['./src/index.js', './src/index.html'], vendor: ['react'] }, entry: ['./src/index.js', './src/index.html'],
HTML
文件,由於開發模式下熱更新若是不設置入口爲HTML
,那麼更改了HTML
文件內容,是不會刷新頁面的,須要手動刷新,因此這裏給了入口HTML
文件,一個細節。出口(output)react
webpack.config.js const path = require('path'); module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' } };
在上面的示例中,咱們經過 output.filename
和 output.path
屬性,來告訴 webpack bundle
的名稱,以及咱們想要 bundle
生成(emit
)到哪裏。可能你想要了解在代碼最上面導入的 path 模塊是什麼,它是一個 Node.js
核心模塊,用於操做文件路徑。webpack
loader
git
use 屬性,表示進行轉換時,應該使用哪一個 loader。es6
webpack.config.js const path = require('path'); const config = { output: { filename: 'my-first-webpack.bundle.js' }, module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] } }; module.exports = config;
module
對象定義了 rules 屬性,裏面包含兩個必須屬性:test 和 use。這告訴 webpack 編譯器(compiler
) 以下信息:webpack
編譯器,當你碰到「在 require()/import
語句中被解析爲 '.txt'
的路徑」時,在你對它打包以前,先使用 raw-loader
轉換一下。」webpack
配置中定義 loader
時,要定義在 module.rules
中,而不是 rules。然而,在定義錯誤時 webpack
會給出嚴重的警告。爲了使你受益於此,若是沒有按照正確方式去作,webpack
會「給出嚴重的警告」loader
還有更多咱們還沒有提到的具體配置屬性。loader
和plugin
手寫一個loader和plugin webpack
的編譯原理 ,爲何要先學學習原理? 由於你起碼得知道你寫的是幹什麼的!webpack
打包原理github
Commonjs、amd
或者es6的import,webpack
都會對其進行分析。來獲取代碼的依賴)webpack
作的就是分析代碼。轉換代碼,編譯代碼,輸出代碼webpack
的一些基礎知識,對於理解webpack
的工做機制頗有幫助。什麼是loader
?web
loader
是文件加載器,可以加載資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一塊兒打包到指定的文件中loader
,loader
的執行順序是和自己的順序是相反的,即最後一個loader
最早執行,第一個loader
最後執行。loader
接收源文件內容做爲參數,其餘loader
接收前一個執行的loader
的返回值做爲參數。最後執行的loader
會返回此模塊的JavaScript
源碼loader
處理文件時,若是要修改outputPath
輸出目錄,那麼請在最上面的loader中options設置
什麼是plugin?
Webpack
運行的生命週期中會廣播出許多事件,Plugin
能夠監聽這些事件,在合適的時機經過 Webpack 提供的 API 改變輸出結果。plugin和loader
的區別是什麼?loader
,它就是一個轉換器,將A文件進行編譯造成B文件,這裏操做的是文件,好比將A.scss或A.less轉變爲B.css,單純的文件轉換過程plugin
是一個擴展器,它豐富了wepack
自己,針對是loader
結束後,webpack
打包的整個過程,它並不直接操做文件,而是基於事件機制工做,會監聽webpack
打包過程當中的某些節點,執行普遍的任務。webpack
的運行
webpack
啓動後,在讀取配置的過程當中會先執行 new MyPlugin(options)
初始化一個 MyPlugin 得到其實例。在初始化compiler 對象後,再調用 myPlugin.apply(compiler)
給插件實例傳入 compiler
對象。插件實例在獲取到 compiler
對象後,就能夠經過 compiler.plugin
(事件名稱, 回調函數) 監聽到 Webpack
廣播出來的事件。而且能夠經過 compiler
對象去操做 webpack
compiler
是啥,compilation
又是啥?Compiler
對象包含了 Webpack 環境全部的的配置信息,包含 options,loaders,plugins
這些信息,這個對象在 Webpack 啓動時候被實例化,它是全局惟一的,能夠簡單地把它理解爲 Webpack
實例;Compilation
對象包含了當前的模塊資源、編譯生成資源、變化的文件等。當 Webpack
以開發模式運行時,每當檢測到一個文件變化,一次新的 Compilation
將被建立。Compilation
對象也提供了不少事件回調供插件作擴展。經過 Compilation
也能讀取到 Compiler
對象。Compiler
和 Compilation
的區別在於:Compiler
表明了整個 Webpack
從啓動到關閉的生命週期,而 Compilation
只是表明了一次新的編譯。事件流
webpack
經過 Tapable
來組織這條複雜的生產線。webpack
的事件流機制保證了插件的有序性,使得整個系統擴展性很好。webpack
的事件流機制應用了觀察者模式,和 Node.js 中的 EventEmitter
很是類似。入口設置 :
chunk
vendor
,能夠code spliting
,將這些公共的複用代碼最終抽取成一個chunk
,單獨打包出來HMTL
文件也熱更新,須要加入·index.html
爲入口文件entry: { app: ['./src/index.js', './src/index.html'], vendor: ['react'] //這裏還能夠加入redux react-redux better-scroll等公共代碼 },
output
出口
webpack
基於Node.js
環境運行,可使用Node.js
的API
,path
模塊的resolve
方法JS
文件,加入contenthash
標示,讓瀏覽器緩存文件,區別版本。output: { filename: '[name].[contenthash:8].js', path: resolve(__dirname, '../dist') },
mode: 'development'
模式選擇,這裏直接設置成開發模式,先從開發模式開始。resolve
解析配置,爲了爲了給全部文件後綴省掉 js jsx json
,加入配置
resolve: { extensions: [".js", ".json", ".jsx"] }
加入插件 熱更新plugin
和html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin') const webpack = require('webpack') new HtmlWebpackPlugin({ template: './src/index.html' }), new webpack.HotModuleReplacementPlugin(),
optimization: { runtimeChunk: true, splitChunks: { chunks: 'all' } }
加入 babel-loader
還有 解析JSX ES6
語法的 babel preset
@babel/preset-react
解析 jsx語法
@babel/preset-env
解析es6
語法@babel/plugin-syntax-dynamic-import
解析react-loadable
的import
按需加載,附帶code spliting
功能 ["import", { libraryName: "antd-mobile", style: true }],
Antd-mobile的按需加載{ loader: 'babel-loader', options: { //jsx語法 presets: ["@babel/preset-react", //tree shaking 按需加載babel-polifill ["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]], plugins: [ //支持import 懶加載 "@babel/plugin-syntax-dynamic-import", //andt-mobile按需加載 true是less,若是不用less style的值能夠寫'css' ["import", { libraryName: "antd-mobile", style: true }], //識別class組件 ["@babel/plugin-proposal-class-properties", { "loose": true }], ], cacheDirectory: true }, }
thread-loader
,在babel
首次編譯後開啓多線程const os = require('os') { loader: 'thread-loader', options: { workers: os.cpus().length } }
React
的按需加載,附帶代碼分割功能 ,每一個按需加載的組件打包後都會被單獨分割成一個文件import React from 'react' import loadable from 'react-loadable' import Loading from '../loading' const LoadableComponent = loadable({ loader: () => import('../Test/index.jsx'), loading: Loading, }); class Assets extends React.Component { render() { return ( <div> <div>這即將按需加載</div> <LoadableComponent /> </div> ) } } export default Assets
html-loader
識別html
文件{ test: /\.(html)$/, loader: 'html-loader' }
eslint-loader
{ enforce:'pre', test:/\.js$/, exclude:/node_modules/, include:resolve(__dirname,'/src/js'), loader:'eslint-loader' }
git
倉庫裏webpack
熱更新原理 :
webpack
的熱更新又稱熱替換(Hot Module Replacement
),縮寫爲HMR
。 這個機制能夠作到不用刷新瀏覽器而將新變動的模塊替換掉舊的模塊。
首先要知道server端和client端都作了處理工做
webpack 的 watch
模式下,文件系統中某一個文件發生修改,webpack
監聽到文件變化,根據配置文件對模塊從新編譯打包,並將打包後的代碼經過簡單的 JavaScript
對象保存在內存中。 webpack-dev-server
和 webpack
之間的接口交互,而在這一步,主要是 dev-server
的中間件 webpack-dev-middleware 和 webpack
之間的交互,webpack-dev-middleware
調用 webpack
暴露的 API對代碼變化進行監控,而且告訴 webpack
,將代碼打包到內存中。webpack-dev-server
對文件變化的一個監控,這一步不一樣於第一步,並非監控代碼變化從新打包。當咱們在配置文件中配置了devServer.watchContentBase
爲 true 的時候,Server 會監聽這些配置文件夾中靜態文件的變化,變化後會通知瀏覽器端對應用進行 live reload。注意,這兒是瀏覽器刷新,和 HMR 是兩個概念。 webpack-dev-server
代碼的工做,該步驟主要是經過 sockjs(webpack-dev-server 的依賴)在瀏覽器端和服務端之間創建一個 websocket 長鏈接,將 webpack 編譯打包的各個階段的狀態信息告知瀏覽器端,同時也包括第三步中 Server 監聽靜態文件變化的信息。瀏覽器端根據這些 socket 消息進行不一樣的操做。固然服務端傳遞的最主要信息仍是新模塊的 hash 值,後面的步驟根據這一 hash 值來進行模塊熱替換。webpack-dev-server/client
端並不可以請求更新的代碼,也不會執行熱更模塊操做,而把這些工做又交回給了 webpack,webpack/hot/dev-server
的工做就是根據 webpack-dev-server/client
傳給它的信息以及 dev-server
的配置決定是刷新瀏覽器呢仍是進行模塊熱更新。固然若是僅僅是刷新瀏覽器,也就沒有後面那些步驟了。 HotModuleReplacement.runtime
是客戶端 HMR 的中樞,它接收到上一步傳遞給他的新模塊的 hash
值,它經過 JsonpMainTemplate.runtime
向 server 端發送 Ajax 請求,服務端返回一個 json
,該 json
包含了全部要更新的模塊的 hash 值,獲取到更新列表後,該模塊再次經過 jsonp 請求,獲取到最新的模塊代碼。這就是上圖中 七、八、9 步驟。HotModulePlugin
將會對新舊模塊進行對比,決定是否更新模塊,在決定更新模塊後,檢查模塊之間的依賴關係,更新模塊的同時更新模塊間的依賴引用。HMR
失敗後,回退到 live reload
操做,也就是進行瀏覽器刷新來獲取最新打包代碼。加入 WorkboxPlugin
, PWA
的插件
pwa
這個技術其實要想真正用好,仍是須要下點功夫,它有它的生命週期,以及它在瀏覽器中熱更新帶來的反作用等,須要認真研究。能夠參考百度的lavas
框架發展歷史~const WorkboxPlugin = require('workbox-webpack-plugin') new WorkboxPlugin.GenerateSW({ clientsClaim: true, //讓瀏覽器當即servece worker被接管 skipWaiting: true, // 更新sw文件後,當即插隊到最前面 importWorkboxFrom: 'local', include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/], }),
const CleanWebpackPlugin = require('clean-webpack-plugin') new CleanWebpackPlugin()
code spliting
代碼分割optimization: { runtimeChunk:true, //設置爲 true, 一個chunk打包後就是一個文件,一個chunk對應`一些js css 圖片`等 splitChunks: { chunks: 'all' // 默認 entry 的 chunk 不會被拆分, 配置成 all, 就能夠了拆分了,一個入口`JS`, //打包後就生成一個單獨的文件 } }
CSS
文件的loader
和插件const MiniCssExtractPlugin = require('mini-css-extract-plugin') { test: /\.(less)$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: true, localIdentName: '[local]--[hash:base64:5]' } }, {loader:'postcss-loader'}, { loader: 'less-loader' } ] } new MiniCssExtractPlugin({ filename:'[name].[contenthash:8].css' }),
css
的插件const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') new OptimizeCssAssetsWebpackPlugin({ cssProcessPluginOptions:{ preset:['default',{discardComments: {removeAll:true} }] } }),
html
一些沒用的代碼new HtmlWebpackPlugin({ template: './src/index.html', minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, } }),
{ test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/, use:[ {loader: 'url-loader', options: { limit: 8 * 1024, name: '[name].[hash:8].[ext]', outputPath:'/img' }}, { loader: 'img-loader', options: { plugins: [ require('imagemin-gifsicle')({ interlaced: false }), require('imagemin-mozjpeg')({ progressive: true, arithmetic: false }), require('imagemin-pngquant')({ floyd: 0.5, speed: 2 }), require('imagemin-svgo')({ plugins: [ { removeTitle: true }, { convertPathData: false } ] }) ] } } ] }
file-loader
把一些文件打包輸出到固定的目錄下{ exclude: /\.(js|json|less|css|jsx)$/, loader: 'file-loader', options: { outputPath: 'media/', name: '[name].[contenthash:8].[ext]' } }
裏面有一些註釋可能不詳細,代碼都是本身一點點寫,試過的,確定沒用任何問題
{ "name": "webpack", "version": "1.0.0", "main": "index.js", "license": "MIT", "dependencies": { "@babel/core": "^7.4.4", "@babel/preset-env": "^7.4.4", "@babel/preset-react": "^7.0.0", "autoprefixer": "^9.5.1", "babel-loader": "^8.0.5", "clean-webpack-plugin": "^2.0.2", "css-loader": "^2.1.1", "eslint": "^5.16.0", "eslint-loader": "^2.1.2", "file-loader": "^3.0.1", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "imagemin": "^6.1.0", "imagemin-gifsicle": "^6.0.1", "imagemin-mozjpeg": "^8.0.0", "imagemin-pngquant": "^7.0.0", "imagemin-svgo": "^7.0.0", "img-loader": "^3.0.1", "less": "^3.9.0", "less-loader": "^5.0.0", "mini-css-extract-plugin": "^0.6.0", "optimize-css-assets-webpack-plugin": "^5.0.1", "postcss-loader": "^3.0.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-loadable": "^5.5.0", "react-redux": "^7.0.3", "style-loader": "^0.23.1", "url-loader": "^1.1.2", "webpack": "^4.30.0", "webpack-cli": "^3.3.2", "webpack-dev-server": "^3.3.1", "workbox-webpack-plugin": "^4.3.1" }, "scripts": { "start": "webpack-dev-server --config ./config/webpack.dev.js", "dev": "webpack-dev-server --config ./config/webpack.dev.js", "build": "webpack --config ./config/webpack.prod.js " }, "devDependencies": { "@babel/plugin-syntax-dynamic-import": "^7.2.0" } }
webpack
配置的源碼地址 已經更新 : 源碼地址啊 看得見嗎親