Rails 7, React & Esbuild - Static assets not served in production - reactjs

I have created a simple new Rails 7 project from scratch with Esbuild and React to get to know these, and they indeed feel like a step up from Webpack, except I can't manage to have my static files (ie image) served in production.
I have an Esbuild config file:
// esbuild.config.js
const path = require('path');
require("esbuild").build({
entryPoints: ["application.jsx"],
bundle: true,
outdir: path.join(process.cwd(), "app/assets/builds"),
absWorkingDir: path.join(process.cwd(), "app/javascript"),
publicPath: "assets",
sourcemap: true,
watch: process.argv.includes('--watch'),
plugins: [],
loader: {
'.js': 'jsx',
'.jpg': 'file',
'.png': 'file',
'.MOV': 'file'
},
}).catch(() => process.exit(1));
I store my images in the app/javascript/images/ folder, and import and use them in a React component like that (for example):
import MyImage from "./images/myimage.jpg"
import styled from "styled-components";
//...
const SomeStyledComponent = styled.div`
background-image: url(${MyImage});
`
In development, everything works fine, Esbuild copies the images in the app/assets/builds/ folder, fingerprints them, and prepends all the images url with the publicPath property of my Esbuild config. The above image for example then has the relative url assets/myimage-FINGERPRINT.jpg which is served correctly in development.
Then things get complicated in production (production here being just a Docker container built for production - I don't add the Dockerfile to keep things simple as I don't think it would help, but happy to provide it of course).
In my production.rb I have added the following:
config.public_file_server.enabled = true
(which will be replaced by an environment variable later)
The assets precompiling succeeds, and my images are in the app/public/assets/ folder, fingerprinted once more by Sprockets this time (from what I understand), but now I get 404. I have tried changing Esbuild publicPath and have tried to get the images directly in my browser, but whatever I try (assets, public, public/assets), nothing work and I am running out of ideas.
I have temporary fix which is to change the loader for images to dataurl, but that does not feel like a good practice, as my compiled javascript is going to explode.
Thank you for your help!

I ran into a similar issue, and by looking at the answers to https://github.com/rails/jsbundling-rails/issues/76 and this PR: https://github.com/rails/sprockets/pull/726/files
I was able to figure out the proper setup that works.
in the build options, I changed
publicPath: "assets",
to
publicPath: "/assets",
(included the leading /, which was missing before and causing the wrong path to be used)
and then added the following option
assetNames: "[name]-[hash].digested",
which, as you might be able to tell from the linked PR, would prevent Sprockets from adding an additional layer of fingerprinting.
I hope that helps.

Related

How to make a path alias in CRA TypeScript in 2022?

I just initiated CRA npx create-react-app my-app --template typescript and I want to make an alias when calling components, like:
import components from '#components'
where the components is located at src/components.
I've tried to config in tsconfig.json by adding:
{
"compilerOptions": {
...
"baseUrl": "./src",
"paths": {
"#utils/": ["./utils/"],
"#utils/*": ["./utils/*"]
}
}
}
Also in webpack.config.js by adding:
// const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const path = require('path')
module.exports = {
resolve: {
// plugins: [new TsconfigPathsPlugin()],
alias: {
'#utils': path.resolve(__dirname, './src/utils/'),
'#utils/*': path.resolve(__dirname, './src/utils/*')
}
}
}
But it's still doesn't work.
Anyone could help me to solving these problem? But, I don't wont to use other libraries like #craco/craco.
The issue is that CRA uses its own Webpack config under the hood. Simply making a new webpack.config file doesn't actually point CRA to it, unless you run npm run eject.
Doing so is irreversible, but will add the config files to your project. From there, you should be able to modify your build settings to fit your needs.
Reminder that this cannot be undone in your project, barring perhaps a git reset, and may be more than you bargained for.
This issue with aliases seems to be a known one. Something people deemed possible earlier seems to no longer be working, or supported. Some people are speculating this could have something to do with the recent update of Webpack to version 5. And while some people claim that craco doesn't work for them, I was able to get it to work in a brand new CRA app with minimal changes. I know you're not interested in that so I won't post it here.
Alternatively, CRA allows the use of absolute imports via the src baseUrl. This will point both VSCode and Webpack to your final files, but you won't be able to set up custom paths.
"baseUrl" : "."
Using multiple index.ts files and exporting nested code up to the highest level in the directory, I'm able to keep the import paths as short as an alias:
import { persistor, store } from "src/state-management";
This could be good enough for you. If not, consider adding a package to override some of CRA's Webpack settings, or ejecting and taking matters into your own hands.

Shipping a React application as a singular JS file

I have an application created with create-react-app which needs to be shipped as a singular .js file, complete with its components and CSS style sheets. How can it be done? Without ejecting, if possible. The standard npm run build command only provides source code split into chunks.
Tried solving this issue with with react-rewire-app, but the build failed to complete.
My config-override.js is as follows:
const path = require('path');
module.exports = function override(config, env) {
config.entry = {
'my-app': './src/index.js'
}
config.module.output = {
path: path.resolve(__dirname, '../target/classes/static/'),
filename: '[name].js',
publicPath: '/'
}
return config
}
``
You're looking to make a widget or a library, likely, not an application, so CRA is the wrong tool. CRA is a tool for applications, where you definitely don't want everything bundled into a single file like you're asking for, as that would be very poor user experience.
For library/widget bundlers, I'd take a look at something like Microbundle: https://github.com/developit/microbundle. Should be very little config.
That being said, you cannot include CSS style sheets in JS. You can't even import them into JS. You need to ship them along with your JS and let your user handle importing it through HTML or what have you.

