(一) 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 //开启/关闭控制台的颜色 [默认值: (supports-color)]
$ webpack --profile // 记录编译的性能数据,并且输出。
它会告诉你编译过程中哪些步骤耗时最长,这对于优化构建的性能很有帮助
$ webpack --display-error-details //显示异常信息
$ webpack --watch //监听变动并自动打包
$ webpack -p //压缩混淆脚本,这个非常非常重要!
$ webpack -d //生成map映射文件,告知哪些模块被最终打包到哪里了
http://www.php.cn/js-tutorial-368142.html
(二) .babelrc文件
.babelrc配置babel,因为默认babel只能编译es6,需求还要用到jsx等语法,也需要是识别
.babelrc配置文件
{
"presets": [
["es2015", {"loose": true}], // 把es6编译成es5,松散模式
"react" // 识别jsx语法,编译成es5
]
}
(三) 服务端渲染
(1) 原理:
用react生成的app在服务端( node 环境中 )进行渲染,得到完整的html内容,直接返回给浏览器可以呈现的html内容
(2) 好处:
1)便于SEO
2)让用户首屏等待时间减少
(3) 流程:
(1) 新建server-entry.js 入口文件
server-entry.js
import App from './app.jsx'; //引入
import React from 'react';
export default <App/> //导出
-----------------------------------------------------------------------------------
(2) 打包server-entry.js
(webpack配置文件命名为:webpack.config.server.js)
打包原因:
server-entry.js导出的是jsx文件,不能被服务端识别(只能识别js代码),
所以需要用webpack打包成js的es5代码
webpack.config.server.js
const path = require('path');
module.exports = {
target: 'node', // node环境,如果是浏览器环境:target:'web'
entry:{
app: path.join(__dirname, '../client/server.entry.js') // 上面的入口文件
},
output:{
filename:'server-entry.js',
path: path.join(__dirname, '../dist'),
publicPath:'/public', //用区分返回静态文件,还是返回服务端渲染的代码 !!!!!!!!!!
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服务
server.js
安装:cnpm install express -save 用于生产环境
使用:
const express = require('express');
const ReactSSR = require('react-dom/server');
// ReactDOMServer 对象使你能够将组件渲染为静态标记。 通常,它在 Node 服务器上
// ReactDOMServer 对象允许您在服务器上渲染组件。
// renderToString()
// ReactDOMServer.renderToString(element)
// 将 React 元素渲染到其初始 HTML 中。 该函数应该只在服务器上使用。 React 将返回一个 HTML 字符串。
// 您可以使用renderToString()此方法在服务器上生成 HTML ,
// 并在初始请求时发送标记,以加快网页加载速度,并允许搜索引擎抓取你的网页以实现 SEO 目的。
const serverEntry = require('../dist/server-entry').default;
const fs = require('fs');
// 引入node的fs模块
// fs模块用于对系统文件及目录进行读写操作。
// 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')))
// 对静态文件指定对应的请求返回
// 对静态文件不返回html形式,而是本来的js形式
// 作用:对于/public文件下的文件都是返回静态文件,( 不返回无端端渲染的文件 )
app.get('*',function(req, res) { // 所有的url请求
const appString = ReactSSR.renderToString(serverEntry);//在服务器上把打包后的组件生成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') // 对应下面注释部分的入口文件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的节点
*/
/*
template.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<div id="root"><app></app></div>
</body>
</html>
*/
服务端渲染和浏览器渲染总结:
rimraf 包:
node包,删除文件夹html-webpack-plugin插件:
自动根据模板生成html,和相关依赖babel相关:
babel-core,babel-loader,babel-preset-es2015,babel-preset-react,babel-preset-es2015-loose松散模式webpack.config.js注意点:
-------target:执行环境 ( target: 'node' 或者 target: 'web')
-------libraryTarget:打包出来的js使用的模块方案(commonjs2),umd,cmd,amd等react-dom/server
-------在服务器中,渲染组件(react生成的文件)
-------ReactDOMServer.renderToString(element)此方法在服务器上生成 HTMLexport default xxx 如果用require方式引用export default的内容,需要加上default
const a = require('....').default用express起一个node服务
-------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() 托管静态文件,例如图片、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属性返回一个包含用户环境信息的对象
process.env.NODE_ENV === 'development';
const isDev = process.env.NODE_ENV === 'development';
// process.env返回对象的 NODE_ENV属性 是否和 'development'相等
// 如果isDev 是true,是开发环境,执行
if(isDev) {
config.devServer = { // 存在就配置config中的devServer 对象
host:'0.0.0.0', // 这样就能用ip,或者localhost等方式访问
port:'8888',
contentBase: path.join(__dirname, '../dist'), // 静态文静
hot: true, // 启动hot-module-replacement
overlay: { // webpack编译出错,在网页显示出错信息
errors: true // 只显示错误信息,不现实警告等信息
},
publicPath:'/public', // webpack-dev-sever的publicPath 对比output中的publicPath
historyApiFallback: { // 让所有404的请求全部返回下面的url
index: '/public/index.html'
}
}
}
(2) cross-env
cross-env能跨平台地设置及使用环境变量
- 安装: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"
}
} // 生产环境和执行环境
cross-env NODE_ENV = development 用来设置环境变量
(3) react如何做判断开发环境,还是生产环境
链接: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 的热加载是开发人员修改了代码,代码经过打包,重新刷新了整个页面。而 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(组件)作为参数
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 属性设置成 "/"。以保证 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) { // 如果是开发环境,在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数组配置中push一个webpack中的 HotModuleReplacementPlugin()插件
}
module.exports = config;
https://www.jianshu.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文件的后缀名
}
}