diff --git a/README.md b/README.md index 6dee1b4..0560151 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ -# magic-comments-loader +# [`magic-comments-loader`](https://www.npmjs.com/package/magic-comments-loader) -Adds [magic coments](https://webpack.js.org/api/module-methods/#magic-comments) to your dynamic import statements. +Adds [magic coments](https://webpack.js.org/api/module-methods/#magic-comments) to your dynamic `import()` statements. -**NOTE**: This loader ignores dynamic imports that already include comments of any kind. +NOTE: **This loader ignores dynamic imports that already include comments of any kind**. Magic comments supported: * `webpackChunkName` * `webpackMode` * `webpackIgnore` -* `webpackPreload` -* `webpackPrefetch` +* [`webpackPreload`](https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules) +* [`webpackPrefetch`](https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules) +* `webpackExports` ## Usage @@ -22,6 +23,7 @@ Add this inside your `webpack.config.js`. #### Without options Adds `webpackChunkName` to all dynamic imports (same as `webpackChunkName: true` when using options). + ```js module: { rules: [ @@ -36,7 +38,7 @@ module: { #### With options -When using the loaders `options` configure the magic comments by using their name as a key in the options object. You can provide a simple value to take on default behavior of the comment. +The loader `options` is an object with keys corresponding to the names of supported magic comments. The following comments have a default behavior and do not require configuration beyond specifying where they should be applied (globally, or to certain files). ```js module: { @@ -49,7 +51,9 @@ module: { options: { webpackChunkName: true, webpackMode: 'lazy', - webpackPreload: 'src/preload/**/*.js' + webpackIgnore: 'src/ignore/**/*.js', + webpackPreload: ['src/preload/**/*.js', '!src/preload/skip/**/*.js'], + webpackPrefetch: 'src/prefetch/**/*.js' } } } @@ -57,9 +61,13 @@ module: { } ``` -For more control you can provide an object literal with futher configuration options specific -to each comment type. All comment types have a configuration option of `active` which is a boolean to enable -or disable the addition of the magic comment. When using an object literal the configuration must be passed under the `config` key. +#### With `config` options + +For more control, all comments support a configuration object with two supported keys, `config` and `overrides`. The `config` key is an object used to specifiy [comment options](#options). The [`overrides`](#overrides) key is defined below. + +All comments support a `config.active` key: `Boolean` | `(modulePath, importPath) => Boolean`. This is used to enable or disable the comment. + +Here is an example of using `config` to customize comment behavior: ```js module: { @@ -71,13 +79,19 @@ module: { loader: 'magic-comments-loader', options: { webpackChunkName: { - basename: true + config: { + basename: true + } }, webpackMode: { - mode: 'lazy-once' + config: { + mode: 'lazy-once' + } }, webpackIgnore: { - active: false + config: { + active: false + } } } } @@ -88,21 +102,24 @@ module: { #### Overrides -You can also override the configuration passed in the `config` key by using `overrides`, which is an array of objects that look like: +You can also override the configuration passed in the `config` key by using `overrides`. It is an array of objects that look like: ```js overrides: [ { - // Can be an array of strings too + // Can be an array of globs too files: 'src/**/*.js', config: { - active: false, - // Possibly other configuration values + active: true, + exports: ['foo', 'bar'] + // Etc. } } ] ``` +**The `config.match` in an override is ignored. You can only have one, top-level `config.match`**. + Here's a more complete example using `config` and `overrides` to customize how comments are applied: ```js @@ -134,6 +151,7 @@ module: { } ] }, + webpackPrefetch: ['src/prefetch/**/*.js', '!src/prefetch/skip/**/*.js'], webpackMode: { config: { mode: 'lazy' @@ -163,7 +181,7 @@ module: { } ``` -### Magic Comments +## Magic Comments With loader options configured like @@ -172,51 +190,63 @@ With loader options configured like loader: 'magic-comments-loader', options: { webpackChunkName: true, - webpackMode: 'lazy' + webpackPrefetch: 'src/some/module.js', + webpackMode: 'eager' } } ``` -an import statement like +an import statement inside `src/some/module.js` like ```js -const dynamicModule = await import('./path/to/some/module') +const dynamicModule = await import('./path/to/module.js') ``` becomes ```js -const dynamicModule = await import(/* webpackChunkName: "path-to-some-module", webpackMode: "lazy" */ './path/to/some/module') +const dynamicModule = await import(/* webpackChunkName: "path-to-module", webpackPrefetch: true, webpackMode: "eager" */ './path/to/module.js') ``` -### Options +## Options -These are the options that can be configured under the loader `options`. All comments accept an [`overrides`](#overrides) key in addition to `config` when defined as an object. +These are the options that can be configured under the loader `options`. When using comments with a [`config`](#with-config-options) key, you may also specify [`overrides`](#overrides)(`config.match` is ignored inside overrides). -* `verbose`: Prints console statements of the updated `import()`s per module filepath during the webpack build. Useful for debugging your custom configurations. +* `verbose`: Boolean. Prints console statements of the module filepath and updated `import()` during the webpack build. Useful for debugging your custom configurations. +* `match`: `String(module|import)`. Sets how globs are matched, either the module file path or the `import()` path. Defaults to `'module'`. * `webpackChunkName` - * `true`: Adds `webpackChunkName` comments to **all** dynamic imports using the full path to the imported module to construct the name, so `import('path/to/module')` becomes `import(/* webpackChunkName: "path-to-module" */ 'path/to/module')`. This is the default. + * `true`: Adds `webpackChunkName` comments to **all** dynamic imports. This is the default. * `false`: Disables adding the `webpackChunkName` comment globally. - * `some/glob/**/*.js`|`['/some/globs/**/*.js']`: Adds the comment with the default behavior of slugifying (hyphenating) the import path. - * `config.active`: Boolean to enable/disable the comment. - * `config.basename`: Boolean to use only the basename from the import path as the chunk name. Some relative path imports may end up with the same basename depsite importing different modules. Use in areas where you know the basenames are unique. -* `webpackMode` - * `true`: Adds `webpackMode` comments to **all** dynamic imports using `lazy`, so `import('path/to/module')` becomes `import(/* webpackMode: "lazy" */ 'path/to/module')`. - * `false`: Disables adding the `webpackChunkName` comment globally. This is the default. - * `config.active`: Boolean to enable/disable the comment. - * `config.mode`: String to set the mode. `lazy`, `lazy-once`, `eager`, or `weak`. -* `webpackIgnore` - * `true`: Adds `webpackIgnore` comments to **all** dynamic imports, so `import('path/to/module')` becomes `import(/* webpackIgnore: true */ 'path/to/module')`. - * `false`: Disables adding the `webpackIgnore` comment globally. This is the default. - * `some/glob/**/*.js`|`['/some/globs/**/*.js']`: Adds the comment with a value of `true` to all module filepaths that match the string or array of strings. - * `config.active`: Boolean to enable/disable the comment. -* `webpackPreload` - * `true`: Adds `webpackPreload` comments to **all** dynamic imports, so `import('path/to/module')` becomes `import(/* webpackPreload: true */ 'path/to/module')`. + * `['/src/**/*.js']`: Adds the comment when the glob(s) match a path from a `match` path. + * `Function`: `(modulePath, importPath) => String()`. Returning `false` does not add the comment. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.basename`: Boolean. Use only the basename from the import path as the chunk name. Relative imports may result in name collisions. Use in areas where you know the basenames are unique. +* [`webpackPreload`](https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules) + * `true`: Adds `webpackPreload` comments to **all** dynamic imports. * `false`: Disables adding the `webpackPreload` comment globally. This is the default. - * `some/glob/**/*.js`|`['/some/globs/**/*.js']`: Adds the comment with a value of `true` to all module filepaths that match the string or array of strings. - * `config.active`: Boolean to enable/disable the comment. -* `webpackPrefetch` - * `true`: Adds `webpackPrefetch` comments to **all** dynamic imports, so `import('path/to/module')` becomes `import(/* webpackPrefetch: true */ 'path/to/module')`. + * `['/src/**/*.js']`: Adds the comment with a value of `true` when the glob(s) match a path from a `match` path. + * `Function`: `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. +* [`webpackPrefetch`](https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules) + * `true`: Adds `webpackPrefetch` comments to **all** dynamic imports. * `false`: Disables adding the `webpackPrefetch` comment globally. This is the default. - * `some/glob/**/*.js`|`['/some/globs/**/*.js']`: Adds the comment with a value of `true` to all module filepaths that match the string or array of strings. - * `config.active`: Boolean to enable/disable the comment. + * `['/src/**/*.js']`: Adds the comment with a value of `true` when the glob(s) match a path from a `match` path. + * `Function`: `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. +* `webpackIgnore` + * `true`: Adds `webpackIgnore` comments to **all** dynamic imports. **You don't want to do this**. + * `false`: Disables adding the `webpackIgnore` comment globally. This is the default. + * `['/src/**/*.js']`: Adds the comment with a value of `true` when the glob(s) match a path from a `match` path. + * `Function`: `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. +* `webpackMode` + * `true`: Adds `webpackMode` comments to **all** dynamic imports using `lazy`. + * `false`: Disables adding the comment globally. This is the default. + * `String(lazy|lazy-once|eager|weak)`: Adds the comment to **all** dynamic imports using the provided value. + * `Function`: `(modulePath, importPath) => String(lazy|lazy-once|eager|weak)`. Return falsy value to skip. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.mode`: `(modulePath, importPath) => String(lazy|lazy-once|eager|weak)`. Return falsy value to skip. +* `webpackExports` + * `Function`: `(modulePath, importPath) => [String()]`. Return falsy value to skip. + * `config.active`: Boolean | `(modulePath, importPath) => Boolean`. Returning `false` does not add the comment. + * `config.exports`: `(modulePath, importPath) => [String()]`. Return falsy value to skip. diff --git a/__tests__/comment.js b/__tests__/comment.js index 1bb863a..adeb1ae 100644 --- a/__tests__/comment.js +++ b/__tests__/comment.js @@ -13,10 +13,11 @@ describe('getCommenter', () => { webpackChunkName: true, webpackMode: 'eager', webpackPrefetch: 'some/**/*.js', - webpackPreload: false + webpackPreload: false, + webpackExports: () => ['a', 'b'] })('import("./some/test/module")', '"./some/test/module"') ).toBe( - 'import(/* webpackChunkName: "some-test-module", webpackMode: "eager", webpackPrefetch: true */ "./some/test/module")' + 'import(/* webpackChunkName: "some-test-module", webpackMode: "eager", webpackPrefetch: true, webpackExports: ["a", "b"] */ "./some/test/module")' ) expect( @@ -40,5 +41,17 @@ describe('getCommenter', () => { webpackChunkName: 'some/**/*.js' })('import("./some/test/module")', '"./some/test/module"') ).toBe('import(/* webpackChunkName: "some-test-module" */ "./some/test/module")') + + expect( + getCommenter('some/file/path.js', { + webpackExports: () => ['foo', 'bar'] + })('import("./some/test/module")', '"./some/test/module"') + ).toBe('import(/* webpackExports: ["foo", "bar"] */ "./some/test/module")') + + expect( + getCommenter('some/file/path.js', { + webpackChunkName: false + })('import("./some/test/module")', '"./some/test/module"') + ).toBe('import("./some/test/module")') }) }) diff --git a/__tests__/strategy.js b/__tests__/strategy.js index 2d357b8..a130d57 100644 --- a/__tests__/strategy.js +++ b/__tests__/strategy.js @@ -8,7 +8,8 @@ describe('commentFor', () => { webpackMode: expect.any(Function), webpackIgnore: expect.any(Function), webpackPreload: expect.any(Function), - webpackPrefetch: expect.any(Function) + webpackPrefetch: expect.any(Function), + webpackExports: expect.any(Function) }) ) }) diff --git a/__tests__/util.js b/__tests__/util.js index 8042c21..326d8a0 100644 --- a/__tests__/util.js +++ b/__tests__/util.js @@ -1,14 +1,14 @@ -import { filepathIsMatch, getOverrideConfig } from '../src/util.js' +import { pathIsMatch, getOverrideConfig } from '../src/util.js' -describe('filepathIsMatch', () => { +describe('pathIsMatch', () => { it('compares a filepath to glob patterns', () => { - expect(filepathIsMatch('some/file/path.js', 'some/**/*.js')).toEqual(true) - expect( - filepathIsMatch('some/file/path', ['some/**/*.js', '!some/file/*.js']) - ).toEqual(false) - expect( - filepathIsMatch('some/file/path.js', ['some/**/*.js', '!some/miss/*.js']) - ).toEqual(true) + expect(pathIsMatch('some/file/path.js', 'some/**/*.js')).toEqual(true) + expect(pathIsMatch('some/file/path', ['some/**/*.js', '!some/file/*.js'])).toEqual( + false + ) + expect(pathIsMatch('some/file/path.js', ['some/**/*.js', '!some/miss/*.js'])).toEqual( + true + ) }) }) diff --git a/__tests__/webpackChunkName.js b/__tests__/webpackChunkName.js index 19afdf2..75bc402 100644 --- a/__tests__/webpackChunkName.js +++ b/__tests__/webpackChunkName.js @@ -4,6 +4,7 @@ describe('webpackChunkName', () => { it('returns a "webpackChunkName" magic comment', () => { const testPath = 'some/test/module.js' const testImportPath = './some/import/path' + const testImportPathExt = './some/import/path.js' expect(webpackChunkName(testPath, testImportPath, true)).toEqual( 'webpackChunkName: "some-import-path"' @@ -15,7 +16,21 @@ describe('webpackChunkName', () => { webpackChunkName(testPath, testImportPath, ['some/**/*.js', '!some/test/*.js']) ).toEqual('') expect( - webpackChunkName(testPath, testImportPath, { config: { basename: true } }) + webpackChunkName(testPath, testImportPathExt, 'some/import/**/*.js', 'import') + ).toEqual('webpackChunkName: "some-import-path"') + expect( + webpackChunkName(testPath, testImportPath, 'some/import/**', 'import') + ).toEqual('webpackChunkName: "some-import-path"') + expect( + webpackChunkName(testPath, testImportPathExt, 'some/import/**/*.js', 'module') + ).toEqual('') + expect(webpackChunkName(testPath, testImportPath, () => 'test-chunk')).toEqual( + 'webpackChunkName: "test-chunk"' + ) + expect( + webpackChunkName(testPath, testImportPath, { + config: { active: () => true, basename: true } + }) ).toEqual('webpackChunkName: "path"') expect( webpackChunkName(testPath, testImportPath, { @@ -30,8 +45,24 @@ describe('webpackChunkName', () => { ] }) ).toEqual('') + expect( + webpackChunkName(testPath, testImportPath, { + config: { basename: true }, + overrides: [ + { + files: 'notsome/**/*.js', + config: { + active: false + } + } + ] + }) + ).toEqual('webpackChunkName: "path"') expect(webpackChunkName(testPath, './dynamic/${path}.json', true)).toEqual( 'webpackChunkName: "dynamic-[request]"' ) + expect(webpackChunkName(testPath, './${path}.json', true)).toEqual( + 'webpackChunkName: "[request]"' + ) }) }) diff --git a/__tests__/webpackExports.js b/__tests__/webpackExports.js new file mode 100644 index 0000000..04dd1f9 --- /dev/null +++ b/__tests__/webpackExports.js @@ -0,0 +1,39 @@ +import { jest } from '@jest/globals' + +import { webpackExports } from '../src/webpackExports.js' + +describe('webpackExports', () => { + it('returns a "webpackExports" magic comment', () => { + const testPath = 'some/test/module.js' + const testImportPath = './some/import/path' + const exports = jest.fn(() => ['mock']) + const comment = webpackExports(testPath, testImportPath, { config: { exports } }) + + expect(comment).toEqual('webpackExports: ["mock"]') + expect(exports).toHaveBeenCalledWith('some/test/module.js', './some/import/path') + expect(webpackExports(testPath, testImportPath, true)).toEqual('') + expect( + webpackExports(testPath, testImportPath, { + config: { active: () => true, exports: () => ['one', 'two'] } + }) + ).toEqual('webpackExports: ["one", "two"]') + expect( + webpackExports(testPath, testImportPath, { + config: { exports: () => "'one', 'two'" } + }) + ).toEqual('') + expect( + webpackExports(testPath, testImportPath, { + config: { exports: () => ['a', 'b'] }, + overrides: [ + { + files: 'some/**/*.js', + config: { + active: false + } + } + ] + }) + ).toEqual('') + }) +}) diff --git a/__tests__/webpackIgnore.js b/__tests__/webpackIgnore.js index 702d147..0efc6a4 100644 --- a/__tests__/webpackIgnore.js +++ b/__tests__/webpackIgnore.js @@ -12,6 +12,9 @@ describe('webpackIgnore', () => { expect( webpackIgnore(testPath, testImportPath, { config: { active: false } }) ).toEqual('') + expect( + webpackIgnore(testPath, testImportPath, { config: { active: () => true } }) + ).toEqual('webpackIgnore: true') expect( webpackIgnore(testPath, testImportPath, { config: { active: false }, diff --git a/__tests__/webpackMode.js b/__tests__/webpackMode.js index 5dc4636..36312c4 100644 --- a/__tests__/webpackMode.js +++ b/__tests__/webpackMode.js @@ -5,10 +5,17 @@ describe('webpackMode', () => { const testPath = 'some/test/module.js' const testImportPath = './some/import/path' + expect(webpackMode(testPath, testImportPath, true)).toEqual('webpackMode: "lazy"') expect(webpackMode(testPath, testImportPath, 'eager')).toEqual('webpackMode: "eager"') - expect(webpackMode(testPath, testImportPath, { config: { mode: 'lazy' } })).toEqual( - 'webpackMode: "lazy"' + expect(webpackMode(testPath, testImportPath, () => 'lazy-once')).toEqual( + 'webpackMode: "lazy-once"' ) + expect(webpackMode(testPath, testImportPath, () => 'invalid')).toEqual('') + expect( + webpackMode(testPath, testImportPath, { + config: { mode: () => 'lazy', active: () => true } + }) + ).toEqual('webpackMode: "lazy"') expect( webpackMode(testPath, testImportPath, { config: { mode: 'weak' }, diff --git a/__tests__/webpackPrefetch.js b/__tests__/webpackPrefetch.js index 92bed77..f098a45 100644 --- a/__tests__/webpackPrefetch.js +++ b/__tests__/webpackPrefetch.js @@ -14,6 +14,13 @@ describe('webpackPrefetch', () => { expect( webpackPrefetch(testPath, testImportPath, { config: { active: false } }) ).toEqual('') + expect( + webpackPrefetch(testPath, testImportPath, { config: { active: () => true } }) + ).toEqual('webpackPrefetch: true') + expect(webpackPrefetch(testPath, testImportPath, () => true)).toEqual( + 'webpackPrefetch: true' + ) + expect(webpackPrefetch(testPath, testImportPath, () => false)).toEqual('') expect( webpackPrefetch(testPath, testImportPath, { config: { active: false }, diff --git a/__tests__/webpackPreload.js b/__tests__/webpackPreload.js index a8586b0..96e2fe7 100644 --- a/__tests__/webpackPreload.js +++ b/__tests__/webpackPreload.js @@ -12,6 +12,13 @@ describe('webpackPreload', () => { expect( webpackPreload(testPath, testImportPath, { config: { active: false } }) ).toEqual('') + expect( + webpackPreload(testPath, testImportPath, { config: { active: () => true } }) + ).toEqual('webpackPreload: true') + expect(webpackPreload(testPath, testImportPath, () => true)).toEqual( + 'webpackPreload: true' + ) + expect(webpackPreload(testPath, testImportPath, () => false)).toEqual('') expect( webpackPreload(testPath, testImportPath, { config: { active: false }, diff --git a/package-lock.json b/package-lock.json index cbc0c74..74f10be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "magic-comments-loader", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", diff --git a/package.json b/package.json index 092e757..320e86b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "magic-comments-loader", - "version": "1.2.0", + "version": "1.3.0", "description": "Add webpack magic comments to your dynamic imports during build time", "main": "dist", "type": "module", diff --git a/src/booleanComment.js b/src/booleanComment.js index 82be193..96d543d 100644 --- a/src/booleanComment.js +++ b/src/booleanComment.js @@ -1,16 +1,16 @@ -import { getOverrideSchema, filepathIsMatch } from './util' +import { getOverrideSchema, pathIsMatch, importPrefix } from './util' const defaultSchema = { type: 'object', properties: { active: { - type: 'boolean' + oneOf: [{ type: 'boolean' }, { instanceof: 'Function' }] } }, additionalProperties: false } const getSchema = (commentSchema = defaultSchema) => ({ - anyOf: [ + oneOf: [ { type: 'boolean' }, { type: 'string' }, { @@ -19,6 +19,7 @@ const getSchema = (commentSchema = defaultSchema) => ({ type: 'string' } }, + { instanceof: 'Function' }, { type: 'object', properties: { @@ -30,7 +31,15 @@ const getSchema = (commentSchema = defaultSchema) => ({ } ] }) -const getConfig = (value, filepath, defaultConfig = { active: true }) => { +const getConfig = ( + value, + match, + filepath, + importPath, + defaultConfig = { active: true } +) => { + const path = match === 'import' ? importPath.replace(importPrefix, '') : filepath + if (value === true) { return defaultConfig } @@ -38,7 +47,24 @@ const getConfig = (value, filepath, defaultConfig = { active: true }) => { if (Array.isArray(value) || typeof value === 'string') { return { ...defaultConfig, - active: filepathIsMatch(filepath, value) + active: pathIsMatch(path, value) + } + } + + if (typeof value === 'function') { + const configValue = value(filepath, importPath) + + if (configValue) { + return { + ...defaultConfig, + active: true, + dynamic: configValue + } + } + + return { + ...defaultConfig, + active: false } } @@ -49,7 +75,7 @@ const getConfig = (value, filepath, defaultConfig = { active: true }) => { const length = overrides.length for (let i = 0; i < length; i++) { - if (filepathIsMatch(filepath, overrides[i].files)) { + if (pathIsMatch(path, overrides[i].files)) { return { ...config, ...overrides[i].config } } } diff --git a/src/comment.js b/src/comment.js index f6c7e56..725942a 100644 --- a/src/comment.js +++ b/src/comment.js @@ -1,22 +1,25 @@ import { commentFor } from './strategy.js' -const getCommenter = (filepath, options) => (match, capturedImportPath) => { +const getCommenter = (filepath, options) => (rgxMatch, capturedImportPath) => { const importPath = capturedImportPath.trim() - const { verbose, ...magicCommentOptions } = options + const bareImportPath = importPath.replace(/['"`]/g, '') + const { verbose, match, ...magicCommentOptions } = options const magicComment = Object.keys(magicCommentOptions) .map(key => { const option = magicCommentOptions[key] if (option) { - return commentFor[key](filepath, importPath, option) + return commentFor[key](filepath, bareImportPath, option, match) } return null }) .filter(comment => comment) - const magicImport = match.replace( + const magicImport = rgxMatch.replace( capturedImportPath, - `/* ${magicComment.join(', ')} */ ${importPath}` + magicComment.length > 0 + ? `/* ${magicComment.join(', ')} */ ${importPath}` + : `${importPath}` ) if (verbose) { diff --git a/src/loader.js b/src/loader.js index a94b382..aec4fab 100644 --- a/src/loader.js +++ b/src/loader.js @@ -17,7 +17,7 @@ const loader = function (source, map, meta) { const filepath = this.utils.contextify(this.rootContext, this.resourcePath) const magicComments = getCommenter( filepath.replace(/^\.\/?/, ''), - optionKeys.length > 0 ? options : { webpackChunkName: true } + optionKeys.length > 0 ? options : { match: 'module', webpackChunkName: true } ) this.callback( diff --git a/src/schema.js b/src/schema.js index 00a614e..131bcaf 100644 --- a/src/schema.js +++ b/src/schema.js @@ -3,6 +3,7 @@ import { schema as webpackModeSchema } from './webpackMode.js' import { schema as webpackIgnoreSchema } from './webpackIgnore.js' import { schema as webpackPrefetchSchema } from './webpackPrefetch.js' import { schema as webpackPreloadSchema } from './webpackPreload.js' +import { schema as webpackExportsSchema } from './webpackExports.js' const schema = { type: 'object', @@ -10,11 +11,15 @@ const schema = { verbose: { type: 'boolean' }, + match: { + enum: ['module', 'import'] + }, webpackChunkName: webpackChunkNameSchema, webpackMode: webpackModeSchema, webpackIgnore: webpackIgnoreSchema, webpackPrefetch: webpackPrefetchSchema, - webpackPreload: webpackPreloadSchema + webpackPreload: webpackPreloadSchema, + webpackExports: webpackExportsSchema }, additionalProperties: false } diff --git a/src/strategy.js b/src/strategy.js index c13c979..de2a13a 100644 --- a/src/strategy.js +++ b/src/strategy.js @@ -3,13 +3,15 @@ import { webpackMode } from './webpackMode.js' import { webpackIgnore } from './webpackIgnore.js' import { webpackPreload } from './webpackPreload.js' import { webpackPrefetch } from './webpackPrefetch.js' +import { webpackExports } from './webpackExports.js' const commentFor = { webpackChunkName, webpackMode, webpackIgnore, webpackPreload, - webpackPrefetch + webpackPrefetch, + webpackExports } export { commentFor } diff --git a/src/util.js b/src/util.js index 319f1c8..429a8a0 100644 --- a/src/util.js +++ b/src/util.js @@ -1,18 +1,6 @@ import micromatch from 'micromatch' -const getOverrideConfig = (overrides, filepath, config) => { - const length = overrides.length - - for (let i = 0; i < length; i++) { - if (filepathIsMatch(filepath, overrides[i].files)) { - return { ...config, ...overrides[i].config } - } - } - - return config -} - -const filepathIsMatch = (filepath, files) => { +const pathIsMatch = (path, files) => { const globs = [] const notglobs = [] @@ -29,11 +17,10 @@ const filepathIsMatch = (filepath, files) => { }) return ( - (globs.length === 0 || globs.some(glob => micromatch.isMatch(filepath, glob))) && - notglobs.every(notglob => micromatch.isMatch(filepath, notglob)) + (globs.length === 0 || globs.some(glob => micromatch.isMatch(path, glob))) && + notglobs.every(notglob => micromatch.isMatch(path, notglob)) ) } - const getOverrideSchema = commentSchema => ({ type: 'array', items: { @@ -57,5 +44,17 @@ const getOverrideSchema = commentSchema => ({ additionalProperties: false } }) +const getOverrideConfig = (overrides, filepath, config) => { + const length = overrides.length + + for (let i = 0; i < length; i++) { + if (pathIsMatch(filepath, overrides[i].files)) { + return { ...config, ...overrides[i].config } + } + } + + return config +} +const importPrefix = /^(?:(\.{1,2}\/)+)|^\/|^.+:\/\/\/?[.-\w]+\// -export { getOverrideConfig, getOverrideSchema, filepathIsMatch } +export { getOverrideConfig, getOverrideSchema, pathIsMatch, importPrefix } diff --git a/src/webpackChunkName.js b/src/webpackChunkName.js index db84162..ff823e1 100644 --- a/src/webpackChunkName.js +++ b/src/webpackChunkName.js @@ -6,7 +6,7 @@ const schema = getSchema({ type: 'object', properties: { active: { - type: 'boolean' + oneOf: [{ type: 'boolean' }, { instanceof: 'Function' }] }, basename: { type: 'boolean' @@ -14,18 +14,26 @@ const schema = getSchema({ }, additionalProperties: false }) -const webpackChunkName = (filepath, importPath, value) => { - const config = getConfig(value, filepath, { +const webpackChunkName = (filepath, importPath, value, match) => { + const config = getConfig(value, match, filepath, importPath, { active: true, basename: false }) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active - if (!config.active) { + if (!isActive) { return '' } + if (typeof config.dynamic === 'string') { + return `webpackChunkName: "${config.dynamic}"` + } + const { basename } = config - const { dir, name } = parse(importPath.replace(/['"`]/g, '')) + const { dir, name } = parse(importPath) const segments = `${dir}/${name}`.split('/').filter(segment => /\w/.test(segment)) const chunkName = basename ? name diff --git a/src/webpackExports.js b/src/webpackExports.js new file mode 100644 index 0000000..f11bb66 --- /dev/null +++ b/src/webpackExports.js @@ -0,0 +1,73 @@ +import { getOverrideConfig, getOverrideSchema } from './util' + +const configSchema = { + type: 'object', + properties: { + active: { + oneOf: [{ type: 'boolean' }, { instanceof: 'Function' }] + }, + exports: { + instanceof: 'Function' + } + }, + required: ['exports'], + additionalProperties: false +} +const schema = { + oneOf: [ + { instanceof: 'Function' }, + { + type: 'object', + properties: { + config: configSchema, + overrides: getOverrideSchema(configSchema) + }, + required: ['config'], + additionalProperties: false + } + ] +} +const defaultConfig = { + active: true +} +const getConfig = (value, filepath) => { + if (typeof value === 'function') { + return { + ...defaultConfig, + exports: value + } + } + + let config = { ...defaultConfig, ...value.config } + + if (Array.isArray(value.overrides)) { + config = getOverrideConfig(value.overrides, filepath, config) + } + + return config +} +const webpackExports = (filepath, importPath, value) => { + let exports = '' + let configExports = [] + const config = getConfig(value, filepath) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active + + if (!isActive || typeof config.exports !== 'function') { + return '' + } + + configExports = config.exports(filepath, importPath) + + if (!Array.isArray(configExports)) { + return '' + } + + exports = `["${configExports.reduce((curr, next) => `${curr}", "${next}`)}"]` + + return `webpackExports: ${exports}` +} + +export { webpackExports, schema } diff --git a/src/webpackIgnore.js b/src/webpackIgnore.js index 0d4dcf4..e68d64d 100644 --- a/src/webpackIgnore.js +++ b/src/webpackIgnore.js @@ -1,10 +1,14 @@ import { getSchema, getConfig } from './booleanComment.js' const schema = getSchema() -const webpackIgnore = (filepath, importPath, value) => { - const config = getConfig(value, filepath) +const webpackIgnore = (filepath, importPath, value, match) => { + const config = getConfig(value, match, filepath, importPath) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active - if (!config.active) { + if (!isActive) { return '' } diff --git a/src/webpackMode.js b/src/webpackMode.js index b77f5c9..791a0f1 100644 --- a/src/webpackMode.js +++ b/src/webpackMode.js @@ -5,22 +5,24 @@ const configSchema = { type: 'object', properties: { active: { - type: 'boolean' + oneOf: [{ type: 'boolean' }, { instanceof: 'Function' }] }, mode: { - enum: validModes + instanceof: 'Function' } }, additionalProperties: false } const schema = { - anyOf: [ + oneOf: [ { type: 'boolean' }, + { type: 'string' }, + { instanceof: 'Function' }, { type: 'object', properties: { config: configSchema, - overrides: getOverrideSchema(configSchema.properties) + overrides: getOverrideSchema(configSchema) }, required: ['config'], additionalProperties: false @@ -29,17 +31,28 @@ const schema = { } const defaultConfig = { active: true, - mode: 'lazy' + mode: () => 'lazy' } const getConfig = (value, filepath) => { + if (value === true) { + return defaultConfig + } + if (typeof value === 'string') { return { ...defaultConfig, - mode: value, + mode: () => value, active: validModes.includes(value) } } + if (typeof value === 'function') { + return { + ...defaultConfig, + mode: value + } + } + let config = { ...defaultConfig, ...value.config } if (Array.isArray(value.overrides)) { @@ -50,12 +63,22 @@ const getConfig = (value, filepath) => { } const webpackMode = (filepath, importPath, value) => { const config = getConfig(value, filepath) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active + + if (!isActive || typeof config.mode !== 'function') { + return '' + } + + const configMode = config.mode(filepath, importPath) - if (!config.active) { + if (!validModes.includes(configMode)) { return '' } - return `webpackMode: "${config.mode}"` + return `webpackMode: "${configMode}"` } export { webpackMode, schema } diff --git a/src/webpackPrefetch.js b/src/webpackPrefetch.js index 6e03a82..2f7ad01 100644 --- a/src/webpackPrefetch.js +++ b/src/webpackPrefetch.js @@ -1,10 +1,14 @@ import { getSchema, getConfig } from './booleanComment.js' const schema = getSchema() -const webpackPrefetch = (filepath, importPath, value) => { - const config = getConfig(value, filepath) +const webpackPrefetch = (filepath, importPath, value, match) => { + const config = getConfig(value, match, filepath, importPath) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active - if (!config.active) { + if (!isActive) { return '' } diff --git a/src/webpackPreload.js b/src/webpackPreload.js index de66719..d7078c0 100644 --- a/src/webpackPreload.js +++ b/src/webpackPreload.js @@ -1,10 +1,14 @@ import { getSchema, getConfig } from './booleanComment.js' const schema = getSchema() -const webpackPreload = (filepath, importPath, value) => { - const config = getConfig(value, filepath) +const webpackPreload = (filepath, importPath, value, match) => { + const config = getConfig(value, match, filepath, importPath) + const isActive = + typeof config.active === 'function' + ? config.active(filepath, importPath) + : config.active - if (!config.active) { + if (!isActive) { return '' }