Angular2 global configuration file - javascript

I've got an Angular2 project with this structure :
client/ // Angular2 client
app/
app.component.ts
...
main.ts
...
server/ // API
server.js
config/ // config files
webpack.config.js
...
I'd like to have all constants and parameters of the Angular2 app (like the url to the API...) in the config directory, with all other config files.
How can I perform it in Angular2 ? As the config folder is outside the client folder, is it a good practice to import something that is outside, with many "../../../" ?
Also I wanted to use dependency injection, but is there anything less heavy ?
And how can I avoid to import manually the file in each component/module I want to use it ?
Thx

What I used is probably the not best approach but, I'm using webpack to inject global variables with the DefinePlugin plugin:
I use the .env file on root, to store the variables, I have a .env.TST, .env.PRD and replace it with deployment script
webpack.common.js
var preEnv = require('../.env');
var envVars = {};
for(var propertyName in preEnv) {
envVars[propertyName] = '"'+preEnv[propertyName]+'"';
}
...
plugins: [
new webpack.DefinePlugin(
envVars
)
]
...
Example .env file
module.exports = {
"APIURL": "https://localhost/MyAPI/",
"PUBLIC_URL": "https://localhost:3000/",
"BASE_PATH": "/"
"ENV": "dev"
}
And you will have APIURL as a global variable
Additionaly I added a file into the typings, to prevent warnings:
typings/typings.d.ts
declare var APIURL: string;
declare var PUBLIC_URL: string;
declare var ENV: string;
declare var BASE_PATH: string;
I hope it helps to someone

Inside you package.json you can write an npm script for every environment. that you are going to support:
"scripts": {
"build:dev": "webpack --config config/webpack.dev.js",
"build:prod": "webpack --config config/webpack.prod.js"
}
Then in every config/webpack.xxxx.dev script you can declare global variables using Webpack DefinePlugin || ExtendedDefinePlugin. It is vital to understand that this plugin allows you to have your global server-side node.js variables, become available as global client-side .js variables.
This is how your webpack.dev.js might look like
new DefinePlugin({
'ENV': JSON.stringify('Development'),
'URL': JSON.stringify('http://...')
})
...
This is how your webpack.prod.js might look like
new DefinePlugin({
'ENV': JSON.stringify('Prod'),
'URL': JSON.stringify('http://...')
})
...
Finally is your .ts files you can then write
declare var URL: string; //keeps the compiler happy
let configUrl: string = URL;
link to example github project: ng2a.frontend

Related

Ignore variable dependency of node_module webpack

I have built a library that I want to use in a Next.JS project. Within this library a certain dependency is using an import via a string passed into a require statement within the source code where the import is taking place. This is causing webpack to not recognize the import. I don't want to change code within any node_modules as this is not a preferred approach but how can I ensure that my project using the library I built is able to compile and run?
Within file_using_string_passed_into_require_to_get_import.js:
let importName = "./potential_import_A.js"
if(condition){
importName = "./potential_import_B.js"
}
module.exports = require(importName)
This is the folder structure:
Project/
| node_modules
| my-library
| node_modules
| library-dependency
| file_using_string_passed_into_require_to_get_import.js
| potential_import_A.js
| potential_import_B.js
To create a local (unpublished) library package
Create a 'my-library' folder (outside your current project dir).
Do npm init (Folder must include the 'package.json' )
Include source code (potential_import_A), exporting any desired functions.
In the actual project folder:
cd into the folder of the project that needs to use your library.
Run npm install --save local/path/to/my-library.
The --save will add the package to your dependencies in the project's package.json file, as it does with 3rd party published packages. It will also add a copy of the source code to the node modules folder of the project, as always.
Importing your new library:
import/require the package as you would normally, from any project.
For example
import { myFunction } from "my-library"
I got it to work by excluding node_modules from the webpack build. Since I am using Next.JS this is within my next.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
webpack: (
config,
{
buildId, dev, isServer, defaultLoaders, nextRuntime, webpack,
},
) => {
if (isServer) {
config.target = 'node';
config.node = {
__dirname: true,
global: true,
__filename: true,
};
config.externals = [nodeExternals()], // in order to ignore all modules in node_modules folder
config.externalsPresets = {
node: true, // in order to ignore built-in modules like path, fs, etc.
};
}
return config;
},
};

