Vite plugin for SvelteKit: Generated files aren't added to the build output - vite

I am currently working on another plugin, that should in the end generate the webmanifest and all images and splash screens needed for a PWA (minus the service worker). I am planning on making this a plugin for vite (rollup), with a special focus on sveltekit, because that's where I plan on using it.
I currently have this setup as a package that exports both mjs and cjs, and should for all I know have a working version to test with. Sadly, the output emitted using this.emitFiles doesn't appear in the build output, even though prior function returns an assetId that resolves to a URL.
Code
index.ts
import { Plugin } from 'vite'
import { PluginOptions } from './types.js'
import { readFileSync } from 'fs'
import { generateResizedWebpIcon, generateResizedJpegIcon } from './utils.js';
export default (options: PluginOptions): Plugin => {
const iconResolutions = [16, 48, 128, 512]
return {
name: 'vite-plugin-pwa',
async transformIndexHtml() {
// add images and manifest to build output
// generate icons and emit them, store the urls
const icon = readFileSync(options.image.src)
let icons = await Promise.all(iconResolutions.map(async res => {
const resolveID = this.emitFile({
type: 'asset',
name: `icon-${res}x${res}.webp`,
source: await generateResizedWebpIcon({...})
})
return {
type: 'image/webp',
sizes: `${res}x${res}`,
src: this.getFileName(resolveID)
}
}, this))
if (options.image.output?.jpeg) {
icons.push(...await Promise.all(iconResolutions.map(async res => {
const resolveID = this.emitFile({
type: 'asset',
name: `icon-${res}x${res}.jpeg`,
source: await generateResizedJpegIcon({...})
})
return {
type: 'image/jpeg',
sizes: `${res}x${res}`,
src: this.getFileName(resolveID)
}
}, this)))
}
const packageInfo = JSON.parse(readFileSync('package.json').toString())
const manifest = {
name: packageInfo.name || 'name',
description: packageInfo.description || 'description',
...options.manifest || {},
icons
};
const manifestUrl = this.getFileName(
this.emitFile({
type: 'asset',
name: 'manifest.json',
source: Buffer.from(JSON.stringify({
manifest
}))
})
)
// generate manifest with icons, save the url
// generate apple splashes and emit them, save the urls
// add links to manifest and apple meta tags
return [
{
tag: 'link',
attrs: {
rel: 'manifest',
href: manifestUrl
},
injectTo: 'head'
}
]
},
}
}
In this example, the <link rel="manifest" href="_app/manifest.webmanifest"> turns up in the html and chrome tries to fetch it. But the server returns a 404 Not Found code. It appears vite emits the file, but it is somehow overwritten by the sveltekit build process?
Does anyone know how to make this emit a file that also turns up in the final build output?

Related

Image uploaded works in localhost but not on server (s3)

Well, I have an option in the system where I can see some documents sent by the user. But the images appear as broken icons.
As "localhost" everything works great, but I can't see the images when I run straight from the website. (On the s3 everything is working normally, I can see the images.)
In the brownser console, an error is thrown about the route that the request is being made "api.mysite.com/the-image-example.png" with status code 404.
What I don't understand is why the request is being made there and not directly on amazon s3 (there I have the images loading normally). Someone cal help me with that??
How images appear:
This is the code
Upload config:
const tmpFolder = path.resolve(__dirname, '..', '..', 'tmp');
export const mimeTypesPhotos = [
'image/png',
'image/jpeg',
'image/bmp',
'image/webp',
];
export const mimeTypesVideos = ['video/webm', 'video/ogg', 'video/mp4'];
export const mimeTypesPng = ['application/pdf'];
const pngRoutes = ['/requests/test', '/test/:id/confirm'];
interface IUploadConfig {
driver: 's3' | 'disk';
tmpFolder: string;
uploadsFolder: string;
multer: multer.Options;
image: {
height: number;
width: number;
};
config: {
disk: unknown;
aws: {
bucket: string;
};
};
}
export default {
driver: process.env.STORAGE_DRIVER,
tmpFolder,
uploadsFolder: path.resolve(tmpFolder, 'uploads'),
multer: {
storage: multer.diskStorage({
destination: tmpFolder,
filename: (request, file, callback) => {
const fileHash = crypto.randomBytes(10).toString('hex');
const fileName = `${fileHash}-${file.originalname}`;
return callback(null, fileName);
},
}),
fileFilter: (request, file, callback) => {
const url = matchId(request.originalUrl);
const mimetypes = [...mimeTypesPhotos];
if (pngRoutes.includes(url)) {
mimetypes.push(...mimeTypesPng);
}
if (!mimetypes.includes(file.mimetype)) {
return callback(new Error("File doesn't supported"));
}
return callback(null, true);
},
},
image: {
width: Number(process.env.MAX_IMAGE_SIZE || '1024'),
height: Number(process.env.MAX_IMAGE_SIZE || '1024'),
},
config: {
disk: {},
aws: {
bucket: process.env.AWS_BUCKET || 'mybucket',
},
},
} as IUploadConfig;
Router file (where is calling the route i mean):
app.use('/files', express.static(uploadConfig.uploadsFolder));
The Media Entity:
#Column({ type: 'varchar' })
type: 'photo';
#Column()
media: string;
#Expose({ name: 'mediaUrl' })
getMediaUrl(): string | null {
if (!this.media) {
return null;
}
switch (uploadConfig.driver) {
case 'disk':
return `${process.env.APP_API_URL}/files/${this.media}`;
case 's3':
return `https://${uploadConfig.config.aws.bucket}.s3.amazonaws.com/${this.media}`;
default:
return null;
}
}
Step-1
we need to Enable CORS in the API gateway, so click on the resource and then click on the Action button and Enable CORS. In CORS settings the value of Access-Control-Allow-Headers is '' and Access-Control-Allow-Origin is '' , and leave other settings as it is. Click on Enable CORS and replace existing CORS headers.
Step-2
Now, go to the settings of that API on which you are working. In settings enable Binary Media Types. Set the value of that field like this / , and Save the changes.

