webpack 配置react脚手架

1 react 基本js文件:

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

ReactDOM.render(<App/>,document.getElementById('root'))

// 把app组件传给 reactCreateElement 作为的参数 要引入 react

基本 webpack.config 配置:

const path = require('path');
module.exports = {
    entry:[
        app: path.join(__dirname,'../clinet/app.js'); //使用path,设置绝对路径,注意 dirname前面是两个下划线
    ],
    output:[
        filename:'[name].[hash].js', //依赖的文件发生变化的时候,hash值变化。
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public' //设置引用文件的公共路径,用于设置CDN的路径
    ],
    module:{
        rules:[
            {
                test:/.jsx$/,
                loader:'babel-loader'//编译jsx,es6,es7等语法
                //这里要安装 babel: npm i babel-loader -D
                //babel-loader只是一个webpack的插件,不包括babel的核心代码,所以还要安装:
                //babel-core: npm i bable-core -D
                //此外,bable-core 默认是编译 es6的,为了还编译jsx,所以要进行另外的配置:.babelrc 文件
            }
        ]
    }
}

 对应的 babelrc文件

// .babelrc 文件
{
    "presets":[ //指定babel编译哪些类型
        ["es2015",{"loose":true}],//指定 es2015是 松散的不是严格的
        "react"    //在这里加上react,这个支持之后,babel才会编译react代码
    ]
}
//安装上述依赖文件:
// npm i babel-preset-es2015 babel-preset-es2015-loose babel-preset-react -D

为了打开html文件:

安装: 1 npm i html-webpack-plugin -D 

webpack.config.js 文件

const path = require('path');
const HTMLPlugin =require('html-webpack-plugin')
module.exports = {
    entry:[
        app: path.join(__dirname,'../clinet/app.js'); //使用path,设置绝对路径,注意 dirname前面是两个下划线
    ],
    output:[
        filename:'[name].[hash].js', //依赖的文件发生变化的时候,hash值变化。
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public/' //设置引用文件的公共路径,用于设置CDN的路径
    ],
    module:{
        rules:[
            {
                test:/.jsx$/,
                loader:'babel-loader'//编译jsx,es6,es7等语法
                //这里要安装 babel: npm i babel-loader -D
                //babel-loader只是一个webpack的插件,不包括babel的核心代码,所以还要安装:
                //babel-core: npm i bable-core -D
                //此外,bable-core 默认是编译 es6的,为了还编译jsx,所以要进行另外的配置:.babelrc 文件
            },
            {
                test:/.js$/,
                loader:'babel-loader',
                exclude:[
                    path.join(__dirname,'../node_modules')
                ]
            }
        ]
    },
    plugins:[
        new HTMLPlugin(); //有两个作用:1 生成html文件 ,2:根据output的配置把entry 文件打包
    ]
}

2 服务端渲染的配置

单页面存在的问题:

1 SEO不友好

2 首次请求等待时间过长,体验不友好。

首先看一下客户端的入口文件 app.js

客户端的入口文件

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

ReactDOM.render(<App/>,document.getElementById('root'))

对比服务端渲染的入口文件  server-entry.js 文件:都是为了引入 最外层组件 <App/> 但是服务端没有dom元素,所以只能抛出 <App/> 组件,而不能挂载到 root的div节点上。

// 在服务端运行的文件
import React from 'react';
import App from './App.jsx';

export default <App/>

服务端和客户端的入口文件都是把<App> 组件提供出来,不同的是客户端可以直接把组件挂载到 root上。而服务器端的入口文件是提供 <App/>组件,之后在服务器端的server.js中,将其挂载到root中;且客户端和服务器端用的是同一个html模板。因为入口文件不同,所以两个的webpack配置文件也不同:

新建一个服务器端的webpack打包的文件:

const path = require('path');
module.exports = {
    target:'node', //这里是新的配置,web/node 规定在哪里使用
    entry:[
        app: path.join(__dirname,'../clinet/serve-entry.js'); 
    ],
    output:[
        filename:'server-entry.js', //node.js 去 improt 这个js文件,且在服务器端没有缓存,不需要hash
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public/',
        libraryTarget: 'commonjs2' //打包出来的js 使用的模块方案, 包括 AMD CMD CommonJS等规范,这里使用的是commonjs2,适用于node端
    ],
    module:{
        rules:[
            {
                test:/.jsx$/,
                loader:'babel-loader'//编译jsx,es6,es7等语法
                //这里要安装 babel: npm i babel-loader -D
                //babel-loader只是一个webpack的插件,不包括babel的核心代码,所以还要安装:
                //babel-core: npm i bable-core -D
                //此外,bable-core 默认是编译 es6的,为了还编译jsx,所以要进行另外的配置:.babelrc 文件
            },
            {
                test:/.js$/,
                loader:'babel-loader',
                exclude:[
                    path.join(__dirname,'../node_modules')
                ]
            }
        ]
    }
}

修改配置项:

//修改 package.json文件

{
    "script":{
        "build:clinet": "webpack --config build/webpack.config.client.js",
        "build:server": "webpack --config build/webpack.config.server.js",
        "clear": "rimraf dist" //rimraf 是node的一个包,专门用来删除文件夹
        "build": "npm run clear && npm run build:client && npm run build:server",        
    }
}

//安装rimraf包。 npm i rimraf -D 