Webpack React: Conditionally load json config files

I have WebPack React project which I'm testing on my "staging" server.
Now its time to release it on "production" server.
I'm using server.json file which consists with server info such as api keys, api address, and so on.
What I want is to use different server.json for "production" and "staging".
And when I use production-server.json, there would be no traces of staging-server.json in my bundle.
src
- config
-- config.js
-- production-server.json
-- staging-server.json
maybe something like: yarn build-staging, yarn build-production
You should use environment variables and webpack's DefinePlugin. Additionally, you can use node-config to automatically load a json configuration file based on your NODE_ENV.
package.json
"scripts": {
"build:dev": "NODE_ENV=development start-something",
"build": "NODE_ENV=production start-something"
}
project config structure
config
default.json
{ "api": "https://api.mysite.com/v1" }
staging.json
{ "api": "http://localhost:8000/v1" }
webpack config
// node-config will load your staging.json or default.json file here
// depending on what NODE_ENV is
const config = require('config');
plugins: [
// inject window.CONFIG into your app
new webpack.DefinePlugin({
CONFIG: JSON.stringify(config)
})
]
Then in your react code you will have access to environment-specific config
componentDidMount() {
// in prod: https://api.mysite.com/v1/user/some-user-id
// in staging: http://localhost:8000/v1/user/some-user-id
return axios(`${CONFIG.api}/user/${this.props.userId}`).then(whatever...)
}
If you're on windows use cross-env to set your environment variable.
Using node-config isn't the only way to do this, there are several, but I find it pretty easy, unless you're working with electron.
edit
Since node-config uses nodejs it is typically used in front end projects in conjunction with webpack. If you are unable to to integrate it with webpack you don't need to use node-config at all, I would do something like this:
project structure
config
default.json
development.json
test.json
index.js
src
...etc
config files
// default.json, typically used for production
{
"api": "https://api.mysite.com/v1"
}
// development.json
{
"api": "http://localhost:8000/v1"
}
// index.js
// get process.env via babel-plugin-transform-inline-environment-variables
import production from './default.json';
import development from './development.json';
const { NODE_ENV: env } = process.env;
const config = {
production,
development
};
export default config[env];

Keys defined in dev.env.js are not accessible with webpack in vuejs

