For the purpose of this response, let's assume there is a primary App.js
file and a directory named components
which includes files like A.js
, B.js
, C.js
, D.js
, etc. The requirement is to include only A.js
and B.js
in the bundle, while excluding all other files at compile time.
Exploring require.context
Webpack offers a specific method called require.context to handle a group of dependencies. It requires four parameters: the path to the directory containing files to be included, a boolean value to indicate whether subdirectories should be considered, a regular expression for filtering files, and a loading mode directive.
To bundle all components, we would use the following code:
// App.js
const req = require.context('./components')
(While this goes beyond the initial query, you can then utilize the exported functions from these files using the req
variable. For instance, if each file exports a default function, you can execute them as follows:
const req = require.context('./components')
req.keys().forEach( key => {
req( key )?.default()
} )
Refer to this question and its answers for more on requiring with require.context
)
However, if we need to load only A.js
and B.js
, we must apply a filter with a regular expression to require.context
. Assuming that we know the required files upfront, we can hardcode the regular expression like so:
// App.js
const req = require.context('./components', true, /(A|B)\.js$/)
The /(A|B)\.js$/
regex filters input to include either A.js
or B.js
. It can be expanded to accommodate more files such as /(A|B|C|D)\.js$/
or to target specific subdirectories. Webpack evaluates this regex at compile time, ensuring only matching files are bundled.
But the challenge arises when the filtering criteria are unknown during compilation due to dynamically generated filenames.
Utilizing webpack.DefinePlugin
By leveraging webpack.DefinePlugin, we can set global variables in our webpack.config.js
known at compile time. These variables become statically accessible across all files by Webpack. For example, we can assign our regex pattern to a global variable like __IMPORT_REGEX__
:
//webpack.config.js
module.exports = {
...
plugins: [
new webpack.DefinePlugin( {
__IMPORT_REGEX__: "/(A|B)\\.js$/"
} )
]
}
// App.js
const req = require.context('./components', true, __IMPORT_REGEX__)
Note that values defined via DefinePlugin
must be stringified, hence raw RegExp objects are not supported. All backslashes must be properly escaped.
If the filenames are stored in an array, say fetched from a JSON file, we can dynamically construct the required regex in webpack.config.js
. Here's a simple approach - concatenate filenames using the pipe symbol as a separator:
// webpack.config.js
const fileNames = ["A", "B"];
const importRegex = `/(${ fileNames.join("|") })\\.js$/`; // will return '/(A|B)\\.js$/'
module.exports = { ...
Bringing it Together
// webpack.config.js
const fileNames = ["A", "B"];
const importRegex = `/(${ fileNames.join("|") })\\.js$/`;
module.exports = {
...
plugins: [
new webpack.DefinePlugin( {
__IMPORT_REGEX__: importRegex
} )
]
}
// App.js
const req = require.context('./components', true, __IMPORT_REGEX__)
Lastly, Typescript users need to declare a type definition for __IMPORT_REGEX__
.
// interface.d.ts
declare global {
var __IMPORT_REGEX__: RegExp
}