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 👍
Related
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.
I have a Next 10 project where I am trying to use WebWorkers. The worker is being initialized like so:
window.RefreshTokenWorker = new Worker(new URL('../refreshToken.worker.js', import.meta.url))
I also have the Worker defined as
self.addEventListener('message', (e) => {
console.info("ON MESSAGE: ", e)
// some logic with e.data
})
Its also being called like this:
const worker = getWorker() // gets worker that is attached at the window level
worker.postMessage('start')
My next.config.js file is defined as
const nextConfig = {
target: 'serverless',
env: getBuildEnvVariables(),
redirects,
rewrites,
images: {
domains: []
},
future: { webpack5: true },
webpack (config) {
config.resolve.alias['#'] = path.join(__dirname, 'src')
return config
}
}
// more definitions
module.exports = nextConfig
The issue I have is the console.info in the Web Worker definition does not receive the message being sent from postMessage on the build version (yarn build && yarn start) but it does on the dev version (yarn dev). Any ways to fix this?
This is not a solution. But can be a messy way to do the job. This turned out to be a nightmare for me.
I have the same setup as yours. I was initializing web worker as you have shown in your question. I got this idea from the nextjs doc itself: https://nextjs.org/docs/messages/webpack5
const newWebWorker = new Worker(new URL('../worker.js', import.meta.url))
Everything working correctly when I work in dev mode. it is picking up the worker.js file correctly and everything looks alright.
But when I build the nextjs and try it, then web worker won't work. When I dive deeply into the issues, I found out that the worker.js chunk file is created directly under the .next folder. It should come under .next/static/chunk/[hash].worker.js ideally.
I could not resolve this issue in a proper way.
So what i did, i placed my worker.js file directly under public directory. I put my worker.js file transpiled and optimized and put the code in the public/worker.js file.
After this, I modified the worker initialization like this:
const newWebWorker = new Worker('/worker.js', { type: 'module' });
it is working in the production build now. I will report once I get a cleaner solution for this.
I'm a bit lost and need some help with VueJs. I am using Vue CLI3 and have created a new Vue project where eveything is working, no errors in the console etc. However, after running the build task, the copy in my dist folder shows as a blank page. I have learnt that this is to do with needing to update the assetsPublicPath: and remove the '/' forwards slash. To do this I have been told you have to update the config file index.js but there is no such file in my proect? I have also been told there is a config folder, but there isnt?
Therefore how do I update the following
from assetsPublicPath: '/',
to assetsPublicPath: '',
Take a look at the documentation. If you don't have the vue.config.js just create it. I would look something like this:
// vue.config.js
module.exports = {
// Any of the config options will come here. Everything you'll need is in the docs
publicPath: ''
}
Only create a vue.config.js in your project and use inside .File is automatic loaded by vue cli serve. After publish your hosting or server file will must work it.
module.exports = {
css: {
extract: true
},
publicPath: process.env.NODE_ENV === "production" ? "" : "",
outputDir: "dist"
};
I have an AngularJs (1.7) SPA with Webpack (4.x).
This is how we create chunknames:
config.output = {
path: PATHS.build,
publicPath: '/dist/',
filename: `[name]${isDev ? '' : '.[contenthash:8]'}.bundle.js`,
chunkFilename: `chunks/[name]${isDev ? '' : '.[contenthash:8]'}.chunk.js`
};
The lazyloading is done in the state definitions in ui-router basically like this:
$stateProvider
.state('reports', {
url: '/projects/:project_id/reports',
lazyLoad: function($transition$) {
const injector = $transition$.injector().get('$injector');
return import(/* webpackChunkName: "admin.reports.module" */ './reports')
.then(mod => {
injector.loadNewModules([mod.default]);
})
.catch(err => {
throw new Error('An error occured, ' + err);
});
}
})
After a deployment due changes to a module in a "dynamic" chunk - the filename will change of this chunk ([contenthash] has changed).
When a logged in user (where all bundled assets are loaded before the last deployment) now tries to open a route with the new chunk - the chunk is not there (404) and it will fail with:
Transition Rejection($id: 4 type: 6, message: The transition errored, detail: Error: An error occured, Error: Loading chunk 14 failed.
(error: admin.reports.module.8fc31757.chunk.js))
Is there a common way to circumvent/deal with this?
Maybe more in general: How can changes to a bundled web app be detected? Is there a common way to trigger a reload? Is a manual refresh always neccessary?
I think there are a few ways to circumvent this, since the javascript in the current context isn't aware of the new hash of the content generated by the latest build you could try:
1.) You could try setting up an http redirect on the hashed files: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
The browser will request the old file and the server can point to the new file instead of returning 404. If all of your files follow a convention and only store one of the file at a time ex: component.hash.js then this should be pretty easy.
2.) A hacky client approach would be handling the transition-rejection in a try catch and reload the page, without the cache to get the new assets. https://developer.mozilla.org/en-US/docs/Web/API/Location/reload
There's always more than one approach, but this is what I could think of to solve the issue.
i'm trying to use web workers in my web app but i'm having a hard time. Adding a new entry to the webpack.config.js does not work.
so, I'm trying to use the npm package called worker-loader but there is no proper example on how to use it. All of my attempts to use it has failed. Can you guys please show me a simple example on how to use it.
import Worker from "worker-loader!./Worker.js";
const myworker = new Worker();
myworker.postMessage(songs);
myworker.onmessage = function(Data) {
//do something
}
my webpack.config.js file is like this with
entry: __dirname + "/js/index.js",
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
{
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
}
}
My server tells me the following:
"GET /279b1e1fcb403131377a.worker.js HTTP/1.1" 404
Since my 279b1e1fcb403131377a.worker.js is inside /dist its giving me 404 error. How can i make the request to go inside /dist.
There are plenty of examples given on the official Github page:
https://github.com/webpack-contrib/worker-loader
That should easily get you going.