diff --git a/fixtures/js/handlebars.js b/fixtures/js/handlebars.js
new file mode 100644
index 00000000..385cad1c
--- /dev/null
+++ b/fixtures/js/handlebars.js
@@ -0,0 +1,5 @@
+var template = require('../templates/template.hbs');
+
+document.getElementById('app').innerHTML = template({
+ title: 'Welcome to Your Handlebars App'
+});
diff --git a/fixtures/templates/template.hbs b/fixtures/templates/template.hbs
new file mode 100644
index 00000000..0869f8cc
--- /dev/null
+++ b/fixtures/templates/template.hbs
@@ -0,0 +1 @@
+
{{ titleĀ }}
diff --git a/index.js b/index.js
index e345b756..082f2159 100644
--- a/index.js
+++ b/index.js
@@ -719,6 +719,27 @@ class Encore {
return this;
}
+ /**
+ * Call this if you plan on loading Handlebars files.
+ *
+ * Encore.enableHandlebarsLoader();
+ *
+ * Or pass options to the loader
+ *
+ * Encore.enableHandlebarsLoader(function(options) {
+ * // https://github.com/pcardune/handlebars-loader
+ * // options.debug = true;
+ * });
+ *
+ * @param {function} callback
+ * @returns {Encore}
+ */
+ enableHandlebarsLoader(callback = () => {}) {
+ webpackConfig.enableHandlebarsLoader(callback);
+
+ return this;
+ }
+
/**
* Call this if you wish to disable the default
* images loader.
diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js
index 6f505124..8e96d533 100644
--- a/lib/WebpackConfig.js
+++ b/lib/WebpackConfig.js
@@ -65,6 +65,7 @@ class WebpackConfig {
this.useCoffeeScriptLoader = false;
this.useForkedTypeScriptTypeChecking = false;
this.useWebpackNotifier = false;
+ this.useHandlebarsLoader = false;
// Features/Loaders options
this.sassOptions = {
@@ -87,6 +88,7 @@ class WebpackConfig {
this.vueLoaderOptionsCallback = () => {};
this.tsConfigurationCallback = () => {};
this.coffeeScriptConfigurationCallback = () => {};
+ this.handlebarsConfigurationCallback = () => {};
// Plugins options
this.cleanWebpackPluginPaths = ['**/*'];
@@ -446,6 +448,16 @@ class WebpackConfig {
this.notifierPluginOptionsCallback = notifierPluginOptionsCallback;
}
+ enableHandlebarsLoader(callback = () => {}) {
+ this.useHandlebarsLoader = true;
+
+ if (typeof callback !== 'function') {
+ throw new Error('Argument 1 to enableHandlebarsLoader() must be a callback function.');
+ }
+
+ this.handlebarsConfigurationCallback = callback;
+ }
+
disableImagesLoader() {
this.useImagesLoader = false;
}
diff --git a/lib/config-generator.js b/lib/config-generator.js
index f863b7e6..9fbe53a3 100644
--- a/lib/config-generator.js
+++ b/lib/config-generator.js
@@ -21,6 +21,7 @@ const babelLoaderUtil = require('./loaders/babel');
const tsLoaderUtil = require('./loaders/typescript');
const coffeeScriptLoaderUtil = require('./loaders/coffee-script');
const vueLoaderUtil = require('./loaders/vue');
+const handlebarsLoaderUtil = require('./loaders/handlebars');
// plugins utils
const extractTextPluginUtil = require('./plugins/extract-text');
const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries');
@@ -242,6 +243,13 @@ class ConfigGenerator {
});
}
+ if (this.webpackConfig.useHandlebarsLoader) {
+ rules.push({
+ test: /\.(handlebars|hbs)$/,
+ use: handlebarsLoaderUtil.getLoaders(this.webpackConfig)
+ });
+ }
+
this.webpackConfig.loaders.forEach((loader) => {
rules.push(loader);
});
diff --git a/lib/features.js b/lib/features.js
index 5eb92b4c..99a25711 100644
--- a/lib/features.js
+++ b/lib/features.js
@@ -77,6 +77,11 @@ const features = {
method: 'configureUrlLoader()',
packages: ['url-loader'],
description: 'use the url-loader'
+ },
+ handlebars: {
+ method: 'enableHandlebarsLoader()',
+ packages: ['handlebars', 'handlebars-loader'],
+ description: 'load Handlebars files'
}
};
diff --git a/lib/loaders/handlebars.js b/lib/loaders/handlebars.js
new file mode 100644
index 00000000..bc06e036
--- /dev/null
+++ b/lib/loaders/handlebars.js
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the Symfony Webpack Encore package.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+'use strict';
+
+const loaderFeatures = require('../features');
+const applyOptionsCallback = require('../utils/apply-options-callback');
+
+/**
+ * @param {WebpackConfig} webpackConfig
+ * @return {Array} of loaders to use for Handlebars
+ */
+module.exports = {
+ getLoaders(webpackConfig) {
+ loaderFeatures.ensurePackagesExist('handlebars');
+
+ const options = {};
+
+ return [
+ {
+ loader: 'handlebars-loader',
+ options: applyOptionsCallback(webpackConfig.handlebarsConfigurationCallback, options)
+ }
+ ];
+ }
+};
diff --git a/package.json b/package.json
index d6f8ba75..da31a87a 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,8 @@
"eslint-plugin-header": "^1.0.0",
"eslint-plugin-node": "^4.2.2",
"fork-ts-checker-webpack-plugin": "^0.2.7",
+ "handlebars": "^4.0.11",
+ "handlebars-loader": "^1.7.0",
"http-server": "^0.9.0",
"less": "^2.7.2",
"less-loader": "^4.0.2",
diff --git a/test/WebpackConfig.js b/test/WebpackConfig.js
index 6d57eefb..3203ce99 100644
--- a/test/WebpackConfig.js
+++ b/test/WebpackConfig.js
@@ -685,6 +685,28 @@ describe('WebpackConfig object', () => {
});
});
+ describe('enableHandlebarsLoader', () => {
+
+ it('Call with no config', () => {
+ const config = createConfig();
+ config.enableHandlebarsLoader();
+
+ expect(config.useHandlebarsLoader).to.be.true;
+ });
+
+ it('Pass config', () => {
+ const config = createConfig();
+ const callback = (options) => {
+ options.debug = true;
+ };
+ config.enableHandlebarsLoader(callback);
+
+ expect(config.useHandlebarsLoader).to.be.true;
+ expect(config.handlebarsConfigurationCallback).to.equal(callback);
+ });
+
+ });
+
describe('addPlugin', () => {
it('extends the current registered plugins', () => {
const config = createConfig();
diff --git a/test/config-generator.js b/test/config-generator.js
index 19db6ee8..a406302d 100644
--- a/test/config-generator.js
+++ b/test/config-generator.js
@@ -354,6 +354,32 @@ describe('The config-generator function', () => {
});
});
+ describe('enableHandlebarsLoader() adds the handlebars-loader', () => {
+
+ it('without enableHandlebarsLoader()', () => {
+ const config = createConfig();
+ config.outputPath = '/tmp/output/public-path';
+ config.publicPath = '/public-path';
+ config.addEntry('main', './main');
+ const actualConfig = configGenerator(config);
+
+ expect(JSON.stringify(actualConfig.module.rules)).to.not.contain('handlebars-loader');
+ });
+
+ it('enableHandlebarsLoader()', () => {
+ const config = createConfig();
+ config.outputPath = '/tmp/output/public-path';
+ config.publicPath = '/public-path';
+ config.addEntry('main', './main');
+ config.enableHandlebarsLoader();
+
+ const actualConfig = configGenerator(config);
+
+ expect(JSON.stringify(actualConfig.module.rules)).to.contain('handlebars-loader');
+ });
+
+ });
+
describe('addLoader() adds a custom loader', () => {
it('addLoader()', () => {
const config = createConfig();
diff --git a/test/functional.js b/test/functional.js
index 63a504c8..eb3f6db6 100644
--- a/test/functional.js
+++ b/test/functional.js
@@ -862,6 +862,32 @@ module.exports = {
});
});
+ it('When configured, Handlebars is compiled', (done) => {
+ const config = createWebpackConfig('www/build', 'dev');
+ config.setPublicPath('/build');
+ config.addEntry('main', ['./js/handlebars.js']);
+ const testCallback = () => {};
+ config.enableHandlebarsLoader(testCallback);
+
+ testSetup.runWebpack(config, () => {
+ expect(config.outputPath).to.be.a.directory().with.deep.files([
+ 'main.js',
+ 'manifest.json'
+ ]);
+
+ testSetup.requestTestPage(
+ path.join(config.getContext(), 'www'),
+ [
+ 'build/main.js'
+ ],
+ (browser) => {
+ browser.assert.text('#app h1', 'Welcome to Your Handlebars App');
+ done();
+ }
+ );
+ });
+ });
+
it('The output directory is cleaned between builds', (done) => {
const config = createWebpackConfig('www/build', 'dev');
config.setPublicPath('/build');
diff --git a/test/index.js b/test/index.js
index aef7e23b..0a7c0e7b 100644
--- a/test/index.js
+++ b/test/index.js
@@ -260,6 +260,15 @@ describe('Public API', () => {
});
+ describe('enableHandlebarsLoader', () => {
+
+ it('must return the API object', () => {
+ const returnedValue = api.enableHandlebarsLoader();
+ expect(returnedValue).to.equal(api);
+ });
+
+ });
+
describe('disableImagesLoader', () => {
it('must return the API object', () => {
diff --git a/test/loaders/handlebars.js b/test/loaders/handlebars.js
new file mode 100644
index 00000000..09ddb5d4
--- /dev/null
+++ b/test/loaders/handlebars.js
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the Symfony Webpack Encore package.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+'use strict';
+
+const expect = require('chai').expect;
+const WebpackConfig = require('../../lib/WebpackConfig');
+const RuntimeConfig = require('../../lib/config/RuntimeConfig');
+const handlebarsLoader = require('../../lib/loaders/handlebars');
+
+function createConfig() {
+ const runtimeConfig = new RuntimeConfig();
+ runtimeConfig.context = __dirname;
+ runtimeConfig.babelRcFileExists = false;
+
+ return new WebpackConfig(runtimeConfig);
+}
+
+describe('loaders/handlebars', () => {
+ it('getLoaders() basic usage', () => {
+ const config = createConfig();
+ config.enableHandlebarsLoader();
+
+ const actualLoaders = handlebarsLoader.getLoaders(config);
+ expect(actualLoaders).to.have.lengthOf(1);
+ expect(actualLoaders[0].options).to.be.empty;
+ });
+
+ it('getLoaders() with options callback', () => {
+ const config = createConfig();
+ config.enableHandlebarsLoader((options) => {
+ options.debug = true;
+ });
+
+ const actualLoaders = handlebarsLoader.getLoaders(config);
+ expect(actualLoaders).to.have.lengthOf(1);
+ expect(actualLoaders[0].options.debug).to.be.true;
+ });
+
+ it('getLoaders() with options callback that returns an object', () => {
+ const config = createConfig();
+ config.enableHandlebarsLoader((options) => {
+ options.debug = true;
+
+ // This should override the original config
+ return { foo: true };
+ });
+
+ const actualLoaders = handlebarsLoader.getLoaders(config);
+ expect(actualLoaders).to.have.lengthOf(1);
+ expect(actualLoaders[0].options).to.deep.equal({ foo: true });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 160e6e79..7860aee3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -308,7 +308,7 @@ async@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.0.tgz#ac3613b1da9bed1b47510bb4651b8931e47146c7"
-async@^1.5.2:
+async@^1.4.0, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -318,6 +318,10 @@ async@^2.1.2, async@^2.4.1:
dependencies:
lodash "^4.14.0"
+async@~0.2.10:
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2508,7 +2512,7 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
-fastparse@^1.1.1:
+fastparse@^1.0.0, fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
@@ -2930,6 +2934,25 @@ handle-thing@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
+handlebars-loader@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/handlebars-loader/-/handlebars-loader-1.7.0.tgz#4f750bc62c350fb922e52d8564d667887e909723"
+ dependencies:
+ async "~0.2.10"
+ fastparse "^1.0.0"
+ loader-utils "1.0.x"
+ object-assign "^4.1.0"
+
+handlebars@^4.0.11:
+ version "4.0.11"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
+ dependencies:
+ async "^1.4.0"
+ optimist "^0.6.1"
+ source-map "^0.4.4"
+ optionalDependencies:
+ uglify-js "^2.6"
+
har-schema@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
@@ -3833,6 +3856,14 @@ loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
+loader-utils@1.0.x:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.0.4.tgz#13f56197f1523a305891248b4c7244540848426c"
+ dependencies:
+ big.js "^3.1.3"
+ emojis-list "^2.0.0"
+ json5 "^0.5.0"
+
loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
@@ -4576,7 +4607,7 @@ opn@^5.1.0:
dependencies:
is-wsl "^1.1.0"
-optimist@0.6.x:
+optimist@0.6.x, optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
dependencies:
@@ -6037,7 +6068,7 @@ source-map@0.1.x, source-map@^0.1.38:
dependencies:
amdefine ">=0.0.4"
-source-map@^0.4.2:
+source-map@^0.4.2, source-map@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
dependencies:
@@ -6499,7 +6530,7 @@ ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
-uglify-js@^2.8.29:
+uglify-js@^2.6, uglify-js@^2.8.29:
version "2.8.29"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
dependencies: