diff --git a/README.md b/README.md index f4aa82e..a986d8f 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,11 @@ Set to `true` to ignore every module not actually required in your bundle. ##### `crossOrigin`:`string` (optional) -Allows you to specify a custom `crossorigin` attribute of either `"anonymous"` or `"use-credentials"`, to configure the CORS requests for the element's fetched data. Visit [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for more information. +Allows you to specify a custom `crossorigin` attribute of either `"anonymous"` or `"use-credentials"`, to configure the CORS requests for the element's fetched data. Visit [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for more information. This setting is ignored when writing to a manifest (`manifest: true`). + +##### `manifest`:`boolean` | `false` + +Instead of writing asset paths to an HTML template via `HtmlWebpackPlugin`, a `cdn-manifes.json` file will be created that contains the paths to your assets. ### Contribution diff --git a/module.js b/module.js index 7cb4a91..30aef81 100644 --- a/module.js +++ b/module.js @@ -15,6 +15,7 @@ class WebpackCdnPlugin { publicPath, optimize = false, crossOrigin = false, + manifest = false, }) { this.modules = Array.isArray(modules) ? { [DEFAULT_MODULE_KEY]: modules } : modules; this.prod = prod !== false; @@ -22,6 +23,7 @@ class WebpackCdnPlugin { this.url = this.prod ? prodUrl : devUrl; this.optimize = optimize; this.crossOrigin = crossOrigin; + this.manifest = manifest; } apply(compiler) { @@ -41,28 +43,48 @@ class WebpackCdnPlugin { const getArgs = [this.url, this.prefix, this.prod, output.publicPath]; compiler.hooks.compilation.tap('WebpackCdnPlugin', (compilation) => { - compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync( - 'WebpackCdnPlugin', - (data, callback) => { - const moduleId = data.plugin.options.cdnModule; - if (moduleId !== false) { - let modules = this.modules[moduleId || Reflect.ownKeys(this.modules)[0]]; - if (modules) { - if (this.optimize) { - const usedModules = WebpackCdnPlugin._getUsedModules(compilation); - modules = modules.filter(p => usedModules[p.name]); + if (this.manifest) { + compiler.hooks.compilation.tap('done', () => { + const assets = {}; + const modules = this.modules[Reflect.ownKeys(this.modules)[0]]; + WebpackCdnPlugin._cleanModules(modules); + assets.js = WebpackCdnPlugin._getJs(modules, ...getArgs); + assets.css = WebpackCdnPlugin._getCss(modules, ...getArgs); + compilation.assets['cdn-manifest.json'] = { + source() { + return JSON.stringify(assets); + }, + size() { + return assets.length; + }, + }; + }); + } else { + compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync( + 'WebpackCdnPlugin', + (data, callback) => { + const moduleId = data.plugin.options.cdnModule; + if (moduleId !== false) { + let modules = this.modules[moduleId || Reflect.ownKeys(this.modules)[0]]; + if (modules) { + if (this.optimize) { + const usedModules = WebpackCdnPlugin._getUsedModules(compilation); + modules = modules.filter(p => usedModules[p.name]); + } + + WebpackCdnPlugin._cleanModules(modules); + data.assets.js = WebpackCdnPlugin._getJs(modules, ...getArgs).concat( + data.assets.js, + ); + data.assets.css = WebpackCdnPlugin._getCss(modules, ...getArgs).concat( + data.assets.css, + ); } - - WebpackCdnPlugin._cleanModules(modules); - data.assets.js = WebpackCdnPlugin._getJs(modules, ...getArgs).concat(data.assets.js); - data.assets.css = WebpackCdnPlugin._getCss(modules, ...getArgs).concat( - data.assets.css, - ); } - } - callback(null, data); - }, - ); + callback(null, data); + }, + ); + } }); const externals = compiler.options.externals || {}; @@ -77,7 +99,7 @@ class WebpackCdnPlugin { compiler.options.externals = externals; - if (this.prod && this.crossOrigin) { + if (this.prod && this.crossOrigin && !this.manifest) { compiler.hooks.afterPlugins.tap('WebpackCdnPlugin', () => { compiler.hooks.thisCompilation.tap('WebpackCdnPlugin', () => { compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', (compilation) => { diff --git a/spec/webpack.spec.js b/spec/webpack.spec.js index 979fb50..ce54a1a 100644 --- a/spec/webpack.spec.js +++ b/spec/webpack.spec.js @@ -14,6 +14,7 @@ let cssCrossOrigin; let jsCrossOrigin; let cssCrossOrigin2; let jsCrossOrigin2; +let manifestFile; const versions = { jasmine: WebpackCdnPlugin.getVersion('jasmine'), @@ -34,6 +35,7 @@ function runWebpack(callback, config) { jsCrossOrigin = []; cssCrossOrigin2 = []; jsCrossOrigin2 = []; + manifestFile = null; const compiler = webpack(config); compiler.outputFileSystem = fs; @@ -60,7 +62,7 @@ function runWebpack(callback, config) { jsAssets2.push(matches[1]); jsCrossOrigin2.push(/crossorigin="anonymous"/.test(matches[2])); } - + manifestFile = stats.compilation.assets['cdn-manifest.json'] && JSON.parse(stats.compilation.assets['cdn-manifest.json'].source()); callback(); }); } @@ -76,6 +78,7 @@ function getConfig({ multipleFiles, optimize, crossOrigin, + manifest, }) { const output = { path: path.join(__dirname, 'dist/assets'), @@ -141,6 +144,7 @@ function getConfig({ prodUrl, optimize, crossOrigin, + manifest, }; if (publicPath) { @@ -339,6 +343,34 @@ describe('Webpack Integration', () => { expect(jsCrossOrigin).toEqual([false, true, true, true, false]); }); }); + + describe('With `manifest`', () => { + beforeAll((done) => { + runWebpack( + done, + getConfig({ + prod: true, manifest: true, + }), + ); + }); + + it('should output the manifest json', () => { + expect(manifestFile).toEqual(jasmine.any(Object)); + }); + + it('should output the manifest with right assets (js)', () => { + expect(manifestFile.js).toEqual(['/assets/local.js', + 'https://unpkg.com/jasmine-spec-reporter@4.2.1/index.js', + 'https://unpkg.com/nyc@13.3.0/index.js', + 'https://unpkg.com/jasmine2@3.3.1/lib/jasmine.js']); + }); + + it('should output the manifest with right assets (css)', () => { + expect(manifestFile.css).toEqual(['/assets/local.css', + 'https://unpkg.com/nyc@13.3.0/style.css', + 'https://unpkg.com/jasmine2@3.3.1/style.css']); + }); + }); }); describe('When `prod` is false', () => { @@ -410,7 +442,7 @@ describe('Webpack Integration', () => { beforeAll((done) => { runWebpack(done, getConfig({ prod: false, - moduleDevUrl: ":name/dist/:path", + moduleDevUrl: ':name/dist/:path', })); }); @@ -517,5 +549,32 @@ describe('Webpack Integration', () => { expect(jsAssets).toEqual(['/jasmine/lib/jasmine.js', '/app.js']); }); }); + + + describe('With `manifest`', () => { + beforeAll((done) => { + runWebpack( + done, + getConfig({ + prod: false, publicPath: null, publicPath2: null, optimize: false, manifest: true, + }), + ); + }); + + it('should output the manifest json', () => { + expect(manifestFile).toEqual(jasmine.any(Object)); + }); + + it('should output the manifest with right assets (js)', () => { + expect(manifestFile.js).toEqual(['/local.js', + '/jasmine-spec-reporter/index.js', + '/nyc/index.js', + '/jasmine/lib/jasmine.js']); + }); + + it('should output the manifest with right assets (css)', () => { + expect(manifestFile.css).toEqual(['/local.css', '/nyc/style.css', '/jasmine/style.css']); + }); + }); }); });