React.js學(xué)習(xí)筆記(16) webpack3 (一) 服務(wù)端渲染 + ( webpack-dev-server ) + ( react-hot-loader )

(一) wepack命令行 ---- ( npm script )

在npm init 初始化的項目中 package.json文件中的"script"對象中配置

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config build/webpack.config.js --progress --color --profile"
  },

// `--config` 指定 webpack 的配置文件,默認是 `webpack.config.js`




-----------------------------------------
$ webpack --help (或者-h) 列出webpack命令行所有可用的配置選項---------查看所有所有命令!!!(重要)

$ webpack --config webpack.min.js  //指定 webpack 的配置文件

$ webpack --progress //打印出編譯進度的百分比值

$ webpack --color //開啟/關(guān)閉控制臺的顏色 [默認值: (supports-color)]

$ webpack --profile // 記錄編譯的性能數(shù)據(jù),并且輸出。
它會告訴你編譯過程中哪些步驟耗時最長,這對于優(yōu)化構(gòu)建的性能很有幫助

$ webpack --display-error-details //顯示異常信息

$ webpack --watch   //監(jiān)聽變動并自動打包
 
$ webpack -p    //壓縮混淆腳本,這個非常非常重要!
 
$ webpack -d    //生成map映射文件,告知哪些模塊被最終打包到哪里了

http://www.php.cn/js-tutorial-368142.html

(二) .babelrc文件

.babelrc配置babel,因為默認babel只能編譯es6,需求還要用到j(luò)sx等語法,也需要是識別

.babelrc配置文件


{
    "presets": [
        ["es2015", {"loose": true}],     // 把es6編譯成es5,松散模式
        "react"                          // 識別jsx語法,編譯成es5
    ]
}

(三) 服務(wù)端渲染

(1) 原理:

用react生成的app在服務(wù)端( node 環(huán)境中 )進行渲染,得到完整的html內(nèi)容,直接返回給瀏覽器可以呈現(xiàn)的html內(nèi)容

(2) 好處:

1)便于SEO
2)讓用戶首屏等待時間減少

(3) 流程:
(1) 新建server-entry.js 入口文件
server-entry.js


import App from './app.jsx';   //引入
import React from 'react';   
export default <App/>          //導(dǎo)出






-----------------------------------------------------------------------------------
(2) 打包server-entry.js  

(webpack配置文件命名為:webpack.config.server.js)

打包原因:
server-entry.js導(dǎo)出的是jsx文件,不能被服務(wù)端識別(只能識別js代碼),
所以需要用webpack打包成js的es5代碼


webpack.config.server.js


const path = require('path');

module.exports = {
    target: 'node',     // node環(huán)境,如果是瀏覽器環(huán)境:target:'web'
    entry:{
        app: path.join(__dirname, '../client/server.entry.js')  // 上面的入口文件
    },
    output:{
        filename:'server-entry.js',
        path: path.join(__dirname, '../dist'),
        publicPath:'/public',  //用區(qū)分返回靜態(tài)文件,還是返回服務(wù)端渲染的代碼 !!!!!!!!!!
        libraryTarget:'commonjs2'  // 打包出來的js使用模塊方案,umd,cmd,amd等
    },
    module:{
        rules:[
            {
                test: /.jsx$/,
                use:[
                    {loader:'babel-loader'}
                ]
            },
            {
                test:/.js$/,
                exclude:[
                    path.join(__dirname, '../node_modules')
                ],
                use:[
                    {loader: 'babel-loader'}
                ]
            }
        ]
    },

}






-----------------------------------------------------------------------------------
(3) rimraf  刪除dist文件夾
因為每次打包都會新增文件,而不是覆蓋原來的文件,所以引入nodejs中的:

-- ( rimraf包,用來刪除文件夾 )  --
安裝: cnpm install rimraf --save-dev
使用:

"scripts": {
"build:client":"webpack --config build/webpack.config.client.js --progress --color --profile",
"build:server":"webpack --config build/webpack.config.server.js --progress --color --profile",
    "clear": "rimraf dist",    //刪除dist文件夾
    "build": "cnpm run clear && cnpm run build:client && cnpm run build:server",
    "start": "node server/server.js"
  },







-----------------------------------------------------------------------------------
(4) 用express啟一個node服務(wù)

