Webpack multiple bundle based on configuration - javascript

I've worked with Webpack a little. I know who to do multiple bundles depends on multiple entry points, but I have a new situation.
One of our projects has a single codebase for 3 different countries (3 different URL and different URL structure). We have set of common codes and codes for C1, C2, C3. I want to run a single build and compile the ES6 code in 2 different files, like
app-common.js, c1-common.js, c2-common.js, c3-common.j2
Or
c1-app.js, c2-app.js, c3-app.js which will have common and specific js code baesd on configuration, like which files I want to bind with which bundle.
Is there any existing way to do it or how this can be achieved?

You could create a single point-of-entry file for your localized code and do conditional require statements based on a custom environment variable. Then set that up in your webpack config:
new webpack.DefinePlugin({
'process.env.COUNTRY_CODE': 'fr'
}),
Then in your code:
let i18n_config;
const country = process.env.COUNTRY_CODE || 'en';
if (country === 'en') {
i18n_config = require("./i18n/en.js");
}
if (country === 'fr') {
i18n_config = require("./i18n/fr.js");
}
You can go even further and use an aggressive minifier to remove dead code:
const isEN = country === 'en';
const isFR = country === 'fr';
if (isEN) {
i18n_config = require("./i18n/en.js");
}
if (isFR) {
i18n_config = require("./i18n/fr.js");
}
// ... and so on
Your if statements will compile to if (true) {...} or if (false) {...}. The false block will be considered dead-code and will be removed by the minifier.

Related

How to use the TypeScript Compiler API to type-check modules imported using `require()`?

I am using TypeChecker from the TypeScript Compiler API in order to extract (inferred) type information for every node in the AST of my program. In particular, I try to find out the return values from imported module functions such as:
var vec3 = require('gl-matrix/vec3')
var myVec = vec3.fromValues(1, 2, 3) // $ExpectedType vec3
This works well for modules that were imported using the import { … } from '…' statement, but unfortunately, modules that were imported using require() like above are not recognized correctly, I only receive the type any for them. However, I have set both compiler options allowJs and checkJs.
Why are the types form require()d modules not inferred correctly? VS Code (which AFAIK relies on the same API?) is able to infer the types from require() statements as well, so I'd guess that in general, tsc is able of handling them. Are there any other compiler options that I need to set differently? Or is this indeed not supported and I need to use some other package for this?
Here is a minimum script to reproduce, I have also put it on repl.it together with two example files: https://replit.com/#LinqLover/typecheck-js
var ts = require("typescript")
// Run `node index.js sample-import.js`` to see the working TypeScript analysis
const files = process.argv[1] != "/run_dir/interp.js" ? process.argv.slice(2) : ["sample-require.js"]
console.log(`Analyzing ${files}:`)
const program = ts.createProgram(files, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
allowJs: true,
checkJs: true
})
const checker = program.getTypeChecker()
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
ts.forEachChild(sourceFile, visit)
}
}
function visit(node) {
try {
const type = checker.getTypeAtLocation(node)
console.log(checker.typeToString(type))
} catch (e) {
// EAFP
}
ts.forEachChild(node, visit)
}
Thank you so much in advance!
For follow-up, this turned out to be an issue with the type definitions for gl-matrix. I should better have tried out multiple packages before suspecting that the TypeScript engine itself could be broken ...
gl-matrix issue: https://github.com/toji/gl-matrix/issues/429

Dynamically require multiple files using Webpack's DefinePlugin?

I have an array of file names that I want to import. The file names are computed at build time. If I have a single file name, I can do:
new webpack.DefinePlugin({
component_file: '"path/Component"',
})
Then in the source:
require(component_file);
This includes path/Component in the build, as expected.
However, if I try the following, it doesn't work.
new webpack.DefinePlugin({
component_files: ['"path/Component"', '"path/Component2"'],
})
Then in the source:
// component_files is converted object by Webpack.
Object.keys(component_files).forEach(file => require(file));
This causes an error Cannot find module '0'. This makes sense because Webpack just does static analysis, it can't process requires with variables as the argument. Is it possible to do what I'm trying to do?
Rather than use DefinePlugin to define dependencies that are then required within your application, you could include them as entries within your config so that they are included at compile time:
{
entry: [
...component_files,
'app.js'
]
}
For achieving dynamic bundling by environment variables, you have to wrap require statements inside conditional blocks that will be determined as "dead code" or not.
Then, on build time, these dead require statement will be removed, which will result exclusion from the final bundle.
The predicate of each conditional block must be evaluated as a boolean value on build time. This can happen only if the predicate is a plain comparison between 2 primitive values or a pure boolean. For example:
// webpack.config.json
new DefinePlugin({
"process.env.component_a": "true",
"process.env.component_b": "false",
"process.env.component_c": "'true'",
})
// in application:
if (process.env.component_a) {
const a = require('./a') // will be included
}
if (process.env.component_b) {
const b = require('./b') // will be excluded
}
if (process.env.component_c === "true") {
const c = require('./c') // will be included
}
Important Note
Leaving the value undefined is not good enough for excluding a module from the final bundle.
/* THE WRONG WAY */
// webpack.config.json
new DefinePlugin({
"process.env.component_a": "true",
})
// in application:
if (process.env.component_a) {
const a = require('./a') // will be included
}
if (process.env.component_b) {
// even though this block is unreachable, b will be included in the bundle!
const b = require('./b')
}

