從零學腳手架(五)---react、browserslist

若是此篇對您有所幫助,在此求一個star。項目地址: OrcasTeam/my-cli

react

react介紹

目前,國內主流的前端應用框架具備兩個:vue.jsreact.js,關於vue和react的優劣性,網上衆說紛紜。在下就不在此引戰。html

而是直接介紹React前端

🐋🐋🐋 vueReact這種都是快速應用開發工具,可能也會像曾經如日中天的JQuery被市場淘汰,因此我的建議不要盲目只追求快速工具的使用,而是花時間去學習原點。例如設計思想數據結構。快速應用框架(或語言)只不過是應用工具而已。vue

🐋 之前都說是「三大框架」,還有一個Google開發的Angular,可是國內Angular使用份額愈來愈少。node

我的感受Angular主要問題是上手成本。Angular比較偏向於後端,不少概念對於前端開發人員都是噩夢。不過對於前端工程化,我的認爲Angular是集大成之做。我的建議,對於有經驗的朋友,能夠稍微學習下Angular中的思想。react

React是一個用於構建用戶界面的 JavaScript 庫,jquery

React自己是一個特別簡單的庫:將元素抽象爲虛擬DOM,更新DOM時對比虛擬DOM,而後只更新那些真正須要更新的元素。webpack

React.createElement()

使用Document構建DOM時,都是使用 document.createElement() 來構建標籤git

const li =  document.createElement('li');
document.body.appendChild(li)

React中, 也提供了這樣一個自定義函數來React組件。github

React.createElement() 返回的是一個React自定義的元素類型:ReactElementweb

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React提供的React.createElement()ReactElement提供了很好平臺隔離性。

使用同一套代碼編寫的元素組件只須要對接不一樣平臺的APi,就能夠實現跨平臺。

React可以跨平臺的緣由也在於此。

在平常開發中,也會常常寫無關業務的通用封裝,其思想與此相似。

虛擬DOM

在直接使用Document更新DOM元素時,不少時候會由於某些緣由 對沒必要更新DOM進行更新 從而產生了性能浪費

解決這個問題通常想到的作法就是作一個DOM緩存。建立DOM時將DOM信息緩存,更新時對比新舊DOM。排除掉沒必要要的更新DOM。

這種緩存DOM數據的方案就叫虛擬DOM(Virtual DOM), 而排除算法叫作diff算法

React也使用了這種方案提高性能

虛擬DOM(Virtual DOM)diff算法 是對數據結構和算法的考驗。每個人均可以模擬出簡單的方案,但不是每個人均可以寫出優秀的解決方案。

在下愚鈍,對於數據結構和算法掌握的很差。因此對虛擬DOM(Virtual DOM)diff算法只有淺薄的認知。有興趣的朋友能夠看一下這篇文章:深度剖析:如何實現一個 Virtual DOM 算法

JSX

React是經過JS構建元素的,

咱們都知道使用JS編寫頁面痛苦是沒有結構性。

使用HTML兩個標籤能搞定的事,使用JS就能寫一大堆代碼。

React爲了解決這個問題,提供了一個模板語言---JSX

JSX是一種JS擴展語言。容許在JS中以標籤形式構建元素。而且JSX開發工具中還能夠具備各類提示和快捷鍵。

可以極大的提升開發效率

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

JSX編寫的組件只是React.createElement()語法糖,打包編譯過程當中會將JSX語法轉換爲React.createElement()

🐋🐋🐋 JSX編寫的組件本質是 React.createElement() 語法糖。因此React還支持使用 React.createElement() 建立虛擬DOM(Virtual DOM)

🐋🐋 JSXReact提供構建代碼方式的一種擴展語言,本質是一個語法糖。JSX定義的事件styleclassJSX自身語法,並非原生DOM。因此有些屬性名稱不一致。

🐋🐋 JSX轉換React.createElement()操做使用的是babel提供的一個plugin,在下面再介紹

🐋 JSX目前被社區承認。Vue@3.X也支持JSX

