【问题标题】:Webpack bundle Node Express with hot reloading = hell带有热重载的 Webpack 捆绑 Node Express = 地狱
【发布时间】:2019-10-08 08:34:45
【问题描述】:

我不想承认这一点,但我已经花了三个漫长的晚上试图做这件事——我认为这是一件很简单的事情。我终于到了受够了它的阶段,坦率地说,我很沮丧,因为“它根本行不通”。

这是我试图实现的目标:

  1. 将我的 Express 服务器与 Webpack 捆绑在一起(虽然我当前的代码只是在浏览器中呈现一个字符串,但它应该编译使用 Babel 编译的服务器呈现的 React 组件)
  2. 将包保存在内存中(如果没有其他方法,则保存在磁盘上)
  3. 运行 webpack / dev / hot 中间件来为我的 Node Express 应用程序提供服务,服务器渲染页面(将是 React 组件)的更改将在浏览器中自动更新。

我尝试了许多组合、已弃用的教程、不再维护的 npm 包和下载的示例,这些示例根本不起作用。

这是我目前的设置:

webpack.server.config.js:

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');

module.exports = {
    name: 'server',
    mode: 'development',
    target: 'node',
    externals: nodeExternals(),
    entry: [ './src/server/index' ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        // path: "/",
        filename: '[name].js',
        publicPath: '/assets/',
        libraryTarget: 'commonjs2'
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: path.resolve(__dirname, 'src/'),
                exclude: /node_modules/,
                options: {
                    presets:
                        [['@babel/preset-env', { modules: 'false' }], '@babel/preset-react'],
                    plugins: [
                        ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
                        '@babel/plugin-proposal-class-properties'
                    ]
                }
            },
            {
                test: /\.scss$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.css$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.(jpg|png|svg|gif|pdf)$/,
                loader: 'file-loader',
                options: {
                    name: '[path][name].[ext]'
                }
            }
        ]
    }
};

index.js:

import http from 'http';
import fs from "fs";
import express from "express";
import favicon from 'serve-favicon';
// import renderer from "./renderer";
import renderApp from './welcome';


const app = express();

app.use(favicon('./public/favicon.ico'));
app.use(express.static("public"));


if (process.env.NODE_ENV !== 'production') {

    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    const webpackHotMiddleware = require('webpack-hot-middleware');
    const serverConfig = require('../../webpack.server.config');

    const compiler = webpack(serverConfig);

    app.use(webpackDevMiddleware(compiler, {
        stats: {colors: true},
        headers: { "Access-Control-Allow-Origin": "http://localhost"},
        publicPath: serverConfig.output.publicPath
    }));

    app.use(require("webpack-hot-middleware")(compiler));

}

app.get("*", function(req, res) {
    fs.readFile("./src/server/html/index.html", "utf8", function(err, data) {
        const context = {};
        const html = renderApp();
        //const html = renderer(data, req.path, context);

        res.set('content-type', 'text/html');
        res.send(html);
        res.end();
    });
});

const PORT = process.env.PORT || 8080;

app.listen(3000);

坦率地说,我也很困惑这应该如何工作。
是否应该执行以下步骤?:

  • webpack webpack.server.config.js --watch
  • node dist/server.js // webpack 输出文件夹

这会神奇地热重载我的服务器吗?

欢迎所有帮助,或者如果您碰巧有一个工作演示。
我只是无法完成这项工作。

最后,我还将热重载(重新渲染)我的客户端包,但我想这将是容易的部分,因为我已经看到了很多很多关于此的资源。

【问题讨论】:

  • 我感受到了你的痛苦。我遇到了同样的问题,最后从我发现的教程种子中复制/粘贴。我在名为 [Tutsgalaxy.com] - React 16 - The Complete Guide (incl. React Router 4 & Redux) 的 torrent 中使用了演示文件。有一整章是关于 webpack 的。如果你给我上传链接,我会为你上传整个项目。

标签: javascript node.js reactjs webpack


【解决方案1】:

Node.js、babel 和 webpack 一直是我这个月的祸根。应该包括几个组件。您应该有一个名为“package.json”的启动文件

内容应如下所示:

{
  "name": "react-complete-guide",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "rimraf dist && webpack --config webpack.prod.config.js --progress --profile --color"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^7.1.5",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-preset-env": "^1.6.0",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "css-loader": "^0.28.7",
    "file-loader": "^1.1.5",
    "html-webpack-plugin": "^2.30.1",
    "postcss-loader": "^2.0.7",
    "style-loader": "^0.19.0",
    "url-loader": "^0.6.2",
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.9.1"
  },
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-router-dom": "^4.2.2"
  }
}