workbox webpack 4 plugin unable to precache non-webpack assets

I am looking to precache images for a PWA, using this documentation.
I have tried several iterations, but I am struggling with the globs.
Here's one instance of the plugin code on my webpack.config.js:
new InjectManifest({
swSrc: './client/sw-src.js',
swDest: '../sw.js',
exclude: [/\.twig$/],
globPatterns: ['/img/*.{svg,jpg,webp}']
}),
The directory structure is as follows:
/public
/dist => there's where the 'regular' webpack assets are
/img => directory I want to add to precache on top of /dist
...
I have also tried to use globDirectory, with no luck.
It works if I manually add the code below to my sw-src.js file, but that is not ideal and prone to errors.
workbox.precaching.precache([
'/img/circles.svg',
'/img/concept-1.jpg',
......
]);
workbox.precaching.addRoute();
One thing worth mentioning is that Workbox works with webpack assets that have been added to the compilation output. It's possible that you have files in the build context, like images in your repository, but they need to actually be required or otherwise added to the output.
One easy way to achieve this is to use copy-webpack-plugin. This is really useful when migrating to webpack from other build tools or when your dynamically constructing asset URLs and aren't using webpack loaders.
EDIT: Adding setup with actual solution:
This is the new directory setup:
/assets/img/ => origin directory for copy-webpack-plugin
/public
/dist => there's where the 'regular' webpack assets are
/img => destination directory for copy-webpack-plugin
And the actual code for copy-webpack-plugin and also adjusting the clean-webpack-plugin
new CleanWebpackPlugin(['public/dist/*.*', 'public/img/*.*']),
new CopyWebpackPlugin([
{ from: './assets/img/', to: '../img' },
]),

Webpack-Serve Example

I'm in the nightmare process of updating one of my older projects from Webpack 3 to 4 and it's introducing a whole chain of things that need fixing. The most annoying one thus far is definitely switching from webpack-dev-server to webpack-serve due to it's lack of an actual example. So with that in mind, how the heck do I use it?
Using Webpack 4.14.0 and Webpack-Serve 1.0.4.
My webpack.config.js had the following options for webpack-dev-server:
devServer: {
contentBase: path.join(__dirname, 'public'),
historyApiFallback: true,
publicPath: '/dist/'
}
I don't see options for history or public paths just yet, so I suppose I only need the content config option.
So according to the docs I'm supposed to do:
serve({
content: path.join(__dirname, 'public')
});
But where do I put this? The Github README claims that the most commonly used is to put it webpack.config.js but that seems false because the example also does this:
const serve = require('webpack-serve');
const config = require('./webpack.config.js');
serve({ config });
Do I really import a config file into itself?
I'd appreciate an example. Thanks.
I answered in another question very similar on "how to set up webpack-serve".
For simplicity, I also uploaded the full example to Github

How to point assets and html angular templates folder after building angularjs with webpack?

i am new to webpack, i have configured webpack to a working condition where my index.html file and budle.js file comes to /dist folder. iam aware that i can build css files too but for now i want to build js and run the app. please check the attached images for better understanding of the directory structure and the webpack build configuration.
My doubt is that if i run app from dist folder i would lose all the path of angular templates and image paths etc. how can i overcome this situation? any help is appreciated.
First of all, you need to know that the goal is to have a fully runnable stand alone application inside ./dist/ after build. All sourcefiles which are needed to run your application should be placed there. In that way you will be able to distribute your application by copy/upload/or-what-ever based on your ./dist/ directory. All other directories in your project are just for development. Those will be not a part of your distribution package.
Wrong approach: Trying to change the include path's in your application.
You need to copy or concat your sourcefiles (static files) into your distribution folder. I realy don't know why your views/templates are not stored in ./app/assets/ and not in ./app/views/ because ./app/views/ should be the correct path to store your views. Well, you need to copy your static sourcefiles. For example: You could use copy-webpack-plugin.
Your webpack config could look like this in the end:
var CopyWebpackPlugin = require('copy-webpack-plugin');
var path = require('path');
module.exports = {
context: path.join(__dirname, 'app'),
devServer: {
// This is required for older versions of webpack-dev-server
// if you use absolute 'to' paths. The path should be an
// absolute path to your build destination.
outputPath: path.join(__dirname, 'dist')
},
plugins: [
new CopyWebpackPlugin([
{
from: 'assets/**/*',
to: 'assets/'
},
{
from: 'views/**/*',
to: 'views/'
},
], {
ignore: [
],
// By default, we only copy modified files during
// a watch or webpack-dev-server build. Setting this
// to `true` copies all files.
copyUnmodified: true
})
]
};

Resources