添加 React

安裝 react

React目前最新版本爲17.0.1,在這裏就直接引用此版原本介紹,對React有興趣的朋友在從老版本循循漸進的學習。

yarn add react@17.0.1

react庫是React的核心庫,具備 React.createElement()虛擬DOMJSX語法支持等一系列核心內容。

可是此庫並不沒有提供與真實DOM交互。與真實DOM交互的代碼則由react-dom提供,

yarn add react-dom@17.0.1

react相似一個通用庫,沒有與任何平臺具備相關性,只負責組織數據結構。

就像寫React Native時,使用了react-native來作平臺交互。

使用 react

接下來就仿照react-cli來組織代碼。

根節點

第一步就是在HTML頁面中建立一個元素做爲React承載的根節點。

🐋 vue-cli也具備這麼一個根節點用來承載vue,只不過元素ID名稱不同,有興趣的朋友能夠自行查看。

接下來處理JS,在以前打包測試中都是使用 /src/index.js 文件做爲源文件

也是使用此文件做爲源文件

🐋🐋 React只是承載在打包器中的一個應用框架。通過打包器打包將JSX轉換爲可運行的代碼。

import React from 'react';
import ReactDOM from 'react-dom';

const root = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
ReactDOM.render(root, document.getElementById('root'));

/src/index.js 文件中使用了JSX建立元素,而後使用

react-dom中的 ReactDOM.render() 添加到根節點中。

🐋 vue-cli也一樣如此,有興趣的朋友能夠自行查看

@babel/preset-react

不過若是此時執行yarn build操做,會直接報錯。

這是由於JSX沒法被識別的問題。前面說過,JSX只是React提供的一種模板語言。本質上並不屬於JS模塊。

因此須要將JSX轉換爲 React.createElement() 形式

提供這個轉換操做的是babel中提供的一個plugin@Babel/plugin-syntax-jsx

不過不須要直接安裝這個plugin

babelReact提供了一個preset@babel/preset-react

@babel/preset-react中封裝了全部處理Reactplugin

yarn add -D @babel/preset-react@7.12.13

🐋 Babel官網提供了JSX轉換爲 React.createElement() 的測試,有興趣的朋友能夠測試測試

而後配置在 .babelrc 文件中

此時執行yarn build即可以執行成功,而且查看生成代碼能夠看到JSX已經轉換爲了React.createElement()

在瀏覽器也能夠正常運行代碼

.jsx文件
app.jsx

React代碼已經運行成功,接下來就組織React代碼。

剛纔,直接在 /src/index.js 文件中編寫了JSX代碼進行測試

可是真正開發中,須要將JSX代碼編寫在 .jsx 文件中,經過模塊導入導入方式提供給 /src/index.js 文件。

JSX提取到 /src/app.jsx 文件,在 /src/index.js 導入。

🐋🐋 app.jsx做爲React框架的根節點。用在承載React組件。

/src/app.jsx 文件中組件做爲React的根節點。React也是以樹的組織方式管理,/src/app.jsx 文件中組件就是樹根。React框架代碼就像 託管 在了 /src/app.jsx 之中

🐋 🐋

  • React組件分爲 函數組件類組件函數組件 方便,再加上 Hooks 的助力,在編寫顆粒度較小組件時使用 函數組件 是個很是好的選擇。類組件 封裝性強,內部提供完善的鉤子函數和一系列功能,再加上繼承特性。比較適合使用在業務代碼主幹中。
  • /src/app.jsx 中返回的 <></> 表明 空標籤React組件只容許返回一個元素,但有時候組件須要返回元素數組,能夠在外部包一層空標籤。與Vue中的template標籤功能一致。
  • React 組件名稱約定爲大寫形式
webpack配置

.jsx做爲一種新的文件格式,須要在webpack進行配置使用babel

const modules = {
  module:{
    rules:[
      {
        //  全部的.js或者.jsx文件都走babel-loader
        test: /\.js(x?)$/,
        include: path.join(config.root,'src'),
        loader: "babel-loader"
      }
    ]
  }
}