然后每次打包完,会有三个文件 客户端的js 服务器端的js 还有一个html。

上面介绍的是 客户端和服务器端的 webpack 配置文件;

===================

下面编写 express 服务端:

首先安装  npm i express -S 

新建 server文件夹 建立 server.js文件:

说明: 1. dist/server-entry文件是打包生成的js文件;res.send()渲染的就是生成的js文件;

2. app.get 获取到浏览器所有的请求,返回 渲染的 js文件;监听端口在3333

3. 由于html中 使用的是es6的语法: export default <App/>;而node中又不能使用 impront { app } from './app' 这种结构赋值的语法,所以这里要用 require().default 获取默认值;

然后在配置json文件中,设置服务端启动命令:

{
    "script":{
        "start": "node server/server.js    "    
    }
}

 运行服务端代码之后,服务端返回的只是打包后入口文件中写的代码 字符串:‘<div></div>’,我们需要返回的是整个html。

 步骤一:先在client文件夹下新建 模板文件 template.html: 其中关键代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root"><!--app--></div>
</body>
</html>

把生成的文件覆盖掉 <!--app-->;

然后修改客户端的配置文件。webpack.config.client.js中的模板插件:

{
    plugins: [
        new HTMLPlugin({
          template: path.join(__dirname, '../client/template.html')
        })
      ]
}

注意这里是改动的客户端的配置文件,客户端打包生成的html文件 ,然后服务端 server.js 去引用:

const express = require('express')
const ReactSSR = require('react-dom/server');
const fs = require('fs')
const path = require('path')
const serverEntry = require('../dist/server-entry').default;//引入的是服务端的配置打包后的js文件
const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf8')//同步引入客户端打包生成的 html 文件,如果不使用 utf8 则是buffer文件

const app = express();
app.use('/public', express.static(path.join(__dirname, '../dist'))); //给静态文件指定返回内容,这里给piblic文件夹下的内容返回的是静态文件的dist文件夹

app.get('*', function (req, res) {
  const appString = ReactSSR.renderToString(serverEntry);
  res.send(template.replace('<!--app-->',appString)) //用返回的js文件替换掉模板中的<app>,然后发送的是模板文件
})

app.listen(3333, function () {
  console.log('server is listening on 3333')
})

注意的是: 客户端和服务端的 output配置中的 publicPath 要写为“public”

output:[
        filename:'[name].[hash].js', //依赖的文件发生变化的时候,hash值变化。
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public' //设置引用文件的公共路径,用于设置CDN的路径
]

这样生成的html中引用的静态文件js和css以及图片等路径前缀都是在public下,所以 上面的服务端 server.js 的  

app.use('/public', express.static(path.join(__dirname, '../dist')));  

给访问的 public 目录下的文件返回 dist文件夹下的文件,其他的 * 返回 模板文件

  • 问题1 为啥还要在 服务端的配置文件中 设置 public
  • 问题2 服务端的 server-entry.js 和 客户端的 app.js 有啥区别?


客户端的入口文件 app.js 可以对浏览器进行操作,比如

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

ReactDOM.render(<App/>,document.getElementById('root'))


而服务器端的入口文件 server-entry 不可以:

import React from 'react';
import App from './App.jsx';

exprot defalut <App/>


所以后续,对应的webpack.config配置文件也需要设置两个,分别对应客户端和服务器端;
他们两个的入口和出口配置不一样
客户端的配置文件:

{
     entry:[
        app: path.join(__dirname,'../clinet/app.js'); //使用path,设置绝对路径,注意 dirname前面是两个下划线
    ],
    output:[
        filename:'[name].[hash].js', //依赖的文件发生变化的时候,hash值变化。
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public' //设置引用文件的公共路径,用于设置CDN的路径
    ],
}


服务器端的配置文件:

{
    target:'node', //这里是新的配置,web/node 规定在哪里使用
    entry:[
        app: path.join(__dirname,'../clinet/serve-entry.js');
    ],
    output:[
        filename:'server-entry.js', //node.js 去 improt 这个js文件,且在服务器端没有缓存,不需要hash
        path:path.join(__dirname,'../dist'),//输出的绝对路径
        publicPath:'/public/',
        libraryTarget: 'commonjs2' //打包出来的js 使用的模块方案, 包括 AMD CMD CommonJS等规范,这里使用的是commonjs2,适用于node端
    ],
}


因此,服务器端和客户端是单独的入口js文件和输出文件。

客户端: client/app.js------> dist/app.hash.js 文件,如下图所示,其提供的组件<App/>挂载到 root下;

服务器端: client/server-entry.js------> dist/server.entry.js 文件,在服务器端 server/server.js的作用下,将其生成的<App/>组件挂载到 root 下。

也就是说,客户端的app.js文件,生成的组件挂载到 root上;服务器端把server-entry生成的组件直接挂载到 root 上。
根据模板文件生成:

<body>
    <div id="root"><!-- app --></div>
    <script type="text/javascript" src="/public/app.9337b7bd3bfeb9af5f86.js"></script>
</body>

两个是独立的,所以两个都要加 /public,这样服务器就会根据两个位于 public 文件夹下 而返回对应的app.hash.js 和 server.entry.js文件,而不是 模板代码。




原文地址:https://www.cnblogs.com/xiaozhumaopao/p/10991630.html