Conditional require file in webpack

I want to bundle the files into my production build only if it is not for DEBUG;
So I've use webpack.DefinePlugin and set the variable DEBUG === true.
Also config webpack.UglifyJsPlugin with default options
And in the js file, I do like this:
const A = DEBUG === true ? null : require('./some-debug.js');
//do things with A, for example
console.log(A)
I checked the final bundle file, A is replaced with null (so DefinePlugin is working fine), but the content of some-debug.js file is still in the bundle js.
Is it possible to let webpack not require the file?
ps:
I think I can use resolve.alias to resolve './some-debug.js' --> undefined. But I want to keep my webpack.config.js generic, dont' want to apply too many resolve.alias entries.
Thanks
It doesn't work with ternary operator.
let A;
if (DEBUG === true) {
A = require('./some-debug.js');
}

Different settings for debug/local ("grunt serve") vs. dist/build ("grunt")?

I want to define some application settings, but I want to provide different values depending on whether I'm running in 'debug' mode (e.g. grunt serve), or whether the final compiled app is running (e.g. the output of grunt). That is, something like:
angular.module('myApp').factory('AppSettings', function() {
if (DebugMode()) { // ??
return { apiPort: 12345 };
} else {
return { apiPort: 8008 };
}
});
How can I accomplish this?
The way I handle it in my apps:
move all your config data for one environment to a file: config.js, config.json,... whatever your app finds easy to read.
now modify your config file to turn it into a template using grunt config values, and generate the file with grunt-template as part of your build - for example: app.constant('myAppConfig', {bananaHammocks: <%= banana.hammocks %>});
finally, add grunt-stage to switch grunt config values depending on environment: create your different config/secret/(env).json files, update your template (app.constant('myAppConfig', {bananaHammocks: <%= stg.banana.hammocks %>});), and then grunt stage:local:build or grunt stage:prod:build
I find this the good balance between complexity and features (separation between environments, runtime code not concerned with building options,...)

How to set a including path in the Gjs code?

As i could see, the Gjs imports, loads only /usr/share/gjs-1.0 and /usr/lib/gjs-1.0 by default. I want to modularize an application, like we can do with node, but i must find modules relative to the script file.
I found this two ways to add include paths:
gjs --include-path=my-modules my-script.js
GJS_PATH=my-modules gjs my-script.js
...but both are related to the current directory, not to the file (obliviously), and they needed to be declared on the command line, making this unnecessarily complex.
How can i set a including path in the Gjs code? (So i can make this relative to the file)
Or... There is another way to import files from anywhere, like in python?
(Please, you don't need to propose to use a shellscript launcher to solve the --include-path and GJS_PATH problem. That is obvious, but less powerful. If we do not have a better solution, we survive with that.)
You need to set or modify imports.searchPath (which is not obvious because it doesn't show up with for (x in imports)print(x)). So this:
imports.searchPath.unshift('.');
var foo = imports.foo;
imports the file “foo.js” as the foo object.
This is compatible with Seed, although there imports knows it has a searchPath.
(Earlier versions of this answer were substantially less accurate and more inflammatory. Sorry).
As Douglas says, you do need to modify imports.searchPath to include your library location. Using . is simple, but depends on the files always being run from the same directory location. Unfortunately finding the directory of the currently executing script is a huge hack. Here's how Gnome Shell does it for the extensions API
I've adapted this into the following function for general use:
const Gio = imports.gi.Gio;
function getCurrentFile() {
let stack = (new Error()).stack;
// Assuming we're importing this directly from an extension (and we shouldn't
// ever not be), its UUID should be directly in the path here.
let stackLine = stack.split('\n')[1];
if (!stackLine)
throw new Error('Could not find current file');
// The stack line is like:
// init([object Object])#/home/user/data/gnome-shell/extensions/u#u.id/prefs.js:8
//
// In the case that we're importing from
// module scope, the first field is blank:
// #/home/user/data/gnome-shell/extensions/u#u.id/prefs.js:8
let match = new RegExp('#(.+):\\d+').exec(stackLine);
if (!match)
throw new Error('Could not find current file');
let path = match[1];
let file = Gio.File.new_for_path(path);
return [file.get_path(), file.get_parent().get_path(), file.get_basename()];
}
Here's how you might use it from your entry point file app.js, after defining the getCurrentFile function:
let file_info = getCurrentFile();
// define library location relative to entry point file
const LIB_PATH = file_info[1] + '/lib';
// then add it to the imports search path
imports.searchPath.unshift(LIB_PATH);
Wee! now importing our libraries is super-easy:
// import your app libraries (if they were in lib/app_name)
const Core = imports.app_name.core;

Categories

Resources