以前一段時間工做緣由把精力都放在小程序上,趁如今有點空閒時間,恰好官方文檔也補充完整了,我準備重溫一下 webpack 之路了,由於官方文檔已經寫得很是詳細,我會大量引用原文描述,主要重點放在怎麼從零構建 webpack4 代碼上,這不是一個系統的教程,而是從零摸索一步步搭建起來的筆記,因此前期可能bug會後續發現繼續修復而不是修改文章.javascript
webpack4從零開始構建(一)
webpack4+React16項目構建(二)
webpack4功能配置劃分細化(三)
webpack4引入Ant Design和Typescript(四)
webpack4代碼去重,簡化信息和構建優化(五)
webpack4配置Vue版腳手架(六)css
基本已經可使用的完整配置webpack4_demohtml
PS: java
2018/12/12 修改細節佈局
2018/12/26上傳,代碼同步到第四篇文章
2019/03/14上傳,補充代碼到第二篇文章node
首先安裝React環境庫react
yarn add react react-dom react-router-dom
修改index.html以下webpack
<!doctype html> <html> <head> <title>webpack + React</title> </head> <body> <div id="root"></div> </body> </html>
修改index.js,引用React語法寫個簡單例子git
import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(<div>Hello world</div>, document.getElementById("root"));
到此還沒結束,咱們還須要安裝下面babel依賴配置webpack環境才能正常打包運行github
yarn add babel-core babel-loader@7 babel-preset-env babel-preset-react
粗略講解一下各個依賴幹嗎的web
注意: 由於babel-core@7+還不穩定,因此默認安裝@6+,須要babel-loader@7+才能運行,因此上面指定了版本
根目錄新增 .babelrc
配置文件,babel全部的操做基本都會來讀取這個配置文件,若是沒有這個配置文件,會從package.json
文件的babel屬性中讀取配置。
{ "presets": [ ["env", { modules: false }], "react" ] }
注意: 由於Tree Shaking這個功能是基於ES6 modules 的靜態特性檢測,來找出未使用的代碼,因此若是你使用了 babel 插件的時候,如:babel-preset-env,它默認會將模塊打包成commonjs,這樣就會讓Tree Shaking失效了,因此咱們要設置一下關閉 Babel 的模塊轉換功能,保留本來的 ES6 模塊化語法。
咱們還要去webpack.common.js
配置loader,完整配置以下
const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CleanWebpackPlugin = require("clean-webpack-plugin"); module.exports = { // 入口 entry: "./src/index.js", // 輸出 output: { // 打包文件名 filename: "[name].bundle.js", // 輸出路徑 path: path.resolve(__dirname, "dist"), // 資源請求路徑 publicPath: "" }, module: { rules: [ { test: /\.(js|jsx)$/, // 匹配文件 exclude: /node_modules/, // 過濾文件夾 use: { loader: "babel-loader" } }, { test: /\.(css|scss)$/, // 匹配文件 use: [ "style-loader", // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面 "css-loader", // 加載.css文件將其轉換爲JS模塊 "sass-loader" // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] }, { test: /\.(png|svg|jpg|jpeg|gif)$/, // 圖片處理 use: ["file-loader"] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, // 字體處理 use: ["file-loader"] }, { test: /\.xml$/, // 文件處理 use: ["xml-loader"] }, { test: /\.html$/, // 處理html資源如圖片 use: ["html-loader"] } ] }, plugins: [ // 清除文件 new CleanWebpackPlugin(), new HtmlWebpackPlugin({ // title title: "test", // 模板 template: "index.html" }) ] };
繼續使用上一章配置過命令和當前依賴文件package.json, 完整代碼以下:
{ "sideEffects": false, "scripts": { "dev": "webpack --config webpack.dev.js", "build": "webpack --config webpack.prod.js", "start": "webpack-dev-server --config webpack.server.js" }, "dependencies": { "babel-core": "^6.26.3", "babel-loader": "7", "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "clean-webpack-plugin": "^2.0.0", "css-loader": "^2.1.1", "file-loader": "^3.0.1", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "node-sass": "^4.11.0", "react": "^16.8.4", "react-dom": "^16.8.4", "react-router-dom": "^4.3.1", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "url-loader": "^1.1.2", "webpack": "^4.29.6", "webpack-bundle-analyzer": "^3.1.0", "webpack-cli": "^3.2.3", "webpack-dev-server": "^3.2.1", "webpack-merge": "^4.2.1", "xml-loader": "^1.2.1" }, "name": "webpack_demo", "version": "1.0.0", "main": "index.js", "license": "MIT" }
打開終端執行命令
npm run dev
運行dist目錄下的index.html文件能夠查看效果
接下來繼續展開代碼,按照正常項目開發使用ES6方式開發和引用資源處理,首先咱們分門別類區分一下資源
index.js修改以下:
import React from "react"; import ReactDOM from "react-dom"; import Main from "./page/main"; ReactDOM.render(<Main />, document.getElementById("root"));
新增main.js
文件代碼以下:
import React, { Component, Fragment } from "react"; import ReactDOM from "react-dom"; import "../style/style.scss"; export default class Main extends Component { constructor(props, context) { super(props, context); this.state = { title: "Hello World!" }; } // 掛載前 componentWillMount() { console.log("componentWillMount"); } // 掛載後 componentDidMount() { console.log("componentDidMount"); } // 接受新props componentWillReceiveProps(nextProps) { console.log("componentWillReceiveProps", nextProps); } // 是否從新渲染 shouldComponentUpdate(nextProps, nextState) { console.log("shouldComponentUpdate", nextProps, nextState); } // 更新前 componentWillUpdate(nextProps, nextState) { console.log("componentWillUpdate", nextProps, nextState); } // 更新後 componentDidUpdate(prevProps, prevState) { console.log("componentDidUpdate", nextProps, nextState); } // 卸載前 componentWillUnmount() { console.log("componentWillUnmount"); } // 捕捉錯誤 componentDidCatch() { console.log("componentDidCatch"); } render() { return ( <Fragment> <img className="img1" src={require("../img/1.jpg")} alt="" /> <div className="img2" /> <p>{this.state.title}</p> </Fragment> ); } }
在jsx裏相對路徑的圖片不會被file-loader和url-loader處理,因此咱們使用這種寫法引入比較方便
<img className="img1" src={require("../img/1.jpg")} alt="" />
style.scss以下:
html { background-color: #666; p { color: red; } .img1, .img2 { width: 250px; height: 400px; } .img2 { background: url("../img/2.jpg") no-repeat center center; background-size: cover; } }
修改一下webpack.common.js
圖片處理,使用url-loader將50kb內的圖片轉成base64編碼保存進代碼減小請求,不符合條件的打包圖片放到一個單獨文件夾img,由於url-loader內置有file-loader,因此咱們沒必要要再引入
yarn add image-webpack-loader
{ test: /\.(html)$/, use: { loader: "html-loader", options: { attrs: ["img:src", "img:data-src", "audio:src"], minimize: true } } }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, // 圖片處理 use: [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 50 * 1024, // size <= 50kb outputPath: "img" } } ] },
從新執行命令
npm run dev
打開頁面會看到1.jpg變成base64代碼,一切都在預期內.
根目錄新增router文件夾,裏面建立index.js,代碼以下:
import React, { Component, Fragment } from "react"; import Main from "../page/main"; class App extends Component { render() { return ( <Fragment> <Main /> </Fragment> ); } }
而後收拾一下main.js頁面,把多餘生命週期清除掉
import React, { Component, Fragment } from "react"; import { Switch, Route, Redirect, Link } from "react-router-dom"; import View1 from "../component/view1"; import View2 from "../component/view2"; import "../style/style.scss"; export default class Main extends Component { constructor(props, context) { super(props, context); this.state = { title: "Hello World!" }; } render() { return ( <Fragment> <p>{this.state.title}</p> <Link to="/view1/">View1</Link> <Link to="/view2/">View2</Link> <Switch> <Route exact path="/" component={View1} /> <Route path="/view1/" component={View1} /> <Route path="/view2/" component={View2} /> <Redirect to="/" /> </Switch> </Fragment> ); } }
分別新增page1.js
和page2.js,main.js
的圖片分別遷移進去新目錄component
import React, { Fragment } from "react"; export default () => { return ( <Fragment> <p>Page1</p> <img className="img1" src={require("../img/1.jpg")} alt="" /> </Fragment> ); };
import React, { Fragment } from "react"; export default () => { return ( <Fragment> <p>Page2</p> <div className="img2" /> </Fragment> ); };
最後src目錄下的index.js修改以下:
import React from "react"; import ReactDOM from "react-dom"; import { HashRouter } from "react-router-dom"; import Main from "./page/main"; ReactDOM.render( <HashRouter> <Main /> </HashRouter>, document.getElementById("root") );
如今整個目錄結構以下
執行命令
npm run dev
一個簡單的路由切換頁面就完成了,界面大概以下
上面咱們只是將小於50kb的圖片內嵌進代碼裏,超過50kb的圖片咱們能夠引入插件做處理
yarn add image-webpack-loader
而後咱們在修改一下loader配置
{ test: /\.(png|svg|jpe?g|gif)$/i, // 圖片處理 use: [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } }, { loader: "image-webpack-loader", options: { // Compress JPEG images mozjpeg: { progressive: true, quality: 65 }, // Compress PNG images optipng: { enabled: false }, // Compress PNG images pngquant: { quality: "65-90", speed: 4 }, // Compress GIF images gifsicle: { interlaced: false }, // Compress JPG & PNG images into WEBP webp: { quality: 75 } } } ] },
注意順序,這種寫法會先通過壓縮以後再有url-loader做處理,可以讓部分本來不符合大小的圖片壓縮以後就知足轉碼base64了,爲了突出效果限制到20kb內.
以個人測試圖爲例,壓縮率達到
80.4kb -> 45.9kb
至於其餘圖片配置可根據本身需求修改
隨着文件愈來愈多,引用路徑愈來愈複雜,會容易讓人混亂,咱們可使用resolve作些依賴處理,這些選項能設置模塊如何被解析
在webpack.common.js
新增下面配置代碼,設置簡化路徑
resolve: { // 建立 import 或 require 的別名,來確保模塊引入變得更簡單 alias: { "@": path.resolve(__dirname, "src/"), IMG: path.resolve(__dirname, "src/img"), STYLE: path.resolve(__dirname, "src/style"), JS: path.resolve(__dirname, "src/js"), ROUTER: path.resolve(__dirname, "src/router"), PAGE: path.resolve(__dirname, "src/page"), CMT: path.resolve(__dirname, "src/component") } }
而後咱們就能夠修改對應的文件引入模塊寫法,例如
import "STYLE/style.scss";
其餘可自行修改
目前基本搭建完了,而後咱們就能夠利用一款檢測打包性能的插件找到可優化空間
yarn add webpack-bundle-analyzer
在webpack.server.js新增依賴
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") .BundleAnalyzerPlugin;
plugins裏初始化方法
new BundleAnalyzerPlugin()
執行命令會自動打開頁面http://127.0.0.1:8888/,這裏能夠看到性能圖,不影響本來的http://localhost:9000/#/查看項目
npm run start
webpack4使用插件和以前版本不同,咱們安裝如下依賴
yarn add mini-css-extract-plugin
修改一下webpack.common.js的配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); --------------------------省略----------------------------------- { test: /\.scss$/, // 匹配文件 use: [ { loader: MiniCssExtractPlugin.loader, options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: "../" } }, // "style-loader", // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面 "css-loader", // 加載.css文件將其轉換爲JS模塊 "sass-loader" // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] }, ---------------------------省略------------------------------------ plugins: [ // 提取樣式文件 new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: "style/[name].[chunkhash:8].css", chunkFilename: "style/[id].css" }) ],
執行命令以後就看到有新的樣式文件獨立出來了
npm run dev
從webpack4開始官方移除了commonchunk插件,改用了optimization屬性進行更加靈活的配置,再生產環境下回自動開啓,只須要設置
mode: "production"
從文檔來看它的默認設置是這樣的
New chunk can be shared OR modules are from the node_modules folder
New chunk would be bigger than 30kb (before min+gz)
Maximum number of parallel requests when loading chunks on demand would be lower or equal to 5
Maximum number of parallel requests at initial page load would be lower or equal to 3
新chunk是可以被共享或者來自node_modules文件
新chunk在min+gz壓縮以前大於30kb
按需加載的並行請求數小於等於5
首屏渲染的最大並行請求數小於等於3
由於如今demo比較小,沒什麼好講解的,通常根據項目狀況調整一下拆分機制就行了,假如我想要把node_modules和組件代碼拆分出來,能夠這麼寫
module.exports = merge(common, { optimization: { splitChunks: { // 表示顯示塊的範圍,有三個可選值:initial(初始塊)、async(按需加載塊)、all(所有塊) chunks: "all", cacheGroups: { libs: { // 優先級高於其餘就不會被打包進其餘chunk,若是想匹配本身定義的拆分規則,則priority須要設置爲正數,優先匹配默認拆分規則就設置爲負數。 priority: 10, test: /[\\/]node_modules[\\/]/, name: "chunk-libs", chunks: "initial" }, commons: { // 優先級高於其餘就不會被打包進其餘chunk,若是想匹配本身定義的拆分規則,則priority須要設置爲正數,優先匹配默認拆分規則就設置爲負數。 priority: 15, test: path.resolve(__dirname, "src/component"), name: "chunk-commons", // 最小共用次數 minChunks: 2, // 若是當前chunk已經被打包進其餘chunk的時候就再也不打包,而是複用其餘chunk reuseExistingChunk: true } } } } });