Open ksweetie opened 3 years ago
I'd be interested in seeing the popularity of vite or other packaging tools as I've not used it before. I'd also be interested in making sure one maintainer uses vite primarily so that the points brought up by @ElMassimo are kept in mind when working on it. The point about using glob for context is something that I'm personally unaware of purely due to unfamiliarity for instance.
So this is a +1 if it's popular or upcoming and we have someone who is qualified to maintain this gem that would use vite regularly.
In development mode Vite seem to use esbuild
". It is possible to import components for esbuild
using plugin similar to https://github.com/rails/jsbundling-rails/compare/main...xiaohui-zhangxh:main (note how Stimulus controllers are imported by @xiaohui-zhangxh)
I tried this plugin (/components
path and .jsx
hardcoded)
const componentPath = (module) => module.replace('./components/', '').replace('.jsx', '')
const glob = require('glob').sync
// thanks: https://github.com/thomaschaaf/esbuild-plugin-import-glob
// thanks: https://github.com/rails/jsbundling-rails/compare/main...xiaohui-zhangxh:main
const ImportGlobPlugin = () => ({
name: 'require-context',
setup: (build) => {
build.onResolve({ filter: /\*/ }, async (args) => {
if (args.resolveDir === '') {
return; // Ignore unresolvable paths
}
return {
path: args.path,
namespace: 'import-glob',
pluginData: {
resolveDir: args.resolveDir,
},
};
});
build.onLoad({ filter: /.*/, namespace: 'import-glob' }, async (args) => {
const files = (
glob(args.path, {
cwd: args.pluginData.resolveDir,
})
).sort();
let importerCode = `
${files
.map((module, index) => {
return `import * as module${index} from '${module}'`})
.join(';')}
export default [${files
.map((module, index) => `module${index}.default`)
.join(',')}];
export const context = {
${files.map((module, index) => `'${componentPath(module)}': module${index}.default`).join(',')}
}
`;
return { contents: importerCode, resolveDir: args.pluginData.resolveDir };
});
},
});
Then in entrypoint this makes my components globally visible to react-rails
import { context } from './components/**/*.jsx';
Object.keys(context).forEach((key) => {
window[key] = context[key]
})
Couldn't fix this using @pustomytnyk solution as it errors out because require
is not defined.
This seems to work for me.
var context = import.meta.globEager('../components/*.{js,jsx}');
Object.keys(context).forEach((path) => {
let component = context[path].default;
`import * as ${ component.name } from '${ path }'`;
window[component.name] = component;
});
@pacMakaveli where that should be located? entrypoint? Thanks
@pacMakaveli where that should be located? entrypoint? Thanks
I've put it in my main application.js
. app/packs/entrypoints/application.js
Did anyone manage to implement vite + SSR successfully?
@Alxzu Yes, although not in the context of react-rails
. See this example with Inertia.js and React.
Can i help in fixing this issue?
Hi!
I found a possible solution (for Vite Ruby) on this line number https://github.com/reactjs/react-rails/blob/master/react_ujs/index.js#L79 I saw that and understood how it was made the constructor so, I take the code and it change a little bit and I do the following approach:
// app/javascript/helpers/viteConstructorRequireContext.js
export const viteConstructorRequireContext = function(reqCtx) {
const fromRequireContext = function(reqCtx) {
return function(className) {
var parts = className.split(".");
var filename = parts.shift();
var keys = parts;
// Load the module:
var componentPath = Object.keys(reqCtx).find((path => path.search(filename) > 0));
var component = reqCtx[componentPath];
// Then access each key:
keys.forEach(function(k) {
component = component[k];
});
component = component.default;
return component;
}
}
const fromCtx = fromRequireContext(reqCtx);
return function(className) {
var component;
try {
// `require` will raise an error if this className isn't found:
component = fromCtx(className);
} catch (firstErr) {
console.error(firstErr);
}
return component;
}
}
// app/javascript/entrypoints/application.jsx
import ReactRailsUJS from "react_ujs";
import { viteConstructorRequireContext } from "../helpers/viteGetConstructor";
const componentsRequireContext = import.meta.globEager("~/components/main/**/*.{js,jsx}");
ReactRailsUJS.getConstructor = viteConstructorRequireContext(componentsRequireContext);
I expect that this approach it works for you.
@memoxmrdl That's awesome! I got the non-SSR pages working.
Is it going to work with SSR? I tried but nothing was displayed with no errors.
Actually I found the exception:
=> #<React::ServerRendering::PrerenderError: Encountered error "#<ExecJS::ProgramError: TypeError: Cannot read properties of undefined (reading 'serverRender')>" when prerendering HelloWorld with {"name":"World"}
eval (eval at <anonymous> ((execjs):36:8), <anonymous>:6:45)
eval (eval at <anonymous> ((execjs):36:8), <anonymous>:18:13)
(execjs):36:8
(execjs):54:14
(execjs):1:40
Object.<anonymous> ((execjs):1:58)
Module._compile (node:internal/modules/cjs/loader:1103:14)
Object.Module._extensions..js (node:internal/modules/cjs/loader:1155:10)
Module.load (node:internal/modules/cjs/loader:981:32)
Function.Module._load (node:internal/modules/cjs/loader:822:12)
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/execjs-2.8.1/lib/execjs/external_runtime.rb:39:in `exec'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/execjs-2.8.1/lib/execjs/external_runtime.rb:21:in `eval'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/bundler/gems/react-rails-7814b829e645/lib/react/server_rendering/exec_js_renderer.rb:39:in `render_from_parts'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/bundler/gems/react-rails-7814b829e645/lib/react/server_rendering/exec_js_renderer.rb:20:in `render'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/bundler/gems/react-rails-7814b829e645/lib/react/server_rendering/bundle_renderer.rb:40:in `render'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/bundler/gems/react-rails-7814b829e645/lib/react/server_rendering.rb:27:in `block in render'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/connection_pool-2.4.1/lib/connection_pool.rb:110:in `block (2 levels) in with'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/connection_pool-2.4.1/lib/connection_pool.rb:109:in `handle_interrupt'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/connection_pool-2.4.1/lib/connection_pool.rb:109:in `block in with'
/home/paul/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/connection_pool-2.4.1/lib/connection_pool[2] pry(#<React::Rails::ComponentMount>)>
Hi!
I found a possible solution (for Vite Ruby) on this line number https://github.com/reactjs/react-rails/blob/master/react_ujs/index.js#L79 I saw that and understood how it was made the constructor so, I take the code and it change a little bit and I do the following approach:
Thanks for posting this memoxmrdl, it was super helpful.
I found that if you have overlapping component names, then there is a chance it will return the wrong component. Eg searching for NewForm
when you have components named NewForm
and SpecialNewForm
, you might get SpecialNewForm
.
Our components tend to be named directory/Name.js[x]
or directory/Name/index.js[x]
so I've tweaked the code a little to work for our specific use case, and shared it here in case it is useful to anyone else. This isn't 100% perfect, but works for our use case and naming style.
We don't use A.B
to reference components, so I've removed the className splitting code.
Note that this won't work on windows as it assumes /
for the path separator.
// Based on https://github.com/reactjs/react-rails/issues/1134#issuecomment-1415112288
export const viteConstructorRequireContext = function(reqCtx) {
const componentNameMatcher = className => {
return path => {
return (
path.includes(`/${className}.js`) || path.includes(`/${className}/index.js`)
);
};
};
const fromRequireContext = function(reqCtx) {
return function(className) {
const componentPath = Object.keys(reqCtx).find(componentNameMatcher(className));
const component = reqCtx[componentPath];
return component.default;
}
}
const fromCtx = fromRequireContext(reqCtx);
return function(className) {
var component;
try {
// `require` will raise an error if this className isn't found:
component = fromCtx(className);
} catch (firstErr) {
console.error(firstErr);
}
return component;
}
}
FWIW, I'm considering supporting Vite with https://github.com/shakacode/react_on_rails, including SSR. If anybody is interested in that, reply here, and consider our Slack Channel.
I suspect that Vite might work very easily with https://www.shakacode.com/react-on-rails-pro/, which is an easy migration from react-rails.
This is a feature suggestion, and I imagine it would be a ton of work, so I mainly just wanted to start a thread for discussion.
React-Rails already supports both Sprockets and Webpack. Adding support for Vite would be great. There's a relatively new vite_ruby gem, and the author there commented on the feasibility of using it with react-rails. In my case, the app I wanted to try it with uses SSR, so I didn't get very far in testing it out.
Any thoughts?