Create multiple builds of the same require.js module - module

Using require.js and r.js, lets say I have a module, "myModule.js".
The r.js docs state that they support integration with has.js such that
has: {
feature: true
}
transforms something like:
if (has('feature')) {
alert('feature detected!');
} else {
alert('feature not supported')
}
to:
alert('feature detected!');
However, how can I go about creating two versions of the same module? One version for those browsers that support the feature and another version for those browsers that do not support the feature.

Let's say your entry point to your application is named app. You could create a module app-has-foo where the feature foo is true and app-no-foo where it is false. You have to use create so that r.js knows it has to create the modules. You use override to override those options that belong outside the modules option.
({
baseUrl: ...,
mainConfigFile: ...,
dir: "out",
modules: [
{
name: "app-has-foo",
create: true,
include: ["app"],
override: {
has: {
foo: true
}
}
},
{
name: "app-no-foo",
create: true,
include: ["app"],
override: {
has: {
foo: false
}
}
}
]
});

Related

How to test dynamic modules in Nest.js?

My implementation is based on this article: https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370
I want to test my generic, Twilio-based SMS sender service that I share between multiple parts of my application. I want to configure it when I'm importing it from somewhere else, so I'm writing it as a dynamic module. On top of that, the options that I pass to the dynamic module are themselves constructed dynamically, they are read from my .env file. I'm using the factory pattern when I'm registering my provider:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
'.env',
],
validationSchema,
}),
SharedSmsModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService<EnvironmentVariables>) => {
return {
accountSid: configService.get('TWILIO_ACCOUNT_SID'),
authToken: configService.get('TWILIO_AUTH_TOKEN'),
smsSenderPhoneNumber: configService.get(
'TWILIO_SMS_SENDER_PHONE_NUMBER'
),
};
},
}),
],
})
export class AppModule {}
My shared-sms module calls the function provided in the registerAsync method in app.module.ts:
// shared-sms.module.ts
export interface SharedSmsModuleOptions {
accountSid: string;
authToken: string;
smsSenderPhoneNumber: string;
}
export interface SharedSmsModuleAsyncOptions extends ModuleMetadata {
imports: any[];
inject: any[];
useFactory?: (
...args: any[]
) => Promise<SharedSmsModuleOptions> | SharedSmsModuleOptions;
}
#Module({})
export class SharedSmsModule {
static registerAsync(
sharedSmsModuleAsyncOptions: SharedSmsModuleAsyncOptions
): DynamicModule {
return {
global: true,
module: SharedSmsModule,
imports: sharedSmsModuleAsyncOptions.imports,
providers: [
{
provide: 'SHARED_SMS_OPTIONS',
useFactory: sharedSmsModuleAsyncOptions.useFactory,
inject: sharedSmsModuleAsyncOptions.inject || [],
},
SharedSmsService,
],
exports: [SharedSmsService],
};
}
}
Now I have access to the options variables in my shared-sms.service:
// shared-sms.service
#Injectable()
export class SharedSmsService {
private twilioClient: Twilio;
constructor(
#Inject('SHARED_SMS_OPTIONS') private options: SharedSmsModuleOptions
) {
this.twilioClient = new Twilio(
this.options.accountSid,
this.options.authToken
);
}
async sendSms(sendSmsDto: SendSmsDto): Promise<MessageInstance> {
await validateOrReject(plainToInstance(SendSmsDto, sendSmsDto));
const smsData = {
from: this.options.smsSenderPhoneNumber,
to: sendSmsDto.to,
body: sendSmsDto.body,
};
return await this.twilioClient.messages.create(smsData);
}
}
So long everything seems to be working. But I'm having issues when I'm trying to test the service's sendSms function. I can write tests that work when I'm providing hardcoded Twilio test account values in my test file. But I don't want to commit them to the repository, so I would want to get them from my .env file. I have tried providing everything to the Test.createTestingModule function when I'm creating my moduleRef, based on what I did in the code that I already wrote, but I couldn't specify the Twilio test account values dynamically. As I don't see documentation regarding this issue, I feel like that I'm either missing a conceptual point (providing so many things in the test seems like an overkill) or there is a trivial work-around. Please help me figure out how to pass those values to my tests from my .env file

