From 5b724d4ece27eefc12ddefeaeae2438b93b8d026 Mon Sep 17 00:00:00 2001 From: Bala Bosch Date: Wed, 26 Sep 2018 09:39:37 -0400 Subject: [PATCH 1/4] feat: expose multiple constructors --- src/DynamicCdnWebpackPlugin.js | 129 ++++++++++++++++++++++ src/HtmlDynamicCdnWebpackPlugin.js | 32 ++++++ src/index.js | 171 +---------------------------- 3 files changed, 163 insertions(+), 169 deletions(-) create mode 100644 src/DynamicCdnWebpackPlugin.js create mode 100644 src/HtmlDynamicCdnWebpackPlugin.js diff --git a/src/DynamicCdnWebpackPlugin.js b/src/DynamicCdnWebpackPlugin.js new file mode 100644 index 0000000..8dc180d --- /dev/null +++ b/src/DynamicCdnWebpackPlugin.js @@ -0,0 +1,129 @@ +import readPkgUp from 'read-pkg-up'; +import ExternalModule from 'webpack/lib/ExternalModule'; +import resolvePkg from 'resolve-pkg'; + +import getResolver from './get-resolver'; + +export const pluginName = 'dynamic-cdn-webpack-plugin'; +const moduleRegex = /^((?:@[a-z0-9][\w-.]+\/)?[a-z0-9][\w-.]*)/; + +const getEnvironment = mode => { + switch (mode) { + case 'none': + case 'development': + return 'development'; + + default: + return 'production'; + } +}; + +export default class DynamicCdnWebpackPlugin { + constructor({disable = false, env, exclude, only, verbose, resolver} = {}) { + if (exclude && only) { + throw new Error('You can\'t use \'exclude\' and \'only\' at the same time'); + } + + this.disable = disable; + this.env = env; + this.exclude = exclude || []; + this.only = only || null; + this.verbose = verbose === true; + this.resolver = getResolver(resolver); + + this.modulesFromCdn = {}; + } + + apply(compiler) { + if (!this.disable) { + this.execute(compiler, {env: this.env || getEnvironment(compiler.options.mode)}); + } + this.output(compiler); + } + + execute(compiler, {env}) { + compiler.hooks.normalModuleFactory.tap(pluginName, nmf => { + nmf.hooks.factory.tap(pluginName, factory => async (data, cb) => { + const modulePath = data.dependencies[0].request; + const contextPath = data.context; + + const isModulePath = moduleRegex.test(modulePath); + if (!isModulePath) { + return factory(data, cb); + } + + const varName = await this.addModule(contextPath, modulePath, {env}); + + if (varName === false) { + factory(data, cb); + } else if (varName == null) { + cb(null); + } else { + cb(null, new ExternalModule(varName, 'var', modulePath)); + } + }); + }); + } + + async addModule(contextPath, modulePath, {env}) { + const isModuleExcluded = this.exclude.includes(modulePath) || + (this.only && !this.only.includes(modulePath)); + if (isModuleExcluded) { + return false; + } + + const moduleName = modulePath.match(moduleRegex)[1]; + const {pkg: {version, peerDependencies}} = await readPkgUp({cwd: resolvePkg(moduleName, {cwd: contextPath})}); + + const isModuleAlreadyLoaded = Boolean(this.modulesFromCdn[modulePath]); + if (isModuleAlreadyLoaded) { + const isSameVersion = this.modulesFromCdn[modulePath].version === version; + if (isSameVersion) { + return this.modulesFromCdn[modulePath].var; + } + + return false; + } + + const cdnConfig = await this.resolver(modulePath, version, {env}); + + if (cdnConfig == null) { + if (this.verbose) { + console.log(`❌ '${modulePath}' couldn't be found, please add it to https://github.com/mastilver/module-to-cdn/blob/master/modules.json`); + } + return false; + } + + if (this.verbose) { + console.log(`✔️ '${cdnConfig.name}' will be served by ${cdnConfig.url}`); + } + + if (peerDependencies) { + const arePeerDependenciesLoaded = (await Promise.all(Object.keys(peerDependencies).map(peerDependencyName => { + return this.addModule(contextPath, peerDependencyName, {env}); + }))) + .map(x => Boolean(x)) + .reduce((result, x) => result && x, true); + + if (!arePeerDependenciesLoaded) { + return false; + } + } + + this.modulesFromCdn[modulePath] = cdnConfig; + + return cdnConfig.var; + } + + output(compiler) { + compiler.hooks.afterCompile.tapAsync(pluginName, (compilation, cb) => { + for (const [name, cdnConfig] of Object.entries(this.modulesFromCdn)) { + compilation.addChunkInGroup(name); + const chunk = compilation.addChunk(name); + chunk.files.push(cdnConfig.url); + } + + cb(); + }); + } +} diff --git a/src/HtmlDynamicCdnWebpackPlugin.js b/src/HtmlDynamicCdnWebpackPlugin.js new file mode 100644 index 0000000..d230df8 --- /dev/null +++ b/src/HtmlDynamicCdnWebpackPlugin.js @@ -0,0 +1,32 @@ +import HtmlWebpackPlugin from "html-webpack-plugin"; +import HtmlWebpackIncludeAssetsPlugin from "html-webpack-include-assets-plugin"; + +import DynamicCdnWebpackPlugin, { pluginName } from "./DynamicCdnWebpackPlugin"; + +export default class HtmlDynamicCdnWebpackPlugin extends DynamicCdnWebpackPlugin { + output(compiler) { + const includeAssetsPlugin = new HtmlWebpackIncludeAssetsPlugin({ + assets: [], + publicPath: "", + append: false + }); + + includeAssetsPlugin.apply(compiler); + + compiler.hooks.afterCompile.tapAsync(pluginName, (compilation, cb) => { + const assets = Object.values(this.modulesFromCdn).map( + moduleFromCdn => moduleFromCdn.url + ); + + // HACK: Calling the constructor directly is not recomended + // But that's the only secure way to edit `assets` afterhand + includeAssetsPlugin.constructor({ + assets, + publicPath: "", + append: false + }); + + cb(); + }); + } +} diff --git a/src/index.js b/src/index.js index 7d6510a..334e615 100644 --- a/src/index.js +++ b/src/index.js @@ -1,169 +1,2 @@ -import readPkgUp from 'read-pkg-up'; -import HtmlWebpackIncludeAssetsPlugin from 'html-webpack-include-assets-plugin'; -import ExternalModule from 'webpack/lib/ExternalModule'; -import resolvePkg from 'resolve-pkg'; - -import getResolver from './get-resolver'; - -const pluginName = 'dynamic-cdn-webpack-plugin'; -let HtmlWebpackPlugin; -try { - // eslint-disable-next-line import/no-extraneous-dependencies - HtmlWebpackPlugin = require('html-webpack-plugin'); -} catch (err) { - HtmlWebpackPlugin = null; -} - -const moduleRegex = /^((?:@[a-z0-9][\w-.]+\/)?[a-z0-9][\w-.]*)/; - -const getEnvironment = mode => { - switch (mode) { - case 'none': - case 'development': - return 'development'; - - default: - return 'production'; - } -}; - -export default class DynamicCdnWebpackPlugin { - constructor({disable = false, env, exclude, only, verbose, resolver} = {}) { - if (exclude && only) { - throw new Error('You can\'t use \'exclude\' and \'only\' at the same time'); - } - - this.disable = disable; - this.env = env; - this.exclude = exclude || []; - this.only = only || null; - this.verbose = verbose === true; - this.resolver = getResolver(resolver); - - this.modulesFromCdn = {}; - } - - apply(compiler) { - if (!this.disable) { - this.execute(compiler, {env: this.env || getEnvironment(compiler.options.mode)}); - } - - const isUsingHtmlWebpackPlugin = HtmlWebpackPlugin != null && compiler.options.plugins.some(x => x instanceof HtmlWebpackPlugin); - - if (isUsingHtmlWebpackPlugin) { - this.applyHtmlWebpackPlugin(compiler); - } else { - this.applyWebpackCore(compiler); - } - } - - execute(compiler, {env}) { - compiler.hooks.normalModuleFactory.tap(pluginName, nmf => { - nmf.hooks.factory.tap(pluginName, factory => async (data, cb) => { - const modulePath = data.dependencies[0].request; - const contextPath = data.context; - - const isModulePath = moduleRegex.test(modulePath); - if (!isModulePath) { - return factory(data, cb); - } - - const varName = await this.addModule(contextPath, modulePath, {env}); - - if (varName === false) { - factory(data, cb); - } else if (varName == null) { - cb(null); - } else { - cb(null, new ExternalModule(varName, 'var', modulePath)); - } - }); - }); - } - - async addModule(contextPath, modulePath, {env}) { - const isModuleExcluded = this.exclude.includes(modulePath) || - (this.only && !this.only.includes(modulePath)); - if (isModuleExcluded) { - return false; - } - - const moduleName = modulePath.match(moduleRegex)[1]; - const {pkg: {version, peerDependencies}} = await readPkgUp({cwd: resolvePkg(moduleName, {cwd: contextPath})}); - - const isModuleAlreadyLoaded = Boolean(this.modulesFromCdn[modulePath]); - if (isModuleAlreadyLoaded) { - const isSameVersion = this.modulesFromCdn[modulePath].version === version; - if (isSameVersion) { - return this.modulesFromCdn[modulePath].var; - } - - return false; - } - - const cdnConfig = await this.resolver(modulePath, version, {env}); - - if (cdnConfig == null) { - if (this.verbose) { - console.log(`❌ '${modulePath}' couldn't be found, please add it to https://github.com/mastilver/module-to-cdn/blob/master/modules.json`); - } - return false; - } - - if (this.verbose) { - console.log(`✔️ '${cdnConfig.name}' will be served by ${cdnConfig.url}`); - } - - if (peerDependencies) { - const arePeerDependenciesLoaded = (await Promise.all(Object.keys(peerDependencies).map(peerDependencyName => { - return this.addModule(contextPath, peerDependencyName, {env}); - }))) - .map(x => Boolean(x)) - .reduce((result, x) => result && x, true); - - if (!arePeerDependenciesLoaded) { - return false; - } - } - - this.modulesFromCdn[modulePath] = cdnConfig; - - return cdnConfig.var; - } - - applyWebpackCore(compiler) { - compiler.hooks.afterCompile.tapAsync(pluginName, (compilation, cb) => { - for (const [name, cdnConfig] of Object.entries(this.modulesFromCdn)) { - compilation.addChunkInGroup(name); - const chunk = compilation.addChunk(name); - chunk.files.push(cdnConfig.url); - } - - cb(); - }); - } - - applyHtmlWebpackPlugin(compiler) { - const includeAssetsPlugin = new HtmlWebpackIncludeAssetsPlugin({ - assets: [], - publicPath: '', - append: false - }); - - includeAssetsPlugin.apply(compiler); - - compiler.hooks.afterCompile.tapAsync(pluginName, (compilation, cb) => { - const assets = Object.values(this.modulesFromCdn).map(moduleFromCdn => moduleFromCdn.url); - - // HACK: Calling the constructor directly is not recomended - // But that's the only secure way to edit `assets` afterhand - includeAssetsPlugin.constructor({ - assets, - publicPath: '', - append: false - }); - - cb(); - }); - } -} +export { default as HtmlDynamicCdnWebpackPlugin } from "./HtmlDynamicCdnWebpackPlugin"; +export { default } from "./DynamicCdnWebpackPlugin"; From 58077d3f8c8736c42622f7d207ea867921e783ff Mon Sep 17 00:00:00 2001 From: Bala Bosch Date: Wed, 26 Sep 2018 10:22:20 -0400 Subject: [PATCH 2/4] fix: linting errors --- index.js | 3 +-- ...WebpackPlugin.js => dynamic-cdn-webpack-plugin.js} | 0 ...ckPlugin.js => html-dynamic-cdn-webpack-plugin.js} | 11 ++++++----- src/index.js | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/{DynamicCdnWebpackPlugin.js => dynamic-cdn-webpack-plugin.js} (100%) rename src/{HtmlDynamicCdnWebpackPlugin.js => html-dynamic-cdn-webpack-plugin.js} (71%) diff --git a/index.js b/index.js index c638d29..bb0a047 100644 --- a/index.js +++ b/index.js @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-unresolved -module.exports = require('./lib').default; +module.exports = require('./lib'); diff --git a/src/DynamicCdnWebpackPlugin.js b/src/dynamic-cdn-webpack-plugin.js similarity index 100% rename from src/DynamicCdnWebpackPlugin.js rename to src/dynamic-cdn-webpack-plugin.js diff --git a/src/HtmlDynamicCdnWebpackPlugin.js b/src/html-dynamic-cdn-webpack-plugin.js similarity index 71% rename from src/HtmlDynamicCdnWebpackPlugin.js rename to src/html-dynamic-cdn-webpack-plugin.js index d230df8..6762997 100644 --- a/src/HtmlDynamicCdnWebpackPlugin.js +++ b/src/html-dynamic-cdn-webpack-plugin.js @@ -1,13 +1,14 @@ -import HtmlWebpackPlugin from "html-webpack-plugin"; -import HtmlWebpackIncludeAssetsPlugin from "html-webpack-include-assets-plugin"; +// eslint-disable-next-line no-unused-vars +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import HtmlWebpackIncludeAssetsPlugin from 'html-webpack-include-assets-plugin'; -import DynamicCdnWebpackPlugin, { pluginName } from "./DynamicCdnWebpackPlugin"; +import DynamicCdnWebpackPlugin, {pluginName} from './dynamic-cdn-webpack-plugin'; export default class HtmlDynamicCdnWebpackPlugin extends DynamicCdnWebpackPlugin { output(compiler) { const includeAssetsPlugin = new HtmlWebpackIncludeAssetsPlugin({ assets: [], - publicPath: "", + publicPath: '', append: false }); @@ -22,7 +23,7 @@ export default class HtmlDynamicCdnWebpackPlugin extends DynamicCdnWebpackPlugin // But that's the only secure way to edit `assets` afterhand includeAssetsPlugin.constructor({ assets, - publicPath: "", + publicPath: '', append: false }); diff --git a/src/index.js b/src/index.js index 334e615..6868bce 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ -export { default as HtmlDynamicCdnWebpackPlugin } from "./HtmlDynamicCdnWebpackPlugin"; -export { default } from "./DynamicCdnWebpackPlugin"; +export {default as HtmlDynamicCdnWebpackPlugin} from './html-dynamic-cdn-webpack-plugin'; +export {default} from './dynamic-cdn-webpack-plugin'; From 82a1b4feca49548e46f12c9a5ff57598a39ee96a Mon Sep 17 00:00:00 2001 From: Bala Bosch Date: Wed, 26 Sep 2018 10:36:02 -0400 Subject: [PATCH 3/4] fix: tests fail --- test/html-webpack-plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/html-webpack-plugin.js b/test/html-webpack-plugin.js index 43d19f4..ed32430 100644 --- a/test/html-webpack-plugin.js +++ b/test/html-webpack-plugin.js @@ -4,7 +4,7 @@ import fs from 'mz/fs'; import test from 'ava'; import HtmlWebpackPlugin from 'html-webpack-plugin'; -import DynamicCdnWebpackPlugin from '../src'; +import {HtmlDynamicCdnWebpackPlugin} from '../src'; import runWebpack from './helpers/run-webpack'; import cleanDir from './helpers/clean-dir'; @@ -26,7 +26,7 @@ test('html-webpack-plugin', async t => { plugins: [ new HtmlWebpackPlugin(), - new DynamicCdnWebpackPlugin() + new HtmlDynamicCdnWebpackPlugin() ] }); From 338e162501054b1fd3cd27de31d7a7049a425971 Mon Sep 17 00:00:00 2001 From: Bala Bosch Date: Wed, 26 Sep 2018 11:37:07 -0400 Subject: [PATCH 4/4] fix: exports not working --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 6868bce..7a67945 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ export {default as HtmlDynamicCdnWebpackPlugin} from './html-dynamic-cdn-webpack-plugin'; -export {default} from './dynamic-cdn-webpack-plugin'; +export {default as DynamicCdnWebpackPlugin} from './dynamic-cdn-webpack-plugin';