[英]Webpack bundle Node Express with hot reloading = hell
我不想承认这一点,但我花了三个漫长的夜晚试图去做——我认为这是一件很简单的事情。 我终于到了对它感到厌烦的阶段,坦率地说相当沮丧,因为“它根本行不通”。
这是我试图实现的目标:
我尝试了多种组合、已弃用的教程、不再维护的 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]'
}
}
]
}
};
索引.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);
坦率地说,我也对这应该如何工作感到困惑。
是否应该执行以下步骤?:
这会神奇地重新加载我的服务器吗?
欢迎所有帮助,或者如果您碰巧有一个工作演示。
我只是无法完成这项工作。
最后,我还将热重新加载(重新渲染)我的客户端包,但我想这将是最简单的部分,因为我已经看到了很多关于这方面的资源。
可能需要夜间睡眠。
我使用 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]'
}
}
]
}
};
索引.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;
})
}
服务器.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 并没有让这变得容易。
我没有尝试编写复杂的配置,而是自己解决了这个问题并创建了一个小型开发服务器,它会在文件更改时进行 webpack 构建和服务器重新加载。 客户端也有重载逻辑,所以这两种情况下的页面都是自动重载的。
概要
开发服务器
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>
`)
})
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"
]
}
这段代码中发生了很多事情。 我强烈建议您观看一些有关此的教程视频。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.