而且能夠提供引用時忽略後綴名稱。

resolve:{
    //  可被忽略的後綴
    extensions:['.jsx', '.js', '.json'],
  }

此時就算成功將React使用在腳手架中了。

而對於React RouterRedux只是用於擴展React的開發庫。在此就再也不添加。

🐋 vue-cli搭建方式與react-cli基本一致,只是各自框架暴露的API不一樣

browserslist

browserslist是什麼

在介紹babel時使用過package.json文件中browserslist屬性設置瀏覽器版本,那麼browserslist屬性究竟是怎麼回事呢?

前面介紹過,前端的運行環境(瀏覽器)版本是由用戶決定的,不一樣的項目對於瀏覽器版本要求不同。

而在打包過程當中。須要指定支持的瀏覽器版本,以這些版本對開發代碼作出適配。(CSS、JS都須要適配)。

browserslist屬性就是提供指定瀏覽器版本功能。是由browserslist庫提供的。

而這個簡單的功能browserslist卻作出了強大的效果,獲得了社區的高度承認。不少庫都直接依賴browserslist

browserslist配置方式

browserslist提供了兩種配置方式。

一種就是配置在package.json文件中的browserslist屬性。browserslist執行時會默認讀取此屬性。

另外一種是使用約定文件。能夠在項目根目錄(package.json所在目錄)建立一個約定文件 .browserslistrc.json ,將屬性配置在此。.browserslistrc.json文件名稱通常會省略後綴:.browserslistrc

兩種方式不可同時設置,不然會直接報錯。

我的推薦直接配置在package.json文件中,不必建立一個文件了。在此也就直接使用此方案。

browserslist環境變量

browserslist可使用不用屬性來靈活的控制瀏覽器版本。

以下所示。能夠設置在不一樣環境下設置不一樣瀏覽器版本。

"browserslist": {
    "development": [
        "chrome > 75"
    ],
     "production": [
         "ie 9"
     ]
}

屬性值取自Node.js中環境變量。環境變量名稱爲BROWSERSLIST_ENV。因此須要設置環境變量。

注意:在此雖然設置在webpack.config.js文件中,但設置的是Node.js中的環境變量, 並非webpack提供的環境變量。

browserslist屬性值名稱能夠隨意命名。只要與Node.jsBROWSERSLIST_ENV環境變量對應便可。

在此就不貼圖測試了,有興趣的朋友能夠自行測試。

至於BROWSERSLIST_ENV 環境變量與 webpack中不一樣模式的關聯,在下一篇介紹。

browserslist支持的瀏覽器

browserslist支持設置當前基本上全部的瀏覽器,在Github上做者說明了能夠設置的瀏覽器

能夠看到,browserslist幾乎支持全部瀏覽器:PC、安卓、IOS 甚至還有國內瀏覽器。

🐋🐋 設置瀏覽器時名稱不區分大小寫

browserslist屬性

browserslist能獲得社區的承認,也就在於browserslist提供了強大的屬性設置。

如前面使用的 指定 區間瀏覽器(chrome > 75) 也只是browserslist簡單的屬性配置

