Skip to content

Commit

Permalink
feat: allow loading scripts dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
forivall committed Jan 20, 2022
1 parent 0c1ff12 commit b5ec439
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 27 deletions.
11 changes: 0 additions & 11 deletions jest.config.js

This file was deleted.

13 changes: 13 additions & 0 deletions readme.md
Expand Up @@ -202,6 +202,19 @@ Default: `'module-to-cdn'`
Allow you to define a custom module resolver, it can either be a `function` or an npm module.
The resolver should return (or resolve as a Promise) either `null` or an `object` with the keys: `name`, `var`, `url`, `version`.

#### options.html
Type: `boolean`<br>
Default: `!loadScripts` (`true`, unless `options.loadScripts` is true)

Inject the CDN script tags into the `HtmlWebpackPlugin`, if available

#### options.loadScripts
Type: `boolean`<br>
Default: `false`

Instead of expecting the scripts to already be loaded via a `<script src="..."></script>` in the html, load them dynamically.
Uses [webpack externalsType.script](https://webpack.js.org/configuration/externals/#externalstypescript)


## Related

Expand Down
5 changes: 0 additions & 5 deletions src/declarations.d.ts

This file was deleted.

37 changes: 26 additions & 11 deletions src/index.ts
@@ -1,8 +1,8 @@
import type HtmlWebpackPlugin from 'html-webpack-plugin';
import readPkgUp from 'read-pkg-up';
import resolvePkg from 'resolve-pkg';
import { ExternalModule } from 'webpack';
import type { Compiler } from 'webpack';
import ExternalModule from 'webpack/lib/ExternalModule';

import getResolver from './get-resolver';
import { CdnModuleInfo, CdnModuleResolver, PluginOptions } from './types';
Expand All @@ -28,12 +28,23 @@ export default class DynamicCdnWebpackPlugin {
verbose: boolean;
resolver: CdnModuleResolver;
modulesFromCdn: { [modulePath: string]: CdnModuleInfo };
loadScripts: boolean;
htmlWebpackPlugin?: typeof HtmlWebpackPlugin;

constructor(
options: PluginOptions = {},
htmlWebpackPlugin?: typeof HtmlWebpackPlugin | false
) {
const { disable = false, env, exclude, only, verbose, resolver } = options;
const {
disable = false,
env,
exclude,
only,
verbose,
resolver,
loadScripts = false,
html = !loadScripts,
} = options;
if (exclude && only) {
throw new Error("You can't use 'exclude' and 'only' at the same time");
}
Expand All @@ -44,9 +55,9 @@ export default class DynamicCdnWebpackPlugin {
this.only = only;
this.verbose = verbose === true;
this.resolver = getResolver(resolver);
this.loadScripts = loadScripts;
this.modulesFromCdn = {};
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
this.htmlWebpackPlugin = htmlWebpackPlugin || undefined;
this.htmlWebpackPlugin = (html && htmlWebpackPlugin) || undefined;

// Support for old way without passing htmlWebpackPlugin as an argument
if (htmlWebpackPlugin === undefined) {
Expand All @@ -71,10 +82,14 @@ export default class DynamicCdnWebpackPlugin {
if (!moduleRegex.test(modulePath)) return;

// Use recognized CDN module if found
const varName = await this.addModule(contextPath, modulePath, { env });
return typeof varName === 'string'
? new ExternalModule(varName, 'var', modulePath)
: undefined;
const info = await this.addModule(contextPath, modulePath, { env });
if (!info) return;

const varName = info.var;

return this.loadScripts
? new ExternalModule([info.url, varName], 'script', modulePath)
: new ExternalModule(varName, 'var', modulePath);
});
});

Expand Down Expand Up @@ -116,7 +131,7 @@ export default class DynamicCdnWebpackPlugin {
contextPath: string,
modulePath: string,
{ env }: { env: string }
) {
): Promise<false | CdnModuleInfo> {
const isModuleExcluded =
this.exclude.includes(modulePath) ||
(this.only && !this.only.includes(modulePath));
Expand Down Expand Up @@ -146,7 +161,7 @@ export default class DynamicCdnWebpackPlugin {
const isModuleAlreadyLoaded = Boolean(this.modulesFromCdn[modulePath]);
if (isModuleAlreadyLoaded) {
const isSameVersion = this.modulesFromCdn[modulePath].version === version;
return isSameVersion ? this.modulesFromCdn[modulePath].var : false;
return isSameVersion ? this.modulesFromCdn[modulePath] : false;
}

const cdnConfig = await this.resolver(modulePath, version, { env });
Expand Down Expand Up @@ -180,6 +195,6 @@ export default class DynamicCdnWebpackPlugin {
}

this.modulesFromCdn[modulePath] = cdnConfig;
return cdnConfig.var;
return cdnConfig;
}
}
23 changes: 23 additions & 0 deletions src/types.ts
Expand Up @@ -22,12 +22,35 @@ export interface CdnModuleInfo {
}

export interface PluginOptions {
/** Useful when working offline, will fallback to webpack normal behaviour */
disable?: boolean;
/**
* Determine if it should load the development or the production version of modules
* @default `mode`
*/
env?: string;
/** List the only modules that should be served by the cdn */
only?: string[];
/** List the modules that will always be bundled (not be served by the cdn) */
exclude?: string[];
/** Log whether the library is being served by the cdn or is bundled */
verbose?: boolean;
/**
* Allow you to define a custom module resolver, it can either be a `function` or an npm module.
* The resolver should return (or resolve as a Promise) either `null` or an `object` with the keys: `name`, `var`, `url`, `version`.
* @default require('module-to-cdn')
*/
resolver?: string | CdnModuleResolver;
/**
* Inject the CDN script tags into the `HtmlWebpackPlugin`, if available
* @default !loadScripts // (`true`, unless `options.loadScripts` is true)
*/
html?: boolean;
/**
* Instead of expecting the scripts to already be loaded via a `<script src="..."></script>` in the html, load them dynamically.
* @see https://webpack.js.org/configuration/externals/#externalstypescript Uses webpack externalsType.script
*/
loadScripts?: boolean;
}

declare module 'webpack' {
Expand Down

0 comments on commit b5ec439

Please sign in to comment.