In an "old way" of managing modules in Node.JS (CommonJS modules) you can do something like this:
Example of Express.js route: app.use('/user', require("./user"));
How to do this when I am using ES6 Modules (import, export) and transcribing by Node.JS server by babel?
I can't just do: app.use('/user', import {user} from './user');
Try separating it out into multiple expressions - import (as well as export) are not available at the same lexical level as you are trying to use it the example:
import { user } from './user'
...
app.use('/user', user)
There is a way to do dynamic inline imports in node, detailed here:
https://javascript.info/modules-dynamic-imports
This code has worked for me:
let {default: foo} = await import('./foo.js');
Here is a working example of a function I wrote as part of a db migration tool in node.
const getMigrations = async (path) => {
const migrateDir = await fs.readdirSync(path);
const migrations = await Promise.all(migrateDir.map(async (filename) => {
let {default: migration} = await import(`${path}/${filename}`);
return migration;
}));
migrations.sort((a, b) => {
return a.seq - b.seq;
});
return migrations;
};
Where an example migration is like:
export default {
seq: 1.1,
label: 'create user table',
sql: `
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
...
);
`
};
I am using node v12.18.4 with "type": "module" in my package.json. When I run the script, I get a warning that the ESM module loader is experimental, but it works. However, there is a note on the page linked to above that says:
Dynamic imports work in regular scripts, they don’t require script type="module".
I hope this helps. I believe you should be able to apply this to your problem.
Related
I'm trying to make an improvement to an existing Gatsby plug-in, and I want to pass a React Component to the plug-in, through its configuration entry in gatsby-config.js:
plugins: [
{
resolve: `gatsby-plugin-modal-routing`,
options: { someComponent: SomeComponentClassOrFunction }
},
However, the problem I'm running into is that I can't figure out how to make it work.
If I try to pass the component itself as part of the plug-in's configuration, it seems to get serialized to/from JSON, resulting in the class becoming a useless object. So it seems I have to pass a path string instead.
plugins: [
{
resolve: `gatsby-plugin-modal-routing`,
options: {
modalComponentPath: path.join(__dirname, 'src/components/SomeComponent.js')
}
},
However, if I try to pass the path instead, I can't figure out how to use it to load the component inside the plug-in. I've tried using a dynamic Node import (ie. import(path).then(component => ...)) ...
with a path that's path.join-ed with __dirname
with a relative path (src/components/SomeComponent)
with a local-path-relative path (./src/components/SomeComponent)
with and without a trailing .js
I'm not sure if this is some sort of issue with the different paths of the app vs. the plug-in or whether there's some other problem, but using import seems like an un-Gatsby-like solution anyway.
So, then I discovered the loadPage and loadPageSync functions which are passed into the plug-in ... but those failed also. Every path I try results in component coming back ... but it's a "page not found" component (presumably because the component I'm trying to pass in hasn't been added as a page).
This seems like it should be a simple question, at least to anyone who has worked on Gatsby plug-ins before: if you want a plug-in to take a component as an input (either as a function/class or as a string of a path to a module) ... how can you actually use that component in your plug-in?
All I'm looking for is a basic pattern or reference to a line in an existing Gatsby plugin that takes a component, or something simple like that (I can look up any details).
This seems like it should be a simple question
I had the same thought while trying this out myself. Oh boy.
TL:DR
// gatsby-node.js
const { DefinePlugin } = require('webpack')
const path = require('path')
exports.onCreateWebpackConfig = ({ actions }, { componentPath }) => {
actions.setWebpackConfig({
plugins: [
new DefinePlugin({
'___COMPONENT___': JSON.stringify(componentPath)
})
]
})
}
// gatsby-ssr
export const onRenderBody = ({ setPreBodyComponents }) => {
const Component = require(___COMPONENT___).default
setPreBodyComponents([<Component />])
}
Long read
Gatsby config doesn't seem to pass functions around (I could have sworn it used to), so passing a React component directly to your custom plugin is out the window. It has to be a path to your component.
// gatsby-config.js
{
resolve: 'my-custom-plugin',
options: {
componentPath: path.join(__dirname, './my-component.js')
}
}
You didn't say if you're using the component in gatsby-node or gatsby-browser/ssr, but I assume it's the later since requiring stuff dynamically in Node is dead simple:
Gatsby Node
// gatsby-node.js
function consume(component) {
const Component = require(component)
}
...although it doesn't understand JSX or ESM, but that's a different problem.
Gatsby Browser
gatsby-browser/ssr is run with webpack, so the module format is not a problem. But import(componentPath) won't work:
Dynamic expressions in import()
It is not possible to use a fully dynamic import statement, such as import(foo). Because foo could potentially be any path to any file in your system or project.
webpack doc
Ok, I suppose so something like this should work:
// gatsby-browser
import('./my-dir' + componentPath)
Nope, because webpack will try to resolve this from wherever the plugin live, i.e node_modules or plugins directory & we're not about to ask our users to put their custom components in node_modules.
What about this, then?
// gatsby-browser
import(process.cwd() + componentPath) // nope
We're right back at the beginning — webpack doesn't like full dynamic path! And also even if this works, this is a terrible idea since webpack will try to bundle the whole working directory.
Only if we could encode the path as a static string beforehand, so webpack can just read that code — like using webpack.DefinePlugin to define environment variables. Fortunately we can do that in gatsby-node.js:
// gatsby-node.js
const { DefinePlugin } = require('webpack')
const path = require('path')
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
plugins: [
new DefinePlugin({
'___CURRENT_DIR___': JSON.stringify(process.cwd())
})
]
})
}
And finally
// gatsby-browser
// eslint throw error for unknown var, so disable it
// eslint-disable-next-line
import(___CURRENT_DIR___ + componentPath) // works, but don't do this
But since we can access user options right in gatsby-node, let's just encode the whole path:
// gatsby-node.js
const { DefinePlugin } = require('webpack')
- const path = require('path')
- exports.onCreateWebpackConfig = ({ actions }) => {
+ exports.onCreateWebpackConfig = ({ actions }, { componentPath }) => {
actions.setWebpackConfig({
plugins: [
new DefinePlugin({
- '___CURRENT_DIR___': JSON.stringify(process.cwd())
+ '___COMPONENT___': JSON.stringify(componentPath)
})
]
})
}
Back in gatsby-browser.js:
// gatsby-browser
// I pick a random API to test, can't imagine why one would import a module in this API
export const onRouteUpdate = async () => {
// eslint-disable-next-line
const { default: Component } = await import(___COMPONENT___)
console.log(Component) // works
}
Gatsby SSR
For the sake of completeness, let's try the same trick in gatby-ssr:
// gatsby-ssr
export const onRenderBody = async ({ setPreBodyComponents }) => {
// const Component = require(___COMPONENT___).default
const { default: Component } = await import(___COMPONENT___)
setPreBodyComponents([<Component />])
}
...and it failed.
Why? If one's curious enough they might go and dig around Gatsby code to see how gatsby-ssr is treated differently than gatsby-browser, but alas I just don't feel like doing that.
Fear not, we still have one trick up our sleeve. Webpack's require can import module dynamically too, though not asynchronously. Since gatsby-ssr doesn't run in the browser, I couldn't care less about asynchronicity.
export const onRenderBody = ({ setPreBodyComponents }) => {
const Component = require(___COMPONENT___).default
setPreBodyComponents([<Component />]) // works
}
And now it works.
Sharing code between gatsby-ssr & gatsby-browser
Let's just say we need this component in both gatsby-ssr and gatsby-browser — would require(...) works in gatsby-browser too?
export const onRouteUpdate = async () => {
// eslint-disable-next-line
const { default: Component } = require(___COMPONENT___)
console.log(Component) // yes
}
It works.
import(..) vs require()
While import() does load stuff dynamically, it is more of a tool for code-splitting. Here's some different, other than asynchronicity:
using import('./my-dir' + componentPath) will bundle all files inside ./my-dir into a chunk. There's magic comment we can use to exclude/include stuff.
require(...) will just inline the required component into whatever chunk's calling it.
In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}
I am working on a Express/NodeJs project. I am new to Express/NodeJs, I am trying to import airportQuery.js into DistanceFormula.js. I am trying to directly import airportQuery.js from DistanceFormula.js. Im trying to call getAirports and return the answer to DistanceFormula.js. I not sure if I have to use the node routing or if i'm doing it correctly.
File Stucture:
File Structure
DistanceFormula.JS
import {getAirports} from "./api/airportQuery";
console.log(getAirports('3c675a'));
AirportQuery.js
async function getAirports(planeIcao) {
let airport = {
arrival: "",
destination: ""
};
const airport_url = 'https://opensky-network.org/api/flights/aircraft?icao24=' + planeIcao + '&begin=1517184000&end=1517270400';
const response = await fetch(airport_url);
const data = await response.json();
console.log(data[0]);
console.log(data[0].estArrivalAirport);
airport.arrival = data[0].estArrivalAirport;
console.log(data[0].estDepartureAirport);
airport.destination = data[0].estDepartureAirport;
return airport
}
const fetch = require("node-fetch");
export {getAirports};
ERROR: Uncaught SyntaxError: Cannot use import statement outside a module
To use modules in node.js, you have to do the following:
Be running a version of nodejs that supports ESM modules (v8.5+).
Run with this command line flag: node --experimental-modules
Name your file with an .mjs file extension OR specify it as a module in package.json
See the relevant documentation for more info.
This is true not only for the top level file you import, but if it also uses import, then the same rules above have to apply to it too.
Note, that once you get your modules to load properly, you will then have a problem with this line of code because getAirports() returns promise, not a value. All async functions return a promise, always. The return value in the function will become the resolved value of the returned promise. That's how async functions work. So, change this:
console.log(getAirports('3c675a'));
To this:
getAirports('3c675a').then(result=> {
console.log(result);
}).catch(err => {
console.log(err);
});
I transpile my ES6 code using Webpack along with BabelJS in NodeJS environment. The problem is that, when I try to import a set of specific .js modules, the MODULE_NOT_FOUND exception would be thrown. This is the main chunk of code I've written so far:
export default async () => {
const modulesToBeImportedByNames = ['a.js', 'b.js', 'c.js'];
const modulesToBeImportedByPromises =
modulesToBeImportedByNames.map(moduleFilename =>
import(`./${moduleFilename}`) // exception is thrown here
);
const importedModules = await Promise.all(modulesToBeImportedByPromises);
}
Note: I've used #babel/plugin-syntax-dynamic-import plugin within my BabelJS configuration.
Looks like the filenames should be relative, as you are importing application files.
const modulesToBeImportedByNames = ['./a.js', './b.js', './c.js'];
I'am working on app with React, TS and Webpack stack.
I need to implement feature that allows my app work with client plugins - js files that override existing functionality of some classes. It can be loaded from anywhere - local file system or remote repository and should be fetched in the runtime, because i need to have an option to specify new extension in config and just press F5.
Dynamic import is not my case, because as far as i understand Webpack needs to be able to at least guess roughly what an import() is meant to be referencing. Using simple 'get' request might be an option, but how can i use loaded script as CommonJS module in this case? And am i correct about dynamic import behavior?
You can use #paciolan/remote-module-loader to remotely load a common js module.
import { createLoadRemoteModule } from "#paciolan/remote-module-loader"
const main = async() => {
const loadRemoteModule = createLoadRemoteModule()
const myModule = await loadRemoteModule("http://fake.url/modules/my-module.js")
const value = myModule.default()
console.log({ value })
}
main()
If you need to pass dependencies to the module:
import {
createLoadRemoteModule,
createRequires
} from "#paciolan/remote-module-loader"
const dependencies = {
react: require("react")
}
const main = async() => {
const requires = createRequires(dependencies)
const loadRemoteModule = createLoadRemoteModule({ requires })
const myModule = await loadRemoteModule("http://fake.url/modules/my-module.js")
const value = myModule.default()
console.log({ value })
}
main()
If need to load a React Component, check out #paciolan/remote-component
You may have to take extra steps if you have a Content Security Policy (CSP) set.