The template is based on vuejs-webpack, and the build, config files are here, I have not modified any of these files.
Based on Environment Variables the keys defined in dev.env.js file must be accessible when running npm run dev in the app.
This is the content of my dev.env.js:
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
SAMPLE: '"XX"',
AUTH_URL: '"http://localhost:3030"'
})
And when I try to access AUTH_URL in App.vue like this process.env.AUTH_URL, I receive undefined.
It seems to me whatever is defined in dev.env.js file will never become accessible when running npm run dev
Use the webpack.DefinePlugin to define the variables you wish to share with your front end. Webpack its self does not expose process to the browser as this is a node js function.
const dev_env = require('dev.env.js)
plugins: [
new webpack.DefinePlugin({
'process.env' : {
NODE_ENV: JSON.stringify(dev_env.NODE_ENV)
}
})
]
Some variation of the above should work for you.
If your run "npm run dev", then your new environment variable is only available after a restart of npm (Ctrl + C and again 'npm run dev').

Path aliases for imports in WebStorm

I use webpack path aliases for ES6 module loading.
E.g. If I define an alias for utils instead of something like
import Foo from "../../../utils/foo", I can do
import Foo from "utils/foo"
The problem is that once I start using aliases, WebStorm looses track of the import and I'm left with warnings and no auto-completion.
Is there a way to instruct WebStorm to use such aliases?
Yes, there is.
In fact, Webstorm can't automatically parse and apply Webpack config, but you can set up aliases the same way.
You just have to mark the parent folder of "utils" (in your example) as a resource root (right-click, mark directory as / resource root).
We just managed to do with the following structure :
/src
/A
/B
/C
We have A B and C folders declared as alias in Webpack.
And in Webstorm we marked "src" as "Resource Root".
And now we can simply import :
import A/path/to/any/file.js
instead of
import ../../../../../A/path/to/any/file.js
while still having Webstorm correctly parsing and indexing all code, link to files, autocompleting and so on ...
I managed to set up aliases for WebStorm 2017.2 within webpack like this:
For the record: in PHPSTORM, working with laravel mix, I managed to solve this by creating a webpack.config.js file separately like:
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
'~': path.resolve(__dirname, './resources/assets/js')
}
},
...
}
And then importing it in the webpack.mix.js like:
const config = require('./webpack.config')
...
mix.webpackConfig(config)
Make sure the webpack configuration file is pointed correctly in the configuration of the PhpStorm in: Settings > Languages & Frameworks > Javascript > Webpack
You can define custom paths, so WebStorm/PhpStorm can understand your aliases. But make sure, they are identical with your aliases. Create file in your root directory and call it something like this: webStorm.config.js (any js file will be ok). Then configure your paths inside:
System.config({
"paths": {
"components/*": "./src/components/*",
"core/*": "./src/core/*",
...
}
});
WebStorm/PhpStorm will recognize System as it's own module and will treat this file as configuration.
This is answered in a comment but to save people digging into comments and link only information, here it is:
As of WS2017.2 this will be done automatically. The information is here.
According to this, webstorm will automatically resolve aliases that are included within the webpack.config in the root of the project. If you have a custom structure and your webpack.config isn't in the root folder then go to Settings | Languages & Frameworks | JavaScript | Webpack and set the option to the config you require.
Note: Most setups have a base config which then call a dev or prod version. In order for this to work properly, you need to tell webstorm to use the dev one.
Not right now, We were also using path aliases for the files in our react project. The import names were shorter but we lost a lot on static checking of webstorm as well as completion features.
We later came up with a decision to reduce the code to only 3 levels of depth, as well a single level for the common parts. The path completion feature of webstom (ctrl + space) even helps reduce the typing overhead. The production build does not use longer names, so hardly makes any difference in final code.
I will suggest please reconsider your decision about aliases. You loose semantic meaning of modules coming from node_modules and your own code, as well as referencing the alias files again and again to make sense of your code, is a much bigger overhead.
add jsconfig.js on your project root
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
}
}
In PHPStorm (using 2017.2 currently), I have not been able to get webpack configs to work properly in regards to aliases.
My fix involves using the "Directories" section of the main settings. I just had to mark each folder referenced by an alias as a sources root, then click the properties dropdown for each and specify the alias as a "Package prefix". This made everything link up for me.
Not sure if the Directories section exists in WebStorm, but if it does, this seems to be a fool-proof method for getting import aliases working.
For anyone struggling: path.resolve() must be called with "__dirname" first argument for Idea (Websorm) to be able to resolve the path correctly.
Will work for Idea (Websorm):
alias: {
'#someAlias': pathLib.resolve(__dirname, 'path/to/directory')
}
Will not work for Idea (Websorm) (while still being valid webpack alias):
alias: {
'#someAlias': pathLib.resolve('path/to/directory')
}
Webstorm can't read webpack.config if module.exports return a function.
For example
module.exports = function (webpackEnv) {
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
...
}
}
Check your config file, maybe this cause you are a problem.
There is a lot of discussion here about Laravel Mix, so I'll leave this here to help out future readers. I solved this by creating a separate (fake) webpack config file which is only used by my IDE (PHPStorm).
1. Create a separate alias.js file (e.g. /webpack/alias.js)
const path = require('path');
const assets = path.join(__dirname,'..','resources','assets');
module.exports = {
'#js' : path.resolve(assets, 'js'),
'#c' : path.resolve(assets, 'js', 'components'),
'#errors' : path.resolve(assets, 'js', 'errors'),
'#utils' : path.resolve(assets, 'js', 'utils'),
'#store' : path.resolve(assets, 'js', 'store'),
'#api' : path.resolve(assets, 'js', 'api'),
'#less' : path.resolve(assets, 'less')
}
2. Require the alias.js file into webpack.mix.js
const mix = require('laravel-mix');
mix.alias(require('./webpack/alias'))
// ... The rest of your mix, e.g.
.js('app.js')
.vue()
.less('app.less');
3. Create the fake webpack config for your IDE (e.g. /webpack/ide.config.js)
Here, import the laravel-mix webpack config, plus your aliases, and any other config that the IDE might need help finding. Also include the prefixed ~ aliases for importing styles into your Vue components.
/*
|--------------------------------------------------------------------------
| A fake config file for PhpStorm to enable aliases
|--------------------------------------------------------------------------
|
| File > Settings... > Languages & Frameworks > Javascript > Webpack
|
| Select "Manually" and set the configuration file to this
|
*/
const path = require('path');
const mixConfig = require('./../node_modules/laravel-mix/setup/webpack.config')();
module.exports = {
...mixConfig,
resolve: {
alias: {
...require('./alias'),
'~#less' : path.resolve('#less'), // <--
},
...mixConfig.resolve
}
}
4. Set your IDE to use webpack/ide.config.js as your webpack config file.
Had the same problem on a new Laravel project with Jetstream. The webpack.config.js was present and correct. But PHPStorm still didn't recognize the # symbol as a resource root.
After opening the webpack config, I got a notification:
After Clicking on Trust project and run, the # symbol became recognized.
I know that this isn't the solution or use-case for everyone. But I still found it worthy to note on this post, because it helped me in my situation.
Using
laravel/framework:8.77.1
npm:8.3.0
node:v14.18.1