下面簡單列舉部分browserslist屬性配置,想了解更多的朋友請參考Github

  • defaultsbrowserslist設置的默認瀏覽器版本。屬性只至關 > 0.5%, last 2 versions, Firefox ESR, not dead

  • 指定版本號: 支持直接指定某個瀏覽器版本號。

    IE 11:設置IE11瀏覽器

  • 範圍版本:支持設置某個瀏覽器指定範圍版本。

    Chrome > 75: 設置大於Chrome75版本的瀏覽器

    而且支持 >=<<= 語法設置

  • 24個月內未更新版本:支持設置24個月內未更新的版本

    dead

  • 瀏覽器使用率:支持設置指定瀏覽器使用率版本

    >5%:全球超過5%人使用的瀏覽器版本

    > 5% in US:美國超過5%使用的瀏覽器版本

    > 5% in alt-AS:亞洲超過5%使用的瀏覽器版本

    也自定義設置地區,具體參考Github文檔

    而且支持 >=<<= 語法設置

  • 最新瀏覽器版本:支持設置最新的幾個版本瀏覽器。

    last 2 versions:設置全部瀏覽器最新的兩個版本。

    last 2 Chrome versions:設置Chrome瀏覽器最新的兩個版本

  • 排除瀏覽器browserslist支持排除指定瀏覽器,

    not ie < 11:排除IE11如下的瀏覽器

  • 條件組合browserslist強大的功能之一是支持多個條件作一個,這也是browserslist靈活所在。

    例如

    "browserslist": [
        "ie 9",
        "Chrome > 75"
    ],

    這就是一個而且(and)組合設置。二者都必須知足

    browserslist一樣支持 或者(or)組合> .5% or last 2 versions

通常只須要簡單的設置便可。

總結

🐋🐋🐋

  • React是一個快速構建高性能網站的開發框架

  • React使用了虛擬DOM(Virtual DOM)diff 算法優化了DOM操做

  • React利用自定義DOM類型解耦平臺限制,以此實現了跨平臺

  • JSX只是一個JS擴展語法。React使用JSX做爲構建元素的模板語言

  • browserslist是一個強大的設置瀏覽器版本庫。

本文參考

本文依賴

package.json

{
  "name": "my-cli",
  "version": "1.0.0",
  "main": "index.js",
  "author": "mowenjinzhao<yanzhangshuai@126.com>",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "7.13.1",
    "@babel/plugin-transform-runtime": "7.13.7",
    "@babel/preset-env": "7.13.5",
    "@babel/preset-react": "7.12.13",
    "@babel/runtime-corejs3": "7.13.7",
    "babel-loader": "8.2.2",
    "clean-webpack-plugin": "3.0.0",
    "html-webpack-plugin": "5.2.0",
    "webpack": "5.24.0",
    "webpack-cli": "4.5.0"
  },
  "dependencies": {
    "jquery": "3.5.1",
    "react": "17.0.1",
    "react-dom": "17.0.1"
  },
  "scripts": {
    "start": "webpack --mode=development  --config webpack.config.js",
    "build": "webpack --mode=production  --config webpack.config.js"
  },
  
  "browserslist": [
    "ie 9",
    "Chrome > 75"
    ]
}

webpack.config.js

const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

//	browserslist環境變量
process.env.BROWSERSLIST_ENV = 'development'

const config = {
  root: path.join(__dirname, './'),
}