server.js

安裝:cnpm install express -save      用于生產(chǎn)環(huán)境
使用:

const express = require('express');

const ReactSSR = require('react-dom/server'); 
 // ReactDOMServer 對象使你能夠?qū)⒔M件渲染為靜態(tài)標(biāo)記。 通常,它在 Node 服務(wù)器上
 // ReactDOMServer 對象允許您在服務(wù)器上渲染組件。
 // renderToString()
 // ReactDOMServer.renderToString(element)
 // 將 React 元素渲染到其初始 HTML 中。 該函數(shù)應(yīng)該只在服務(wù)器上使用。 React 將返回一個 HTML 字符串。 
 // 您可以使用renderToString()此方法在服務(wù)器上生成 HTML ,
 // 并在初始請求時發(fā)送標(biāo)記,以加快網(wǎng)頁加載速度,并允許搜索引擎抓取你的網(wǎng)頁以實現(xiàn) SEO 目的。

const serverEntry = require('../dist/server-entry').default;

const fs = require('fs');
 // 引入node的fs模塊
 // fs模塊用于對系統(tǒng)文件及目錄進行讀寫操作。
 // 1、異步讀取
 //  fs.readFile( url , code , callback); 
 // 2、同步讀取
 //  fs.readFileSync( url , code );

const path = require('path');
const app = express();

const template =  fs.readFileSync(path.join(__dirname,'../dist/index.html'),'utf8');
// fs.readFileSync()同步讀取
// utf8格式讀取,才是一個string字符串

app.use('/public', express.static(path.join(__dirname, '../dist')))  
// 對靜態(tài)文件指定對應(yīng)的請求返回
// 對靜態(tài)文件不返回html形式,而是本來的js形式
// 作用:對于/public文件下的文件都是返回靜態(tài)文件,( 不返回?zé)o端端渲染的文件 )

app.get('*',function(req, res) {   // 所有的url請求
  const appString = ReactSSR.renderToString(serverEntry);//在服務(wù)器上把打包后的組件生成HTML字符串
  const templeteA = template.replace('<app></app>', appString)  //字符串替換操作
  res.send(templeteA);   // 返回html代碼
}) 
app.listen(3333,function(){   // 啟用3333端口
   console.log('server is listening on 3333')
})

/*
template.html文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
      <div id="root"><app></app></div>
  </body>
</html>

*/

(五) 瀏覽器渲染

webpack.config.client.js




const path = require('path');
const HTMLWEBPACk = require('html-webpack-plugin');   // 插件

module.exports = {
    entry:{
        app: path.join(__dirname, '../client/app.js')   // 對應(yīng)下面注釋部分的入口文件app.js
    },
    output:{
        filename:'[name].[hash].js',
        path: path.join(__dirname, '../dist'),
        publicPath:'/public'
    },
    module:{
        rules:[
            {
                test: /.jsx$/,
                use:[
                    {loader:'babel-loader'}
                ]
            },
            {
                test:/.js$/,
                exclude:[
                    path.join(__dirname, '../node_modules')
                ],
                use:[
                    {loader: 'babel-loader'}
                ]
            }
        ]
    },
    plugins:[
        new HTMLWEBPACk({
            template: path.join(__dirname, '../client/template.html')   // 使用的html模板
        })
    ]
}


/*
app.js入口文件

import React from 'react';
import ReactDom from 'react-dom';
import App from './App.jsx';

ReactDom.hydrate(<App/>, document.getElementById('root'));  // 插入template.html的id=root的節(jié)點

*/



/*
template.html文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
      <div id="root"><app></app></div>
  </body>
</html>
*/