Conditional build based on environment using Webpack

I have some things for development - e.g mocks which I would like to not bloat my distributed build file with.
In RequireJS you can pass a config in a plugin file and conditonally require things in based on that.
For webpack there doesn't seem to be a way of doing this. Firstly to create a runtime config for an environment I have used resolve.alias to repoint a require depending on the environment, e.g:
// All settings.
var all = {
fish: 'salmon'
};
// `envsettings` is an alias resolved at build time.
module.exports = Object.assign(all, require('envsettings'));
Then when creating the webpack config I can dynamically assign which file envsettings points to (i.e. webpackConfig.resolve.alias.envsettings = './' + env).
However I would like to do something like:
if (settings.mock) {
// Short-circuit ajax calls.
// Require in all the mock modules.
}
But obviously I don't want to build in those mock files if the environment isn't mock.
I could possibly manually repoint all those requires to a stub file using resolve.alias again - but is there a way that feels less hacky?
Any ideas how I can do that? Thanks.
You can use the define plugin.
I use it by doing something as simple as this in your webpack build file where env is the path to a file that exports an object of settings:
// Webpack build config
plugins: [
new webpack.DefinePlugin({
ENV: require(path.join(__dirname, './path-to-env-files/', env))
})
]
// Settings file located at `path-to-env-files/dev.js`
module.exports = { debug: true };
and then this in your code
if (ENV.debug) {
console.log('Yo!');
}
It will strip this code out of your build file if the condition is false. You can see a working Webpack build example here.
Not sure why the "webpack.DefinePlugin" answer is the top one everywhere for defining Environment based imports/requires.
The problem with that approach is that you are still delivering all those modules to the client -> check with webpack-bundle-analyezer for instance. And not reducing your bundle.js's size at all :)
So what really works well and much more logical is: NormalModuleReplacementPlugin
So rather than do a on_client conditional require -> just not include not needed files to the bundle in the first place
Hope that helps
Use ifdef-loader. In your source files you can do stuff like
/// #if ENV === 'production'
console.log('production!');
/// #endif
The relevant webpack configuration is
const preprocessor = {
ENV: process.env.NODE_ENV || 'development',
};
const ifdef_query = require('querystring').encode({ json: JSON.stringify(preprocessor) });
const config = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: `ifdef-loader?${ifdef_query}`,
},
},
],
},
// ...
};
I ended up using something similar to Matt Derrick' Answer, but was worried about two points:
The complete config is injected every time I use ENV (Which is bad for large configs).
I have to define multiple entry points because require(env) points to different files.
What I came up with is a simple composer which builds a config object and injects it to a config module.
Here is the file structure, Iam using for this:
config/
└── main.js
└── dev.js
└── production.js
src/
└── app.js
└── config.js
└── ...
webpack.config.js
The main.js holds all default config stuff:
// main.js
const mainConfig = {
apiEndPoint: 'https://api.example.com',
...
}
module.exports = mainConfig;
The dev.js and production.js only hold config stuff which overrides the main config:
// dev.js
const devConfig = {
apiEndPoint: 'http://localhost:4000'
}
module.exports = devConfig;
The important part is the webpack.config.js which composes the config and uses the DefinePlugin to generate a environment variable __APP_CONFIG__ which holds the composed config object:
const argv = require('yargs').argv;
const _ = require('lodash');
const webpack = require('webpack');
// Import all app configs
const appConfig = require('./config/main');
const appConfigDev = require('./config/dev');
const appConfigProduction = require('./config/production');
const ENV = argv.env || 'dev';
function composeConfig(env) {
if (env === 'dev') {
return _.merge({}, appConfig, appConfigDev);
}
if (env === 'production') {
return _.merge({}, appConfig, appConfigProduction);
}
}
// Webpack config object
module.exports = {
entry: './src/app.js',
...
plugins: [
new webpack.DefinePlugin({
__APP_CONFIG__: JSON.stringify(composeConfig(ENV))
})
]
};
The last step is now the config.js, it looks like this (Using es6 import export syntax here because its under webpack):
const config = __APP_CONFIG__;
export default config;
In your app.js you could now use import config from './config'; to get the config object.
another way is using a JS file as a proxy, and let that file load the module of interest in commonjs, and export it as es2015 module, like this:
// file: myModule.dev.js
module.exports = "this is in dev"
// file: myModule.prod.js
module.exports = "this is in prod"
// file: myModule.js
let loadedModule
if(WEBPACK_IS_DEVELOPMENT){
loadedModule = require('./myModule.dev.js')
}else{
loadedModule = require('./myModule.prod.js')
}
export const myString = loadedModule
Then you can use ES2015 module in your app normally:
// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"
Faced with the same problem as the OP and required, because of licensing, not to include certain code in certain builds, I adopted the webpack-conditional-loader as follows:
In my build command I set an environment variable appropriately for my build. For example 'demo' in package.json:
...
"scripts": {
...
"buildDemo": "./node_modules/.bin/webpack --config webpack.config/demo.js --env.demo --progress --colors",
...
The confusing bit that is missing from the documentation I read is that I have to make this visible throughout the build processing by ensuring my env variable gets injected into the process global thus in my webpack.config/demo.js:
/* The demo includes project/reports action to access placeholder graphs.
This is achieved by using the webpack-conditional-loader process.env.demo === true
*/
const config = require('./production.js');
config.optimization = {...(config.optimization || {}), minimize: false};
module.exports = env => {
process.env = {...(process.env || {}), ...env};
return config};
With this in place, I can conditionally exclude anything, ensuring that any related code is properly shaken out of the resulting JavaScript. For example in my routes.js the demo content is kept out of other builds thus:
...
// #if process.env.demo
import Reports from 'components/model/project/reports';
// #endif
...
const routeMap = [
...
// #if process.env.demo
{path: "/project/reports/:id", component: Reports},
// #endif
...
This works with webpack 4.29.6.
I've struggled with setting env in my webpack configs. What I usually want is to set env so that it can be reached inside webpack.config.js, postcss.config.js and inside the entry point application itself (index.js usually). I hope that my findings can help someone.
The solution that I've come up with is to pass in --env production or --env development, and then set mode inside webpack.config.js.
However, that doesn't help me with making env accessible where I want it (see above), so I also need to set process.env.NODE_ENV explicitly, as recommended here.
Most relevant part that I have in webpack.config.js follow below.
...
module.exports = mode => {
process.env.NODE_ENV = mode;
if (mode === "production") {
return merge(commonConfig, productionConfig, { mode });
}
return merge(commonConfig, developmentConfig, { mode });
};
Use envirnment variables to create dev and prod deployments:
https://webpack.js.org/guides/environment-variables/
I use string-replace-loader to get rid of an unnecessary import from the production build, and it works as expected: the bundle size becomes less, and a module for development purposes (redux-logger) is completely removed from it. Here is the simplified code:
In the file webpack.config.js:
rules: [
// ... ,
!env.dev && {
test: /src\/store\/index\.js$/,
loader: 'string-replace-loader',
options: {
search: /import.+createLogger.+from.+redux-logger.+;/,
replace: 'const createLogger = null;',
}
}
].filter(Boolean)
In the file src/store/index.js:
// in prod this import declaration is substituted by `const createLogger = null`:
import { createLogger } from 'redux-logger';
// ...
export const store = configureStore({
reducer: persistedReducer,
middleware: createLogger ? [createLogger()] : [],
devTools: !!createLogger
});
While this is not the best solution, it may work for some of your needs. If you want to run different code in node and browser using this worked for me:
if (typeof window !== 'undefined')
return
}
//run node only code now

Categories

Resources