Skip to content

Commit

Permalink
HTML bundle loader (#1732)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Jul 16, 2018
1 parent 5355685 commit 075ca6f
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 9 deletions.
25 changes: 17 additions & 8 deletions src/Bundler.js
Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions 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();
});
};
17 changes: 17 additions & 0 deletions 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);
});
}
});
});
};
5 changes: 5 additions & 0 deletions src/packagers/HTMLPackager.js
Expand Up @@ -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 || '';

Expand Down
4 changes: 4 additions & 0 deletions src/packagers/Packager.js
Expand Up @@ -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)) {
Expand Down
5 changes: 5 additions & 0 deletions src/packagers/RawPackager.js
Expand Up @@ -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() {}
Expand Down
Binary file added test/integration/import-html-async/100x100.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions test/integration/import-html-async/index.css
@@ -0,0 +1,3 @@
body {
background: red;
}
1 change: 1 addition & 0 deletions test/integration/import-html-async/index.js
@@ -0,0 +1 @@
module.exports = import('./other.html');
10 changes: 10 additions & 0 deletions test/integration/import-html-async/other.html
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<h1>Other page</h1>
<img src="100x100.png" />
</body>
</html>
Binary file added test/integration/import-html-sync/100x100.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions test/integration/import-html-sync/index.css
@@ -0,0 +1,3 @@
body {
background: red;
}
1 change: 1 addition & 0 deletions test/integration/import-html-sync/index.js
@@ -0,0 +1 @@
output(require('./other.html'));
10 changes: 10 additions & 0 deletions test/integration/import-html-sync/other.html
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<h1>Other page</h1>
<img src="100x100.png" />
</body>
</html>
126 changes: 125 additions & 1 deletion 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() {
Expand Down Expand Up @@ -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('<html>'));
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('<html>'));
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('<html>'));
assert(output.includes('Other page'));
});
});
5 changes: 5 additions & 0 deletions test/utils.js
Expand Up @@ -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')
);
}
});
}
Expand Down

0 comments on commit 075ca6f

Please sign in to comment.