服務(wù)端渲染和瀏覽器渲染總結(jié):

  • rimraf 包:
    node包,刪除文件夾

  • html-webpack-plugin插件:
    自動根據(jù)模板生成html,和相關(guān)依賴

  • babel相關(guān):
    babel-core,babel-loader,babel-preset-es2015,babel-preset-react,babel-preset-es2015-loose松散模式

  • webpack.config.js注意點:
    -------target:執(zhí)行環(huán)境 ( target: 'node' 或者 target: 'web')
    -------libraryTarget:打包出來的js使用的模塊方案(commonjs2),umd,cmd,amd等

  • react-dom/server
    -------在服務(wù)器中,渲染組件(react生成的文件)
    -------ReactDOMServer.renderToString(element)此方法在服務(wù)器上生成 HTML

  • export default xxx 如果用require方式引用export default的內(nèi)容,需要加上default
    const a = require('....').default

  • 用express起一個node服務(wù)
    -------app.use(),app.get(),app.listen()
    -------fs模塊,讀取寫文件(node中的模塊)
    1、異步讀取
      fs.readFile( url , code , callback);
    2、同步讀取
      fs.readFileSync( url , code ); --- !!!!!!!注意:用 utf8 模式生成字符串!!!!!!
    const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'),'utf8');

  • express.static() 托管靜態(tài)文件,例如圖片、CSS、JavaScript 文件等。
    app.use('/public', express.static(path.join(__dirname, '../dist')))







(六) webpack-dev-server

webpack-dev-server需要安裝

cnpm install webpack-dev-server --save-dev

簡寫形式:cnpm i webpack-dev-server -D
(1) process.env

process.env屬性返回一個包含用戶環(huán)境信息的對象


process.env.NODE_ENV === 'development';



const isDev = process.env.NODE_ENV === 'development';
// process.env返回對象的 NODE_ENV屬性 是否和 'development'相等


// 如果isDev 是true,是開發(fā)環(huán)境,執(zhí)行
if(isDev) {
    config.devServer = {  // 存在就配置config中的devServer 對象
        host:'0.0.0.0',   // 這樣就能用ip,或者localhost等方式訪問
        port:'8888',
        contentBase: path.join(__dirname, '../dist'), // 靜態(tài)文靜
        hot: true,    // 啟動hot-module-replacement
        overlay: {    // webpack編譯出錯,在網(wǎng)頁顯示出錯信息
            errors: true    // 只顯示錯誤信息,不現(xiàn)實警告等信息
        },
        publicPath:'/public',    // webpack-dev-sever的publicPath 對比output中的publicPath
        historyApiFallback: {     // 讓所有404的請求全部返回下面的url
            index: '/public/index.html'
        }
    }
}

(2) cross-env

cross-env能跨平臺地設(shè)置及使用環(huán)境變量

  • 安裝:cnpm install cross-env --save-dev
  • 使用:
package.json


{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack --config build/webpack.config.js",
   "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
  }
}                           // 生產(chǎn)環(huán)境和執(zhí)行環(huán)境

cross-env NODE_ENV = development 用來設(shè)置環(huán)境變量
(3) react如何做判斷開發(fā)環(huán)境,還是生產(chǎn)環(huán)境

鏈接:https://segmentfault.com/q/1010000007782377

(4)完整代碼
webpack.config.client.js文件

const path = require('path');
const htmlPlugin = require('html-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development';

const config = {
    entry: {
        app:path.join(__dirname, '../client/app.js')
    },
    output:{
        filename: '[name].[hash].js',
        path: path.join(__dirname, '../dist'),
        publicPath:'/public'
    },
    resolve: {       // resolve是分解的意思,extensions:是擴展的意思(擴展名)
        extensions:['.js','.jsx']     // 不需要寫.js和.jsx文件的后綴名
    },
    module: {
        rules:[
            {
                test:/.js$/,
                exclude: [
                    path.join(__dirname, '../node_modules')
                ],
                use:[
                    {
                        loader: 'babel-loader'
                    }
                ]
            },
            {
                test:/.jsx$/,
                use:[ 
                    {
                        loader: 'babel-loader'
                    }
                ]
            }
        ]
    },
    plugins:[
        new htmlPlugin({
            template: path.join(__dirname, '../client/template.html')
        })
    ]

}

if(isDev) {
    config.devServer = {     
        host:'0.0.0.0',
        port:'8888',
        contentBase: path.join(__dirname, '../dist'),
        // hot: true,
        overlay: {
            errors: true
        },
        publicPath:'/public',
        historyApiFallback: {
            index: '/public/index.html'
        }
    }
}

module.exports = config;

-------------------------------------------------------------------------------

package.json文件


{
  "name": "new",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "clear": "rimraf dist",
    "build:client": "webpack --config build/webpack.config.client.js --color --progress --profile",
    "build:server": "webpack --config build/webpack.config.server.js --color --progress --profile",
    "build": "cnpm run clear && cnpm run build:client && cnpm run build:server",
    "start": "node server/server.js",
    "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-es2015-loose": "^8.0.0",
    "babel-preset-react": "^6.24.1",
    "cross-env": "^5.1.3",
    "express": "^4.16.2",
    "html-webpack-plugin": "^2.30.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "rimraf": "^2.6.2",
    "webpack": "^3.10.0",
    "webpack-dev-server": "^2.11.0"
  },
  "dependencies": {
    "express": "^4.16.2",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  }
}










