I'm developing a plugin feature in my next.js react app in which I have to dynamically import components from a bunch of given modules.
component.tsx :
const MyComponent = (props) => {
useEffect(() => {
const pluginNames = ['test-component'];
pluginNames.forEach(async (name) => {
try {
const plugin = await import(name);
} catch(err) {
// I don't want an unvalid plugin to crash my app
console.warn(err);
}
});
}, []);
// returns any html template
}
but when I run my code, I get the following error:
it seem to clearly indicate that the plugin is not found despite it's installed.
From what I understood, it happens because webpack doesn't pack up the dynamically imported plugins in my webpack config. Is there a way to tell webpack to include the specified modules (considering they are fixed since the startup of the app).
Possible solutions:
create a js file importing all modules and tell webpack to inject it into the bundle page
configure webpack so it adds the required modules
Related
Given the following directory structure, is it possible to have ALL react imports resolve to react-b?
|__node_modules
| |__react-a
|
|__app-a
| |__component-a
|
|__next-app
| |__react-b
| |__component-b
// component-a
import { useEffect } from 'react' // I need this to resolve to next-app/node_modules/react
export function() {
useEffect(() => {} , [])
return <></>
}
// component-b
import ComponentA from "../app-a/component-a"
export function() {
return <ComponentA />
}
The issue I am having is that we are migrating to a Next.JS app (next-app) but we want to continue to import components from (app-a). app-a is stuck for now on react 17.x.x but Next.JS is using 18.x.x. So when next-app is built, I need all react imports to resolve to react 18.x.x. At the time of writing this post we are using the experimental.externalDir setting to allow for importing components from outside the root of the next.js app.
The crux of it is that when importing from app-a I still need react to resolve to next-app/node_modules/react.
Webpack aliases seem to be the recommended answer generally but they don't appear to apply correctly in this situation.
I have solved this specific problem by using the next config transpilePackages list. Some dependencies that are dependent on react are causing the react version mismatch by importing react from the root node_modules. By including these packages in the transpilePackages list in the next config, it seems that next is pre-compiling these libs using the correct react version.
Example:
// next.config.js
const nextConfig = {
...other_config,
transpilePackages: ["react-focus-lock"],
}
Unfortunately I haven't fully appreciated why this imports the correct react dependency while using webpack resolve aliases does not.
I have a problem in a Storybook addon I created.
CURRENT SCENARIO
The issue occurs in the Storybook addon environment when a random function is imported (let's call it getSomeData) from a file (let's call it lib.ts) that contains other functions who require .md files with dynamic paths (for example a README.md file)
lib.ts
const getSomeData = () => {
...
return ...
}
export const getPackageReadme = (packageName: string) => {
try {
return require(`#namespace/${packageName}/README.md`)
} catch {
return null
}
}
These dynamic requires that import these .md files as README.md are executed in compile time by Storybook engine.
The weird fact is that in the log of Storybook it seems that this function is executed and read all .md files with the exact name README.md living everywhere in the current repository, also inside node_modules, giving an exact amount of errors as the amount of all found README.md files in the current repository. And consequence, Storybook build fails.
While this error occurs in compiling time of Storybook, at runtime in the browser any issue occurs and everything work as expected.
CURRENT SOLUTION
The current solution -that smells of a hook- to fix the issue was to split this lib.ts in different files (for examples lib-md.ts and lib-data.ts).
In this way, the Storybook addon includes just lib-data.ts and in this way any README.md file is required in compile time, so build succeeds.
lib-data.ts
const getSomeData = () => {
...
return ...
}
lib-md.ts
export const getPackageReadme = (packageName: string) => {
try {
return require(`#namespace/${packageName}/README.md`)
} catch {
return null
}
}
Is there a solution to prevent this error?
How can I programmatically render a react app in gulp and node 12?
I taking over and upgrading an old react (0.12.0) app to latest. This also involved upgrading to ES6. The react code itself is done, but we also need to prerender the application (The app is an interactive documentation and must be crawled by search engines).
Previously, the gulp build process ran browserify on the code and then ran it with vm.runInContext:
// source code for the bundle
const component = path.resolve(SRC_DIR + subDir, relComponent);
vm.runInNewContext(
fs.readFileSync(BUILD_DIR + 'bundle.js') + // ugly
'\nrequire("react").renderToString(' +
'require("react").createElement(require(component)))',
{
global: {
React: React,
Immutable: Immutable,
},
window: {},
component: component,
console: console,
}
);
I am suprised it worked before, but it really did. But now it fails, because the source uses ES6.
I looked for pre-made solutions, but they seem all targeting old react versions, where react-tools was still around.
I packaged the special server-side script below with browserify & babel and then ran it using runInNewContext. It does not fail but also not output any code, it just logs an empty object
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
I found tons of articles about "server-side rendering", but they all seem to be about rendering with express and use the same lines as the script above. I can't run that code directly in gulp, as it does not play well with ES6 imports, which are only available after node 14 (and are experimental).
I failed to show the gulp-browserify task, which was rendering the app component directly, instead of the server-side entrypoint script above. In case anyone ever needs to do this, here is a working solution.
Using vm.runInNewContext allows us to define a synthetic browser context, which require does not. This is important if you access window anywhere in the app.
src/server.js:
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
global.output = content;
above script serves as entry point to browserify. Gulp task to compile:
function gulpJS() {
const sourcePath = path.join(SRC_DIR, 'src/server.js');
return browserify(sourcePath, { debug:true })
.transform('babelify', {
presets: [
["#babel/preset-env", { targets: "> 0.25%, not dead" }],
"#babel/preset-react",
],
})
.bundle()
.pipe(source('server_output.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('.'))
.pipe(dest(BUILD_DIR));
}
The generated file can now be used by later tasks, e.g. to insert the rendered content into a HTML file.
const componentContent = fs.readFileSync(path.join(BUILD_DIR, 'server.js'));
const context = {
global: {
React: React,
Immutable: Immutable,
data: {
Immutable
},
},
window: {
addEventListener() { /* fake */ },
removeEventListener() { /* fake */ },
},
console,
};
vm.runInNewContext(componentContent, context);
const result = context.global.output;
I have a Gatsby site with a React component called ArticleBody that uses react-markdown to convert an article written in Markdown to a React tree.
As this is a bit of an expensive operation and a somewhat large component — and for SEO reasons — I'd like to pre-render ArticleBody at build time. However, I'd also like to load ArticleBody asynchronously in the client. Since the article body will already be included in the HTML, there's no rush to load and render the Markdown component in the client, so async should be fine.
How would I accomplish this? It's almost as if I want to have two different JS bundles — one bundle that loads ArticleBody synchronously, for the build, and one that loads it asynchronously, for the client. Is this possible in Gatsby?
Thanks!
Instead of React.lazy which is not supported, you can use loadable components. There is a Gatsby plugin to handle SSR correctly gatsby-plugin-loadable-components-ssr
Currently there is an issue with it since Gatsby 3.x, but there is a way to implement it yourself without the extra plugin. See the comment in the issue here. Also add the changes mentioned in the comment below of it.
I haven't tried this specific implementation yet, but it should work with the following steps:
npm install --save-dev #loadable/babel-plugin #loadable/server #loadable/webpack-plugin #loadable/component
gatsby-browser.js
import { loadableReady } from '#loadable/component'
import { hydrate } from 'react-dom'
export const replaceHydrateFunction = () => (element, container, callback) => {
loadableReady(() => {
hydrate(element, container, callback)
})
}
gatsby-node.js
exports.onCreateWebpackConfig = ({ actions, stage }) => {
if (
stage === "build-javascript" ||
stage === "develop" ||
stage === "develop-html"
) {
actions.setWebpackConfig({
plugins: [
new LoadablePlugin({
filename:
stage === "develop"
? `public/loadable-stats.json`
: "loadable-stats.json",
writeToDisk: true
})
]
});
}
};
gatsby-ssr.js
import { ChunkExtractor } from '#loadable/server'
import path from 'path'
const extractor = new ChunkExtractor({
// Read the stats file generated by webpack loadable plugin.
statsFile: path.resolve('./public/loadable-stats.json'),
entrypoints: [],
})
// extractor.collectChunks() will wrap the application in a ChunkExtractorManager
export const wrapRootElement = ({ element }) =>
extractor.collectChunks(element)
export const onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => {
// Set link rel="preload" tags in the head to start the request asap. This will NOT parse the assets fetched
setHeadComponents(extractor.getLinkElements())
// Set script and style tags at the end of the document to parse the assets.
setPostBodyComponents([...extractor.getScriptElements(), ...extractor.getStyleElements()])
// Reset collected chunks after each page is rendered
extractor.chunks = []
}
If you have DEV_SSR enabled, you should not add stage === "develop-html". Otherwise, you are good.
Hope this summary of the issue's comments help to get you started.
So I was trying to export a directory with webpack and came up with this script that imports recursively all the files that I need:
// lib1
const req = require.context('./', true, /^(?!.*test.js)^(?!.*Spec.js)^(?!.*__mocks__.*\.js)((.*\.(js\.*))[^.]*$)/)
const modules = {}
req.keys().forEach(key => {
modules[key] = req(key)
})
export default modules
That works fine, I have all modules available to me on subsequent bundles of webpack but it's not exactly what I wanted.
What I really need is a way to export those modules in a way that I can import them using paths instead of grabbing this module object and accessing services via it.
Note:
Apps have ^lib as external module.
webpack#4.1.1
// ex1 what I need:
import theme from 'lib1/services/theme'
// ex2 what I have:
import lib1 from 'lib1'
const theme = lib1.services.theme
Is there a way to achieve this?