新建一個文件夾做爲示例項目,項目根目錄運行命令初始化package.json
:css
npm init -y
而後按照如下目錄先建立空文件:
html
npm i -D webpack@4.44.2 webpack-cli@3.3.12
/config/webpack.base.config.js
寫入內容:const path = require('path') module.exports = { entry: { // 入口配置 app: './src/index.js' }, output: { // 出口配置 filename: 'js/[name].[contenthash:8].js', path: path.resolve(__dirname, '../dist'), } }
package.json
裏寫入script命令:"script": { "build": "webpack --config ./config/webpack.base.config.js" }
npm run build
hash
、chunkhash
、contenthash
的區別:hash
是每次打包時從新生成,全部文件共用同一個hash;chunkhash
根據不一樣的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值。在生產環境中將一些公共庫分離出來,只要公共庫不改變,生成的哈希值就不會變。contenthash
和文件內容相關,內容不變,生成的哈希值就不變。node
使用 html-webpack-plugin 插件來配置html模板文件的關聯,這樣打包後的js、css等會自動引入到html中,就能夠訪問html文件查看效果了。react
npm i -D html-webpack-plugin@4.5.0
/config/webpack.base.config.js
添加plugins,配置html-webpack-plugin:const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: ......, output: ......, // 在這裏添加 plugins: [ new HtmlWebpackPlugin({ template: 'public/index.html', inject: 'body', hash: false }), ], }
/public/index.html
寫入代碼:<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1"> <title>Title</title> </head> <body> <div id="root"></div> </body> </html>
console.log('小王子')
npm run build
,打包後會在dist下生成index.html,打開該html查看控制檯輸出效果。用哪一個框架都行,目的都是學習webpack的配置,以react示例。webpack
npm i -S react react-dom
/src/index.js
替換爲如下代碼:import React from 'react' import ReactDOM from 'react-dom' function App () { return ( <div> <div className="test">小王子</div> </div> ) } ReactDOM.render( <App/>, document.getElementById('root') )
因爲react使用的jsx語法,不是js標準語言語法,因此須要藉助babel插件來轉碼,固然babel用處遠不止這些,還須要babel將es6+代碼轉爲兼容性更好的es5代碼。
安裝babel相關依賴:git
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
module.exports = { presets: [ '@babel/preset-env', '@babel/preset-react' ], }
/config/webpack.base.config.js
添加module,配置babel的rules:const path = require('path') module.exports = { entry: ......, output: ......, plugins: ......, // 在這裏添加代碼 module: { rules: [ { test: /\.(js|jsx)?$/, options: { cacheDirectory: true }, loader: 'babel-loader' } ] } }
npm run build
,而後打開打包後的index.html查看效果。webpack配置裏能夠指定mode屬性來把運行環境劃分爲development和production,
使用webpack-merge插件能夠針對不一樣mode環境使用不一樣的webpack配置,插件幫咱們智能合併配置。es6
npm i -D webpack-merge@4.2.2
/config/webpack.dev.config.js
寫入代碼:const merge = require('webpack-merge') const common = require('./webpack.base.config') module.exports = merge(common, { mode: 'development', output: { filename: 'js/[name].js', }, })
/config/webpack.prod.config.js
寫入代碼:const merge = require('webpack-merge') const common = require('./webpack.base.config') module.exports = merge(common, { mode: 'production', })
"scripts": { "start": "webpack --config ./config/webpack.dev.config.js", "build": "webpack --config ./config/webpack.prod.config.js" },
注意build命令裏的webpack.base.config.js換成了webpack.prod.config.js。
這樣就分了開發環境和生產環境。github
使用clean-webpack-plugin插件能夠在build打包以前自動刪除上次打包的dist文件夾,防止冗餘文件的產生。web
npm i -D clean-webpack-plugin
/config/webpack.prod.config.js
裏添加plugins配置:const merge = require('webpack-merge') const common = require('./webpack.base.config') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = merge(common, { mode: 'production', // 在這裏添加代碼 plugins: [ new CleanWebpackPlugin(), ], })
npm run build
查看dist文件夾還有沒有以前遺留的js文件。使用webpack-dev-server插件,在webpack運行時自動啓動一個本地服務器運行打包後的html文件,配合熱更新,實現代碼改動後實時查看效果。chrome
npm i -D webpack-dev-server@3.11.0
/config/webpack.dev.config.js
裏添加devServer和plugins配置:const merge = require('webpack-merge') const common = require('./webpack.base.config') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = merge(common, { mode: 'development', output: { filename: 'js/[name].[hash:8].js', }, // 在這裏添加代碼 devServer: { open: true, port: 9000, compress: true, hot: true, inline: true, }, plugins: [ new webpack.HotModuleReplacementPlugin(), ], })
package.json
裏修改scripts的start命令:"start": "webpack-dev-server --inline --progress --config ./config/webpack.dev.config.js",
npm start
查看效果。devtool用於配置source map選項,幫助咱們調試時追蹤原始源代碼,有多重source map格式供選擇,具體能夠參考文檔,綜合構建速度和使用效果,通常選擇 cheap-module-eval-source-map,各方面都比較均衡。
/config/webpack.dev.config.js
裏添加devtool配置:module.exports = merge(common, { mode: 'development', devtool: 'cheap-module-eval-source-map', // 在這裏添加便可 output: ......, devServer: ......, plugins: ......, })
/src/index.js
裏添加一條console語句:import React from 'react' import ReactDOM from 'react-dom' console.log(123) // 在這裏添加便可 function App () { ...... } ......
style-loader 用於建立樣式標籤引入css代碼,不能單獨使用;
css-loader 用於解析css文件生成css代碼,給style-loader使用;
less-loader 用於將less文件轉換爲css文件,給css-loader使用;
npm i -D style-loader css-loader less less-loader
index.less
:@color: red; .test { color: @color; }
/src/index.js
引入該less使用:import React from 'react' import ReactDOM from 'react-dom' import './index.less' function App () { return ( <div> <div className="test">小王子</div> </div> ) } ReactDOM.render( <App/>, document.getElementById('root') )
/config/webpack.base.config.js
裏配置loader:module.exports = { module: { rules: [ ...... // 接上,追加如下代碼 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, ] } }
rules裏的use數組在解析時是按從右往左解析的,須要注意順序。
postcss 是一個容許使用 JS 插件轉換樣式的工具集合;
postcss-loader 用於webpack中對css作進一步處理的loader;
autoprefixer 屬於postcss的一個插件,配合postcss-loader能夠自動給css樣式添加瀏覽器前綴,以兼容低版本瀏覽器;
browserlist 用於指定項目運行的目標瀏覽器範圍,能被autoprefixer和babel等識別,根據目標瀏覽器範圍作兼容適配。
npm i -D postcss postcss-loader autoprefixer browserlist
/src/index.less
添加樣式:@color: red; .test { color: @color; display: flex; justify-content: center; }
/config/webpack.base.config.js
裏修改css和less的loader配置:module.exports = { module: { rules: [ ...... // 修改css和less的loader配置 { test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] }, { test: /\.less$/, use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] }, ] } }
postcss.config.js
:module.exports = { plugins: { autoprefixer: {} } }
package.json
裏添加browserlist配置:{ "dependencies": ......, "devDependencies": ......, ---在這裏追加--- "browserslist": [ "> 1% in CN", "last 2 versions" ] }
npm start
運行,chrome開發者工具查看文字的css樣式,看flex相關樣式是否自動加上了瀏覽器前綴。file-loader用於打包靜態文件並將引入路徑和js關聯;
url-loader用於處理圖片資源的打包,低於指定大小時會將資源轉換爲base64格式使用,其餘狀況處理和file-loader同樣。
npm i -D file-loader url-loader
/config/webpack.base.config.js
裏添加loader配置:module.exports = { module: { rules: [ ...... // 接上,追加如下代碼 { test: /\.(jpe?g|png|gif)$/i, options: { esModule: false, limit: 4096, // 配置低於4k的圖片會轉爲base64格式 }, loader: 'url-loader', }, { test: /\.(woff|woff2|eot|ttf|otf)$/i, // 處理字體文件 options: { esModule: false }, loader: 'file-loader' }, ] } }
mini-css-extract-plugin用於將打包後的css單獨抽離出來,webpack打包時默認是將css整合進js裏經過動態建立style標籤實現的,而這個插件將css剝離出來,能減小沒必要要的js代碼及dom操做,提高頁面加載性能。
npm i -D mini-css-extract-plugin
/config/webpack.base.config.js
裏配置plugins和loader:const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { plugins: [ ......, // 接上,在這裏追加 new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css', chunkFilename: 'css/[id].[contenthash:8].css', ignoreOrder: true }), ], module: { rules: [ // 修改css和less的loader,替換掉style-loader { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }, { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] }, ] } }
npm run build
查看打包目錄是否生成單獨的css文件。alias是webpack內置支持的一個屬性,用來指定快捷路徑標識,配置後就能方便的書寫引入路徑。
/config/webpack.base.config.js
裏配置:module.exports = { entry: ......, output: ......, // 接上,在這裏追加 resolve: { alias: { '@': path.resolve(__dirname, '../src'), } }, }
/src/index.js
修改文件的引入路徑:import './index.less' 替換爲 import '@/index.less'
npm start
查看是否正常運行terser-webpack-plugin 用於對js作代碼壓縮及代碼混淆等處理,對es6+支持更好,替代之前的uglifyjs-webpack-plugin。
npm i -D terser-webpack-plugin@4.2.3
/config/webpack.prod.config.js
裏添加配置:const TerserWebpackPlugin = require('terser-webpack-plugin') module.exports = merge(common, { mode: 'production', plugins: ......, // 在這裏添加 optimization: { minimize: true, minimizer: [ new TerserWebpackPlugin({ parallel: true, // 使用多進程提升構建速度 extractComments: false, // 禁止生成license文件 terserOptions: { compress: { // 刪除console.log代碼 drop_console: true, pure_funcs: ['console.log'] }, output: { comments: false // 刪除註釋代碼 } } }), ], }, })
optimize-css-assets-webpack-plugin 插件用於對css文件作壓縮處理,默認使用cssnano壓縮。
npm i -D optimize-css-assets-webpack-plugin
/config/webpack.prod.config.js
裏添加plugins:const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') module.exports = merge(common, { plugins: [ ......, // 接上,在這裏追加 new OptimizeCssAssetsPlugin(), ] })
npm run build
查看打包後的css文件是否已被壓縮。splitChunks用於代碼分離,有利於性能優化。模塊是否分離的判斷原則:體積大、穩定不變。
/config/webpack.pord.config.js
添加splitChunks:module.exports = merge(common, { optimization: { ......, // 接上,在這裏追加 splitChunks: { chunks: 'all', maxAsyncRequests: 8, maxInitialRequests: 6, minSize: 10000, cacheGroups: { react: { // 分離react和react-dom name: 'chunk-react', test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, // 匹配規則 priority: 20 // 匹配優先級 }, vendors: { // 其餘npm依賴(生產環境) name: 'chunk-vendors', test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, common: { // 組件公共抽離 name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } } } }, })
/config/webpack.dev.config.js
裏添加chunkFilename:module.exports = merge(common, { output: { filename: 'js/[name].js', // 在這裏添加 chunkFilename: 'js/[name].js', }, })
/config/webpack.base.config.js
裏添加chunkFilename:module.exports = { output: { filename: 'js/[name].[contenthash:8].js', path: path.resolve(__dirname, '../dist'), // 在這裏添加 chunkFilename: 'js/[name].[contenthash:8].js', }, }
npm run build
查看打包後的文件,多打包幾回,對比分離出來的chunk文件名是否有變化。DefinePlugin 是webpack內置的一個插件,容許建立一個在編譯時能夠配置的全局常量,配置後就能夠在代碼裏使用這個常量了。
/config/webpack.base.config.js
裏添加plugins:const webpack = require('webpack') module.exports = { plugins: [ ......, // 接上,在這裏追加 new webpack.DefinePlugin({ VERSION_H5: +new Date() // 這裏添加了VERSION_H5 }), ] }
須要注意,若是給定義的常量賦值爲string類型時須要帶上原始引號,能夠經過單引號包裹雙引號的方式或經過JSON.stringify包裹,例如 '"abc"' 或 JSON.stringify('abc')
// 找個合適的地方添加就行 console.log(VERSION_H5)
css modules是一種防止css樣式污染的模塊化解決方案。
接下來就配置.module.css或.module.less後綴的文件自動以css modules方式處理。
/config/webpack.base.config.js
裏配置loader:const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 爲了代碼簡潔,在這裏封裝了一下 const cssTest = /\.css$/ const lessTest = /\.less$/ const cssModuleTest = /\.module\.css$/ const lessModuleTest = /\.module\.less$/ const baseCssUse = [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] const baseCssModuleUse = [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: { localIdentName: "[name]_[local]__[hash:5]" } }, }, 'postcss-loader' ] module.exports = { module: { rules: [ ......, // 把以前的css和less的配置 替換成如下代碼 { test: cssTest, exclude: cssModuleTest, use: baseCssUse }, { test: lessTest, exclude: lessModuleTest, use: [...baseCssUse, 'less-loader'] }, { test: cssModuleTest, use: baseCssModuleUse }, { test: lessModuleTest, use: [...baseCssModuleUse, 'less-loader'] }, ......, ] }, }
.name { text-decoration: line-through; }
/src/index.js
添加代碼:import style from './index.module.less' function App () { return ( <div> <div className='test'>小王子</div> <div className={style.name}>Neo</div> <div> <img src={icon} alt=""/> </div> </div> ) }
npm start
重啓項目,chrome控制檯查看元素及樣式效果。es6包含新的語法和新的api,新api是用更底層的語言實現的,新語法默承認以被babel降級處理,但新api默認不會處理,例如數組的find、Object.assign、promise等,須要配置polyfill來處理。
從babel v7.4版本開始,官方再也不推薦使用@babel/polyfill,更推薦直接使用core-js/stable和regenerator-runtime/runtime。
npm i -S core-js regenerator-runtime
/src/index.js
裏最頂部導入:// 必須在入口文件最頂部導入 import "core-js/stable" import "regenerator-runtime/runtime" // 而後再導入其餘的 ......
babel.config.js
裏修改@babel/preset-env配置:module.exports = { presets: [ // 在這裏修改 @babel/preset-env 的配置 [ '@babel/preset-env', { modules: false, useBuiltIns: 'entry', corejs: { version: '3.8', // 你的core-js版本號前兩位 proposals: true, }, }, ], // 其餘的保持不變 ...... ], }
實際項目中,本地開發通常都會遇到接口跨域的問題,協議、域名、端口號 這三項任意一項不一致就會跨域,在devServer裏配置proxy代理能夠解決跨域問題。
/proxy
。/config/webpack.dev.config.js
裏配置devServer:module.exports = merge(common, { devServer: { contentBase: path.resolve(__dirname, '../dist'), open: false, port: 9000, compress: true, hot: true, inline: true, proxy: { '/proxy': { target: 'https://192.111:8800', ws: true, changeOrigin: true, secure: false, pathRewrite: { '^/proxy': '' } } } }, })
target
目標地址,有端口號的須要帶上端口號;ws
配置是否支持 web socket;changeOrigin
配置是否支持虛擬主機站點,我也不清楚具體啥意思;secure
是否開啓安全驗證,目標地址爲https
時需設置secure爲false;pathRewrite
路徑重寫,上述是配置了代理後將/proxy替換爲空字符串,即實際接口地址再也不須要攜帶/proxy。cross-env是一款運行跨平臺設置和使用環境變量的腳本。使用cross-env在scripts腳本命令裏配置自定義變量能夠實現命令行快捷切換環境配置的功能。
好比配置不一樣的測試環境使用不一樣的接口地址,傳統方式多是直接在devServer裏修改proxy代理地址的代碼,人工修改代碼容易出錯,在多人開發時也容易出現代碼衝突,若是使用cross-env配置的變量進行判斷設置對應的代理地址,經過切換scripts命令來切換代理就變的方便多了。
npm i cross-env -D
package.json
裏修改scripts:"scripts": { "start": "npm run start:test1", "start:test1": "cross-env MY_TYPE=test1 webpack-dev-server --progress --config ./config/webpack.dev.config.js", "start:test2": "cross-env MY_TYPE=test2 webpack-dev-server --progress --config ./config/webpack.dev.config.js", "build": "webpack --config ./config/webpack.prod.config.js" },
MY_TYPE
這個變量,兩個命令設置的值分別是test1和test2,運行npm run start:test1
時在webpack配置文件裏就能夠經過process.env.MY_TYPE
獲取到值。cross-env自動把咱們設置的變量加在了process.env這個對象上,可是process.env只能在node環境裏獲取到,而在瀏覽器環境裏獲取不到。
不過還記得上面第4條介紹的DefinePlugin嗎,利用DefinePlugin咱們能夠添加個瀏覽器環境也能用的process.env對象,方式以下:
/config/webpack.base.config.js
裏定義DefinePlugin插件配置:new webpack.DefinePlugin({ // VERSION_H5: +new Date(), 'process.env': Object.keys(process.env).reduce( (env, key) => { env[key] = JSON.stringify(process.env[key]); return env; }, {} ) }),
/src/index.js
裏添加打印語句:console.log(process.env.NODE_ENV) console.log(process.env.MY_TYPE)
npm run start:test1
和npm run start:test2
查看瀏覽器打印結果。