magic-comments
v2.1.12
Published
Node.js tool for adding Webpack magic comments at build time.
Downloads
8,335
Maintainers
Readme
✨ magic-comments
Tooling utility to add configurable webpack magic comments to dynamic import()
expressions at build time.
Useful when working with:
- Babel plugins
- Webpack loaders
- Vite plugins
- Anywhere you want to add magic comments to your source code before running through webpack.
Getting Started
First install magic-comments
:
npm install magic-comments
Next, for each file processed provide the following required information for every import()
found:
- The absolute filename of the file being processed (
modulePath
). - The dynamic import's specifier (
importPath
).
Then pass those values to getMagicComment()
to generate a magic comment that can be inserted into the corresponding import()
:
src/file.js
const mod = import('./folder/module.js')
tooling
import { resolve } from 'node:path'
import { readFileSync } from 'node:fs'
import { parse } from 'acorn' // Or another parser
import { getMagicComment } from 'magic-comments'
import { traverseForImportSpecifier } from './utils.js'
const filename = resolve(cwd, './src/file.js')
const code = readFileSync(filename)
const ast = parse(code)
const dynamicImports = traverseForImportSpecifiers(ast)
dynamicImports.forEach(({ specifier }) => {
const magicComment = getMagicComment({
modulePath: filename,
importPath: specifier,
// The options are names of webpack magic comments
options: {
webpackChunkName: true,
webpackFetchPriority: (modulePath, importPath) => {
if (importPath.endsWith('important.js')) {
return 'high'
}
}
}
})
console.log(magicComment)
// /* webpackChunkName: "module-important", webpackFetchPriority: "high" */
})
getMagicComment()
Generates a webpack magic comment.
getMagicComment(ctx: MagicCommentsContext) => string
The only parameter is an object with the following properties.
interface MagicCommentsContext {
importPath: string
modulePath: string
match?: 'module' | 'import'
open?: boolean
options?: MagicComments
}
MagicCommentsContext
The only required properties are modulePath
and importPath
:
modulePath
required*
The absolute path to the file with the dynamic imports.
importPath
required*
The specifier from the dynamic import. For example, for import('./specifier.js')
the importPath
would be ./specifier.js
.
open
default false
Whether the returned comment should be surrounded by /*
and */
, for example, /* comment */
vs comment
.
match
default 'module'
Sets how globs are matched, either the module file path, or the import()
specifier.
options
An object with properties corresponding to magic comments supported by webpack.
All options can be defined with a CommentFunc
or a CommentConfig
to support overrides of CommentOptions
. Options that support globs use micromatch
for pattern matching, where type Glob = string | string[]
.
webpackChunkName
webpackFetchPriority
webpackMode
webpackPrefetch
webpackPreload
webpackInclude
webpackExclude
webpackExports
webpackIgnore
CommentFunc
All options can be defined as a function to dynamically determine their value, or turn on and off.
interface CommentFunc<T> {
(modulePath: string, importPath: string): T
}
The exact shape of T
is determined by the magic comment the option is associated with, similar to CommentOptions
.
CommentConfig
To allow overrides based on module or import paths, all options can be defined with an object having the following interface:
interface CommentConfig<T extends CommentOptions> {
options: T
overrides?: Array<{
files: string | string[]
options: T
}>
}
CommentOptions
The exact shape defining options
is determined by the magic comment it is associated with, but the interface always extends CommentOptions
:
interface CommentOptions {
active?: boolean | ((modulePath: string, importPath: string): boolean)
}
The active
property turns the option on or off. Each particular magic comment extends this interface in their own way, adding additional properties relevant to their functioning.
For example, webpackChunkName
adds a couple additional properties for adjusting the chunk name used:
interface WebpackChunkNameOptions extends CommentOptions {
/**
* Use the basename of the import specifier as the chunk name.
*/
basename?: boolean
/**
* Provide a custom chunk name for all dynamic imports or
* those matching a particular override glob.
*/
name?: string
}
You can skip to the overrides example to get a better sense of how this all works.
webpackChunkName
type
boolean
| Glob
| CommentFunc<string | false>
| CommentConfig<WebpackChunkNameOptions>
default true
Adds webpackChunkName
magic comments. The assumption is that this is the most popular webpack magic comment, so it will be enabled by default when options
is empty of falsy.
When using a CommentConfig
the following comment options are supported:
{
// Use the basename of the import specifier as the chunk name.
basename?: boolean
// If overrides are not used, this will apply to ALL dynamic imports.
name?: string
}
Possible values:
true
- AddswebpackChunkName
comments to all dynamic imports using the derived path from the import specifier in kebab-case as the chunk name. This is the default.false
- Disables adding thewebpackChunkName
comment globally.string | string[]
- When the glob(s) match a path from amatch
path, awebpackChunkName
comment is added using the derived path from the import specifier in kebab-case as the chunk name.(modulePath: string, importPath: string) => string | false
- Return a string to be used as the chunk name. Returning a falsy value will skip adding the comment.options.basename
:true
- Use only the basename from the import specifier as the chunk name. Might result in name collisions. Use in areas where you know the basenames are unique.false
- Use the full derived path from the import specifier in kebab-case as the chunk name, same as the default behavior.
options.name
:string
- Set a fixed string to be used for all dynamic imports, or based on overrides.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackFetchPriority
type
boolean
| FetchPriority
| CommentFunc<false | FetchPriority>
| CommentConfig<WebpackFetchPriorityOptions>
default None
Adds webpackFetchPriority
magic comments.
FetchPriority
is an enum:
enum FetchPriority {
AUTO = 'auto',
HIGH = 'high',
LOW = 'low'
}
When using a CommentConfig
the following comment options are supported:
{
fetchPriority?: FetchPriority | CommentFunc<false | FetchPriority>
}
Possible values:
false
- Disables the comment globally. This is the default behavior.true
- AddwebpackFetchPriority
magic comments to all dynamic imports with the default value of'auto'
.string
- AddwebpackFetchPriority
magic comments to all dynamic imports with the provided string value as the priority. If the string is not'high'
,'low'
, or'auto'
the comment will not be added.(modulePath: string, importPath: string) => FetchPriority | false
- Return a string to be used as the priority. Returning a falsy value or an unsupported string will not add the comment.options.fetchPriority
:FetchPriority
- Sets the fetch priority to the provided value when adding the comment.(modulePath: string, importPath: string) => FetchPriority | false
- Same as using a function for the value.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackMode
type
boolean
| Mode
| CommentFunc<false | Mode>
| CommentConfig<WebpackModeOptions>
default None
Adds webpackMode
magic comments.
Mode
is an enum:
enum Mode {
LAZY = 'lazy',
LAZY_ONCE = 'lazy-once',
EAGER = 'eager',
WEAK = 'weak'
}
When using a CommentConfig
the following comment options are supported:
{
mode?: Mode | CommentFunc<false | Mode>
}
Possible values:
false
- Disables the comment globally. This is the default behavior.true
- AddwebpackMode
magic comments to all dynamic imports with the default value of'lazy'
.string
- AddwebpackMode
magic comments to all dynamic imports with the provided string value as the mode. If the string is not'lazy'
,'lazy-once'
,'eager'
, or'weak'
the comment will not be added.(modulePath: string, importPath: string) => Mode | false
- Return a string to be used as the mode. Returning a falsy value or an unsupported string will not add the comment.options.mode
:Mode
- Sets the chunk loading mode to the provided value when adding the comment.(modulePath: string, importPath: string) => Mode | false
- Same as using a function for the value.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackPrefetch
type
boolean
| Glob
| CommentFunc<boolean>
| CommentConfig<CommentOptions>
default None
Adds webpackPrefetch
magic comments.
When using a CommentConfig
this option does not add additional properties to CommentOptions
.
Possible values:
false
- Disables the comment globally. This is the default behavior.true
- AddwebpackPrefetch
magic comments with a value oftrue
to all dynamic imports.string | string[]
- AddwebpackPrefetch
comment with a value oftrue
when the glob(s) match a path from amatch
path.(modulePath: string, importPath: string) => boolean
- Returningfalse
will disable adding the comment, otherwise it will be added.options.active
:true
- Disable the comment.false
- Enable the comment.
webpackPreload
type
boolean
| Glob
| CommentFunc<boolean>
| CommentConfig<CommentOptions>
default None
Adds webpackPreload
magic comments.
When using a CommentConfig
this option does not add additional properties to CommentOptions
.
Possible values:
false
- Disables the comment globally. This is the default behavior.true
- AddwebpackPreload
magic comments with a value oftrue
to all dynamic imports.string | string[]
- AddwebpackPreload
comment with a value oftrue
when the glob(s) match a path from amatch
path.(modulePath: string, importPath: string) => boolean
- Returningfalse
will disable adding the comment, otherwise it will be added.options.active
:true
- Disable the comment.false
- Enable the comment.
webpackInclude
type
RegExp
| CommentFunc<RegExp>
| CommentConfig<WebpackIncludeOptions>
default None
Adds webpackInclude
magic comments.
When using a CommentConfig
the following comment options are supported:
{
include?: RegExp | CommentFunc<RegExp>
}
Possible values:
RegExp
- Adds awebpackInclude
comment to all dynamic imports using the provided regular expression.(modulePath: string, importPath: string) => RegExp
- Adds awebpackInclude
comment using the provided regular expression. Returning anything other than a regular expression does not add the comment.options.include
:RegExp
- Adds awebpackInclude
comment to all dynamic imports, or only those matching a path from thematch
path if using overrides.(modulePath: string, importPath: string) => RegExp
- Same as using a function for the value.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackExclude
type
RegExp
| CommentFunc<RegExp>
| CommentConfig<WebpackExcludeOptions>
default None
Adds webpackExclude
magic comments.
When using a CommentConfig
the following comment options are supported:
{
exclude?: RegExp | CommentFunc<RegExp>
}
Possible values:
RegExp
- Adds awebpackExclude
comment to all dynamic imports using the provided regular expression.(modulePath: string, importPath: string) => RegExp
- Adds awebpackExclude
comment using the provided regular expression. Returning anything other than a regular expression does not add the comment.options.exclude
:RegExp
- Adds awebpackExclude
comment to all dynamic imports, or only those matching a path from thematch
path if using overrides.(modulePath: string, importPath: string) => RegExp
- Same as using a function for the value.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackExports
type
CommentFunc<string[]> | CommentConfig<WebpackExportsOptions>
default None
Adds webpackExports
magic comments.
When using a CommentConfig
the following comment options are supported:
{
exports?: CommentFunc<string[]>
}
Possible values:
(modulePath: string, importPath: string) => string[]
- Adds awebpackExports
comment using the strings in the returned array as the export names. Returning anything other than an array will not add the comment.options.exports
:(modulePath: string, importPath: string) => string[]
- Same as using a function for the value.
options.active
:true
- Disable the comment.false
- Enable the comment.
webpackIgnore
type
boolean
| Glob
| CommentFunc<boolean>
| CommentConfig<CommentOptions>
default None
Adds webpackIgnore
magic comments.
When using a CommentConfig
this option does not add additional properties to CommentOptions
.
Possible values:
false
- Disables the comment globally. This is the default behavior.true
- AddwebpackIgnore
magic comments with a value oftrue
to all dynamic imports. Effectively, opt-out of webpack code-splitting for dynamic imports.string | string[]
- AddwebpackIgnore
comment with a value oftrue
when the glob(s) match a path from amatch
path.(modulePath: string, importPath: string) => boolean
- Returningfalse
will not add the comment, otherwise it will be added.options.active
:true
- Disable the comment.false
- Enable the comment.
Examples
Below are some examples. Consult one of the used by packages, or the unit tests in this repo for something more comprehensive. Particularly, the loader specification from magic-comments-loader
.
Multiple
Since options
is an object, you can define more than one type of a webpack magic comment.
import { getMagicComment, Mode } from 'magic-comments'
const magicComment = getMagicComment({
modulePath: '/path/file.js',
importPath: './import/module.js',
options: {
webpackMode: Mode.EAGER,
webpackPreload: (modulePath, importPath) => {
return importPath.includes('module')
}
}
})
console.log(magicComment) // /* webpackMode: "eager", webpackPreload: true */
Overrides
When using a CommentConfig<T>
object, you can override the configuration passed in the options
property by defining overrides
. It is an array of objects that look like:
Array<{
files: string | string[];
options: T;
}>
Where the generic T
is related to the magic comment the options are associated with. The files
and options
properties are both required, where the former is a glob string, or an array thereof, and the latter is the associated magic comment's CommentOptions
.
Here's a more complete example of how overrides can be applied.
import { getMagicComment } from 'magic-comments'
const comment = getMagicComment({
modulePath: '/file/module.js',
importPath: './dynamic/import.js',
options: {
webpackChunkName: {
options: { active: false },
overrides: [
{
files: '**/file/*.js',
options: {
active: true,
name: 'override'
}
}
]
}
}
})
console.log(comment) // /* webpackChunkName: "override" */
Here, match
is set to import
, so the glob used in the override will not match.
const comment = getMagicComment({
match: 'import',
modulePath: '/file/module.js',
importPath: './dynamic/import.js',
options: {
webpackChunkName: {
options: { active: true },
overrides: [
{
files: '**/file/*.js',
options: {
basename: true
}
}
]
}
}
})
console.log(comment) // /* webpackChunkName: "dynamic-import" */
Changing the glob to **/dynamic/*.js
will then match, and the override options will be used.
console.log(comment) // /* webpackChunkName: "import" */