代碼:githubjavascript
mkdir webpack4 cd webpack4 mkdir demo1 cd demo1 npm init -y 或 npm init
webpack4 ├── webpack4/demo1 │ └── webpack4/demo1/package.json └── webpack4/README.md
npm install webpack --save-dev
css
npm install --save-dev webpack@<version>
html
npm install webpack-cli --save-dev
java
npx webpack -v
:查看webpack版本npx webpack-cli -v
:查看webpack-cli版本推薦本地安裝webpack和webpack-cli
寫這篇博客的時候webpack最新版本爲:4.30.0,也是這篇學習webpack4使用的版本
在demo1目錄下新建src目錄,在src目錄下新建index.jsnode
mkdir src cd src touch index.js
demo1目錄結構webpack
demo1 ├── demo1/package.json ├── demo1/package-lock.json └── demo1/src └── demo1/src/index.js
在index.js中加寫代碼,例如:git
//index.js let demo='webpack4' console.log(demo)
webpack4能夠零配置打包,webpack4會默認對src目錄下的index.js文件打包。
如今運行npx webapck
,能夠在demo1目錄下看到dist目錄,dist目錄下有一個main.js文件,這就是打包後的文件,打開查找能夠看到 console.log(demo)
,說明index.js被打包到main.js中。github
在demo1
目錄下新建webpack
配置文件webpack.config.js
web
webpack
--webpack.config.jsconst path = require('path') module.exports={ //mode development: 開發環境 production:生產環境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成後文件輸出位置配置 output: { //filename 設置打包後文件的名字 //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣 filename: 'bundle.js', //path 設置打包完成後文件輸出路徑 path: path.resolve(__dirname,'dist') } }
運行npx webpack
命令npx webpack
等價於npx webpack --config webpack.config.js
chrome
當webapck
配置文件命名爲webpack.config.js
時能夠省略--config *.js
,直接執行npx webpack
便可,不然執行npx webpack --config 配置文件名
。
看到dist目錄下有bundle.js
,說明webpack配置正確。
在package.json
中配置'script'
"scripts": { "build": "webpack" }
添加"build": "webpack"
,運行npm run build
效果等價於執行npx webpack
命令。
file-loader
的使用安裝file-loader
npm i file-loader --save-dev
const path = require('path') module.exports={ //mode development: 開發環境 production:生產環境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成後文件輸出位置配置 output: { //filename 設置打包後文件的名字 //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣 filename: 'bundle.js', //path 設置打包完成後文件輸出路徑 path: path.resolve(__dirname,'dist') }, module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'file-loader', options: { name: '[name].[ext]', //對打包後的圖片命名 outputPath: 'images/' //打包後圖片輸出的位置 dist\images } } } ] } }
在src目錄下新建images文件夾,存放圖片
//index.js //import導入圖片 import image from './images/11.png' let img=new Image() img.src=image document.body.append(img)
運行npm run build
後的目錄結構以下
demo1 ├── demo1/dist │ ├── demo1/dist/bundle.js │ ├── demo1/dist/images │ │ └── demo1/dist/images/11.png │ └── demo1/dist/index.html ├── demo1/package.json ├── demo1/package-lock.json ├── demo1/src │ ├── demo1/src/images │ │ └── demo1/src/images/11.png │ └── demo1/src/index.js └── demo1/webpack.config.js
在dist目錄下出現了images目錄和圖片,建立index.html,引入js文件,在瀏覽器中打開就能夠看到圖片。
url-loader
的使用url-loader
安裝
npm i url-loader -D
url-loader
的做用跟'file-loader'的做用很相似
module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', //對打包後的圖片命名 outputPath: 'images/', //打包後圖片放的位置 dist\images limit: 20480 //1024 == 1kb //小於20kb時打包成base64編碼的圖片不然單獨打包成圖片 } } } ] } }
limit
屬性:當圖片大小大於屬性值時打包成圖片輸出到images目錄下,不然打包成base64編碼的圖片注入bundle.js中
由於base64編碼的圖片致使打包文件變大,因此圖片比較小時打包成base64編碼的圖片,圖片比較大時單獨打包成一張圖片。
css
和scss
的打包安裝相應的loader
npm i css-loader style-loader -D
npm i node-sass sass-loader -D
npm i postcss-loader -D
npm i autoprefixer -D
postcss-loader
和autoprefixer
配合使用能夠在打包過程當中自動添加前綴
在demo1根目錄下新建postcss.config.js
,配置以下
//postcss.config.js module.exports={ plugins: [ require('autoprefixer') ] }
module:{ rules:[ { test: /\.css$/, use:[ 'style-loader', 'css-loader', 'postcss-loader' //加前綴 npm i autoprefixer -D //在項目根目錄下配置postcss.config.js文件 ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, //importLoaders //用於配置css-loader做用於@import的資源以前有多少個loader先做用於@import的資源 } }, 'postcss-loader', 'sass-loader' ] } ] }
在demo1
的src
下新建css
文件夾,在css
文件夾下新建style.css
和index.scss
文件。
index.scss
body{ border: 1px solid red; width: 300px; height: 300px; img{ width: 100px; height: 100px; border-radius: 10%; transform: translate(100px,100px); } }
style.css
body{ border-radius: 10%; }
index.js
//index.js import image from './images/11.png' import './style.css' import './index.scss' let img=new Image() img.src=image document.body.append(img)
運行npm run build
,在dist目錄下新建index.html,引入js文件,在瀏覽器中打開就能夠看到效果,說明打包成功。
css模塊化,避免頁面樣式之間相互影響
在webpack.config.js
中的css-loader
添加modules: true
//webpack.config.js module:{ rules: [ { test: /\.css$/, use:[ 'style-loader', { loader: 'css-loader', options: { modules: true } }, 'postcss-loader' //加前綴 npm i autoprefixer -D //在項目根目錄下配置postcss.config.js文件 ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, //importLoaders //用於配置css-loader做用於@import的資源以前有多少個loader先做用於@import的資源 modules: true //加載css模塊化打包,避免樣式文件之間相互影響 } }, 'postcss-loader', 'sass-loader' ] } ] }
修改index.js.img
是類名須要在樣式文件中提早寫好樣式
//index.js import image from './images/11.png' import style from './css/style.css' // import style from './css/index.scss' let img=new Image() img.src=image //style.img .img是scss文件中寫好的類名 img.classList.add(style.img) document.body.append(img)
index.scss
body{ border: 1px solid red; width: 300px; height: 300px; img{ width: 100px; height: 100px; border-radius: 10%; transform: translate(100px,100px); } .img{ border: 10px solid royalblue; } }
style.css
body{ border-radius: 10%; } body .img{ border: 10px solid yellow; }
結果
能夠看到添加了一個class,類名是一串比較複雜的字符串,從而避免這個樣式對別的元素產生影響。
這一部分主要是學會使用html-webpack-plugin
和clean-webpack-plugin
插件,主要是學會配置devServer
以及使用webpack的熱模塊替換功能。
首先,在webpack4
目錄下新建demo2
文件夾將demo1
目錄下的全部東西複製到demo2
中
在上一部分咱們都是手動在dist目錄下建立index.html引入js文件查看打包結果,這樣會很麻煩。咱們可使用html-webpack-plugin
來自動生產index.html,而且可以自動引入打包好的文件,直接打開生產的html就能夠看到打包結構。
html-webpack-plugin
的使用安裝
npm i html-webpack-plugin -D
在webpack.config.js
中配置plugins配置項
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') module.exports={ //mode development: 開發環境 production:生產環境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成後文件輸出位置配置 output: { //filename 設置打包後文件的名字 //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣 filename: 'bundle.js', //path 設置打包完成後文件輸出路徑 path: path.resolve(__dirname,'dist') }, module: { }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }) ] }
在demo2目錄下新建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>模板</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="bundle.js"></script></body> </html>
運行npm run build
,能夠看到dist目錄下自動生產index.html,而且還自動引入js文件
clean-webpack-plugin
的使用每次打包生成的dist目錄,若是改一次代碼,都得要刪除一次dist目錄,這樣很麻煩,能夠經過clean-webpack-plugin
在每次打包的前自動清空dist目錄。
安裝
npm i clean-webpack-plugin -D
在webpack.config.js
的plugins
中配置以下
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports={ //mode development: 開發環境 production:生產環境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成後文件輸出位置配置 output: { //filename 設置打包後文件的名字 //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣 filename: 'bundle.js', //path 設置打包完成後文件輸出路徑 path: path.resolve(__dirname,'dist') }, module: { }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin() ] }
運行npm run build
,能夠本身測試,每次打包前都會把dist目錄下的文件刪掉。
entry
和output
多入口配置module.exports={ //mode development: 開發環境 production:生產環境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js', main: './src/index.js' }, //打包完成後文件輸出位置配置 output: { //filename 設置打包後文件的名字 //若是不設置filename,則文件的名字跟入口文件路徑的屬性名同樣 // 佔位符 filename: '[name].js', //path 設置打包完成後文件輸出路徑 path: path.resolve(__dirname,'dist') }, }
當有多入口的時候,須要修改filename
的屬性值爲'[name].js'
運行npm run build
,就會在dist目錄下生產index.js
和main.js
devtool
devtool決定源代碼與打包後的代碼之間的映射關係,方便對代碼進行調試。
開發環境推薦: cheap-module-eval-source-map
生產環境推薦: cheap-module-source-map
devtool具體內容請查閱:文檔:devtool
module.exports={ devtool: 'cheap-module-eval-source-map', //開發環境推薦: cheap-module-eval-source-map //生產環境推薦: cheap-module-source-map }
devServer
安裝webpack-dev-server
npm i webpack-dev-server -D
module.exports={ devServer: { contentBase: './dist', // open: true, //自動打開瀏覽器 // port: 8080, //默認8080 } }
修改package.json
的script
,添加 "start": "webpack-dev-server"
"scripts": { "start": "webpack-dev-server" },
執行npm run start
後打開瀏覽器就能夠看到效果,當咱們修改代碼的時候頁面就會從新刷新。
有時咱們但願頁面只更新咱們修改的那一部分就能夠了,而並非刷新頁面,因此須要啓用webpack的熱模塊替換功能。
import './css/style.css' var btn = document.createElement('button') btn.innerHTML='新增' document.body.appendChild(btn) btn.onclick=function(){ var div=document.createElement('div') div.innerHTML='items' document.body.appendChild(div) }
//style.css body{ background: yellow; } div:nth-of-type(odd){ background: chartreuse; font-size: 18px; }
引入webpack:const webpack=require('webpack')
添加內容以下:
const webpack=require('webpack') module.exports={ plugins: [ new webpack.HotModuleReplacementPlugin() //啓用HMR ], devServer: { contentBase: './dist', // open: true, //自動打開瀏覽器 // port: 8080, hot: true, //啓用webpack的熱模塊替換功能 hotOnly: true //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備 } }
hot:true
啓用HotModuleReplacementPlugin
(HMR)
執行npm run start
,在瀏覽器打開之後,修改div的背景顏色,只有改變的地方纔發生變化,可是頁面並無刷新。
在demo2的src目錄下新建number.js
var number=function(){ var div=document.createElement('div') div.setAttribute("id","number") div.innerHTML=103 document.body.appendChild(div) } export default number
修改index.js
import number from './number' number()
運行npm run start
,在瀏覽器中打開看結果,而後在number.js中修改內容,可是頁面並無顯示修改後的內容
這是由於在引入js文件的時候,熱模塊替換的實現方式有點區別。
js要達到熱模塊替換的效果,得要if(module.hot){}這一部分代碼,不然就算改了代碼,頁面不刷新,修改的地方在頁面上頁面變化。
css樣式由於css-loader已經實現if(module.hot){}這一部分,因此不須要單獨實現這一部分。
再次修改index.js
import number from './number' number() if(module.hot){ module.hot.accept('./number.js',function(){ number() document.body.removeChild(document.getElementById('number')) }) }
運行npm run start
,在瀏覽器中打開看結果,而後在number.js中修改內容,發現頁面顯示修改後的內容
Babel是一個普遍使用的轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。
Babel總共分爲三個階段:解析(parse),轉換(transform),生成(generate)。
Babel自己不具備任何轉化功能,它把轉化的功能都分解到一個個plugin
裏面。所以當咱們不配置任何插件時,通過Babel
輸出的代碼和輸入是相同的。
Babel插件的使用
.babelrc
配置文件或是webapck.config.js
中配置,通常都是在.babelrc
中配置。Babel的配置文件是.babelrc
,存放在項目的根目錄下。使用Babel的第一步,就是配置這個文件。
該文件用來設置轉碼規則和插件,基本格式以下。
{ "presets": [], "plugins": [] }
preset
preset(預設)就是一系列插件的集合@babel/preset-env
包含全部ES6轉譯爲ES5的插件集合
core-js
轉換一些內置類(Promise, Symbols等等)和靜態方法(Array.from等)。
@babel/core
是做爲Babel的核心存在,Babel的核心api都在這個模塊裏面。
babel-loader
babel-loader
在webpack中使用,是webpack和Babel之間的通信橋樑
@babel/polyfill介紹
@babel/preset-env
默認只轉譯js
語法,而不轉譯新的API
,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise
等全局對象,以及一些定義在全局對象上的方法(好比Object.assign
)都不會轉譯。這時就必須使用@babel/polyfill
(內部集成了core-js
和regenerator
)。
使用時,在全部代碼運行以前增長import "@babel/polyfill"
或者是在webpack.config.js
入口配置
module.exports = { entry: ["@babel/polyfill", "./app/js"], }
所以必須把@babel/polyfill
做爲dependencies
而不是devDependencies
@babel/polyfill主要有兩個缺點:
1.使用@babel/polyfill
須要作些額外配置,實現打包的時候按需引入,不然會把@babel/polyfill
所有注入代碼中會致使打出來的包很是大。
2.@babel/polyfill
會污染全局變量。
Babel7
的一個重大變化就是npm package
名稱的變化,把全部babel-*
重命名爲@babel/*
,例如:
babel-polyfill
重命名爲@babel/polyfill
babel-preset-env
重命名爲@babel/preset-env
首先實現對ES6語法的轉譯
在webpack4目錄下新建demo3文件夾,將demo2目錄下的全部東西複製到demo3中
安裝babel-loader、 @babel/core、@babel/preset-env
npm i babel-loader -D
npm i @babel/core -D
npm i @babel/preset-env -D
babel-loader@8
須要安裝@babel/core7.x
版本。
在webpack.config.js配置
module.exports={ module: { rules:[ { test: /\.js$/, exclude: /node_modules/, use:{ loader: 'babel-loader', options:{ presets: [ ["@babel/preset-env",{ //targets:表示編譯出的代碼想要支持的瀏覽器版本 targets: { chrome: "67" } }] ] } } } ] } }
執行npm run build
或npx webpack
就能夠看到dist目錄下的打包文件,可是隻是將ES6的語法進行轉譯,並無對ES6新API進行轉譯,因此咱們須要配置@babel/polyfill
解決這個問題。
安裝@babel/polyfill
npm i @babel/polyfill --save
在index.js
中引入@babel/polyfill
index.js
//index.js import '@babel/polyfill' let arr=[ new Promise(()=>{}), new Promise(()=>{}), 2 ] arr.map((item)=>{ console.log(item) })
引入@babel/polyfill
前,main.js的大小爲29.5KB
引入@babel/polyfill
後,main.js的大小爲1MB
注意:以上對比都是在沒有targets
這個選項的狀況下,由於有些瀏覽器幾乎都支持ES6,在這種狀況下,@babel/preset-env
將不會對代碼進行處理。
這是由於把@babel/polyfill
對全部API的實現都注入到打包文件中,可是裏面不少的API咱們在代碼中並無用到,因此須要修改配置,按需引入對應的API。
修改webpack.config.js配置
添加"useBuiltIns": "usage"
之後,須要安裝core-js@2
,而且添加"corejs": 2
配置項,這時配置選項比較多,須要在項目根目錄下新建.babelrc
文件,在這個文件中配置。
.babelrc
配置以下:
"useBuiltIns"
屬性值爲"usage"
時,會自動引入@babel/polyfill
,必須保證已經安裝了@babel/polyfill
"useBuiltIns"
屬性值爲"usage"
時,須要添加"corejs": 2
配置項,不然報錯,須要安裝core-js
首先刪掉index.js
中的import '@babel/polyfill'
安裝core-js
npm i --save core-js@2
或npm i --save core-js@3
{ "presets": [["@babel/preset-env",{ "useBuiltIns": "usage", //不須要把polly都打包到代碼中,根據代碼按需轉譯 // core-js@3和core-js@2二選一 //"corejs": 3, //npm i --save core-js@3 "corejs": 2 //npm i --save core-js@2 }]] }
修改webpack.config.js
,刪除options
對象
module.exports={ module: { rules:[ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] } }
執行npm run build
,打包後的文件大小爲165KB
可是,在開發類庫或是第三方模塊時不適合使用@babel/polyfill
,因此接下來使用@babel/plugin-transform-runtime
來解決這個問題。
@babel/plugin-transform-runtime、@babel/runtime和@babel/runtime-corejs2的用法
@babel/runtime-corejs2:是一個包含Babel modular runtime helpers
和regenerator-runtime
以及core-js
的庫。
@babel/runtime:是一個包含Babel modular runtime helpers
和regenerator-runtime
的庫。
在配置項中corejs
屬性值爲默認爲false
,若是須要將Promise
等API
進行轉譯,則須要設置屬性值爲2
時,而且安裝@babel/runtime-corejs2
安裝:
npm i @babel/plugin-transform-runtime -D
npm i --save @babel/runtime
npm i --save @babel/runtime-corejs2
修改.babelrc文件
{ "plugins": [ ["@babel/plugin-transform-runtime",{ "helpers": true, "regenerator": true, "useESModules": false, "corejs": 2 }] ] }
咱們把presets
配置項去掉了,而後npm run build
打包,打開打包後的main.js
查看,雖然把轉譯了Promise
,可是ES6新語法並沒被轉譯,例如:let
沒有被轉譯爲var
。
因此仍是須要配置presets
,由於"@babel/preset-env"
包含了對全部ES6語法轉譯爲ES5插件。
再次修改.babelrc文件
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-transform-runtime",{ "helpers": true, "regenerator": true, "useESModules": false, "corejs": 2 }] ] }
添加presets
配置項,而後npm run build
打包,打開打包後的main.js
查看,能夠看到let
和箭頭函數都被轉譯爲ES5語法了。
Tree Shaking
使用首先,在webpack4目錄下新建demo4文件夾,將demo3目錄下的全部東西複製到demo4中
Tree Shaking
能夠用來剔除JavaScript
中用不上的死代碼。它依賴靜態的ES6
模塊化語法,例如經過import
和export
導入導出。
須要注意的是要讓Tree Shaking
正常工做的前提是JavaScript
代碼必須採用ES6
模塊化語法,由於ES6
模塊化語法是靜態的,這讓Webpack
能夠簡單的分析出哪些export
的被import
過了。
接下來配置Webpack
讓Tree Shaking
生效
webpack4
默認保留ES6模塊化語句,並無經過Babel將其轉換
修改.babelrc
文件爲以下:
//.babelrc { "presets": [["@babel/preset-env",{ "useBuiltIns": "usage", "corejs": 2, "modules":false //關閉 Babel 的模塊轉換功能,保留本來的 ES6 模塊化語法 //默認是auto,取值還能夠是 amd, umd, systemjs, commonjs,auto等 }]] }
修改webapck.config.js
,添加
optimization: { usedExports: true }
到module.exports{}
中
module.exports={ mode: 'development', optimization: { //開發壞境使用tree shaking時加usedExports: true usedExports: true }, }
還需經過package.json
的"sideEffects"
屬性來告訴webpack哪些模塊是能夠忽略掉,若是沒有則設置爲false
,來告知webpack,它能夠安全地刪除未用到的export
。
修改package.json
{ "name": "your-project", "sideEffects": false }
在demo4下的src新建math.js
index.js
//tree shaking import export import {cube} from './math.js' let component = () => { let element = document.createElement('pre') element.innerHTML = [ 'Hello webpack!', '2 cubed is equal to ' + cube(2) ].join('\n\n'); console.log(cube) return element; } document.body.appendChild(component());
math.js
export let square= (x) => { console.log(x) return x * x; } export let cube = (x) => { console.log(x) return x * x * x; }
運行npm run build
,而後打開打包後的js文件:main.js找到下面這段文字
/*!*********************!*\ !*** ./src/math.js ***! \*********************/ /*! exports provided: square, cube */ /*! exports used: cube */ /***/
從上面這段文字能夠看出Tree Shaking
生效了,可是在開發環境下,並無把沒有用的代碼刪掉,由於在開發環境下還須要對代碼進行調試。
咱們已經找出須要刪除的「未引用代碼(dead code)」,然而,不只僅是要找出,還要刪除它們。爲此,咱們須要將mode
配置選項設置爲production
,將optimization對象刪掉,修改devtool
配置選項
webpack.config.js
module.exports = { mode: 'production', devtool: 'cheap-module-source-map' }
運行npm run build
,查看打包結果就能夠看到沒有用的代碼被刪掉了。
Develoment
和Production
不一樣環境的配置在webpack4下新建demo5,將demo4下的全部文件複製到demo5中
由於在不一樣的環境下,webpack的配置稍微有點區別,若是咱們須要在不一樣的換將下切換,就得修改webpack配置,這是很麻煩並且還容易改錯,因此咱們須要把配置文件進行拆分。
在項目根目錄下新建build文件夾,而後在build文件夾中新建webpack.dev.js
、webpack.prod.js
和webpack.base.js
三個文件
webpack.dev.js
:是開發環境webpack.prod.js
:是生產環境webpack.base.js
:是開發環境和生產環境都用到的配置
這幾個文件之間的結合靠'webpack-merge'這個插件。
安裝
npm i webpack-merge -D
webpack.dev.js
//webpack.dev.js const webpack=require('webpack') const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const devConfig={ mode: 'development', devtool: 'cheap-module-eval-source-map', plugins: [ new webpack.HotModuleReplacementPlugin() ], optimization: { usedExports: true }, devServer: { contentBase: './dist', // open: true, //自動打開瀏覽器 // port: 8080, hot: true, //啓用webpack的熱模塊替換功能 //hotOnly: true //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備 } } module.exports=merge(baseConfig,devConfig)
webapck.prod.js
//webapck.prod.js const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const prodConfig={ mode: 'production', devtool: 'cheap-module-source-map' } module.exports=merge(baseConfig,prodConfig)
可是這兩個文件還有大量重複的代碼,新建webpack.base.js
//webpack.base.js const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports={ entry: { main: './src/index.js' }, output: { filename: '[name].js', path: path.resolve(__dirname,'dist') }, module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.css$/, use:[ 'style-loader', 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'sass-loader', 'postcss-loader' ] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin(), ] }
修改package.json
的script
:
{ "scripts": { "dev": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" }, }
開發環境:運行npm run dev
,打開瀏覽器訪問http://localhost:8080/
就能夠看到結果
生產環境:運行npm run build
webpack 4
移除CommonsChunkPlugin
,取而代之的是SplitChunksPlugin
。下面介紹SplitChunksPlugin
的用法。
在webpack4目錄
下新建demo6目錄
,將demo5目錄
下的全部文件都複製到demo6
中。
安裝 lodash
npm i lodash --save
在package.json
添加
"scripts": { "dev-build": "webpack --config ./build/webpack.dev.js" }
修改index.js
import _ from 'lodash' console.log(_.join(['lodash', 'babel', 'webpack'], '-'))
運行npm run dev-build
後,demo6目錄結構以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/index.html │ └── demo6/dist/main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
dist目錄下只有main.js一個js文件,lodash庫也一塊兒打包到main.js裏面,這種狀況下會致使文件變大,致使頁面加載速度變慢,咱們須要把第三庫或是須要單獨打包的代碼給分割出來。
這時須要使用webpack4自帶的插件SplitChunksPlugin
,默認狀況下它將只會影響按需加載的代碼塊
在webpack.config.js
添加optimization.splitChunks.chunks
optimization: { splitChunks: { //chunks: all, async, initial. //async針對異步加載的chunk作切割,initial針對初始chunk,all針對全部chunk。 chunks: 'async' } }
運行npm run dev-build
後,打包後的代碼並無分割。
修改optimization.splitChunks.chunks
爲all
optimization: { splitChunks: { //chunks: all, async, initial. //async針對異步加載的chunk作切割,initial針對初始chunk,all針對全部chunk。 chunks: 'all' } }
運行npm run dev-build
後,demo6目錄結構以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/index.html │ ├── demo6/dist/main.js │ └── demo6/dist/vendors~main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
能夠看到dist目錄下多了vendors~main.js
文件,說明SplitChunksPlugin
插件生效了
接下來先看optimization.splitChunks
的默認配置
module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } }
chunk
,共享該模塊的chunk必須不小於1時才分割從以上能夠看出來,默認的配置只對異步加載的模塊有效
修改index.js,異步加載lodash
function getComponent(){ return import('lodash').then(({default: _})=>{ var element=document.createElement('div') element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-') return element }) } getComponent().then(element=>{ document.body.appendChild(element) })
這時運行npm run dev-build
會報錯,須要下載安裝 @babel/plugin-syntax-dynamic-import
npm i @babel/plugin-syntax-dynamic-import -D
在.babelrc中添加
"plugins": ["@babel/plugin-syntax-dynamic-import"]
再次運行npm run dev-build
,此時打包成功,目錄結構以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/0.js │ ├── demo6/dist/index.html │ └── demo6/dist/main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
能夠看到dist目錄下0.js
,就是對lodash打包後的文件,有時咱們但願可以改變0.js
的名字
修改index.js,添加/* webpackChunkName:"lodash" */
function getComponent(){ return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _})=>{ var element=document.createElement('div') element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-') return element }) } getComponent().then(element=>{ document.body.appendChild(element) })
運行npm run dev-build
,發現0.js
變爲vendors~lodash.js
也能夠經過設置optimization.splitChunks.cacheGroups.vendors.name
來修改打包後的文件名字
修改optimization.splitChunks
配置
optimization: { splitChunks: { chunks: 'all', minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10, name: 'vendors' }, default: { minChunks: 1, priority: -20, reuseExistingChunk: true } } } },
運行npm run dev-build
,發現打包後的文件名字變爲vendors.js
以上就是SplitChunksPlugin
的簡單用法
MiniCssExtractPlugin
插件將CSS提取到單獨的文件中。它爲每一個包含CSS的JS文件建立一個CSS文件,在webpack 4才能使用
在webpack目錄下新建demo7,將demo6下的全部文件都複製到demo7中,進入demo7
安裝
npm install --save-dev mini-css-extract-plugin
官網提示,這個插件應該在生產環境中使用,因此修改webpack的相關配置。
首先將webpack.base.js
中對css和scss處理的loader配置分別複製粘貼(在webpack.base.js中刪掉這一部分)到webpack.prod.js
和webpack.dev.js
中。
在webpack.prod.js
中引入const miniCssExtractPlugin = require('mini-css-extract-plugin')
修改後的相關配置以下:
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { entry: { main: './src/index.js' //對應filename //入口文件引入的模塊,分割打包的名字對應chunkFilename }, output: { filename: '[name].js', chunkFilename: '[name].chunk.js', path: path.resolve(__dirname, '../dist') }, module: { rules: [ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin(), ], optimization: { usedExports: true, splitChunks: { //chunks: all, async, initial. //async 只對異步分割有效 // initial 同步 //all 要配置cacheGroups chunks: 'all', // 引入的包或是庫大於30kb纔對代碼進行分割 minSize: 30000, maxSize: 0, //沒多大意義 minChunks: 1, //當一個模塊至少引入多少次時纔會進行代碼分割 maxAsyncRequests: 5, //同時加載的模塊數最可能是5個 maxInitialRequests: 3, automaticNameDelimiter: '~', //打包後的文件名字之間的鏈接符 name: true, cacheGroups: { //緩存組 // vendors: false, vendors: { //同步 檢查是否在node_modules裏面 test: /[\\/]node_modules[\\/]/, priority: -10, //優先級 name: 'vendors' }, // default: false default: { minChunks: 1, priority: -20, reuseExistingChunk: true, //若是模塊已經打包過了就引用以前打包好的模塊 // filename: 'common.js' } } } } }
const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const miniCssExtractPlugin = require('mini-css-extract-plugin') const prodConfig={ mode: 'production', devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.css$/, use: [ miniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use: [ miniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'postcss-loader', 'sass-loader' ] }, ] }, plugins: [ new miniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].chunk.css' }) ] } module.exports=merge(baseConfig,prodConfig)
const webpack=require('webpack') const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const devConfig={ mode: 'development', // devtool: 'cheap-module-eval-source-map', module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'sass-loader', 'postcss-loader' ] }, ] }, plugins: [ new webpack.HotModuleReplacementPlugin() ], devServer: { contentBase: './dist', // open: true, //自動打開瀏覽器 // port: 8080, hot: true, //啓用webpack的熱模塊替換功能 //hotOnly: true //devServer.hot在沒有頁面刷新的狀況下啓用熱模塊替換做爲構建失敗時的後備 } } module.exports=merge(baseConfig,devConfig)
"sideEffects": [ "*.css" ],
不然經過import './*.css'
方式引入的css文件會被刪除掉。
運行npm run build
,咱們就能夠看到dist目錄下,css文件被單獨分割出來了。
官網還提供一個插件能夠對css文件進行壓縮
安裝
npm i optimize-css-assets-webpack-plugin -D
//引入 const optimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') //添加 optimization: { minimizer: [new optimizeCSSAssetsPlugin({})], },
再次運行npm run build
,打開打包後的css文件,就發現css文件被壓縮了。
代碼都已經上傳到github:github:webpack4