如果你输入“npm start”,那么代码“start”:“webpack-dev-server”就会运行。这将加载代码的预览。 要将内容打包到构建中,请键入“npm run build”,这将运行代码“build”:“rimraf dist && webpack --config webpack.prod.config.js --progress --profile --color”。该代码将运行“rimraf”,如果存在则删除“dist”文件夹,其余代码运行 webpack 配置文件。

你应该有两个 webpack 文件。一种用于热重载,一种用于生产环境的打包。这些文件应该被调用:

“webpack.config.js”和“webpack.prod.config.js”。

“webpack.config.js”的内容如下所示:

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

module.exports = {
    devtool: 'cheap-module-eval-source-map',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        chunkFilename: '[id].js',
        publicPath: ''
    },
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: [
                    { loader: 'style-loader' },
                    { 
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1,
                            modules: true,
                            localIdentName: '[name]__[local]__[hash:base64:5]'
                        }
                     },
                     { 
                         loader: 'postcss-loader',
                         options: {
                             ident: 'postcss',
                             plugins: () => [
                                 autoprefixer({
                                     browsers: [
                                        "> 1%",
                                        "last 2 versions"
                                     ]
                                 })
                             ]
                         }
                      }
                ]
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: 'url-loader?limit=8000&name=images/[name].[ext]'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + '/src/index.html',
            filename: 'index.html',
            inject: 'body'
        })
    ]
};

“webpack.prod.config.js”的内容如下所示:

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

module.exports = {
    devtool: 'cheap-module-source-map',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist/new'),
        filename: 'bundle.js',
        chunkFilename: '[id].js',
        publicPath: ''
    },
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: [
                    { loader: 'style-loader' },
                    { 
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1,
                            modules: true,
                            localIdentName: '[name]__[local]__[hash:base64:5]'
                        }
                     },
                     { 
                         loader: 'postcss-loader',
                         options: {
                             ident: 'postcss',
                             plugins: () => [
                                 autoprefixer({
                                     browsers: [
                                        "> 1%",
                                        "last 2 versions"
                                     ]
                                 })
                             ]
                         }
                      }
                ]
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: 'url-loader?limit=8000&name=images/[name].[ext]'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + '/src/index.html',
            filename: 'index.html',
            inject: 'body'
        }),
        new webpack.optimize.UglifyJsPlugin()
    ]
};

你还需要一个文件来告诉 babel 如何操作。如果您使用 babel,该文件称为“.babelrc”。内容是这样的

{
    "presets": [
        ["env", {
            "targets": {
                "browsers": [
                    "> 1%",
                    "last 2 versions"
                ]
            }
        }],
        "stage-2",
        "react"
    ],
    "plugins": [
        "syntax-dynamic-import"
    ]
}

这段代码中有很多内容。我强烈建议观看一些关于此的教程视频。

【讨论】:

  • 谢谢,但 webpack 开发服务器不适用于服务器渲染页面。
【解决方案2】:

可能需要晚上睡觉。
我使用 StartServerPlugin 得到了这个工作(包括 React 服务器渲染的组件)。
以下设置热重载 Node Express 服务器:

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const StartServerPlugin = require('start-server-webpack-plugin');

module.exports = {
    name: 'server',
    mode: 'development',
    target: 'node',
    externals: nodeExternals({
        whitelist: ['webpack/hot/poll?1000']
    }),
    entry: [ 'webpack/hot/poll?1000', './src/server/index' ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        // path: "/",
        filename: 'server.js',
        publicPath: '/assets/',
        libraryTarget: 'commonjs2'
    },
    plugins: [
        new StartServerPlugin({'name': 'server.js', nodeArgs: ['--inspect']}),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.DefinePlugin({
            "process.env": {
                "BUILD_TARGET": JSON.stringify('server')
            }
        })
    ],
    module: {
        rules: [
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: path.resolve(__dirname, 'src/'),
                exclude: /node_modules/,
                options: {
                    presets:
                        [['@babel/preset-env', { modules: 'false' }], '@babel/preset-react'],
                    plugins: [
                        ['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
                        '@babel/plugin-proposal-class-properties'
                    ]
                }
            },
            {
                test: /\.scss$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.css$/,
                loader: 'ignore-loader'
            },
            {
                test: /\.(jpg|png|svg|gif|pdf)$/,
                loader: 'file-loader',
                options: {
                    name: '[path][name].[ext]'
                }
            }
        ]
    }
};

index.js:

import http from 'http'
import app from './server'

const server = http.createServer(app)
let currentApp = app;

const PORT = process.env.PORT || 8080;

server.listen(PORT);

