Tan*_*may 5 node.js express webpack vue.js webpack-hmr
我正在尝试在我的 Express 应用程序中启用 webpack HMR。它不是 SPA 应用程序。在视图方面,我使用 EJS 和 Vue。我这里没有vue-cli的优势,所以我必须手动为webpack中的SFC(.vue文件)配置vue-loader。另外值得一提的是,我的工作流程非常典型:我的主要客户端资源(scss、js、vue 等)都在resources目录中。我希望将它们捆绑在我的public目录中。
我的webpack.config.js:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: [
'./resources/css/app.scss',
'./resources/js/app.js',
'webpack-hot-middleware/client'
],
output: {
path: path.resolve(__dirname, 'public/js'),
publicPath: '/',
filename: 'app.js',
hotUpdateChunkFilename: "../.hot/[id].[hash].hot-update.js",
hotUpdateMainFilename: "../.hot/[hash].hot-update.json"
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development'
}
},
'css-loader',
'sass-loader'
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '../css/app.css'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
};
Run Code Online (Sandbox Code Playgroud)
我的app/index.js文件:
import express from 'express';
import routes from './routes';
import path from 'path';
import webpack from 'webpack';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
const config = require('../webpack.config');
const compiler = webpack(config);
const app = express();
app.use(express.static('public'));
app.use(devMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(hotMiddleware(compiler));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'))
routes(app);
app.listen(4000);
export default app;
Run Code Online (Sandbox Code Playgroud)
scripts我的package.json文件的部分:
"scripts": {
"start": "nodemon app --exec babel-node -e js",
"watch": "./node_modules/.bin/webpack --mode=development --watch",
"build": "./node_modules/.bin/webpack --mode=production"
}
Run Code Online (Sandbox Code Playgroud)
我正在使用 nodemon 重新启动服务器以获取服务器端代码的更改。在一个标签中,我保持npm run start打开状态,而在另一个标签中npm run watch。
在我的控制台中,我看到 HMR 已连接:
它仅第一次接受更改,并抛出一些警告,如下所示:
忽略对未接受模块的更新 ./resources/css/app.scss -> 0
并且不接受后续更改。我怎样才能解决这个问题?
因为它不是 SPA 并且您想要使用需要服务器端渲染的 EJS。对于您的情况来说,这并不容易,首先您需要覆盖 render 方法,然后您需要添加由 webpack 生成的文件。
根据您的描述中的存储库,https://bitbucket.org/tanmayd/express-test您走在正确的轨道上,但是您在 webpack 配置中结合了开发和生产设置。
由于我无法推送您的存储库,因此我将在下面列出发生更改的文件或新文件。
1. 脚本和包
"scripts": {
"start": "cross-env NODE_ENV=development nodemon app --exec babel-node -e js",
"watch": "./node_modules/.bin/webpack --mode=development --watch",
"build": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode=production",
"dev": "concurrently --kill-others \"npm run watch\" \"npm run start\"",
"production": "cross-env NODE_ENV=production babel-node ./app/server.js"
},
Run Code Online (Sandbox Code Playgroud)
我安装了cross-env(因为我在Windows上),cheerio(一个nodejs jquery类型的版本——它并没有那么糟糕),style-loader(这是使用webpack开发时必须的)。
脚本:
2. webpack.config.js - 已更改
style-loader添加到混合中,以便 webpack 将从捆绑包中传递您的 css(请参阅 ./resources/js/app.js - 第 1 行)。MiniCssExtractPlugin当您想要将样式提取到单独的文件(即在生产中)时使用。
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
// Plugins
let webpackPlugins = [
new VueLoaderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
];
// Entry points
let webpackEntryPoints = [
'./resources/js/app.js',
];
if (process.env.NODE_ENV === 'production') {
webpackPlugins = [
new VueLoaderPlugin()
];
// MiniCssExtractPlugin should be used in production
webpackPlugins.push(
new MiniCssExtractPlugin({
filename: '../css/app.css',
allChunks: true
})
)
}else{
// Development
webpackEntryPoints.push('./resources/css/app.scss');
webpackEntryPoints.push('webpack-hot-middleware/client');
}
module.exports = {
mode: process.env.NODE_ENV === 'development' ? 'development' : 'production',
entry: webpackEntryPoints,
devServer: {
hot: true
},
output: {
path: path.resolve(__dirname, 'public/js'),
filename: 'app.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
// use style-loader in development
(process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader),
'css-loader',
'sass-loader',
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: webpackPlugins
};
Run Code Online (Sandbox Code Playgroud)
3../resources/js/app.js - 已更改
样式现在添加到第一行import "../css/app.scss";
4../app/middlewares.js - 新
在这里你会发现 2 个中间件,overwriteRenderer以及webpackAssets.
overwriteRenderer,必须是路由之前的第一个中间件,它在开发和生产中都使用,在开发中它将抑制渲染后请求的结束,并res.body用文件的渲染字符串填充response()。在生产中,您的视图将充当布局,因此生成的文件将添加到 head(link) 和 body(script) 中。
webpackAssets仅在开发中使用,必须是最后一个中间件,这将添加到res.bodywebpack(app.css 和 app.js)在内存中生成的文件中。这是此处找到的示例的自定义版本webpack-dev-server-ssr
const cheerio = require('cheerio');
let startupID = new Date().getTime();
exports.overwriteRenderer = function (req, res, next) {
var originalRender = res.render;
res.render = function (view, options, fn) {
originalRender.call(this, view, options, function (err, str) {
if (err) return fn(err, null); // Return the original callback passed on error
if (process.env.NODE_ENV === 'development') {
// Force webpack in insert scripts/styles only on text/html
// Prevent webpack injection on XHR requests
// You can tweak this as you see fit
if (!req.xhr) {
// We need to set this header now because we don't use the original "fn" from above which was setting the headers for us.
res.setHeader('Content-Type', 'text/html');
}
res.body = str; // save the rendered string into res.body, this will be used later to inject the scripts/styles from webpack
next();
} else {
const $ = cheerio.load(str.toString());
if (!req.xhr) {
const baseUrl = req.protocol + '://' + req.headers['host'] + "/";
// We need to set this header now because we don't use the original "fn" from above which was setting the headers for us.
res.setHeader('Content-Type', 'text/html');
$("head").append(`<link rel="stylesheet" href="${baseUrl}css/app.css?${startupID}" />`)
$("body").append(`<script type="text/javascript" src="${baseUrl}js/app.js?${startupID}"></script>`)
}
res.send($.html());
}
});
};
next();
};
exports.webpackAssets = function (req, res) {
let body = (res.body || '').toString();
let h = res.getHeaders();
/**
* Inject scripts only when Content-Type is text/html
*/
if (
body.trim().length &&
h['content-type'] === 'text/html'
) {
const webpackJson = typeof res.locals.webpackStats.toJson().assetsByChunkName === "undefined" ?
res.locals.webpackStats.toJson().children :
[res.locals.webpackStats.toJson()];
webpackJson.forEach(item => {
const assetsByChunkName = item.assetsByChunkName;
const baseUrl = req.protocol + '://' + req.headers['host'] + "/";
const $ = require('cheerio').load(body.toString());
Object.values(assetsByChunkName).forEach(chunk => {
if (typeof chunk === 'string') {
chunk = [chunk];
}
if (typeof chunk === 'object' && chunk.length) {
chunk.forEach(item => {
console.log('File generated by webpack ->', item);
if (item.endsWith('js')) {
$("body").append(`<script type="text/javascript" src="${baseUrl}${item}"></script>`)
}
});
}
body = $.html();
});
});
}
res.end(body.toString());
}
Run Code Online (Sandbox Code Playgroud)
5../app/index.js - 已更改
该文件用于开发。在这里,我添加了4中的中间件,并添加了serverSideRender: true选项,以便 webpack 将为我们提供4devMiddleware中使用的那些资产
import express from 'express';
import routes from './routes';
import path from 'path';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
import webpack from 'webpack';
const {webpackAssets, overwriteRenderer} = require('./middlewares');
const config = require('../webpack.config');
const compiler = webpack(config);
const app = express();
app.use(express.static('public'));
app.use(devMiddleware(compiler, {
publicPath: config.output.publicPath,
serverSideRender: true // enable serverSideRender, https://github.com/webpack/webpack-dev-middleware
}));
app.use(hotMiddleware(compiler));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'));
// This new renderer must be loaded before your routes.
app.use(overwriteRenderer); // Local render
routes(app);
// This is a custom version for server-side rendering from here https://github.com/webpack/webpack-dev-middleware
app.use(webpackAssets);
app.listen(4000, '0.0.0.0', function () {
console.log(`Server up on port ${this.address().port}`)
console.log(`Environment: ${process.env.NODE_ENV}`);
});
export default app;
Run Code Online (Sandbox Code Playgroud)
6../app/server.js - 新
这是生产版本。它主要是5的清理版本,所有开发工具都被删除,仅overwriteRenderer保留下来。
import express from 'express';
import routes from './routes';
import path from 'path';
const {overwriteRenderer} = require('./middlewares');
const app = express();
app.use(express.static('public'));
app.use(overwriteRenderer); // Live render
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'));
routes(app);
app.listen(5000, '0.0.0.0', function() {
if( process.env.NODE_ENV === 'development'){
console.error(`Incorrect environment, "production" expected`);
}
console.log(`Server up on port ${this.address().port}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
});
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5259 次 |
| 最近记录: |