diff --git a/packages/core/parcel-bundler/src/Bundler.js b/packages/core/parcel-bundler/src/Bundler.js index 8850c10aeaf..dd7d17312a8 100644 --- a/packages/core/parcel-bundler/src/Bundler.js +++ b/packages/core/parcel-bundler/src/Bundler.js @@ -51,6 +51,10 @@ class Bundler extends EventEmitter { browser: require.resolve('./builtins/loaders/browser/js-loader'), node: require.resolve('./builtins/loaders/node/js-loader') }); + this.addBundleLoader('html', { + browser: require.resolve('./builtins/loaders/browser/html-loader'), + node: require.resolve('./builtins/loaders/node/html-loader') + }); this.pending = false; this.loadedAssets = new Map(); @@ -620,6 +624,12 @@ class Bundler extends EventEmitter { let isEntryAsset = asset.parentBundle && asset.parentBundle.entryAsset === asset; + // If the asset generated a representation for the parent bundle type, and this + // is not an async import, add it to the current bundle + if (bundle.type && asset.generated[bundle.type] != null && !dep.dynamic) { + bundle.addAsset(asset); + } + if ((dep && dep.dynamic) || !bundle.type) { // If the asset is already the entry asset of a bundle, don't create a duplicate. if (isEntryAsset) { @@ -628,24 +638,23 @@ class Bundler extends EventEmitter { // Create a new bundle for dynamic imports bundle = bundle.createChildBundle(asset, dep); - } else if (asset.type && !this.packagers.has(asset.type)) { + } else if ( + asset.type && + !this.packagers.get(asset.type).shouldAddAsset(bundle, asset) + ) { // If the asset is already the entry asset of a bundle, don't create a duplicate. if (isEntryAsset) { return; } - // No packager is available for this asset type. Create a new bundle with only this asset. - bundle.createSiblingBundle(asset); + // No packager is available for this asset type, or the packager doesn't support + // combining this asset into the bundle. Create a new bundle with only this asset. + bundle = bundle.createSiblingBundle(asset, dep); } else { // Add the asset to the common bundle of the asset's type bundle.getSiblingBundle(asset.type).addAsset(asset); } - // If the asset generated a representation for the parent bundle type, also add it there - if (asset.generated[bundle.type] != null) { - bundle.addAsset(asset); - } - // Add the asset to sibling bundles for each generated type if (asset.type && asset.generated[asset.type]) { for (let t in asset.generated) { diff --git a/packages/core/parcel-bundler/src/builtins/loaders/browser/html-loader.js b/packages/core/parcel-bundler/src/builtins/loaders/browser/html-loader.js new file mode 100644 index 00000000000..36a71480421 --- /dev/null +++ b/packages/core/parcel-bundler/src/builtins/loaders/browser/html-loader.js @@ -0,0 +1,5 @@ +module.exports = function loadHTMLBundle(bundle) { + return fetch(bundle).then(function (res) { + return res.text(); + }); +}; diff --git a/packages/core/parcel-bundler/src/builtins/loaders/node/html-loader.js b/packages/core/parcel-bundler/src/builtins/loaders/node/html-loader.js new file mode 100644 index 00000000000..64f6f958be6 --- /dev/null +++ b/packages/core/parcel-bundler/src/builtins/loaders/node/html-loader.js @@ -0,0 +1,17 @@ +var fs = require('fs'); + +module.exports = function loadHTMLBundle(bundle) { + return new Promise(function(resolve, reject) { + fs.readFile(__dirname + bundle, 'utf8', function(err, data) { + if (err) { + reject(err); + } else { + // wait for the next event loop iteration, so we are sure + // the current module is fully loaded + setImmediate(function() { + resolve(data); + }); + } + }); + }); +}; diff --git a/packages/core/parcel-bundler/src/packagers/HTMLPackager.js b/packages/core/parcel-bundler/src/packagers/HTMLPackager.js index 498ba7dda9d..fdd5364e505 100644 --- a/packages/core/parcel-bundler/src/packagers/HTMLPackager.js +++ b/packages/core/parcel-bundler/src/packagers/HTMLPackager.js @@ -16,6 +16,11 @@ const metadataContent = new Set([ ]); class HTMLPackager extends Packager { + static shouldAddAsset() { + // We cannot combine multiple HTML files together - they should be written as separate bundles. + return false; + } + async addAsset(asset) { let html = asset.generated.html || ''; diff --git a/packages/core/parcel-bundler/src/packagers/Packager.js b/packages/core/parcel-bundler/src/packagers/Packager.js index a9aa82d8bd8..c60dfb7fd4a 100644 --- a/packages/core/parcel-bundler/src/packagers/Packager.js +++ b/packages/core/parcel-bundler/src/packagers/Packager.js @@ -10,6 +10,10 @@ class Packager { this.options = bundler.options; } + static shouldAddAsset() { + return true; + } + async setup() { // Create sub-directories if needed if (this.bundle.name.includes(path.sep)) { diff --git a/packages/core/parcel-bundler/src/packagers/RawPackager.js b/packages/core/parcel-bundler/src/packagers/RawPackager.js index cc40ff971db..06677f38b04 100644 --- a/packages/core/parcel-bundler/src/packagers/RawPackager.js +++ b/packages/core/parcel-bundler/src/packagers/RawPackager.js @@ -3,6 +3,11 @@ const path = require('path'); const fs = require('../utils/fs'); class RawPackager extends Packager { + static shouldAddAsset() { + // We cannot combine multiple raw assets together - they should be written as separate bundles. + return false; + } + // Override so we don't create a file for this bundle. // Each asset will be emitted as a separate file instead. setup() {} diff --git a/packages/core/parcel-bundler/test/integration/import-html-async/100x100.png b/packages/core/parcel-bundler/test/integration/import-html-async/100x100.png new file mode 100644 index 00000000000..8a1daa0121d Binary files /dev/null and b/packages/core/parcel-bundler/test/integration/import-html-async/100x100.png differ diff --git a/packages/core/parcel-bundler/test/integration/import-html-async/index.css b/packages/core/parcel-bundler/test/integration/import-html-async/index.css new file mode 100644 index 00000000000..67ce83e4d09 --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-async/index.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/packages/core/parcel-bundler/test/integration/import-html-async/index.js b/packages/core/parcel-bundler/test/integration/import-html-async/index.js new file mode 100644 index 00000000000..7235f4c3023 --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-async/index.js @@ -0,0 +1 @@ +module.exports = import('./other.html'); diff --git a/packages/core/parcel-bundler/test/integration/import-html-async/other.html b/packages/core/parcel-bundler/test/integration/import-html-async/other.html new file mode 100644 index 00000000000..36d14dc9d2f --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-async/other.html @@ -0,0 +1,10 @@ + + + + + + +

Other page

+ + + diff --git a/packages/core/parcel-bundler/test/integration/import-html-sync/100x100.png b/packages/core/parcel-bundler/test/integration/import-html-sync/100x100.png new file mode 100644 index 00000000000..8a1daa0121d Binary files /dev/null and b/packages/core/parcel-bundler/test/integration/import-html-sync/100x100.png differ diff --git a/packages/core/parcel-bundler/test/integration/import-html-sync/index.css b/packages/core/parcel-bundler/test/integration/import-html-sync/index.css new file mode 100644 index 00000000000..67ce83e4d09 --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-sync/index.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/packages/core/parcel-bundler/test/integration/import-html-sync/index.js b/packages/core/parcel-bundler/test/integration/import-html-sync/index.js new file mode 100644 index 00000000000..e01db990d4a --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-sync/index.js @@ -0,0 +1 @@ +output(require('./other.html')); diff --git a/packages/core/parcel-bundler/test/integration/import-html-sync/other.html b/packages/core/parcel-bundler/test/integration/import-html-sync/other.html new file mode 100644 index 00000000000..36d14dc9d2f --- /dev/null +++ b/packages/core/parcel-bundler/test/integration/import-html-sync/other.html @@ -0,0 +1,10 @@ + + + + + + +

Other page

+ + + diff --git a/packages/core/parcel-bundler/test/javascript.js b/packages/core/parcel-bundler/test/javascript.js index 5393296ad02..5a05db1b17b 100644 --- a/packages/core/parcel-bundler/test/javascript.js +++ b/packages/core/parcel-bundler/test/javascript.js @@ -1,7 +1,7 @@ const assert = require('assert'); const fs = require('../src/utils/fs'); const path = require('path'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree, deferred} = require('./utils'); const {mkdirp} = require('../src/utils/fs'); describe('javascript', function() { @@ -1222,4 +1222,128 @@ describe('javascript', function() { let module = await run(b); assert.equal(module.default, 'Hello Hello!'); }); + + it('should support importing HTML from JS async', async function() { + let b = await bundle( + __dirname + '/integration/import-html-async/index.js', + {sourceMaps: false} + ); + + await assertBundleTree(b, { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-loader.js', + 'bundle-url.js', + 'html-loader.js' + ], + childBundles: [ + { + type: 'html', + assets: ['other.html'], + childBundles: [ + { + type: 'png', + assets: ['100x100.png'], + childBundles: [] + }, + { + type: 'css', + assets: ['index.css'], + childBundles: [] + } + ] + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'string'); + assert(output.includes('')); + assert(output.includes('Other page')); + }); + + it('should support importing HTML from JS async with --target=node', async function() { + let b = await bundle( + __dirname + '/integration/import-html-async/index.js', + { + target: 'node', + sourceMaps: false + } + ); + + await assertBundleTree(b, { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-loader.js', + 'bundle-url.js', + 'html-loader.js' + ], + childBundles: [ + { + type: 'html', + assets: ['other.html'], + childBundles: [ + { + type: 'png', + assets: ['100x100.png'], + childBundles: [] + }, + { + type: 'css', + assets: ['index.css'], + childBundles: [] + } + ] + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'string'); + assert(output.includes('')); + assert(output.includes('Other page')); + }); + + it('should support importing HTML from JS sync', async function() { + let b = await bundle(__dirname + '/integration/import-html-sync/index.js', { + sourceMaps: false + }); + + await assertBundleTree(b, { + name: 'index.js', + assets: [ + 'index.js', + 'bundle-loader.js', + 'bundle-url.js', + 'html-loader.js' + ], + childBundles: [ + { + type: 'html', + assets: ['other.html'], + childBundles: [ + { + type: 'png', + assets: ['100x100.png'], + childBundles: [] + }, + { + type: 'css', + assets: ['index.css'], + childBundles: [] + } + ] + } + ] + }); + + let promise = deferred(); + await run(b, {output: promise.resolve}, {require: false}); + let output = await promise; + assert.equal(typeof output, 'string'); + assert(output.includes('')); + assert(output.includes('Other page')); + }); }); diff --git a/packages/core/parcel-bundler/test/utils.js b/packages/core/parcel-bundler/test/utils.js index a0cd065248a..fd65563cc47 100644 --- a/packages/core/parcel-bundler/test/utils.js +++ b/packages/core/parcel-bundler/test/utils.js @@ -106,6 +106,11 @@ function prepareBrowserContext(bundle, globals) { nodeFS.readFileSync(path.join(__dirname, 'dist', url)) ).buffer ); + }, + text() { + return Promise.resolve( + nodeFS.readFileSync(path.join(__dirname, 'dist', url), 'utf8') + ); } }); }