if (module.hot) {
    module.hot.accept('./server', () => {
        server.removeListener('request', currentApp);
        server.on('request', app);
        currentApp = app;
    })
}

server.js:

import http from 'http';
import fs from "fs";
import express from "express";
import favicon from 'serve-favicon';
import renderer from "./renderer";
import renderApp from './welcome';


const app = express();

app.use(favicon('./public/favicon.ico'));
app.use(express.static("public"));


app.get("*", function(req, res) {
    fs.readFile("./src/server/html/index.html", "utf8", function(err, data) {
        const context = {};
        //const html = renderApp();
        console.log('test');
        const html = renderer(data, req.path, context);
        res.set('content-type', 'text/html');
        res.send(html);
        res.end();
    });
});

export default app;

运行:

rm -rf ./dist && webpack --config webpack.server.config.js --watch

【讨论】:

  • 感谢您的帮助!我能够让它在我的应用程序中首次重新加载,但不能用于任何后续重新加载。似乎 webpack 具有某种缓存机制,并且在更新子项而不是父项时不会触发 accept 函数。你遇到过这样的事情吗?
  • @amann 我创建了一个不同的设置并将其上传到 Github。它对我之前的项目非常有效:github.com/kimgysen/isomorphic-react-setup所有解释都在那里。
  • 哦,非常感谢您创建存储库并撰写随附的文章!我去看看!
【解决方案3】:

我认为这里的 anwsers 有点太复杂了。看来 Webpack 并没有让这一切变得简单。

我没有尝试编写复杂的配置,而是自己解决了这个问题,并创建了一个小型开发服务器,它可以在文件更改时执行 webpack 构建和服务器重新加载。客户端也有重载逻辑,所以这两种情况下的页面都会自动重载。

hot module reload for express server and webpack client

概要

开发服务器

const fetch = require('node-fetch')

let process

function spawnserver(){
    process = require('child_process').spawn("node", ["server/server.js", "dev"])

    process.stdout.on('data', (data) => {    
        console.error(`stdout: ${data}`)
    })

    process.stderr.on('data', (data) => {
        console.error(`stderr: ${data}`)
    })
}

function rebuildsrc(){
    process = require('child_process').spawn("npm", ["run", "build"])

    process.stdout.on('data', (data) => {    
        console.error(`stdout: ${data}`)
    })

    process.stderr.on('data', (data) => {
        console.error(`stderr: ${data}`)
    })

    process.on("close", code => {
        console.log("build exited with code", code)
        fetch("http://localhost:3000/reloadsrc").then(response=>response.text().then(content=>console.log(content)))
    })
}

spawnserver()

const watcher = require("chokidar").watch("./server")

watcher.on("ready", _=>{    
    watcher.on("all", _=>{      
        console.log("server reload")
        process.kill()
        spawnserver()
    })
})

const srcWatcher = require("chokidar").watch("./src")

srcWatcher.on("ready", _=>{    
    srcWatcher.on("all", _=>{      
        console.log("rebuild src")        
        rebuildsrc()
    })
})

客户端重新加载

let stamp = new Date().getTime()
let lastStamp = null

app.get('/stamp', (req, res) => {
    lastStamp = new Date().getTime()
    res.send(`${stamp}`)
})

app.get('/reloadsrc', (req, res) => {
    stamp = new Date().getTime()
    res.send(`updated stamp to ${stamp}`)
})

let welcomeMessage = "Welcome !!"

let reloadScript = IS_PROD ? ``:`
let stamp = null
let reloadStarted = false
setInterval(_=>{
    fetch('/stamp').then(response=>response.text().then(content=>{                        
        if(stamp){
            if(content != stamp) setTimeout(_=>{                                
                if(!reloadStarted){
                    console.log("commence reload")
                    setInterval(_=>{
                        fetch(document.location.href).then(response=>response.text().then(content=>{
                            if(content.match(/Welcome/)){
                                console.log("reloading")
                                document.location.reload()
                            }                                        
                        }))
                    }, 1000)                                
                    reloadStarted = true
                }                                
            }, 500)
        }else{
            if(!reloadStarted){
                stamp = content
                console.log("stamp set to", stamp)
            }
        }
    }))
}, 200)    
`

app.use('/dist', express.static('dist'))

app.get('/', (req, res) => {
    res.send(`
    <!doctype html>
        <head>
            <title>Reload Express Sample App</title>
        </head>
        <body>
            <h1>${welcomeMessage}</h1>            
            <script>
                ${reloadScript}
            </script>
            <script src="dist/bundle.js"></script>
        </body>
    </html>
    `)  
})

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-13
    • 2017-11-22
    • 1970-01-01
    • 2017-07-13
    • 2020-10-15
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    相关资源
    最近更新 更多