(七) react-hot-loader

webpack-dev-server 的熱加載是開發(fā)人員修改了代碼,代碼經(jīng)過打包,重新刷新了整個頁面。而 react-hot-loader 不會刷新整個頁面,它只替換了修改的代碼,做到了頁面的局部刷新。但它需要依賴 webpack 的 HotModuleReplacement 熱加載插件。

(1) 安裝
cnpm install react-hot-loader --save-dev
(2) 使用
.babelrc文件


{
    "presets":[
        ["es2015", {"lose": true}],
        "react"
    ],
    "plugins":["react-hot-loader/babel"]    // 配置babel
}

app.js入口文件



import React from 'react';
import ReactDom from 'react-dom';
import App from './App.jsx';
import {AppContainer} from 'react-hot-loader';    // 引入AppContainer組件



const root = document.getElementById('root');

const render = Component => {    // 自建render方法,Component(組件)作為參數(shù)
    ReactDom.hydrate(
        <AppContainer>
            <Component />
        </AppContainer>, 
        root
    )
}

render(App)

if(module.hot) {
    module.hot.accept('./App.jsx', ()=> {
        const NextApp = require('./App.jsx').default;
        render(NextApp)
    })
}
webpack.config.client.js文件


const webpack = require('webpack');
// 因為HotModuleReplacementPlugin()是在 webpack中,所以引入webpack

entry : {
        app: [
            'react-hot-loader/patch',
            path.join(__dirname, '../client/app.js')
        ]
    }

plugins:[
  new webpack.HotModuleReplacementPlugin()    // 使用webpack的HotModuleReplacementPlugin插件
]

--------------------------------------------------------------------------------------

完整寫法:


const path = require('path');
const HTMLWEBPACk = require('html-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
const webpack = require('webpack');

const config = {
    entry:{
        app: path.join(__dirname, '../client/app.js')
    },
    output:{
        filename:'[name].[hash].js',
        path: path.join(__dirname, '../dist'),
        publicPath:'/public/'
    }, // 注意:保證把 output.publicPath 屬性設(shè)置成 "/"。以保證 hot reloading 會在嵌套的路由有效。
    module:{
        rules:[
            {
                test: /.jsx$/,
                use:[
                    {loader:'babel-loader'}
                ]
            },
            {
                test:/.js$/,
                exclude:[
                    path.join(__dirname, '../node_modules')
                ],
                use:[
                    {loader: 'babel-loader'}
                ]
            }
        ]
    },
    plugins:[
        new HTMLWEBPACk({
            template: path.join(__dirname, '../client/template.html')
        })
    ]
}

if (isDev) {    // 如果是開發(fā)環(huán)境,在package.json中的script對象中指定
    config.entry = {
        app: [
            'react-hot-loader/patch',   // 入口文件中添加react-hot-loader/patch
            path.join(__dirname, '../client/app.js')
        ]
    }
    config.devServer = {
        host: '0.0.0.0',
        port:'8888',
        contentBase: path.join(__dirname, '../dist'),
        hot: true,
        overlay: {
            errors: true
        },
        publicPath:'/public',
        historyApiFallback: {
            index:'/public/index.html'
        }
    }
    config.plugins.push(new webpack.HotModuleReplacementPlugin())

    // 向config的plugin數(shù)組配置中push一個webpack中的 HotModuleReplacementPlugin()插件
}

module.exports = config;

http://www.lxweimin.com/p/b7accbae3a1c
http://blog.csdn.net/huangpb123/article/details/78556652


2018-1-27更

webpack.config.js

module.exports = {
  resolve: {     // resolve是分解的意思, extensions是擴展的意思(擴展名)
      extensions: ['.js', '.jsx']    // 不用寫.js和.jsx文件的后綴名
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內(nèi)容