How to render a notebook with voila in a self-implemented extension?

I'd like to create a personal Jupyter Lab extension to add a button to the Launcher. When clicking on it, it should render a jupyter lab GUI with Voila.
As I'm quite new to this kind of implementation, I copied a lots this extension example to create a new Launcher button. But I don't understand well how to implement the voila part.
I've gone directly to the source code as the npm page did not give a lots of information. I think I need to import the IVoilaPreviewTracker object from this module, which gives me the following code.
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '#jupyterlab/application';
import { ICommandPalette } from '#jupyterlab/apputils';
import { IFileBrowserFactory } from '#jupyterlab/filebrowser';
import { ILauncher } from '#jupyterlab/launcher';
import { LabIcon } from '#jupyterlab/ui-components';
import IVoilaPreviewTracker from "#voila-dashboards/jupyterlab-preview"
import pythonIconStr from '../style/Python-logo-notext.svg';
const PALETTE_CATEGORY = 'My tools';
const LAUNCHER_CATEGORY = 'My tools';
namespace CommandIDs {
export const createNew = 'my-tools:open-gippy';
}
const extension: JupyterFrontEndPlugin<IVoilaPreviewTracker> = {
id: 'launcher',
autoStart: true,
requires: [IFileBrowserFactory],
optional: [ILauncher, ICommandPalette],
activate: (
app: JupyterFrontEnd,
browserFactory: IFileBrowserFactory,
launcher: ILauncher | null,
notebook: IVoilaPreviewTracker | null,
palette: ICommandPalette | null
) => {
const { commands } = app;
const command = CommandIDs.createNew;
const icon = new LabIcon({
name: 'launcher:gipp-icon',
svgstr: pythonIconStr,
});
commands.addCommand(command, {
label: (args) => (args['isPalette'] ? 'Open tool' : 'My tool'),
caption: 'Open the Voila interface to use my tool',
icon: (args) => (args['isPalette'] ? null : icon),
execute: async (args) => {
// Get the directory in which the Python file must be created;
// otherwise take the current filebrowser directory
const cwd = args['cwd'] || browserFactory.tracker.currentWidget.model.path;
const gui_path = cwd + "ihm_nb.ipynb";
commands.execute('notebook:render-with-voila', {
path: gui_path,
});
}
});
// Add the command to the launcher
if (launcher) {
launcher.add({
command,
category: LAUNCHER_CATEGORY,
rank: 1,
});
}
// Add the command to the palette
if (palette) {
palette.addItem({
command,
args: { isPalette: true },
category: PALETTE_CATEGORY,
});
}
},
};
export default extension;
This does not work. My linter prints me 'IVoilaPreviewTracker' refers to a value, but is being used as a type here. Did you mean 'typeof IVoilaPreviewTracker'?.
I can't understand which object to import to use the notebook:render-with-voila command.
Please help!
First:
You have to use void instead of IVoilaPreviewTracker here:
const extension: JupyterFrontEndPlugin<void>
Next you need to use this kind of command:
commands.addCommand(command, {
label: (args) => (args['isPalette'] ? 'Open tool' : 'My tool'),
caption: 'Open the Voila interface to use my tool',
icon: (args) => (args['isPalette'] ? null : icon),
execute: () => {
const notebook_path = "ihm_nb.ipynb";
commands.execute("docmanager:open", {
path: notebook_path,
factory: "Voila-preview",
options: {
mode: "split-right",
},
});
},
}

Phaser 3: Finish loading json before Scene.preload() gets called

I want to describe my scenes in a JSON file, and then preload images etc. according to the contents of that JSON file. So the content of the JSON file needs to have been fully loaded before entering preload. I don't know how to do this. I've created a minimal, reproducible jsfiddle that logs preload: undefined when prepreload = true.
For prepreload = false I get create: [object Object].
var prepreload = true
var config = { scene: { init: init, preload: preload, create: create } }
var game = new Phaser.Game(config)
function init(config) {
if (prepreload) {
this.load.json('scenedata', 'scene.json');
this.load.start();
}
}
function preload () {
if (prepreload)
console.log(this.cache.json.get("scenedata"));
else
this.load.json('scenedata', 'scene.json');
}
function create () {
if (!prepreload)
console.log(this.cache.json.get("scenedata"));
}
Any idea how to preload images according to the contents of a JSON file - ideally all inside the Scene class, rather than doing it externally and passing it into the init method or similar.
SettingsConfig can be used for that - the files listed under pack will already have been loaded when entering the init method.
Here's how it can be done (Phaser 3.55.2):
var config = {
scene: {
init: init,
preload: preload,
create: create,
pack: {
"files": [
{ type: 'json', key: 'scene', url: 'scene.json' }
}
}
}
}
var game = new Phaser.Game(config)
Here's a slightly different variant, using TypeScript:
export class TestScene extends Phaser.Scene {
constructor() {
super({ pack: {
"files": [ { type: 'json', key: 'scene', url: 'scene.json' }

How to make Mapbox load on gatsby site? Build succeeds but map not displaying, despite working in dev mode

I'm trying to use a Mapbox map on a gatsby website. I used the code from their react Mapbox tutorial: https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/ and put it into a map component.
I call the map component on the footer and it works perfectly during development mode, but when I run gatsby build and then gatsby serve it refuses to show the map despite showing the container :
https://postimg.cc/mPcRfYhV
I've tried the other suggestion which is from the docs too, that is to use in gatsby-node.js
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox|mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
for the test property I've tried using /mapbox|#mapbox|mapbox-gl|mapboxgl/ but nothing seems to make it work like it does in dev mode.
any ideas?
The component:
import React, { useEffect, useRef, useState } from "react";
import mapboxgl from 'mapbox-gl';
import "mapbox-gl/dist/mapbox-gl.css";
const styles = {
width: "220px",
height: '130px',
margin: '2em 0'
};
const Map = () => {
const [map, setMap] = useState(null);
const mapContainer = useRef(null);
useEffect(() => {
mapboxgl.accessToken = process.env.MY_KEY
const initializeMap = ({ setMap, mapContainer }) => {
const map = new mapboxgl.Map({
container: mapContainer.current,
style: "mapbox://styles/mapbox/streets-v11",
center: [-6.2603, 53.3498],
zoom: 9
});
map.on("load", () => {
setMap(map);
map.resize();
});
new mapboxgl.Marker({color:'#1E3873'}).setLngLat([-6.2603, 53.3498]).addTo(map)
};
if (!map) initializeMap({ setMap, mapContainer });
}, [map]);
return <div ref={el => (mapContainer.current = el)} style={styles} />;
};
export default Map;
The Map component is called on the footer component in a normal way by just importing the component and using <Map />.
Picture of it working fine when using gatsby develop
Picture of it not working when using gatsby build gatsby serve
SOLUTION EDIT: for some reason mapbox-gl 2.0 doesn't work, or I can't get it to work. Someone else had the same issue and suggested what worked for them which was to use mapbox-gl 1.13.0 . I tried it and it works.
npm uninstall mapbox-gl
npm i mapbox-gl#1.13.0
Leave the other configurations the same and it should work.
For some reason mapbox-gl 2.0 doesn't work properly. Someone else had the same issue and suggested what worked for them which was to use mapbox-gl 1.13.0 . I tried it and it works.
npm uninstall mapbox-gl
npm i mapbox-gl#1.13.0
Remember to use the following code in gatsby-node.js :
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox|mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
For future readers, I was able to make it work w/ mapbox v2 in Gatsby 2 with import mapboxgl from "!mapbox-gl"; as described in mapboxgl docs https://docs.mapbox.com/mapbox-gl-js/api/#transpiling-v2
Among the configuration, you are missing some stuff, because in the guide, there is a class-based component and you are using a functional one, change it to:
import React, { useEffect, useRef, useState } from "react";
import mapboxgl from 'mapbox-gl';
import "mapbox-gl/dist/mapbox-gl.css";
const styles = {
width: "220px",
height: '130px',
margin: '2em 0'
};
const Map = () => {
const [map, setMap] = useState(null);
const mapContainer = useRef(null);
useEffect(() => {
mapboxgl.accessToken = process.env.MY_KEY
const initializeMap = ({ setMap, mapContainer }) => {
const map = new mapboxgl.Map({
container: mapContainer.current,
style: "mapbox://styles/mapbox/streets-v11",
center: [-6.2603, 53.3498],
zoom: 9
});
map.on("load", () => {
setMap(map);
map.resize();
});
new mapboxgl.Marker({color:'#1E3873'}).setLngLat([-6.2603, 53.3498]).addTo(map)
};
if (!map) initializeMap({ setMap, mapContainer });
return () => {
map.remove()
}
}, []);
return <div ref={mapContainer} style={styles} />;
};
export default Map;
Notice the ref and the map initializer. I think that it's not properly set when running a gatsby build and, since you have passing it as a reference in el => (mapContainer.current = el) it's not being rehydrated. React guides should differ a little bit when dealing with Gatsby due to their SSR (Server-Side Rendering).
Regarding the webpack issue.
Try:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
Or:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox/,
use: loaders.null(),
},
],
},
})
}
}
Or ignoring both with:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /#mapbox/,
use: loaders.null(),
},
{
test: /mapbox-gl/,
use: loaders.null(),
},
],
},
})
}
}
Keep in mind that the testing rule is a regular expression (that's why is written between slashes) that should match a folder inside node_modules so it should be an exact path.
This is a needed workaround for every dependency that uses window, or other global objects as a dev-dependency for themselves. This is because, the interpreter when running gatsby develop is the browser (browser-side rendering), where there's a window available and while running gatsby build, the code is compiled in the server (Node), where there isn't a window obviously.
The snippet above adds a null loader during the webpack's transpilation, or in other words, forces webpack to ignore the module to avoid a code-breaking.

NextJS redirects not redirecting urls after define in next.config.js file

I tried to define redirects in my NextJS app.
but it is not working.
This is how I tried to do it in my next.config.js file:
const withImages = require('next-images')
const withPlugins = require("next-compose-plugins");
const optimizedImages = require("next-optimized-images");
module.exports = withPlugins(
[
[optimizedImages, {
inlineImageLimit: 512
}]
],
{
async redirects() {
return [
{
source: "/sales/guest/form",
destination: "/",
permanent: true
}
]
},
env:{
testEnvVar: 'vallll'
}
}
);
This is the documentation of how to do it:
https://nextjs.org/docs/api-reference/next.config.js/redirects
For redirects and rewrites to work properly in NextJs, you also need to ensure one more thing:
If you are using trailingSlash: true then your source paths must end with a slash.
{
source: '/old/:id/', // Notice the slash at the end
destination: '/new/:id',
},
Any other plugins or configurations that interfere with routing also need to be taken into account.
you can add all you imports and also const definitions to first array parameter like this
const withPlugins = require('next-compose-plugins');
const css = require('#zeit/next-css');
const less = require('#zeit/next-less');
const nextConfig = {
target: 'serverless',
webpack(config, { isServer, webpack }) {
// al your config
return config;
},
};
const redirects = {
async redirects() {
return [
{
source: '/old/blogs/:slug*',
destination: 'whatever your new rewrite url',
permanent: true,
},
];
},
};
module.exports = withPlugins(
[
[css],
[less],
[redirects], // you can directly drop your redirect rules here
],
nextConfig
);
What NextJS Version are you on? Redirects are supported from 9.5 upwards
For anyone who has this problem, try restarting the server. The config file will be reloaded then.
In my case, I tried to redirect to external link. I had trailingSlash: true and I ended my source path with slash.
It didn't work because I use Link component from next/link
I changed it to normal a tag and it worked.
Before:
<Link href="/some-path" passHref>
<a>
to external
</a>
</Link>
After:
{/* eslint-disable-next-line #next/next/no-html-link-for-pages */}
<a href="/some-path">
to external
</a>
You need to disable eslint rule #next/next/no-html-link-for-pages so it won't raise error while building
in next.config.js file:
module.exports = {
trailingSlash: true,
reactStrictMode: true,
async redirects() {
return [
{
source: "/some-path",
destination: "https://example.com",
permanent: true,
},
]
},
}

Resources