I'm using the charting library from TradingView, which requires static HTML to be loaded inside an iFrame. I've placed the static html inside the public folder:
/public/charting_library/en-tv-chart.b555c6a4.html
And it is accessed via:
localhost:3000/charting_library/en-tv-chart.b555c6a4.html
However, when requesting the above URL, the contents are that of the root index.html, not that of the static asset.
How can I get Vite to route the HTML asset correctly here?
I solved this by using Vite middleware:
function chartingLibrary(): PluginOption {
return {
apply: 'serve',
configureServer(server: ViteDevServer) {
return () => {
server.middlewares.use(async (req, res, next) => {
if (req.originalUrl?.includes('/charting_library/') && req.originalUrl?.includes('html')) {
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write(fs.readFileSync(path.join(__dirname, `public/${req.originalUrl}`)));
res.end();
}
next();
});
};
},
name: 'charting-library',
};
}
And then in the config:
{
// ...
plugins: [
// ...
chartingLibrary(),
],
}
Related
I have a following Vite configuration:
import { defineConfig } from "vite";
const zlib = require("zlib");
export default defineConfig(() => {
return {
server: {
proxy: {
"/start": {
target: "https://someremoteurl.com",
secure: false,
changeOrigin: true,
configure: (proxy) => {
proxy.on("proxyRes", (proxyRes, req, res) => {
const chunks = [];
proxyRes.on("data", (chunk) => chunks.push(chunk));
proxyRes.on("end", () => {
const buffer = Buffer.concat(chunks);
const encoding = proxyRes.headers["content-encoding"];
if (encoding === "gzip" || encoding === "deflate") {
zlib.unzip(buffer, (err, buffer) => {
if (!err) {
let remoteBody = buffer.toString();
const modifiedBody = remoteBody.replace() // do some string manipulation on remoteBody
res.write(modifiedBody);
res.end();
} else {
console.error(err);
}
});
}
});
});
},
},
},
},
};
});
Everything works as expected modifiedBody is of needed shape.
However the server doesn't return the modified response, it retuns the initial html that the "https://someremoteurl.com" url served.
With the following code the response is "correctly" changed:
proxyRes.on("end", () => {
res.end('<h1>Some Test HTML</h1>')
});
But this wouldnt work for me, as i need to read the response first, unzip it, modify it and only then send back.
To me it looks like the proxied response is streamed, but dev server doesn't wait for the response to first finish streaming, running transformations and only then serving the desired document.
Any idea how can i achieve the desired result?
As Vite uses the http-node-proxy lib under the hood i had to look fo the answer in their documentation. I found that selfHandleResponse option needs to be true in order to serve your modified response.
Setting that option solved my question.
I have an project set up and running with Webpack 5.28.0 and webpack-dev-server 4.11.1
Its all working nicely but I would like to be able to have the dev server write some files back to my project root. These are debug/log files that I'd like to save as JSON.
I'd also like this to be automatic, I don't want to have to click anything or trigger the action manually.
So the ideal flow would be that I run npm start, my build kicks off in a browser, the page generates a load of log data and this is then written back to my project root. Either using some browser function or calling back to Node script in my build.
Is this possible with dev-server?
You could setup the dev-server middleware to add an API endpoint to accept data and write it to your filesystem
// webpack.config.js
const { writeFile } = require("node:fs/promises");
const bodyParser = require("body-parser");
module.exports = {
// ...
devServer: {
setupMiddlewares: (middlewares, devServer) => {
devServer.app?.post(
"/__log",
bodyParser.json(),
async (req, res, next) => {
try {
await writeFile(
"debug-log.json",
JSON.stringify(req.body, null, 2)
);
res.sendStatus(202);
} catch (err) {
next(err);
}
}
);
return middlewares;
},
},
};
Then your front-end app needs only to construct the payload and POST it to the dev-server
const debugData = { /* ... */ };
fetch("/__log", {
method: "POST",
body: JSON.stringify(debugData),
headers: { "content-type": "application/json" },
});
Introduction
I have this Node.js code from a function that handle the request/response. To explain my problem, I will use Express.js example. That piece of code is not a part of my problem but I can include to you a simple context to help you answer my question. So my code will be put in app.get handler, assuming app is an Express.js instance :
app.get("/", function (request, response, next) {
// The code below could be here...
});
app.get("/test/", function (request, response, next) {
// ...and here...
});
app.get("/*", function (request, response, next) {
// ...and here...
});
response is the representation of response I will send to the client and I will fill it with the Vue Renderer result.
request contains all information about the client that execute this part of code.
My code
Assuming my code is running from the / route, the code is for example the following :
app.get("/", function (request, response, next) {
var Vue = require("vue"),
VueRouter = require("vue-router"),
renderers = require("vue-server-renderer"),
renderer = renderers.createRenderer();
global.Vue = Vue;
Vue.use(VueRouter);
stream = renderer.renderToStream(new Vue({
template: "<div><router-view></router-view><div>",
router: new VueRouter({
routes: [{
path: '/',
component: { template: '<div>foo</div>' }
}, {
path: '/test/',
component: { template: '<div>bar</div>' }
}, {
path: '/*',
component: { template: '<div>baz</div>' }
}]
})
}));
response.write("<html><head><title>test</title></head><body>");
stream.on('data', function (chunk) {
response.write(chunk);
});
stream.on('end', function () {
response.end('</body></html>');
});
});
And when response.end is called, the content send to the client is
<html><head><title>test</title></head><body><div server-rendered="true"><!----></div></body></html>
We can see the part where the router should be display the component is <!----> so I guess it's because for router, no route actually match my code.
Questions
Why the result is not the following if no route matchs :
<html><head><title>test</title></head><body><div server-rendered="true"><div>baz</div></div></body></html>
and
How to inform my router the current url is / to generate this code in this case :
<html><head><title>test</title></head><body><div server-rendered="true"><div>foo</div></div></body></html>
and for exemple the following code if my current request come from /test/
<html><head><title>test</title></head><body><div server-rendered="true"><div>bar</div></div></body></html>
etc.
Answer
Thanks to the answer of Ilya Borovitinov (https://stackoverflow.com/a/42872542/2412797), my previous code become :
app.get("/", function (request, response, next) {
var Vue = require("vue"),
VueRouter = require("vue-router"),
renderers = require("vue-server-renderer"),
renderer = renderers.createRenderer(),
router = new VueRouter({
routes: [{
path: '/',
component: { template: '<div>foo</div>' }
}, {
path: '/test/',
component: { template: '<div>bar</div>' }
}, {
path: '/*',
component: { template: '<div>baz</div>' }
}]
});
global.Vue = Vue;
Vue.use(VueRouter);
stream = renderer.renderToStream(new Vue({
template: "<div><router-view></router-view><div>",
router: router
}));
/* THIS IS THE SOLUTION */
router.push(request.url);
response.write("<html><head><title>test</title></head><body>");
stream.on('data', function (chunk) {
response.write(chunk);
});
stream.on('end', function () {
response.end('</body></html>');
});
});
You are trying to use vue-router in an environment, which does not provide explicit address for the router to use. To actually force router to render proper path, you need to call router.push(currentUrl) for it to register.
I wonder if it is possible with gulp-connect to serve some files from a different directory. Something like:
http://localhost:8080/index.html => root: '/root/app'
but
http://localhost:8008/js/main.js => from '/root/js/' not from 'root/app/js'
http://localhost:8008/css/main.css => from '/root/css/' not from 'root/app/css/'
You can pass a middleware function to gulp-connect that allows you to modify the request object and therefore rewrite request URLs:
gulp.task('serve', function() {
connect.server({
root: 'root',
middleware: function() {
return [ function(req, res, next) {
if (!/^\/(js|css)\/.*/.test(req.url)) {
req.url = '/app' + req.url;
}
next();
}];
}
});
});
In the above any path that starts with /js/ or /css/ will be passed through unchanged. Since our base folder is root that means a path like /js/main.js will resolve to root/js/main.js.
All other paths will be prepended with /app, meaning a path like /index.html will transparently resolve to root/app/index.html.
Instead of using custom logic as I did above, you can also use something like http-rewrite-middleware, which allows you to specify nginx-inspired rewrite expressions:
var rewrite = require('http-rewrite-middleware');
gulp.task('serve', function() {
connect.server({
root: 'root',
middleware: function() {
return [ rewrite.getMiddleware([
{ from: '^/js/(.*)$', to: '/js/$1' },
{ from: '^/css/(.*)$', to: '/css/$1' },
{ from: '^(.*)$', to: '/app/$1' }
])];
}
});
});
I downloaded my XML sitemap from the sitemap xml generator website. I placed my sitemap.xml on my public directory but when I tried to submit the sitemap.xml into google console i received the following error: General HTTP error: 404 not found
HTTP Error: 404So i codedapp.get('/sitemap.xml', function( req, res, next ) {
res.header('Content-Type', 'text/xml');
res.render( 'sitemap' );
)};And when i navigate to the 'website/sitemap.xml' I am getting the following error: This page contains the following errors:
error on line 1 at column 42: Specification mandate value for attribute itemscope
Thanks for your help
Generate your sitemap.xml file using a tool like https://www.xml-sitemaps.com/
upload the sitemap.xml in your project
then add this to your .js file:
router.get('/sitemap.xml', function(req, res) {
res.sendFile('YOUR_PATH/sitemap.xml');
});
make sure you change YOUR_PATH for the actual path where your sitemap.xml file is.
Sitemaps do not have to be XML documents. A simple text file with URLs is all you need so something like below works fine. In the following example, fetchMyUrls() would be a function/method that asynchronously gets and returns the available URLs as an array of strings (URL strings).
async function index (req, res){
return fetchMyUrls().then((urls) => {
var str = '';
for (var url of urls) {
str = str + url + '\n';
}
res.type('text/plain');
return res.send(str);
});
}
For those looking for a way to create the XML dynamically on your code and don't want to use another library nor have a file stored in the public folder, you can use this:
app.get('/sitemap.xml', async function(req, res, next){
let xml_content = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
' <url>',
' <loc>http://www.example.com/</loc>',
' <lastmod>2005-01-01</lastmod>',
' </url>',
'</urlset>'
]
res.set('Content-Type', 'text/xml')
res.send(xml_content.join('\n'))
})
In my NodeJS express project and without installing any library I was able to add this to my routes with my preferred view engine (handlebar).
export const routes: RouteMapper[] = [
{
"/sitemap.xml": [
{
method: "get",
handler: (req, res) =>
res.sendFile("/src/views/sitemap.xml", { root: "." }),
},
],
},
];
Cheers!
The best way is to create a script that would automatically generate a sitemap. In a lot of cases, the URLs should be dynamic based on data from the database.
Great package for creating the sitemap in Express is sitemap package:
STEP 1
Create a middleware that will generate the sitemap dynamically and then cache it for each next call to the server. We can extract logic in separate file called sitemap_generator.js for example, and we can define and export generate_sitemap middleware for it:
const { SitemapStream, streamToPromise } = require('sitemap');
const { Readable } = require('stream');
let sitemap;
const generate_sitemap = async (req, res, next) => {
res.header('Content-Type', 'application/xml');
if (sitemap) return res.status(200).send(sitemap); // If we have a cached entry send it
let changefreq = 'weekly';
try {
let links = [
{ url: '', changefreq, priority: 1 },
{ url: 'aboutus', changefreq, priority: 0.9 },
{ url: 'blog', changefreq },
{ url: 'login', changefreq },
{ url: 'register', changefreq },
];
// Additionally, you can do database query and add more dynamic URLs to the "links" array.
const stream = new SitemapStream({ hostname: 'https://example.com', lastmodDateOnly: true })
return streamToPromise(Readable.from(links).pipe(stream)).then((data) => {
sitemap = data; // Cache the generated sitemap
stream.end();
return res.status(200).send(data.toString())
});
} catch (error) {
return res.status(500).end();
}
}
module.exports = { generate_sitemap };
STEP 2
Import generate_sitemap middleware from sitemap_generator.js in your server configuration file and mound it to the /sitemap.xml endpoint:
const { generate_sitemap } = require('./sitemap_generator');
...
app.get('/sitemap.xml', generate_sitemap);
That's it. Your sitemap should be available on /sitemap.xml endpoint now so navigate in the browser to that endpoint and check if it is there.