I know this sounds as easy as using globals, but I'm not so sure.
Here's my case:
I have multiple files within /src directory of my React app, let's call them src/a.js, src/b.js,
every single of these files exports one object which I then use within my app:
./src/a.js:
export default {
filename: 'a',
foo: 'bar',
};
./src/b.js:
export default {
filename: 'b',
foo: 'bar',
blah: 'hah',
};
Now I have a command to check whether or not structure of objects within these files match (they are being changed by many developers multiple times a day), so when I do npm check in terminal it will return false for above input, because blah does not exist within two files.
My package.json looks like this:
"scripts": {
"check": "node check.js runCheck",
/.../
}
My question is: how the heck do I load these variables to compare them in package.json?
I have a file called:
./check.js:
function check(files) {
// checking files there
};
module.exports.check = check;
Approach #1 - imports
This is a build file, not part of the application itself, so when I try to do:
./check.js:
import a from './src/a';
import b from './src/b';
I'm getting:
SyntaxError: Cannot use import statement outside a module.
Approach #2 - require
This is going to cause trouble, because I'm using imports, not modules within my app, therefore doing:
./check.js:
const a = require('./src/a');
const b = require('./src/b');
Returns:
Error: Cannot find module './src/a'.
Of course I can't do module.exports within the a.js/b.js files, because they're part of my app and they should use exports, I've tried using both export and module.exports, but it does not work as well and looks shitty.
How do I tackle this? Should I load the files using some file loader, parse it as JSON an then compare? Or maybe there's an easier way?
You'll need to use something like esm (https://github.com/standard-things/esm) to run node with module support.
It should be as simple as:
npm install esm
Then update your package script to be:
"check": "node -r esm check.js runCheck",
Edit Btw, a very clear and well structured question.
I'm in the process of building an npm package which will be installed globally. Is it possible to have non-code files installed alongside code files that can be referenced from code files?
For example, if my package includes someTextFile.txt and a module.js file (and my package.json includes "bin": {"someCommand":"./module.js"}) can I read the contents of someTextFile.txt into memory in module.js? How would I do that?
The following is an example of a module that loads the contents of a file (string) into the global scope.
core.js : the main module file (entry point of package.json)
//:Understanding: module.exports
module.exports = {
reload:(cb)=>{ console.log("[>] Magick reloading to memory"); ReadSpellBook(cb)}
}
//:Understanding: global object
//the following function is only accesible by the magick module
const ReadSpellBook=(cb)=>{
require('fs').readFile(__dirname+"/spellBook.txt","utf8",(e,theSpells)=>{
if(e){ console.log("[!] The Spell Book is MISSING!\n"); cb(e)}
else{
console.log("[*] Reading Spell Book")
//since we want to make the contents of .txt accesible :
global.SpellBook = theSpells // global.SpellBook is now shared accross all the code (global scope)
cb()//callBack
}
})
}
//·: Initialize :.
console.log("[+] Time for some Magick!")
ReadSpellBook((e)=>e?console.log(e):console.log(SpellBook))
spellBook.txt
ᚠ ᚡ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ ᚮ ᚯ
ᚰ ᚱ ᚲ ᚳ ᚴ ᚵ ᚶ ᚷ ᚸ ᚹ ᚺ ᚻ ᚼ ᚽ ᚾ ᚿ
ᛀ ᛁ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ ᛈ ᛉ ᛊ ᛋ ᛌ ᛍ ᛎ ᛏ
ᛐ ᛑ ᛒ ᛓ ᛔ ᛕ ᛖ ᛗ ᛘ ᛙ ᛚ ᛛ ᛜ ᛝ ᛞ ᛟ
ᛠ ᛡ ᛢ ᛣ ᛤ ᛥ ᛦ ᛧ ᛨ ᛩ ᛪ ᛫ ᛬ ᛭ ᛮ ᛯ
If you require it from another piece of code, you will see how it prints to the console and initializes by itself.
If you want to achieve a manual initalization, simply remove the 3 last lines (·: Initialize :.) and use reload() :
const magick = require("core.js")
magick.reload((error)=>{ if(error){throw error}else{
//now you know the SpellBook is loaded
console.log(SpellBook.length)
})
I have built some CLIs which were distributed privately, so I believe I can illuminate a bit here.
Let's say your global modules are installed at a directory called $PATH. When your package will be installed on any machine, it will essentially be extracted at that directory.
When you'll fire up someCommand from any terminal, the module.js will be invoked which was kept at $PATH. If you initially kept the template file in the same directory as your package, then it will be present at that location which is local to module.js.
Assuming you edit the template as a string and then want to write it locally to where the user wished / pwd, you just have to use process.cwd() to get the path to that directory. This totally depends on how you code it out.
In case you want to explicitly include the files only in the npm package, then use files attribute of package.json.
As to particularly answer "how can my code file in the npm package locate the path to the globally installed npm folder in which it is located in a way that is guaranteed to work across OSes and is future proof?", that is very very different from the template thingy you were trying to achieve. Anyway, what you're simply asking here is the global path of npm modules. As a fail safe option, use the path returned by require.main.filename within your code to keep that as a reference.
When you npm publish, it packages everything in the folder, excluding things noted in .npmignore. (If you don't have an .npmignore file, it'll dig into .gitignore. See https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package) So in short, yes, you can package the text file into your module. Installing the module (locally or globally) will get the text file into place in a way you expect.
How do you find the text file once it's installed? __dirname gives you the path of the current file ... if you ask early enough. See https://nodejs.org/docs/latest/api/globals.html#globals_dirname (If you use __dirname inside a closure, it may be the path of the enclosing function.) For the near-term of "future", this doesn't look like it'll change, and will work as expected in all conditions -- whether the module is installed locally or globally, and whether others depend on the module or it's a direct install.
So let's assume the text file is in the same directory as the currently running script:
var fs = require('fs');
var path = require('path');
var dir = __dirname;
function runIt(cb) {
var fullPath = path.combine(__dirname, 'myfile.txt');
fs.readFile(fullPath, 'utf8' , function (e,content) {
if (e) {
return cb(e);
}
// content now has the contents of the file
cb(content);
}
}
module.exports = runIt;
Sweet!
I am using create-react-app. I am trying to call an image from my public folder from a file inside my src/components. I am receiving this error message.
./src/components/website_index.js Module not found: You attempted to
import ../../public/images/logo/WC-BlackonWhite.jpg which falls
outside of the project src/ directory. Relative imports outside of
src/ are not supported. You can either move it inside src/, or add a
symlink to it from project's node_modules/.
import logo from '../../public/images/logo_2016.png';
<img className="Header-logo" src={logo} alt="Logo" />
I have read many things saying you can do an import to the path but that is still not working for me. Any help would be greatly appreciated. I know there are many questions like this but they are all telling me to import logo or image so clearly I am missing something in the big picture.
This is special restriction added by developers of create-react-app. It is implemented in ModuleScopePlugin to ensure files reside in src/. That plugin ensures that relative imports from app's source directory don't reach outside of it.
There is no official way to disable this feature except using eject and modify webpack config.
But, most features and its updates are hidden into the internals of create-react-app system. If you make eject you will have no more new features and its update. So if you are not ready to manage and configure application included to configure webpack and so on - do not do eject operation.
Play by the existing rules - move assets to src or use based on public folder url without import.
However instead of eject there are much unofficial solutions, based on
rewire which allows you to programmatically modify the webpack config without eject. But removing the ModuleScopePlugin plugin is not good - this loses some protection and does not adds some features available in src. ModuleScopePlugin is designed to support multiple folders.
The better way is to add fully working additional directories similar to src also protected by ModuleScopePlugin. This can be done using react-app-alias
Anyway do not import from public folder - that will be duplicated in the build folder and will be available by two different url (and with different ways to load), which ultimately worsen the package download size.
Importing from the src folder is preferable and has advantages. Everything will be packed by webpack to the bundle with chunks optimal size and for best loading efficiency.
The package react-app-rewired can be used to remove the plugin. This way you do not have to eject.
Follow the steps on the npm package page (install the package and flip the calls in the package.json file) and use a config-overrides.js file similar to this one:
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
module.exports = function override(config, env) {
config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin));
return config;
};
This will remove the ModuleScopePlugin from the used WebPack plugins, but leave the rest as it was and removes the necessity to eject.
Remove it using Craco:
module.exports = {
webpack: {
configure: webpackConfig => {
const scopePluginIndex = webpackConfig.resolve.plugins.findIndex(
({ constructor }) => constructor && constructor.name === 'ModuleScopePlugin'
);
webpackConfig.resolve.plugins.splice(scopePluginIndex, 1);
return webpackConfig;
}
}
};
If your images are in the public folder then you should use
"/images/logo_2016.png"
in your <img> src instead of importing
'../../public/images/logo_2016.png';
This will work
<img className="Header-logo" src="/images/logo_2016.png" alt="Logo" />
To offer a little bit more information to other's answers. You have two options regarding how to deliver the .png file to the user. The file structure should conform to the method you choose. The two options are:
Use the module system (import x from y) provided with react-create-app and bundle it with your JS. Place the image inside the src folder.
Serve it from the public folder and let Node serve the file. create-react-app also apparently comes with an environment variable e.g. <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;. This means you can reference it in your React app but still have it served through Node, with your browser asking for it separately in a normal GET request.
Source: create-react-app
There are a few answers that provide solutions with react-app-rewired, but customize-cra includes a removeModuleScopePlugin() API which is a bit more elegant. (It's the same solution, but abstracted away by the customize-cra package.)
npm i --save-dev react-app-rewired customize-cra
package.json
"scripts": {
- "start": "react-scripts start"
+ "start": "react-app-rewired start",
...
},
config-overrides.js
const { removeModuleScopePlugin } = require('customize-cra')
module.exports = removeModuleScopePlugin()
I was able to import files outside of src/ by "copying" the outside files with file: as local dependency.
"dependencies": {
"#my-project/outside-dist": "file:./../../../../dist".
}
then
import {FooComponent} from "#my-project/outside-dist/components";
No eject or react-app-rewired or other 3rd-party solution was needed.
You need to move WC-BlackonWhite.jpg into your src directory. The public directory is for static files that's going to be linked directly in the HTML (such as the favicon), not stuff that you're going to import directly into your bundle.
install these two packages
npm i --save-dev react-app-rewired customize-cra
package.json
"scripts": {
- "start": "react-scripts start"
+ "start": "react-app-rewired start"
},
config-overrides.js
const { removeModuleScopePlugin } = require('customize-cra');
module.exports = function override(config, env) {
if (!config.plugins) {
config.plugins = [];
}
removeModuleScopePlugin()(config);
return config;
};
I think Lukas Bach solution to use react-app-rewired in order to modify webpack config is a good way to go, however, I wouldn't exclude the whole ModuleScopePlugin but instead whitelist the specific file that can be imported outside of src:
config-overrides.js
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const path = require("path");
module.exports = function override(config) {
config.resolve.plugins.forEach(plugin => {
if (plugin instanceof ModuleScopePlugin) {
plugin.allowedFiles.add(path.resolve("./config.json"));
}
});
return config;
};
Copy-Paste Typescript solution
(e.g. this will work for a CRA/TS stack, which requires an additional step compared to CRA/JS. The solution itself is not typed.)
Adds the required paths to the ModuleScopePlugin instead of bluntly removing the plugin.
This code below is using craco, but should be easily usable for react-app-rewired or similar solutions. You just need to find the spot where you have a webpackConfig object (react-app-rewired: module.exports.webpack inside your config-overrides.js), and pass it to the provided functions.
craco.config.js
const path = require("path");
const enableImportsFromExternalPaths = require("./src/helpers/craco/enableImportsFromExternalPaths");
// Paths to the code you want to use
const sharedLibOne = path.resolve(__dirname, "../shared-lib-1/src");
const sharedLibTwo = path.resolve(__dirname, "../shared-lib-2/src");
module.exports = {
plugins: [
{
plugin: {
overrideWebpackConfig: ({ webpackConfig }) => {
enableImportsFromExternalPaths(webpackConfig, [
// Add the paths here
sharedLibOne,
sharedLibTwo,
]);
return webpackConfig;
},
},
},
],
};
helpers/craco/enableImportsFromExternalPaths.js
const findWebpackPlugin = (webpackConfig, pluginName) =>
webpackConfig.resolve.plugins.find(
({ constructor }) => constructor && constructor.name === pluginName
);
const enableTypescriptImportsFromExternalPaths = (
webpackConfig,
newIncludePaths
) => {
const oneOfRule = webpackConfig.module.rules.find((rule) => rule.oneOf);
if (oneOfRule) {
const tsxRule = oneOfRule.oneOf.find(
(rule) => rule.test && rule.test.toString().includes("tsx")
);
if (tsxRule) {
tsxRule.include = Array.isArray(tsxRule.include)
? [...tsxRule.include, ...newIncludePaths]
: [tsxRule.include, ...newIncludePaths];
}
}
};
const addPathsToModuleScopePlugin = (webpackConfig, paths) => {
const moduleScopePlugin = findWebpackPlugin(
webpackConfig,
"ModuleScopePlugin"
);
if (!moduleScopePlugin) {
throw new Error(
`Expected to find plugin "ModuleScopePlugin", but didn't.`
);
}
moduleScopePlugin.appSrcs = [...moduleScopePlugin.appSrcs, ...paths];
};
const enableImportsFromExternalPaths = (webpackConfig, paths) => {
enableTypescriptImportsFromExternalPaths(webpackConfig, paths);
addPathsToModuleScopePlugin(webpackConfig, paths);
};
module.exports = enableImportsFromExternalPaths;
Taken from here and here 🙏
Image inside public folder
use image inside html extension
<img src="%PUBLIC_URL%/resumepic.png"/>
use image inside js extension
<img src={process.env.PUBLIC_URL+"/resumepic.png"}/>
use image inside js Extension
This restriction makes sure all files or modules (exports) are inside src/ directory, the implementation is in ./node_modules/react-dev-utils/ModuleScopePlugin.js, in following lines of code.
// Resolve the issuer from our appSrc and make sure it's one of our files
// Maybe an indexOf === 0 would be better?
const relative = path.relative(appSrc, request.context.issuer);
// If it's not in src/ or a subdirectory, not our request!
if (relative.startsWith('../') || relative.startsWith('..\\')) {
return callback();
}
You can remove this restriction by
either changing this piece of code (not recommended)
or do eject then remove ModuleScopePlugin.js from the directory.
or comment/remove const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); from ./node_modules/react-scripts/config/webpack.config.dev.js
PS: beware of the consequences of eject.
Adding to Bartek Maciejiczek's answer, this is how it looks with Craco:
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const path = require("path");
module.exports = {
webpack: {
configure: webpackConfig => {
webpackConfig.resolve.plugins.forEach(plugin => {
if (plugin instanceof ModuleScopePlugin) {
plugin.allowedFiles.add(path.resolve("./config.json"));
}
});
return webpackConfig;
}
}
};
My previous workaround worked with Webpack 4, but not with 5. After skimming through the accumulated workarounds since then, I found the following one really easy (and seemingly scalable).
import { CracoAliasPlugin } from 'react-app-alias';
const cracoConfig = {
plugins: [
{
plugin: CracoAliasPlugin,
options: {
alias: { '~': './' },
},
},
],
}
Then import like so:
import whatever from '~/<path-to-file>';
I have had to overcome this same issue in Truffle. The solution was as follows:
ince Create-React-App's default behavior disallows importing files from outside of the src folder, we need to bring the contracts in our build folder inside src. We can copy and paste them every time we compile our contracts, but a better way is to simply configure Truffle to put the files there.
In the truffle-config.js file, replace the contents with the following:
const path = require("path");
module.exports = {
contracts_build_directory: path.join(__dirname, "client/src/contracts")
};
I don't know if this helps you, but I know I found your question when I had the same issue in Truffle, and this might help someone else.
This can be done directly without using the path to the public folder.
You can do it like
<img src="/images/image-name" alt=""/>
This happens because we do not use App.js in the browser. Since index.html is executed in the browser itself and the path to images is already in the public folder containing index.html file
You don't need to eject, you can modify the react-scripts config with the rescripts library
This would work then:
module.exports = config => {
const scopePluginIndex = config.resolve.plugins.findIndex(
({ constructor }) => constructor && constructor.name === "ModuleScopePlugin"
);
config.resolve.plugins.splice(scopePluginIndex, 1);
return config;
};
Came to the same issue in my project, and found this in the official create-react-app docs: https://create-react-app.dev/docs/using-the-public-folder/
There is an "escape hatch" to add an asset outside the module system:
If you put a file into the public folder, it will not be processed by
webpack. Instead it will be copied into the build folder untouched. To
reference assets in the public folder, you need to use an environment
variable called PUBLIC_URL.
Here's an example they provide:
render() {
// Note: this is an escape hatch and should be used sparingly!
// Normally we recommend using `import` for getting asset URLs
// as described in “Adding Images and Fonts” above this section.
return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;
}
This worked for me without installing/ changing anything
Context: I got this error when I tried to generate a build using yarn run build
Things I have done between the working and failing of yarn run build
I updated my ant-design to the latest stable version (v4.23.5).
Note: I highly believe that there is nothing to do with this version. I am just mentioning it to add more details.
This answer solved my issue. But I have changed no imports that access something outside the src directory.
The changes include updated package.json, yarn.lock, new Antd implementations (change in props mainly).
It made no sense why the build command broke/ why the answer is working.
Solution here
As all the changes are related to package.json, yarn.lock. I deleted node_modules and clean installed all the packages.
Run
yarn
or
npm install
If you only need to import a single file, such as README.md or package.json, then this can be explicitly added to ModuleScopePlugin()
config/paths.js
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
appPackageJson: resolveApp('package.json'),
appReadmeMD: resolveApp('README.md'),
};
config/webpack.config.dev.js + config/webpack.config.prod.js
module.exports = {
resolve: {
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
paths.appReadmeMD // README.md lives outside of ./src/ so needs to be explicitly included in ModuleScopePlugin()
]),
]
}
}
the best solution is to fork react-scripts, this is actually mentioned in the official documentation, see: Alternatives to Ejecting
If you need multiple modifications, like when using ant design, you can combine multiple functions like this:
const {
override,
removeModuleScopePlugin,
fixBabelImports,
} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
removeModuleScopePlugin(),
);
You can try using simlinks, but in reverse.
React won't follow simlinks, but you can move something to the source directory, and create a simlink to it.
In the root of my project, I had a node server directory that had several schema files in it. I wanted to use them on the frontend, so I:
moved the files /src
in the termal, I cd'ed into where the schema files belonged in server
ln -s SRC_PATH_OF_SCHEMA_FILE
This gave react what it was looking for, and node was perfectly happy including files through simlinks.
If you want to access CSS files from the public, you might face an error OUTSIDE OF SOURCE DIRECTORY
Alternatively, you can link this file in index.html which also resides in the public directory.
<link rel="stylesheet" href="App.css">
Here's an alternative that works well in simple cases (using fs and ncp). While developing, keep a script running that watches for changes to your shared folder(s) outside of /src. When changes are made, the script can automatically copy the shared folder(s) to your project. Here's an example that watches a single directory recursively:
// This should be run from the root of your project
const fs = require('fs')
const ncp = require('ncp').ncp;
ncp.limit = 16
// Watch for file changes to your shared directory outside of /src
fs.watch('../shared', { recursive: true }, (eventType, filename) => {
console.log(`${eventType}: ${filename}`)
// Copy the shared folder straight to your project /src
// You could be smarter here and only copy the changed file
ncp('../shared', './src/shared', function(err) {
if (err) {
return console.error(err);
}
console.log('finished syncing!');
});
})
This is an issue with the relative import, which might have caused because we've used "create-react-app project" command which forms a directory named project with node_modules folder and several other files in public and src folders inside it.
The create-react-app command puts a limitation that we can't import anything from outside src.
My Problem:
I had to import react-bootstrap css files which are created in node_modules folder outside the src folder.
I used import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; but I got the error on terminal.
I found out that I can create a new react app and follow solution steps from A to G, in order to fix this issue.
Solution:
A) Create a new react app, using create-react-app new
B) cd new
C) run this command: "npm install react-bootstrap bootstrap#4.6.0" (without the "" double quotes )
D) in your react file put this to import bootstrap:
D.1) import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
or
D.2)import Button from "react-bootstrap/Button";
E) create a bootstrap element like a Button or anything in your react file, for D.1) < button className="btn btn-success" > Bootstrap < /button>
or
for D.2) < Button variant="primary"> Bootstrap < /Button>
F) in terminal: cd src
G) in terminal: npm start,
this time it will be compiled successfully.
Reasoning:
I could see react-bootstrap working finally once I followed steps A to G in order, and this time I didn't get any error.
(I thought of this solution because:
I've used npm install "#material-ui/icons" and that got installed in the node_modules folder outside the src.
In my react file I've used import Add from "#material-ui/icons/Add"
and Material-ui icons were working fine for me,
but here also we are importing from outside src, from node_modules.. and everything works fine. Why there is no error of importing from outside src this time)
That's why I just created a new react app, and followed solution steps A to G.
If you want to set a background image using CSS. So you have to set the image using the URL of your's localhost and add the path of your image. Just see the example below.
.banner {
width: 100%;
height: 100vh;
background-image: url("http://localhost:3000/img/bg.jpg");
background-size: cover;
background-repeat: no-repeat;
}
Posting here what #Flaom wrote as a comment in the marked as reply answer and that actually saves lives:
"How is this the accepted answer? This bogus restriction is trivially eliminated by simply setting NODE_PATH=./src/.. in the .env file. By doing so, you can import from outside of the src folder without going through the pain associated with ejecting your app. "
Flaom
EDIT Added some more info as #cigien requested.
All the answers above describe very well why we cannot use an image from the public folder when we create our react app with the create-react-app. Having the issue myself and reading all these answers I realized that, what the answers say is to "hack" the app in order to remove the module that restricts us. Some of the answers don't even have an undo option. For a "training" application that is ok.
Personally I would not want to add a solution that alters the concept of the app to my own project, specially in a commercial one. #Flaom solution is the simplest and if anything change in the future it can be replaced with another solution. It has no risk, it can be removed anytime and is the simplest.
This was my code:
import React from 'react';
import './Navbar.scss';
import {images} from '../../constants';
const Navbar = () => {
return (
<nav>
<div>
< img src = {images.logo} alt = "logo" />
</div>
</nav>
);
}
export default Navbar;
Changed it too:
import React from 'react';
import './Navbar.scss';
import {images} from '././constants';
const Navbar = () => {
return (
<nav>
<div>
< img src = {images.logo} alt = "logo" />
</div>
</nav>
);
}
export default Navbar;
And it worked! Im getting better at fixing bugs haha.
If you file reside in public folder and if you want to import it without eject or without using react-app-rewired then in that case you can access file via domains name and the path of the file and using axios.
Example: There is a font file called favico.ico located inside public folder. You want to import it in one the file located in src. You
can access the font using following logic.
axios.get('example.com/favico.ico').then(() => {
// here you can access this file.
})
In above example example.com is domain. If you have different environment like localhost, staging, production then in that case the domain name is different.
So, to get the favico.ico you can use following logic.
axios.get(`${window.location.origin}/favico.ico`).then(() => {
// here you can access this file.
})
In above example you window.location.origin give you current domain meaning if you run your code locally then, it will give you http://localhost:{portnumber},
If your code run on production and production domain is example.com then, it will give you "example.com". So using this pattern you can access assets located in public folder.
I'm trying to figure out how to perform dynamic import of classes in ES6 one the server side (node.js with Babel).
I would like to have some functionalities similar to what reflection offers in Java. The idea is to import all the classes in a specific folder and instanciate them dynamically.
So for example I could have multiple classes declared in a folder like the one below :
export default class MyClass {
constructor(somevar) {
this._somevar = somevar
}
//...
//some more instance level functions here
}
and then somewhere else in my app's code I could have a function that finds out all the classes in a specific folder and tries to instanciate them :
//somewhere else in my app
instanciationFunction(){
//find all the classes in a specific folder
var classFiles = glob.sync(p + '/path_to_classes/**/*.js', {
nodir: true
});
_.each(classFiles, async function (file) {
console.log(file);
var TheClass = import(file);
var instance = new TheClass();
//and then do whatever I want with that new instance
});
}
I've tried doing it with require but I get errors. Apparently the constructor cant be found.
Any idea would be greatly appreciated.
Thanks
ES module definitions are declarative, and the current direction tools are taking is the path where dependencies are determined during parse (via static analysis), waaay before any of the code is executed. This means dynamic and conditional imports go against the said path. It's not like in Node where imports are determined on execution, upon executing require.
If you want dynamic, runtime imports, consider taking a look at SystemJS. If you're familiar with RequireJS, it takes the same concept, but expands it to multiple module formats, including ES6. It has SystemJS.import which appears to do what you want, plus handles the path resolution that you're currently doing.
Alternatively, if your intention is to shed off excess code, consider using Rollup. It will analyze code for you and only include code that's actually used. That way, you don't need to manually do conditional loading.
You need to preprocess with babel, because they are not yet a part of node (for that matter, neither are static imports - node uses require).
https://github.com/airbnb/babel-plugin-dynamic-import-node
steps:
pre
npm i -D babel-cli or npm i -D babel
1
npm i -D babel-plugin-dynamic-import-node
2
.babelrc
{
"plugins": ["dynamic-import-node"]
}
ready, go!
babel-node test_import.js for babel-cli, or for raw babel:
a
(edit) package.json
"scripts": {
"pretest": "babel test_imports.js -o dist/test_imports.js",
"test": "node dist/test_imports.js"
//...
b
node test
I had the same usecase and i managed to dynamically load and instantiate default exported classes using:
const c = import("theClass.js")
const i = new c.default();
using node v16.4.0
Is it possible to define a global variable with webpack to result something like this:
var myvar = {};
All of the examples I saw were using external file require("imports?$=jquery!./file.js")
There are several way to approach globals:
1. Put your variables in a module.
Webpack evaluates modules only once, so your instance remains global and carries changes through from module to module. So if you create something like a globals.js and export an object of all your globals then you can import './globals' and read/write to these globals. You can import into one module, make changes to the object from a function and import into another module and read those changes in a function. Also remember the order things happen. Webpack will first take all the imports and load them up in order starting in your entry.js. Then it will execute entry.js. So where you read/write to globals is important. Is it from the root scope of a module or in a function called later?
config.js
export default {
FOO: 'bar'
}
somefile.js
import CONFIG from './config.js'
console.log(`FOO: ${CONFIG.FOO}`)
Note: If you want the instance to be new each time, then use an ES6 class. Traditionally in JS you would capitalize classes (as opposed to the lowercase for objects) like
import FooBar from './foo-bar' // <-- Usage: myFooBar = new FooBar()
2. Use Webpack's ProvidePlugin.
Here's how you can do it using Webpack's ProvidePlugin (which makes a module available as a variable in every module and only those modules where you actually use it). This is useful when you don't want to keep typing import Bar from 'foo' again and again. Or you can bring in a package like jQuery or lodash as global here (although you might take a look at Webpack's Externals).
Step 1. Create any module. For example, a global set of utilities would be handy:
utils.js
export function sayHello () {
console.log('hello')
}
Step 2. Alias the module and add to ProvidePlugin:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
// ...
module.exports = {
// ...
resolve: {
extensions: ['', '.js'],
alias: {
'utils': path.resolve(__dirname, './utils') // <-- When you build or restart dev-server, you'll get an error if the path to your utils.js file is incorrect.
}
},
plugins: [
// ...
new webpack.ProvidePlugin({
'utils': 'utils'
})
]
}
Now just call utils.sayHello() in any js file and it should work. Make sure you restart your dev-server if you are using that with Webpack.
Note: Don't forget to tell your linter about the global, so it won't complain. For example, see my answer for ESLint here.
3. Use Webpack's DefinePlugin.
If you just want to use const with string values for your globals, then you can add this plugin to your list of Webpack plugins:
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
Use it like:
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
4. Use the global window object (or Node's global).
window.foo = 'bar' // For SPA's, browser environment.
global.foo = 'bar' // Webpack will automatically convert this to window if your project is targeted for web (default), read more here: https://webpack.js.org/configuration/node/
You'll see this commonly used for polyfills, for example: window.Promise = Bluebird
5. Use a package like dotenv.
(For server side projects) The dotenv package will take a local configuration file (which you could add to your .gitignore if there are any keys/credentials) and adds your configuration variables to Node's process.env object.
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
var db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
Notes
Regarding Webpack's Externals, use it if you want to exclude some modules from being included in your built bundle. Webpack will make the module globally available but won't put it in your bundle. This is handy for big libraries like jQuery (because tree shaking external packages doesn't work in Webpack) where you have these loaded on your page already in separate script tags (perhaps from a CDN).
I was about to ask the very same question. After searching a bit further and decyphering part of webpack's documentation I think that what you want is the output.library and output.libraryTarget in the webpack.config.js file.
For example:
js/index.js:
var foo = 3;
var bar = true;
webpack.config.js
module.exports = {
...
entry: './js/index.js',
output: {
path: './www/js/',
filename: 'index.js',
library: 'myLibrary',
libraryTarget: 'var'
...
}
Now if you link the generated www/js/index.js file in a html script tag you can access to myLibrary.foo from anywhere in your other scripts.
Use DefinePlugin.
The DefinePlugin allows you to create global constants which can be
configured at compile time.
new webpack.DefinePlugin(definitions)
Example:
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
})
//...
]
Usage:
console.log(`Environment is in production: ${PRODUCTION}`);
You can use define window.myvar = {}.
When you want to use it, you can use like window.myvar = 1
DefinePlugin doesn't actually define anything. What it does is replace variables that exist in your bundle code. If the variable doesn't exist in your code, it will do nothing. So it doesn't create global variables.
In order to create a global variable, write it in your code:
window.MyGlobal = MY_GLOBAL;
And use DefinePlugin to replace MY_GLOBAL with some code:
new webpack.DefinePlugin({
'MY_GLOBAL': `'foo'`,
// or
'MY_GLOBAL': `Math.random()`,
}),
Then your output JS will be like this:
window.MyGlobal = 'foo';
// or
window.MyGlobal = Math.random();
But MY_GLOBAL will never actually exist at runtime, because it is never defined. So that's why DefinePlugin has a misleading name.
I solved this issue by setting the global variables as a static properties on the classes to which they are most relevant. In ES5 it looks like this:
var Foo = function(){...};
Foo.globalVar = {};
You may hit this issue, when triing bundle < script > tag js files in some old project.
Do not use webpack for this, it may be even impossible if joining 50+ libraries like jquery and then figuring out all global variables or if they used nested require. I would advice to simply use uglify js instead , which drops all this problems in 2 commands.
npm install uglify-js -g
uglifyjs --compress --mangle --output bundle.js -- js/jquery.js js/silly.js