recently I have started working with vite on a couple of small projects and found it very interesting, however got a blocker once tried to work on ExpressJS + Svelte coupled project.
I usually use Express as BFF (Backend For Frontend) when it comes to working on rather more serious projects since it allows me to go for HTTPOnly cookies as well as proxy gateway for the frontend. However for development (specially when it comes to oauth2) it is hard to develop the spa separated form the server so what I usually do with webpack is activating the WriteToDisk option for devserver which then allows me to have my development build in the dist folder.
Example with webpack will be something like the webpack config below for the frontend:
module.exports = {
devServer: {
devMiddleware: {
writeToDisk: true,
},
},
//...
}
and then on the server basically rendering the dist as static folder:
app.get(
"*",
(req, res, next) => {
if (req.session.isAuth) return next();
else return res.redirect(staticURL);
},
(req, res) => {
return res.sendFile(staticProxyPage());
}
);
My problem
I can not find in vite's documentation any APIs to do something like this, does anyone have any experience with such cases?
if it is possible with the help of plugins, can you please provide references to the plugin or dev logs of it?
Many Thanks :)
Here is an example plugin I was able to hack together. You might need to modify it to suit your needs:
// https://vitejs.dev/guide/api-plugin.html#universal-hooks=
import {type Plugin} from 'vite';
import fs from 'fs/promises';
import path from 'path';
const writeToDisk: () => Plugin = () => ({
name: 'write-to-disk',
apply: 'serve',
configResolved: async config => {
config.logger.info('Writing contents of public folder to disk', {timestamp: true});
await fs.cp(config.publicDir, config.build.outDir, {recursive: true});
},
handleHotUpdate: async ({file, server: {config, ws}, read}) => {
if (path.dirname(file).startsWith(config.publicDir)) {
const destPath = path.join(config.build.outDir, path.relative(config.publicDir, file));
config.logger.info(`Writing contents of ${file} to disk`, {timestamp: true});
await fs.access(path.dirname(destPath)).catch(() => fs.mkdir(path.dirname(destPath), {recursive: true}));
await fs.writeFile(destPath, await read());
}
},
});
In short, it copies the content of the publicDir (public) to the outDir (dist).
Only thing missing is the ability to watch the public folder and copy whatever changed to the dist folder again. It will also re-write the file if any of the files within the public folder changes.
Note: The use of fs.cp relies on an experimental API from node 16.
Feel free to modify as you please. Would be nice to be able to specify file globs to include/exclude, etc.
Related
I'm trying to install SSR on my current Vue app, and for this I'm using the vue-plugin-ssr extension. I want to run it with express, so I created a new file called server.mjs and have this:
import express from "express";
import { createServer as createViteServer } from "vite";
import { renderPage } from "vite-plugin-ssr";
async function createServer() {
const app = express();
// Create Vite server in middleware mode and configure the app type as
// 'custom', disabling Vite's own HTML serving logic so parent server
// can take control
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "custom",
});
// use vite's connect instance as middleware
// if you use your own express router (express.Router()), you should use router.use
app.use(vite.middlewares);
app.get("*", async (req, res) => {
// `renderPage()` can also be used in serverless environments such as
// Cloudflare Workers and Vercel
const { httpResponse } = await renderPage({ url: req.url });
res.send(httpResponse.body);
});
app.listen(3000);
}
createServer();
So this actually works for dev, when I run node server.mjs, but neither client or server folder has an index.html file, so how can I run this on production actually?
Only what I'm doing is to set the folder on nginx to path/dist/client, do I need to do something else?
btw as a response on production I have only 403 forbbiden.
You need to run the client and ssr build process as demonstrated in Vite's documentation and then:
Move the creation and all usage of the vite dev server behind dev-only
conditional branches, then add static file serving middlewares to
serve files from dist/client
I have a Sapper.js application that I have successfully running on AWS Lambda. Lambda is able to deliver the server-side generated HTML created by Sapper to AWS API Gateway which then serves the app to the user. I am using S3 to host the client side assets (scripts, webpack chunks, etc). The S3 bucket is on a different domain than API Gateway.
The issue I'm having is that I need to set an asset prefix for these scripts so that Sapper can find them. Currently all of my client side scripts include relative links and look like this: <script src="/client/be33a1fe9c8bbaa6fa9d/SCRIPT_NAME.js"></script> I need to have them look like this: <script src="https://AWS_S3_BUCKET_ENDPOINT.com/client/be33a1fe9c8bbaa6fa9d/SCRIPT_NAME.js"></script>
Looking in the Sapper docs, I see that I can specify a base url for the client and server. However, changing this base url breaks my app and causes the Lambda rendering the pages to return 404 errors.
I know that when using, say, Next.js, I can accomplish this by modifying the next.config.js file to include the following:
module.exports = {
assetPrefix: "https://AWS_S3_BUCKET_ENDPOINT.com/client",
}
But I don't know how to do this in Sapper. Do I need to modify the bundler (using webpack) config? Or is there some other way?
Thank you.
I think I've figured it out.
I had to change two sapper files. First I went into sapper/dist/webpack.js and modified it like so:
'use strict';
var __chunk_3 = require('./chunk3.js');
var webpack = {
dev: __chunk_3.dev,
client: {
entry: () => {
return {
main: `${__chunk_3.src}/client`
};
},
output: () => {
return {
path: `${__chunk_3.dest}/client`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
// change this line to point to the s3 bucket client key
publicPath: "https://AWS_S3_BUCKET_ENDPOINT.com/client"
};
}
},
server: {
entry: () => {
return {
server: `${__chunk_3.src}/server`
};
},
output: () => {
return {
path: `${__chunk_3.dest}/server`,
filename: '[name].js',
chunkFilename: '[hash]/[name].[id].js',
libraryTarget: 'commonjs2'
};
}
},
serviceworker: {
entry: () => {
return {
'service-worker': `${__chunk_3.src}/service-worker`
};
},
output: () => {
return {
path: __chunk_3.dest,
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js',
// change this line to point to the s3 bucket root
publicPath: "https://AWS_S3_BUCKET_ENDPOINT.com"
}
}
}
};
module.exports = webpack;
//# sourceMappingURL=webpack.js.map
Then I had to modify sapper/runtime/server.mjs so that the main variable points to the bucket like so:
...
const main = `https://AWS_S3_BUCKET_ENDPOINT.com/client/${file}`;
...
Testing with the basic sapper webpack template, I can confirm that the scripts are loading from the s3 bucket successully. So far this all looks good. I will mess around with the sapper build command next to make it so I can pass these hacks in as command line arguments so I don't have to hardcode them every time.
Now, I'm not sure if this will hold up as the app becomes more complicated. Looking into the sapper/runtime/server.mjs file, I see that the req.baseUrl property is referenced in several different locations and I don't know if my hacks will cause any issues with this. Or anywhere else in sapper for that matter.
If anyone with more experience with the Sapper internals is reading, let me know in the comments if I screwed something up 👍
When trying to fetch data from an API, the key that I was using was considered "undefined". My key works because I replaced the {key=undefined} in the network console with the actual string and I was able to get the data that I needed. Any thoughts? Also, I know you shouldn't hide any API keys in a React app but I am just using this for testing purposes. If it helps to clarify things, I did use Create-React-App and they did have a major update in the last 3 months, so I wonder if that has anything to do with it.
const bartKey = process.env.REACT_API_BART_API_KEY;
console.log(`Api key: ${process.env.REACT_API_BART_API_KEY}` );
//inside class Component
async getAllStations(){
try{
const response = await fetch(`http://api.bart.gov/api/etd.aspx?cmd=etd&orig=${this.state.selectedStation}&key=${bartKey}&json=y`);
// console.log(response.json());
const data = await response.json();
console.log('Initial data: ', data);
// fetch(`http:api.bart.gov/api/etd.aspx?cmd=etd&orig=${this.state.selectedStation}&key=${bartKey}&json=y`)
// .then(response => response.json())
// .then(data => console.log(`here: ${data}`))
}catch(e){
console.log(`Error: ${e}`)
}
}
this works for me when using create-react-app:
instead of REACT_API_BART_API_KEY use REACT_APP_BART_API_KEY in your .env
Then you can call it as process.env.REACT_APP_BART_API_KEY
check this url from create-react-app docs https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables
I would say a really good solution if your .env file is working weird is to make a config file. Keep the API key in there. Put that file in git ignore and it will be hidden the same way and it is sure to work.
This changes helped me solve my issue:
Please note dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. So this you should at first download this package:
1 step: npm install dotenv --save-dev
Import webpack and dotenv in your webpack.config.js file and make this changes:
After make sure module.exports an function which at first generates the environment keys:
When the keys are generated use webpack.DefinePlugin() which will help you generate global constants for your app.
// other imports
const dotenv = require('dotenv');
const webpack = require('webpack');
module.exports = () => {
const env = dotenv.config().parsed;
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
/* ... here should be your previous module.exports object attributes */
entry: ['babel-polyfill', './src/index.js'],
output: {
path: path.join(__dirname, 'build'),
filename: 'bundle.js',
},
plugins: [
new HtmlWebPackPlugin({
template: "./public/index.html",
filename: "./index.html"
}),
new webpack.DefinePlugin(envKeys)
]
}
};
Also note that on logging process.env you will still get empty object or nothing. But if you log process.env.YOU_KEY then you will get your key value stringified
Hope it helps you!
As early as possible in your application, require and configure dotenv. (https://www.npmjs.com/package/dotenv)
require('dotenv').config()
You should write that line as early as possible in your program.
As the title suggests, when I'm ready to host the code for production, should I remove all usages of webpack-dev-middleware and webpack-hot-middleware from my server code as they are dev-dependencies? What's the best way to set this up so maybe I don't have to worry about this?
This is a snapshot of my server code:
// webpack -> HMR
const webpack = require("webpack");
const webpackConfig = require("../webpack.config");
const compiler = webpack(webpackConfig);
// webpack HMR init
app.use(
require("webpack-dev-middleware")(compiler, {
noInfo: false,
publicPath: webpackConfig.output.publicPath,
})
);
app.use(require("webpack-hot-middleware")(compiler));
...
app.get("/", async (req, res) => {
const initialContent = await serverRender();
res.render("index", {
...initialContent,
});
});
app.listen(port, () => {
console.log(`Express application listening on port ${port}`);
});
You should wrap your HMR code (or really, any development/environment specific setting) into it's own area. I wouldn't recommend taking it out of your code as you may come back to the application and want to update something. Having HMR is a pretty nice luxery, so I would just have you code sniff out the environment, and if it's development, run the associated code. Otherwise, don't run it.
How do you detect the environment in an express.js app?
I want to display the current git tag on my app's login page,
Its built using react.
Im trying to use the 'git-rev-sync' library to do this.
but it doesnt seem to work on the client side because I keep getting errors like
'cannot find module 'children process', it works on the server side where Im able to console.log and print the tag
anyone know how to achieve this? Open to any solutions with any library
import version from 'git-rev-sync'
...
class Login extends Component {
...
render ()
...
return (
<div> my version: {version.tag()} </div>
) }
Thanks
I decided to use git-revision-webpack-plugin which creates a VERSION file (among other files) in the dist folder, and then I read the file from my client side react app:
add this to your webpack.js:
const GitRevisionPlugin = require('git-revision-webpack-plugin')
module.exports = {
plugins: [
new GitRevisionPlugin({
lightweightTags: true //I added this to get the tags as well
})
]
}
then my client side looks like this:
const [revision, setRevision] = useState('')
const fetchRevision = async () => {
let result = await fetch('/dist/VERSION')
let txt = await result.text()
txt = txt.replace(/^(.*?)(?:\-.*)?$/, '$1') //I only care for the tag.
setRevision(txt)
}
useEffect(() => {
fetchRevision()
}, [])
and then you can render the revision
One thing to notice, depending on your server, you may need to tell it to serve this VERSION file as is, so for example in express, you might find you need this:
server.get('*', (req, res, next) => {
if (/^\/dist\/*/.test(req.originalUrl)) {
const relative = req.originalUrl.replace(/\/dist(\/.*)/, '$1')
const filename = path.join(compiler.outputPath, relative)
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) {
return next(err)
}
res.send(result)
res.end()
})
}
...
})
Hope this helps for future use.
If you used create-react-app#0.2.3 > to generate your app.
create-react-app scripts use environment variables that start with the REACT_APP_ symbol in the root .env file. create-react-app - Adding custom environment variables is a good place to dig into the details.
or just include the following in your .env file.
.env
REACT_APP_VERSION=$npm_package_version
and access it on your react login component by referring to {process.env.REACT_APP_VERSION}