上一篇記錄了一下webpack4使用的一些基礎使用小技巧,確實沒有想到能收穫這麼大的反響,仍是很是感謝各位的錯愛,沒有看過的關於webpack4的14個知識點,童叟無欺javascript
這一篇文章將react和webpack4進行結合,集webpack的優點於一身,從0開始構建一個強大的react開發環境css
本篇全部代碼線上代碼react-webpack4-cook,翻譯過來叫:webpack4和react的亂燉,能夠跟着代碼進行配置,以前有不少坑,線上代碼都已經被解決了 。若是對您有幫助,不妨給個star.點贊關注不迷路html
一篇文章不寫前言總感受不太正式,大概介紹下我是怎麼完成一個總的知識點的歸納的把。其實不少人都有一看就會,一作就廢的特色(固然也包括我在內),這個時候,你須要制定一個略微詳細的計劃,就好比我這篇會首先列出知識點,列出大的方向,制定思惟導圖,而後根據思惟導圖編寫代碼,計劃明確,就會事半功倍,所以,但願你能夠跟着本篇按部就班的跟着代碼走一遍,無論是真實開發,仍是面試,都有的扯。好了,不扯了,能夠先看下目錄。如今開始java
mkdir react-webpack4-cook
cd react-webpack4-cook
mkdir src
mkdir dist
npm init -y
複製代碼
yarn add webpack webpack-cli webpack-dev-server -D //webpack4把webpack拆分了
touch webpack.config.js
// webpack.config.js初始化內容
module.exports = {
mode: "development",
entry: ["./src/index.js"],
output: {
// 輸出目錄
path: path.join(__dirname, "dist"),
// 文件名稱
filename: "bundle.js"
},
module:{},
plugins:[],
devServer:{}
}
複製代碼
這部分代碼篇幅過多,就是一些簡單的react和react-router的一些代碼編寫,能夠去github上查看,這裏只闡述基本功能node
cd src
cnpm i react react-router-dom -S
// 創建以下的文件目錄,並編寫安裝react和react-router並編寫react代碼以下
|-src
│ index.js 主文件
├───pages
│ Count.jsx -- 實現了一個計數器的功能,點擊按鈕,會讓數字增長,按鈕會實時顯示在頁面上
│ Home.jsx -- 一個簡單的文字展現
└───router
index.js -- 路由配置文件,兩個頁面分別對應兩個路由 count和 home
複製代碼
// @babel/core-babel核心模塊 @babel/preset-env-編譯ES6等 @babel/preset-react-轉換JSX
cnpm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/preset-react -D
// @babel/plugin-transform-runtime: 避免 polyfill 污染全局變量,減少打包體積
// @babel/polyfill: ES6 內置方法和函數轉化墊片
cnpm i @babel/polyfill @babel/runtime
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
複製代碼
新建.babelrc文件react
{
"presets": ["@babel/preset-env","@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
複製代碼
在src下的index.js中全局引入 @babel/polyfill
並寫入 ES6 語法 ,可是這樣有一個缺點:jquery
@babel/polyfill
的這種方式可能會導入代碼中不須要的 polyfill,從而使打包體積更大更改 .babelrc
,只轉譯咱們使用到的webpack
npm install core-js@2 @babel/runtime-corejs2 -S
{
"presets": ["@babel/preset-env",
{ "useBuiltIns": "usage" },
"@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
將將全局引入這段代碼註釋掉
// import '@babel/polyfill'
複製代碼
這就配置好了按需引入。配置了按需引入 polyfill
後,用到es6
以上的函數,babel
會自動導入相關的polyfill
,這樣能大大減小 打包編譯後的體積git
你通過屢次打包後會發現,每次打包都會在dist目錄下邊生成一堆文件,可是上一次的打包的文件還在,咱們須要每次打包時清除 dist 目錄下舊版本文件es6
cnpm install clean-webpack-plugin -D
// 注意這個引入的坑,最新版的須要這樣引入,而不是直接const CleanWebpackPlugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin()
]
複製代碼
通過上一步的操做,index.html
也被清除了。所以咱們將使用 HtmlWebpackPlugin
插件,來生成 html
, 並將每次打包的js自動插入到你的 index.html
裏面去,並且它還能夠基於你的某個 html
模板來建立最終的 index.html
,也就是說能夠指定模板哦
cnpm install html-webpack-plugin -D
// 建立template.html
cd src
touch template.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>react-webpack4-cook</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
// webpack.config.js作出更改
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html', // 最終建立的文件名
template: path.join(__dirname, 'src/template.html') // 指定模板路徑
})
]
複製代碼
webpack中devtool選項用來控制是否生成,以及如何生成 source map。簡言之,source map就是幫助咱們定位到錯誤信息位置的文件。正確的配置source map,可以提升開發效率,更快的定位到錯誤位置。
在webpack.config.js中選項mode下加上以下這句話:
devtool:"cheap-module-eval-source-map",// 開發環境配置
devtool:"cheap-module-source-map", // 線上生成配置
複製代碼
webpack-dev-server
就是在本地爲搭建了一個小型的靜態文件服務器,有實時重加載的功能,爲將打包生成的資源提供了web服務
devServer: {
hot: true,
contentBase: path.join(__dirname, "./dist"),
host: "0.0.0.0", // 可使用手機訪問
port: 8080,
historyApiFallback: true, // 該選項的做用全部的404都鏈接到index.html
proxy: {
// 代理到後端的服務地址,會攔截全部以api開頭的請求地址
"/api": "http://localhost:3000"
}
}
複製代碼
創建了開發環境本地服務器 後,當修改內容後,網頁會同步刷新,咱們如今進入toCOunt頁面
點擊按鈕,將數字加到一個不爲0的數,好比加到6
而後你能夠在代碼中改變按鈕的文字,隨便改點東西,會發現,頁面刷新後,數字從新變爲0
這顯然不是咱們想要的,想要的是,能不能把頁面的狀態保存了,也就是更改了代碼後,頁面仍是保存了數字爲6的狀態,也就是實現局部更改,首先須要用到:HotModuleReplacementPlugin插件
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
複製代碼
完事以後,繼續更上邊的操做,點擊按鈕,數字增長,而後更改內容,發現仍是沒有保存狀態。。。what?怎麼辦
對@!這還沒完呢,接着往下看,咱們還須要react-hot-loader這個插件
咱們繼續接着上邊的進行操做,分一下四步
cnpm i react-hot-loader -D
// 在主文件裏這樣寫
import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";-------------------1、首先引入AppContainre
import { BrowserRouter } from "react-router-dom";
import Router from "./router";
/*初始化*/
renderWithHotReload(Router);-------------------2、初始化
/*熱更新*/
if (module.hot) {-------------------3、熱更新操做
module.hot.accept("./router/index.js", () => {
const Router = require("./router/index.js").default;
renderWithHotReload(Router);
});
}
function renderWithHotReload(Router) {-------------------4、定義渲染函數
ReactDOM.render(
<AppContainer> <BrowserRouter> <Router /> </BrowserRouter> </AppContainer>,
document.getElementById("app")
);
}
複製代碼
好了,如今你再試試
cnpm install css-loader style-loader sass-loader node-sass -D
{
test: /\.scss$/,
use: [
"style-loader", // 建立style標籤,並將css添加進去
"css-loader", // 編譯css
"sass-loader" // 編譯scss
]
}
複製代碼
最關心的仍是這有啥用啊?自動增長前綴, postcss-cssnext容許你使用將來的css特性,並作一些兼容處理
cnpm install postcss-loader postcss-cssnext -D
{
test: /\.scss$/,
use: [
"style-loader", // 建立style標籤,並將css添加進去
"css-loader", // 編譯css
"postcss-loader",
"sass-loader" // 編譯scss
]
}
// 在剛纔的home.scss 加上 transform: scale(2);
經過控制檯查看,已經自動加上了前綴
複製代碼
cnpm i file-loader url-loader -D
file-loader 解決css等文件中引入圖片路徑的問題
url-loader 當圖片較小的時候會把圖片BASE64編碼,大於limit參數的時候仍是使用file-loader 進行拷貝
{
test: /\.(png|jpg|jpeg|gif|svg)/,
use: {
loader: 'url-loader',
options: {
outputPath: 'images/', // 圖片輸出的路徑
limit: 10 * 1024
}
}
}
複製代碼
{
test: /\.(eot|woff2?|ttf|svg)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]-[hash:5].min.[ext]',
limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
publicPath: 'fonts/',
outputPath: 'fonts/'
}
}
]
}
複製代碼
resolve: {
extension: ["", ".js", ".jsx"],
alias: {
"@": path.join(__dirname, "src"),
pages: path.join(__dirname, "src/pages"),
router: path.join(__dirname, "src/router")
}
},
複製代碼
CDN經過將資源部署到世界各地,使得用戶能夠就近訪問資源,加快訪問速度。要接入CDN,須要把網頁的靜態資源上傳到CDN服務上,在訪問這些資源時,使用CDN服務提供的URL。
output:{
publicPatch: '//【cdn】.com', //指定存放JS文件的CDN地址
}
複製代碼
若是不作配置,咱們的css
是直接打包進js
裏面的,咱們但願能單獨生成css
文件。 由於單獨生成css,css能夠和js並行下載,提升頁面加載效率
cnpm install mini-css-extract-plugin -D
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
{
test: /\.scss$/,
use: [
// "style-loader", // b再也不須要style-loader要已經分離處理
MiniCssExtractPlugin.loader,
"css-loader", // 編譯css
"postcss-loader",
"sass-loader" // 編譯scss
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
複製代碼
爲何要實現按需加載?
咱們如今看到,打包完後,全部頁面只生成了一個bundle.js,當咱們首屏加載的時候,就會很慢。由於他也下載了別的頁面的js
了,也就是說,執行完畢以前,頁面是 完!全!空!白!的!。 若是每一個頁面單獨打包本身的js,就能夠在進入頁面時候再加載本身 的js,首屏加載就能夠快不少
optimization: {
splitChunks: {
chunks: "all", // 全部的 chunks 代碼公共的部分分離出來成爲一個單獨的文件
},
},
複製代碼
webpack4只要在生產模式下, 代碼就會自動壓縮
mode:productioin
複製代碼
能夠直接在全局使用$變量
new webpack.ProvidePlugin({
$: 'jquery', // npm
jQuery: 'jQuery' // 本地Js文件
})
複製代碼
plugins: [
new webpack.DefinePlugin({
'process.env': {
VUEP_BASE_URL: JSON.stringify('http://localhost:9000')
}
}),
]
複製代碼
npm i glob-all purify-css purifycss-webpack --save-dev
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
// 清除無用 css
new PurifyCSS({
paths: glob.sync([
// 要作 CSS Tree Shaking 的路徑文件
path.resolve(__dirname, './src/*.html'), // 請注意,咱們一樣須要對 html 文件進行 tree shaking
path.resolve(__dirname, './src/*.js')
])
})
]
複製代碼
清除到代碼中無用的js代碼,只支持import方式引入,不支持commonjs的方式引入
只要mode是production就會生效,develpoment的tree shaking是不生效的,由於webpack爲了方便你的調試
optimization: {
usedExports:true,
}
複製代碼
項目中引入了不少第三方庫,這些庫在很長的一段時間內,基本不會更新,打包的時候分開打包來提高打包速度,而DllPlugin動態連接庫插件,其原理就是把網頁依賴的基礎模塊抽離出來打包到dll文件中,當須要導入的模塊存在於某個dll中時,這個模塊再也不被打包,而是去dll中獲取。
安裝jquery,並在入口文件引入。新建webpack.dll.config.js文件
/* * @desc 靜態公共資源打包配置 */
const path = require('path')
const webpack = require('webpack')
const src = path.resolve(process.cwd(), 'src'); // 源碼目錄
const evn = process.env.NODE_ENV == "production" ? "production" : "development";
module.exports = {
mode: 'production',
entry: {
// 定義程序中打包公共文件的入口文件vendor.js
jquery: ['jquery']
},
output: {
path: path.resolve(__dirname, '..', 'dll'),
filename: '[name].dll.js',
library: '[name]_[hash]',
libraryTarget: 'this'
},
plugins: [
new webpack.DllPlugin({
// 定義程序中打包公共文件的入口文件vendor.js
context: process.cwd(),
// manifest.json文件的輸出位置
path: path.resolve(__dirname, '..', 'dll/[name]-manifest.json'),
// 定義打包的公共vendor文件對外暴露的函數名
name: '[name]_[hash]'
})
]
}
複製代碼
在package.json中添加
"build:dll": "webpack --config ./build/webpack.dll.config.js",
複製代碼
運行
npm run build:dll
複製代碼
你會發現多了一個dll文件夾,裏邊有dll.js文件,這樣咱們就把咱們的jquery這些已經單獨打包了,接下來怎麼使用呢?
須要再安裝一個依賴 npm i add-asset-html-webpack-plugin
,它會將咱們打包後的 dll.js 文件注入到咱們生成的 index.html 中.在 webpack.base.config.js 文件中進行更改。
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/jquery.dll.js') // 對應的 dll 文件路徑
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '..', 'dll/jquery-manifest.json')
})
複製代碼
好了,你可有吧new webpack.DllReferencePlugin這個插件註釋掉,打包試下,在放開打包試一下,我測試結果,註釋錢5689,註釋後,5302ms,才差了300ms?注意,我這裏只有一個jquery包做爲演示,要是你把不少個都抽離了出來呢???那豈不是很恐怖了。若是你看的有點迷迷糊糊,那推薦去線上看一下個人代碼吧,一看便知
運行在 Node.之上的Webpack是單線程模型的,也就是說Webpack須要一個一個地處理任務,不能同時處理多個任務。 Happy Pack 就能讓Webpack作到這一點,它將任務分解給多個子進程去併發執行,子進程處理完後再將結果發送給主進程。
cnpm i -D happypack
// webpack.config.js
rules: [
{
// cnpm i babel-loader @babel/core @babel/preset-env -D
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
// 一個loader對應一個id
loader: "happypack/loader?id=busongBabel"
}
]
}
]
//在plugins中增長
plugins:[
new HappyPack({
// 用惟一的標識符id,來表明當前的HappyPack是用來處理一類特定的文件
id:'busongBabel',
// 如何處理.js文件,用法和Loader配置中同樣
loaders:['babel-loader?cacheDirectory'],
threadPool: HappyPackThreadPool,
})
]
複製代碼
簡言之:在你第一次訪問一個網站的時候,若是成功,作一個緩存,當服務器掛了以後,你依然可以訪問這個網頁 ,這就是PWA。那相信你也已經知道了,這個只須要在生產環境,才須要作PWA的處理,以防不測。
cnpm i workbox-webpack-plugin -D
const WorkboxPlugin = require('workbox-webpack-plugin') // 引入 PWA 插件
const prodConfig = {
plugins: [
// 配置 PWA
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
]
}
在入口文件加上
// 判斷該瀏覽器支不支持 serviceWorker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(registration => {
console.log('service-worker registed')
})
.catch(error => {
console.log('service-worker registed error')
})
})
}
複製代碼
配置完後,你能夠打包到dist目錄下,在dist目錄下啓動一個靜態服務器,訪問首頁,而後關閉這個服務器,你會驚訝的發現:網站居然還可以訪問,哈哈,是否是很神奇?
webpack
公共配置開發環境與生產環境以及webpack配置文件的分離,具體須要用到webpack-merge,用來 合併 webpack配置
複製代碼
因爲時間和篇幅的限制,基本到這裏就結束了。以上,無論是提到的未提到的,或者還有一些細枝末節,github上的源碼基本都已經所有包括在內了,若是有須要能夠去github參照配置文件,本身跟着配一份出來,會更加事半功倍
都到這裏了,您不妨點個贊,給個star,加個關注。本篇全部代碼線上代碼react-webpack4-cook,翻譯過來叫:webpack4和react的亂燉,能夠跟着代碼進行配置,以前有不少坑,線上代碼都已經被解決了 。若是對您有幫助,不妨給個star.點贊關注不迷路