(一) 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ù)器上生成 HTMLexport 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文件的后綴名
}
}