From cf4311071e9f9c6c5a3f23dacc4d822795238fbf Mon Sep 17 00:00:00 2001 From: fedeoo Date: Tue, 18 Jul 2017 16:40:13 +0800 Subject: [PATCH 1/2] feat:handle version confilct --- src/index.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 4ccf3b0..9cbf43c 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ export default class ModulesCdnWebpackPlugin { this.exclude = exclude || []; this.only = only || null; this.verbose = verbose === true; + this.cacheModuleVersion = {}; } apply(compiler) { @@ -81,21 +82,33 @@ export default class ModulesCdnWebpackPlugin { return false; } + if (this.hasConflict(cdnConfig.name, version)) { + return false; + } + if (this.verbose) { console.log(`✔️ '${cdnConfig.name}' will be served by ${cdnConfig.url}`); } + let result = cdnConfig.var; if (peerDependencies) { for (const peerDependencyName in peerDependencies) { if ({}.hasOwnProperty.call(peerDependencies, peerDependencyName)) { - this.addModule(contextPath, peerDependencyName, {env}); + result = result && this.addModule(contextPath, peerDependencyName, {env}); } } } - this.urls[cdnConfig.name] = cdnConfig.url; + if (result) { + this.urls[cdnConfig.name] = cdnConfig.url; + this.cacheModuleVersion[cdnConfig.name] = version; + } + + return result; + } - return cdnConfig.var; + hasConflict(varName, version) { + return this.cacheModuleVersion[varName] && this.cacheModuleVersion[varName] !== version; } applyWebpackCore(compiler) { From 4ca5dc71b86c7074108bbf0d384e052b7e1a9288 Mon Sep 17 00:00:00 2001 From: Thomas Sileghem Date: Wed, 19 Jul 2017 13:09:07 +0100 Subject: [PATCH 2/2] fix: don't overwrite modules when using multiple versions --- src/index.js | 45 ++++++------ test/core.js | 35 ++++++++++ test/fixtures/app/dir/single.js | 1 + test/fixtures/app/mix.js | 2 + test/fixtures/app/node_modules/a/a.js | 2 + .../a/node_modules/react/package.json | 68 +++++++++++++++++++ .../a/node_modules/react/react.js | 1 + 7 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 test/fixtures/app/dir/single.js create mode 100644 test/fixtures/app/node_modules/a/node_modules/react/package.json create mode 100644 test/fixtures/app/node_modules/a/node_modules/react/react.js diff --git a/src/index.js b/src/index.js index 9cbf43c..f7624a3 100644 --- a/src/index.js +++ b/src/index.js @@ -21,11 +21,11 @@ export default class ModulesCdnWebpackPlugin { this.disable = disable; this.env = env || process.env.NODE_ENV || 'development'; - this.urls = {}; this.exclude = exclude || []; this.only = only || null; this.verbose = verbose === true; - this.cacheModuleVersion = {}; + + this.modulesFromCdn = {}; } apply(compiler) { @@ -73,6 +73,16 @@ export default class ModulesCdnWebpackPlugin { const {version, peerDependencies} = readPkgUp({cwd: resolvePkg(modulePath, {cwd: contextPath})}).pkg; + const isModuleAlreadyLoaded = Boolean(this.modulesFromCdn[modulePath]); + if (isModuleAlreadyLoaded) { + const isSameVersion = this.modulesFromCdn[modulePath].version === version; + if (isSameVersion) { + return modulePath; + } + + return false; + } + const cdnConfig = moduleToCdn(modulePath, version, {env}); if (cdnConfig == null) { @@ -82,33 +92,26 @@ export default class ModulesCdnWebpackPlugin { return false; } - if (this.hasConflict(cdnConfig.name, version)) { - return false; - } - if (this.verbose) { console.log(`✔️ '${cdnConfig.name}' will be served by ${cdnConfig.url}`); } - let result = cdnConfig.var; if (peerDependencies) { for (const peerDependencyName in peerDependencies) { if ({}.hasOwnProperty.call(peerDependencies, peerDependencyName)) { - result = result && this.addModule(contextPath, peerDependencyName, {env}); + this.addModule(contextPath, peerDependencyName, {env}); } } } - if (result) { - this.urls[cdnConfig.name] = cdnConfig.url; - this.cacheModuleVersion[cdnConfig.name] = version; - } - - return result; - } + // TODO: on next breaking change, rely on module-to-cdn>=3.1.0 to get version + this.modulesFromCdn[modulePath] = Object.assign( + {}, + cdnConfig, + {version} + ); - hasConflict(varName, version) { - return this.cacheModuleVersion[varName] && this.cacheModuleVersion[varName] !== version; + return cdnConfig.var; } applyWebpackCore(compiler) { @@ -116,11 +119,11 @@ export default class ModulesCdnWebpackPlugin { const entrypoint = compilation.entrypoints[Object.keys(compilation.entrypoints)[0]]; const parentChunk = entrypoint.chunks.find(x => x.isInitial()); - for (const name of Object.keys(this.urls)) { - const url = this.urls[name]; + for (const name of Object.keys(this.modulesFromCdn)) { + const cdnConfig = this.modulesFromCdn[name]; const chunk = compilation.addChunk(name); - chunk.files.push(url); + chunk.files.push(cdnConfig.url); chunk.parents = [parentChunk]; parentChunk.addChunk(chunk); @@ -141,7 +144,7 @@ export default class ModulesCdnWebpackPlugin { includeAssetsPlugin.apply(compiler); compiler.plugin('after-compile', (compilation, cb) => { - const assets = Object.values(this.urls); + const assets = Object.keys(this.modulesFromCdn).map(key => this.modulesFromCdn[key].url); // HACK: Calling the constructor directly is not recomended // But that's the only secure way to edit `assets` afterhand diff --git a/test/core.js b/test/core.js index 53be913..eab9c43 100644 --- a/test/core.js +++ b/test/core.js @@ -391,3 +391,38 @@ test('async loading', async t => { const doesIncludeReact = outputs.some(output => includes(output, 'THIS IS REACT!')); t.false(doesIncludeReact); }); + +test('when using multiple versions of a module, make sure the right version is used for each', async t => { + await cleanDir(path.resolve(__dirname, './fixtures/output/multiple-versions')); + + const stats = await runWebpack({ + context: path.resolve(__dirname, './fixtures/app'), + + output: { + publicPath: '', + path: path.resolve(__dirname, './fixtures/output/multiple-versions') + }, + + entry: { + app: './mix.js' + }, + + plugins: [ + new ModulesCdnWebpackPlugin() + ] + }); + + const files = stats.compilation.chunks.reduce((files, x) => files.concat(x.files), []); + + t.true(includes(files, 'app.js')); + t.true(includes(files, 'https://unpkg.com/react@15.6.1/dist/react.js')); + + const output = await fs.readFile(path.resolve(__dirname, './fixtures/output/multiple-versions/app.js')); + + // NOTE: not inside t.false to prevent ava to display whole file in console + const doesIncludeReact14 = includes(output, 'THIS IS REACT@0.14.9!'); + t.true(doesIncludeReact14); + + const doesIncludeReact = includes(output, 'THIS IS REACT!'); + t.false(doesIncludeReact); +}); diff --git a/test/fixtures/app/dir/single.js b/test/fixtures/app/dir/single.js new file mode 100644 index 0000000..aaf4046 --- /dev/null +++ b/test/fixtures/app/dir/single.js @@ -0,0 +1 @@ +import React from 'react'; diff --git a/test/fixtures/app/mix.js b/test/fixtures/app/mix.js index be7e2dd..20b8dc8 100644 --- a/test/fixtures/app/mix.js +++ b/test/fixtures/app/mix.js @@ -1,2 +1,4 @@ import a from 'a'; import React from 'react'; + +import single from './dir/single'; diff --git a/test/fixtures/app/node_modules/a/a.js b/test/fixtures/app/node_modules/a/a.js index d8326ac..554db1c 100644 --- a/test/fixtures/app/node_modules/a/a.js +++ b/test/fixtures/app/node_modules/a/a.js @@ -1 +1,3 @@ +import React from 'react'; + console.log('THIS IS A!'); diff --git a/test/fixtures/app/node_modules/a/node_modules/react/package.json b/test/fixtures/app/node_modules/a/node_modules/react/package.json new file mode 100644 index 0000000..be720f4 --- /dev/null +++ b/test/fixtures/app/node_modules/a/node_modules/react/package.json @@ -0,0 +1,68 @@ +{ + "_from": "react", + "_id": "react@0.14.9", + "_inBundle": false, + "_integrity": "sha1-uqhDTsZ4C96ZfNw4C3nNM7ljk98=", + "_location": "/react", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "react", + "name": "react", + "escapedName": "react", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz", + "_shasum": "baa8434ec6780bde997cdc380b79cd33b96393df", + "_spec": "react", + "_where": "/home/thomas/Code/modules-cdn-webpack-plugin/test/fixtures/multiple", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "bundleDependencies": false, + "dependencies": { + "create-react-class": "^15.6.0", + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + }, + "deprecated": false, + "description": "React is a JavaScript library for building user interfaces.", + "engines": { + "node": ">=0.10.0" + }, + "files": [ + "LICENSE", + "PATENTS", + "addons.js", + "react.js", + "addons/", + "dist/", + "lib/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "react.js", + "name": "react", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "version": "0.14.9" +} diff --git a/test/fixtures/app/node_modules/a/node_modules/react/react.js b/test/fixtures/app/node_modules/a/node_modules/react/react.js new file mode 100644 index 0000000..95a0187 --- /dev/null +++ b/test/fixtures/app/node_modules/a/node_modules/react/react.js @@ -0,0 +1 @@ +console.log('THIS IS REACT@0.14.9!');