Serving a modified asset-manifest.json in CRA using CRACO doesn't work

I have just created a new CRA app. In our organization we have a micro frontend framework which has certain requirements when it comes to the the asset file of each micro frontend app. CRA will by default, create a asset-manifest.json file.
https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js#L656
Now I need to change this file to assets.json and make some structural changes as well. To achieve this I use CRACO and add the WebpackManifestPlugin.
const ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
webpack: {
plugins: {
// tried removing CRA definition for ManifestPlugin.
// It worked, but had no impact on my problem
// remove: ['ManifestPlugin'],
add: [
new ManifestPlugin({
fileName: 'assets.json',
generate: (seed, files, entrypoints) => {
const js = [],
css = [];
files.forEach((file) => {
if (file.path.endsWith('.js') && file.isInitial) {
js.push({ value: file.path, type: 'entry' });
}
if (file.path.endsWith('.css') && file.isInitial) {
css.push({ value: file.path, type: 'entry' });
}
});
return { js, css };
},
})
]
}
}
};
Whenever I build the application, my new assets.json file is generated as expected.
However, I can't get CRA, or webpack-dev-server I assume, to serve this file while I run my CRA app in development mode. It only resolves to the index.html file. I have looked through CRA source code and can't really find any relevant place where asset-manifest.json is mentioned.
So how do I get webpack-dev-server to serve my assets.json file?
You need to add the ManifestPlugin to webpack.plugins.remove array to receive only the configuration from WebpackManifestPlugin:
...
webpack: {
alias: {},
plugins: {
add: [
new WebpackManifestPlugin(webpackManifestConfig)
],
remove: [
"ManifestPlugin"
],
},
configure: (webpackConfig, { env, paths }) => { return webpackConfig; }
},
...

Is there a way to disable filenameHashing only for specific resources (images) in webpack?

After building my website with Vue.js 2.6.2 using vue-cli, I encountered a problem with static resources (images in this case). Webpack bundles them up in the /img/ folder which is fine, but the images are given hashes like image_demo.25d62d92.png which is causing issues when trying to access those resources from an external source e.g. from another website.
There is an option for webpack to disable filenameHashing altogether, but that too great a sacrifice to not have the cache control for all the orher images on the website.
The desired solution is the option to have only some hand picked resources with their default names without the extra hash, while the other resources get the default hash for cache control.
Yes, there is a way. You will need to override the 'images' rule that vue-cli comes with.
vue inspect --rule images yields the following:
$ vue inspect --rule images
/* config.module.rule('images') */
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
}
(Protip: vue inspect is a useful tool for figuring out why things behave like they do when building with vue-cli)
All images
vue-cli recommends webpack-chain for 'advanced' configuration. I'm personally not a fan, but if you want to remove hashes for all images, you should probably modify the 'images' rule. Edit vue.config.js like so:
module.exports = {
// ...
chainWebpack: (config) => {
config.module
.rule("images")
.use("url-loader")
.loader("url-loader")
.tap((options) => {
options.fallback.options.name = "img/[name].[ext]"
return options
})
}
// ...
}
Specific images
In my case I wanted to remove hashes only for a specific group of images with a unique prefix, so I added the following to configureWebpack in vue.config.js:
module.exports = {
// ...
configureWebpack: {
module: {
rules: [
{
test: /unique-prefix-\d*\.png/, // <= modify this to suit your needs
use: [
{
loader: "url-loader",
options: {
limit: 4096,
fallback: {
loader: "file-loader",
options: {
name: "img/[name].[ext]", // <= note how the hash is removed
},
},
},
},
],
},
],
},
// ...
}
It could probably be done with webpack-chain as well, but I prefer the more vanilla Webpack config format.

Cannot add locales files from aurelia-i18n to dist folder for production

I have been using aurelia Framework and the aurelia-i18n plugin for my translations. Everything works fine in local dev but when I build my app (npm run build) the locales folders are not included.
Here is my main.js plugin setup:
.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance) => {
let aliases = ['t', 'i18n'];
TCustomAttribute.configureAliases(aliases);
instance.i18next.use(Backend);
return instance.setup({
fallbackLng: 'en',
whitelist: ['en', 'es'],
preload: ['en', 'es'],
ns: 'translation',
attributes: aliases,
lng: 'en',
debug: true,
backend: {
loadPath: 'src/locales/{{lng}}/{{ns}}.json',
}
});
})
In webpack module rules I have this:
{
test: /\.json$/,
loader: 'json', // I have also tried json-loader
include: ['/locales/en', '/locales/es'],
},
Now, if I include the lang file in the webpack module dependencies the files do appear in the dist folder but are not accessible by the aurelia-i18n plugin as it is looking in the locales folder to find the files.
new ModuleDependenciesPlugin(
{
"aurelia-i18n": [
{ name: "locales/en/translation.json", chunk: "lang-en" },
{ name: "locales/es/translation.json", chunk: "lang-es" }
]
})
Any ideas?
Thank you in advance!
For production build we just copy the locales folder to dist.
This is how the webpack.config.js looks like
new CopyWebpackPlugin([
{
from: path.join(staticDir, 'locales'),
to: path.join(outDir, 'locales')
}
])
In the root of the project we have a folder called static that contains the locales folder with the translations.
And this is the function we use in main.js to configure i18n.
export function configureI18N(aurelia: Aurelia): void {
aurelia.use.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance?: any) => {
// register backend plugin
instance.i18next.use(Backend);
const aliases = ['localization-key', 't', 'i18n'];
// add aliases for 't' attribute
TCustomAttribute.configureAliases(aliases);
TParamsCustomAttribute.configureAliases(['localization-key-value-params']);
// adapt options to your needs (see http://i18next.com/docs/options/)
// make sure to return the promise of the setup method, in order to guarantee proper loading
return instance.setup({
backend: { // <-- configure backend settings
loadPath: '../locales/{{lng}}.{{ns}}.json' // <-- XHR settings for where to get the files from
},
lng: 'en',
attributes: aliases,
fallbackLng: 'en',
lowerCaseLng: true
});
});
}

