Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML bundle loader #1732

Merged
merged 3 commits into from Jul 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -616,6 +620,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 @@ -624,24 +634,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 @@ -1140,4 +1140,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