const modules = {

  //  入口文件
  //  字符串形式
  entry: path.join(config.root, 'src/index.js'),
  //  對象形式
  // entry:{
  //   'index':  path.join(config.root, 'src/index.js'),
  // },

  //  輸出文件
  //  字符串形式
  // output:path.join(config.root, './dist/[name].js')
  //對象形式
  output: {
    //  輸出文件的目錄地址
    path: path.join(config.root, 'dist'),
    //  輸出文件名稱,contenthash表明一種緩存,只有文件更改纔會更新hash值,從新打包
    filename: '[name]_[contenthash].js'
  },

  //devtool:false, //'eval'

  module:{
    rules:[
      {
        //  全部的.js(x?)文件都走babel-loader
        test: /\.js(x?)$/,
        include: path.join(config.root,'src'),
        loader: "babel-loader"
      }
    ]
  },


  optimization: {
    minimize: false,
    minimizer: [
    new TerserPlugin({
          //  指定壓縮的文件
          include: /\.js(\?.*)?$/i,

          // 排除壓縮的文件
          // exclude:/\.js(\?.*)?$/i,

          //  是否啓用多線程運行,默認爲true,開啓,默認併發數量爲os.cpus()-1
          //  能夠設置爲false(不使用多線程)或者數值(併發數量)
          parallel: true,

          //  能夠設置一個function,使用其它壓縮插件覆蓋默認的壓縮插件,默認爲undefined,
          minify: undefined,

          //  是否將代碼註釋提取到一個單獨的文件。
          //  屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
          //  默認爲true, 只提取/^\**!|@preserve|@license|@cc_on/i註釋
          //  感受沒什麼特殊狀況直接設置爲false便可
          extractComments: false,

          // 壓縮時的選項設置
          terserOptions: {
            //  是否保留原始函數名稱,true表明保留,false即保留
            //  此屬性對使用Function.prototype.name
            //  默認爲false
            keep_fnames: false,

            // 是否保留原始類名稱
            keep_classnames: false,

            //  format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
            // 指定壓縮格式。例如是否保留*註釋*,是否始終爲*if*、*for*等設置大括號。
            format: {
              comments: false,
            },
            output: undefined,

            //  是否支持IE8,默認不支持
            ie8: false,

            compress: {
              // 是否使用默認配置項,這個屬性當只啓用指定某些選項時能夠設置爲false
              defaults: false,

              // 是否移除沒法訪問的代碼
              dead_code: false,

              // 是否優化只使用一次的變量
              collapse_vars: true,

              warnings: true,

              //  是否刪除全部 console.*語句,默認爲false,這個能夠在線上設置爲true
              drop_console: false,

              //  是否刪除全部debugger語句,默認爲true
              drop_debugger: true,

              //  移除指定func,這個屬性假定函數沒有任何反作用,可使用此屬性移除全部指定func
              // pure_funcs: ['console.log'], //移除console
            },
          },
    	})
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
       //  HTML的標題,
        //  template的title優先級大於當前數據
        title: 'my-cli',

        //  輸出的html文件名稱
        filename: 'index.html',

        //  本地HTML模板文件地址
        template: path.join(config.root, 'src/index.html'),

        // 引用JS文件的目錄路徑
        publicPath: './',

        //  引用JS文件的位置
        //  true或者body將打包後的js腳本放入body元素下,head則將腳本放到中
        //  默認爲true
        inject: 'body',

        //  加載js方式,值爲defer/blocking
        //  默認爲blocking, 若是設置了defer,則在js引用標籤上加上此屬性,進行異步加載
        scriptLoading: 'blocking',

        //  是否進行緩存,默認爲true,在開發環境能夠設置成false
        cache: false,

        //  添加mate屬性
        meta: {}
    }),

    new CleanWebpackPlugin({
 		// 是否僞裝刪除文件
        //  若是爲false則表明真實刪除,若是爲true,則表明不刪除
        dry: false,

        //  是否將刪除日誌打印到控制檯 默認爲false
        verbose: true,

        //  容許保留本次打包的文件
        //  true爲容許,false爲不容許,保留本次打包結果,也就是會刪除本次打包的文件
        //  默認爲true
        protectWebpackAssets: true,

        //  每次打包以前刪除匹配的文件
        cleanOnceBeforeBuildPatterns: ['**/*'],

        //  每次打包以後刪除匹配的文件
        cleanAfterEveryBuildPatterns:["*.js"],
    }),


    new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全局變量") }),
  ],

  resolve: {
    alias:{
      //  設置路徑別名
      '@': path.join(config.root, 'src') ,

      '~':  path.join(config.root, './src/assets') ,
    },
    //  可互忽略的後綴
    extensions:['.JSX', '.js', '.json'],
    //  默認讀取的文件名
    mainFiles:['index', 'main'],
  }
}

//  使用node.js的導出,將配置進行導出
module.exports = modules

.babelrc

{
  "presets": [
    "@babel/preset-react",
    [
      "@babel/preset-env",
      {
        "modules":false
        //  移除useBuiltIns設置
        //      "targets": "chrome > 75",
        //      "useBuiltIns": "usage",
        //      "corejs": {
        //        "version": 3,
        //        "proposals":true
        //      }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3,
          "proposals": true
        }
      }
    ]
  ]
}
相關文章
相關標籤/搜索