Keys in One Object Must be the Same as Keys in Another Object

Initial Setting
You have a JavaScript object for keeping configs, it may be extended by plugins, each plugin has a version and one property on the configs object.
const CONFIGS = {
plugins: {
plugins: { version: '0.15' }, // Plugins is a plugin itself.
proxies: { version: '0.15' } // Used to configure proxies.
},
proxies: {
HTTPS: ['satan.hell:666']
}
}
Question
How to express in JSON schema, that each key of CONFIGS.plugins MUST have corresponding property on root of CONFIGS object and vice versa.
My Failed Attempt
ajv is 4.8.2, prints "Valid!" but must be "Invalid"
'use strict';
var Ajv = require('ajv');
var ajv = Ajv({allErrors: true, v5: true});
var schema = {
definitions: {
pluginDescription: {
type: "object",
properties: {
version: { type: "string" }
},
required: ["version"],
additionalProperties: false
}
},
type: "object",
properties: {
plugins: {
type: "object",
properties: {
plugins: {
$ref: "#/definitions/pluginDescription"
}
},
required: ["plugins"],
additionalProperties: {
$ref: "#/definitions/pluginDescription"
}
}
},
required: { $data: "0/plugins/#" }, // current obj > plugins > all props?
additionalProperties: false
};
var validate = ajv.compile(schema);
test({
plugins: {
plugins: { version: 'def' },
proxies: { version: 'abc' }
}
// Sic! No `proxies` prop, but must be.
});
function test(data) {
var valid = validate(data);
if (valid) console.log('Valid!');
else console.log('Invalid: ' + ajv.errorsText(validate.errors));
}
There are three solutions here:
create another property on the root level, e.g. requiredProperties. Its value should be an array with the list of properties you want to have both on the top level and inside plugins. Then you can use required with $data pointing to this array both on top level and inside plugins.
See example here: https://runkit.com/esp/581ce9faca86cc0013c4f43f
use custom keyword(s).
check this requirement in code - there is no way in JSON schema to say keys in one object should be the same as keys in another (apart from above options).